> **一段话看懂本文:** 把模型迁移到海光 DCU 后,控制台没有报错、接口也返回了文字,并不等于迁移正确。本文给出一套可以直接照着执行的四层检查法:先确认任务真的跑在目标设备上,再比较高风险算子、模型中间层和业务指标,最后把结果整理成可复现的证据包。
模型成功输出一句“你好”,你会认为迁移完成了吗?
这是国产算力适配中最容易被忽略、也最难在演示现场发现的问题:**程序可能跑得很顺,结果却在悄悄变错。** 比直接报错更麻烦的是,这类“静默错误”可能直到模型上线、长文本输入或低精度优化后才暴露。
如果你正在做 PyTorch 模型迁移、算子适配或大模型推理,建议先收藏文末的七项验收清单。它能避免把一次“成功启动”误判为“迁移完成”。
## 一、最危险的迁移失败,往往不会报错
软件迁移有两种失败。第一种很醒目:缺少动态库、算子不支持、设备不可见,程序直接退出。第二种更隐蔽:程序正常运行,却因为精度转换、算子替代、张量布局或非确定性执行,让输出发生了不易察觉的变化。
对分类模型,这种变化可能只是置信度从 0.81 变成 0.78;对检测模型,可能表现为边界框轻微偏移;对大语言模型,最终生成文本本来就具有随机性,仅比较两段文字是否相同,几乎无法判断迁移是否正确。
所以,正确的问题不是“DCU 能不能跑这个模型”,而是:**同一输入经过迁移前后的计算路径,在哪一层开始出现了超出容忍范围的偏差?**
这需要一条由浅入深的证据链。
## 二、第一层:先证明程序确实运行在目标环境
验证结果之前,先固定环境身份。至少应记录以下信息:
- DCU 型号与可见设备编号;
- 操作系统、内核和容器镜像标识;
- 驱动、DTK/DAS、Python、框架版本;
- 模型提交版本或权重文件校验值;
- 实际启用的数据类型、量化方式和推理参数。
在兼容 PyTorch 的环境里,可以用一个短脚本保存框架能看到的设备状态:
```python
import json, platform, torch
info = {
"python": platform.python_version(),
"torch": torch.__version__,
"device_available": torch.cuda.is_available(),
"device_count": torch.cuda.device_count(),
"device_names": [
torch.cuda.get_device_name(i)
for i in range(torch.cuda.device_count())
],
}
print(json.dumps(info, ensure_ascii=False, indent=2))
```
这里的 `torch.cuda` 是 PyTorch 为兼容加速后端保留的接口名,并不表示底层一定是英伟达硬件。真正需要保存的是脚本输出、设备管理工具输出以及镜像摘要。只截一张“程序成功启动”的图片,无法说明任务运行在哪张卡、用了什么软件栈。
## 三、第二层:先对算子,再谈整网
端到端结果异常时,直接在几百层网络里搜索问题成本很高。更有效的方法是先挑出高风险算子做最小验证,例如:
- softmax、layer normalization、RMS normalization;
- 矩阵乘、卷积和 attention;
- top-k、采样、排序等对边界值敏感的算子;
- 自定义 CUDA/HIP 扩展;
- 量化、反量化与类型转换算子。
同一批输入分别在参考后端和 DCU 后端执行,保存输出后比较绝对误差和相对误差:
```python
import torch
def compare(ref, out, atol=1e-4, rtol=1e-3):
ref = ref.float().cpu()
out = out.float().cpu()
diff = (ref - out).abs()
rel = diff / ref.abs().clamp_min(1e-8)
return {
"allclose": torch.allclose(ref, out, atol=atol, rtol=rtol),
"max_abs": diff.max().item(),
"mean_abs": diff.mean().item(),
"p99_rel": torch.quantile(rel.flatten(), 0.99).item(),
"nan_count": torch.isnan(out).sum().item(),
"inf_count": torch.isinf(out).sum().item(),
}
```
为什么不能只看 `max_abs`?因为一个极端值可能放大最大误差,却不影响大部分元素;反过来,大量小偏差持续累积,也可能在模型末端造成明显变化。`allclose`、均值、分位数、NaN/Inf 数量应该一起看。
容差也不能照抄。FP32、FP16、BF16 和 INT8 的有效精度不同,归一化与指数运算又比简单加法更敏感。合理做法是先用参考环境的重复运行得到“自然波动带”,再为不同算子制定阈值。
## 四、第三层:用中间层定位第一处漂移
单算子全部通过,整网仍可能异常。这时最有价值的不是最终输出,而是“第一处发生显著偏差的中间张量”。
PyTorch 可以用 forward hook 采集关键模块输出摘要:
```python
summaries = {}
def make_hook(name):
def hook(module, inputs, output):
x = output[0] if isinstance(output, tuple) else output
x = x.detach().float().cpu()
summaries[name] = {
"shape": list(x.shape),
"mean": x.mean().item(),
"std": x.std().item(),
"min": x.min().item(),
"max": x.max().item(),
"finite": torch.isfinite(x).float().mean().item(),
}
return hook
for name, module in model.named_modules():
if name.endswith(("self_attn", "mlp", "norm")):
module.register_forward_hook(make_hook(name))
```
模型很大时,不必保存全部张量。先保存形状、均值、标准差、极值和有限值比例;发现异常区间后,再对少数层保存完整张量。定位过程可以采用二分法:先比较每 8 层的摘要,再缩小到某一层,最后落到该层内部的具体算子。
这一层尤其能发现三类问题:
1. 张量形状或布局虽然可执行,但语义不一致;
2. 某个低精度算子的误差逐层放大;
3. attention mask、位置编码或 KV Cache 更新出现边界错误。
## 五、第四层:业务正确性要用任务指标,不要凭肉眼
生成式模型的逐字一致不是合理目标。固定随机种子、关闭采样虽然能减少干扰,但不同后端仍可能因为并行归约顺序而产生微小差异,微小差异又可能改变后续 token。
更稳妥的验证方式是分层比较:
- 首 token 前的 logits:比较最大绝对误差、余弦相似度和 top-k 重合率;
- 固定数据集上的任务指标:分类准确率、检测 mAP、语音 WER、检索召回率等;
- LLM 的任务集结果:正确率、格式遵循率、拒答率、引用命中率;
- 关键业务样本:边界输入、长文本、空输入、多轮对话和异常字符。
例如,比较 top-k 集合比比较最终文案更接近模型本身:
```python
def topk_overlap(a, b, k=20):
ia = set(a.topk(k).indices.tolist())
ib = set(b.topk(k).indices.tolist())
return len(ia & ib) / k
```
如果 logits 高度一致而生成文本不同,问题可能只是采样随机性;如果从某一中间层开始快速漂移,就应回到算子级排查,而不是反复修改提示词。
## 六、性能测试必须排在正确性之后
为了提高吞吐量而开启混合精度、算子融合或量化,会改变数值路径。一个常见误区是看到速度提升就宣布优化成功,却没有重新跑正确性基线。
建议把每次优化都当作一个实验单元:
```text
环境快照
├─ 固定输入与预处理配置
├─ 参考输出或参考指标
├─ DCU 输出与误差报告
├─ 性能数据(预热、重复次数、分位数)
└─ 结论与已知限制
```
通过正确性门槛后,性能数据才有比较意义。否则“快了 30%”可能只是少算了一部分、精度被意外降低,或者输入形状已经改变。
## 七、一份可复用的验收清单
迁移验收可以压缩为七个问题:
1. 能否证明任务运行在目标 DCU 和指定软件栈上?
2. 输入、权重、预处理和随机种子是否完全固定?
3. 高风险算子是否有独立的误差报告?
4. 是否找到了迁移前后第一处显著漂移的中间层?
5. 业务指标是否落在预先定义的容忍区间内?
6. 每次性能优化后是否重新执行正确性回归?
7. 结论是否附带环境、日志和已知限制,能被别人复现?
## 结语
国产算力迁移的质量,不应由一张成功运行的截图证明。真正可靠的迁移,是把“看起来没问题”拆成环境、算子、中间层和业务指标四层证据,再把每个结论固定到可复现的输入与版本上。
当验证链建立起来后,DCU 适配就不再是一场靠经验猜测的排错:结果不一致时能定位,性能变化时能解释,软件栈升级后也能自动回归。这套能力,往往比某一次“成功跑通”更有长期价值。
**讨论题:** 你在模型迁移中遇到过“没有报错,但结果明显不对”的情况吗?最早是在哪一层发现的?欢迎把现象留在评论区,后续可以继续整理成一份 DCU 静默错误排查案例库。
## 参考资料
- 光合开发者社区文档中心:DCU 编程、调试和优化相关文档,https://developer.sourcefind.cn/document
- 光合开发者社区 DAS:API 兼容性列表与 PyTorch LayerCheck 使用资料,https://developer.sourcefind.cn/document
- PyTorch 官方文档:Numerical Accuracy,https://docs.pytorch.org/docs/stable/notes/numerical_accuracy.html