☰
开源大模型训练全流程:100亿Token从数据到部署
2026/10/12 2:43:48 网站建设 项目流程

这两年 AI 圈的阵仗确实大,新模型一个接一个,但我始终觉得,真正让技术圈热闹起来的,不是那些只发技术报告不开源的团队,而是愿意把模型、代码、数据管线老老实实放出来的项目。今天想聊的,就是我最近刚完成并开源的一个项目。不止是模型权重,包括整个数据处理流程、训练脚本、评测脚本,全都整理好放出来了。

说实话,做这个项目的初衷很简单:我自己在微调和部署小模型时,发现很多开源仓库要么只给一个训练完的权重文件,要么训练代码和数据处理逻辑写得比较随意,很难复现。我希望能有一个相对完整、可复现的“大语言模型从数据准备到开源发布”全流程项目,于是花了两个多月时间,前后累计消耗了约 100 亿 Token 的数据量用于清洗、配比和训练验证,最终把一套可运行的开源方案整理了出来。

这篇文章我不会只贴一个 GitHub 地址就完事。而是把整个项目从立项、数据工程、模型训练、评测、到开源打包部署的完整链路拆开来讲。如果你正准备开始自己的第一个开源模型项目,或者正在为团队搭建私有化 LLM 推理服务,这篇文章应该能帮你少走不少弯路。

1. 背景与核心概念:为什么值得投入两个月做一个开源项目

1.1 这个项目到底是什么

先解释一下标题中的几个关键词,避免刚接触大模型的朋友产生误解。

“100 亿 Token”指的是整个项目在数据处理、模型训练、评测验证过程中累计消耗的 Token 数量。Token 是 LLM 处理文本时的最小单位,可以粗略理解为一个词或一个子词片段。100 亿 Token 大约相当于 7.5 亿英文单词,或者 4~5 亿汉字,这个量级已经足够训练一个亿级参数的小模型,也足够做一遍高质量的数据筛选和配比实验。

项目最终交付物是:

  • 一套完整的数据清洗与配比脚本;
  • 一个在清洗后数据上训练的基座模型;
  • 一套可复现的指令微调流程;
  • 一个轻量的推理服务包装;
  • 详细的评测结果和失败案例分析。

整体架构可以概括为:数据工程 -> 预训练 -> 指令微调 -> 评测 -> 开源部署。

1.2 为什么要做“开源”而不是自己闷头用

很多开发者会问:模型训出来了,自己用不就行了,为什么非要开源?

我的理由有三个。

第一,可复现性是技术沉淀的基础。如果训练脚本、数据配比、超参数都不公开,那这次训练就是一次不可复现的“玄学”。开源相当于强迫自己把每一个环节写成文档和脚本,对工程化能力是很好的锻炼。

第二,社区反馈能加速迭代。开源之后,会有使用者反馈新场景下的表现,也可能有贡献者提交更好的数据清洗策略。这种外部输入比闭门造车高效得多。

第三,降低团队试错成本。真正在企业里落地的开发者,往往需要参考成熟的模型仓库来设计自己的训练管线。一个结构清晰的开源项目,能直接作为模板复用。

1.3 哪些场景适合参考这个项目

  • 想训练一个特定领域的小模型,但不知道数据怎么清洗、怎么配比;
  • 想微调开源模型做私有化部署,但希望补全数据处理到评测的完整链路;
  • 想学习大模型训练工程的细节,尤其是 Token 消耗和成本控制的关系;
  • 实验室或小团队需要一套可复现的训练代码。

如果你只是想直接用模型做应用,不需要训练,那这个项目的推理部署部分仍然有参考价值,尤其是量化部署部分。

2. 环境准备与版本说明

在跑任何训练或推理之前,先把环境信息说清楚。以下版本是我实际使用的环境,你的环境不一定完全一致,但思路是通用的。

2.1 硬件与运行环境

资源配置
GPU单机 8 x A800-80G / 或多机多卡
CPU32 核以上
内存256GB 以上
系统Ubuntu 20.04 / 22.04
存储至少 2TB 可用空间

如果你是个人开发者,硬件条件达不到,也完全可以用单卡 RTX 4090 跑通数据处理和微调流程,只是预训练阶段需要调小模型参数规模。

2.2 软件依赖版本

Python 3.10+ CUDA 11.8 / 12.1 PyTorch 2.1+ transformers 4.36+ datasets 2.16+ accelerate 0.26+ peft 0.8+ deepspeed 0.12+ vllm 0.3+

