资讯动态

从零构建AI系统:1.5B参数模型全流程实战解析

发布时间:2026/10/2 7:49:55 来源:尧图企业网站定制
从零构建AI系统我用一个自制项目搞明白了AI工程的完整链路做AI工程开发这些年我一直有个执念不能只会调别人的API。所谓“ai-engineering-from-scratch”不是一句口号而是真正动手从零搭一套AI系统——数据自己清洗、模型自己训练、推理自己优化、评估自己设计。这个项目我前后花了四个月最终跑通了一个1.5B参数规模的文本生成与理解系统整个过程把AI工程的底层逻辑彻底盘明白了。这篇文章不写理论综述就把我从零构建的过程中踩过的坑、验证过的方案、想清楚的原理全部分享出来。适合那些已经能熟练使用现成模型但想深入理解AI工程内部机制的开发者也适合准备系统入门大模型技术的学生。无论你是想训练一个小模型验证想法还是想搞懂工业级AI系统的工程链路这篇内容都能给你一条切实可行的路线。1. 从零开始的本质这个项目到底在解决什么问题1.1 为什么选择从零构建而不是直接微调很多人问过我同一个问题现在开源模型这么多直接拿Llama或者Qwen微调不香吗为什么要费劲从零搞这个问题本身问得没毛病但答案不在“香不香”而在“懂不懂”。直接微调就像是给了你一台组装好的电脑你只需要换个显卡、加条内存——你能学会怎么用电脑但学不会电脑是怎么工作的。从零构建的意义在于你需要亲手解决每一个环节的问题这些问题是任何现成框架都不会替你考虑的数据配比怎么设计、词表怎么训练、上下文窗口怎么设、学习率怎么调、分布式训练怎么做梯度同步、量化之后精度掉了多少。每一个决策背后都是一整套工程逻辑这些逻辑恰恰是AI工程能力的核心。这个项目的目标不是做一个比开源模型更强的模型而是构建一套完整可复现的AI工程链路。说得直白一点你能不能靠自己的代码从一堆纯文本数据出发得到一个能跑、能推理、能评估的完整系统。如果能你就掌握了AI工程的底层方法论之后再看任何论文、任何框架、任何工具你看到的都不是黑盒而是一个个你可以替换和优化的环节。1.2 我选的切入点一个1.5B参数的基础模型定项目规模的时候我做了很多权衡。参数太小比如100M以下很多工程问题根本不会暴露出来——不需要分布式训练、不需要梯度累积、量化编个离线脚本就完事等于没练到真本事。参数太大比如7B以上训练成本会直接劝退个人开发者——单卡根本跑不动多卡又涉及复杂的并行策略一个集群配置问题就能卡你两周。我最后选了1.5B参数这个档位原因很实在单张A100 80G就能跑全参数微调对于个人项目来说硬件门槛可控训练数据量级在50B到100B token之间就能训出合理效果数据获取和处理成本在个人可承受范围内1.5B模型推理速度够快可以做实时交互和快速迭代评估参数量中等张量并行和流水线并行都能涉及但又不至于复杂到难以调试再往下细化我决定采用纯decoder架构基于Transformer配合因果注意力掩码。没有采用encoder-decoder结构因为现代大语言模型的主流方向是统一的decoder架构而且从零构建时decoder架构的代码实现更简洁后续扩展多模态、Agent、工具调用都更方便。上下文长度我定为4096 token这是当时计算资源和实际需求的折中——太短处理不了长文档太长训练效率会明显下降。1.3 项目全景从数据到推理的完整链路这个项目整体分为六个模块我用一条流水线把它们串起来数据工程从多个公开数据源收集原始文本设计清洗规则、去重策略、数据配比方案最终训练出Tokenizer模型架构用PyTorch从零实现Transformer解码器包括多头注意力、RMSNorm、RoPE旋转位置编码、SwiGLU激活函数训练系统实现分布式训练、混合精度、梯度累积、学习率调度、检查点管理推理优化实现KV Cache、FP16/INT8量化、批量推理、流式输出评估体系设计多维度评测集包括知识问答、推理、代码生成、指令跟随工程化封装把训练好的模型封装成可部署的服务提供统一接口每个模块单独拿出来都有对应的开源工具可以用但我的要求是全部自己实现核心逻辑。不是不用工具而是工具只用来做底层支撑比如PyTorch做张量运算、CUDA做算子执行上层逻辑全部自己写。这样才能真正理解每个环节的内部机制。2. 数据工程AI系统的起点也是最大的坑2.1 数据收集与清洗的实操细节我一开始天真地以为数据就是“爬下来、拼一起、开训”实际做了才知道数据工程的复杂度和工作量至少占整个项目的40%。数据质量直接决定模型上限架构和训练技巧都只是在逼近这个上限而已。我收集了以下几类公开数据源网页文本数十亿token规模来源多样化需要重度清洗百科类内容知识密度高适合作为基础语料代码数据GitHub上的开源代码训练代码理解和生成能力对话数据指令-回复对用于后续的对齐能力数学与推理数据带详细解题步骤的数学题提升推理能力清洗是我投入最多的环节设计了一整套管道。第一层是格式清洗把HTML标签、URL、广告词、导航信息全部剥掉只保留正文文本。第二层是语言过滤用语言识别模型筛掉非目标语言的噪声数据。第三层是质量过滤使用困惑度评分和启发式规则剔除低质量内容——比如标点符号比例异常、重复字符过多、句子长度分布离谱的样本。第四层是去重我在这个环节踩了大坑最初只做了精确去重后来发现很多文本虽然不完全一样但结构高度相似这些近似重复会导致模型部分记忆过强泛化能力变差。后来我加了MinHash去重和SimHash去重效果比之前好了不少。2.2 数据配比决定模型性格的隐藏参数数据配比是我在这个项目里学到的最有价值的东西之一。很多人以为把所有数据混合在一起就完事但实际不同来源的数据对模型能力的影响是非线性的。代码数据占比太高会让模型变得机械对话数据占比太高会让模型变得空泛。我最终采用的配比方案经过多轮调优网页文本40%百科/知识类20%代码数据15%数学与推理10%对话指令10%其他新闻、书籍、论文5%这个配比的逻辑是网页文本提供语言的广泛覆盖和多样性百科保证知识密集度代码和数学负责逻辑推理能力对话指令则为后续对齐打底。关键是代码和数学的比例不能太低至少要有20%以上否则模型的逻辑能力会很弱。我还做了每类数据的样本量换算。因为不同类型数据的平均长度差异很大所以我用token数而不是样本数来配比。计算方法是先随机抽样各类数据的平均token长度然后设定目标token数反推需要采集的样本量。比如目标代码数据是15B token代码样本平均2000 token就需要750万条代码样本这个量级对数据收集的要求非常明确。2.3 Tokenizer训练很多人容易忽略的关键环节很多人直接用现成的Tokenizer但既然是从零构建Tokenizer的每一步最好也都自己走一遍。Tokenizer决定了模型的基本粒度词表太小会导致每个token的信息量不够序列过长词表太大会增加embedding层的内存占用和训练成本。我选择了BPEByte Pair Encoding算法主要在HuggingFace的tokenizers库上做二次开发。词表大小试了多个档位最后定在50K。50K对于中文和英文混合的场景是一个比较平衡的选择英文单词基本能覆盖中文常用字和常用词也能覆盖大部分。训练Tokenizer的时候有一个细节值得注意语料配比要和正式训练的配比保持一致。如果正式训练中代码数据占15%Tokenizer训练时也应该保持这个比例否则中英文边界、代码中常见的空格缩进结构就可能会在token化时处理得不稳定。另外我把训练好的Tokenizer做了一组压力测试用它跑中文古诗、Python代码、数学公式、JSON结构分别统计序列长度分布和unk占比。测试中发现了几个特殊token被错误拆分的情况我额外补充了这些错误示例到语料中重新训练这才让Tokenizer对格式类内容的表现稳定下来。3. 模型架构实现从零写一个Decoder3.1 核心模块的选型逻辑Transformer解码器的基本结构大家都熟悉但每个模块的具体选型是有讲究的这些细节决定训练稳定性和推理效率。注意力机制我采用的是标准的多头自注意力头数设为16每个头的维度是64这样总维度对应1024。在实现时特别处理了因果掩码上三角矩阵确保每个位置只能attend到之前的位置。推理时为了效率KV Cache是必备的这需要在代码层面把计算和缓存逻辑分开设计。归一化层选用的RMSNorm而不是LayerNorm。两个的差别在于RMSNorm去掉了均值归一的步骤只做方差归一化计算量更低在深层网络中训练稳定性反而更好。这个细节看似小但训练中确实感受到了RMSNorm带来的收敛速度提升。位置编码选用了RoPE旋转位置编码没有用传统的绝对位置编码。RoPE的关键优势是通过旋转变换把相对位置信息直接编码进注意力计算中模型对超出训练长度的位置有一定的外推能力。实现的时候需要把每个位置的旋转角度预先算好然后对query和key做旋转操作这个逻辑改起来麻烦但对模型的长文本能力帮助很大。激活函数用了SwiGLU而不是传统的ReLU或者GELU。SwiGLU本质上是一个门控线性单元和Swish激活的组合增加了一层非线性变换。虽然参数量有少量增加但实测对于同样的训练量模型的困惑度有明显下降。嵌入层和输出层我会共享权重这样能减少大量参数同时输出层又不需要单独的矩阵。最终模型结构参数如下参数项数值层数24隐藏维度1536注意力头数16头维度96词表大小50,000上下文长度4096参数量约1.5B3.2 初始化和稳定性设计模型初始化的细节直接影响训练早期的稳定性。Embedding层我用均值为0、标准差为0.02的正态分布初始化这个范围是Transformer的常见选择。各层权重根据方差缩放初始化具体做法是按层数做缩放。关键细节是残差连接处的初始化。标准的做法是在每个子层的输出部分加一个缩放因子数值等于1除以层数开根号。这么做的目的就是防止深层网络中梯度在反向传播时因为连乘效应而过小从而避免深层网络无法训练的问题。训练早期还会遇到一个经典现象loss在刚开始的几千步会突然飙升一下然后再下降。这通常是因为模型在早期快速调整参数分布时出现了不稳定。解决方法是加warmup步数让学习率从很小的值慢慢爬升到设定值同时配合梯度裁剪。我把梯度裁剪阈值设为1.0至少在1.5B这个规模上训练全程没有出现梯度爆炸的情况。3.3 从零实现中的调试心得Debug一个从零实现的模型很多时候问题不是出在理论上而是出在一些琐碎的地方。我踩过一次印象很深的坑forward时忘记把attention mask传给所有层导致短文本正常、长文本时loss忽然变成NaN。排查了很久才定位到是mask广播维度不匹配。另外一个经验是尽量在训练前用小规模数据做overfit测试。我先构造了100条包含简单规律的文本让模型跑几百步看能不能把loss降到接近0。如果连这种小数据集都拟合不了说明代码逻辑肯定有问题。这比直接上大规模训练去等半天再发现问题要高效得多。我建议每个人在做类似项目时都保留这一环节它能节省的调试时间至少在一半以上。再有一点值得分享训练过程中的loss记录很重要但我更看重的是验证集loss和实际生成质量。模型在两个不同batch之间的验证集loss波动过大时说明batch size可能偏小或者学习率需要调整。一个坚实有效的判断指标比一堆训练技巧更重要。4. 训练系统比想象中复杂十倍的工程环节4.1 优化器与学习率调度训练系统是整个项目中最“工程”的部分。刚开始我用的是AdamW优化器这个基本是标配。但在学习率选择上试出来的经验是1.5B模型、batch size 512按token计约200万tokens per batch对应的峰值学习率3e-4比较合适这个数比很多论文里写的值略低一些。在把峰值学习率定下来之前我先跑了几个小规模的对比实验即Learning Rate Finder。小规模下跑几百步观察不同学习率下loss下降的情况选了较为平稳的一个区间。这个环节我很推荐因为直接抄别人的学习率不一定适合你当前的数据分布和模型规模。学习率调度我用了预热加余弦衰减预热2000步从初始值的1/10线性升到峰值然后在后续训练中按cosine曲线衰减到峰值的1/10。预热解决的是训练初期参数剧烈变动导致的不稳定余弦衰减则保证了后期可以更平滑地逼近最优点避免在最优解附近震荡。4.2 分布式训练的实战策略1.5B参数单卡也能训但训练速度太慢每轮迭代都要等待很久不利于快速试错。我最终用了4卡A100做分布式训练采用ZeRO Stage 1。整体思路是优化器状态分片到不同GPU上每个GPU只负责自己分片对应的优化器参数更新但梯度在全卡范围内做AllReduce聚合。这种方式能把显存占用降到单卡满载训练的40%左右同时通信开销尚在可接受范围内。我还开启了混合精度训练用BF16而不是FP16。BF16的指数位和FP32一致动态范围更大在训练过程中基本不会出现FP16常见的溢出问题。代价是尾数精度降低但在大规模训练中梯度的噪声效应反而能带来一定正则化效果实测收敛速度还略优于FP16。梯度累积是我同时打开的一个功能。因为实际每张卡的batch size不可能设得太大我把目标batch拆成多个micro-batch累积梯度后再做参数更新。我的做法是micro-batch设为8梯度累积步数为4这样在不提高单卡显存压力的前提下等效放大了batch size。4.3 检查点管理与训练监控从零训练动辄跑一两周检查点管理做不好代价就是白训。我每500步保存一个临时检查点每个5000步保存一个周期检查点同时保留最近3个周期检查点的备份。磁盘空间允许的情况下再多保留不同训练阶段的标记版本方便后期回溯某个中间状态做评测。训练监控方面我用了一套组合方案loss曲线、梯度范数、学习率变化、显存占用、吞吐量。loss曲线看趋势梯度范数看稳定性显存占用看内存是否泄漏吞吐量看训练效率。这些都是最基本的指标但把每个指标的合理区间摸清楚之后系统出问题几乎可以秒定位。举个具体例子有段时间loss下降曲线正常但梯度范数从1.0级别突然涨到5.0以上对应的训练吞吐也在下降。查了之后发现是数据加载线程阻塞导致GPU空转同时上游队列积压了过多未处理样本。这种问题如果只看loss根本发现不了必须靠多维监控才能及时感知。5. 推理优化与部署让模型真正能用起来5.1 KV Cache和自回归解码的工程化训练完成后的模型只是“半成品”真正能用起来必须做推理优化。自回归生成的朴素实现会有一个严重问题每次生成新token都要重新计算之前所有token的key和value。这样不仅慢而且随着序列变长计算量呈二次增长。我说服自己实现KV Cache的理由很简单如果这个模型要支持实时对话生成速度不够就根本没法用。KV Cache的核心思路是把历史token的key和value缓存下来每次只计算当前token对应的新key和value然后追加到缓存中。配合prefill和decode分离的推理策略首token延迟能降低很多。具体实现时我维护了一个大小可变的缓存张量并设计了动态扩缩容机制。序列太长时缓存超出显存限制自动把最早的序列段置为无效只保留最近窗口内的内容。这个逻辑需要处理RoPE旋转位置编码在不同位置之间的对齐问题花了一些时间调试但最终让长文本推理的显存占用从线性增长变成了窗口内可控。5.2 量化INT8和FP16之间的取舍为了让模型在推理设备上有更好的延迟表现我做了量化实验。FP16是默认方案在此基础上对比了INT8动态量化。INT8量化实现并不复杂重点在于权重和激活值的定点化。权重量化用per-channel的方式每个输出通道单独算scale和zero-point这样做量化误差要显著小于per-tensor。激活值我采用了动态量化即运行时根据实际激活值范围选择scale。实测数据对比方案显存占用生成速度token/s困惑度验证集FP163.2GB458.32INT8动态量化1.8GB628.51INT8显存占用接近减半生成速度提升约37%但困惑度增加了0.19这个精度损失幅度在可接受范围内。实际做评估时面对标准知识问答和代码生成任务INT8的输出质量几乎看不出差别只有在长尾词汇和复杂推理题中偶尔会出现次优结果。我的建议是分场景部署对延迟敏感的在线服务用INT8对精度要求高的离线分析用FP16。不需要在“哪个更好”上纠结量化本身是一种工程取舍而不是模型层面的能力提升。5.3 服务化部署的完整方案模型跑通之后我封装了一个类OpenAI接口的服务支持流式输出和批量请求。这里有几个工程细节值得提一下。流式输出用SSE协议实现。因为大模型生成一个完整回答可能需要好几秒如果用户要等全部生成完才能看到结果体验会很差。我按token粒度将输出推送给客户端用户能看到文字逐字蹦出来体感延迟大幅下降。实现的时候需要注意网络分层避免因为缓冲导致SSE事件被积压。并发控制也很重要。单GPU上同时跑多个推理请求显存和计算资源都会互相争夺。我设置了请求队列同一时刻最多执行2个推理任务其余请求排队等待。后来加了Continuous Batching策略把不同请求的prefill和decode阶段合并成同一个batch计算GPU利用率明显提升吞吐量相比逐请求处理提高了将近一倍。6. 评估体系让模型的进步和退化有据可查6.1 多维度评测集设计训练过程中我面对的终极问题是这个模型到底变强了没有只看loss不够因为loss下降不代表下游能力提升。我把整个评估体系拆分成了若干个独立维度。知识问答常识类、事实类问答测试模型的知识广度和准确性逻辑推理数学应用题、符号推理题测试模型的逻辑链能力代码生成根据自然语言描述生成代码测试代码理解和生成能力指令跟随多轮对话测试看模型能否理解复杂的用户意图文本摘要对长文档做摘要测试信息抽取和重组能力每个维度我都设计了固定评测集数量从几百到几千条不等。评测集的数据必须和训练数据来源隔离确保评测结果反映的是泛化能力而不是记忆能力。这一点很多人容易忽略如果评测集本身和训练集重叠结果就是虚高。6.2 评测过程中的关键发现通过评测系统我发现了几个有价值的问题。最显著的是模型的知识问答能力在训练的中期就基本饱和了后期继续训练更多数据提升幅度极小。但逻辑推理能力会随着训练持续缓慢提升说明推理能力对数据量的需求更大。另一个发现是代码生成能力非常依赖数据配比中代码数据的占比。我把代码数据占比从10%提升到15%之后代码评测的通过率提升了将近20%而知识问答能力几乎没有下降。这说明不同能力之间的数据共享并不是互斥的关键是找到平衡点并对占比较为敏感的能力做针对性的数据倾斜。模型生成长度控制也是个反复调试的重点。限制生成长度之后模型的回答质量明显上升因为它的长文本生成中经常出现重复和偏离主题的问题。后来我加了重复惩罚项对已生成token在采样时施加一个衰减系数这个操作对输出质量的影响非常直接。6.3 监控指标与人工评估配合自动评测只能解决“是否变好”的问题但“好”本身是多元的。我保留了一个固定的人工评估流程每周抽一批模型输出做盲测和其他开源同规模模型放在一起对标只针对流畅性、相关性、准确性三个指标打分。人工评估的意义在于发现自动指标无法感知的问题。有一次自动评测显示困惑度明显下降但人工评估时发现模型在指令理解上开始出现“过度对齐”即对指令中的例子机械模仿丧失了灵活性。这个发现促使我调整了指令数据的比例之后模型的表现才稳定下来。7. 常见问题与排查技巧实录7.1 训练阶段的典型问题速查表现象可能原因排查思路loss为NaN学习率过高、梯度爆炸检查梯度范数降低学习率或增加梯度裁剪loss不下降数据配比异常、初始化不当用小数据overfit测试验证代码逻辑训练速度慢CPU数据加载瓶颈、GPU利用率低检查数据加载管道使用异步加载验证集loss反弹过拟合增加数据多样性或提前停止生成重复文本温度设置过低、重复惩罚不够调高温度、增加重复惩罚系数7.2 训练Infrastructure的坑数据加载是训练环节中最容易出问题的地方也是最容易被忽视的。我的经验是尽量做离线token化提前把原始文本全部转成token ID并保存为二进制格式训练时直接读取。虽然这样会占用额外磁盘空间但换来的是训练过程中数据加载速度提升数倍。在线token化看似省事实际上会频繁触发Tokenizer推理并产生大量CPU耗时GPU就很难吃满。分布式训练的集群配置容易在通信阶段出问题。有一次训练到第2000步突然所有GPU的loss都开始抖动后来排查发现是某一块GPU的散热出现问题导致降频影响了AllReduce的同步速度。这提醒我分布式训练的问题未必在代码层硬件环境同样值得持续监控。7.3 评估和部署的坑评测部署时的坑主要体现在生成参数上。同一个模型温度设为0.2和0.8评测结果差异可能超过想象。所以对比实验必须在相同生成参数下进行否则测试结果没有可比性。部署时最容易忽视的是预热推理。模型服务刚启动时CUDA kernel的初始化会导致首次推理特别慢这种情况下直接上线会导致第一批请求超时。我的做法是在服务启动后主动发几个探测请求做预热等模型跑过几轮推理后再对用户开放这样可以避免启动延迟带来的请求失败。8. 最后的实操经验与后续扩展建议回顾整个项目我觉得最值得说的不是技术本身而是“从零构建”这个动作带来的思维转变。完成这个项目后再看那些开源模型的论文和技术报告我的关注点从“它效果多好”变成了“它到底在哪个环节做了优化”这是本质层面的不同。对于准备开始类似项目的朋友我有几条建议第一范围控制很重要第一次做不要追求大模型把1B到3B之间做透比什么都强。第二数据工程的时间和成本预算是训练本身的两倍别低估数据清洗和配比的复杂度。第三建立评测体系一定要超前于模型训练不要等模型训完才开始设计评测方法否则你无法知道训练过程中的每个阶段模型能力是在提升还是在退化。后续我想在这个基础上扩展三个方面一是多模态能力让模型具备图像理解的能力二是Agent能力让模型学会使用工具和规划任务三是更高效的推理优化尝试稀疏率和投机解码等更前沿的技术路线。这些方向都是从这个“从零构建”的项目中自然生长出来的也希望能给读到这里的你一些启发。

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

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

免费获取报价 →
↑