尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

两人团队从零训练1B学术LLM:完整技术路线与工程实践

两人团队从零训练1B学术LLM:完整技术路线与工程实践 最近在 Hacker News 上看到一个很有意思的项目AQ一个由印度两人团队从零开始训练的 1B 学术大语言模型。这个项目引起了不少开发者的讨论不是因为它的参数规模有多大而是因为“两人团队 from-scratch 学术模型”这几个关键词组合在一起确实能让人产生很多好奇。我自己在尝试复现一些小型 LLM 训练实验时也踩过不少环境、数据、精度方面的坑所以看到这类项目时会特别关注它的技术路线。这篇文章就围绕 AQ 项目展开结合从零训练 1B 级 LLM 的完整流程聊聊背后的模型架构、数据处理、精度选择、训练调试和工程落地问题。无论你是刚接触 LLM 的新手还是想尝试自己训练小规模模型的开发者这篇文章都能给你一条比较清晰的路线参考。1. 背景与核心概念1.1 from-scratch LLM 是什么意思先说“from-scratch”。在 LLM 领域from-scratch 指的是不基于任何预训练权重从随机初始化开始训练一个模型。也就是说不使用 LLaMA、Mistral、Qwen 这些已经训练好的底座也不做领域微调而是自己准备数据、设计分词器、定义模型结构然后从零开始跑预训练。AQ 项目的核心标签是AQ模型名称。1B参数量约为 10 亿1 Billion。academic LLM学术用途的大语言模型偏向研究、教学、实验验证。from-scratch不依赖现成底座。two-person team两人团队。对于很多研究者和工程师来说从零训练一个 1B 模型是一个比较合理的“中间规模”选择。它比 7B、13B 模型训练成本低很多但又比几百 M 的 tiny model 更能体现 LLM 的涌现能力和语言理解能力。1B 模型可以在单机多卡甚至单卡 梯度累积的情况下完成训练也适合用来验证数据配比、模型结构、训练策略等想法。1.2 为什么关注 1B 学术模型先看一组对比模型规模训练成本单机可训练性适合场景100M - 300M低容易教学实验、结构验证1B - 2B中较容易需多卡或梯度累积学术研究、小规模预训练、领域模型7B - 13B高困难需要多机多卡通用底座、商用模型70B极高需要大规模集群前沿研究1B 模型处在“能跑出真实效果”和“训练成本可控”的交叉点。对于两人团队来说这是一个非常务实的选择。AQ 项目的价值不在于“我们做出了一个超越 LLaMA 的模型”而在于它证明了在资源有限的情况下一个学术团队仍然可以从数据准备、分词器训练、模型预训练、评估对齐这几个环节完整地走一遍 LLM 生产流程。这对学术界和工业界的轻量级团队都有参考意义。1.3 常见应用场景从零训练一个 1B 学术 LLM 通常用于以下场景教学与科研让学生或研究人员理解 Transformer 的每一个训练细节。领域模型验证在垂直领域先用小规模数据验证训练 pipeline再决定是否扩大规模。低资源语言研究某些语料稀缺的语言可以用小模型做可行性验证。架构实验测试新的注意力机制、位置编码、分词策略。产品原型在 GPU 资源有限的情况下验证 LLM 产品逻辑。2. 环境准备与版本说明从零训练 LLM 对硬件和软件环境有一定要求。下面以最常见的 PyTorch 训练环境为例说明如何准备。2.1 硬件环境训练 1B 模型的最低建议配置GPU至少 1 张 24GB 显存的显卡如 RTX 3090 / 4090 / A5000推荐 2 到 4 张。内存64GB 以上用于数据加载和预处理。存储SSD 500GB 以上用于存放数据集和 checkpoint。CPU16 核以上数据预处理和 tokenize 时会用到。如果显存不足可以通过梯度累积、混合精度训练、序列长度缩减等方式降低显存压力但训练时间会变长。2.2 软件环境Python 3.10 PyTorch 2.1 transformers 4.36 tokenizers 0.15 datasets 2.16 accelerate 0.26 sentencepiece 0.1.99 wandb # 可选用于实验跟踪安装命令pip install torch transformers tokenizers datasets accelerate sentencepiece wandb版本需要根据你的实际环境调整本文示例以常见环境为例重点演示配置思路。2.3 项目结构建议按下面的结构组织训练项目aq-llm/ ├── config/ │ └── train_config.yaml ├── data/ │ ├── raw/ # 原始语料 │ └── tokenized/ # 分词后的数据 ├── scripts/ │ ├── train_tokenizer.py │ ├── preprocess.py │ ├── train.py │ └── evaluate.py ├── models/ # 模型输出目录 └── logs/ # 训练日志3. 核心原理拆解从零训练 1B LLM 的关键环节无论团队规模多大训练一个 LLM 都绕不开下面这条流水线数据采集 → 数据清洗 → 分词器训练 → Tokenization → 模型初始化 → 预训练 → 评估 →可选对齐下面逐个拆解。3.1 数据准备与清洗数据是 LLM 训练的基石。1B 模型的训练通常需要几十 GB 到几百 GB 的文本数据。数据质量直接决定模型能力的上限。学术场景下常用数据来源包括开源语料如 The Pile、RedPajama、C4、Wikipedia。领域论文、书籍、代码仓库。自己爬取的垂直领域语料。数据清洗的几个关键步骤去重使用 MinHash 或 SimHash 去除重复文本。过滤去除低质量内容、广告、乱码、过短文本。语言识别保留目标语言内容。隐私与安全过滤去除个人信息、敏感内容。格式归一化统一换行、空格、编码格式。3.2 分词器训练分词器决定了模型“看到”的文本单元。常见选择BPEByte-Pair EncodingGPT 系列使用适合多语言和代码场景。WordPieceBERT 系列使用。SentencePieceLLaMA 系列使用支持 byte-level。1B 模型常用的词表大小是 32K 到 64K。学术实验可以先用 32K 起步。训练分词器的示例代码如下# 文件路径scripts/train_tokenizer.py from tokenizers import Tokenizer from tokenizers.models import BPE from tokenizers.trainers import BpeTrainer from tokenizers.pre_tokenizers import ByteLevel # 创建 BPE 分词器 tokenizer Tokenizer(BPE(unk_tokenunk)) tokenizer.pre_tokenizer ByteLevel() # 配置训练器 trainer BpeTrainer( vocab_size32000, special_tokens[unk, s, /s, pad], ) # 准备训练文件列表 files [data/raw/corpus1.txt, data/raw/corpus2.txt] # 训练 tokenizer.train(files, trainer) # 保存 tokenizer.save(models/tokenizer.json)这里需要注意的是分词器的词表大小直接影响 embedding 层的参数量。词表越大模型的 embedding 参数越多训练和推理时显存占用也越高。3.3 模型架构选择1B 级别的模型通常采用 decoder-only 架构这也是 GPT、LLaMA 等主流模型的架构。核心组件包括Token Embedding将 token 映射为向量。位置编码RoPE旋转位置编码是当前主流选择。多层 Transformer Decoder Block包含自注意力、前馈网络、LayerNorm。输出层映射到词表大小的 logits。下面是一个基于 PyTorch 的简化 GPT 模型实现# 文件路径scripts/model.py import math import torch import torch.nn as nn import torch.nn.functional as F class RMSNorm(nn.Module): def __init__(self, dim, eps1e-6): super().__init__() self.eps eps self.weight nn.Parameter(torch.ones(dim)) def forward(self, x): return x * torch.rsqrt(x.pow(2).mean(-1, keepdimTrue) self.eps) * self.weight class CausalSelfAttention(nn.Module): def __init__(self, dim, num_heads): super().__init__() self.num_heads num_heads self.head_dim dim // num_heads self.wq nn.Linear(dim, dim, biasFalse) self.wk nn.Linear(dim, dim, biasFalse) self.wv nn.Linear(dim, dim, biasFalse) self.wo nn.Linear(dim, dim, biasFalse) def forward(self, x): B, T, C x.shape q self.wq(x).view(B, T, self.num_heads, self.head_dim).transpose(1, 2) k self.wk(x).view(B, T, self.num_heads, self.head_dim).transpose(1, 2) v self.wv(x).view(B, T, self.num_heads, self.head_dim).transpose(1, 2) att (q k.transpose(-2, -1)) * (self.head_dim ** -0.5) mask torch.tril(torch.ones(T, T, devicex.device)).view(1, 1, T, T) att att.masked_fill(mask 0, float(-inf)) att F.softmax(att, dim-1) y att v y y.transpose(1, 2).contiguous().view(B, T, C) return self.wo(y) class FeedForward(nn.Module): def __init__(self, dim, hidden_dim): super().__init__() self.w1 nn.Linear(dim, hidden_dim, biasFalse) self.w2 nn.Linear(hidden_dim, dim, biasFalse) def forward(self, x): return self.w2(F.silu(self.w1(x))) class TransformerBlock(nn.Module): def __init__(self, dim, num_heads, hidden_dim): super().__init__() self.attn CausalSelfAttention(dim, num_heads) self.ffn FeedForward(dim, hidden_dim) self.norm1 RMSNorm(dim) self.norm2 RMSNorm(dim) def forward(self, x): x x self.attn(self.norm1(x)) x x self.ffn(self.norm2(x)) return x class GPT(nn.Module): def __init__(self, vocab_size, dim, num_heads, num_layers, max_seq_len): super().__init__() self.tok_emb nn.Embedding(vocab_size, dim) self.blocks nn.ModuleList([ TransformerBlock(dim, num_heads, dim * 4) for _ in range(num_layers) ]) self.norm RMSNorm(dim) self.output nn.Linear(dim, vocab_size, biasFalse) self.pos nn.Parameter(torch.zeros(1, max_seq_len, dim)) def forward(self, idx): B, T idx.shape x self.tok_emb(idx) self.pos[:, :T, :] for block in self.blocks: x block(x) x self.norm(x) logits self.output(x) return logits这是一个非常简化的实现用于理解核心结构。实际训练时更推荐使用 Hugging Face 的GPT2LMHeadModel或 LLaMA 实现因为它们已经做了大量优化。3.4 精度选择fp16、fp32 与 bf16这是很多初学者容易困惑的地方。简单来说fp32单精度训练初期和数值稳定性要求高的场景使用显存占用大。fp16半精度显存占用减半速度更快但数值范围小容易出现溢出或精度损失。bf16脑浮点显存占用和 fp16 一样但动态范围与 fp32 接近训练更稳定是当前大模型预训练的主流选择。精度位宽动态范围显存占用稳定性适用场景fp3232-bit大高高基线训练、调试fp1616-bit小低较低推理、显存受限场景bf1616-bit大低高预训练、微调在从零训练 1B 模型时推荐使用 bf16 梯度累积。如果 GPU 不支持 bf16少数老显卡可以使用 fp16 动态损失缩放。4. 完整实战训练一个最小可用的 1B 级模型为了让读者能真正跑起来这里给出一个完整的最小训练脚本思路。实际运行时请根据你的数据集和硬件调整参数。4.1 创建项目结构mkdir -p aq-llm/{config,data/raw,data/tokenized,scripts,models,logs}4.2 准备训练配置# 文件路径config/train_config.yaml model: vocab_size: 32000 dim: 1536 num_heads: 16 num_layers: 24 max_seq_len: 512 training: batch_size: 8 grad_accum_steps: 16 learning_rate: 3e-4 min_learning_rate: 1e-5 max_steps: 100000 warmup_steps: 2000 weight_decay: 0.1 bf16: true save_steps: 5000 logging_steps: 100这里的关键参数说明dim: 1536隐藏层维度1B 模型常用 1536 或 2048。num_heads: 16注意力头数。num_layers: 24Transformer 层数。max_seq_len: 512序列长度学术实验可以先从 512 开始。grad_accum_steps: 16梯度累积步数用于模拟更大的 batch size。等效 batch size batch_size x grad_accum_steps。上面配置中8 x 16 128也就是每次参数更新使用 128 个样本。4.3 数据预处理将原始文本转换为模型可读取的 token ID# 文件路径scripts/preprocess.py from tokenizers import Tokenizer from datasets import Dataset, load_dataset import os # 加载训练好的分词器 tokenizer Tokenizer.from_file(models/tokenizer.json) def tokenize_function(examples): text examples[text] # 编码并截断到最大长度 encodings tokenizer.encode_batch(text) input_ids [enc.ids[:512] for enc in encodings] # 填充到相同长度 max_len max(len(ids) for ids in input_ids) padded_ids [ids [0] * (max_len - len(ids)) for ids in input_ids] return {input_ids: padded_ids, attention_mask: [[1] * len(ids) [0] * (max_len - len(ids)) for ids in input_ids]} # 加载原始数据 dataset load_dataset(text, data_files{train: data/raw/*.txt})[train] # 分片处理避免一次性占用过多内存 dataset dataset.map(tokenize_function, batchedTrue, remove_columns[text]) # 保存到磁盘 dataset.save_to_disk(data/tokenized/train)4.4 编写训练脚本使用 Hugging Face 的 Trainer 或 Accelerate 都可以。下面给出一个基于transformersTrainer的完整脚本示例# 文件路径scripts/train.py import os import torch from transformers import ( GPT2Config, GPT2LMHeadModel, Trainer, TrainingArguments, DataCollatorForLanguageModeling, ) from datasets import load_from_disk from tokenizers import Tokenizer # 加载分词器并添加到模型配置 tokenizer Tokenizer.from_file(models/tokenizer.json) tokenizer.add_special_tokens([unk, s, /s, pad]) # 模型配置 config GPT2Config( vocab_size32000, n_embd1536, n_head16, n_layer24, n_positions512, resid_pdrop0.0, embd_pdrop0.0, attn_pdrop0.0, ) model GPT2LMHeadModel(config) # 加载 tokenized 数据集 dataset load_from_disk(data/tokenized/train) # 定义数据整理器 data_collator DataCollatorForLanguageModeling( tokenizerNone, mlmFalse, pad_to_multiple_of8, ) # 训练参数 training_args TrainingArguments( output_dirmodels/aq-1b, overwrite_output_dirTrue, num_train_epochs1, max_steps100000, per_device_train_batch_size8, gradient_accumulation_steps16, learning_rate3e-4, weight_decay0.1, warmup_steps2000, logging_steps100, save_steps5000, save_total_limit3, bf16True, dataloader_num_workers4, report_tonone, ) trainer Trainer( modelmodel, argstraining_args, train_datasetdataset, data_collatordata_collator, ) trainer.train()运行命令cd aq-llm python scripts/train.py注意上面的脚本中tokenizerNone是因为我们使用的是 tokenizers 库的分词器而不是 transformers 的 PreTrainedTokenizer。实际使用时需要把 tokenizer 包装为 PreTrainedTokenizerFast或者直接用 tokenizers 库做预处理这里只是演示核心思路。4.5 运行与验证训练完成后可以用下面脚本做简单的文本生成验证# 文件路径scripts/generate.py from transformers import GPT2LMHeadModel, GPT2TokenizerFast # 加载模型和分词器 model GPT2LMHeadModel.from_pretrained(models/aq-1b/checkpoint-100000) tokenizer GPT2TokenizerFast.from_pretrained(models/tokenizer) # 输入提示词 prompt The future of AI is # 编码 inputs tokenizer(prompt, return_tensorspt) # 生成 outputs model.generate( inputs.input_ids, max_new_tokens50, do_sampleTrue, temperature0.8, top_p0.9, ) print(tokenizer.decode(outputs[0], skip_special_tokensTrue))预期输出的文本会基于你的训练语料风格。如果语料是论文摘要输出就会偏向学术风格如果是代码输出就偏向代码风格。5. 精度问题详解fp16、fp32、bf16 的选择与实践5.1 为什么精度问题在 1B 模型训练中很重要很多初学者训练 1B 模型时会遇到 loss 突然变成 NaN 的情况。这种问题绝大多数和精度选择有关。如果使用 fp16 训练梯度值小于 2^-24 时会被舍入为 0而大于 65504 时会变成无穷大。对于 1B 模型某些层的梯度可能非常大导致溢出。解决方案是 bf16。bf16 的指数位和 fp32 一样多动态范围更大虽然尾数位较少但在预训练场景下足够使用。5.2 混合精度训练的最佳实践推荐做法优先使用 bf16。如果硬件不支持 bf16使用 fp16 动态损失缩放。关键层如 loss 计算层保持 fp32。使用 gradient clipping将梯度范数限制在 1.0 左右。在 Trainer 中的配置training_args TrainingArguments( bf16True, # 优先 bf16 fp16False, # 不能和 bf16 同时开启 max_grad_norm1.0, # 梯度裁剪 )5.3 精度选择实战建议场景推荐方案备注从零预训练 1B 模型bf16稳定且显存友好微调开源模型bf16 或 fp16取决于显卡支持推理部署fp16 或 int8平衡速度和效果学术实验快速验证fp32避免精度干扰适合小模型调试6. 两人团队训练 1B 模型的工程实践AQ 项目的亮点之一是“两人团队”。对于小团队来说工程效率比算力规模更重要。下面分享一些在有限资源下训练 LLM 的工程经验。6.1 实验管理两人团队意味着没有那么多精力做重复实验。建议使用wandb或tensorboard记录 loss、lr、梯度范数。每次实验记录数据版本、模型结构、超参数。checkpoint 保存时同时保存 optimizer 状态方便断点续训。在 Trainer 中加入 wandbimport wandb wandb.init(projectaq-llm, namerun-1b-bf16) training_args TrainingArguments( report_towandb, )6.2 显存优化技巧使用gradient_accumulation_steps增大有效 batch size。使用gradient_checkpointing节省显存代价是训练速度下降约 30%。降低max_seq_len从 1024 降到 512 可以显著减少显存占用。使用torch.compile加速训练PyTorch 2.x 支持。training_args TrainingArguments( gradient_checkpointingTrue, )6.3 训练数据的重要性对于学术模型数据质量比数据量更重要。建议构建一个小而精的验证集人工检查 loss 曲线和生成效果。使用困惑度perplexity作为评估指标。如果 loss 不下降优先检查数据而不是模型结构。6.4 训练成本预估1B 模型在 4 张 A10080GB上的训练速度大约是每步 2 到 3 秒。10 万步大约需要 3 到 5 天。这个成本对学术团队来说是可控的。如果使用消费级显卡比如 4 张 RTX 409024GB训练时间会翻倍甚至更久。建议先用小模型如 100M验证整个 pipeline再扩展到 1B。7. 常见问题与排查思路从零训练 LLM 过程中新手最容易遇到以下几类问题。7.1 Loss 不下降或直接变成 NaN问题现象常见原因解决思路Loss 为 NaN学习率过高降低学习率从 1e-4 开始Loss 为 NaNfp16 溢出切换 bf16 或开启损失缩放Loss 不下降数据有问题检查数据是否包含大量重复或空文本Loss 不下降梯度消失检查 LayerNorm 位置是否正确7.2 显存不足CUDA Out of Memory排查顺序降低per_device_train_batch_size。开启gradient_checkpointing。降低max_seq_len。使用梯度累积补偿 batch size。training_args TrainingArguments( per_device_train_batch_size4, gradient_accumulation_steps32, gradient_checkpointingTrue, )7.3 训练速度过慢检查是否使用了 bf16/fp16。减少dataloader_num_workers或增加硬件资源。使用torch.compile优化计算图。检查数据加载是否是瓶颈尝试预先把数据缓存到内存。7.4 生成效果很差增加训练步数。检查分词器是否有太多未知词。增加训练数据量和多样性。调整生成参数temperature、top_k、top_p。8. 最佳实践与工程建议8.1 从数据出发而不是从模型出发很多团队第一个错误是急着写模型代码而忽略了数据。建议花 40% 的时间在数据清洗和验证上。一个人工检查过的 10GB 高质量数据集效果可能好于 100GB 的噪声数据。8.2 先验证 pipeline再扩大规模强烈建议先训练一个 100M 参数、5 万步的模型确认整个 pipeline 没有问题再扩展到 1B。这一步可以为你节省大量时间和算力。8.3 建立完整的 checkpoint 策略每 5000 步保存一个 checkpoint。保存 optimizer 状态。记录训练配置和 loss 曲线。至少保留最近 3 个 checkpoint。8.4 安全与合规训练学术模型时要注意只使用合法获取的数据。过滤个人隐私和敏感信息。发布模型时明确模型用途和局限。不要使用绕过安全限制的方式获取数据。8.5 记录一切两人团队最大的风险是人员流动和知识断层。建议每个实验写 brief 记录。数据清洗脚本全部版本控制。使用统一的配置文件管理超参数。9. 总结与下一步学习路线AQ 这个项目给我们最大的启发是训练一个 1B 级学术 LLM 不再是只有大厂才能做的事情。两人团队、有限的 GPU 资源依然可以完成从数据到模型再到评估的完整闭环。从零开始训练 LLM 的过程才是真正理解大模型底层原理的最佳途径。如果你想动手尝试建议按这个顺序推进先准备 1GB 到 5GB 的小数据集。训练一个 100M 参数的模型跑通整个 pipeline。逐步增加数据量和模型规模到 1B。关注训练曲线和生成效果不断调优。如果你对模型架构感兴趣可以深入研究 RoPE 位置编码、SwiGLU 激活函数、RMSNorm 的实现原理。如果对数据更感兴趣可以研究 MinHash 去重、PII 过滤、多语言数据配比。也可以尝试在 AQ 的思路上做扩展比如加入领域数据继续预训练或者用 RLHF 做对齐。1B 是一个理想的起点但不要停留在“能跑通”的阶段。真正有价值的是训练过程中对每一个决策的理解为什么选这个学习率、为什么用 bf16、为什么数据清洗能提升模型效果。这些判断力才是从零训练 LLM 最宝贵的收获。如果有条件拿一张消费级显卡从 100M 模型开始跑起来吧。理论看十遍不如自己训练一个模型印象深刻。
返回列表