这里单独提醒一句:大模型训练项目的依赖版本非常敏感,尤其是 transformers、peft、deepspeed 三者之间经常存在兼容性问题。建议先创建独立的 conda 环境,再按顺序安装。

conda create -n llm-project python=3.10 conda activate llm-project pip install torch==2.1.0 --index-url https://download.pytorch.org/whl/cu118 pip install transformers==4.36.0 datasets==2.16.0 accelerate==0.26.0 pip install peft==0.8.0 deepspeed==0.12.0

2.3 项目目录结构

构建开源项目从第一天起就要保持良好的目录结构,下面是我实际使用的目录组织方式。

llm-open-project/ ├── configs/ # 配置文件 ├── data/ # 数据处理脚本 │ ├── raw/ # 原始数据存放 │ ├── cleaned/ # 清洗后数据 │ ├── tokenized/ # Token 化后数据 │ └── process_pipeline.py # 数据管道 ├── train/ # 训练脚本 │ ├── pretrain.py # 预训练入口 │ ├── sft.py # 指令微调入口 │ └── deepspeed_config/ # DeepSpeed 配置 ├── eval/ # 评测脚本 │ ├── run_benchmark.py │ └── eval_prompts.json ├── deploy/ # 推理部署 │ ├── vllm_server.py │ └── quantize.py ├── scripts/ # 一键执行脚本 ├── README.md └── requirements.txt

3. 核心原理拆解:Token 消耗在哪,数据工程怎么做

3.1 100 亿 Token 到底消耗在哪里

很多朋友看到 100 亿 Token 的第一反应是“好多”,但实际拆开看,这个数字是有清晰去向的。

环节Token 消耗占比说明
数据清洗与去重约 20%多次跑规则过滤和语义去重模型,需要反复读取全量数据
预训练基座约 60%在通用语料上训练模型,是 Token 消耗的大头
指令微调约 15%构建指令样本并完成模型对齐
评测与验证约 5%多次跑基准测试,尤其是对比不同版本效果时

这里要特别说明一个容易踩坑的点:数据清洗环节消耗的 Token 远比你以为的多。因为去重阶段需要把全量文本发送给语义去重模型,每跑一遍都要消耗与语料规模相当的 Token 量。如果清洗策略设计得不好,返工次数一多,成本很快就会失控。

3.2 数据清洗的三个关键步骤

大模型训练圈有句名言:数据决定模型的上限,训练只是逼近这个上限。整个项目花费时间最长的不是训练,而是数据处理。

第一步:语言过滤和质量过滤

使用fasttext或cld3做语言识别,过滤掉目标语言之外的语料。质量过滤则依赖规则和分类模型相结合:

# 文件路径:data/process_pipeline.py import re import fasttext # 加载语言识别模型 lang_model = fasttext.load_model("lid.176.bin") MIN_TEXT_LENGTH = 100 MAX_TEXT_LENGTH = 5000 REPETITION_RATIO_THRESHOLD = 0.3 def is_high_quality(text: str) -> bool: """基础规则过滤:长度、重复率、语言""" if len(text) < MIN_TEXT_LENGTH or len(text) > MAX_TEXT_LENGTH: return False # 重复字符比例检测,过滤“哈哈哈哈...”这类无意义文本 char_set = set(text) if len(char_set) / max(len(text), 1) < 0.05: return False # 语言过滤,检测是否为中文/英文 lang = lang_model.predict(text.replace("\n", " "))[0][0] if lang not in ["__label__zh", "__label__en"]: return False return True

第二步:MinHash 去重

互联网语料中重复内容非常多,一篇热门文章可能被转载上百次。使用 MinHash + LSH 做近似去重,是数据工程的标配做法。

from datasketch import MinHash, MinHashLSH def build_minhash(text: str, num_perm=128): m = MinHash(num_perm=num_perm) # 使用 5-gram 进行分片 for i in range(len(text) - 5): m.update(text[i:i+5].encode("utf-8")) return m

这里使用 5-gram 分片的原因是,太短的 n-gram 容易把完全不相关的文本误判为重复,太长的 n-gram 又可能漏掉改写后的重复文本。5-gram 在中文语料上效果比较均衡。

第三步:数据配比

数据配比是一个经常被人忽略但影响极大的环节。如果你直接把所有清洗后的语料混在一起训练,模型可能会被灌入大量低质量或特定领域的文本,导致下游任务表现失衡。

我的配比策略是:按“通用语料:领域语料 = 7:3”进行混合。领域语料又细分为代码、数学、法律、医学等子领域,每个子领域按 token 数量加权采样。

