
最近 Show HN 上出现了一个名为 AQ 的项目两位开发者在印度从零训练了一个 10 亿参数规模的学术大语言模型。这个项目真正值得关注的地方不是参数数量而是“从零”这两个字。从零意味着分词器要自己训练、语料要自己清洗、模型结构要自己搭建、训练脚本要自己调通、评测要自己设计整条链路没有任何现成模型权重可以依赖。对大多数开发者来说1B 是一个很合适的观察窗口。它足够大能完整暴露大模型训练里的数据、显存、精度、稳定性等工程问题又足够小单张 24GB 显存显卡配合梯度累积和激活检查点就有机会跑通。本文不介绍 AQ 模型的内部细节那要以官方发布为准而是以这类项目为背景梳理从零训练一个 1B 学术模型必须处理的数据、分词器、模型结构、训练稳定性、评测和排错问题并给出一套可以直接参考的工程路径。1. 1B 学术大模型到底“从零训练”了什么1.1 从零训练不是只训练网络很多第一次接触大模型训练的开发者会把“从零训练”理解为写一个 Transformer 模型然后喂语料跑反向传播。真正进入项目后才发现训练网络只占整条链路的一小部分。一个完整的小规模 LLM 训练链路至少包括语料采集、清洗、去重、质量过滤和配比设计分词器训练与特殊符号设计模型架构选择和超参数配置训练脚本、数据加载、精度管理、断点续训训练过程中的 loss、梯度、吞吐量监控困惑度、生成样例、下游任务和人工评估每次实验的数据版本、配置版本、权重版本管理任何一个环节出错模型最终都会以很难排查的方式表现出来。比如语料里混入了大量重复文本loss 会一直降但生成结果空洞分词器词表与实际模型配置不一致推理时会出现大量未知词评测数据没做污染检查指标虚高但真实能力不足。1.2 1B 规模为什么适合小团队和学术研究学术研究需要的是“可控性”。1B 模型可以在有限算力下做多组对照实验验证数据配比、学习率、上下文长度、分层结构等因素的影响再把结论外推到更大规模。从计算量上看可以做一个粗略估算。参考 Chinchilla 论文给出的约 20 tokens/parameter 的经验值1B 模型需要大约 20B token 的语料。训练计算量约为 6 × 参数 × token 数也就是 6 × 1B × 20B 1.2 × 10^20 FLOPs。单张 A100 的实用吞吐量大约在每秒 3 × 10^14 FLOPs 量级8 卡并行也需要数天时间。所以小团队通常会把一个实验版本的 token 数降到 5B 到 20B 之间先把模型从 0 跑到 1再决定是否扩展。这个规模还意味着可以在 3090、4090 这类消费级显卡上做小批量调试。很多超参数问题在 1B 规模上就能暴露不需要等到百亿甚至千亿参数才发现。1.3 从 AQ 这类项目看小团队的工程取舍AQ 这类由两人团队完成的项目工程上通常有三个明显取舍。第一依赖公开数据集但在领域比例上做取舍。学术模型一般会混入论文、教材、百科、代码等数据其中论文类数据的比例会被刻意调高。第二训练目标不追求“全能”而是追求可评测、可复现。学术模型更关注在公开 benchmark 上的表现和实验记录是否完整。第三用更频繁的中间评测替代一次性的最终评测因为小团队的容错空间小等到训练结束再发现问题重跑成本很高。这些取舍对普通开发者同样有参考价值预算有限时先把一条完整链路跑通再逐步提升数据质量比一开始就堆算力更有效。2. 数据与分词器是第一个真正的大工程2.1 数据来源、清洗与去重学术 LLM 的语料以论文、教材和技术文档为主常见公开来源包括 arXiv、PubMed、S2ORC、Wikipedia、The Pile 和 Common Crawl 的子集。不要直接使用原始爬虫文本里面包含大量导航、广告、HTML 标签和重复段落。清洗流程可以按这个顺序执行用 trafilatura 或 readability 提取正文去掉 HTML 噪音用 fastText 语言检测过滤掉无关语言过滤过短、符号占比过高、连续重复行过多的文档用精确哈希去掉完全重复的文档用 MinHash 去掉近似重复的文档用域名和关键词黑名单去掉垃圾内容对学术文档做公式、表格、引用的噪音处理import trafilatura # 从 URL 抓取并提取正文替换原始 HTML downloaded trafilatura.fetch_url(https://example.com/paper.html) text trafilatura.extract(downloaded)from datasketch import MinHash def build_minhash(text: str, num_perm: int 128): m MinHash(num_permnum_perm) for token in text.split(): m.update(token.encode(utf-8)) return m # 对每个文档生成 MinHash再放入 MinHashLSH 做近似去重这里要特别注意近似去重对学术语料非常重要。同一个论文主题会被不同课程笔记反复改写不做去重模型会反复记忆同一批观点生成时出现严重重复。2.2 训练自己的 BPE 分词器为什么不能直接拿其他开源模型的分词器用学术领域有大量领域词汇直接使用通用分词器会把一个术语切得支离破碎还可能因为词表 ID 不一致导致模型配置和分词器不匹配。训练自己的分词器是“从零训练”的一部分。推荐使用 SentencePiece 训练 BPE 分词器设置好特殊符号和词表大小import sentencepiece as spm spm.SentencePieceTrainer.Train( inputcorpus_clean.txt, model_prefixaq_tokenizer, vocab_size32768, model_typebpe, character_coverage0.9995, bos_id1, eos_id2, pad_id0, unk_id3, bos_pieces, eos_piece/s, pad_piecepad, unk_pieceunk, user_defined_symbols[[MATH], [CODE], [TABLE]], )训练完成后必须做一次编码解码回环测试python -c import sentencepiece as spm sp spm.SentencePieceProcessor(model_fileaq_tokenizer.model) ids sp.encode(transformer is a deep learning model) print(ids) print(sp.decode(ids)) 如果 decode 结果和原文不一致说明词表或特殊符号配置有问题需要重新训练。2.3 数据混合比例与质量过滤数据混合比例没有统一答案但可以从领域目标反推。以学术模型为例论文类数据比例会明显高于通用模型。下面是一个示例配置实际项目要按语料可用量调整数据源类型参考比例清洗重点学术论文论文、预印本30%-40%去公式噪音、去模板重复、去引用列表Wikipedia百科10%去维护模板、去分类导航书籍教材、专著10%-15%OCR 修复、版权过滤代码开源代码5%-10%去自动生成文件、去敏感配置网页文本通用网页20%-30%去广告、去导航、去重复段落质量过滤时可以用几个简单规则快速排除垃圾文本文本长度小于 200 字符的丢弃连续重复行占比超过 30% 的丢弃标点符号占比异常的丢弃。也可以用一个小的语言模型计算语料困惑度把困惑度明显偏高的文档视为低质量文本。3. 模型结构和训练配置要围绕 1B 参数做减法3.1 架构选择RMSNorm、RoPE、SwiGLU、GQA1B 模型的基本架构通常采用 decoder-only 风格但会在细节上做工程取舍。常用组件可以整理成一张表组件作用为什么适合小团队RMSNorm替代 LayerNorm去掉均值中心化计算更快数值稳定性接近RoPE 旋转位置编码给注意力注入位置信息支持上下文长度外推训练稳定SwiGLU 激活函数提升表达能力效果更好但参数量略高GQA 分组查询注意力减少 KV 头数量降低显存和推理开销共享词嵌入输入输出词表共享权重显著减少参数量GQA 是 1B 模型里很值得加的一个设计。全量多头注意力在推理时每个头都要保存 KV 缓存用 GQA 后 KV 头可以降到 4 个甚至更少推理阶段的内存和带宽压力会明显下降。3.2 一份可落地的 1B 模型配置下面是一份接近 1B 参数的示例配置列表里的数值用于说明思路落地时要根据自己分词器词表和显存情况调整config { vocab_size: 32768, hidden_size: 2048, intermediate_size: 5632, num_hidden_layers: 24, num_attention_heads: 16, num_key_value_heads: 4, max_position_embeddings: 4096, rms_norm_eps: 1e-6, rope_theta: 500000, use_bias: False, use_swiglu: True, tie_word_embeddings: True, }用这个配置估算参数量词嵌入部分为 32768 × 2048 ≈ 6700 万每一层 Transformer 中注意力约 1000 万MLP 约 3400 万24 层合计约 11.3 亿加上共享词嵌入总计约 12 亿。MLP 的 intermediate_size 一般取 hidden_size 的 2.7 到 3 倍SwiGLU 因为有三个线性层实际计算量和参数量要单独核算。3.3 fp32、fp16、bf16 怎么选训练大模型时精度选择直接影响收敛速度和稳定性。这是网上讨论非常多的问题这里给出一个实用结论格式位宽指数位尾数位适用场景fp3232823基线计算、优化器状态fp1616510早期加速方案需要 loss scalingbf161687LLM 预训练推荐动态范围与 fp32 一致fp8852推理加速和部分训练场景精度风险更高fp16 的问题在于尾数位只有 10 位小数值很容易溢出。bf16 牺牲了尾数精度但保留了与 fp32 相同的动态范围训练中不易出现损失溢出所以成为当前预训练的主流选择。在 PyTorch 中bf16 的使用非常简单with torch.autocast(device_typecuda, dtypetorch.bfloat16): logits model(input_ids)如果必须使用 fp16则需要引入 GradScaler 处理梯度缩放from torch.cuda.amp import GradScaler scaler GradScaler() with torch.autocast(device_typecuda, dtypetorch.float16): loss model(input_ids) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()实际项目里通常让模型权重和优化器状态保持 fp32前向和反向计算使用 bf16这样可以兼顾数值稳定性和显存占用。4. 训练工程显存、断点续训和日志监控4.1 先估算显存再决定并行方案训练 1B 模型前建议先做显存估算不要直接开跑。显存项目1B 模型约占用模型权重 fp324GB模型权重 bf16 副本2GBAdam 一阶动量 fp324GBAdam 二阶动量 fp324GB梯度 bf16 或 fp322GB 到 4GB激活值取决于 batch size 和序列长度可能数 GB也就是说仅模型、梯度和优化器状态就可能在 16GB 到 18GB 之间。24GB 显存能跑但很紧张必须配合激活检查点、梯度累积和序列长度控制。如果只有单张 24GB 显卡推荐组合是bf16 混合精度 激活检查点 梯度累积 DeepSpeed ZeRO-1 或关闭分片的 FSDP。如果有多张卡可以使用 FSDP 或 DeepSpeed ZeRO-2 把模型分片到多卡。4.2 一个最小训练循环怎么写训练循环的关键点在于梯度累积、梯度裁剪和学习率调度。下面是一个可以直接作为起点的示例import torch model.train() optimizer torch.optim.AdamW( model.parameters(), lr3e-4, weight_decay0.1, betas(0.9, 0.95), ) scheduler torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_maxsteps_total ) grad_accum_steps 8 optimizer.zero_grad() for step, batch in enumerate(train_loader): input_ids batch[input_ids].to(device) labels input_ids[:, 1:].contiguous() with torch.autocast(device_typecuda, dtypetorch.bfloat16): logits model(input_ids[:, :-1]) loss torch.nn.functional.cross_entropy( logits.view(-1, config[vocab_size]), labels.view(-1), ignore_index-100, ) loss loss / grad_accum_steps loss.backward() if (step 1) % grad_accum_steps 0: torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() scheduler.step() optimizer.zero_grad() if step % 100 0: print(fstep {step} loss {loss.item():.4f} lr {scheduler.get_last_lr()[0]})这里有两个容易忽略的细节。第一loss 除以 grad_accum_steps 是为了把多次微批次的梯度平均到同一量级否则有效学习率会随累积步数放大。第二ignore_index-100 是为了让填充位置不参与损失计算否则模型会花费精力去学习预测无意义的 pad token。4.3 断点续训和 loss spike 自动保护从零训练的周期长断点续训不是可选项。每个 checkpoint 至少保存模型、优化器、学习率调度器、步数和随机数状态checkpoint { model: model.state_dict(), optimizer: optimizer.state_dict(), scheduler: scheduler.state_dict(), step: step, config: config, rng_state: torch.get_rng_state(), } torch.save(checkpoint, fckpt_{step}.pt)训练稳定性监控要盯三个指标loss、梯度范数、当前学习率。更稳妥的做法是加一道 loss spike 保护维护最近若干步的滑动平均 loss当前步 loss 如果超过滑动平均的若干倍就回退到上一个 checkpoint降低学习率后继续训练。if loss loss_avg * 3: print(floss spike at step {step}: {loss.item():.4f}) restore_from_checkpoint(last_ckpt) optimizer.param_groups[0][lr] * 0.5 continue日志建议同时输出到本地文件和 wandb 或 TensorBoard因为训练结束后复盘实验时loss 曲线和梯度范数曲线是定位问题最有用的证据。5. 评测不能只看 loss要把“学术能力”拆开看5.1 困惑度、生成样例和下游任务评测预训练阶段最常用的在线指标是评估集困惑度perplexity但它只能反映模型对语料分布的拟合程度不能反映问答、推理和生成质量。完整的评测至少分三层困惑度评估在留出的、与训练集同分布的语料上计算 PPL生成样例用人工挑选的学术类 prompt 查看模型输出质量下游任务用公开 benchmark 评估常识、推理、知识能力下游任务可以用 lm-evaluation-harness 快速跑起来pip install lm-evaluation-harness lm_eval --model hf \ --model_args pretrained./aq-1b \ --tasks hellaswag,arc_easy,piqa,winogrande,openbookqa \ --device cuda:0 \ --batch_size 8需要注意小模型的 benchmark 分数波动很大单次评测结果不能说明问题。建议固定 prompt 模板多次运行取均值并且把评测代码和版本一起记录。5.2 数据污染检查训练集和评测集必须隔离如果训练语料里混入了评测集样本模型等于“背过答案”指标会虚高。这个问题在小模型上尤其容易发生因为训练语料往往来自公开爬虫而公开 benchmark 的题目也来自网页。至少做三件事训练前把评测集的所有 prompt 和答案从语料中精确删除用 MinHash 对评测集和训练集做近似重叠检查在训练过程中定时计算评估集困惑度观察是否提前出现剧烈下降写成脚本可以这样python filter_overlap.py \ --train corpus_clean.txt \ --eval eval_prompts.jsonl \ --n 8n 表示 n-gram 重叠长度。如果训练语料和评测数据存在 8-gram 级别的连续重叠基本可以判定污染。5.3 多次小步迭代比一次大训练更可靠小团队最常见的浪费是直接启动大规模训练跑了一周后发现模型生成质量糟糕却不知道问题在数据还是架构。推荐的做法是先跑小规模实验用 300M 左右的小模型在 1B 到 2B token 上做 10 到 20 步的快速验证检查 loss 是否按预期下降、生成样例是否可读确认没有 NaN、分词错误、数据加载错位等问题再放到 1B 模型上启动正式训练在正式训练过程中每 1000 步保存一个中间 checkpoint每遇到一个 checkpoint 就跑一次轻量评测。这样即使最终版本不理想也能从中间 checkpoint 里挑选出可用的版本。6. 从零训练最常见的坑和排查链路小团队训练 LLM 时问题往往集中在训练稳定性、数据质量和评测可信度三个方向。下面整理了一张常用排查表问题现象常见原因检查方式处理建议loss 变 NaN学习率过高、fp16 溢出、数据含异常值检查梯度范数、输入数据、logits 是否包含 NaN使用 bf16、降低学习率、开启梯度裁剪loss 突然飙升学习率调度异常、数据批次重复、梯度累积归一化错误回放 loss 曲线和梯度范数曲线回退最近 checkpoint降低学习率继续训练生成结果大量重复语料去重不彻底、训练步数过长检查语料重复率、生成测试加强 MinHash 去重提前停止或增加数据解码出现大量 unk分词器词表与模型配置不一致做 encode/decode 回环测试重新训练或统一词表 ID 映射PPL 很低但任务分数低评测数据污染、数据分布偏差检查训练集与评测集重叠度清洗评测数据增加领域数据比例训练吞吐量低数据加载瓶颈、序列 padding 浪费观察 GPU 利用率和 dataloader 耗时用序列打包、加大 num_workers、开启 prefetch6.1 loss 变 NaN 或突然飙升这是最常见也是最紧急的问题。第一反应不是改代码而是先确认是哪个环节出现 NaN。排查顺序输入数据是否有空文档或异常 token id前向 logits 是否包含 NaN梯度范数是否在某一层爆炸当前学习率和 warmup 步数是否合理是否开启了 fp16 而没有配置 loss scaling如果是 fp16 问题直接换 bf16 通常能解决。如果是学习率问题把峰值学习率从 3e-4 降到 1e-4 再试。如果是数据问题定位到具体 batch 后从数据管线里过滤掉。6.2 分词器出现未登录词或词表不一致症状是训练正常但生成时出现大量[UNK]或[PAD]。原因一般是分词器训练时设置的特殊 token 顺序和模型初始化时的 pad_token_id、bos_token_id、eos_token_id 不一致。解决办法是建立一次全链路校验加载分词器和模型配置后打印所有特殊 token 的 ID确认与训练时一致并跑一轮编码解码回环测试。sp spm.SentencePieceProcessor(model_fileaq_tokenizer.model) for token in [s, /s, pad, unk, [MATH]]: print(token, sp.piece_to_id(token))6.3 模型“死记”训练集下游任务不涨如果训练 loss 不断下降而评测集指标停滞首先要怀疑评测数据污染其次要怀疑数据分布过于狭窄。学术语料如果只来自单一期刊或单一爬虫源模型会记忆文章片段但无法泛化到问答和推理场景。建议在数据配比中保留 10% 以上的通用文本例如 Wikipedia 和书籍并定期生成一批与训练数据风格不同的 prompt 做人工评测。7. 学习环境、单卡环境和生产环境的边界小团队从零训练模型必须区分清楚自己是在哪个环境里工作不同环境的工程要求完全不同。环境类型硬件参考数据规模目标核心任务学习环境CPU、Colab、小显存显卡100M 到 1B token跑通代码链路理解训练循环、分词器、评测流程单卡开发环境24GB 显存显卡5B 到 20B token小规模实验验证调超参、测数据、排 bug生产训练环境多卡集群、多节点50B token 以上发布模型权重监控、断点续训、数据版本管理、模型评估7.1 本地学习怎么跑通最小案例如果是学习目的不需要从 1B 开始。可以先建一个 10M 到 50M 参数的小模型用几万条文本跑几十步确认数据管线、训练循环、评测脚本都能跑通。这个阶段要重点关注过程而不是结果每次运行后检查 loss 是否稳定下降观察日志里是否出现 NaN 或异常梯度用固定 prompt 看生成结果是否有变化建议把整个项目放在一个仓库里使用固定版本的依赖。llm、tokenizers、datasets、transformers 这四类库的版本变化都会导致行为差异。7.2 单卡 24GB 训练 1B 的可行方案单卡 24GB 训练 1B 是可行的但要把很多设置在“勉强可用”的边界上使用 bf16不单独保存 fp32 权重副本开启激活检查点牺牲约 30% 的吞吐量换取显存微批次大小设为 1梯度累积设为 16 到 32序列长度从 4096 降到 2048先保证能跑使用 DeepSpeed ZeRO-1 把优化器状态分片并可选 offload一个参考命令deepspeed train.py \ --deepspeed ds_config.json \ --model config.json \ --batch_size 1 \ --grad_accum_steps 32 \ --seq_len 2048 \ --bf16这种配置的吞吐量不会太高但它能让小团队在不依赖集群的情况下完成 1B 模型的完整训练链路这对验证数据方法和评测流程已经足够。7.3 生产训练还必须补哪些模块进入生产环境后单机脚本要扩展成一套工程系统配置外置训练参数、数据路径、模型配置全部用配置文件注入日志监控loss、梯度范数、吞吐量、显存、温度全部上报数据版本管理每次实验记录语料来源、清洗脚本和版本哈希模型注册每个 checkpoint 记录评测结果和训练配置回滚机制loss 异常时自动回退 checkpoint权限与安全训练数据、代码、权重按角色隔离很多开源项目失败不是因为模型架构不对而是因为实验不可复现。配置、数据、代码、评测结果和权重必须捆绑记录这是小团队最值得投入的工程习惯。8. 小团队训练完模型之后评测、落地和扩展8.1 模型发布前要做的检查清单无论模型是发到 Hugging Face 还是内部使用发布前建议按清单逐项检查模型 config 与训练配置一致隐藏层、头数、词表大小无偏差分词器文件与模型 config 里的 vocab_size 一致特殊 token ID 在加载后能正确对应评测报告包含任务名称、prompt 模板、运行次数、均值与方差训练数据和评测数据无重叠生成样例覆盖多个领域不只有训练集风格已知限制要写明例如上下文长度上限、领域外表现差、存在幻觉可能数据来源和训练资源要说明便于他人判断可复现性8.2 从基座模型走向应用SFT、RAG、Agent 与 MCP预训练得到的 1B 模型只是基座不能直接作为产品使用。常见扩展路径是SFT 微调用问答数据让模型学会回复格式DPO 或 RLHF让模型对齐偏好减少有害和空洞输出RAG 检索增强把学术论文、知识库的检索结果拼到 prompt 里缓解幻觉Agent 工具调用让模型按格式输出调用参数MCP 协议通过标准化接口连接外部工具和数据源量化与推理服务用 vLLM、TGI 或 GGUF 部署降低显存和延迟对 1B 模型来说RAG 尤其重要。它本身知识容量有限无法记住整个学术领域的事实但通过检索可以把外部知识带进上下文在算力受限的场景里性价比很高。8.3 对小团队和学术研究者的建议从零训练 1B 模型最重要的不是模型结构而是流程纪律。每一次实验都记录数据版本、配置版本、日志路径和评测结果每次改动只动一个变量每个 checkpoint 都能独立加载和评测。对新手来说最值得做的一个练习是在单卡环境下用公开语料训练一个 100M 到 300M 的小模型完整走一遍清洗数据、训练分词器、搭建模型、训练、评测、发布检查清单的流程。这个小模型的表现并不重要真正收获的是理解整条链路里每个环节的失败模式。AQ 这类项目给行业的启示在于小团队也能做有意义的基础模型工作前提是把工程边界控制好。1B 规模既是算力限制下的务实选择也是研究训练方法和数据策略的合适实验场。下一步无论往更大规模走还是往垂直领域微调走这段从零训练的经验都会成为最踏实的底子。