https://github.com/wdndev/tiny-llm-zhhttps://github.com/wdndev/tiny-llm-zh目录一、为什么 tiny-llm-zh 值得拿来做训练入门项目二、Tokenizer先把“字和词”变成模型能学的东西三、模型结构它不是玩具架构而是“小号 LLaMA 路线”1. 自回归目标2. 注意力机制3. RMSNorm4. RoPE5. 门控 MLP四、模型规模为什么小参数量反而更适合实战入门五、预训练 PTM从 0 到 1 学会“续写中文”1. 训练入口很工程化2. PTM 脚本直接统计参数量3. 数据读取是“喂 bin”六、SFT把“会续写”变成“会对话”七、效果如果你想真正把“大模型训练”这件事跑通一上来就盯着 7B、13B 甚至更大的模型其实很容易劝退。更适合入门的路线往往不是追求参数越大越好而是先找一个足够小、链路完整、代码可读、能本地改动的项目把“Tokenizer → 预训练 → 指令微调 → 对齐 → 评测 → 部署”这一整套流程走通。tiny-llm-zh就很适合做这件事。这个项目的目标不是刷榜而是从零构建一个小参数量中文大语言模型并把分词、预训练、SFT、人类对齐、测评、量化、部署这些环节尽量完整公开出来README 也明确提醒项目的第一目标是“走通全流程”不是把效果打磨到特别高所以评测分数并不高部分生成会出错。这篇文章我不准备把它写成“README 翻译版”而是站在训练实战视角把这个项目拆成四个问题来看为什么它适合入门大模型训练它的模型结构到底怎么搭它的 PTM 和 SFT 训练链路怎么串起来你能从这个项目学到哪些真正可迁移的训练经验一、为什么 tiny-llm-zh 值得拿来做训练入门项目这个项目最有价值的地方不是单点技术有多新而是它把一条完整中文小模型训练链路拉通了。README 里给出的主线非常清楚Tokenizer → 预训练PTM→ 指令微调SFT→ 人类对齐RLHF / DPO→ 测评 → 量化 → 部署同时它公开了预训练 token 数、SFT 数据量、RL 数据量并提供多种模型规模、vLLM 与 llama.cpp 推理支持。你可以把它理解成一个“迷你版工业流程”。它不是只教你某个局部技巧而是让你看到一个中文 LLM 从词表到最终推理服务训练链路是怎么接起来的。项目 README 明确写到它支持 Bash 脚本启动训练支持不同大小模型并提供了train、script、demo、vllm、llama.cpp等完整目录。二、Tokenizer先把“字和词”变成模型能学的东西训练大模型的第一步不是堆参数而是先解决一个最基础的问题模型到底以什么单位来读中文README 在 Tokenizer 部分说得很直接LLM 分词器常见有两条路一条是自己训练词表和分词器另一条是直接选开源模型已有的分词器。这个项目两条路都保留了一方面tokenizer目录提供了扩充词表的实现思路另一方面README 也明确说明当前主项目实际使用的是 ChatGLM3 的词表。这件事背后的训练直觉其实很重要中文不像英文那样天然有空格分词词表设计会直接影响序列长度训练效率参数规模稀有词覆盖能力下游中文生成质量如果把一句文本写成 token 序列 x1,x2,…,xTx_1, x_2, \dots, x_Tx1​,x2​,…,xT​那后续无论是预训练还是 SFT本质上都在学习这个序列上的条件概率分布所以 Tokenizer 不是“前处理细节”而是训练目标的起点。三、模型结构它不是玩具架构而是“小号 LLaMA 路线”这个项目并不是随便拼一个 Transformer 就开练。README 明确写到主模型采用的是类 Llama2 结构包括RMSNorm、RoPE、MHA等核心组件而train/modeling_tinyllm.py里也确实实现了TinyllmRMSNorm、TinyllmRotaryEmbedding、TinyllmAttention和门控 MLP。1. 自回归目标作为因果语言模型它的核心训练目标仍然是 next-token prediction这也是ptm_train.py使用TinyllmForCausalLM做预训练的本质给定一串 token让模型学会预测下一个 token。2. 注意力机制在TinyllmAttention里Q/K/V 线性映射、自回归 mask、缩放点积注意力都很标准其中 MMM 是因果 mask保证当前位置不能偷看未来 token。代码里也明确把is_causal设为了True。3. RMSNorm这个项目没有用 LayerNorm而是走了 LLaMA 系路线里很常见的 RMSNorm。它的一个常见写法是在TinyllmRMSNorm里代码先对 hidden states 计算平方均值再乘以rsqrt得到归一化结果最后乘上可学习参数weight。4. RoPE位置编码部分走的是旋转位置编码RoPE。如果把一对偶数维与奇数维特征看成二维向量那么它的核心可以写成项目中的TinyllmRotaryEmbedding预先缓存了cos与sin再通过apply_rotary_pos_emb把旋转编码应用到 Q 和 K 上。5. 门控 MLPTinyllmMLP不是最朴素的两层前馈而是门控结构gate_proj、up_proj、down_proj配合激活函数使用这和 LLaMA 系模型的门控前馈网络思路是一致的。代码里用的是其中 ϕ 是激活函数项目配置默认使用silu。四、模型规模为什么小参数量反而更适合实战入门README 给出了从 16M 到 1.5B 的多组配置最常见的是 16M、42M、92M、210M、440M。项目训练脚本ptm_demo.sh和sft_demo.sh也把这些模型规模写成了可切换参数。下面这张表我按 README 和脚本里的配置整理了一下模型hidden sizeintermediate sizelayersheadsmax context参数量vocab sizetiny-llm-16m1203846651216M64798tiny-llm-42m2887686651242M64798tiny-llm-92m5121408/1024*88102492M64798tiny-llm-210m768204816121024210M64798tiny-llm-440m1024281624161024440M64798README 的模型尺寸表里写的是1024而ptm_demo.sh与sft_demo.sh中 92M 配置使用的是1408。为什么我反而觉得这种“小参数量多档位”很适合学习因为它让你能真正观察到hidden size 增大后显存怎么涨层数增加后训练速度怎么变context length 提高后吞吐如何变化模型质量和资源消耗之间如何取平衡这类“可控的小规模实验”比死盯一个大模型 checkpoint 更有训练感。五、预训练 PTM从 0 到 1 学会“续写中文”在train/ptm_train.py里项目是直接根据配置创建 TinyllmConfig再实例化 TinyllmForCausalLM从头开始训。也就是说PTM 阶段不是在一个现成中文大模型上继续预训练而是先造出一个小模型再喂预训练数据。代码主线可以概括成下面这张图1. 训练入口很工程化ptm_demo.sh不是一条简单命令而是把torchrunDeepSpeedfp16 / bf16ZeRO stage学习率调度batch size多机多卡参数都集中在脚本里统一管理。默认配置里可以看到N_GPUS8、ZERO_STAGE2、LR3e-4、LR_SCHEDULER_TYPEcosine、TRAIN_EPOCHS5这样的训练超参数。2. PTM 脚本直接统计参数量ptm_train.py在模型初始化后会打印总参数和可训练参数这对检查模型规模是否与你预期一致非常有用。3. 数据读取是“喂 bin”脚本里从dataset_dir_or_path递归找.bin文件再交给PTMDataset。这说明项目在预训练阶段更偏向“先离线处理语料再高效喂训练器”的路线而不是训练时现场拼接文本。六、SFT把“会续写”变成“会对话”只做 PTM模型学到的是广义语言建模能力但用户真正需要的是能按指令回答能理解 system / user / assistant 结构能更像助手而不是随机续写器这就是 SFT 的作用。在train/sft_train.py里项目流程变成了从base_model_path加载预训练好的基础模型加载 tokenizer构造SFTDataset用Trainer继续训练保存last_sft_model。在训练目标上SFT 仍然是交叉熵但通常不会对整段输入都平均计算损失而是重点监督“回答区域”。一个常见写法是这样模型更专注于学“怎么回答”而不是去死记 system 和 user 的模板格式。sft_demo.sh也体现了这条链路它默认从outputs/ckpt/ptm_tiny_llm_92m_epoch5/last_ptm_model加载基础模型再用data/sft_train/sft_data.jsonl做 SFT。七、效果