Mini-Infer (13): 端到端验证 — LeNet-5 实战与 PyTorch 对齐
Mini-Infer (13): 端到端验证 — LeNet-5 实战与 PyTorch 对齐
1. 为什么需要端到端测试?
单元测试(Unit Test)只能保证单个算子(如 Conv2D)在特定输入下是正确的。但当几十个算子串联成一个网络时,微小的误差(如 Padding 处理、NCHW vs NHWC 布局差异、float 精度累积)可能会被放大,导致最终分类错误。
端到端测试的目标:
- 权重加载:验证我们能否正确读取 PyTorch 导出的二进制权重。
- 计算精度:验证
Mini-Infer的输出 logits 与 PyTorch 的差异是否在允许范围内(如1e-5)。 - 流程打通:验证从图片预处理到最终分类的整个链路。
2. 训练与导出:PyTorch 侧准备 (lenet5_model.py & train_lenet5.py)
首先,我们需要一个“标准答案”。我们在 PyTorch 中定义并训练一个经典的 LeNet-5。
关键细节:
- 模型定义:我们严格遵循
Conv -> ReLU -> MaxPool的顺序,这与我们在Mini-Infer中搭建的流程一致。 - 数据预处理:
transforms.Normalize((0.1307,), (0.3081,))。这是 MNIST 的标准归一化。我们在 C++ 推理时必须执行完全相同的预处理,否则结果将毫无意义。
1 | # lenet5_model.py |
训练脚本 (train_lenet5.py) 会保存 lenet5_best.pth。之后,我们需要使用之前编写的 export_lenet5.py(未展示,但逻辑很简单)将 .pth 转换为我们自定义的二进制权重格式。
3. 推理实现:C++ 侧实战 (lenet5_inference.cpp)
这是 Mini-Infer 的“高光时刻”。我们将手动“拼装”出 LeNet-5。
A. 模型组装 (Model Assembly)
不同于 PyTorch 的 nn.Module,在 C++ 中我们需要显式地管理内存和算子。
1 | // lenet5_inference.cpp |
B. 前向传播 (Forward Pass)
因为我们还没有实现 ONNX Parser(这是 B 轨任务),所以目前我们是手动硬编码计算图的执行顺序。这虽然繁琐,但非常有助于理解数据流。
1 | // lenet5_inference.cpp |
C. 结果验证
我们将 C++ 的推理结果保存为 JSON 文件,其中包含每个样本的 Logits、Probabilities 和预测类别。
4. 自动化测试脚本:test_lenet5.sh
为了让验证过程自动化,我们编写了一个 Shell 脚本,它串联了 Python 和 C++:
- 生成基准:运行
generate_reference_outputs.py,用 PyTorch 跑一遍测试集,保存结果。 - 运行推理:运行编译好的
lenet5_inferenceC++ 程序,保存结果。 - 对比:运行
compare_outputs.py,对比两个 JSON 文件。
1 | # test_lenet5.sh |
如果对比脚本报告 [SUCCESS],这意味着 Mini-Infer 在数学上是正确的!
5. 总结与致谢
我们从零开始:
- 设计了 Tensor/Memory 系统。
- 搭建了 KernelRegistry 和 Operator 架构。
- 实现了高性能的 Conv2D (im2col+GEMM) 和 Pooling。
- 实现了图优化 (Fusion)。
- 最后,通过 LeNet-5 实战证明了框架的正确性。
这不仅仅是一个玩具,它是一个微型但完整、现代、高性能的推理引擎雏形。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 James的成长之路!
评论





