深度学习工程实践
深度学习工程实践
学会网络结构和损失函数,只说明你能读懂论文。工程交付还要回答:数据从哪里来,训练是否可复现,模型怎么证明有效,导出后是否仍然一致,线上出错时如何定位和回滚。
本页以 CNN、RNN 和 GNN 都适用的环节为主,代码使用 PyTorch 风格示意,不提供可直接运行的训练脚本,也不新增 Python 依赖。RNN Encoder-Decoder 的机制、<BOS>、<EOS>、padding 和基本解码见深度学习基础;大模型规模化训练与推理分别见模型训练与对齐和模型推理与部署。
一条完整工程链路
深度学习项目不是“选一个模型然后训练”,而是一条需要逐段验收的链路:
flowchart LR
problem[任务与指标] --> data[数据管线与版本]
data --> split[切分与泄漏检查]
split --> baseline[基线模型]
baseline --> train[训练与实验记录]
train --> checkpoint[Checkpoint]
checkpoint --> eval[离线评估与错误切片]
eval --> export[导出与一致性验证]
export --> serve[推理服务]
serve --> monitor[线上监控]
monitor --> feedback[Badcase 与漂移]
feedback -.回到数据与评估.-> data每一段都有自己的输入、输出和失败模式。模型指标提升但数据切分泄漏,不能上线;离线指标很好但导出后预处理不一致,也不能上线;服务稳定但线上分布已经变化,还需要重新评估。
任务与模型契约
先固定输入输出
在写模型代码前,用一页文档固定数据和接口。至少写清:
| 项目 | 需要固定的内容 | 常见事故 |
|---|---|---|
| 输入 | dtype、shape、取值范围、缺失值处理、单位 | 训练用归一化图片,线上传原始像素 |
| 标签 | 类型、shape、类别编号、无效标签 | 类别编号在训练和服务中含义不同 |
| 变长字段 | lengths、PAD_ID、BOS_ID、EOS_ID | padding 被当成真实数据或标签 |
| 输出 | logits、概率、类别、序列和终止原因 | 客户端把 logits 当概率使用 |
| 版本 | 数据、词表、预处理、模型、配置 | 只保存权重,无法复现输入处理 |
| SLA | 质量、延迟、吞吐、内存和成本 | 只验准确率,不验 P95 延迟 |
典型输入形状如下:
| 模型 | 训练输入 | 需要额外传递的内容 |
|---|---|---|
| CNN | [B, C, H, W] | 图像尺寸、归一化参数 |
| RNN | [B, T, F] | 每条序列的 lengths、必要时的 hidden state |
| RNN seq2seq | [B, S] 与 [B, T] | 词表、PAD/BOS/EOS、解码上限 |
| GNN | 节点/边特征与图索引 | 节点到图的归属、节点和边的 schema |
对外接口应该返回可排查的信息。生成式序列服务除了 tokens,还应记录 termination_reason:eos、max_length、cancelled 或 error。分类服务至少保留模型版本、预处理版本和置信度,不能只返回一个类别字符串。
训练、评估、推理的接口一致
训练代码、离线评估脚本和线上服务应复用同一套预处理与后处理逻辑。可以用不同的 batch size 和设备,但不能悄悄改变:
- tokenization、词表和特殊 token 的编号;
- 图像 resize、裁剪、归一化和颜色通道顺序;
- 缺失值填充、特征顺序和单位换算;
- 序列的 padding、mask、截断和
<EOS>处理; - 置信度阈值、标签映射和后处理规则。
只要输入契约发生变化,就算模型权重没有变化,也应当当作一次需要评估的版本变更。
数据管线与 batch
数据切分先于模型选择
训练集用于学习参数,验证集用于选模型和调超参数,测试集只用于最终验收。切分方式要匹配真实上线场景:
| 数据特征 | 推荐切分 | 防止的问题 |
|---|---|---|
| 独立同分布样本 | 分层随机切分 | 类别比例在集合间严重失衡 |
| 同一用户、设备或商品有多条记录 | 按实体分组切分 | 同源样本泄漏,让测试集看起来过于简单 |
| 时间序列或预测未来 | 按时间切分 | 用未来数据预测过去,评估虚高 |
| 同一文档切成多个片段 | 按文档切分 | 相邻片段跨集合,模型实际上见过答案 |
| 数据持续到达 | 固定历史窗口 + 时间留出 | 无法模拟线上新分布 |
所有切分都要保存随机种子、规则和集合快照。不要每次运行都重新随机切分,否则两个实验的分数可能来自不同测试样本,无法比较。
数据质量检查
在训练前把数据检查做成脚本或流水线步骤,而不是靠人工打开几条样本:
- schema、dtype、shape 和必填字段是否正确;
- 空样本、异常值、重复样本和损坏文件的比例;
- 标签是否在合法范围,类别分布是否异常;
- 用户、文档、设备和时间是否跨集合重复;
- 图像通道、尺寸、色彩空间,文本编码和 token 长度是否符合约定;
- 训练集、评估集与线上回流数据的授权和用途是否一致。
预处理统计量只能在训练集上拟合。例如标准化的均值和方差、词表、归一化范围,都不能先用全量数据计算再切分。否则验证集信息已经泄漏进训练流程。
数据集快照至少记录版本号或 hash、来源、清洗规则、标注指南、切分规则、许可证和生成时间。数据版本与模型版本的关系见数据与标注。
Dataset、DataLoader 与 collate_fn
固定长度数据可以直接堆叠。变长数据需要 collate_fn 在组成 batch 时完成 padding,并返回真实长度。真实项目中还应处理排序、反排序和设备迁移:
1 2 3 4 5 6 7 8 9 10 | |
PAD 只解决形状对齐。RNN Encoder 可以使用 packing 减少补位计算,Transformer 通常使用 padding mask;Decoder 的目标 loss 还要使用 loss mask。三者不是同一个概念,必须在接口文档中分别命名。
当序列长度差异很大时,按长度分桶可以减少 padding 浪费。分桶改变的是 batch 组成,不应改变样本标签或验证集边界;验证和测试时也要记录长度分布,防止只在短样本上取得好成绩。
最小训练闭环
训练和验证是两种模式
典型训练步骤的顺序是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
model.train()打开 Dropout、BatchNorm 等训练行为。model.eval()固定推理行为,不能用训练模式的结果验收。torch.no_grad()节省验证阶段的显存并避免无意义的计算图。zero_grad、反向传播和optimizer.step()的顺序应固定并可记录。- 训练 loss 用于优化,验证 loss 和任务指标用于选择 checkpoint。
RNN seq2seq 的 loss 必须对齐右移后的目标,并忽略 PAD:
1 2 3 4 5 6 7 8 9 | |
上面的缩进只为突出伪代码中的数据流;正式实现要统一代码格式,并在单元测试中验证 decoder_in 与 gold 没有错位。序列任务还要把 Teacher Forcing 下的 loss 与真实自回归解码指标分开记录,具体机制见深度学习基础。
先过拟合一个 batch
第一次训练不要立刻跑完整数据集。固定一个很小的 batch,关闭或减弱随机增强,观察模型是否可以把它过拟合到接近零的训练误差。这个实验用于验证:
- 输入和标签没有错位;
- loss 连接到了正确的输出;
- padding 和 mask 没有屏蔽真实信号;
- 参数确实收到梯度并更新;
- 输出类别映射和后处理没有写反。
如果一个 batch 都过拟合不了,优先检查数据、标签、shape、loss 和优化器,不要先换更大的模型。这个检查通过后,再逐步恢复数据增强、正则化和完整数据集。
梯度累积、裁剪与混合精度
- 梯度裁剪:限制梯度范数,主要用于 RNN 长序列和训练不稳定的场景;裁剪值应记录并在验证集上比较,不是越小越好。
- 梯度累积:连续处理多个小 batch,只在累计若干步后更新一次参数,用显存换取更大的有效 batch size。记录有效 batch size 和更新频率,否则学习率实验不可比。
- 混合精度:用 FP16 或 BF16 保存部分中间结果,减少显存并提升吞吐。FP16 更容易溢出,BF16 通常数值范围更宽;正式实现需按当前 PyTorch 与硬件支持核对
autocast、梯度缩放和算子兼容性。 - 激活检查点:不保存部分中间激活,反向时重新计算,以额外计算换显存。长序列和大 batch 场景先测吞吐变化,再决定是否启用。
性能优化必须有基线:固定模型、数据、硬件、精度和 batch 条件,逐次只改一个变量,同时记录吞吐、显存、训练时间和验证指标。
可复现与 checkpoint
实验记录的最小四元组
每次训练都要能回答“哪段代码,用哪份数据,以什么配置,产出了这个模型”:
| 资产 | 至少记录 |
|---|---|
| 代码 | Git commit、训练入口、依赖版本 |
| 数据 | 数据集版本/hash、切分规则、预处理版本 |
| 模型 | 架构、权重初始化、词表或标签映射 |
| 配置 | 学习率、batch、epoch、优化器、精度、随机种子 |
再补充训练开始与结束时间、硬件、指标曲线、失败原因和 checkpoint 路径。固定随机种子只能减少随机差异,不能保证不同 GPU、驱动、并行顺序和算子实现下完全一致。
实验记录应包含假设、只改变了什么、结果、结论和下一步。只保存一张 loss 曲线截图,无法支持复盘和回归。
Checkpoint 不是只存权重
可恢复训练的 checkpoint 通常包含:
- 模型
state_dict; - 优化器和学习率调度器状态;
- 当前 epoch、global step 和最佳指标;
- 随机数状态(需要严格复现时);
- 训练配置、数据版本、词表版本和代码 commit;
- 评估结果与保存时间。
同时保留两个角色不同的版本:
- latest checkpoint:最近一次保存,用于中断后继续训练;
- best checkpoint:验证指标最好的版本,用于候选发布。
保存频率要按 step 和预计损失共同决定,不能只在 epoch 结束时保存。至少保留上一份可用文件,避免当前写入中断造成唯一 checkpoint 损坏。恢复后先检查 loss 曲线是否连续、global step 是否正确,再继续长时间训练。
不要把任意 Python 对象作为唯一 artifact。优先保存权重和明确的 JSON/YAML 元数据,并在干净环境中验证加载;模型文件、词表、预处理和配置要作为一个可定位的发布包管理。
调试与故障排查
推荐排查顺序
训练或推理异常时按以下顺序缩小范围:
- 固定一条样本和一个 batch,记录原始输入、标签、输出和版本。
- 检查输入、标签、logits、mask 的 shape、dtype、device。
- 检查标签范围、类别映射、
BOS/EOS/PAD是否错位。 - 检查 loss 是否为
NaN或Inf,再检查梯度范数。 - 让模型过拟合一个 batch,排除数据和实现错误。
- 对比
train()、eval()、Teacher Forcing 和真实自回归结果。 - 检查 tokenizer、预处理、词表和模型 artifact 是否成套。
- 最后再调整学习率、模型规模、batch size 和正则化。
故障对照表
| 症状 | 优先检查 | 常见修复 |
|---|---|---|
| loss 完全不下降 | 标签、loss 连接、学习率、输入范围 | 打印一条样本,过拟合一个 batch |
| loss 剧烈震荡 | 学习率、有效 batch、异常样本 | 降低学习率、梯度裁剪、定位异常 batch |
出现 NaN/Inf | FP16 溢出、梯度爆炸、非法输入 | 切换 BF16/FP32、裁剪梯度、清洗数据 |
| 训练好、真实推理差 | Teacher Forcing、EOS、padding、解码策略 | 跑自回归回归集,记录终止原因 |
| 序列总是提前结束 | EOS 标签、初始状态、解码阈值 | 检查目标右移、统计空输出率 |
| 序列从不结束 | EOS 未进入标签、循环无上限 | 加 max_length,追查 EOS 学习信号 |
| 输出全部相同 | PAD 参与 loss、类别映射、梯度 | 检查 ignore_index 和 label 分布 |
| GPU 利用率低 | 文件 I/O、CPU 预处理、padding 比例 | 预处理缓存、长度分桶、调整 worker |
| batch 越大反而越慢 | 最长样本拖尾、显存换页、通信 | 统计长度分布,限制 batch token 数 |
| 线上状态串线 | hidden state 生命周期、session key | 默认请求重置,流式接口显式传 state |
| 导出后结果变化 | eval 模式、算子、预处理和精度 | 用固定样例逐层或端到端对比 |
不要同时改学习率、模型结构、数据清洗和解码参数。变量过多时,即使结果变好,也无法知道真正原因。
评估与回归
指标要和任务及上线方式匹配
分类、回归、检测和分割的基本指标见机器学习基础与深度学习基础。序列任务还要分别记录:
| 指标 | 用途 |
|---|---|
| token-level loss / accuracy | 检查逐位置学习,不能替代真实生成质量 |
| BLEU、chrF、ROUGE 等 | 比较序列与参考答案的重叠,适合版本对照 |
| exact match 或任务成功率 | 检查结果是否满足业务任务 |
<EOS> 命中率 | 检查模型是否学会正常终止 |
max_length 截断率 | 发现解码上限过小或模型不收敛 |
| 输出长度分布与长度比 | 发现空输出、话痨和异常重复 |
| 按输入长度分桶的质量 | 发现长输入上的隐性退化 |
BLEU、ROUGE 等自动指标适合比较同一任务上的版本,不等价于用户价值。机器翻译还要抽检专名、数字、否定、格式和长句;真实自回归推理必须和 Teacher Forcing 指标分开展示。
切片评估与校准
总体平均分会掩盖局部失败。至少按以下维度切片:类别、输入长度、语言、设备、用户类型、时间段、置信度和关键业务场景。高风险类别应单独设门槛,不能被多数类平均掉。
分类器还要看置信度是否校准:预测 0.8 的样本是否约有 80% 正确。置信度不可靠时,阈值路由、人工复核和风险分层都会失效。校准方法和评估集应固定在验证流程中,不能用测试集反复调到满意。
鲁棒性评估要覆盖输入噪声、缺失字段、分辨率变化、拼写变化、异常长度和分布外样本。模型在干净测试集上的高分,不代表它能承受线上输入。
版本回归与 badcase 回流
每次模型、数据、词表、预处理、阈值或解码参数变化,都在同一版本回归集上运行。保留“通过样本”和“失败样本”两份记录,失败样本要包含触发条件、影响范围、归因、修复方向和复测结果。
上线后的低置信、用户纠正、人工接管、投诉和异常长度样本进入 badcase 队列。人工确认后再回填训练集、评测集或红队集,不能把未经审核的线上日志直接用于训练。AI 产品的评测闭环见评估与评测。
导出与服务化
导出前后的数值一致性
导出模型前固定 eval(),并用 no_grad() 运行一组覆盖正常、边界、长输入和空输入的样例。对比原框架与导出运行时的:
- 输出 shape、dtype 和类别顺序;
- logits 或概率的最大绝对误差与相对误差;
- 序列的 token、
<EOS>位置和termination_reason; - 端到端任务结果,而不只是第一层输出。
ONNX、torch.export、TorchScript 或专用推理引擎各有算子和动态 shape 限制,具体 API 以当前版本官方文档为准。导出方式不是验收标准,导出前后在真实样例上的行为一致才是验收标准。
预处理是模型的一部分
模型 artifact 不应只有一个权重文件。发布包至少绑定:
- tokenizer、词表和特殊 token;
- 图像 resize、归一化、颜色通道和裁剪配置;
- 数值特征的列顺序、单位和统计量;
- 模型权重、结构配置和后处理阈值;
- 输入 shape、动态维度和版本兼容范围。
RNN 服务还要明确 hidden state 的策略:每个请求重置,或由同一 session 显式传递。默认设计成无状态接口更容易扩容和排查;需要流式状态时,定义 session 过期、重置、重启恢复和跨实例路由。
服务接口和压测
服务化至少处理:输入校验、超时、限流、批推理、健康检查、优雅退出和错误返回。压测报告写清硬件、模型与精度、输入长度分布、batch、并发、冷启动、流式与否,以及 P50/P95/P99 延迟、吞吐、内存和错误率。
不要只报告单条样本的最快耗时。线上容量取决于真实长度分布、并发和尾延迟。对序列模型还要分别统计输入长度、输出长度、正常 EOS 结束率和强制截断率。LLM 的 Prefill/Decode 与服务调度可参考模型推理与部署,这里关注通用模型服务的契约。
MLOps 与上线治理
模型版本是一组资产
可发布的模型版本应能反查:
1 2 3 | |
模型注册表或等价的资产目录要保存状态:实验、候选、灰度、生产、回滚。旧版本不能随发布立即删除,要按回滚窗口保留。
监控不只看服务是否存活
| 监控层 | 关键指标 |
|---|---|
| 服务 | 请求量、错误率、超时率、P50/P95/P99 延迟 |
| 资源 | GPU/CPU 利用率、显存、内存、队列长度、吞吐 |
| 输入 | 长度、缺失值、异常值、预处理失败、分布漂移 |
| 模型 | 置信度、类别分布、<EOS> 命中率、截断率、重复率 |
| 业务 | 任务完成率、采纳率、人工接管、投诉和复核结果 |
| 版本 | 模型、数据、配置和服务实例版本 |
输入分布变化不等于模型一定变差,但它是重新抽样和评估的触发信号。先检查数据管道和业务分布,再决定重训、换模型或回滚。
灰度与回滚
推荐的发布路径是:
1 | |
每一步预先写清放量条件、停止条件、观测窗口、责任人和回滚动作。影子模式只记录预测不产生业务副作用;高风险写操作要先经过人工确认。
回滚不仅是切换权重,还要确认:
- 旧模型的预处理、词表和服务接口仍兼容;
- 线上已产生的事件、标签和缓存如何处理;
- 新旧模型写入的数据是否需要迁移或补偿;
- 回滚后如何保留 trace、事故样本和用户反馈;
- 哪个指标触发自动熔断,谁有权限执行人工回滚。
模型、Prompt、阈值和数据集的变更都应进入同一发布记录。通用研发流程与灰度治理见 开发流程与节奏。
最小交付清单
一个可以交给团队继续维护的深度学习项目,至少包含:
- 任务定义、输入输出契约、验收指标和 SLA
- 数据来源、授权、质量检查、版本快照和切分规则
- 可复现的基线,以及相对于基线的改进证据
- 训练配置、代码 commit、依赖环境和实验记录
- “过拟合一个 batch”与 shape、mask、梯度检查记录
- latest/best checkpoint 和断点恢复验证
- 独立测试集、错误切片、边界样本和 badcase 回归集
- 导出模型与原框架的固定样例一致性报告
- 预处理、词表、模型和后处理的版本绑定
- 服务接口、健康检查、超时、限流和压测报告
- 线上质量、资源、延迟、漂移和版本监控
- 灰度、熔断、回滚责任人与旧版本保留策略
与工程师协作时要问的十个问题
- 输入和输出分别是什么 shape,哪些维度可以变化?
- 训练、评估和推理是否使用同一套预处理?
PAD、BOS、EOS、mask 和无效标签分别如何处理?- 训练指标与真实推理指标是否分开?
- 数据怎样切分,如何证明没有用户、文档或时间泄漏?
- 这个 checkpoint 能否在干净环境中恢复训练或加载服务?
- 模型导出后如何验证 logits、序列终止和任务结果一致?
- 线上如何识别截断、空输出、状态串线和输入漂移?
- 质量、延迟、吞吐、显存和成本的上线门槛分别是什么?
- 出现回归时切回哪一个完整 artifact,数据副作用如何处理?
产品视角:上线前 PM 验收什么
工程清单由研发执行,产品经理验收下面几条:
- 契约:输入输出 shape、缺失值、
PAD/ mask 与训练推理是否同一套预处理,写进发布说明。 - 指标:离线指标与真实推理指标分开;上线门槛写清质量、延迟、吞吐、成本和回滚条件。
- 灰度与回滚:谁按什么信号切流量,5 分钟内回到哪一个完整 artifact。
- 数据副作用:用户数据会不会在失败重试里被写脏;回滚时数据怎么处理。
协作时要问的问题见上一节「与工程师协作时要问的十个问题」。评测方法见 评估与评测。表格模型与 CNN 的上线闭环以本页为准;LLM 服务的 Prefill/Decode、KV Cache 与私有化见 模型推理与部署。
练习
选一个图像分类或机器翻译任务,写一页工程交付单:
- 画出数据 → 训练 → 评估 → 导出 → 服务 → 监控的链路。
- 写出一个 batch 的输入、标签、输出 shape,以及所有 mask 的含义。
- 设计“过拟合一个 batch”、checkpoint 恢复和导出一致性三个验收实验。
- 列出至少五种 badcase,分别指定离线指标、线上信号和回滚条件。
- 说明为什么简单模型、较小模型或预训练模型是你的第一条基线,而不是直接选择最大网络。
来源说明
本文为原创整理,主要参考以下资料与站内内容(引用日期:2026-09-04):
- PyTorch 官方教程与文档:保存和加载模型、数据加载与处理、自动混合精度。具体 API 以当前版本为准。
- ONNX 官方文档:Model Training and Deployment,用于理解跨运行时导出与算子兼容边界。
- Sutskever I, Vinyals O, Le Q V. Sequence to Sequence Learning with Neural Networks,2014:序列模型的 Encoder-Decoder 训练与推理背景。
- 机器学习基础、深度学习基础、评估与评测、模型训练与对齐与模型推理与部署:站内相关概念、评估和 LLM 专题。
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:AI-PM Wiki Team
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用