跳转至

深度学习工程实践

深度学习工程实践

学会网络结构和损失函数,只说明你能读懂论文。工程交付还要回答:数据从哪里来,训练是否可复现,模型怎么证明有效,导出后是否仍然一致,线上出错时如何定位和回滚。

本页以 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、类别编号、无效标签类别编号在训练和服务中含义不同
变长字段lengthsPAD_IDBOS_IDEOS_IDpadding 被当成真实数据或标签
输出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_reasoneosmax_lengthcancellederror。分类服务至少保留模型版本、预处理版本和置信度,不能只返回一个类别字符串。

训练、评估、推理的接口一致

训练代码、离线评估脚本和线上服务应复用同一套预处理与后处理逻辑。可以用不同的 batch size 和设备,但不能悄悄改变:

  • tokenization、词表和特殊 token 的编号;
  • 图像 resize、裁剪、归一化和颜色通道顺序;
  • 缺失值填充、特征顺序和单位换算;
  • 序列的 padding、mask、截断和 <EOS> 处理;
  • 置信度阈值、标签映射和后处理规则。

只要输入契约发生变化,就算模型权重没有变化,也应当当作一次需要评估的版本变更。

数据管线与 batch

数据切分先于模型选择

训练集用于学习参数,验证集用于选模型和调超参数,测试集只用于最终验收。切分方式要匹配真实上线场景:

数据特征推荐切分防止的问题
独立同分布样本分层随机切分类别比例在集合间严重失衡
同一用户、设备或商品有多条记录按实体分组切分同源样本泄漏,让测试集看起来过于简单
时间序列或预测未来按时间切分用未来数据预测过去,评估虚高
同一文档切成多个片段按文档切分相邻片段跨集合,模型实际上见过答案
数据持续到达固定历史窗口 + 时间留出无法模拟线上新分布

所有切分都要保存随机种子、规则和集合快照。不要每次运行都重新随机切分,否则两个实验的分数可能来自不同测试样本,无法比较。

数据质量检查

在训练前把数据检查做成脚本或流水线步骤,而不是靠人工打开几条样本:

  • schema、dtype、shape 和必填字段是否正确;
  • 空样本、异常值、重复样本和损坏文件的比例;
  • 标签是否在合法范围,类别分布是否异常;
  • 用户、文档、设备和时间是否跨集合重复;
  • 图像通道、尺寸、色彩空间,文本编码和 token 长度是否符合约定;
  • 训练集、评估集与线上回流数据的授权和用途是否一致。

预处理统计量只能在训练集上拟合。例如标准化的均值和方差、词表、归一化范围,都不能先用全量数据计算再切分。否则验证集信息已经泄漏进训练流程。

数据集快照至少记录版本号或 hash、来源、清洗规则、标注指南、切分规则、许可证和生成时间。数据版本与模型版本的关系见数据与标注

DatasetDataLoadercollate_fn

固定长度数据可以直接堆叠。变长数据需要 collate_fn 在组成 batch 时完成 padding,并返回真实长度。真实项目中还应处理排序、反排序和设备迁移:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# PyTorch 风格示意代码:从原始序列计算长度,再 padding

def collate_fn(batch):
    src_items, tgt_items = zip(*batch)
    src_lengths = torch.tensor([item.size(0) for item in src_items])
    tgt_lengths = torch.tensor([item.size(0) for item in tgt_items])

    src = pad_sequence(src_items, batch_first=True, padding_value=PAD_ID)
    tgt = pad_sequence(tgt_items, batch_first=True, padding_value=PAD_ID)
    return src, src_lengths, tgt, tgt_lengths

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
# PyTorch 风格示意代码:省略数据搬运、AMP 和日志细节
model.train()
for batch in train_loader:
    optimizer.zero_grad(set_to_none=True)

    prediction = model(batch.inputs)
    loss = criterion(prediction, batch.labels)
    loss.backward()

    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
    optimizer.step()

model.eval()
with torch.no_grad():
    for batch in valid_loader:
        prediction = model(batch.inputs)
        valid_loss = criterion(prediction, batch.labels)
  • 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
# 示意代码:tgt 包含 BOS、目标 token、EOS 和 PAD
decoder_in = tgt[:, :-1]
gold = tgt[:, 1:]
logits = model(src, decoder_in)  # [B, T-1, V]
loss = F.cross_entropy(
    logits.reshape(-1, vocab_size),
    gold.reshape(-1),
    ignore_index=PAD_ID,
)

上面的缩进只为突出伪代码中的数据流;正式实现要统一代码格式,并在单元测试中验证 decoder_ingold 没有错位。序列任务还要把 Teacher Forcing 下的 loss 与真实自回归解码指标分开记录,具体机制见深度学习基础

先过拟合一个 batch

第一次训练不要立刻跑完整数据集。固定一个很小的 batch,关闭或减弱随机增强,观察模型是否可以把它过拟合到接近零的训练误差。这个实验用于验证:

  1. 输入和标签没有错位;
  2. loss 连接到了正确的输出;
  3. padding 和 mask 没有屏蔽真实信号;
  4. 参数确实收到梯度并更新;
  5. 输出类别映射和后处理没有写反。

如果一个 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 元数据,并在干净环境中验证加载;模型文件、词表、预处理和配置要作为一个可定位的发布包管理。

调试与故障排查

推荐排查顺序

