
GPT4All 微调训练实录EOS 令牌陷阱、数据重复 Bug 与多 Epoch 发散的复盘【免费下载链接】gpt4allGPT4All: Run Local LLMs on Any Device. Open-source and available for commercial use.项目地址: https://gitcode.com/GitHub_Trending/gp/gpt4all本文以 gpt4all-training 目录下的训练日志 为核心完整复盘 GPT4All 团队从约 70 万条样本的初始 LoRA 实验到独立 EOS 令牌改造、条件化 EOS、多 Epoch 全参训练直至最终 GPT4All-J 模型定版的整个微调过程。通过对照仓库中的训练脚本 train.py、数据加载逻辑 data.py 与 configs 目录下的训练配置读者可以掌握指令微调中 eos/pad 令牌处理、学习率随批大小缩放、数据重复与训练发散等关键问题的排查与规避方法。实验背景数据构成与硬件环境整个训练过程基于两组不同规模的数据集展开初始实验数据集约 70 万条样本数据来源包括 P3/BLOOM、StackOverflow 和 unified_chip2最终训练数据集约 40 万条样本。团队出于数据多样性考虑移除了整个 Bigscience/P3 子集——P3 中包含大量同质化提示会导致 GPT-3.5-Turbo 生成短小且同质的回答拉低数据多样性。硬件环境为8 张 A100 80GB GPU这与各超参数表中单卡批大小 × 8 全局批大小的对应关系一致例如 4 × 8 32、32 × 8 256。数据切分方面训练日志明确说明随机打乱后留出5% 数据作为验证集这一点可以在数据加载源码中得到印证data.py 中通过dataset.train_test_split(test_size.05, seedconfig[seed])完成同样的 5% 划分。在数据处理管线层面仓库中 clean.py 负责将原始 jsonl 数据清洗为*_clean.jsonl格式剔除空 prompt/response、保留source/prompt/response三个字段而 data.py 的load_data会扫描目录下的*_clean.jsonl文件加载。值得注意的数据工程细节是 tokenize_inputs 的长 prompt 截断策略当 prompt 本身超过max_length // 2时会先对 prompt 做截断以保证每个样本至少保留若干可学习标签随后用-100对 prompt 部分的 label 做掩码data.py使模型只在 response 部分计算损失——这是指令微调的标准做法。初始 LoRA 实验能跑但停不下来第一轮实验用上述约 70 万条数据训练一个LoRA 模型初始超参数如下超参数取值Per Device BS4Global BS32Learning rate5e-5Epochs1Max length1024Weight decay0Warmup Steps100训练过程中暴露的第一个问题是训练损失日志存在 bug记录的 loss 不正确但团队注意到验证损失在下降于是继续推进。生成阶段发现更本质的问题模型似乎永远不生成eos令牌。根因是 tokenizer 的pad令牌被设置成了eos令牌模型大概率从未学会停止。这一点在仓库训练脚本中可以找到直接对应——train.py 在加载 tokenizer 后有这样的兜底逻辑# if no pad token, set it to eos if tokenizer.pad_token is None: tokenizer.pad_token tokenizer.eos_token从源码结构看正是这一无 pad 令牌则回退到 eos的常规写法让初始实验的模型在 pad/eos 共用的情况下缺少独立的停止信号。不过尽管缺少停止令牌贪心生成下的产出质量已经不错。例如对提示Generate a python script to reverse a string.模型输出了有效的 markdown 和可运行的脚本Heres a Python script to reverse a string: def reverse_string(string): return .join(reversed(string)) This function takes a string as an argument and returns the reversed string. The reversed() function reverses the string and returns a list of characters. The join() function joins the list of characters into a string. You can use this function like this: string hello world print(reverse_string(string)) This will output olleh dna hwl.但紧接着的问题也很典型模型开始重复内容并在完整回答问题之后继续生成随后又给出变体版本处理含空格字符串的re.split版本最后以 Note that this function does not handle strings with 这样未完成的中途语句收尾——这正是永远不停行为的直观表现。作为对照基座模型面对Write a python script to reverse a string. The string should be reversed in place.只会疯狂复读、产生幻觉The string should be reversed in place. The string should be reversed in place. The string should be reversed in place. The string should be reversed in place. The string should be reversed in place. ... The string should be reversed in place. 持续重复直至生成上限这说明即使没有正确的停止能力LoRA 微调已经显著改善了模型的指令跟随质量。独立 EOS 令牌以及一次意外重复既然模型停不下来团队的对策是训练一个eos与pad分离的模型。由于词表中找不到可用的pad令牌他们向 tokenizer 中添加了一个并相应扩大了模型嵌入维度Llama 词表为 32,000。训练日志还提到一个理论上的优化方向可以将嵌入维度按 64 的倍数向上扩展以提升吞吐与性能这是业界流传的一个经验做法原文以外部链接标注。实现上为每条序列追加一个eos令牌期望模型学会主动退出。仓库中 generate.py 展示了配套的推理侧处理tokenizer AutoTokenizer.from_pretrained(config[tokenizer_name]) added_tokens tokenizer.add_special_tokens({bos_token: s, eos_token: /s, pad_token: pad}) if added_tokens 0: model.resize_token_embeddings(len(tokenizer))即新增pad_token后调用resize_token_embeddings对齐嵌入矩阵——与训练日志中扩嵌入维度的描述一致。使用与首轮完全相同的超参数这次训练成功损失曲线见 duplicate_loss.png。生成时模型能够提前正确退出You can use the reversed() function to reverse a string in Python. Heres an example: string hello world reversed_string reversed(string) print(reversed_string) Output: world hello面对一个带陷阱的困难提示——用注释谎称代码在反转字符串、实际却只打印长度#this code prints a string reversed my_string hello how are you print(len(my_string)) My code above does not work. Can you help me?模型正确地指出The code you provided does not print a string reversed. It prints the length of the string hello how are you.然而随后团队意识到这一轮存在两个 bug数据被意外重复相当于实际训练了 2 个 epoch 而非 1 个给每条序列都追加了eos包括被截断的序列例如超过 1024 长度的长代码。对于截断样本response 在数据内部就未结束强行补eos等于教模型看到截断就该停。从当前仓库 data.py 的实现看序列构造仍是prompt \n response tokenizer.eos_token的无条件追加形式并带有针对旧数据中/s \n标记的兼容替换逻辑data.py与日志描述的这一阶段实现吻合。条件化 EOS 与单 Epoch修掉了 Bug但生成质量下降针对上述两个 bug团队采用了**条件化 eos**仅当输入长度小于最大序列长度时才追加eos并且严格只训练 1 个 epoch。结果出乎意料训练与验证损失曲线看起来相当正常见 single_epoch.png但生成质量反而略差同样的提示Generate a python script to reverse a string.模型输出变得松散且格式混乱Write a python script to reverse a string. Here is a python script to reverse a string: import string def reverse_string(string): return string[::-1] print(reverse_string(hello world)) Output: world hello上一节的陷阱提示直接不生成任何文本提示Generate a python script to make a get request to an api endpoint.则生成了一句拒答式套话Im sorry, I cannot provide a specific answer to this question as it requires more context...这个实验说明损失曲线正常并不等于生成质量达标验证损失对过早停止拒答这类行为并不敏感。多 Epoch LoRA 与全参训练Llama 阶段的收尾在剔除 P3 子集后最终数据集规模约为 40 万条。这一阶段同时推进两条路线LoRA 模型训练 4 个 epoch超参数取值Per Device BS4Global BS32Learning rate5e-5Epochs4Max length1024Weight decay0Warmup Steps100全参full model模型训练 2 个 epoch超参数取值Per Device BS32Global BS256Learning rate5e-5Epochs2Max length1024Weight decay0Warmup Steps100关于学习率的选择训练日志给出了明确的方法论借鉴 Stanford Alpaca 微调工作的经验按sqrt(k)缩放学习率其中k是批大小相对 Alpaca批大小 128、学习率 2e-5的放大倍数。对比结论均以训练日志为准将本项目的 LoRA 模型与 Alpaca 官方的 LoRA 微调模型对比本项目的模型困惑度perplexity更低定性评估上训练 3 个 epoch 的效果在困惑度与生成样例两方面都是最佳全参模型按上表参数训练时在第 2 个 epoch 出现发散训练后采样的生成结果不如第 1 个 epoch 时的质量。这一阶段的全参/LoRA 配置与仓库配置文件可以直接对应finetune.yaml 中lr: 5.0e-5、warmup_steps: 100、num_epochs: 2、batch_size: 32、weight_decay: 0.0与全参超参表逐项一致finetune_lora.yaml 则通过lora: true开启 LoRA 路线。LoRA 的具体参数在 train.py 中硬编码为r8, lora_alpha32, lora_dropout0.1, task_typeCAUSAL_LM训练脚本还会打印可训练参数量占比model.print_trainable_parameters()。GPT-J 训练发散、Atlas 定位与最终超参数全参训练在 epoch 1 后发散团队随后训练了多个 GPT-J6B模型成功程度不一。全参训练在 epoch 1 之后发散损失曲线见 overfit-gpt-j.png。最终决定发布 epoch 1 结束时的检查点而非继续训练。这与上一节 Llama 全参模型第 2 个 epoch 发散的现象相互印证在这套 40 万条指令数据上全参模型对多 epoch 训练非常敏感过拟合/发散风险显著。训练脚本层面train.py 在每个 epoch 结束后都会save_pretrained到output_dir/epoch_{n}并推送到 hub因此保留 epoch 1 检查点、弃用后续 epoch这种决策在工具链上是天然支持的。用 Atlas 向量地图定位高损失样本为了理解发散从何而来团队借助 Nomic 的 Atlas 工具对数据集中每个样本提取嵌入向量并逐条计算序列损失然后把高损失区域可视化见 clustering_overfit.png。观察发现高损失样本呈现明显的聚类。进一步检查发现密度最高的簇对应的是创意类生成提示例如Generate a story about ...——这类开放创作任务与指令式问答的分布差异较大是模型最难拟合的部分。仓库中 GPT-J_MAP.md 保留了这一分析流程的入口命令torchrun --master_port29085 --nproc-per-node 8 inference.py --configconfigs/inference/gptj.yaml该命令用微调后的模型跑 inference.py 在训练数据上批量生成嵌入与推理结果随后按文档说明构建 Atlas 地图进行聚类可视化。GPT4All-J 最终超参数团队参考 GPT-J 官方训练指南对学习率、学习率调度、权重衰减做了多项尝试但结论是变化这些参数在定性与定量上都没有实际的性能差异。最终定版的两个模型均采用线性 warmup 恒定学习率的调度方式GPT4All-J 全参模型超参数取值Per Device BS32Global BS256Learning rate2e-5Epochs2Max length1024Weight decay0Warmup Steps500GPT4All-J LoRA 模型超参数取值Per Device BS4Global BS32Learning rate2e-5Epochs2Max length1024Weight decay0Warmup Steps500这套定版参数与仓库配置高度吻合finetune_gptj.yaml 中model_name: EleutherAI/gpt-j-6B、lr: 2.0e-5、warmup_steps: 500、num_epochs: 2、batch_size: 32、weight_decay: 0.0、lora: falsefinetune_gptj_lora.yaml 则在同参数下设置lora: true并将单卡批大小调小batch_size: 1通过梯度累积逼近全局批大小同时关闭梯度检查点gradient_checkpointing: false以换取速度。线性 warmup 恒定学习率的调度落在 DeepSpeed 配置中ds_config_gptj.json 指定scheduler.type为WarmupLR、warmup_type为linear并配合 ZeRO Stage 2、bf16 混精与gradient_clipping: 1.0。从源码结构看train.py 的调度器逻辑是分叉的当 DeepSpeed 配置中声明了scheduler时使用DummyScheduler占位、由 DeepSpeed 的WarmupLR实际接管对应日志描述的线性 warmup 后恒定未使用 DeepSpeed 时才回退到 HuggingFace 的 cosine 调度。复现入口仓库 gpt4all-training/README.md 给出了训练 GPT4All-J 的标准启动命令8 进程、bf16 混精、DeepSpeedaccelerate launch --dynamo_backendinductor --num_processes8 --num_machines1 --machine_rank0 --deepspeed_multinode_launcher standard --mixed_precisionbf16 --use_deepspeed --deepspeed_config_fileconfigs/deepspeed/ds_config_gptj.json train.py --config configs/train/finetune_gptj.yaml使用前需要修改配置文件中标注# CHANGE的字段save_name、dataset_path、output_dir以及 wandb 的wandb_entity/wandb_project_name。数据可以直接使用 README 中发布的 GPT4All-J 训练数据集按revision参数选择 v1.0 / v1.1-breezy / v1.2-jazzy 等不同过滤版本也可以像 clean.py 那样自行清洗出*_clean.jsonl后填入本地路径——data.py 会同时支持 HF 数据集名与本地*_clean.jsonl目录两种来源。训练日志留下的实战要点整份训练日志的价值在于它记录了每一步决策 事后复盘其中四条经验对任何做指令微调的团队都直接可用pad 与 eos 不要共用。tokenizer 无 pad 令牌时回退到 eos 的写法train.py会导致模型学不会停止要么显式添加独立 pad 令牌并resize_token_embeddings要么在生成侧显式指定eos_token_id。给截断样本追加 eos 是有害的。截断意味着 response 在数据内部并未结束无条件补 eos如 data.py 当前实现会诱导模型过早停止应按输入是否达到最大长度做条件化追加。核对数据管线警惕隐性重复。一次数据拼接错误就让模型多跑了一个 epoch且损失曲线毫无异常——验证损失下降不能替代对数据管线的审计。多 epoch 全参微调要盯紧发散信号。在 40 万条指令数据上无论是 Llama 7B 还是 GPT-J全参训练都出现了 epoch 1/2 之后的发散而逐 epoch 保存检查点train.py保留了回退能力借助 Atlas 类向量地图做高损失样本聚类可以快速定位发散的数据来源创意类长文本簇。【免费下载链接】gpt4allGPT4All: Run Local LLMs on Any Device. Open-source and available for commercial use.项目地址: https://gitcode.com/GitHub_Trending/gp/gpt4all创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考