# 数据配比采样逻辑 import random def sample_by_domain(domain_and_texts, target_domain_ratio=0.3): """按领域比例对流式数据做采样""" # 实现细节根据数据源结构调整 pass

3.3 为什么预训练要分阶段

预训练阶段我采用了两阶段策略,而不是一次性把所有数据灌进去。

第一阶段:在海量通用语料上训练,让模型学习基本的语言能力和世界知识。第二阶段:用中道清洗后的领域语料继续训练,增强模型在目标领域的效果。

这种“通用预训练 + 领域继续训练”的思路,比直接混合训练能更容易控制领域能力的增强幅度,也能减少灾难性遗忘。

3.4 指令微调:把“会说话”变成“会办事”

基座模型只能做文本续写,比如你输入“Python 的 GIL 是”,它会尝试补全。但如果你希望它回答“什么是 Python 的 GIL?”,就需要指令微调。

指令微调的关键是样本质量。这里的原则是:样本宁缺毋滥。我最后用来微调的高质量指令样本大约是 10 万条,但中间清洗掉的不合格样本超过 30 万。

一个典型指令样本格式如下:

{ "instruction": "解释什么是 Python 的 GIL", "input": "", "output": "GIL 是 CPython 解释器中的全局解释器锁,它保证同一时刻只有一个线程执行 Python 字节码..." }

4. 完整实战案例:从数据处理到开源发布

下面进入整篇文章最核心的部分,我会把整个项目从训练到发布的流程走一遍。如果你想完整复现,可以直接对照本节操作。

4.1 数据准备与 Token 化

首先是准备原始数据。这里假设你已经有一批文本语料放在data/raw/目录下。第一步是执行清洗管道:

python data/process_pipeline.py \ --input_dir data/raw \ --output_dir data/cleaned \ --language zh,en \ --min_length 100 \ --max_length 5000

清洗完成后,需要将文本 Token 化为模型输入的 ID 序列。使用 Hugging Face 的datasets库进行批量处理:

# 文件路径:data/tokenize_dataset.py from datasets import load_dataset from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("your-base-model") def tokenize_function(examples): # 拼接文本并按 chunk 长度截断 texts = [t + tokenizer.eos_token for t in examples["text"]] tokenized = tokenizer( texts, truncation=True, max_length=2048, padding=False, return_tensors=None ) return tokenized dataset = load_dataset("json", data_files="data/cleaned/train.jsonl") tokenized_dataset = dataset.map( tokenize_function, batched=True, remove_columns=["text"], num_proc=32 ) tokenized_dataset.save_to_disk("data/tokenized/train")

Token 化这一步是计算密集型的,强烈建议使用num_proc开启多进程,单进程处理几十 GB 数据会慢到怀疑人生。

4.2 配置 DeepSpeed 训练环境

由于项目在预训练阶段需要处理大量 Token,单卡显存远远不够,因此多卡分布式训练是必须的。这里选择 DeepSpeed ZeRO-2 来切分优化器状态和梯度。

// 文件路径:train/deepspeed_config/zero2.json { "train_batch_size": 64, "gradient_accumulation_steps": 4, "train_micro_batch_size_per_gpu": 2, "optimizer": { "type": "AdamW", "params": { "lr": 3e-5, "weight_decay": 0.01 } }, "zero_optimization": { "stage": 2, "offload_optimizer": { "device": "cpu", "pin_memory": true } }, "gradient_clipping": 1.0, "fp16": { "enabled": true } }

关键参数解释:

  • train_micro_batch_size_per_gpu:每张卡一次前向传播的 batch 大小,这里设为 2 是因为显存有限;
  • gradient_accumulation_steps:梯度累积步数,用于模拟更大的 batch;
  • offload_optimizer:将优化器状态放到 CPU,节省 GPU 显存。

4.3 编写预训练脚本

预训练脚本的核心逻辑是加载配置、初始化模型、设置数据加载器、启动训练循环。

# 文件路径:train/pretrain.py import os import torch from transformers import ( AutoConfig, AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments ) from datasets import load_from_disk def main(): model_name = "your-base-model" config = AutoConfig.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained(model_name, config=config) tokenizer = AutoTokenizer.from_pretrained(model_name) # 设置填充 token,避免训练时报错 if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token train_dataset = load_from_disk("data/tokenized/train") training_args = TrainingArguments( output_dir="./checkpoints/pretrain", evaluation_strategy="steps", eval_steps=500, save_steps=1000, logging_steps=50, per_device_train_batch_size=2, per_device_eval_batch_size=2, gradient_accumulation_steps=4, learning_rate=3e-5, warmup_steps=200, weight_decay=0.01, fp16=True, deepspeed="train/deepspeed_config/zero2.json", save_total_limit=3, report_to="none", ) trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=train_dataset.select(range(1000)), # 示例用法,实际应单独划分 tokenizer=tokenizer, ) trainer.train() trainer.save_model("./checkpoints/pretrain/final") tokenizer.save_pretrained("./checkpoints/pretrain/final") if __name__ == "__main__": main()

