尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

从零构建大语言模型:AI工程实战路线与关键技术解析

发布时间:2026/9/29 17:10:52

资讯中心
01
ARTICLE

从零构建大语言模型:AI工程实战路线与关键技术解析

从零构建大语言模型:AI工程实战路线与关键技术解析
1. 从零开始做AI工程先搞清楚你学的到底是什么我最早接触AI工程这个概念的时候跟很多人一样以为就是调库、跑模型、看指标。后来真动手做了才发现AI工程和AI研究是两码事。研究的核心是探索新方法工程的核心是把已有方法变成稳定、可靠、可复用的系统。而from scratch这条路恰恰是理解这两者差异的最快方式——不是用现成框架搭积木而是从矩阵乘法、梯度传播、注意力机制这些最底层的东西开始亲手把一个大语言模型从零建起来。这篇文章想跟你聊的就是我走完这条路之后的一些体会为什么要从零开始、需要补哪些知识、怎么做tokenizer、怎么搭Transformer、怎么把模型真正训练起来以及那些只看论文绝对学不到的工程坑。不管你是想转入AI方向的学生还是已经在做业务想补底层功底的开发者这套路线都可以直接参考。网上关于build a large language model from scratch的资源不少也有不少人在做build a reasoning model from scratch这样的实践。我的建议是别急着冲推理模型这种高难度副本先把语言模型本身跑通再一步步往上加东西。下面我把整条路线拆开讲。1.1 AI工程的核心不是模型是系统很多人第一次接触AI工程会把注意力全放在模型结构上觉得只要把Transformer背熟就算入门了。这其实是个误区。真实生产环境里的AI系统模型只是其中一个环节。数据管道怎么处理脏数据、训练脚本怎么保证可复现、推理服务怎么控制延迟、模型版本怎么管理、线上效果怎么监控这些都属于AI工程的范畴。我见过不少团队模型在离线评测里效果不错一上线就崩原因往往不在模型本身而在工程链路。比如训练数据和推理数据的分布不一致、预处理逻辑写了两套、服务端和训练端的tokenizer版本对不上这些问题都只能靠工程手段去解决。所以从零开始构建一个语言模型真正的价值不在于你复现了一个ChatGPT而在于你把整条链路都走了一遍知道每个环节为什么存在、出问题时该往哪儿查。1.2 为什么从零构建是最高效的学习方式直接调库当然快比如用Hugging Face的transformers几行代码就能加载一个预训练模型。但这样做有个问题很多关键细节被封装掉了你根本不知道模型是怎么吃数据的、损失是怎么算的、推理时的KV Cache是怎么工作的。一旦遇到线上问题你连定位的思路都没有。从零构建就完全不一样。你得自己写分词器才会理解BPE为什么要那样合并词元自己实现注意力机制才会明白为什么QKV要分开投影为什么要有缩放因子自己写训练循环才会对batch size、学习率、梯度裁剪这些超参数产生直觉。这个过程确实慢但慢有慢的价值——构建出来的是一个完整的知识体系而不是一堆零散的API调用经验。我还想强调一点从零构建不等于忽略现有工具。PyTorch该用就用数据并行该上就上。重点是模型本身要自己搭核心算法要自己写。这跟学手排挡一样不是为了不开自动挡而是为了在自动挡出问题时你知道变速箱里发生了什么。2. 动手前的技术地基哪些知识必须补哪些可以边做边学我见过不少人兴致勃勃地开始从零写模型结果卡在第一步——连梯度回传都看不明白。为了避免这种情况我把动手前需要的地基知识整理了一下分成必须提前掌握和可以边做边补两类。2.1 数学与编程基础不要求深但必须能用先说数学。你不需要成为数学系博士但线性代数、微积分、概率论这三块的基本概念必须扎实。线性代数里矩阵乘法、转置、形状变换是日常操作微积分里链式法则必须形成肌肉记忆因为反向传播就是链式法则的工程实现概率论里交叉熵、softmax、采样这些概念会一直陪着你。我的建议是不需要系统刷教材而是边写代码边补。比如当你实现softmax时发现自己对为什么要求exp的和这个细节不清楚就去翻一翻概率论中关于分布归一化的部分。这样带着问题学效率远高于从头啃一本600页的教材。编程基础方面Python是绕不开的。除了基本语法你还得熟练操作NumPy和PyTorch的Tensor。我建议你在动手写模型之前先做几个小练习用NumPy实现一个线性回归、手写一个两层的MLP做MNIST分类、用PyTorch重写一遍并对比结果。这些练习能帮你建立张量操作自动求导的直觉后面写Transformer会顺畅很多。2.2 语言模型的核心概念先建立整体认知在写第一行模型代码之前有几个概念你必须先建立整体认知否则很容易在实现细节里迷失。第一个是语言建模任务本身。所谓语言模型本质上就是在做一件事给定前文预测下一个词的概率分布。这个任务看似简单却撑起了整个生成式AI。你需要理解为什么预测下一个词能学到语法、事实、推理能力——这背后是压缩和表示学习在起作用。第二个是分词tokenization。模型不是直接吃文本的而是把文本切分成子词单元每个子词映射成一个整数ID。BPEByte Pair Encoding是目前最主流的算法你需要理解它是怎么从字节对合并开始的vocab size怎么定未知词怎么处理。第三个是上下文与注意力。Transformer的核心是注意力机制它让模型在处理当前位置时能够关注到序列中其他位置的信息。你需要理解Q、K、V三个矩阵的来历理解为什么注意力分数要除以根号下维度这是为了防止softmax输入过大导致梯度消失理解因果掩码causal mask为什么让当前位置只能看到左侧的token。这些概念不是背下来就行而是要在实现过程中反复对照。当你把每个概念都亲手写成代码它们才会真正变成你的工具。3. 从零实现一个迷你语言模型完整实操路线现在进入正题。我按自己实操的顺序把从零构建一个语言模型的完整路线分成四步数据准备与分词、模型架构搭建、训练循环、评估与生成。每一步我都会给出关键代码思路和踩坑经验。我用的是小规模但完整的策略——在一个几十MB的文本语料上训练一个参数量在千万级别的模型。这个规模能在消费级显卡上跑通同时保留了完整的技术链路。你别小看这个迷你模型它麻雀虽小五脏俱全走完一遍之后再看那些几十B参数的大模型论文你会发现自己能跟上思路了。3.1 数据准备与分词器被大多数人低估的环节第一步是找数据和处理数据。我用的是一份开源的中文语料大概50MB的纯文本清洗掉HTML标签、多余空白和乱码之后剩下约30MB有效内容。这里有个经验数据清洗的质量直接决定训练效果脏数据比数据量小更致命。数据清洗完成后就要写分词器了。我建议自己实现一个BPE不要直接调现成库。BPE的核心逻辑不复杂先把文本转成字节序列初始词元表就是256个字节统计相邻字节对的出现频率每次合并频率最高的一对生成新词元重复合并直到达到目标词元数def bpe_merge(texts, vocab_size): # 统计初始词元频率 tokens [list(text.encode(utf-8)) for text in texts] vocab {i: bytes([i]) for i in range(256)} while len(vocab) vocab_size: # 统计所有相邻pair的频率 pair_freq {} for token_list in tokens: for pair in zip(token_list, token_list[1:]): pair_freq[pair] pair_freq.get(pair, 0) 1 if not pair_freq: break # 找频率最高的pair best_pair max(pair_freq, keypair_freq.get) new_id len(vocab) vocab[new_id] vocab[best_pair[0]] vocab[best_pair[1]] # 合并所有出现的位置 new_tokens [] for token_list in tokens: merged [] i 0 while i len(token_list): if i len(token_list) - 1 and (token_list[i], token_list[i1]) best_pair: merged.append(new_id) i 2 else: merged.append(token_list[i]) i 1 new_tokens.append(merged) tokens new_tokens return vocab, tokens这里有几个容易踩的坑。第一个是编码方式中文直接用UTF-8字节做BPE效果比先按字符切分要好因为UTF-8天然解决了未登录字的问题。第二个是词表大小迷你模型建议4000到8000太大了模型容量不够学不好太小了压缩率不够。我试过用2048效果明显变差很多常用词被切得很碎模型很难学到稳定的词级表示。分词器做好之后要把文本转成ID序列并打包成训练样本。每个样本是一个固定长度的token序列比如512。这里有个知识点需要设置步长stride让相邻样本有重叠。比如步长设为256那么第一个样本是token 0到511第二个样本是256到767。这样能充分利用语料避免样本边界处的上下文信息被浪费。3.2 模型架构搭建一个极简但完整的GPT模型架构这块我选择实现一个标准的decoder-only transformer也就是GPT的结构。它的构成可以拆成几个模块token嵌入层、位置编码层、若干层decoder block、输出投影层。每个decoder block里又包含多头自注意力、前馈网络、LayerNorm和残差连接。我用一个15M参数的小模型做演示具体配置如下参数取值说明vocab size6000与分词器对应hidden size384嵌入和隐藏层维度layers6decoder block数量heads6注意力头数sequence length512最大上下文长度这里有一个工程细节为了在消费级显卡上稳定训练我建议把hidden size和head数量设成能整除的关系384除以6等于64每个头的维度就是64。这样张量形状在拆分和合并时不会出错也方便后续调试。注意力机制的实现是核心。它的公式是[ \text{Attention}(Q,K,V) \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V ]d_k是每个头的维度除以d_k的平方根是为了稳定梯度。我见过有的实现漏掉了这个缩放因子小模型上可能不明显但模型变大后训练会非常不稳定。因果掩码的实现也要注意用一个上三角矩阵把未来位置设为负无穷的概率值这样softmax之后那些位置的概率就趋近于零。def attention(q, k, v, maskNone): d_k q.size(-1) scores torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k) if mask is not None: scores scores.masked_fill(mask 0, float(-inf)) weights F.softmax(scores, dim-1) return torch.matmul(weights, v)LayerNorm和残差连接的位置也要注意。现代Transformer普遍采用Pre-LN结构也就是先做LayerNorm再做注意力/前馈计算最后加残差。这个顺序选择很重要Pre-LN在训练时更稳定允许用较大的学习率而Post-LN原始Transformer论文的结构对学习率非常敏感调参难度大很多。我一开始用的是Post-LN结果训练loss一直在1.5左右下不去换成Pre-LN之后很快就降到1.0以下。3.3 训练循环理解损失、优化器和超参数模型搭好之后就该写训练循环了。这个部分看起来平淡无奇却是最考验工程能力的地方。核心组件有三个损失函数、优化器和学习率调度。损失函数用交叉熵。因为语言模型是预测下一个token所以要把模型输出的logits和真实token序列做交叉熵计算。这里有一个细节logits的形状是(batch, seq_len, vocab_size)真实标签的形状是(batch, seq_len)计算损失前要把logits的前两维合并变成(batch * seq_len, vocab_size)再和展平的标签对比。优化器我用的是AdamW。相比经典AdamAdamW把权重衰减和梯度更新解耦能更有效地控制过拟合。学习率设置方面我建议使用warmup cosine decay策略前2000步从0线性上升到峰值之后按余弦曲线衰减到峰值的10%。optimizer torch.optim.AdamW(model.parameters(), lr3e-4, weight_decay0.1) scheduler torch.optim.lr_scheduler.LambdaLR( optimizer, lr_lambdalambda step: min((step 1) / warmup_steps, 1.0) * (0.5 * (1 math.cos(math.pi * step / total_steps))) )我踩过的一个大坑是学习率太大。刚开始图省事把学习率设为标准的1e-3结果训练不到几百步loss就发散到了NaN。后来把峰值学习率降到3e-4配合梯度裁剪max_grad_norm1.0才稳定下来。经验是如果你的模型loss突然变成NaN优先检查学习率其次检查是否有除零或者log(0)的操作。训练过程中的监控也很重要。我建议每100步记录一次训练loss每1000步在验证集上算一次验证loss。如果训练loss下降但验证loss上升说明过拟合了可以考虑加dropout或增大数据量。如果两个loss都不下降问题可能出在模型结构或数据上需要回头检查。3.4 文本生成让模型输出有意义的内容训练完成后就要写生成逻辑了。这一步很有成就感因为模型的输出能直观反映训练效果。最基本的生成策略是贪心解码每次取概率最大的token作为下一个输出。但贪心解码有个问题就是容易陷入重复。原因是按概率取最大值会让模型倾向于输出自己反复见过的模式。更常用的方法是温度采样temperature sampling先对logits除以一个温度系数再做softmax然后从分布中随机采样。温度参数很有意思。温度趋近于0时分布变得尖锐输出接近贪心温度偏高时分布变得平坦输出更随机但也会引入更多错误。我在实践中发现0.8左右的温度对中文文本生成效果比较平衡既不会太死板也不至于胡言乱语。重复惩罚repetition penalty也是必须加的。实现方式不复杂对已经生成的token在计算logits时把它们的分数乘以一个惩罚系数比如0.5降低它们再次被选中的概率。这个技巧能显著改善长文本生成的重复问题。def generate(model, tokenizer, prompt, max_new_tokens100, temperature0.8): model.eval() input_ids tokenizer.encode(prompt) with torch.no_grad(): for _ in range(max_new_tokens): # 截取最后seq_len个token作为输入 inputs torch.tensor([input_ids[-seq_len:]]).to(device) logits model(inputs)[0, -1, :] logits logits / temperature probs F.softmax(logits, dim-1) next_token torch.multinomial(probs, num_samples1).item() input_ids.append(next_token) return tokenizer.decode(input_ids)我训练的那个迷你模型在几百万条短文本上跑了大约3个epoch之后已经能生成通顺的短句了。当然它不具备真正的知识储备但语法结构是正确的——这说明语言建模这个任务本身确实能让模型学到语言的统计规律。4. 从单模型到AI系统推理、微调与评估的工程化模型训练完AI工程的路才走了一半。接下来的推理部署、微调和评估才是实际工作中花时间最多的地方。这一节我讲三块推理效率优化、微调的实操方法、以及评估体系的搭建。4.1 推理部署把模型变成可用服务训练时的模型是浮点权重加优化器状态推理时只需要权重。要把模型变成一个能响应请求的服务有几个工程问题要处理。第一个问题是KV Cache。在自回归生成时每一步都要用前面所有token的K和V向量做注意力计算。如果不缓存每生成一个token都要重新计算前面所有的K和V复杂度是二次方增长。实现KV Cache时要注意缓存长度是动态的生成时要维护两个列表分别存K和V每一步把新的K、V追加进去再和新的query做注意力。第二个问题是精度。训练时用FP32或混合精度推理时可以把模型转成FP16甚至INT8显存占用和推理速度都会有明显改善。PyTorch里一行model.half()就能转FP16但要注意某些算子比如LayerNorm在FP16下可能不稳定需要保持FP32计算。INT8量化稍微复杂一些需要做校准但效果很值得。第三个问题才是服务化。最简单的方案是用FastAPI包一层HTTP接口把生成逻辑封装成一个POST请求。但生产环境要考虑并发控制、请求排队、超时处理这些就不是模型层面的问题了需要按常规后端工程的思路去做。我在本地部署这个迷你模型时用FP16精度后显存占用从原来的约2GB降到了1GB左右推理速度提升了接近一倍。这个优化思路在大模型上同样适用而且收益更明显。4.2 微调的思路在预训练模型上做领域适配如果你是从零训练了一个模型下一步很自然的需求就是让它适应特定领域。微调fine-tuning就是干这个的。最基础的微调方法叫做全参数微调full fine-tuning把预训练好的权重作为初始化在领域数据上继续训练。这个方法效果最好但计算成本高而且存下来的每个领域版本都是一整套完整权重存储成本大。更实用的方案是LoRALow-Rank Adaptation。思路是冻结原模型的权重不动在注意力层的Q和V投影旁边插入低秩分解的适配矩阵。训练时只更新这些新增的参数参数量可能只有原来的1%甚至更少但效果能接近全参数微调。class LoRALayer(nn.Module): def __init__(self, in_features, out_features, rank8): super().__init__() self.lora_a nn.Parameter(torch.randn(in_features, rank) * 0.01) self.lora_b nn.Parameter(torch.zeros(rank, out_features)) def forward(self, x): return x self.lora_a self.lora_b用LoRA微调时有几个细节值得注意。排名rank决定了新增参数量一般8到64之间够用不是越大越好。学习率要比全参数微调高一些通常1e-4左右。另外LoRA只适配了部分层的权重所以推理时要记得把LoRA的输出加到原始权重上或者用框架的合并功能把适配矩阵融回原模型。我在自己的迷你模型上试过用LoRA做古诗词风格微调。只用了约5万条古诗文本训练了几千步模型生成的文本风格就明显往古诗方向偏移了。这说明LoRA的容量虽然小但足够捕获领域风格特征。4.3 评估体系没有评估就没有工程模型做出来不是用来发朋友圈的要上线就得有评估。评估这件事很多入门者会忽略但它其实是AI工程里最接近工程的部分。离线评估通常分成两类。一类是量化指标比如困惑度Perplexity。它衡量的是模型对文本的预测能力值越低越好。困惑度算起来很直接模型在测试集上的平均交叉熵的指数。但我建议不要只看困惑度因为它和文本生成质量不是严格对应的关系——一个困惑度很低的模型生成内容也可能很无聊。另一类是指标生成了但需要人工或LLM来评。比如生成文本的流畅度、信息准确度、指令遵循度。这类评估一般要建立评测集和评分规则。我常用的做法是准备几十到几百条提示词跑完生成之后要么人工打分要么用一个更强的模型按评分规则自动打分。后者在实操上成本低、可复现逐渐成了主流。更重要的还有线上评估。模型上线后要看真实用户的使用数据请求成功率、平均响应时长、用户反馈、内容安全合规率。线上评估才是AI工程的终点因为只有真实流量能告诉你模型在实验室里没暴露出来的问题。我见过离线指标很漂亮的模型一上线就开始输出重复内容原因就是训练数据分布和用户实际输入分布差别太大。这类问题只能靠监控和反馈闭环来发现和修复。5. 常见问题与排查技巧实录最后分享一些我在实操中遇到的典型问题。这些问题在教程和文档里很少看到但几乎每个人都会踩到。5.1 训练Loss不下降或发散这是最多人问的问题。Loss不下降先查数据管道。我遇到过好几次语料加载函数写错导致模型看到的每个batch内容都一样loss自然纹丝不动。排查方法是打印几个batch的输入文本肉眼确认多样性。Loss发散成NaN优先查学习率和数值稳定性。学习率太高是最常见的原因。另外注意检查模型里有没有log(0)的运算比如某些实现里计算归一化概率时分子分母都可能是0。写代码时适当在分母上加一个小常量epsilon能避免很多坑。5.2 显存不够小模型还好但如果要把序列长度加到1024或2048显存很快就不够用了。几个可行的方案按优先级排序降低batch size、使用梯度累积gradient accumulation、开启混合精度训练、用序列打包把多个短文本拼成一个长序列。梯度累积是我最推荐的方法因为它几乎不影响模型效果只是训练时间稍微变长。5.3 生成的文本重复生成文本重复的原因通常有两个一是训练不充分模型没有学到足够丰富的模式二是解码策略问题。如果是后者调整采样温度、加重复惩罚就能有效缓解。如果是前者回头多训几个epoch比调参数更管用。6. 一些实际的体会我做完这个从零到一的AI工程项目之后最深的一个体会是AI工程的瓶颈往往不在模型而在系统思维。你能写出一个能跑的Transformer和你能构建一个可靠、可维护、可迭代的AI系统中间隔着大量看不见的工程工作。数据治理、实验管理、评估体系、监控告警这些才是真正决定一个AI项目能否长期运转的东西。另外学习路线方面我建议先完成一个最小闭环——哪怕模型很小、数据很少也要把数据、训练、评估、推理的完整链路走通。然后再逐步放大规模尝试微调、推理优化、分布式训练。别一上来就去看那些几十B参数模型的架构设计没有亲手实现过基础模型看那些只会觉得云里雾里。从零构建一个模型这条路确实慢但走过一遍之后你会发现以后再接触任何新的模型结构、任何新的训练技巧都能很快定位它在整个系统中的位置。这种我大概能猜到它内部在发生什么的感觉就是从零开始练习的价值所在。希望这篇文章能帮你少走一些弯路更快找到这种感觉。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。