PyTorch C++ Frontend 指南:用 LibTorch 定义、训练与序列化神经网络模型
本篇基于 PyTorch 官方 C++ 文档(docs/cpp/source/frontend.md)展开,系统讲解 PyTorch C++ Frontend(LibTorch)的组成结构、端到端 MNIST 训练示例、torch::nn::Module 的设计机制以及模型序列化 API。读完后你将能够:在 C++ 中用与 Python 前端一致的命名约定定义模型、编写训练循环、管理参数与缓冲区,并通过 torch::save/torch::load 完成检查点存取。
一、C++ Frontend 是什么
PyTorch C++ frontend 是一个 C++17 库,面向 CPU 与 GPU 的张量计算,提供自动微分以及构建现代机器学习应用所需的高层构件。官方文档将其定位为"Python 前端的 C++ 版本",它由以下七个核心组件构成(下表完整继承自原文档):
| 组件 | 说明 |
|---|---|
torch::Tensor |
支持自动微分、高效的 CPU/GPU 张量 |
torch::nn |
用于神经网络建模的可组合模块集合 |
torch::optim |
SGD、Adam、RMSprop 等训练模型的优化算法 |
torch::data |
数据集、数据管道与多线程异步数据加载器 |
torch::serialize |
保存和加载模型检查点的序列化 API |
torch::python |
将 C++ 模型绑定到 Python 的胶水代码 |
torch::jit |
纯 C++ 访问 TorchScript JIT 编译器 |
从仓库源码结构看,这些命名空间对应的公共头文件都位于 torch/csrc/api/include/torch/ 下:
- torch/nn.h 与 torch/nn/:模块基类
Module(torch/nn/module.h)、各标准层实现(torch/nn/modules/ 下的linear.h、conv.h、dropout.h、loss.h等)以及功能函数(torch/nn/functional.h); - torch/optim.h:
torch::optim::SGD、Adam、RMSprop等优化器; - torch/data.h:
torch::data::make_data_loader、数据集与Stack<>等变换; - torch/serialize.h:
torch::save/torch::load; - torch/jit.h 与 torch/python.h:JIT 与 Python 绑定入口。
其中 torch/torch.h(torch/csrc/api/include/torch/torch.h)是"一揽子"头文件——官方安装文档明确指出,包含它是获取 C++ API 大部分功能最稳妥的方式;如果你只需要部分功能,也可以只包含更细粒度的头文件。
二、端到端示例:在 MNIST 上定义并训练一个网络
以下是官方文档给出的完整端到端示例:定义一个三层全连接网络,用多线程数据加载器读取 MNIST,以 SGD 训练 10 个 epoch,并定期打印 loss 与保存检查点。
#include <torch/torch.h>
// Define a new Module.
struct Net : torch::nn::Module {
Net() {
// Construct and register two Linear submodules.
fc1 = register_module("fc1", torch::nn::Linear(784, 64));
fc2 = register_module("fc2", torch::nn::Linear(64, 32));
fc3 = register_module("fc3", torch::nn::Linear(32, 10));
}
// Implement the Net's algorithm.
torch::Tensor forward(torch::Tensor x) {
// Use one of many tensor manipulation functions.
x = torch::relu(fc1->forward(x.reshape({x.size(0), 784})));
x = torch::dropout(x, /*p=*/0.5, /*train=*/is_training());
x = torch::relu(fc2->forward(x));
x = torch::log_softmax(fc3->forward(x), /*dim=*/1);
return x;
}
// Use one of many "standard library" modules.
torch::nn::Linear fc1{nullptr}, fc2{nullptr}, fc3{nullptr};
};
int main() {
// Create a new Net.
auto net = std::make_shared<Net>();
// Create a multi-threaded data loader for the MNIST dataset.
auto data_loader = torch::data::make_data_loader(
torch::data::datasets::MNIST("./data").map(
torch::data::transforms::Stack<>()),
/*batch_size=*/64);
// Instantiate an SGD optimization algorithm to update our Net's parameters.
torch::optim::SGD optimizer(net->parameters(), /*lr=*/0.01);
for (size_t epoch = 1; epoch <= 10; ++epoch) {
size_t batch_index = 0;
// Iterate the data loader to yield batches from the dataset.
for (auto& batch : *data_loader) {
// Reset gradients.
optimizer.zero_grad();
// Execute the model on the input data.
torch::Tensor prediction = net->forward(batch.data);
// Compute a loss value to judge the prediction of our model.
torch::Tensor loss = torch::nll_loss(prediction, batch.target);
// Compute gradients of the loss w.r.t. the parameters of our model.
loss.backward();
// Update the parameters based on the calculated gradients.
optimizer.step();
// Output the loss and checkpoint every 100 batches.
if (++batch_index % 100 == 0) {
std::cout << "Epoch: " << epoch << " | Batch: " << batch_index
<< " | Loss: " << loss.item<float>() << std::endl;
// Serialize your model periodically as a checkpoint.
torch::save(net, "net.pt");
}
}
}
}
示例中的几个关键设计点值得展开:
- 模块注册模式:
Net继承torch::nn::Module,在构造函数里用register_module("fc1", ...)把子模块以"名字 + 智能指针"的形式挂到模块树上。成员变量torch::nn::Linear fc1{nullptr}初值为空,真正的对象由register_module返回的shared_ptr持有——这正是 Python 端self.fc1 = nn.Linear(...)的 C++ 对应物。 - 训练态感知:
torch::dropout(x, 0.5, is_training())的第三个参数读取模块当前是否处于训练模式(train()/eval()切换),与 Python 端F.dropout(x, 0.5, self.training)行为一致。 - 数据管道:
torch::data::datasets::MNIST("./data")构造数据集,.map(torch::data::transforms::Stack<>())把逐样本张量堆叠成批次,make_data_loader(..., 64)生成 batch_size 为 64 的多线程加载器。 - 标准训练循环:
zero_grad() → forward → nll_loss → loss.backward() → step()五步曲,与 Python 前端的语义一一对应。 - 检查点:
torch::save(net, "net.pt")把整个模块树(含参数)序列化到磁盘。
源码印证:Module 的三类持久化状态
阅读 torch/csrc/api/include/torch/nn/module.h 的类注释可以看到,Module 与 torch::nn::Module(Python 版)在设计上刻意保持一致,并明确区分了三类持久化数据:
- Parameters:记录梯度的张量,典型如
Linear的weight,由register_parameter注册; - Buffers:不记录梯度、通常在 forward 中更新的张量,典型如
BatchNorm的运行均值/方差,由register_buffer单独注册; - 其他附加状态:模块实现或配置所需的非张量数据。
前两类注册进 Module 系统后可批量访问:parameters() 可迭代整棵子树的所有参数;Module::to() 可一次性改设备或 dtype(如 module->to(torch::kCUDA));clone() 则对整个可克隆模块层次做深拷贝。Module 还通过 enable_shared_from_this 支持 shared_ptr 语义,name() 在禁用 RTTI 时也可由构造函数手工指定——这些细节解释了上面示例中 std::make_shared<Net>() 与 fc1{nullptr} 初始化方式背后的原因。
源码印证:torch::save / torch::load 的实现
示例中的 torch::save(net, "net.pt") 对应 torch/csrc/api/include/torch/serialize.h。从实现看,torch::save 是一个模板函数:内部构造 serialize::OutputArchive(附带一个 jit::CompilationUnit),通过 operator<< 写出值后再调用 save_to(...),因此除文件名外还可以直接传 ostream;torch::load 则对称地构造 InputArchive 并 load_from(...) 后用 operator>> 读回。该头文件的文档注释明确列出了当前支持的类型:torch::nn::Module、torch::optim::Optimizer、torch::Tensor 的子类,另外还额外提供了 std::vector<torch::Tensor> 的 save/load 重载以及 pickle_save/pickle_load 用于 IValue 级别的操作。一个值得注意的限制(同样来自该头文件的注释):同一个 stream 不能在多次 torch::save(...) 调用中复用,否则会损坏文件头。
三、设计哲学:为什么还要一个 C++ 前端
原文档的 Philosophy 一节给出了非常明确的定位,值得完整保留:
PyTorch 的 Python 前端很优秀,能用 Python 时应该优先用 Python。但在一些场景下,性能与可移植性要求使得引入 Python 解释器不可行——例如低延迟、高性能或多线程环境(视频游戏、生产服务器)。C++ 前端的目标就是覆盖这些场景,同时不牺牲 Python 前端的用户体验。
由此衍生出两条设计原则:
- 在结构、命名、约定和功能上紧密对齐 Python 前端。虽然偶有差异(例如砍掉了已弃用的功能、修复了 Python 端的"毛刺"),但官方保证:把一个 Python 模型移植到 C++,工作量应该只体现在翻译语言特性上,而不是修改功能或行为。
- 优先考虑灵活性与易用性,而非微观优化。C++ 常常能写出极致性能的代码,但代价是糟糕的用户体验;PyTorch 的核心是灵活与动态,C++ 前端在某些场景甚至"藏起"性能旋钮以保持 API 简单,目标是让不写 C++ 为生的研究者也能上手。
文档同时给出一句重要的告诫:Python 未必比 C++ 慢。Python 前端对几乎一切计算密集型操作(尤其是数值运算)都会下沉到 C++ 调用,这些操作占了程序运行时间的大头。因此,如果你"能用 Python 就尽量用 Python";只有当你想写 C++,或因多线程、延迟、部署要求必须写 C++ 时,C++ 前端才发挥价值。两个前端服务于不同用例、相互配合,谁也不无条件取代谁。
四、安装与构建:依赖 LibTorch 的最小工程
C++ 前端的二进制发行版称为 LibTorch,包含依赖 PyTorch 所需的全部头文件、库与 CMake 配置。完整安装说明见仓库内的 docs/cpp/source/installing.md,此处给出可复制的最小工程骨架。
第一步,下载 LibTorch ZIP 并解压(CPU 版示例,GPU 版需在官方发布页选择对应包):
wget https://download.pytorch.org/libtorch/nightly/cpu/libtorch-shared-with-deps-latest.zip
unzip libtorch-shared-with-deps-latest.zip
第二步,编写最小 CMakeLists.txt(CMake 不是硬性要求,但官方推荐且会长期维护支持):
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
project(example-app)
find_package(Torch REQUIRED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}")
add_executable(example-app example-app.cpp)
target_link_libraries(example-app "${TORCH_LIBRARIES}")
set_property(TARGET example-app PROPERTY CXX_STANDARD 20)
# The following code block is suggested to be used on Windows.
# According to https://github.com/pytorch/pytorch/issues/25457,
# the DLLs need to be copied to avoid memory errors.
if (MSVC)
file(GLOB TORCH_DLLS "${TORCH_INSTALL_PREFIX}/lib/*.dll")
add_custom_command(TARGET example-app
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${TORCH_DLLS}
$<TARGET_FILE_DIR:example-app>)
endif (MSVC)
第三步,编写入口(example-app.cpp):
#include <torch/torch.h>
#include <iostream>
int main() {
torch::Tensor tensor = torch::rand({2, 3});
std::cout << tensor << std::endl;
}
第四步,在 example-app/ 目录下构建:
mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=/absolute/path/to/libtorch ..
cmake --build . --config Release
其中 CMAKE_PREFIX_PATH 必须指向绝对路径的 LibTorch 解压目录。如果 PyTorch 是 pip 安装的,可直接查询前缀路径:
cmake -DCMAKE_PREFIX_PATH=`python3 -c 'import torch;print(torch.utils.cmake_prefix_path)'` ..
运行 build/example-app,应能打印一个 2×3 的随机张量。
系统要求(来自 installing.md):
- cxx11 ABI 版本需要 GLIBC ≥ 2.29;
- GCC ≥ 9(cxx11 ABI)。
Windows 注意事项:Windows 上 debug 与 release 构建 ABI 不兼容,计划用 debug 模式构建项目时请改用 debug 版 LibTorch,并在 cmake --build . 行指定正确的 configuration。
五、延伸入口
- 更多完整的 C++ 前端用法示例,可参考 PyTorch 官方 examples 仓库的
cpp目录(原文档中的外链,此处不再重复给出网址)。 - 仓库内与 C++ API 相关的进一步阅读路径:
- C++ API 顶层头文件:torch/csrc/api/include/torch/torch.h、torch/csrc/api/include/torch/all.h
- 模块基类与注册机制:torch/csrc/api/include/torch/nn/module.h
- 优化器接口:torch/csrc/api/include/torch/optim.h
- 数据加载:torch/csrc/api/include/torch/data.h
- 序列化:torch/csrc/api/include/torch/serialize.h
- 安装与构建文档:docs/cpp/source/installing.md
适用前提小结:本文所有 API 形态(register_module 返回值、make_data_loader 模板参数、torch::save 的流式重载等)均以当前仓库 torch/csrc/api/include/torch/ 中的头文件为准;不同版本的 LibTorch 发行版在个别签名上可能略有差异,落地前建议以你所下载版本的头文件注释为最终依据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00