运行命令:

cd train && deepspeed --num_gpus=8 pretrain.py

4.4 指令微调:让模型学会对话

基座模型训练完成后,需要进行监督微调(SFT)。这一步的核心是使用高质量的指令数据,让模型对齐用户意图。

# 文件路径:train/sft.py from transformers import AutoModelForCausalLM, AutoTokenizer from peft import LoraConfig, get_peft_model from datasets import load_dataset model = AutoModelForCausalLM.from_pretrained("./checkpoints/pretrain/final") tokenizer = AutoTokenizer.from_pretrained("./checkpoints/pretrain/final") # LoRA 配置 lora_config = LoraConfig( r=8, lora_alpha=16, target_modules=["q_proj", "v_proj", "k_proj", "o_proj"], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) model = get_peft_model(model, lora_config) model.print_trainable_parameters() # 输出 LoRA 层可训练参数量

使用 LoRA 的核心原因是效率。虽然你也可以全量微调,但 LoRA 只需要训练约 0.5% 的参数,显存占用和训练时间都大幅缩减,效果在指令跟随场景下已经足够好。

指令数据集的格式参考前面提到的 JSON 结构。训练完成后保存 LoRA 权重:

model.save_pretrained("./checkpoints/sft/lora") tokenizer.save_pretrained("./checkpoints/sft/lora")

如果需要合并 LoRA 权重到基座模型:

from peft import PeftModel base_model = AutoModelForCausalLM.from_pretrained("./checkpoints/pretrain/final") merged_model = PeftModel.from_pretrained(base_model, "./checkpoints/sft/lora") merged_model = merged_model.merge_and_unload() merged_model.save_pretrained("./checkpoints/sft/merged")

4.5 评测:不要只盯着 Loss 曲线

模型训练完成后,评测环节直接决定你是否能发布。我的评测分为三类:通用能力评测(如 C-Eval、MMLU)、专项任务评测(领域问答、代码生成)、人工抽检。

# 文件路径:eval/run_benchmark.py from datasets import load_dataset from transformers import pipeline generator = pipeline( "text-generation", model="./checkpoints/sft/merged", device=0 ) eval_prompts = [ "请解释什么是反向传播算法", "用 Python 实现一个快速排序", "列出数据库索引失效的三种场景", ] for prompt in eval_prompts: output = generator( prompt, max_new_tokens=256, temperature=0.3, top_p=0.9, do_sample=True ) print("Prompt:", prompt) print("Output:", output[0]["generated_text"]) print("=" * 50)

评测过程中要有“对比基线”,我的建议是至少拿一个同参数规模的开源模型作为对照,否则你无法判断自己的模型到底是好还是坏。

4.6 开源打包与部署

4.6.1 模型权重发布

开源大模型项目,权重发布建议使用 Hugging Face 仓库或 ModelScope。权重文件会包含config.json、tokenizer.json、model-*.safetensors等文件。在 README 中必须写清楚:

  • 基座模型来源与许可证;
  • 训练数据来源与清洗说明;
  • 评测结果;
  • 模型限制与已知问题。
4.6.2 推理服务部署

模型部署最终需要做成可调用的服务。这里用 vLLM 搭建一个高吞吐的推理服务:

# 文件路径:deploy/vllm_server.py from vllm import LLM, SamplingParams llm = LLM(model="./checkpoints/sft/merged", tensor_parallel_size=2) sampling_params = SamplingParams( temperature=0.3, top_p=0.9, max_tokens=512 ) prompts = [ "用一句话解释大语言模型中的 Token 是什么", "写一段 Python 代码,读取 CSV 文件并计算每列平均值", ] outputs = llm.generate(prompts, sampling_params) for output in outputs: print(output.outputs[0].text) print("=" * 50)

使用 vLLM 可以实现连续批处理,大幅提升吞吐,是生产环境推荐的方案。如果你的显存有限,可以先对模型做量化,再部署:

python deploy/quantize.py \ --model_path ./checkpoints/sft/merged \ --quant_method gptq \ --bits 4 \ --output_path ./checkpoints/sft/merged-int4
4.6.3 开源仓库编写

