<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      從LLaMA-Factory項目認識微調

      http://www.rzrgm.cn/lm970585581/p/18140564

       

      什么是LLaMA-Factory?

      LLaMA-Factory是一個在github上開源的,專為大模型訓練設計的平臺。項目提供中文說明,可以參考官方文檔:https://github.com/hiyouga/LLaMA-Factory/blob/main/README_zh.md

      為什么要學習LLaMA-Factory?

      大模型技術發展到現在,企業想要真正利用大模型做些事情,一定需要懂得大模型微調的過程。注意,這里說的是過程,而不是原理,專業技術人員才需要懂原理,普通用戶只要懂過程就可以完成對大模型的微調。
      對于有微調大模型需求,卻對大模型微調完全是一個門外漢的用戶來說,通過學習LLaMA-Factory后,可以快速的訓練出自己需要的模型。
      對于想要了解微調大模型技術的技術人員,通過學習LLaMA-Factory后也能快速理解模型微調的相關概念。
      所以,我認為LLaMA-Factory是走向大模型微調的一條捷徑。

      如何學習?

      如果你只想了解如何利用LLaMA-Factory進行模型的微調,直接通過官方文檔即可完成。無需閱讀本文。
      如果你想對大模型微調技術本身感興趣,想要深入了解,可以繼續閱讀本專欄,筆者將通過閱讀源碼的方式,對大模型微調技術進行深入剖析,看到哪里,遇到不懂的概念再去理解,最終展現出大模型訓練的全貌。
      理解了微調技術后,再通過使用LLaMA-Factory進行模型的微調實踐,即可掌握大模型微調技術。

      基礎知識

      閱讀源碼之前,我們需要對模型微調相關概念有一定的認識,來協助我們理解源碼。

      模型訓練階段

      在理解模型微調概念之前,我們先來理解大模型訓練階段有哪些。

      Pre-Training

      Pre-Training:預訓練階段。
      這個階段是用來訓練基礎模型的,是最消耗算力的階段,也是大模型誕生的起始階段。

      Supervised Finetuning(SFT)

      sft:指令微調/監督微調階段
      和預訓練階段相比,這個階段最大的變化就是訓練數據由"量多質低"變為"量少質高",訓練數據主要由人工進行篩選或生成。這個階段完成后其實已經能獲得一個可以上線的大模型了

      RLHF

      RLHF:基于人類反饋的強化學習(Rainforcement Learning from Human Feedback,RLHF)
      可以分成兩個環節

      獎勵建模階段(Reward Modeling)

      在這一階段,模型學習和輸出的內容發生了根本性的改變。前面的兩個階段,預訓練和微調,模型的輸出是符合預期的文本內容;獎勵建模階段的輸出不僅包含預測內容,還包含獎勵值或者說評分值,數值越高,意味著模型的預測結果越好。這個階段輸出的評分,并不是給最終的用戶,而是在強化學習階段發揮重大作用。

      強化學習階段(Reinforcement Learning)

      這個階段非常“聰明”的整合了前面的成果:

      • 針對特定的輸入文本,通過 SFT 模型獲得多個輸出文本。
      • 基于 RM 模型對多個輸出文本的質量進行打分,這個打分實際上已經符合人類的期望了。
      • 基于這個打分,為多個輸出文本結果加入權重。這個權重其實會體現在每個輸出 Token 中。
      • 將加權結果反向傳播,對 SFT 模型參數進行調整,就是所謂的強化學習。

      常見的強化學習策略包括PPODPO,它們的細節我們不去研究,只要知道DPO主要用于分布式訓練,適合大規模并行處理的場景,PPO通常指的是單機上的算法就可以了。

      模型訓練模式

      了解了模型訓練階段后,現在有個問題,我們應該在哪個階段進行微調訓練呢?
      通常會有以下訓練模式進行選擇,根據領域任務、領域樣本情況、業務的需求我們可以選擇合適的訓練模式。
      模式一:基于base模型+領域任務的SFT;
      模式二:基于base模型+領域數據 continue pre-train +領域任務SFT;
      模式三:基于base模型+領域數據 continue pre-train +通用任務SFT+領域任務SFT;
      模式四:基于base模型+領域數據 continue pre-train +通用任務與領域任務混合SFT;
      模式五:基于base模型+領域數據 continue pre-train(混入SFT數據+通用任務與領域任務混合SFT;
      模式六:基于chat模型+領域任務SFT;
      模式七:基于chat模型+領域數據 continue pre-train +領域任務SFT

      是否需要continue pre-train

      大模型的知識來自于pre-train階段,如果你的領域任務數據集與pre-train的數據集差異較大,比如你的領域任務數據來自公司內部,pre-train訓練樣本基本不可能覆蓋到,那一定要進行continue pre-train。
      如果你的領域任務數據量較大(token在1B以上),并只追求領域任務的效果,不考慮通用能力,建議進行continue pre-train。

      是選擇chat模型 還是base模型

      如果你有一個好的base模型,在base模型基礎進行領域數據的SFT與在chat模型上進行SFT,效果上差異不大。
      基于chat模型進行領域SFT,會很容導致災難性遺忘,在進行領域任務SFT之后,模型通用能力會降低,如只追求領域任務的效果,則不用考慮。
      如果你的領域任務與通用任務有很大的相關性,那這種二階段SFT會提升你的領域任務的效果。
      如果你既追求領域任務的效果,并且希望通用能力不下降,建議選擇base模型作為基座模型。在base模型上進行多任務混合訓練,混合訓練的時候需要關注各任務間的數據配比。

      其他經驗

      • 在資源允許的情況下,如只考慮領域任務效果,我會選擇模式二;
      • 在資源允許的情況下,如考慮模型綜合能力,我會選擇模式五;
      • 在資源不允許的情況下,我會考慮模式六;
      • 一般情況下,我們不用進行RLHF微調;

      開發工具庫Transformers

      Transformers是Hugging Face提供的Python庫,Hugging Face是什么這里就不介紹了。國內可以通過鏡像站訪問:https://hf-mirror.com/
      Transformers庫的文檔地址:https://hf-mirror.com/docs/transformers/index
      我們要關注那些內容呢?下邊將會列舉一些關鍵內容,詳細內容請查閱官方文檔。

      Pipeline

      Pipeline是一個用于模型推理的工具,它與模型訓練關系不大,它主要是將預訓練好的模型加載,推理預測使用的,我們了解它是什么即可。

      AutoClass

      AutoClass是一個比較重要的角色,主要是用來加載預訓練模型的,通過from_pretrained()方法可以加載任意Hugging Face中的預訓練模型和本地模型。

      AutoTokenizer

      幾乎所有的NLP任務都以tokenizer開始,用它來加載模型對應的分詞器。

      AutoModel

      真正來加載模型實例的是AutoModel,不同任務使用的AutoModel也不同,針對大語言模型一般使用AutoModelForCausalLM。

      模型量化

      量化技術專注于用較少的信息表示數據,同時盡量不損失太多準確性。
      Transformers支持三種量化方法:AWQ、GPTQ、 BNB。底層細節我們不必研究
      GPTQ是專為GPT模型設計的,AWQ適用于多種模型和任務,包括多模態語言模型。
      BNB是將模型量化為8位和4位的最簡單選擇,4位量化可以與QLoRA一起用于微調量化LLM。

      PEFT庫

      PEFT是Hugging Face提供的庫,是一個為大型預訓練模型提供多種高效微調方法的python庫。
      PEFT文檔地址:https://hf-mirror.com/docs/peft/index
      PEFT可以輕松與Transformers庫集成,一起完成模型微調的工作。
      微調方式包括LoRA、AdaLoRA、P-tuning等。
      補充說明:QLoRA是量化LoRA的縮寫,需要把模型量化再進行訓練,細節暫不研究。

      LLaMA-Factory源碼分析

      從pt預訓練開始

      首先從分析pt預訓練過程開始研究。
      根據官方文檔可知,預訓練執行命令如下:

      CUDA_VISIBLE_DEVICES=0 python src/train_bash.py \
          --stage pt \
          --do_train \
          --model_name_or_path path_to_llama_model \
          --dataset wiki_demo \
          --finetuning_type lora \
          --lora_target q_proj,v_proj \
          --output_dir path_to_pt_checkpoint \
          --overwrite_cache \
          --per_device_train_batch_size 4 \
          --gradient_accumulation_steps 4 \
          --lr_scheduler_type cosine \
          --logging_steps 10 \
          --save_steps 1000 \
          --learning_rate 5e-5 \
          --num_train_epochs 3.0 \
          --plot_loss \
          --fp16
      
      參數說明
      其中很多訓練參數可能會看不懂,但沒關系,先整體有個印象就行。
      

      --stage pt:指定訓練階段為預訓練
      --do_train:指定是訓練任務
      --model_name_or_path:本地模型的文件路徑或 Hugging Face 的模型標識符
      --dataset:指定數據集
      --finetuning_type lora:指定微調方法為lora
      --lora_target q_proj,v_proj:Lora作用模塊為q_proj,v_proj 此參數后續詳解
      --output_dir: 保存訓練結果的路徑
      --overwrite_cache: 覆蓋緩存的訓練集和評估集
      --per_device_train_batch_size 4: 每個gpu的批處理大小,訓練參數
      --gradient_accumulation_steps 4:梯度累計的步數,訓練參數
      --lr_scheduler_type cosine:學習率調度器,訓練參數
      --logging_steps 10:每兩次日志輸出間的更新步數,訓練參數
      --save_steps 1000:每兩次斷點保存間的更新步數,訓練參數
      --learning_rate 5e-5:學習率,adamW優化器的默認值為5e-5,訓練參數
      --num_train_epochs 3.0:需要執行的訓練輪數,訓練參數
      --plot_loss:是否保存訓練損失曲線
      --fp16:使用fp16混合精度訓練,此參數后續詳解

      lora_target

      lora_target被設置到LoraConfig中的target_modules參數中,LoraConfig是PEFT庫中提供的。
      文檔地址:https://hf-mirror.com/docs/peft/v0.9.0/en/package_reference/lora#peft.LoraConfig
      LLaMA-Factory框架中通過lora_target進行了封裝,說明如下:

          lora_target: str = field(
              default="all",
              metadata={
                  "help": """Name(s) of target modules to apply LoRA. \
                          Use commas to separate multiple modules. \
                          Use "all" to specify all the linear modules. \
                          LLaMA choices: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], \
                          BLOOM & Falcon & ChatGLM choices: ["query_key_value", "dense", "dense_h_to_4h", "dense_4h_to_h"], \
                          Baichuan choices: ["W_pack", "o_proj", "gate_proj", "up_proj", "down_proj"], \
                          Qwen choices: ["c_attn", "attn.c_proj", "w1", "w2", "mlp.c_proj"], \
                          InternLM2 choices: ["wqkv", "wo", "w1", "w2", "w3"], \
                          Others choices: the same as LLaMA."""
              },
      

      目前細節我們還無法理解,但可以通過以上說明進行對應的設置。
      注意:經調試結果觀察,Qwen1.5的lora_target與LLaMA choices相同。

      混合精度訓練

      在深度學習中,混合精度訓練是一種利用半精度浮點數(16位)和單精度浮點數(32位)混合計算的訓練技術。傳統上,神經網絡訓練過程中使用的是單精度浮點數,這需要更多的內存和計算資源。而混合精度訓練通過將一部分計算過程轉換為半精度浮點數來減少內存占用和加快計算速度。
      FP16(半精度浮點數):FP16通常用于混合精度訓練,其中大多數計算操作使用FP16來減少內存占用和加快計算速度。
      BF16(bfloat16):BF16提供了更大的動態范圍和更好的數值精度,相比于FP16更適合于保持梯度更新的穩定性。
      FP32(單精度浮點數):傳統神經網絡訓練中使用FP32來表示參數、梯度等數值,但相對于FP16和BF16,它需要更多的內存和計算資源。
      Pure BF16(純 bfloat16):這個術語通常用于強調使用純粹的BF16格式,而不是在混合精度環境中與其他精度混合使用。使用純BF16意味著所有的計算和存儲都使用BF16格式。

      理解源碼

      請自行配合源碼閱讀以下內容,文中不會粘貼完整源碼。

      源碼入口

      根據運行命令,我們可以從src/train_bash.py部分看起。
      會進入src/llmtuner/train/pt/workflow.py中的run_pt方法中,

      run_pt函數主要實現了以下功能:
      加載tokenizer和dataset:根據傳入的參數,使用load_tokenizer函數加載tokenizer,然后使用get_dataset函數根據tokenizer、model_args、data_args和training_args獲取dataset。
      加載模型:使用load_model函數根據tokenizer、model_args、finetuning_args和training_args的do_train參數加載模型。
      初始化Trainer:使用CustomTrainer初始化一個Trainer實例,傳入模型、參數、tokenizer、data_collator、callbacks和其他額外的參數。data_collator是通過調用DataCollatorForLanguageModeling類的實例化來創建一個數據整理器,主要用于自然語言處理任務中,將原始文本數據轉換為模型可以輸入的格式。
      訓練模型:如果training_args.do_train為True,則調用trainer的train方法進行訓練,根據需要恢復checkpoint。訓練完成后,保存模型、日志和狀態。
      評估模型:如果training_args.do_eval為True,則調用trainer的evaluate方法進行評估,并計算perplexity。然后記錄和保存評估指標。
      創建模型卡片:使用create_modelcard_and_push函數創建并推送模型卡片,其中包含了模型的相關信息和訓練、評估結果。

      開頭的代碼如下:

          # 獲取分詞器
          tokenizer = load_tokenizer(model_args)
          # 獲取數據集
          dataset = get_dataset(tokenizer, model_args, data_args, training_args, stage="pt")
          # 獲取模型實例
          model = load_model(tokenizer, model_args, finetuning_args, training_args.do_train)
      

      接下來我們將分析一下以上三部分的代碼實現。

      load_tokenizer

      先來分析load_tokenizer方法:

      def load_tokenizer(model_args: "ModelArguments") -> "PreTrainedTokenizer":
          r"""
          Loads pretrained tokenizer. Must before load_model.
      
          Note: including inplace operation of model_args.
          """
          try_download_model_from_ms(model_args)
          init_kwargs = _get_init_kwargs(model_args)
          # 核心方法在這,加載分詞器內容,具體參數含義先忽略
          tokenizer = AutoTokenizer.from_pretrained(
              model_args.model_name_or_path,
              use_fast=model_args.use_fast_tokenizer,
              split_special_tokens=model_args.split_special_tokens,
              padding_side="right",
              **init_kwargs,
          )
          patch_tokenizer(tokenizer)
          return tokenizer
      

      get_dataset

      比較核心的方法其實是get_dataset,因為要訓練模型,最重要的部分是組織訓練數據。
      函數主要執行以下操作:

      1. 根據提供的tokenizer、model_args、data_args和training_args參數,獲取數據集的模板并進行一些預處理。
      2. 檢查是否需要從緩存路徑加載數據集,如果是,則加載并返回數據集。
      3. 如果緩存路徑不存在,則根據data_args參數獲取數據集列表,并加載每個數據集。
      4. 將所有加載的數據集合并為一個數據集。
      5. 對數據集進行預處理,包括使用tokenizer對數據進行編碼、根據指定的stage進行額外的預處理。
      6. 如果指定的cache_path不為空,則將預處理后的數據集保存到cache_path路徑。
      7. 如果指定should_log參數,則打印數據集的一個樣本

      這里內容比較多,我們一步一步來分析。

      獲取數據集模板

      首先來分析一下獲取數據集模板在做什么。可以進入以下路徑查看代碼。
      src/llmtuner/data/template.py的get_template_and_fix_tokenizer方法。

      函數主要做了以下幾件事情:

      1. 根據輸入的 name,獲取相應的模板,如果沒有提供 name 或提供的 name 不存在,則使用默認模板 "vanilla"。
      2. 如果模板指示需要替換 EOS(End of String)標記,且模板還指定了停用詞,則取出第一個停用詞在分詞器中替換 EOS 標記。,并將剩余的停用詞保存起來。
      3. 如果分詞器中沒有定義 EOS 標記,則在分詞器中添加一個空EOS 標記"<|endoftext|>"。
      4. 如果分詞器中沒有定義 PAD 標記,則將 PAD 標記設置為與 EOS 標記相同的值,并在日志中記錄。
      5. 如果模板指定了停用詞,并且有剩余的停用詞,則將這些停用詞添加到分詞器的特殊標記中,并在日志中記錄。如果成功添加了新的特殊標記,則會發出警告提醒用戶確認是否需要調整詞匯表大小。
      6. 嘗試將模板轉換為 Jinja 模板,并將其與分詞器相關聯。

      最后返回選定的模板對象。

      這段代碼內容有點多,我們先不考慮模板的事,先來理解一下分詞器中對應的概念。

      概念理解

      首先我們理解一下什么是分詞器。

      在自然語言處理(NLP)中,分詞器(tokenizer)是一個將文本輸入分割成單詞、子詞或符號序列的工具。這個過程稱為分詞或者標記化。在 NLP 中,文本通常以字符串的形式存在,而計算機需要將其轉換成可以處理的結構化數據形式,例如單詞序列或標記序列,以便進行后續的語言處理任務,如詞嵌入、語言模型訓練、序列標注等。

      那分詞器的EOS 標記, PAD 標記,停用詞分別代表什么呢?

      1. EOS 標記(End of String):
        • EOS 標記通常用于表示序列的結束。在自然語言處理任務中,特別是序列到序列的任務(如機器翻譯、文本生成等),需要在序列的末尾添加 EOS 標記以指示句子的結束。這有助于模型正確處理不同長度的輸入序列。
        • 在分詞器中,EOS 標記用于標記化后的文本中表示句子結束的位置。
      2. PAD 標記(Padding):
        • PAD 標記通常用于對不同長度的序列進行填充,使它們具有相同的長度。這在很多深度學習模型中是必要的,因為它們需要輸入具有固定長度的序列。通過將序列填充到相同的長度,可以方便地將它們組成一個批次進行并行處理,提高訓練效率。
        • 在分詞器中,PAD 標記用于填充序列,使其達到指定的最大長度。
      3. 停用詞:
        • 停用詞是在自然語言處理任務中通常會被忽略的常見詞語,因為它們通常不攜帶太多的信息。在一些任務中,特別是文本生成任務,停用詞可能會影響模型的生成結果,因此需要在預處理階段將其去除。
        • 在分詞器中,停用詞通常被用作特殊的標記,例如 EOS 或 PAD 標記的替代。在一些情況下,停用詞可能還會被添加到詞匯表中作為特殊標記,以便模型學習到如何處理它們。

      至于Jinja模板,這里就不過多介紹了,后邊我們會去查看源碼,看看在做什么的。

      獲取模板

      理解了以上內容,我們回過頭來分析一下最開始的根據name獲取相應模板是怎么做到的,它獲取到的模板到底是什么。
      通過閱讀源碼,我們可以看到,模板就是一個字典:

      templates: Dict[str, Template] = {}
      

      字典中的內容是通過_register_template方法注冊進去的。我們來分析一下_register_template方法.
      方法的入參比較多,我們可以分析各個參數的作用如下:

      • name: 模板的名稱。
      • format_user: 用戶對話部分的格式化器。
      • format_assistant: AI 助手對話部分的格式化器。
      • format_system: 系統對話部分的格式化器。
      • format_function: 函數對話部分的格式化器。
      • format_observation: 觀察部分的格式化器。
      • format_tools: 工具部分的格式化器。
      • format_separator: 分隔符部分的格式化器。
      • default_system: 默認系統消息。
      • stop_words: 停用詞列表。
      • efficient_eos: 是否使用高效 EOS 標記。
      • replace_eos: 是否替換 EOS 標記。
      • force_system: 是否強制使用系統。

      函數的主要工作是根據提供的參數創建對應的格式化器,并使用這些格式化器創建一個新的對話模板(Template 對象或 Llama2Template 對象),然后將該模板注冊到全局變量 templates 中。
      注意,這里的Template 對象或 Llama2Template 對象都是項目自定義的類。類中的內容我們先不看。
      只要知道在初始化時,會調用此方法注冊模板即可。
      以qwen模板為例,在代碼中我們可以看到如下注冊模板的內容:

      _register_template(
          name="qwen",
          format_user=StringFormatter(slots=["<|im_start|>user\n{{content}}<|im_end|>\n<|im_start|>assistant\n"]),
          format_system=StringFormatter(slots=["<|im_start|>system\n{{content}}<|im_end|>\n"]),
          format_separator=EmptyFormatter(slots=["\n"]),
          default_system="You are a helpful assistant.",
          stop_words=["<|im_end|>"],
          replace_eos=True,
      )
      

      根據入參,我們再重新查看_register_template方法的源碼(請自行查看源碼往下看):
      首先,efficient_eos沒有傳參,默認值為False,以至于eos_slots為[],由此可以理解所謂的高效EOS標記,就是可能不需要額外的 EOS 標記,從而節省了內存和計算資源。
      后續就是在實例化Template 對象返回。

      轉換為 Jinja 模板

      接下來我們看一下轉換Jinja模板做了什么。

      該函數 _get_jinja_template 的作用是根據輸入的模板和分詞器生成一個 Jinja2 模板字符串。
      首先,函數會判斷模板中是否設置了默認系統消息,如果有,則將該消息添加到 Jinja2 模板中。
      接著,函數會檢查模板消息列表中是否存在系統消息,并將其內容賦值給變量 system_message。
      然后,根據模板類型和是否強制顯示系統消息,將 system_message 變量添加到 Jinja2 模板中。
      接下來,函數會遍歷模板消息列表,并根據消息的角色(用戶或助手)將相應的內容添加到 Jinja2 模板中。
      最后,函數返回生成的 Jinja2 模板字符串。
      在處理過程中,函數會使用 _convert_slots_to_jinja 函數將模板中的占位符轉換為對應的 Jinja2 表達式,并使用 PreTrainedTokenizer 對模板內容進行分詞處理。

      可以看到,Jinja2模板中支持if else 和 for,不過這些都不重要,我們只要知道組織好模板后將模板賦值給了tokenizer的chat_template屬性即可。

      獲取數據集列表

      接下來就是獲取數據集列表的實現了。主要代碼如下:

          with training_args.main_process_first(desc="load dataset"):
              all_datasets = []
              for dataset_attr in get_dataset_list(data_args):
                  all_datasets.append(load_single_dataset(dataset_attr, model_args, data_args))
              dataset = merge_dataset(all_datasets, data_args, training_args)
      

      這里先來關注get_dataset_list方法。

      該函數用于獲取數據集列表。根據輸入的data_args參數中的dataset字段,將數據集名稱列表進行處理并保存。然后從data_args參數中的dataset_dir目錄下讀取數據集配置文件DATA_CONFIG,并解析其中的內容。實際文件路徑為data/dataset_info.json。
      接下來,根據配置文件中定義的數據集信息,創建并填充DatasetAttr對象,并將其添加到dataset_list列表中。最后,返回dataset_list列表。
      在創建DatasetAttr對象時,根據配置文件中的不同字段,選擇不同的數據集類型和屬性,并設置相應的屬性值。
      如果配置文件中定義了列名,則將其添加到DatasetAttr對象的屬性中。
      如果數據集格式為sharegpt,并且配置文件中定義了標簽信息,則將其添加到DatasetAttr對象的屬性中。
      如果在讀取配置文件時發生異常,將拋出相應的異常。

      現在我們知道get_dataset_list方法會返回數據集的一些元數據,load_single_dataset方法就會根據元數據來加載真正的數據了。

      get_dataset_list根據給定的dataset_attr、model_args和data_args參數加載單個數據集。根據dataset_attr.load_from的值,函數從不同的來源加載數據集。支持的來源包括"Hugging Face Hub"、"ModelScope Hub"、腳本或文件。
      當從"Hugging Face Hub"或"ModelScope Hub"加載數據集時,函數會使用相應的庫加載數據集。
      當從腳本或文件加載數據集時,函數會根據文件類型選擇合適的方式加載數據。
      函數還支持數據集的截斷和對齊操作。

      其中加載數據到內容的代碼如下:

              dataset = load_dataset(
                  path=data_path,
                  name=data_name,
                  data_dir=data_dir,
                  data_files=data_files,
                  split=data_args.split,
                  cache_dir=model_args.cache_dir,
                  token=model_args.hf_hub_token,
                  streaming=(data_args.streaming and (dataset_attr.load_from != "file")),
                  **kwargs,
              )
      

      這部分使用的是Hugging Face的datasets庫來進行加載的。具體使用方法可以參考官網。
      https://hf-mirror.com/docs/datasets/index
      這里就不介紹了。
      后續使用align_dataset對數據集進行轉換后返回單個數據集的結果

      align_dataset函數用于對給定的dataset進行格式轉換,使其符合指定的dataset_attr屬性要求。
      該函數根據dataset_attr的格式要求選擇不同的轉換函數(convert_alpaca或convert_sharegpt),并為轉換后的數據集定義了特定的特征字典(features)。
      轉換函數將對數據集中的每個樣本進行處理,重新組織其字段,并添加額外的"prompt"、"response"、"system"和"tools"字段。
      處理過程中,可以選擇是否使用批處理,并可以指定并行處理的工作線程數、是否從緩存文件加載以及是否覆蓋緩存文件等參數。最終返回轉換后的數據集。

      具體的轉換邏輯我們先不用看了,知道是轉換成一種方便訓練的格式就可以了。
      后續預處理的邏輯我們也先不用看,大體了解會使用tokenizer對數據進行處理就可以了。

      load_model

      數據準備就緒,接下來就是加載模型了。

      函數首先通過model_args.model_name_or_path使用AutoConfig獲取模型的配置和初始化參數,然后根據是否可訓練和是否使用unsloth選擇不同的模型加載方式。
      如果可訓練且使用unsloth,則使用FastLanguageModel.from_pretrained加載模型;
      否則,使用AutoModelForCausalLM.from_pretrained加載模型。
      接著,函數會對模型進行一些修改和注冊,然后根據是否添加值頭(value head)來初始化或修改模型。
      最后,函數將模型設置為相應的模式(可訓練或不可訓練),并返回模型。
      參數說明:
      tokenizer: 預訓練的分詞器。
      model_args: 模型參數,包括模型名稱、最大序列長度、計算數據類型等。
      finetuning_args: 微調參數。
      is_trainable: 模型是否可訓練,默認為False。
      add_valuehead: 是否添加值頭,默認為False。
      返回值:
      model: 加載的預訓練模型。

      其中比較核心的就是對模型進行的一些修改和注冊了。這部分代碼如下:

          patch_model(model, tokenizer, model_args, is_trainable)
          register_autoclass(config, model, tokenizer)
          model = init_adapter(model, model_args, finetuning_args, is_trainable)
      

      接下來會進入內部了解一下具體對模型做了哪些修改。

      概念理解

      這里我們看到了新的概念,unsloth。所以我們先來理解一下新概念。
      通過觀察LLaMA-Factory的可視化頁面中高級設置,可以看到加速方式。加速方式包括flashattn和unsloth,那它們代表什么呢?

      "unsloth" 和 "flashattn" 是兩種不同的加速技術,通常用于優化神經網絡模型的推理速度。

      1. Unsloth: Unsloth 是一種基于量化的加速技術,它的主要思想是通過減少模型參數的精度來降低計算的復雜度,從而提高推理速度。在 Unsloth 中,通常會將模型參數從浮點數轉換為低精度的整數或定點數。這樣可以降低模型的存儲需求,并且在推理時減少了浮點運算的開銷,從而加快了模型的推理速度。不過,由于參數精度的降低可能會帶來一定的精度損失,因此在選擇使用 Unsloth 技術時需要權衡推理速度和模型精度之間的關系。
      2. FlashAttn: FlashAttn 是一種用于加速注意力機制(attention mechanism)的技術。注意力機制在深度學習模型中廣泛應用于處理序列數據,例如機器翻譯、文本生成等任務。然而,由于注意力機制的計算量較大,它可能成為模型推理速度的瓶頸。FlashAttn 通過優化注意力計算的方式來加速模型推理過程。具體來說,FlashAttn 可能采用一些技巧,例如降低注意力矩陣的計算復雜度、減少注意力頭的數量、或者采用特定的注意力結構等。這些技巧可以有效地減少模型推理時的計算量,從而加速推理速度。

      簡單來說,Unsloth 和 FlashAttn 都是用于加速神經網絡模型推理過程的技術,但它們的具體實現和優化方式有所不同,適用于不同類型的模型和應用場景。

      patch_model

      patch_model函數用于根據不同的模型類型和參數,對模型和分詞器進行一系列的修改和配置。
      具體包括以下幾個方面:

      如果模型的generate方法不是GenerationMixin的子類,則將其替換為PreTrainedModel.generate方法。
      如果模型配置中的model_type為"chatglm",則設置模型的lm_head為transformer.output_layer,并設置保存模型時忽略lm_head.weight。(chatglm需要一些特殊處理,我們暫不關心)
      如果model_args.resize_vocab為True,則調用_resize_embedding_layer函數來調整嵌入層的大小。
      如果模型是可訓練的,則調用_prepare_model_for_training函數對模型進行訓練前的準備。
      如果模型配置中的model_type為"mixtral"且啟用了DeepSpeed的Zero3優化器,則導入set_z3_leaf_modules和MixtralSparseMoeBlock,并調用set_z3_leaf_modules函數將model中的葉子模塊設置為MixtralSparseMoeBlock。如果模型是可訓練的,則調用patch_mixtral_replace_moe_impl函數。
      嘗試向模型添加標簽"llama-factory",如果添加失敗則打印警告信息。
      這些修改和配置的目的是為了適應不同模型的需求,提高模型的性能和效率。

      我們如果使用qwen模型,主要需要觀察_prepare_model_for_training函數對模型做了哪些準備。

      該函數主要為模型訓練做準備,具體包括以下操作:
      如果model_args.upcast_layernorm為True,則將模型中的層歸一化(layernorm)權重轉換為float32類型。
      如果model_args.disable_gradient_checkpointing為False且模型支持梯度檢查點(gradient checkpointing),則啟用梯度檢查點,并設置相關屬性。
      如果模型具有output_layer_name屬性且model_args.upcast_lmhead_output為True,則將語言模型頭(lm_head)的輸出轉換為float32類型

      這里的概念可能不是太懂,可以先了解個大概即可。

      register_autoclass

      這個方法的代碼如下:

      def register_autoclass(config: "PretrainedConfig", model: "PreTrainedModel", tokenizer: "PreTrainedTokenizer"):
          if "AutoConfig" in getattr(config, "auto_map", {}):
              config.__class__.register_for_auto_class()
          if "AutoModelForCausalLM" in getattr(config, "auto_map", {}):
              model.__class__.register_for_auto_class()
          if "AutoTokenizer" in tokenizer.init_kwargs.get("auto_map", {}):
              tokenizer.__class__.register_for_auto_class()
      

      就是字面意思,注冊Transformers框架中的自動類,具體用處目前還不明確。

      init_adapter

      init_adapter函數用于初始化適配器,并支持全參數、凍結和LoRA訓練。根據傳入的模型、模型參數、微調參數和是否可訓練,該函數將根據微調類型對模型進行相應的處理。此方法屬于比較核心的方法

      如果模型不可訓練且沒有指定適配器名稱路徑,則加載基本模型。
      如果微調類型為"full"且模型可訓練,則將模型參數轉換為float32類型。
      如果微調類型為"freeze"且模型可訓練,則根據num_layer_trainable和其他參數來確定可訓練的層,并將其他層的參數設置為不可訓練。可訓練的層可以是最后n層、前面n層或指定的層。
      如果微調類型為"lora",則根據是否指定適配器名稱路徑和其他參數來加載、合并和恢復LoRA模型,并創建新的LoRA權重。
      最終,該函數返回處理后的模型

      這部分代碼內容還是比較多的,full和freeze我們不用關注,重點關注lora部分。由于這部分比較重要,我把lora部分代碼放到下邊,并用注釋解釋一下:

          if finetuning_args.finetuning_type == "lora":
              logger.info("Fine-tuning method: {}".format("DoRA" if finetuning_args.use_dora else "LoRA"))
              adapter_to_resume = None
              # 這部分是可以通過adapter_name_or_path路徑,來進行進行增量的訓練,增量邏輯我們可以先不看,代碼沒有放到這里
              if model_args.adapter_name_or_path is not None:...
              # 重點內容在這里
              if is_trainable and adapter_to_resume is None:  # create new lora weights while training
                  if len(finetuning_args.lora_target) == 1 and finetuning_args.lora_target[0] == "all":
                      # 通過調試,可以在這里看到模型所有的lora_target
                      target_modules = find_all_linear_modules(model)
                  else:
                      target_modules = finetuning_args.lora_target
                  # 這里通過可視化頁面,可以看到解釋:僅訓練塊擴展后的參數。細節我們先不看
                  if finetuning_args.use_llama_pro:
                      target_modules = find_expanded_modules(model, target_modules, finetuning_args.num_layer_trainable)
                  # 這里驗證了使用dora的時候,如果使用了量化,必須是使用BNB方式,否則不支持
                  if finetuning_args.use_dora and getattr(model, "quantization_method", None) is not None:
                      if getattr(model, "quantization_method", None) != QuantizationMethod.BITS_AND_BYTES:
                          raise ValueError("DoRA is not compatible with PTQ-quantized models.")
      
                  peft_kwargs = {
                      "r": finetuning_args.lora_rank,
                      "target_modules": target_modules,
                      "lora_alpha": finetuning_args.lora_alpha,
                      "lora_dropout": finetuning_args.lora_dropout,
                      "use_rslora": finetuning_args.use_rslora,
                  }
                  # 這里使用了unsloth加速,在之前的章節中有講到
                  if model_args.use_unsloth:
                      from unsloth import FastLanguageModel  # type: ignore
      
                      unsloth_peft_kwargs = {"model": model, "max_seq_length": model_args.model_max_length}
                      model = FastLanguageModel.get_peft_model(**peft_kwargs, **unsloth_peft_kwargs)
                  else:
                      # 組織LoraConfig
                      lora_config = LoraConfig(
                          task_type=TaskType.CAUSAL_LM,
                          inference_mode=False,
                          modules_to_save=finetuning_args.additional_target,
                          use_dora=finetuning_args.use_dora,
                          **peft_kwargs,
                      )
                      # 加載模型
                      model = get_peft_model(model, lora_config)
              # 這里的pure_bf16在前邊章節頁講過,混合精度訓練的一種模式
              if not finetuning_args.pure_bf16:
                  for param in filter(lambda p: p.requires_grad, model.parameters()):
                      param.data = param.data.to(torch.float32)
      

      通過閱讀以上源碼和注釋,想要更好的理解,需要解決下邊的問題。

      1. lora_target應該怎么設置比較合適?
      2. dora是什么?
      3. LoraConfig應該怎么配置更合適?

      想要解決這些問題,我們應該去了解一下LoraConfig。

      解讀LoraConfig

      首先,LoraConfig屬于PEFT庫。所以可以閱讀一下官方文檔,來理解一下Lora,地址如下:
      https://hf-mirror.com/docs/peft/developer_guides/lora
      通過閱讀這部分文檔,我們可以對Lora整體有了一個認識。
      之后我們可以閱讀這部分內容,來理解一下LoraConfig中每個參數的作用。
      https://hf-mirror.com/docs/peft/package_reference/lora
      回到我們自己的代碼中,現在可以解釋一下這部分代碼具體的含義了:

                  peft_kwargs = {
                      "r": finetuning_args.lora_rank,
                      "target_modules": target_modules,
                      "lora_alpha": finetuning_args.lora_alpha,
                      "lora_dropout": finetuning_args.lora_dropout,
                      "use_rslora": finetuning_args.use_rslora,
                  }
                  lora_config = LoraConfig(
                      task_type=TaskType.CAUSAL_LM,
                      inference_mode=False,
                      modules_to_save=finetuning_args.additional_target,
                      use_dora=finetuning_args.use_dora,
                      **peft_kwargs,
                  )
      
      task_type:此參數不是LoraConfig的參數,而是它的父類PeftConfig的參數,可選值為TaskType中的值,具體的含義是什么呢?我們可以直接看源碼:
      
      class TaskType(str, enum.Enum):
          """
          Enum class for the different types of tasks supported by PEFT.
      
          Overview of the supported task types:
          - SEQ_CLS: Text classification.
          - SEQ_2_SEQ_LM: Sequence-to-sequence language modeling.
          - CAUSAL_LM: Causal language modeling.
          - TOKEN_CLS: Token classification.
          - QUESTION_ANS: Question answering.
          - FEATURE_EXTRACTION: Feature extraction. Provides the hidden states which can be used as embeddings or features
            for downstream tasks.
          """
      
          SEQ_CLS = "SEQ_CLS"
          SEQ_2_SEQ_LM = "SEQ_2_SEQ_LM"
          CAUSAL_LM = "CAUSAL_LM"
          TOKEN_CLS = "TOKEN_CLS"
          QUESTION_ANS = "QUESTION_ANS"
          FEATURE_EXTRACTION = "FEATURE_EXTRACTION"
      

      可以看到,其實就是在指定任務類型是大語言模型。
      inference_mode:此參數也是父類PeftConfig的參數,表示模型是否是推理模型,由于我們要進行訓練,所以設置為False
      modules_to_save:除 LoRA 層以外的可訓練模塊名稱,我們先不用管這里。
      use_dora:使用權重分解的 LoRA。
      r:lora微調的維度,我們默認設置的是8。
      target_modules:就是要微調的模塊,前邊已經有介紹。
      lora_alpha:LoRA微調的縮放因子,默認為r * 2。
      lora_dropout: LoRA微調的隨機丟棄率。了解過深度學習的一定可以理解這個指標。
      use_rslora:是否使用LoRA層的秩穩定縮放因子,閱讀官網可以理解為:將適配器的縮放因子設置為 lora_alpha/math.sqrt(r) ,因為它被證明工作得更好。 否則,它將使用原始的默認值 lora_alpha/r 。
      至此,目前我們已經理解了項目中使用到的參數。
      其他內容可根據官方文檔理解。

      模型訓練部分

      前邊的內容只是訓練的前提,接下來我們就來看一下訓練部分的實現。
      我們回到src/llmtuner/train/pt/workflow.py中的run_pt方法中繼續往下看。這里我把核心源碼直接放到下邊。

          # 部分主要是對數據轉換,轉換成模型可以輸入的格式。
          data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
      
          # Initialize our Trainer
          trainer = CustomTrainer(
              model=model,
              args=training_args,
              finetuning_args=finetuning_args,
              tokenizer=tokenizer,
              data_collator=data_collator,
              callbacks=callbacks,
              #就是數據集的拆分(訓練集/測試集)
              **split_dataset(dataset, data_args, training_args), 
          )
      
          # Training
          if training_args.do_train:
              # 開始訓練,resume_from_checkpoint可以是字符串或布爾值,如果為字符串,則是本地保存的檢查點的路徑,如果為布爾值且為True,則加載args.output_dir中由之前的[Trainer]實例保存的最后一個檢查點。如果存在,則從加載的模型/優化器/調度器狀態繼續訓練。
              train_result = trainer.train(resume_from_checkpoint=training_args.resume_from_checkpoint)
              # 保存模型
              trainer.save_model()
              # 記錄指標日志
              trainer.log_metrics("train", train_result.metrics)
              # 保存指標日志
              trainer.save_metrics("train", train_result.metrics)
              # 保存訓練狀態
              trainer.save_state()
              # 如果是主進程,且plot_loss參數為True,則保存損失曲線圖
              if trainer.is_world_process_zero() and finetuning_args.plot_loss:
                  plot_loss(training_args.output_dir, keys=["loss", "eval_loss"])
      

      首先我們先來理解一下DataCollatorForLanguageModeling,可以閱讀官網文檔進行理解,地址如下:
      https://hf-mirror.com/docs/transformers/v4.39.1/zh/main_classes/data_collator
      簡單來說就是轉換成模型可以輸入的格式。
      CustomTrainer是一個比較重要的內容,接下來我們將對它進行解讀。

      解讀CustomTrainer

      CustomTrainer繼承自Trainer類,并且重寫了create_optimizer_and_scheduler方法。

      create_optimizer_and_scheduler方法用于創建自定義的優化器和學習率調度器。它首先調用create_custom_optimzer函數來創建優化器,該函數根據模型、參數和finetuning_args來決定如何創建優化器。如果create_custom_optimzer返回None,則調用Trainer類的create_optimizer方法來創建優化器。

      我們可以看一下create_custom_optimzer的底層實現,代碼內容不多,直接放到下邊:

      def create_custom_optimzer(
          model: "PreTrainedModel",
          training_args: "Seq2SeqTrainingArguments",
          finetuning_args: "FinetuningArguments",
          max_steps: int,
      ) -> Optional["torch.optim.Optimizer"]:
          if finetuning_args.use_galore:
              return _create_galore_optimizer(model, training_args, finetuning_args, max_steps)
      
          if finetuning_args.loraplus_lr_ratio is not None:
              return _create_loraplus_optimizer(model, training_args, finetuning_args)
      

      如何創建自定義優化器我們先不研究,目前我們還用不到。
      要研究的重點其實是Trainer類,Trainer是Transformers庫中比較重要的一部分。可以閱讀官方文檔進行了解:https://hf-mirror.com/docs/transformers/v4.39.1/zh/main_classes/trainer
      接下來我們分析一下代碼中傳入的參數的含義:

      model:這個不用太解釋,就是傳入之前加載的模型
      args=training_args:這個training_args實際上是Transformers庫中的Seq2SeqTrainingArguments,這其中包含的參數就比較多了。
      finetuning_args:這個參數是自定義的參數,我們不關注。
      tokenizer、data_collator:這兩個參數不再解釋,你應該也能看懂了。
      callbacks:指定回調函數,LLaMA-Factory指定了一個打印日志的回調函數。
      split_dataset:這是自定義的函數,用來做數據拆分,實現細節我們先不看,主要就是返回split_dataset參數和eval_dataset參數,分別用來表示訓練的數據集和評估的數據集,是Trainer類自帶的參數。

      至此,對CustomTrainer我們已經有了整體的認識。

      解讀Seq2SeqTrainingArguments

      為了更進一步的了解訓練參數,我們可以看一下Seq2SeqTrainingArguments中都有哪些參數。官方文檔地址如下:
      https://hf-mirror.com/docs/transformers/v4.39.1/en/main_classes/trainer#transformers.Seq2SeqTrainingArguments
      由于參數太多了,就不挨個參數解讀了,只解釋一些我們常用到的參數,其他參數詳見官方文檔:

      output_dir :輸出目錄,將寫入模型預測和檢查點。
      overwrite_output_dir:如果設置為 True,將覆蓋輸出目錄中的內容。可以在 output_dir 指向檢查點目錄時使用此選項繼續訓練。
      do_train :是否進行訓練。這個參數不是直接由 [Trainer] 使用的
      do_eval:是否在驗證集上運行評估。如果 evaluation_strategy 不是 "no" ,將被設置為 True。這個參數不是直接由 [Trainer] 使用的
      do_predict:是否在測試集上進行預測。這個參數不是直接由 [Trainer] 使用的
      evaluation_strategy :訓練過程中采用的評估策略。可能的取值有:
      "no": 不進行評估。
      "steps": 每隔 eval_steps 進行評估。
      "epoch": 每個 epoch 結束時進行評估
      per_device_train_batch_size (int,可選,默認為 8):每個 GPU/TPU 核心/CPU 的訓練批次大小。
      per_device_eval_batch_size (int,可選,默認為 8):每個 GPU/TPU 核心/CPU 的評估批次大小。
      gradient_accumulation_steps (int,可選,默認為 1):在執行反向傳播/更新步驟之前,累積梯度的更新步驟數。

      模型評估部分

      直接看剩余部分的代碼,理解了之前訓練部分的代碼,評估部分代碼很容易就能看懂。

          # Evaluation
          if training_args.do_eval:
              # 評估
              metrics = trainer.evaluate(metric_key_prefix="eval")
              try:
                  # 計算困惑度,困惑度是自然語言處理領域常用的評價模型生成或預測文本的能力的指標,它是損失函數指數運算的結果。越低代表模型越好。
                  perplexity = math.exp(metrics["eval_loss"])
              except OverflowError:
                  perplexity = float("inf")
              
              metrics["perplexity"] = perplexity
              trainer.log_metrics("eval", metrics)
              trainer.save_metrics("eval", metrics)
      

      總結

      至此,我們已經打通了pt預訓練這條通道,接下來我們就要開始查看sft指令微調部分的實現了。

      sft指令微調

      首先我們還是查看官方文檔提供的sft腳本,內容如下:

      CUDA_VISIBLE_DEVICES=0 python src/train_bash.py \
          --stage sft \
          --do_train \
          --model_name_or_path path_to_llama_model \
          --dataset alpaca_gpt4_zh \
          --template default \
          --finetuning_type lora \
          --lora_target q_proj,v_proj \
          --output_dir path_to_sft_checkpoint \
          --overwrite_cache \
          --per_device_train_batch_size 4 \
          --gradient_accumulation_steps 4 \
          --lr_scheduler_type cosine \
          --logging_steps 10 \
          --save_steps 1000 \
          --learning_rate 5e-5 \
          --num_train_epochs 3.0 \
          --plot_loss \
          --fp16
      

      可以發現,只有--stage被設置成了sft,其他參數之前已經介紹過了。
      所以我們直接開始查看源碼。

      理解源碼

      有了之前的經驗,源碼入口可以很快找到。
      即src/llmtuner/train/sft/workflow.py中的run_sft方法中。這里我直接把完整代碼放到下邊:

      def run_sft(
          model_args: "ModelArguments",
          data_args: "DataArguments",
          training_args: "Seq2SeqTrainingArguments",
          finetuning_args: "FinetuningArguments",
          generating_args: "GeneratingArguments",
          callbacks: Optional[List["TrainerCallback"]] = None,
      ):
          tokenizer = load_tokenizer(model_args)
          # 數據預處理部分有變化,后期可以進入查看一下
          dataset = get_dataset(tokenizer, model_args, data_args, training_args, stage="sft")
          model = load_model(tokenizer, model_args, finetuning_args, training_args.do_train)
      
          if training_args.predict_with_generate:
              tokenizer.padding_side = "left"  # use left-padding in generation
      
          if getattr(model, "is_quantized", False) and not training_args.do_train:
              setattr(model, "_hf_peft_config_loaded", True)  # hack here: make model compatible with prediction
        
          data_collator = DataCollatorForSeq2Seq(
              tokenizer=tokenizer,
              pad_to_multiple_of=8 if tokenizer.padding_side == "right" else None,  # for shift short attention
              label_pad_token_id=IGNORE_INDEX if data_args.ignore_pad_token_for_loss else tokenizer.pad_token_id,
          )
      
          # Override the decoding parameters of Seq2SeqTrainer
          training_args.generation_max_length = training_args.generation_max_length or data_args.cutoff_len
          training_args.generation_num_beams = data_args.eval_num_beams or training_args.generation_num_beams
      
          # Initialize our Trainer
          # trainer使用了CustomSeq2SeqTrainer,這是一個比較大的變化
          trainer = CustomSeq2SeqTrainer(
              model=model,
              args=training_args,
              finetuning_args=finetuning_args,
              tokenizer=tokenizer,
              data_collator=data_collator,
              callbacks=callbacks,
              compute_metrics=ComputeMetrics(tokenizer) if training_args.predict_with_generate else None,
              **split_dataset(dataset, data_args, training_args),
          )
      
          # Keyword arguments for `model.generate`
          gen_kwargs = generating_args.to_dict()
          gen_kwargs["eos_token_id"] = [tokenizer.eos_token_id] + tokenizer.additional_special_tokens_ids
          gen_kwargs["pad_token_id"] = tokenizer.pad_token_id
          gen_kwargs["logits_processor"] = get_logits_processor()
      
          # Training
          if training_args.do_train:
              train_result = trainer.train(resume_from_checkpoint=training_args.resume_from_checkpoint)
              trainer.save_model()
              trainer.log_metrics("train", train_result.metrics)
              trainer.save_metrics("train", train_result.metrics)
              trainer.save_state()
              if trainer.is_world_process_zero() and finetuning_args.plot_loss:
                  plot_loss(training_args.output_dir, keys=["loss", "eval_loss"])
      
          # Evaluation
          if training_args.do_eval:
              metrics = trainer.evaluate(metric_key_prefix="eval", **gen_kwargs)
              if training_args.predict_with_generate:  # eval_loss will be wrong if predict_with_generate is enabled
                  metrics.pop("eval_loss", None)
              trainer.log_metrics("eval", metrics)
              trainer.save_metrics("eval", metrics)
      
          # Predict
          # 多了一個預測推理階段,基本過程都是一樣的,只不過調用了trainer.predict方法
          if training_args.do_predict:
              predict_results = trainer.predict(dataset, metric_key_prefix="predict", **gen_kwargs)
              if training_args.predict_with_generate:  # predict_loss will be wrong if predict_with_generate is enabled
                  predict_results.metrics.pop("predict_loss", None)
              trainer.log_metrics("predict", predict_results.metrics)
              trainer.save_metrics("predict", predict_results.metrics)
              trainer.save_predictions(predict_results)
      
          # Create model card
          create_modelcard_and_push(trainer, model_args, data_args, training_args, finetuning_args)
      

      發現了什么?
      沒錯,代碼結構基本與之前的預訓練部分差不多。
      只有在get_dataset處理數據部分,會有所差異,具體差異我們暫時不看,只要知道sft階段數據預處理時,是需要增加指令、角色信息的就可以了。
      實際上,如果選擇使用LLaMA-Factory進行微調,我們按照LLaMA-Factory的數據集格式要求準備數據就可以了。

      解讀CustomSeq2SeqTrainer

      這階段除了預處理數據部分有差別,最大的差別就是訓練器與之前不同,使用的是CustomSeq2SeqTrainer,
      CustomSeq2SeqTrainer是Seq2SeqTrainer的子類,而Seq2SeqTrainer是Trainer的子類。
      Seq2SeqTrainer的源碼我們就不去仔細閱讀了,簡單閱讀后發現,Seq2SeqTrainer主要是重寫了Trainer的評估和推理的方法,沒有重寫訓練方法,所以與使用Trainer進行訓練應該差別不大。
      這里為什么使用Seq2SeqTrainer,我們不必糾結。
      閱讀qwen1.5官方提供的sft示例,可以看到示例中使用的也是Trainer而不是Seq2SeqTrainer。
      示例地址:https://github.com/QwenLM/Qwen1.5/blob/main/examples/sft/finetune.py

      總結

      可以發現,理解了pt階段后,再來理解sft階段其實是很容易的,一通百通,sft階段我們就介紹到這里,如果你對哪部分感興趣,可以自己去閱讀源碼,相信有了pt階段的知識儲備后,你可以很容易的閱讀源碼了。
      另外,我們只要懂得pt與sft微調,就能實際上手微調了。所以后續的RLHF階段就先不去查看了。

      微調實踐

      我們已經理解了大模型微調的基本過程,但實踐是檢驗真理的唯一標準,所以接下來將與大家一起對大模型微調進行實踐,觀察一下微調效果。
      注意:UI界面的使用請閱讀官方文檔,這里不會介紹UI如何使用。
      https://github.com/hiyouga/LLaMA-Factory/blob/main/README_zh.md

      數據集準備

      根據LLaMA-Factory官方提供的數據準備文檔,可以看到訓練數據的格式。地址如下:
      https://github.com/hiyouga/LLaMA-Factory/blob/main/data/README_zh.md
      文檔中比較重要的說明部分:

      對于預訓練數據集,僅 prompt 列中的內容會用于模型訓練。
      對于偏好數據集,response 列應當是一個長度為 2 的字符串列表,排在前面的代表更優的回答

      偏好數據集是用在獎勵建模階段的。

      本次微調選擇了開源項目數據集,地址如下:
      https://github.com/KMnO4-zx/huanhuan-chat/blob/master/dataset/train/lora/huanhuan.json
      下載后,將json文件存放到LLaMA-Factory的data目錄下。
      修改dataset_info.json 文件。
      直接增加以下內容即可:

        "huanhuan": {
          "file_name": "huanhuan.json"
        }
      

      添加后,在UI頁面中直接可以看到新增加的數據集:
      image.png

      開始微調

      為了減少資源消耗,本次選擇測試的模型是Qwen1.5-0.5B-Chat。
      通過可視化頁面配置后,可以得到微調命令如下:

      CUDA_VISIBLE_DEVICES=0 python src/train_bash.py \
          --stage sft \
          --do_train True \
          --model_name_or_path /home/jqxxuser/model/Qwen1.5-0.5B-Chat \
          --finetuning_type lora \
          --template qwen \
          --dataset_dir data \
          --dataset huanhuan \
          --cutoff_len 1024 \
          --learning_rate 5e-05 \
          --num_train_epochs 2.0 \
          --max_samples 100000 \
          --per_device_train_batch_size 2 \
          --gradient_accumulation_steps 8 \
          --lr_scheduler_type cosine \
          --max_grad_norm 1.0 \
          --logging_steps 5 \
          --save_steps 100 \
          --warmup_steps 0 \
          --optim adamw_torch \
          --output_dir saves/Qwen1.5-0.5B-Chat/lora/train_2024-03-28-10-54-09 \
          --fp16 True \
          --lora_rank 8 \
          --lora_alpha 16 \
          --lora_dropout 0.1 \
          --lora_target q_proj,v_proj \
          --plot_loss True
      

      說明:SFT階段是最常用訓練階段,所以我們在SFT階段進行微調,測試效果。
      我們直接在UI中點擊開始即可進行微調,可以觀察到整個的過程,發現損失值在逐漸降低。
      image.png
      等待訓練完畢即可。

      測試聊天效果

      刷新適配器,可以看到我們剛剛訓練完的模型,選擇即可
      image.png
      選擇Chat功能,加載模型后即可開始聊天。
      image.png
      看看效果吧:
      image.png
      目測效果還可以,至少我們看到模型確實發生了改變。

      評估模型

      通過聊天觀察效果是一種直觀的感覺,我們可以在通過項目自帶的評估功能做一下評估。
      注意,如果報錯,需要確保安裝了以下庫。
      jieba
      rouge-chinese
      nltk

      image.png

      運行后可以在目錄中看到評估指標:

      {
          "predict_bleu-4": 2.487403191204076,
          "predict_rouge-1": 16.790678761061947,
          "predict_rouge-2": 1.1607781979082865,
          "predict_rouge-l": 14.878193322606597,
          "predict_runtime": 900.9563,
          "predict_samples_per_second": 4.139,
          "predict_steps_per_second": 1.38
      }
      

      這些指標應該怎么看呢?首先我們來了解一下這些指標的概念。

      1. predict_bleu-4:
        • BLEU(Bilingual Evaluation Understudy)是一種常用的用于評估機器翻譯質量的指標。
        • BLEU-4 表示四元語法 BLEU 分數,它衡量模型生成文本與參考文本之間的 n-gram 匹配程度,其中 n=4。
        • 值越高表示生成的文本與參考文本越相似,最大值為 100。
      2. predict_rouge-1 和 predict_rouge-2:
        • ROUGE(Recall-Oriented Understudy for Gisting Evaluation)是一種用于評估自動摘要和文本生成模型性能的指標。
        • ROUGE-1 表示一元 ROUGE 分數,ROUGE-2 表示二元 ROUGE 分數,分別衡量模型生成文本與參考文本之間的單個詞和雙詞序列的匹配程度。
        • 值越高表示生成的文本與參考文本越相似,最大值為 100。
      3. predict_rouge-l:
        • ROUGE-L 衡量模型生成文本與參考文本之間最長公共子序列(Longest Common Subsequence)的匹配程度。
        • 值越高表示生成的文本與參考文本越相似,最大值為 100。
      4. predict_runtime:
        • 預測運行時間,表示模型生成一批樣本所花費的總時間。
        • 單位通常為秒。
      5. predict_samples_per_second:
        • 每秒生成的樣本數量,表示模型每秒鐘能夠生成的樣本數量。
        • 通常用于評估模型的推理速度。
      6. predict_steps_per_second:
        • 每秒執行的步驟數量,表示模型每秒鐘能夠執行的步驟數量。
        • 對于生成模型,一般指的是每秒鐘執行生成操作的次數。

      所以,單獨看指標數據的話,模型的效果并不是太好,這就需要大家自行摸索如何讓模型能力更好了。

      導出模型

      切換到Export選項卡,指定導出目錄,點擊開始導出,即可導出模型。
      image.png
      導出后的模型與其他大模型的使用方法一致。

      總結

      至此,我們的一次模型微調的嘗試就完成了。
      可以發現,使用LLaMA-Factory進行微調基本上可以做到傻瓜式操作了。最大的工作量還是在組織訓練數據這個階段。

      posted @ 2024-10-07 22:30  China Soft  閱讀(1392)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 亚洲AV无码国产永久播放蜜芽| 一本久道中文无码字幕av| 国产精品日日摸夜夜添夜夜添2021| 少妇扒开双腿自慰出白浆| 国产精品七七在线播放| 精品国产中文字幕第一页| 伊人成人在线视频免费| 成人国产乱对白在线观看| 亚洲韩国精品无码一区二区三区 | 亚洲日本高清一区二区三区| 在线中文字幕国产精品| 狠狠久久五月综合色和啪| 无人去码一码二码三码区| 国产成人精品成人a在线观看| 韩国精品福利视频一区二区| 亚洲人成精品久久久久| 久久青草国产精品一区| 国产麻豆放荡av激情演绎| 精品国产女同疯狂摩擦2| 国产精品最新免费视频| 国产另类ts人妖一区二区| 精品 日韩 国产 欧美 视频| 四虎女优在线视频免费看| 2019亚洲午夜无码天堂| 欧洲人妻丰满av无码久久不卡| 69精品无人区国产一区| 亚洲色欲色欱WWW在线| 精品视频在线观看免费观看| 午夜福利免费视频一区二区 | 亚洲阿v天堂网2021| 国产精品午夜福利精品| 国产一国产看免费高清片| 国产免费一区二区三区在线观看| 免费人成在线观看网站| 乱老年女人伦免费视频| 男女一边摸一边做爽爽| 中文国产人精品久久蜜桃| 精品久久久久久成人AV| 国产精品亚洲中文字幕| 久久精品国产国产精品四凭| 99在线精品视频观看免费|