ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大模型公开训练全流程实战:从数据准备到模型发布

大模型公开训练全流程实战:从数据准备到模型发布 如果你最近在尝试跑通一个大模型训练项目可能会发现一个尴尬的问题网上教程很多但几乎每一篇都只讲某一个环节。要么告诉你“数据要清洗”却不给清洗后的格式要么给你一段训练脚本但环境一换就跑不起来。真正动手去搭一个“公开训练全流程”从数据到模型发布往往要踩十几轮坑才能跑通。这篇博客就以一个名为 Marin 的训练项目为例把一条可以复制的公开训练全流程拆开来讲。我的判断是公开训练的真正难点不在模型结构也不在单条命令而在于数据、训练、评测、发布这几个环节之间的衔接。文章会从环境准备开始一步步走到模型导出尽量把每一步为什么这么做、做错会出现什么问题讲清楚。如果你正准备训练自己的模型或者团队需要把训练过程标准化、开放给更多人复现这篇文章值得你收藏。1. 为什么要关注公开训练全流程先解决一个“工程化”问题先定义一个概念公开训练并不是把训练脚本丢到 GitHub 上就叫公开。它至少包含这几层含义数据是开放的别人能理解数据来源、清洗规则和格式。训练过程是可复现的依赖、版本、随机种子、超参数都要可追踪。结果是可验证的评测方式、指标口径、对比基线都要明确。发布物是完整的包括模型权重、tokenizer、配置文件、推理示例。很多人第一次接触公开训练时会误以为只要单机跑通一个模型就完成了。但实际项目中真正的成本往往集中在这些地方数据脚本和训练脚本脱节开发机用的是精简数据全量数据在另一套机器上又出现编码问题。训练中断后checkpoint 加载逻辑不完整导致恢复训练后 loss 爆炸。没有建立评测基线模型训完了却说不清楚比 baseline 好在哪里。模型导出后 tokenizer 配置和推理脚本不一致部署端调用时出现乱码或维度错误。所以这篇文章要解决的核心问题不只是“怎么调用某个训练 API”而是“从零到一建立一条稳定的训练流水线”。Marin 在这里不是某个开源项目的代称而是一个示例训练项目代号。后续所有命令、目录结构、脚本都围绕这个项目展开你可以直接把它替换为自己的项目名。2. 公开训练的基础概念与整体阶段划分在动手写代码之前先建立一个全局视图。一个大模型公开训练全流程可以划分为六个阶段阶段核心目标主要产出物常见失败点数据准备形成高质量、可追踪的训练数据集清洗脚本、数据集文件、数据卡片数据格式不一致、脏数据混入环境准备复现训练所需的运行环境依赖清单、镜像或环境导出文件CUDA、PyTorch、框架版本冲突模型训练让模型在目标分布上收敛训练日志、checkpoint、损失曲线显存溢出、loss 发散、训练中断训练监控及时发现问题并调整训练策略指标面板、日志记录指标口径错误、日志缺失评测与验证量化模型效果确认是否达到基线评测脚本、评测报告、对比结果评测数据污染、指标计算不一致发布与部署把模型打包并交付给下游使用模型权重、tokenizer 配置、推理示例配置丢失、版本不匹配这六个阶段不是各管各的而是一条流水线。一个典型的公开训练项目数据团队产出干净数据后训练团队才能开始跑实验训练稳定后评测团队再介入。如果前期数据脚本不完善后面的训练、评测、发布都会跟着返工。你还会听到一些相关术语比如“预训练”“微调”“分布式训练”。这些是更细分的概念本文把它们融入到流程中讲解不单独展开成百科式介绍。2.1 公开训练与内部训练的区别很多人会问公开训练和平时自己调参训练有什么区别核心区别在于“可复现性”和“开放边界”。内部训练可以默认“跑通就行”数据放在固定路径脚本只在自己机器上有效公开训练要求任何拿到文档的人都能复现。内部训练可以用随机种子加日志口头沟通公开训练需要把种子、超参数、数据版本、代码 commit 全部记录在案。内部训练的模型只要任务跑完就行公开训练还要考虑别人如何用你的 checkpoint 继续训练或做推理。理解了这一点就能理解为什么后面每一步都要强调“记录”和“验证”。3. 环境准备与前置条件3.1 硬件环境训练大模型对硬件有基本要求。这里不写死具体配置因为不同规模的项目差异很大但可以给出一个通用参考场景建议硬件说明小规模微调单张 24GB 以上显存显卡适合验证流程、跑小 batch全参数训练多卡服务器或云上 GPU 实例需要支持 NCCL 多机通信数据预处理CPU 内存 32GB 以上tokenization 可能非常吃内存如果你是第一次接触训练建议先在小数据集上跑通流程不要一上来就全量数据。3.2 操作系统与基础软件强烈建议使用 Linux 环境主流的训练框架、分布式通信库、CUDA 支持都对 Linux 更友好。Windows 也可以跑单机小任务但遇到多卡分布式训练时坑会明显增多。基础软件清单如下操作系统Ubuntu 20.04 或 22.04或其他等价 Linux 发行版Python3.10 或 3.11版本以训练框架官方要求为准GPU 驱动NVIDIA 驱动配合对应 CUDA 版本版本管理git、conda 或 venv大文件管理git-lfs用于托管模型权重和大型数据集版本细节不要照搬网上教程因为你的显卡、驱动、框架版本都可能不同。最稳妥的顺序是先确认 GPU 驱动和 CUDA 版本再安装 PyTorch最后安装上层训练框架。3.3 安装核心依赖Marin 项目建议使用 conda 创建独立环境避免污染系统 Python。conda create -n marin python3.10 -y conda activate marin # 安装 PyTorch具体命令以 PyTorch 官网根据本机 CUDA 版本生成的命令为准 pip install torch torchvision torchaudio # 安装 Hugging Face 生态组件 pip install transformers datasets tokenizers accelerate # 分布式训练辅助 pip install deepspeed安装完成后用下面命令验证 GPU 是否可见python -c import torch; print(torch.cuda.is_available()); print(torch.cuda.device_count())如果输出True和正确的显卡数量说明环境基本没问题。如果输出False优先检查 PyTorch 版本是否匹配 CUDA 版本而不是直接重装驱动。3.4 项目目录结构规划环境准备好后先规划目录。Marin 项目建议采用如下结构marin/ ├── configs/ # 训练、评测、导出配置 ├── data/ # 数据集可能通过软链接指向实际存储 ├── scripts/ # 数据清洗、训练、评测、导出脚本 ├── src/ # 自定义代码比如数据集类、模型封装 ├── outputs/ # 训练输出包括 checkpoint 和日志 ├── eval_results/ # 评测结果 ├── README.md # 项目说明和复现文档 └── requirements.txt # 依赖清单目录结构看起来很简单但实际项目中大部分混乱都源于目录不清晰。比如数据脚本和输出混在一起、长时间训练后找不到某个 checkpoint 对应的配置。提前把目录定好后面能省很多事。4. 数据准备与预处理数据质量直接决定训练效果。公开训练对数据的要求更高因为你要让别人理解你为什么选择这些数据、如何清洗。4.1 数据收集与格式统一一开始拿到手的数据往往是“脏”的可能来自多个来源格式各不相同。第一步是先统一成一种目标格式Marin 项目采用 JSONL 格式每行一个 JSON 对象是当前大模型训练的主流格式。示例数据格式{instruction: 解释什么是梯度消失, input: , output: 当深层网络的反向传播过程中梯度逐层相乘后变得非常小导致浅层参数几乎无法更新这种现象称为梯度消失。} {instruction: 写一个 Python 函数判断回文串, input: , output: def is_palindrome(s):\n s s.lower()\n return s s[::-1]}每行包含instruction、input、output字段。具体字段名可以根据任务调整但需要保证整份数据集格式一致。4.2 数据清洗脚本清洗脚本负责过滤低质量内容。常见规则包括去重基于文本 hash 或 MinHash去掉重复样本。过滤过短样本比如少于 10 个字符。过滤噪音样本比如全是符号、乱码或超长重复文本。根据领域关键词过滤广告、垃圾信息。下面给一个最小清洗脚本示例文件路径为scripts/clean_data.py# scripts/clean_data.py import json import hashlib from pathlib import Path def get_text_hash(text: str) - str: return hashlib.md5(text.encode(utf-8)).hexdigest() def is_valid_sample(sample: dict, min_length: int 10) - bool: text sample.get(instruction, ) sample.get(output, ) if len(text) min_length: return False # 过滤掉正常比例过低的“符号噪音” symbol_count sum(not ch.isalnum() and not ch.isspace() for ch in text) if symbol_count / max(len(text), 1) 0.3: return False return True def main(input_path: str, output_path: str) - None: seen_hashes set() with open(input_path, r, encodingutf-8) as fin, \ open(output_path, w, encodingutf-8) as fout: for line in fin: line line.strip() if not line: continue try: sample json.loads(line) except json.JSONDecodeError: continue if not is_valid_sample(sample): continue text_hash get_text_hash(sample[instruction] sample[output]) if text_hash in seen_hashes: continue seen_hashes.add(text_hash) fout.write(json.dumps(sample, ensure_asciiFalse) \n) if __name__ __main__: main(data/raw_data.jsonl, data/clean_data.jsonl)运行方式python scripts/clean_data.py这里的关键逻辑是先过滤明显无效的行再做去重最后输出标准化格式。实际项目中清洗规则可能复杂得多但最小脚本可以帮助你验证流水线是通的。4.3 Tokenization 与数据集对象数据清洗完成后还需要把文本转换为模型可以读取的 token id。这里推荐直接使用 Hugging Facedatasets和tokenizer。# scripts/load_dataset.py import json from datasets import Dataset from transformers import AutoTokenizer # 假设你有一个本地 tokenizer 目录或者从模型仓库加载 tokenizer AutoTokenizer.from_pretrained(your-tokenizer-path) def preprocess(example): text example[instruction] \n example[input] \n example[output] return tokenizer(text, max_length512, truncationTrue) def load_and_tokenize(jsonl_path: str, output_dir: str) - None: samples [] with open(jsonl_path, r, encodingutf-8) as f: for line in f: samples.append(json.loads(line.strip())) dataset Dataset.from_list(samples) dataset dataset.map(preprocess, remove_columnsdataset.column_names) dataset.save_to_disk(output_dir) if __name__ __main__: load_and_tokenize(data/clean_data.jsonl, data/tokenized_data)这段代码把清洗后的 JSONL 转换成 Hugging Face Datasettokenize 后保存到磁盘。这样训练脚本就可以直接加载预处理好后的数据不用每次训练前重复清洗和 tokenize。5. 训练配置与模型训练5.1 训练配置文件Marin 项目采用 YAML 作为训练配置。配置文件的好处是超参数变化不再改代码而是改配置方便实验记录和复现。# configs/train_config.yaml model_name_or_path: your-base-model tokenizer_name_or_path: your-tokenizer-path train_file: data/tokenized_data output_dir: outputs/marin-checkpoints logging_dir: outputs/logs num_train_epochs: 3 per_device_train_batch_size: 4 per_device_eval_batch_size: 8 gradient_accumulation_steps: 8 learning_rate: 2e-5 weight_decay: 0.01 warmup_ratio: 0.03 logging_steps: 50 save_steps: 500 eval_strategy: steps eval_steps: 500 save_total_limit: 3 seed: 42 fp16: true deepspeed: configs/deepspeed_config.json字段的解释如下per_device_train_batch_size每张卡上的 batch size。gradient_accumulation_steps梯度累积步数等效 batch size 单卡 batch size × 卡数 × 梯度累积步数。save_steps每隔多少步保存一次 checkpoint。save_total_limit最多保留几个 checkpoint防止磁盘被写满。fp16混合精度训练能显著节省显存。注意不要照抄这里的超参数需要根据模型大小、数据量、显存情况调整。5.2 分布式训练启动当数据量和模型规模上来后单卡很难满足要求。DeepSpeed torchrun 是当前常用的分布式训练方案。DeepSpeed 配置示例{ train_batch_size: 64, gradient_accumulation_steps: 8, fp16: { enabled: true }, zero_optimization: { stage: 2 } }训练脚本本身可以直接使用 Hugging FaceTrainer它已经封装了 DeepSpeed 的接入逻辑。训练入口写法如下# scripts/train.py from transformers import ( AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments, HfArgumentParser, ) from datasets import load_from_disk import yaml def main(): parser HfArgumentParser(TrainingArguments) training_args parser.parse_args_into_dataclasses()[0] tokenizer AutoTokenizer.from_pretrained(training_args.tokenizer_name_or_path) model AutoModelForCausalLM.from_pretrained(training_args.model_name_or_path) dataset load_from_disk(training_args.train_file) trainer Trainer( modelmodel, argstraining_args, train_datasetdataset, tokenizertokenizer, ) trainer.train() if __name__ __main__: main()实际启动命令用torchrun假设有 4 张显卡torchrun --nproc_per_node4 \ --master_port29500 \ scripts/train.py \ --model_name_or_path your-base-model \ --tokenizer_name_or_path your-tokenizer-path \ --train_file data/tokenized_data \ --output_dir outputs/marin-checkpoints \ --logging_dir outputs/logs \ --per_device_train_batch_size 4 \ --gradient_accumulation_steps 8 \ --learning_rate 2e-5 \ --num_train_epochs 3 \ --fp16 \ --deepspeed configs/deepspeed_config.json这里要强调一个容易踩的坑--deepspeed参数必须和--gradient_accumulation_steps等参数配合deepspeed 配置里的train_batch_size是全局等效 batch size不是单卡值。配置不一致时训练能启动但日志里的 batch 信息会变得难以理解。5.3 训练中断与断点续训长训练任务几乎必然会遇到中断。机器重启、显存溢出、网络波动都可能导致训练停止。因此断点续训不是可选项而是必须项。断点续训的关键在于加载 checkpoint 时要同时恢复模型权重、优化器状态、学习率调度器状态、随机种子状态和全局 step。Hugging FaceTrainer的resume_from_checkpoint参数已经封装了大部分逻辑。在训练脚本中增加trainer.train(resume_from_checkpointTrue)命令行方式torchrun --nproc_per_node4 scripts/train.py ... --resume_from_checkpoint outputs/marin-checkpoints/checkpoint-1000注意如果训练脚本或模型代码在断点后发生了结构性变化加载旧 checkpoint 可能会报维度不匹配或 key 缺失。这种情况下优先排查 checkpoint 是否完整而不是盲目改代码绕过报错。6. 运行监控与效果验证训练启动不是终点更重要的是观测训练状态。很多问题在训练早期不会暴露需要持续监控。6.1 日志和 loss 观察训练过程中日志里最重要的指标是 loss。正常情况下loss 应呈下降趋势。可以通过 TensorBoard 查看tensorboard --logdir outputs/logs --port 6006如果看到 loss 突然变成nan或inf第一反应应该是检查学习率是否过大。检查数据中是否混入了异常样本。检查是否启用了 fp16 但梯度缩放器没有正常工作。检查 checkpoint 加载是否完整。很多人在 loss 发散时会立刻调学习率但更稳妥的做法是先保留现场日志再用小数据集复现确认是否数据问题。6.2 显存和吞吐量监控训练过程中还需要关注显存占用和吞吐量。可以用nvidia-smi快速查看显存状态nvidia-smi -l 5-l 5表示每 5 秒刷新一次。如果显存接近上限可以调小per_device_train_batch_size或开启梯度累积。一个合理训练状态的判断标准是GPU 利用率保持较高而不是长期 0%。显存没有频繁 OOM。训练吞吐量稳定没有周期性骤降。如果训练速度忽快忽慢可能需要检查数据加载是否有瓶颈比如磁盘 IO 太慢或者 DataLoader 的num_workers设置过低。7. 模型评测与发布训练完成后模型是否达到预期效果不能只靠训练集 loss 判断必须有独立的评测流程。7.1 评测集与指标评测集需要和训练集严格分离不能有重叠。建议在数据准备阶段就预留评测数据而不是训练完成后再临时找评测样本。Marin 项目使用一个简单的指令评测脚本# scripts/evaluate.py import json from transformers import AutoModelForCausalLM, AutoTokenizer import torch model_path outputs/marin-checkpoints/checkpoint-3000 tokenizer AutoTokenizer.from_pretrained(model_path) model AutoModelForCausalLM.from_pretrained(model_path, device_mapauto) eval_samples [ {instruction: 解释什么是过拟合, input: }, {instruction: 用 Python 写一个读取 CSV 文件的函数, input: }, ] for sample in eval_samples: prompt sample[instruction] \n sample[input] inputs tokenizer(prompt, return_tensorspt).to(model.device) outputs model.generate(**inputs, max_new_tokens128) response tokenizer.decode(outputs[0], skip_special_tokensTrue) print( * 20) print(输入, prompt) print(输出, response)运行方式python scripts/evaluate.py评测时要注意两个问题不要用训练过的样本做评测否则结果虚高。生成式模型的评测指标往往不只是准确率还需要人工抽查回答质量。7.2 模型导出训练完成后需要把 checkpoint 导出为适合部署或上传的格式。Hugging Face 的AutoModelForCausalLM保存目录已经包含了模型权重和配置但部署前仍需检查完整性。标准导出命令python -c from transformers import AutoModelForCausalLM, AutoTokenizer model AutoModelForCausalLM.from_pretrained(outputs/marin-checkpoints/checkpoint-3000) model.save_pretrained(outputs/marin-final) tokenizer AutoTokenizer.from_pretrained(outputs/marin-checkpoints/checkpoint-3000) tokenizer.save_pretrained(outputs/marin-final) 导出后outputs/marin-final目录下应有以下关键文件outputs/marin-final/ ├── config.json ├── generation_config.json ├── model.safetensors ├── tokenizer_config.json ├── tokenizer.json └── tokenizer.model缺少tokenizer.json或tokenizer.model部署端通常无法正常 encode/decode这是最常见的发布事故之一。7.3 发布前检查清单发布前建议做一遍检查训练配置、数据清洗脚本、模型代码是否提交到版本库。checkpoint 是否做过完整性校验比如加载后能正常执行一次前向传播。tokenizer 配置是否与训练时一致。是否提供最小推理示例方便使用者快速验证。8. 常见问题与排查问题现象可能原因排查方式解决方案启动训练时报 CUDA out of memorybatch size 过大或显存不足以支撑模型驻留查看nvidia-smi实际显存占用调小per_device_train_batch_size或开启梯度累积、降低序列长度loss 直接变成 nan/inf学习率过大、数据有异常、fp16 溢出查看训练日志确认出现 nan 的 step检查数据样本降低学习率检查数据清洗规则尝试关闭 fp16 或使用 bf16恢复训练后 loss 明显高于中断前checkpoint 加载不完整或优化器状态未恢复检查trainer_state.json中的 global step确保resume_from_checkpoint指向正确 checkpoint不要重新初始化 trainer训练速度很慢GPU 利用率低数据加载成为瓶颈或单卡 batch 太小观察训练日志中迭代耗时检查 DataLoader worker 数增加num_workers把数据预处理好存为内存映射格式避免在线 tokenize多个进程互相抢占端口torchrun 默认端口冲突查看master_port是否被占用更换--master_port端口或在训练脚本中动态分配端口模型推理输出乱码保存和加载时 tokenizer 不一致比较训练时和推理时的 tokenizer 文件统一使用同一个 tokenizer 目录导出时确保 tokenizer 文件完整数据集加载时直接内存溢出全量数据一次性读入内存查看系统内存占用情况使用datasets的流式加载或分片加载避免load_dataset一次性读入大文件9. 工程最佳实践与建议9.1 记录一切可复现信息公开训练最看重可复现性。建议从第一天起就记录以下信息数据版本用 DVC 或简单哈希记录数据集的 commit。代码版本每个实验对应一个 git commit。超参数YAML 配置文件入库训练脚本自动读取。环境信息pip freeze requirements.txt必要时记录 CUDA 版本和驱动版本。9.2 先小规模验证再全量训练任何人直接跑全量训练都可能浪费大量算力。建议先用 1% 的小数据子集跑通完整流水线验证数据格式、训练脚本、评测脚本都没有问题后再启动全量训练。这样可以在 30 分钟内暴露 80% 的流程问题。9.3 保持幂等性数据清洗脚本、训练脚本最好具备幂等性即重复执行结果一致。数据清洗脚本应该做到“重新执行不会改变输出”这样别人才能放心复现。有条件时还可以在脚本入口加上参数校验。9.4 及时保存 checkpoint 并设计回滚策略训练过程中的 checkpoint 是最大的资产。建议设置合理的save_steps不要太稀疏。保留最后若干个 checkpoint而不是只留一个。对关键 checkpoint 做一次独立备份防止磁盘损坏或误删。9.5 安全与权限如果训练数据涉及敏感信息不要把数据直接提交到公开仓库。建议用.gitignore忽略data/raw_data等目录。通过环境变量或配置文件管理 API Key。公开发布前确认数据符合版权和隐私要求。9.6 文档也是一种交付物公开训练项目的文档至少要包含数据来源和清洗规则。环境安装步骤。训练启动命令。评测指标和结果。模型使用示例。文档不是写完代码后的装饰而是让别人理解你训练流程的重要管道。10. 总结与下一步实践建议这篇文章以 Marin 项目为例把公开训练全流程拆成了六个阶段数据准备、环境准备、模型训练、训练监控、评测与发布。你可能会发现任何一个阶段单独拿出来都不算难难的是把阶段之间衔接好。我建议你按照以下步骤开始实践先复制文章里的目录结构新建一个最小项目。准备一个几百条数据的小数据集跑通数据清洗脚本。在单卡上用小模型跑一次训练确认 checkpoint 可以正常保存和恢复。加上评测脚本验证模型输出。最后再评估是否需要切换到多卡分布式训练和 DeepSpeed。这整个过程中最值得投入精力的不是调模型结构而是把数据版本、训练配置、评测流程串起来。你可以先在自己的机器上搭建一条最小闭环流程之后再把大数据量、多机训练逐步加进去。如果你后续对 RAG 知识库搭建、模型微调部署、推理加速感兴趣这篇文章里提到的 tokenizer 配置、checkpoint 保存、评测验证等基础能力都会继续复用。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进