最近在 Hacker News 上看到一个很有意思的项目:AQ。它的标题信息量很大——一个来自印度的两人团队,从零训练了一个 1B 参数的学术 LLM。没有套壳开源模型,没有基于 Llama 做 LoRA 微调,而是真正从数据、tokenizer、预训练一路做到对齐和评估。
这个项目让我想写一篇比较完整的拆解文章。一方面,“1B 参数”是个人开发者和小团队最容易摸到的模型规模档位;另一方面,“from-scratch”这条路虽然辛苦,但对理解 LLM 底层机制非常有帮助。
在动笔之前先声明一点:本文不是对 AQ 项目代码的逐行解析,而是围绕“一个 2 人小团队如何从零训练 1B 学术 LLM”这个主题,把完整技术路线拆开讲清楚。文章会覆盖数据工程、分词器、模型架构、预训练、指令微调、对齐、评估、量化和部署,最后附上常见问题排查和工程建议。即使你没有自己的训练集群,也可以把这里的思路用在小型实验、课程项目或者开源模型研究中。
1. 项目背景:为什么 1B 学术 LLM 值得关注
1.1 “from-scratch”与小团队定位
过去两年里,大模型领域的“造词运动”很盛行,很多项目标称“自研大模型”,实际只是把开源模型拿来做 SFT(监督微调)。从训练视角来看,这属于微调,不是预训练。
AQ 的价值在于它选择了更硬核的路线——从随机权重开始,经过数据清洗、tokenizer 训练、预训练、指令微调、偏好对齐等完整流程。这种做法的优点是团队能完全掌握模型行为,不会被上游模型的数据分布和偏见绑架;缺点是成本高、周期长、坑多。
两位开发者能完成这件事,说明他们对整个链路有非常系统的理解。对于国内开发者来说,这个项目最大的参考价值不是“我也要复刻一个 AQ”,而是“在有限的算力下,如何用工程手段把 1B 模型做到可用”。
1.2 为什么 1B 参数是“甜蜜点”
- 1B 左右的模型在消费级显卡上可以做推理,甚至在 CPU 上也能跑出可接受的速度。
- 训练成本比 7B、13B 低一个数量级,小团队可以负担。
- 学术场景中,1B 模型适合做可控实验,可以快速验证数据配比、架构改动、对齐方法。
- 它具备基本的推理、问答、摘要能力,虽然不如大模型全面,但作为研究基座足够。
在当前的 LLM 生态里,1B 模型通常被当作研究工具或垂直领域底座。你做 RAG(检索增强生成)、Agent 工具调用、领域知识问答,1B 模型配合外部知识库往往能打出不错的性价比。
1.3 “academic LLM”意味着什么
AQ 标注“academic LLM”,说明它的设计目标偏向学术研究与可复现性,而不是追求商业级综合能力。这意味着项目会更重视数据配比的记录、训练过程的透明度、评估基准的公开性。
这一点对研究者和学生非常友好。通过这类项目,你能摸清一套开源小模型从零训练的完整工程闭环,这比直接读论文更容易落地。
2. 1B 级 LLM 从零训练的总体技术路线
2.1 训练流水线全景
从零训练一个 1B 模型,大致可以划分成以下阶段:
- 数据收集与过滤。
- Tokenizer(分词器)训练。
- 预训练(Pre-training)。
- 指令微调(SFT)。
- 偏好对齐(DPO/RLHF)。
- 评估与迭代。
- 量化压缩与部署。
每个阶段都会直接影响最终模型质量。数据决定上限,模型架构决定拟合能力,训练策略决定收敛效率,对齐决定可用性。
2.2 三个核心规模决策
在小团队资源有限的情况下,最先要确认三个规模:
- 数据规模:对 1B 模型,常见的预训练数据量在 100B~500B token 之间(取决于算力)。数据太少模型欠拟合,太多则算力浪费。
- 批量大小与学习率:一般使用较大 batch,配合 cosine 学习率调度。
- 训练步数:通常训练 1~3 个 epoch,多轮重复数据容易导致过拟合。
规模决策是经验与算力的折中,不能盲目照搬大模型配置。
2.3 项目时间与资源规划
两人团队做这件事,合理的规划是:
- 数据准备:2~4 周。
- Tokenizer 与模型代码验证:1~2 周。
- 预训练小规模试跑(验证 loss 下降):1 周。
- 正式预训练:2~6 周(取决于 GPU 数量)。
- SFT 与对齐:1~2 周。
- 评估与迭代:2 周以上。
如果只有 1~2 张消费级显卡,完整预训练 1B 模型会很吃力,通常需要用到多卡并行或云上租用算力。即便 AQ 是两人团队,大概率也依赖了多卡训练环境。
3. 数据工程:从零训练最关键的生命线
很多项目失败不是因为模型结构没选对,而是数据太脏。下面拆解数据环节的几个关键动作。
3.1 数据来源与版权筛选
学术 LLM 通常希望数据尽可能公开、可追踪、版权清晰。常见的数据源包括:
- Wikipedia、Arxiv、PubMed 等学术公开数据。
- 开源代码仓库,以及开源许可证允许的数据集。
- 公开的书籍、论文、专利摘要。
- 各机构发布的开放数据集(如 RedPajama、The Pile 的子集)。
在筛选时,必须确认许可协议。学术用途不等于可以随意抓取,版权风险在发布模型权重时会被放大。
3.2 数据清洗与去重
数据清洗的几个常用步骤:
- 去除 HTML 标签、乱码字符、不可见 Unicode 字符。
- 统一换行和空格,处理全半角符号。
- 检测并去除低质量段落(过短、重复、无语义内容)。
- MinHash 去重,消除网页之间的重复内容。
- 根据困惑度过滤低质量文本。
下面的代码演示了一个简化版数据清洗流程:
import re import hashlib from typing import Iterable def clean_text(text: str) -> str: # 去除 HTML 标签 text = re.sub(r"<[^>]+>", "", text) # 去除控制字符 text = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f]", "", text) # 统一换行 text = text.replace("\r\n", "\n").replace("\r", "\n") # 合并多余空行 text = re.sub(r"\n{3,}", "\n\n", text) return text.strip() def is_low_quality(text: str, min_len: int = 200) -> bool: # 过短、连续重复、标点占比过高等都可以视为低质量 if len(text) < min_len: return True if len(set(text)) < 50: return True return False def minhash_deduplicate(texts: Iterable[str], threshold: int = 5): seen = set() for text in texts: # 简化版去重:用句子集合的 hash 做粗筛 sentences = set(re.split(r"[。!?.!?]", text)) sig = hashlib.md5( "|".join(sorted(sentences)[: threshold]).encode("utf-8") ).hexdigest() if sig in seen: continue seen.add(sig) yield text实际工程会使用更复杂的 MinHash + LSH 方案,但核心思想一致:去掉重复内容,保留多样性。
3.3 数据配比与采样
不同来源的数据对模型能力的影响不同。常见的配比逻辑是:
- 通用网页数据占大头,保证语言能力和知识广度。
- 学术论文、书籍提升推理和深度知识。
- 代码数据提升逻辑能力和指令跟随能力。
- 对话数据虽然量少,但直接影响模型交互质感。
配比不是一次定死的,通常需要做小规模预训练实验,观察不同配比在 benchmark 上的表现差异。
3.4 数据格式与流式读取
预训练数据通常保存为 JSONL 格式,每个样本是一个 JSON 对象,包含text字段。为了高效流式读取,建议使用datasets库或自研内存映射读取器。
{"text": "Transformer is a neural network architecture based on self-attention..."} {"text": "In this paper, we propose a novel method for language modeling..."}流式加载的参考代码:
from datasets import load_dataset from torch.utils.data import DataLoader dataset = load_dataset( "json", data_files="data/train.jsonl", streaming=True, ) def tokenize_fn(example): return tokenizer( example["text"], truncation=True, max_length=2048, ) dataset = dataset.map(tokenize_fn) loader = DataLoader(dataset, batch_size=8)这里需要注意,tokenizer必须在进入map前完成加载,流式数据集不会自动缓存 tokenize 结果,所以每次迭代都会重复计算。生产环境建议先做离线 tokenize 再开始训练。
4. 分词器与模型架构设计
4.1 训练自己的 tokenizer
很多人会忽略 tokenizer 的重要性。对于英语和代码,BPE(Byte Pair Encoding)效果稳定;如果要支持中文,还要考虑是否混入中文字符级 token。
对于 1B 模型,词表大小一般选择 32K~128K。词表太小会导致序列过长、训练变慢;词表太大会让 embedding 矩阵占大量显存。
训练一个 SentencePiece tokenizer 的参考代码:
import sentencepiece as spm spm.SentencePieceTrainer.train( input=["data/corpus.txt"], model_prefix="aq_tokenizer", vocab_size=32768, model_type="bpe", character_coverage=0.9995, max_sentence_length=4096, pad_id=0, unk_id=1, bos_id=2, eos_id=3, )训练完成后会生成aq_tokenizer.model和aq_tokenizer.vocab。需要注意:tokenizer 的训练数据要覆盖预训练语料的分布,否则会出现大量 unknown token,严重降低模型效果。
4.2 1B 模型架构选择
当前中小型 LLM 的主流架构基本都基于 Transformer,但会做一些调整:
- RMSNorm 替代 LayerNorm,训练更稳定。
- SwiGLU 激活函数,提升非线性表达能力。
- RoPE(旋转位置编码),更好处理长序列。
- GQA(分组查询注意力),降低推理显存开销。
- 移除 bias 项,减少参数并提升训练稳定性。
以 1B 参数为例,常见配置大致如下:
| 超参数 | 常见取值 |
|---|---|
| hidden_size | 2048 |
| intermediate_size | 5632 |
| num_hidden_layers | 24 |
| num_attention_heads | 16 |
| num_key_value_heads | 8(GQA) |
| vocab_size | 32768 |
| max_position_embeddings | 4096 |
| rms_norm_eps | 1e-5 |
不同框架的命名略有差异,实际以你使用的库为准。
4.3 模型配置文件示例
以下是一个基于 Hugging Face Transformers 风格的config.json示例,可以帮助你理解参数之间的关系:
{ "architectures": ["LlamaForCausalLM"], "bos_token_id": 2, "eos_token_id": 3, "hidden_act": "silu", "hidden_size": 2048, "initializer_range": 0.02, "intermediate_size": 5632, "max_position_embeddings": 4096, "model_type": "llama", "num_attention_heads": 16, "num_hidden_layers": 24, "num_key_value_heads": 8, "pad_token_id": 0, "rms_norm_eps": 1e-5, "tie_word_embeddings": false, "torch_dtype": "bfloat16", "vocab_size": 32768 }这里model_type写llama是为了利用开源库中已经实现的 Llama 类,并不是说你的模型就是 Llama。如果你改了架构细节,需要自定义模型类。
5. 预训练实操:配置、训练脚本与监控
5.1 预训练环境与并行策略
1B 模型单卡 BF16 训练,激活显存至少需要 20GB 以上,实际建议使用多卡。小团队通常选择 DeepSpeed ZeRO-2 或 ZeRO-3,也可以用 PyTorch FSDP。
训练过程中的核心监控指标:
loss:主损失,应平稳下降。grad norm:梯度范数,过大说明训练不稳定。learning rate:当前学习率,观察调度器是否正常。throughput tokens/s:每秒处理 token 数,衡量训练效率。
5.2 DeepSpeed 训练脚本示例
下面给出一个简化但完整的预训练脚本框架。它不是一个可以直接跑通所有环境的生产代码,而是一个可以参照的骨架。
# train.py import torch from transformers import ( AutoTokenizer, AutoConfig, LlamaForCausalLM, Trainer, TrainingArguments, ) from datasets import load_dataset model_config = AutoConfig.from_pretrained("config.json") tokenizer = AutoTokenizer.from_pretrained("aq_tokenizer") model = LlamaForCausalLM(config=model_config) model.train() dataset = load_dataset( "json", data_files="data/tokenized/train.jsonl", streaming=False, ) def tokenize(example): return tokenizer( example["text"], truncation=True, max_length=2048, ) dataset = dataset.map(tokenize, remove_columns=["text"]) training_args = TrainingArguments( output_dir="./checkpoints", per_device_train_batch_size=4, gradient_accumulation_steps=8, learning_rate=3e-4, warmup_steps=500, weight_decay=0.1, num_train_epochs=2, bf16=True, logging_steps=10, save_steps=500, save_total_limit=5, report_to="wandb", deepspeed="ds_config.json", ) trainer = Trainer( model=model, args=training_args, train_dataset=dataset, tokenizer=tokenizer, data_collator=lambda data: { "input_ids": torch.stack([torch.tensor(d["input_ids"]) for d in data]), "attention_mask": torch.stack([torch.tensor(d["attention_mask"]) for d in data]), "labels": torch.stack([torch.tensor(d["input_ids"]) for d in data]), }, ) trainer.train()对应ds_config.json:
{ "bf16": { "enabled": true }, "zero_optimization": { "stage": 2, "allgather_partitions": true, "reduce_scatter": true, "overlap_comm": true }, "train_batch_size": 64, "train_micro_batch_size_per_gpu": 4, "gradient_accumulation_steps": 8, "optimizer": { "type": "AdamW", "params": { "lr": 3e-4, "betas": [0.9, 0.95], "eps": 1e-8, "weight_decay": 0.1 } }, "scheduler": { "type": "WarmupCosine", "params": { "warmup_min_lr": 0, "warmup_max_lr": 3e-4, "warmup_num_steps": 500, "total_num_steps": 100000 } } }注意train_batch_size是全局批量大小,等于micro_batch_size × gradient_accumulation_steps × GPU 数。这个值直接影响模型收敛质量,不建议随意调小。
5.3 Loss 曲线怎么看
从零训练时,loss 通常会在前几百步快速下降,之后进入缓慢下降区间。如果 loss 在很早期就停滞:
- 检查学习率是否过大或过小。
- 检查数据是否存在大量重复。
- 检查 tokenizer 是否把文本切得过于细碎。
- 检查数值稳定性,尝试降低初始学习率。
如果 loss 出现突然飙升,大概率是数据批次中混入了异常样本,或者学习率调度设置不合理。
5.4 中途评估
预训练不是训完再测,而是每隔固定步数做一次评估。对于学术模型,建议至少保留以下几个维度:
- 训练集上的 loss。
- 留存验证集上的 loss。
- 少量通用 benchmark(如 HellaSwag、MMLU 子集)。
验证集必须和训练集严格去重,否则评估结果会虚高。
6. 指令微调与对齐
6.1 SFT 数据构建
预训练完成后,模型只会续写文本,不会“回答问题”。要让它变成可对话的助手,需要 SFT(监督微调)。
SFT 数据通常组织为对话格式:
{ "conversations": [ { "from": "human", "value": "什么是 Transformer?" }, { "from": "gpt", "value": "Transformer 是一种基于自注意力机制的神经网络架构,最早由 Vaswani 等人在 2017 年提出。它通过自注意力机制建模序列中任意两个位置之间的依赖关系,同时支持并行计算。" } ] }SFT 阶段不需要太多数据,几万条高质量对话足以让模型具备对话能力。重点在于数据多样性,包括问答、写作、翻译、代码生成、总结、推理等任务。
6.2 LoRA 与全参微调取舍
小团队在 SFT 阶段通常会面临 LoRA 与全参微调的取舍:
- 全参微调:效果上限高,资源消耗大,容易遗忘预训练知识。
- LoRA:显存占用小,训练快,适合快速迭代,但如果 rank 太小会限制表达能力。
对 1B 模型,如果显存允许,推荐先做全参 SFT,再使用 LoRA 做 DPO。这样既能保证基础能力,又能快速迭代对齐策略。
6.3 DPO 对齐
DPO(Direct Preference Optimization)是目前小团队最常用的对齐方法,比 RLHF 简单很多。你不需要训练 Reward Model,也不用搭建复杂的强化学习环境,只需要构造chosen和rejected数据对。
一个简化的 DPO 训练数据项:
{ "prompt": "请解释一下贝叶斯定理。", "chosen": "贝叶斯定理描述了在已知某些条件下,如何更新事件发生的概率。其公式为 P(A|B) = P(B|A) * P(A) / P(B)。", "rejected": "贝叶斯定理是一种数学工具,可以用来计算概率,它很有用。" }DPO 训练代码可以基于trl库实现:
from trl import DPOTrainer, DPOConfig from transformers import AutoModelForCausalLM, AutoTokenizer model = AutoModelForCausalLM.from_pretrained("checkpoints/sft_final") ref_model = AutoModelForCausalLM.from_pretrained("checkpoints/sft_final") tokenizer = AutoTokenizer.from_pretrained("checkpoints/sft_final") training_args = DPOConfig( output_dir="./dpo_checkpoints", per_device_train_batch_size=2, learning_rate=5e-6, max_length=2048, max_prompt_length=1024, bf16=True, logging_steps=10, save_steps=200, ) dpo_trainer = DPOTrainer( model=model, ref_model=ref_model, args=training_args, train_dataset=dpo_dataset, tokenizer=tokenizer, ) dpo_trainer.train()DPO 对学习率比较敏感,常用学习率在 1e-6 到 1e-5 之间。过大的学习率会把模型训崩,导致回答退化。
6.4 学术模型的安全与拒答
即使是学术模型,也需要基本的对齐策略:
- 不回答涉及个人隐私、医疗诊断、金融投资建议等高风险问题。
- 对明显有害内容,做拒答处理。
- 不编造“公司内部资料”或“虚构事实”。
学术模型可以开放更多边界探索,但发布时应在 README 中明确说明适用场景与局限。
7. 评估、量化与部署
7.1 学术基准评估
评估 1B 模型不能只用主观对话体验,要结合标准 benchmark。常用评估包括:
- HellaSwag:常识推理。
- MMLU:多学科知识。
- HumanEval:代码生成。
- GSM8K:数学推理。
- IFEval:指令跟随能力。
评估代码可以参考lm-evaluation-harness:
lm_eval --model hf \ --model_args pretrained=./checkpoints/sft_final \ --tasks hellaswag,mmlu,gsm8k \ --batch_size 8 \ --output_path ./eval_results评估结果需要与开源同尺寸模型横向对比,例如 0.5B、1B、2B 的模型。这里需要注意的是,不同版本的 benchmark 结果差异很大,对比时尽量使用相同评估脚本和相同版本。
7.2 量化与推理服务
1B 模型 FP16 权重约 2GB,量化到 4bit 后约 500MB,CPU 也能跑。常用方案:
- GPTQ:适合 GPU 推理。
- AWQ:激活感知量化,效果稳定。
- GGUF:配合 llama.cpp,适合 CPU 与边缘设备。
使用 llama.cpp 量化的简化流程:
# 1. 转为 GGUF 格式 python convert.py checkpoints/sft_final --outfile models/aq-1b.gguf --outtype q8_0 # 2. 使用 llama.cpp 启动本地服务 ./llama-server -m models/aq-1b.gguf --host 127.0.0.1 --port 80807.3 上线后的持续观测
部署只是开始。上线后要持续关注:
- 回答长度、拒答率。
- 是否出现有害内容。
- 是否存在上下文记忆混乱。
- 用户输入分布与训练集分布的差异。
学术模型的部署通常不追求高并发,更多是验证和演示。如果用到生产环境,建议加一层内容安全过滤。
8. 常见问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 预训练 loss 不下降 | 学习率过大或过小,数据太脏,模型结构错误 | 先跑小数据过拟合测试,确认 loss 能收敛;再逐步放大 |
| loss 突然飙升 | 学习率调度异常,数据批次混入异常样本 | 检查数据 pipeline,临时降低学习率,恢复 checkpoint |
| 显存溢出 | batch size 过大,序列过长,优化器状态占用高 | 减小 micro batch,开启 gradient checkpointing,使用 ZeRO |
| tokenizer 大量输出 unk | 词表与语料不匹配,tokenizer 训练数据不充分 | 重新训练 tokenizer,增加领域数据覆盖 |
| SFT 后模型能力下降 | 学习率过大,SFT 数据分布单一 | 降低学习率,增加数据多样性,保留部分通用语料 |
| DPO 后回答退化 | 学习率过大,chosen/rejected 差异不明显 | 降低学习率,重新清洗偏好数据 |
| 评估结果与其他模型不可比 | 评估 prompt 不同,数据集版本不同,采样参数不同 | 统一评估脚本、数据集版本、解码参数 |
9. 最佳实践与工程建议
9.1 小团队的项目管理
两人团队最大的风险不是技术,而是迭代失控。建议:
- 每次实验只改一个变量,记录实验结果。
- 用实验管理工具跟踪 loss、benchmark、训练配置。
- 每周固定一次 checkpoint 评估和模型试玩。
9.2 数据与实验的可复现性
学术模型的立足点之一是可复现性。建议做到:
- 数据清洗代码和版本全部入库。
- 数据配比写进配置文件。
- 训练脚本和依赖锁版本。
- 发布时附上 tokenizer、config、评估脚本。
9.3 成本与算力控制
训练 1B 模型时,算力消耗比 7B 小得多,但依然建议:
- 先做小规模 smoke test,再上全量训练。
- 定期保存 checkpoint,并清理冗余副本。
- 使用 bf16 混合精度,节省显存和带宽。
- 如果预算有限,优先租用短期 GPU,训练完立即释放。
9.4 安全与合规边界
任何模型发布都建议注意以下边界:
- 明确模型的训练数据来源与许可。
- 在模型卡中列出已知局限和可能偏见。
- 高风险场景(医疗、金融、法律)只做研究辅助,不做最终决策。
- 涉及个人隐私数据的场景,需要额外的匿名化和脱敏处理。
10. 总结与学习路线
AQ 这个项目的意义不在于“印度团队做出了 1B 模型”这个结果,而在于它用最小规模复现了从零训练 LLM 的完整流程。对国内开发者来说,这是一个非常值得参考的路线图:从数据工程开始,到 tokenizer、预训练、SFT、DPO、评估、量化,每一步都有对应的工程挑战和优化空间。
如果你也想尝试类似项目,建议按下面的顺序推进:
- 先拿 1000 万 token 级别的数据做过拟合测试,跑通代码链路。
- 再扩展到 10 亿 token 级别,验证数据 pipeline 的吞吐和稳定性。
- 然后尝试完整预训练,期间穿插评估。
- 最后做 SFT 和 DPO,把模型变成可对话的助手。
1B 参数是个人开发者、学术团队和中小企业最容易切入的档位。无论你最终的目标是研究、垂直领域应用还是教育项目,这条路都值得完整走一遍。希望这篇文章能帮你在从零训练 LLM 的路上少踩几个坑。