跳转至

深度学习工程实践

深度学习工程实践

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

本页以 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_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_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
# 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_in 与 gold 没有错位。序列任务还要把 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 是否为 NaN 或 Inf,再检查梯度范数。
  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. PAD、BOS、EOS、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):