资讯动态

从零手写AI工程:打破调包侠循环的完整路线图

发布时间:2026/10/3 5:59:05 来源:尧图企业网站定制
1. 打破调包侠循环为什么值得从零开始做AI工程大概在两年前我还是一个典型的调包侠。那时候做AI项目流程非常固定先到HuggingFace上找一个排名靠前的模型用transformers库的from_pretrained一把梭加载下来再写几十行微调代码炼丹跑上几天就交付一个AI应用上线了。模型效果不好那就换更大的模型或者去GitHub上再翻翻有没有人放出更好的微调代码。那段时间项目交付得很快领导很满意客户也觉得AI能力很强。但我心里一直有个疙瘩。每次模型在线上出现诡异的行为——比如对某个输入产生完全不可解释的输出或者训练集上指标好看、上线后却崩得一塌糊涂——我都只能靠试错去蒙完全没有能力从原理层面判断问题出在哪里。因为我对模型内部的注意力机制、tokenizer词表、训练数据的分布、损失函数的行为模式全都是一知半解。说白了我只是一个模型搬运工离AI工程师还差着十万八千里。后来我下决心做了个有点反效率的决定把主流框架全部放一边从数据清洗、tokenizer训练、模型架构设计、预训练脚本、RLHF对齐再到推理服务的部署全部手写一遍。这个项目我起名为ai-engineering-from-scratch。整个过程耗时大半年期间踩了无数坑但也正是因为这些坑我才真正理解了AI工程每一个环节背后的设计逻辑。这篇文章就是我对这段经历的完整复盘把我认为最具价值的路线图、实操细节和避坑经验全部整理出来希望能帮那些同样不想只做调包侠的朋友找到一条可以真正入门的路径。如果你也想搞清楚这些问题——为什么模型会幻觉为什么SFT监督微调后模型会变笨为什么同一个模型在不同的推理框架下速度差好几倍——那这篇内容就是为你准备的。2. AI工程全景图不是只有训练模型这么简单2.1 AI工程与AI研究的本质区别很多刚入门的朋友会把AI工程师和AI研究员混为一谈。我个人的理解是研究员的目标是把模型的准确率从80%提到85%而工程师的目标是让这个85%准确率的模型在真实业务里稳定跑半年、成本可控、出问题能快速定位。这是两种完全不同的思维方式前者追求上限后者保证下限。从零开始做AI工程意味着你要同时具备三块能力第一深度学习的理论基础知道注意力机制、反向传播、归一化这些底层原理是怎么一回事第二软件工程的硬功夫能写出高效的数据处理管道、可靠的训练脚本、可维护的推理服务第三系统工程思维能把GPU资源调度、分布式训练、模型版本管理、线上监控这些非AI的环节也纳入考量范围。我不建议你一开始就冲去读那本著名的《Build a Large Language Model from Scratch》。不是说这本书不好而是对于完全没有深度学习基础的读者来说它还是有点跳跃——你至少要先把PyTorch的基本张量操作、自动求导机制、简单的多层感知机和Transformer组件搞明白再去看从零构建LLM的内容会更顺畅。但如果你的基础已经足够那这本书绝对是值得逐行手敲代码的最佳路径之一。2.2 完整的工程链路拆解以我自己的项目经验来看一个完整的从零AI工程链路应该包含七个环节缺一个后面都要返工数据工程采集、清洗、去重、质量过滤、格式标准化。这是整个链路里最耗时、最不性感、但最重要的环节。业内有个说法叫垃圾进垃圾出我后来在项目里才真正体会到这六个字的份量。Tokenizer训练很多人直接下载别人的词表但不同语料、不同任务对词表的需求差异很大。自己训练一个BPEByte Pair Encoding分词器你会对词表大小如何影响模型容量有远超书本的理解。模型架构实现从Embedding、多头注意力、残差连接、LayerNorm、FFN到最终的输出头每个模块从零手写。这一步不用非得卷到SOTA目标是让自己对每一行代码都了如指掌。预训练包括学习率调度策略、数据采样顺序、梯度累积、混合精度训练、分布式通信。这里面的坑最多也是最考验工程能力的地方。监督微调与对齐SFT阶段构造指令数据、损失函数屏蔽、DPO/RLHF阶段设计偏好数据。评估体系不仅仅是跑几个benchmark要建一个能反映真实业务场景的评测集和评估流程。推理优化与部署包括量化、剪枝、vLLM/TensorRT-LLM等框架的选型、KV Cache优化、在线监控体系。很多教程会把注意力全放在第4步预训练上但真实项目中第1步和第6步往往才是决定成败的关键。我见过太多团队花大价钱训练模型结果因为评测集没建好连模型改进没改进都说不清楚。2.3 从零项目的四个阶段划分如果你也准备启动这样一个从零项目我的建议是不要一上来就规划训练一个70B模型。按阶段递进每阶段只解决一个核心问题阶段一基础构建。目标是用纯PyTorch实现一个小型GPT模型比如参数量在10M到50M之间在本地CPU或单卡GPU上训练能生成通顺的短文本即可。这个阶段核心解决的是我能不能让一个模型从随机初始化开始学会一件事。阶段二规模扩展。当你能在小模型上跑通全流程再逐步增大模型规模比如到300M到1B引入分布式训练技术。这个阶段核心解决的是算力不足时如何高效利用多卡。阶段三指令对齐。在预训练模型基础上做SFT和偏好对齐让模型学会听从指令。这个阶段核心解决的是模型从会续写到会干活的跨越。阶段四部署运维。把模型量化为INT8/INT4部署到推理服务里建立监控和版本迭代机制。这个阶段核心解决的是模型如何变成稳定的线上服务。这四个阶段不是严格线性的但在没走通阶段一之前不要急着跳到阶段四。我知道很多人一上来就想把模型量化部署但如果你连推理时KV Cache是怎么存取的都不清楚遇到显存不足的问题根本无从下手。3. 为什么必须手写TokenizerBPE分词器的实现与陷阱3.1 字节级BPE的核心原理先来说说Tokenizer分词器。很多教程把这块一笔带过因为transformers库一行AutoTokenizer.from_pretrained就搞定了。但如果你真的从零实现过BPE你会发现很多后续问题——比如模型为什么对某些拼写变体不敏感、为什么中英文混合语料效果差、为什么词表大小对推理速度有巨大影响——全都跟分词器有关。BPEByte Pair Encoding的核心思想很简单从字符级别开始不断统计语料中相邻字节对的出现频率把最高频的字节对合并成一个新词重复这个过程直到词表达到了预设大小。举个具体例子假设语料里lowlowestnewernewest这几个词出现频繁BPE会先统计到l-o-w这个组合然后合并成low再统计到low-e-s-t组合合并成lowest。最终词表里会同时出现low、lowest这样的完整词也有est、er这种词缀片段。工程实现上BPE训练分三步把训练语料中的所有字符转成字节序列统计每个字节对相邻两个字节的频次。迭代合并频次最高的字节对每合并一次就生成一个新token记录下这个合并规则。迭代到目标词表大小比如32000、50000输出最终的token列表和合并规则表。代码实现并不复杂核心逻辑大约就是下面这个循环# 伪代码BPE合并循环 def train_bpe(texts, vocab_size): # 统计初始字符频率 word_freqs defaultdict(int) for text in texts: for word in text.split(): word tuple(word.encode(utf-8)) # 转成字节序列 word_freqs[word] 1 # 初始化字节对频率 pair_freqs defaultdict(int) for word, freq in word_freqs.items(): for i in range(len(word) - 1): pair_freqs[(word[i], word[i 1])] freq merges [] while len(merges) vocab_size - 256: # 找到频率最高的字节对 best_pair max(pair_freqs, keypair_freqs.get) merges.append(best_pair) # 更新所有包含best_pair的词 ... return merges3.2 中英文混合语料的词表特化我在训练中文英文混合语料的分词器时踩过一个很典型的坑模型架构一样、数据量一样只因为词表不同下游任务效果差了将近10个百分点。原因在于中英文的语言特性差异很大。英文天然有空格分词BPE很快就学会了合并ing、tion这样的常用词缀而中文没有空格如果语料中文字符比例不够高BPE会把大量合并预算浪费在英文字节对上面导致中文字和词被切得很碎。比如人工智能四个字可能被切成人工智能这样的碎片模型需要更多的层数和更长的上下文才能拼凑出完整语义。解决这个问题有几个实践方案方案一中文语料先用jieba或其他分词工具做预切分让BPE在词的粒度上工作。方案二词表里预先放入中文常用字表比如按频率排序的前8000个汉字作为初始词表再开始合并。方案三直接使用字符级别的tokenizer比如按单字切分牺牲一些序列长度换取语义完整性。我的经验是如果你的语料是中英混合方案一加方案三结合最稳妥。先用jieba切分中文英文保持空格切分然后统一走BPE。词表大小建议开到5万以上否则中文的覆盖度明显不够。另外在训练词表时记得留出足够的特殊token位pad、unk、sos、eos和数字token我见过不少人忘了这茬后期补特殊token导致整个词表reindex前面的训练权重全部作废。3.3 Tokenizer与模型容量的联动关系这里我想多说一句词表大小直接决定了Embedding层的参数量。假设词表是5万隐藏维度是4096那么仅Embedding层就有5万×4096×2输入和输出两块约4亿参数。对于一个7B模型来说这占了将近6%的参数量而且这部分参数在推理时还经常是内存带宽的瓶颈——因为每生成一个token都要把整个输出Embedding矩阵扫一遍做logits计算。在实际工程里词表大小和模型性能的权衡就体现在这里词表太大Embedding层吃掉大量算力和内存词表太小每个token携带的信息量少序列变长注意力计算量反而增加。如果你从头训一个模型我建议先用小词表比如3万或4万跑通全流程等模型架构和训练管线都稳定了再逐步扩大词表规模。不要一上来就追求大而全的词表会显著拖慢你迭代验证的速度。4. 手写Transformer核心模块从单卡到多卡分布式训练4.1 注意力机制里的工程级细节Transformer的注意力公式大家都很熟了Attention(Q,K,V)softmax(QK^T/√d)V。但真正从工程角度实现时有一堆影响性能和稳定性的细节是论文里不会展开讲的。第一个是attention mask的实现。很多人的第一个版本是用一个巨大的布尔矩阵做mask然后用masked_fill把非法位置填成-inf。这个方法在小模型上没问题但到了大规模预训练阶段处理超长序列时布尔矩阵的显存开销非常吓人。业界更常用的做法是采用偏置相加additive mask定义一个形状为[batch, 1, seq_len, seq_len]的浮点矩阵合法位置全为0非法位置为-inf直接加到注意力得分上。更进一步还可以使用torch.nn.functional.scaled_dot_product_attention它内部会在融合的CUDA kernel里处理mask速度比手动实现快非常多。第二个是因果注意力的上三角掩码。实现时可以用torch.triu生成上三角矩阵但注意要搞清楚对角线是否保留。生成任务里第i个token只能看到前i-1个token所以对角线也要mask掉。不少新手在这里搞反导致模型偷看了当前token训练损失骤降看着指标很好但生成出来的内容完全不连贯。第三个是数值稳定性。当序列长度很长时QK^T的点积结果可能非常大直接softmax会梯度消失。除以√d是一种缓解手段但更稳妥的方式是用flash attention或者torch.nn.functional.scaled_dot_product_attention它们内部用了online softmax技巧既能省显存数值上也更稳定。我在自己的实现里早期版本就是因为在长序列上softmax溢出损失函数出现NaN排查了两天才定位到是数值范围的问题。4.2 残差连接与LayerNorm的放置顺序Transformer的另一个看起来简单但有讲究的设计是Pre-LN和Post-LN的差异。标准Transformer论文用的是Post-LN先残差后归一化但现代大模型比如GPT系列几乎都改用Pre-LN先归一化再残差。原因在于Post-LN在深层网络中容易出现梯度消失需要额外的warmup策略来稳定训练而Pre-LN对学习率没那么敏感训练更稳。但Pre-LN也有它的代价模型表达能力会略受限制因为每个子层的输出都要经过一次LayerNorm就在一定程度上标准化掉了分布信息。实际工程里主流方案是在关键地方加一个额外的scale参数来补偿或者设置norm作用于残差流上。我的建议是如果你追求稳定训练、快速出结果用Pre-LN如果你后期想压榨模型的上限精度再去尝试调整成Post-LN配合精细的warmup策略。另外LayerNorm里有一个epsilon参数一般是1e-5很多人不会去动它但在混合精度训练FP16/BF16下这个epsilon如果太小会导致数值不稳定出现输出方差异常的问题。踩过一次坑以后我习惯在任何自定义模型实现里用eps1e-5打底一旦出现loss异常波动首先检查是不是归一化层在低精度下的数值问题。4.3 ZeRO分布式训练的一步步配置在单卡上训练一个1.5B模型还是勉强能跑的但要到13B级别没有分布式训练能力基本寸步难行。分布式训练的完整实现包括通信原语、分片策略、流水线并行非常深这里我只说说我在项目里实际用到的、也建议你上手的第一步DeepSpeed ZeRO Stage 1和Stage 2。ZeRO的核心思想是把模型状态参数、梯度、优化器状态切分到多张卡上。Stage 1只切分优化器状态Stage 2把梯度和优化器状态都切了Stage 3连模型参数也切。对大多数人来说Stage 2是性价比最高的选择——它不需要改动模型代码只需要用DeepSpeedEngine包装就能在8卡环境下训练比单卡大4-6倍的模型。配置DeepSpeed时最容易踩的两个坑显存碎片。ZeRO需要对参数进行分片如果模型定义时有一些非常小的显式buffer分片效率会大幅下降。建议把模型里所有nn.Parameter统一定义在几个大tensor中或者至少保证每个参数块大于一定阈值。通信开销。Stage 2在每步训练都需要all-gather梯度通信耗时占比不小。一个有效的优化是开启gradient_accumulation_steps配合较大的micro_batch_size减少通信频率。我团队实际训练7B模型的经验配置大概是这样的8张A10080G开启DeepSpeed Stage 2zero_optimization.stage2gradient_accumulation_steps8train_batch_size32train_micro_batch_size_per_gpu1学习率峰值3e-4warmup占比3%用余弦衰减降到3e-5。这套配置在保持稳定性的前提下能把有效吞吐做到相对较高LOSS曲线也比较平滑。4.4 混合精度训练的三个关键设置现代GPU训练大模型默认都用混合精度。FP16能省一半显存、加速不少但两个数值问题要特别留心Loss Scaler损失缩放。FP16的数值范围有限梯度在反向传播时很容易下溢或上溢。PyTorch AMP会自动管理scaler但你要注意设置init_scale2**16类似的初始值并打开growth_interval自动调整。如果你用DeepSpeed可以在配置里设置fp16.enabledtrue并指定loss_scale0让框架自动处理。BF16 vs FP16。如果你用的是A100/H100这类新卡强烈建议直接用BF16而不是FP16。BF16的指数位和FP32一样数值范围几乎不会溢出省去了loss scaling的麻烦。代价是精度略低但实际训练大模型时影响不大对梯度稳定性反而有帮助。显存统计。别只看nvidia-smi显示的显存占用那不一定准。用torch.cuda.memory_summary()看的是实际申请和释放的情况。我遇到过训练中途显存突然涨了几G的情况排查原因是PyTorch的缓存分配器没有把临时张量内存返还给CUDA用torch.cuda.empty_cache()解决了一部分根治还是要优化代码里的临时变量生命周期。5. 构造推理模型SFT数据设计与RLHF偏好对齐的实战细节5.1 从会续写到会推理思维链数据的构造方法如果只看预训练模型它顶多是个高级的文本续写器。要让模型具备推理能力比如数学题、逻辑题、代码生成一个关键手段是在SFT阶段喂给它大量思维链Chain-of-Thought数据。所谓思维链就是让模型在给出最终答案之前先把推理步骤一步步写出来。我做的第一个思维链数据集就是从零构造的。最初我天真地以为只要把题目的详细解答过程写出来就行结果模型学到的只是字数变多了而不是逻辑变通了。后来才明白思维链数据有两个关键设计原则第一中间步骤不一定是正确的。为了增强模型的抗干扰能力需要混入一些包含错误尝试然后纠正的推理轨迹。这样模型才会学到我可以先走错一步再发现并修正而不是只会背答案。第二推理步骤颗粒度要适中。太粗的步骤比如只有两三行学不到推理习惯太细的步骤每一小步都完整写出会让模型产生冗长的输出习惯增加推理时延。关于这一点业内实践比较推荐的是把一步能完成的逻辑拆成2到4个中间子步骤每个子步骤尽量自包含即单独看也能理解。构造思维链数据的方式我个人很推荐从已有模型弱引导先用一个中等规模的通用模型对一批题目生成推理过程然后人工筛选、修正把这些数据再作为训练语料。这样做出来的数据比纯人工手写效率高很多而且覆盖面更广——因为你能喂给模型的题目数量取决于算力而不是人力标注速度。5.2 SFT阶段为什么模型会变笨损失钳制与数据配比很多人在SFT后发现一个奇怪现象模型在指令数据上表现变好了但通用能力比如代码、常识问答明显下降。这就是大模型领域常说的灾难性遗忘或者SFT模型变笨问题。原因其实不复杂SFT阶段的数据分布和预训练分布严重不一致模型在反向传播时为了拟合指令数据的loss把一部分通用知识的参数覆盖掉了。解决方案有几个层面数据配比。SFT数据不要只放指令数据要混入一定比例的通用文本数据比如代码、百科、书籍把通用数据和指令数据按7:3或8:2配比。不过要控制好通用数据的loss权重不要太大否则指令跟随能力会被稀释。损失函数屏蔽。SFT时应该只计算模型回复部分的loss不能把问题部分的loss也算进去。很多人实现时直接在完整序列上算交叉熵这样模型会花大量容量去记忆问题怎么复述而不是问题怎么回答。学习率设置。SFT阶段的学习率要远低于预训练阶段一般取预训练的1/10到1/20。我自己常用峰值3e-5到5e-5配合很短的warmup比如200步让模型在很小范围内微调而不是大幅改动参数。5.3 偏好对齐DPO为什么比RLHF更容易上手说RLHF可能让人望而生畏因为你需要同时训练4个模型Actor、Reference、Reward、Critic还有PPO那一整套稳定性的调参技巧。相比而言DPODirect Preference Optimization在工程实现上要友好得多——它不需要训练奖励模型也不需要在线采样只需要一个静态的偏好数据集和一个参考模型就能完成类似RLHF的对齐效果。DPO的数学思想是把最大化奖励模型分数这个目标转为直接优化策略朝向人类偏好数据。具体到代码实现你只需要构造好偏好对——同一问题有两个回答一个是人类更喜欢的chosen一个是不太喜欢的rejected然后计算两个回答的相对概率差用这个差值构造损失。# DPO损失核心逻辑简化版 def dpo_loss(policy_logps, ref_logps, chosen_mask, rejected_mask, beta0.1): # policy_logps: 当前模型对每个token的log概率 # ref_logps: 参考模型通常是SFT后的模型的log概率 chosen_policy (policy_logps * chosen_mask).sum(-1) chosen_ref (ref_logps * chosen_mask).sum(-1) rejected_policy (policy_logps * rejected_mask).sum(-1) rejected_ref (ref_logps * rejected_mask).sum(-1) log_ratio (chosen_policy - chosen_ref) - (rejected_policy - rejected_ref) loss -torch.nn.functional.logsigmoid(beta * log_ratio) return loss.mean()DPO有几个坑要记住参考模型一定要用SFT后的模型不能用预训练模型。逻辑是DPO是在SFT基础上再微调参考模型提供的是还没对齐之前的分布基线。如果参考模型选错了整个偏好优化就失去了参照系。beta参数KL散度约束强弱是核心超参。值太大模型变化很保守偏好改善不明显值太小模型容易偏离原有分布输出变得疯狂。实践上7B模型用beta0.1是个不错的起点跑完一轮eval再调整。偏好数据的质量远重要于数量。5000条高质量偏好对的效果经常好于5万条自动生成的噪声数据。如果偏好数据里有大量错误答案比正确答案标注得更好的情况DPO会直接把模型带偏。6. 从预训练到推理部署评估体系与工程化落地的缺一不可6.1 怎么构建能真正反映业务的评测集不少团队的模型评估工作流就三个字跑benchmark。从ZeroBench到MMLU、HumanEval、GSM8K刷完一遍看数字涨了就说模型变好了。但如果你拿这些benchmark结果去指导业务迭代大概率会被坑得很惨。因为通用benchmark测的是模型的平均能力而不是你做这个业务需要的能力。我在自己的项目里建立了一套三层评估体系第一层通用能力基准。用MMLU、GSM8K这些公开数据集对标行业内进展用户看模型是否在通用能力上没有明显倒退。第二层领域专测。针对项目的核心业务比如法律文本摘要、代码补全、SQL生成构造300到1000条的专测集每条都带人工标注的标准答案或评分维度。这个专测集不是在网上下载的而是从真实业务数据里抽样、清洗、标注出来的。第三层线上行为追踪。模型上线后记录所有输入和输出注意脱敏每周抽样进行人工评分相关、准确、安全、格式合规四个维度监控分数随时间的变化趋势。很多团队只做第一层这是远远不够的。我见过有模型MMLU刷得很高但从业务专测集的表现来看很多领域细节完全不对——比如把法律条文里的应当理解成了可以这是通用benchmark永远测不出来的。6.2 推理框架选型vLLM与TensorRT-LLM的取舍逻辑模型训练好了部署上线时又有一堆选择等着你。这步选型直接决定你的服务成本和P99延迟马虎不得。vLLM是目前最主流的开源推理框架核心卖点是PagedAttention——它把KV Cache像操作系统分页一样管理起来能实现非常高的显存利用率和吞吐。它的优点上手极快几乎支持所有主流模型动态batching效果好文档完善。缺点对定制化模型的支持偶尔滞后某些极端低延迟场景下不如TensorRT-LLM激进。TensorRT-LLM是NVIDIA官方的推理框架它会把模型编译成TensorRT引擎做非常激进的算子融合和显存规划。优点推理速度和延迟极致适合高并发低延迟的线上场景。缺点编译时间长大模型编译一次要几十分钟模型结构改动后要重新编译动态shape支持也相对麻烦。我的个人建议如果你还在快速迭代模型结构或者刚起步直接上vLLM省时省力收益很大如果模型结构已经稳定、业务有极端低延迟要求比如P99要求在100ms以内再花一到两周时间折腾TensorRT-LLM是值得的。6.3 量化踩坑从FP16到INT4的真实代价最后说说量化。业界主流做法是把模型从FP16量化到INT8或INT4来降低显存占用和推理成本。但量化不是免费的午餐代价要事先想清楚。我实测过一个7B模型在FP16、INT8、INT4三种精度下的表现精度显存占用推理吞吐tokens/sMMLU分数相对FP16业务专测分数相对FP16FP16约14GB约2200100%100%INT8约8GB约310098%-99%97%-99%INT4约5GB约420091%-95%88%-93%INT4确实省显存又加速但推理能力的下降是肉眼可见的尤其是在数学推理、多步逻辑这类对精确计算要求高的任务上掉分会更严重。如果你的业务场景涉及较强的推理能力建议只量化到INT8或者用INT4但也得结合一些补偿技术比如AWQ、GPTQ里的量化校准策略。另外提醒一句量化不是量化完就完事儿了量化后的模型一定要重新过一遍完整的评测集。我有过惨痛教训一次把模型量化到INT4后单测几个case看着都正常直接上线结果业务方反馈某些输入会出现胡言乱语一查就是量化误差在某些数据分布上被放大了。7. 算力受限时的替代路线LoRA微调与知识蒸馏的实战选择7.1 LoRA到底改了模型的什么很多个人开发者或者中小团队没有几十张A100的预算但又想微调一个大模型。LoRALow-Rank Adaptation这时候就非常有价值。它的核心思想非常优雅冻结预训练模型的所有参数只在每一层旁边注入两个小的低秩矩阵A和B前向传播时把AB的输出加到原模型的输出上。这样训练时只更新AB两个小矩阵参数量通常是原模型的0.5%到2%大幅降低了显存需求和训练时间。这里有一个理解误区要澄清LoRA并不是让小模型学会新能力它是在原有模型能力的基础上做定向调整。所以LoRA微调的效果上限取决于基础模型本身的能力边界。如果你拿一个7B的通用模型用LoRA去学专业法律问答效果会比直接用全量微调差一些但只要基础模型预训练时见过足够多的法律文本LoRA还是可以把它调教得很好。实现LoRA的技术路径现在很成熟。如果你用HuggingFace生态peft库几行代码就能搞定。但如果你想真正吃透它的原理强烈建议手写一次低秩分解模块# LoRA模块的核心实现简化版 class LoRALayer(nn.Module): def __init__(self, in_features, out_features, rank8, alpha16): super().__init__() self.lora_A nn.Parameter(torch.zeros(rank, in_features)) self.lora_B nn.Parameter(torch.zeros(out_features, rank)) self.scaling alpha / rank nn.init.kaiming_uniform_(self.lora_A, a5 ** 0.5) nn.init.zeros_(self.lora_B) def forward(self, x): return self.lora_B self.lora_A x * self.scaling注意代码里lora_A用Kaiming初始化lora_B用全零初始化这样初始状态下LoRA分支输出为0不会扰乱原模型的预测。训练时只更新A和B的梯度。7.2 知识蒸馏让小模型继承大模型的推理能力如果你想把一个70B教师模型的推理风格压缩到一个7B学生模型直接拿数据去训练小模型是不够的——小模型根本学不会那些复杂逻辑的隐含步骤。知识蒸馏的一个有效做法是让教师模型生成详细的思维链和解答过程把过程而不是答案作为训练目标。这被很多团队称作思维链蒸馏。蒸馏过程大致分三步用教师模型在目标领域题目上生成推理过程要求模型输出带逻辑链的完整解答。对生成的推理过程做质量过滤人工抽检自动规则过滤掉明显胡说的。拿这些数据对学生模型做SFT训练重点让学生模型学会一步步推理的表达习惯。我实测下来经过思维链蒸馏的7B模型在数学类专测集上的成绩能提升15到25个百分点虽然还达不到教师模型70B的水平但已经比直接用普通答案训练好太多了。而且学生模型的推理时延低得多可以用于高并发场景教师模型则可以在离线场景处理最难的case。7.3 算力分配的决策框架最后聊一个偏软技能的话题算力有限的团队怎么分配资源最合理我的决策框架是按任务重要度分层投入日常高频任务用蒸馏后的学生模型/量化模型跑追求低延迟和低成本。高难度/低频任务路由到教师模型或更大的云端API追求高质量。持续迭代每一到两个月用小批量高质量数据做一次LoRA微调或SFT更新验证后再灰度上线。这个大小模型混合路由的思路在实践中比一个模型打天下更适合中小团队。毕竟资源永远有限工程的核心就是学会做取舍把算力花在ROI最高的地方。8. 写在最后从零开始项目里我最后悔和最有价值的几个决定这个ai-engineering-from-scratch项目做完以后回头总结经验有后悔的地方也有觉得特别有价值的决定。最后悔的一件事前期直接在2张消费级显卡上硬跑1B规模的预训练浪费了大量时间在反复解决显存溢出和OOM上。如果重新来我会老老实实用10M小模型先跑通全流程再来谈规模。算力紧缺不是问题问题是没想清楚在哪个阶段解决规模问题。最有价值的一件事坚持手写Tokenizer和模型架构而不是直接调用transformers。虽然多花了大概三周时间但后面遇到线上问题时的排查速度快了不止一倍——因为我知道每一个报错背后对应的是代码里的哪一行、哪个数据结构。另一个很有价值的决定建立了专属于业务的评测集。这东西的ROI极高几乎每一次模型迭代都靠它来决策。评测集质量可能不高但只要有你就有了版本之间的对比基准而不是靠拍脑袋说这次模型好像变好了。如果你也想启动类似的项目我的建议是不要贪多先定一个非常小的里程碑。比如训练一个10M参数的小模型让它能用你的私有数据生成出读得通的中文短句。这个目标听起来不大但它能逼你打通数据清洗、Tokenizer、训练、推理这整条链路。一旦这条链路通了后续的所有扩展都只是换更大的数据、更大的模型、更多的卡而已。最后分享一个小技巧在整个项目过程中坚持写训练日志。不是指那种记录loss曲线的日志而是记录每一次我改了哪个配置、为什么改、结果怎么样的工程日志。三个月后回头看你就会发现那些最让你头疼的问题通常不是模型架构不够好而是一些很小的配置细节在默默拖后腿。把这些细节记下来就是你从调包侠走向AI工程师的真正台阶。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