最后一步,写 README。这步千万不能敷衍,因为开源项目的 README 就是项目的门面。我建议的 README 结构是:

  • 项目简介(三句话内说清楚);
  • 整体架构图(使用文字描述);
  • 快速开始(环境安装、推理 Demo);
  • 训练复现(数据处理、预训练、微调);
  • 评测结果(用表格);
  • 已知问题与后续规划;
  • 许可证与引用。

5. 常见问题与排查思路

整个项目踩过的坑实在不少,这里把高频问题整理成表,方便遇到类似报错的读者直接对照排查。

问题现象常见原因解决思路
训练 Loss 为 NaN学习率过大 / 数据中包含异常值降低学习率,检查数据清洗流程,增加梯度裁剪
显存不足 OOMbatch size 过大 / 序列过长减小 micro batch size,开启 DeepSpeed offload,降低 max_length
Token 化速度极慢未开启多进程使用num_proc参数开启多进程,提高数据读取效率
微调后模型胡言乱语指令样本格式不一致 / 基座模型未充分训练统一模板格式,减少低质量样本,必要时对基座模型继续预训练
DeepSpeed 启动失败版本不匹配 / 未正确设置环境变量对照官方文档检查deepspeed、transformers、torch版本
评测分数低于基线模型数据配比不合理 / 评测 prompt 不一致检查数据配比,统一评测 prompt 模板
推理速度太慢未使用 vLLM / 未开启 tensor parallel使用 vLLM 推理框架,显存充足时开启多卡并行

6. 最佳实践与工程建议

6.1 数据永远是第一优先级

这个项目做完,我最深的体会就是:80% 的问题可以通过更好的数据解决,而不是更大的模型。如果你准备做自己的模型项目,先不要急着买 GPU,先用 3~5 天时间把数据管道做到位。数据清洗、去重、配比这三步,值得反复迭代。

6.2 训练工程要留好监控点

训练跑起来不代表万事大吉。我在训练每个阶段都会做以下监控:

  • 每 50 步看一次 Loss 曲线;
  • 每 500 步跑一次小样本评测,而不是等全部训练结束;
  • 每个 checkpoint 都保存优化器状态,方便中途恢复;
  • 训练日志必须带有时间戳和 Token 消耗计数。

6.3 开源许可证要提前确定

开源项目最容易忽略的是许可证问题。如果你的基座模型使用了 Llama 或 ChatGLM 等模型权重,请先确认基座的许可证允许派生作品商用。另外,训练数据如果来自网上抓取,需要评估数据集的合规性。

6.4 版本控制与可复现性

训练工程和普通软件开发一样需要可复现性。我强烈建议:

  • 使用requirements.txt锁定依赖版本;
  • 每个训练实验记录完整超参数和配置文件;
  • 使用实验管理工具记录每次实验的代码版本、数据版本和训练结果。

6.5 安全与合规红线

内容安全审核是部署大模型前必须做的环节。建议增加输入输出过滤,至少对以下类型内容做拦截:

  • 违法和不良信息;
  • 个人隐私数据;
  • 未经授权的商业化内容。

可以引入开源的安全审核模型或关键词过滤方案,避免模型生成风险内容后再被动处理。

7. 总结与学习路线

这篇文章从头到尾梳理了一个开源大模型项目的完整链路:从项目立项、数据清洗与配比、Token 消耗分析、预训练与指令微调,到评测、量化、部署和开源打包。核心内容可以归纳为以下几条:

  • Token 消耗不只是训练,数据清洗和评测同样是大头;
  • 数据质量决定模型上限,数据配比是容易被忽视的关键点;
  • 预训练最好分阶段,指令微调优先尝试 LoRA;
  • 评测不能只看 Loss,必须有基线和人工抽检;
  • 开源不等于丢权重,README、许可证、文档同样重要。

如果你想继续深入,建议按以下顺序学习:

  1. 先熟悉 Hugging Face Transformers 库的基本用法;
  2. 理解 Tokenizer 的原理与不同分词策略的区别;
  3. 自己实现一套小规模数据清洗流程,体会数据质量的差异;
  4. 用单卡完成一次小模型的 LoRA 微调;
  5. 再扩展到多卡训练和 DeepSpeed;
  6. 最后才是完整的预训练和开源发布。

做开源模型项目,最重要的一点是:不要追求一步到位,先把最小可用的闭环跑通,再逐步迭代数据和训练策略。希望这篇文章能帮你省下一些摸索时间,也欢迎在使用过程中交流问题。如果对你有帮助,可以收藏备用,后续我会继续补充推理优化和评测相关的实战内容。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询