训练或推理异常时按以下顺序缩小范围:

  1. 固定一条样本和一个 batch,记录原始输入、标签、输出和版本。
  2. 检查输入、标签、logits、mask 的 shape、dtype、device。
  3. 检查标签范围、类别映射、BOS/EOS/PAD 是否错位。
  4. 检查 loss 是否为 NaNInf,再检查梯度范数。
  5. 让模型过拟合一个 batch,排除数据和实现错误。
  6. 对比 train()eval()、Teacher Forcing 和真实自回归结果。
  7. 检查 tokenizer、预处理、词表和模型 artifact 是否成套。
  8. 最后再调整学习率、模型规模、batch size 和正则化。

故障对照表

症状优先检查常见修复
loss 完全不下降标签、loss 连接、学习率、输入范围打印一条样本,过拟合一个 batch
loss 剧烈震荡学习率、有效 batch、异常样本降低学习率、梯度裁剪、定位异常 batch
出现 NaN/InfFP16 溢出、梯度爆炸、非法输入切换 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
模型权重 + 模型结构 + 代码 commit
+ 数据集版本 + 预处理/词表版本
+ 训练配置 + 评测结果 + 依赖环境

模型注册表或等价的资产目录要保存状态:实验、候选、灰度、生产、回滚。旧版本不能随发布立即删除,要按回滚窗口保留。

监控不只看服务是否存活

监控层关键指标
服务请求量、错误率、超时率、P50/P95/P99 延迟
资源GPU/CPU 利用率、显存、内存、队列长度、吞吐
输入长度、缺失值、异常值、预处理失败、分布漂移
模型置信度、类别分布、<EOS> 命中率、截断率、重复率
业务任务完成率、采纳率、人工接管、投诉和复核结果
版本模型、数据、配置和服务实例版本

输入分布变化不等于模型一定变差,但它是重新抽样和评估的触发信号。先检查数据管道和业务分布,再决定重训、换模型或回滚。

灰度与回滚

推荐的发布路径是:

1
离线回归 → 影子模式 → 内部用户 → 小比例灰度 → 扩大流量 → 全量

每一步预先写清放量条件、停止条件、观测窗口、责任人和回滚动作。影子模式只记录预测不产生业务副作用;高风险写操作要先经过人工确认。

回滚不仅是切换权重,还要确认:

  • 旧模型的预处理、词表和服务接口仍兼容;
  • 线上已产生的事件、标签和缓存如何处理;
  • 新旧模型写入的数据是否需要迁移或补偿;
  • 回滚后如何保留 trace、事故样本和用户反馈;
  • 哪个指标触发自动熔断,谁有权限执行人工回滚。

模型、Prompt、阈值和数据集的变更都应进入同一发布记录。通用研发流程与灰度治理见 开发流程与节奏

最小交付清单

一个可以交给团队继续维护的深度学习项目,至少包含:

  • 任务定义、输入输出契约、验收指标和 SLA
  • 数据来源、授权、质量检查、版本快照和切分规则
  • 可复现的基线,以及相对于基线的改进证据
  • 训练配置、代码 commit、依赖环境和实验记录
  • “过拟合一个 batch”与 shape、mask、梯度检查记录
  • latest/best checkpoint 和断点恢复验证
  • 独立测试集、错误切片、边界样本和 badcase 回归集
  • 导出模型与原框架的固定样例一致性报告
  • 预处理、词表、模型和后处理的版本绑定
  • 服务接口、健康检查、超时、限流和压测报告
  • 线上质量、资源、延迟、漂移和版本监控
  • 灰度、熔断、回滚责任人与旧版本保留策略

与工程师协作时要问的十个问题

  1. 输入和输出分别是什么 shape,哪些维度可以变化?
  2. 训练、评估和推理是否使用同一套预处理?
  3. PADBOSEOS、mask 和无效标签分别如何处理?
  4. 训练指标与真实推理指标是否分开?
  5. 数据怎样切分,如何证明没有用户、文档或时间泄漏?
  6. 这个 checkpoint 能否在干净环境中恢复训练或加载服务?
  7. 模型导出后如何验证 logits、序列终止和任务结果一致?
  8. 线上如何识别截断、空输出、状态串线和输入漂移?
  9. 质量、延迟、吞吐、显存和成本的上线门槛分别是什么?
  10. 出现回归时切回哪一个完整 artifact,数据副作用如何处理?

产品视角:上线前 PM 验收什么

工程清单由研发执行,产品经理验收下面几条:

  • 契约:输入输出 shape、缺失值、PAD / mask 与训练推理是否同一套预处理,写进发布说明。
  • 指标:离线指标与真实推理指标分开;上线门槛写清质量、延迟、吞吐、成本和回滚条件。
  • 灰度与回滚:谁按什么信号切流量,5 分钟内回到哪一个完整 artifact。
  • 数据副作用:用户数据会不会在失败重试里被写脏;回滚时数据怎么处理。

协作时要问的问题见上一节「与工程师协作时要问的十个问题」。评测方法见 评估与评测。表格模型与 CNN 的上线闭环以本页为准;LLM 服务的 Prefill/Decode、KV Cache 与私有化见 模型推理与部署

练习

选一个图像分类或机器翻译任务,写一页工程交付单:

  1. 画出数据 → 训练 → 评估 → 导出 → 服务 → 监控的链路。
  2. 写出一个 batch 的输入、标签、输出 shape,以及所有 mask 的含义。
  3. 设计“过拟合一个 batch”、checkpoint 恢复和导出一致性三个验收实验。
  4. 列出至少五种 badcase,分别指定离线指标、线上信号和回滚条件。
  5. 说明为什么简单模型、较小模型或预训练模型是你的第一条基线,而不是直接选择最大网络。

来源说明

本文为原创整理,主要参考以下资料与站内内容(引用日期:2026-09-04):