1. 为什么绕不开VS2026和LibTorch(CPU版)的这个组合
先说一个我实际遇到的场景。有个客户要做一个离线运行的文档解析工具,输入是一批PDF和扫描图片,输出是结构化文本。模型我们用PyTorch训练的,效果没问题,但到了交付环节就卡住了:客户的办公电脑不允许安装Python解释器,更不可能装Anaconda。最初方案是用PyInstaller打包了一个exe,结果打出来的目录300多MB,启动还要等好几秒,偶尔还会被杀毒软件拦截。最后我决定把推理部分整个换成C++实现,直接用LibTorch来加载PyTorch导出的模型——这就是VS2026 + LibTorch(CPU版)这套环境的来历。
先说结论:这套组合解决的核心问题就是"没有Python环境也能跑PyTorch模型"。LibTorch是PyTorch的C++发行版,张量运算、自动求导、TorchScript推理这些能力都有,但不需要Python解释器。而CPU版指的是预编译库不包含CUDA相关组件,对显卡没要求,在任何一台x64 Windows电脑上都能跑,体积也比GPU版小不少,特别适合做客户端工具、边缘设备推理、或者纯CPU服务器的服务端部署。
如果你属于下面这几类人,这篇环境搭建记录应该能帮你少走弯路:
- 想把PyTorch模型集成到C++/C#桌面程序里的Windows开发者;
- 需要给客户交付"免Python环境"推理程序的工程师;
- 做深度学习推理部署,但手头机器没有NVIDIA显卡,只能依赖CPU;
- 想搞懂LibTorch的include、lib、dll到底怎么配置的初学者。
这篇文章不是官方文档的翻译,而是我从下载包、建工程、配属性、写验证代码,到踩了各种链接错误、运行时DLL缺失这些坑之后整理出来的完整链路,包括每一步为什么要这么做的原理,以及排查思路。
2. 版本匹配是第一道门槛:VS组件和LibTorch包对上了才不折腾
2.1 VS2026安装时真正需要勾选的东西
VS2026在安装的时候,默认的Workloads界面有一堆选项,如果只是写纯C++业务代码,理论上勾一个"使用C++的桌面开发"就够了。但LibTorch这件事上,有几个点特别容易被忽略。
第一,C++工具集要选最新版MSVC。LibTorch官方预编译包是基于MSVC编译的,如果你用较旧的工具链版本,可能遇到std库头文件不兼容这类问题。VS2026安装器里有一个"单个组件"选项卡,建议确认一下MSVC v143_x64(或更新的)和Windows 11 SDK这两个组件被勾选上。第二,建议把"C++ CMake tools for Windows"也勾上,虽然我们用VS的"新建项目"向导也能手工配LibTorch,但后面如果你打算用CMake管理项目,这个组件省去很多麻烦。
安装完VS2026后,还有一个很多人会忽略的步骤:确认编译器是x64版本。LibTorch官方只提供64位预编译包,没有32位版。在VS菜单栏"工具→命令行→开发者命令提示符"里输入cl能看到编译器版本,如果是x86版,后续链接阶段会冒出一堆"无法解析的外部符号"错误,原因就在这里。
2.2 LibTorch包到底应该下载哪个
下载入口是pytorch.org首页的Get Started。进去之后有几个下拉选项:
- PyTorch Build选Stable(稳定版);
- Package选LibTorch;
- Language选C++/Java;
- Compute Platform选CPU。
这里注意,官方下载页上的是libtorch-win-shared-with-deps-版本号+cpu.zip这样一个包。shared表示动态链接版本,里面的torch、torch_cpu这些功能以dll存在,后面运行程序时需要这些dll在场;with-deps表示把依赖的三方库(比如protobuf、asmjit、dnnl)一起打包了,不用额外再找依赖。文件名里带有cpu的才是CPU版,不带cpu的通常默认包含CUDA组件,体积大一倍不止,而且对没有N卡的环境并没有收益。
还有Release和Debug两个选择。官方推荐生产环境用Release包。我在实际搭建过程中,发现Debug包很少被用到,因为如果VS工程是Debug模式而链接的是Release版LibTorch库,会报LNK2038(运行库不匹配),反过来也一样。为了避免折腾,最省心的策略是:VS工程统一用Release模式,LibTorch也统一用Release包。这点后面踩坑部分再细说。
2.3 解压之后目录结构里藏着关键信息
下载下来的是一个zip包,解压后目录结构大致是这样:
D:\libtorch\ ├── bin\ # 运行所需的dll ├── include\ # 头文件 ├── lib\ # 导入库 .lib 和 CMake 配置 └── share\ # CMake 查找包所需文件include目录里藏着一个容易被忽略的二级路径:include\torch\csrc\api\include。刚接触LibTorch的人,在VS里配置附加包含目录时只填了D:\libtorch\include,结果#include <torch/torch.h>依然报"找不到文件"。原因就是torch/torch.h这个头文件实际放在include\torch\csrc\api\include\torch\torch.h那里,需要把D:\libtorch\include\torch\csrc\api\include也加进包含路径,编译器才能逐层找到它。
lib目录下的所有.lib文件都对应了一个动态库。官方推荐的做法是把整个lib目录加入链接器路径,然后在附加依赖项里把所有.lib写进去。我一开始图省事只写了torch.lib和torch_cpu.lib,编译链接时直接报了一大堆LNK2019 无法解析的外部符号,最后乖乖把目录下所有lib全部加进去了。这不是玄学,是LibTorch内部模块化之后,彼此之间有大量符号引用,缺一个都不行。
3. 项目工程配置的完整链路:从包含目录到dll路径
3.1 新建项目时先把平台切到x64
打开VS2026,新建C++控制台应用项目。项目创建完之后,第一件事就是把解决方案平台从默认的x86改成x64。这一步漏掉,后面所有配置都白搭——LibTorch的.lib是x64的,你用x86平台去链接,一分钟内会看到几百行"无法解析的外部符号"相关错误,非常劝退。
切换位置在VS工具栏的"解决方案平台"下拉框,如果没看到x64选项,通过"配置管理器→活动解决方案平台→新建→x64"添加。这一步建议在配置LibTorch之前就做掉,否则后面改了项目属性,一切换平台可能又需要重新设置。
3.2 附加包含目录填两个路径,少一个都不行
项目右键→属性→C/C++→常规→附加包含目录,添加:
D:\libtorch\include D:\libtorch\include\torch\csrc\api\include为什么需要第一个路径?因为LibTorch内部很多头文件之间是相对引用的,比如ATen/ATen.h、c10/util/ArrayRef.h这些,它们都以include作为根目录来组织。为什么不只填第二个路径?因为第二个路径是torch C++前端API所在的位置,torch/torch.h在这里没错,但它内部还会includeATen、c10、torch/csrc等路径下的头文件,那些头文件在第一个路径下才能被找到。
为了验证配置是否生效,可以临时在main函数里写一行:
#include <torch/torch.h> #include <iostream>如果C++项目属性里这两个路径都填了,编译时这段代码能通过,至少说明头文件搜索链路是通的。
3.3 附加库目录、附加依赖项,以及一个偷懒但安全的方法
继续在项目属性里配置:
- 链接器→常规→附加库目录:
D:\libtorch\lib - 链接器→输入→附加依赖项:把
D:\libtorch\lib目录下所有.lib文件名都写进去,用分号隔开。
如果不想一个个敲,可以在lib目录下按住Shift右键打开PowerShell,执行:
Get-ChildItem -Name *.lib | ForEach-Object { $_ -join ';' }把输出复制到附加依赖项里即可。这个操作看起来很笨,但实测是最稳的。LibTorch的lib目录下三四十个.lib文件不是摆设,它们之间互相依赖,手写精简列表很容易漏掉某些间接依赖。
还有一个细节:在链接器→命令行里其实可以通过-LIBPATH加上通配符实现类似效果,但VS的图形界面不支持.lib通配符,所以老老实实全量粘贴是最不容易出错的。
3.4 运行时dll问题:为什么项目编译通过却一运行就报错
项目编译通过只是第一步。运行的时候,如果VS提示:
由于找不到 torch.dll,无法继续执行代码。这说明程序运行时找不到LibTorch的动态库。解决方案有两种。
第一种:把D:\libtorch\bin目录加入系统环境变量PATH,然后重启VS2026,让VS进程能拿到新的PATH。注意是重启VS,不是重启电脑,VS的进程环境变量在启动时读取,不重启它拿不到最新的值。
第二种:把bin目录下的所有dll文件复制到exe输出目录,也就是与生成的.exe同一个文件夹。这种方法最适合后面分发程序,因为部署时根本不可能每台机器都配一次PATH。
我的建议是,开发阶段用第一种,方便调试;准备交付时用第二种,把dll和exe放一起。LibTorch运行需要的dll包括torch.dll、torch_cpu.dll、c10.dll、asmjit.dll、fbgemm.dll、dnnl.dll等,直接复制整个bin目录下所有dll即可,不用刻意挑。
3.5 C++语言标准和字符集也顺手确认一下
LibTorch 2.x系列需要C++17标准支持。在项目属性→C/C++→语言→C++语言标准里,选择ISO C++17 标准 (/std:c++17)或更高版本。如果默认是C++14,编译torch头文件时会出现各种奇怪的模板报错,场面很混乱。字符集建议使用Unicode字符集,LibTorch内部路径处理对宽字符更友好,这个不是必须,但能减少一些文件路径相关的潜在问题。
4. 第一个验证示例:让LibTorch真正跑起来才算搭完
4.1 一段能验证环境完整性的最小代码
环境配置完,总得写点代码验证一下。下面是最小但覆盖面足够的验证程序,能确认头文件、链接库、运行时dll三条链路全部畅通:
#include <torch/torch.h> #include <torch/script.h> #include <iostream> int main() { // 1. 基础张量运算 torch::Tensor a = torch::tensor({1.0, 2.0, 3.0}); torch::Tensor b = torch::tensor({4.0, 5.0, 6.0}); torch::Tensor c = a + b; std::cout << "Sum: " << c << std::endl; // 2. 随机张量和维度信息 torch::Tensor random_tensor = torch::rand({2, 3}); std::cout << "Random tensor: " << random_tensor << std::endl; std::cout << "Size: " << random_tensor.sizes() << std::endl; // 3. 输出LibTorch版本号 std::cout << "LibTorch version: " << TORCH_VERSION << std::endl; // 4. 确认是CPU版,CUDA不可用 std::cout << "CUDA available: " << torch::cuda::is_available() << std::endl; std::cout << "CPU threads: " << torch::get_num_threads() << std::endl; return 0; }这段代码里,torch/torch.h提供张量和自动求导能力,torch/script.h提供JIT推理接口,虽然验证环境用不到script.h,但为了后面加载模型方便,现在就把这两个头文件的编译路径验证到位。TORCH_VERSION是一个宏,定义在头文件里,能输出编译LibTorch时对应的PyTorch版本号。
4.2 运行之后的预期输出
编译运行,正确输出大致是:
Sum: 5 7 9 [ CPUFloatType{3} ] Random tensor: 0.3176 0.2544 0.1354 0.4092 0.7102 0.1017 [ CPUFloatType{2,3} ] Size: [2, 3] LibTorch version: 2.6.0 CUDA available: 0 CPU threads: 8这里注意CUDA available: 0,这个0正是CPU版的正解。如果你下载的是GPU版,在没有N卡的环境下打印的也是0,但包里带了一堆用不到的CUDA组件,体积大、启动慢,所以纯CPU场景务必选CPU版包。
如果程序能完整跑出这段结果,说明环境搭建已经成功。我自己的习惯是再把三件事做一遍,作为最终确认:第一,把torch/torch.h单独放一个新建空项目里编译一次,确保不是旧项目缓存的假象;第二,在命令行直接运行编译出的exe,而不是在VS里按F5,避免VS代理进程干扰;第三,把exe复制到另一个目录,确认dll是否已经放在exe旁,如果报错,验证PATH方案或dll复制方案是否生效。
4.3 编译失败还是运行失败的排查顺序
环境出问题时,先别急着重装LibTorch,按这个顺序排查:
- 如果编译阶段就报错:100%是包含目录有问题,检查是否两个路径都加了;
- 如果编译通过、链接阶段报错:100%是x64平台问题,或者附加依赖项没写全;
- 如果链接通过、运行时报找不到dll:100%是PATH没生效或dll没复制到exe旁;
- 如果运行时报其他奇怪的崩溃、内存访问冲突:大概率是Debug/Release混用,或者是机器缺少VC++运行库,安装一下VS2026自带的对应运行库即可。
这几类问题占到了LibTorch环境搭建失败的90%以上。
5. 高频踩坑点:链接错误、DLL缺失与头文件路径的完整排查
5.1 LNK2038运行库不匹配:Debug和Release水火不容
我在一个新的同事们的工作站上复现过这个问题。VS工程默认是Debug模式,LibTorch官方包默认选择Release版,然后在链接阶段报:
LNK2038: mismatch detected for 'RuntimeLibrary': value 'MD_DynamicRelease' doesn't match value 'MT_StaticRelease'这个错误的意思是,LibTorch的lib文件是用"多线程DLL动态链接"模式编译的,而你的工程配置成了"多线程静态链接"模式。MSVC的C运行时库有/MD、/MT两种模式,Debug和Release下又有不同名字,选错就链接不上。
解决办法有两个,推荐第一个:项目属性→C/C++→代码生成→运行库→选择多线程 DLL (/MD)。如果工程是Debug模式,就把LibTorch官方包换成Debug版。但LibTorch Debug包在Windows下用的人少,网上资料也少,体验不如Release包稳定,所以最终建议还是:项目切Release模式,运行库选/MD。
5.2 无法解析的外部符号:99%是x86/x64不匹配
如果报错信息里有大量:
LNK2019 无法解析的外部符号 "class c10::TensorTypePtr ...",该符号在函数 ... 中被引用并且你是x64的LibTorch,但VS平台是x86,那这些符号找不到是必然的。x86的链接器只能链接x86的lib,x64的lib对x86链接器来说就是一堆无法识别的符号。
这个坑特别容易在"新建项目后忘记切换解决方案平台"时出现。尤其是多人协作仓库,如果项目管理文件里缓存了x86平台配置,新clone下来的人一编译就懵。所以我在配置文档里会特意用加粗标注:先切x64,再动LibTorch属性。
5.3 运行时报找不到dll:PATH、cwd和UAC的三角关系
链接都通过了,运行时报:
由于找不到 torch_cpu.dll,无法继续执行代码。重新安装程序可能会修复此问题。我在几个不同环境里遇到的原因有三种:
第一种是PATH没生效。VS2026是图形界面程序,修改系统环境变量后VS不会自动刷新,必须完全关闭VS再重新打开。我见过有人改完PATH不重启VS,然后开始怀疑人生。
第二种是dll其实就放在exe旁边,但加载顺序问题。Windows加载dll的顺序是先看exe所在目录,然后看系统目录,再看PATH。理论上exe旁有同名dll时,不会去PATH找。但如果项目输出目录和exe运行目录不是同一个(比如在VS里设置了自定义输出路径),就容易出现"明明bin里没有崩溃,换台机器却崩了"的情况。
第三种跟用户账户控制(UAC)有关。如果程序是以管理员权限启动的,PATH可能会被系统重置,导致原本能搜到的目录失效。这种情况下更稳妥的做法是把dll放到exe同目录,而不是依赖PATH。我自己最终部署时一律用"exe同目录放全量dll"策略,没有再遇到运行时缺失dll的问题。
5.4 CPU版LibTorch初始化阶段的资源占用问题
还有一个不算报错但很容易让人误判的坑:LibTorch程序一启动,内存占用可能直接冲上几百MB,CPU占用也会短暂飙升。第一次跑通验证程序的人看到任务管理器里这个现象,很容易以为程序卡死了,其实这只是LibTorch在初始化CPU算子库,包括oneDNN(原MKL-DNN)的原语缓存、注册线程池等资源。
如果这个初始化峰值影响了你的业务(比如在非常低配的机器上),可以在main函数最开始设置:
#include <c10/thread/ThreadPool.h> int main() { torch::set_num_threads(4); // 业务代码 }把线程数限制为业务环境实际可承受的并发度。不过要注意,torch::set_num_threads必须在任何张量运算之前调用,否则已在运行的算子线程池不会跟着变化。更多细节后面会展开。
5.5 不同版本LibTorch的残留问题
如果你之前电脑上装过旧版LibTorch(比如1.13或2.0系列的),新项目一定要检查附加包含目录和库目录是否指到了旧路径。VS的属性是保存在项目文件里的,不同机器上目录可能不同。我见过有人新项目配置没问题,但编译时头文件却被旧版本抢先引用,导致模板库不兼容的报错。排查方法很简单:在源码里右键"torch/torch.h"→打开文档,看VS实际打开的是哪个路径,基本一眼就能定位。
6. 环境搭完后的工程化建议:从验证程序走向真实模型部署
6.1 与其在VS属性面板里折腾,不如用CMake
手工配置VS属性面板能跑通环境,但到了真实项目阶段——尤其是多人协作、跨平台、持续集成——建议尽快切换到CMake。LibTorch官方在share\cmake目录里提供了完整的CMake配置,用起来非常顺滑。
一个最简的CMakeLists.txt长这样:
cmake_minimum_required(VERSION 3.18) project(LibTorchDemo) set(CMAKE_CXX_STANDARD 17) # libtorch 根目录 set(LIBTORCH_DIR "D:/libtorch") list(APPEND CMAKE_PREFIX_PATH ${LIBTORCH_DIR}) find_package(Torch REQUIRED) add_executable(demo main.cpp) target_link_libraries(demo "${TORCH_LIBRARIES}") # 确保dll能被复制到输出目录 if(WIN32) add_custom_command(TARGET demo POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE_DIR:${TORCH_LIBRARIES}>" "$<TARGET_FILE_DIR:demo>") endif()这里find_package(Torch REQUIRED)会自动从share\cmake目录里找到TorchConfig.cmake,设置好所有头文件、库目录和宏定义。TORCH_LIBRARIES是CMake提供的变量,包含了所有需要链接的库,不用再手动枚举几十个.lib文件了。
对于团队里多个开发机环境不一致的窘况,CMake版配置直接把LIBTORCH_DIR改成各自机器上的路径就行,其他逻辑保持一致,比手写VS属性要可维护得多。
6.2 加载PyTorch模型的正确姿势:TorchScript
环境搭好之后,绝大多数人的目标是加载训练好的PyTorch模型。在Python里用torch.jit.trace或在训练代码里用torch.jit.script导出TorchScript模型:
import torch model = MyModel() model.load_state_dict(torch.load("model_weights.pth", map_location="cpu")) model.eval() example_input = torch.rand(1, 3, 224, 224) traced_model = torch.jit.trace(model, example_input) traced_model.save("model_script.pt")然后在C++侧加载:
#include <torch/script.h> torch::jit::Module module; try { module = torch::jit::load("model_script.pt"); } catch (const c10::Error& e) { std::cerr << "Failed to load model: " << e.what() << std::endl; return -1; } std::vector<torch::jit::IValue> inputs; inputs.push_back(torch::ones({1, 3, 224, 224})); torch::Tensor output = module.forward(inputs).toTensor();这里最容易犯的错是在验证代码里只包含了torch/torch.h,没包含torch/script.h。虽然torch/torch.h也包含了一些script相关头,但torch::jit::load的完整声明在script.h里。环境验证阶段就把script.h包含进来编译一次,后面加载模型时能省去一轮报错排查。
6.3 CPU版的性能调优方向
LibTorch在CPU上跑模型,有几件事值得做:
第一,控制线程数。默认情况下LibTorch会使用所有物理核心,如果程序还开了自己的线程池,可能有超额争抢。前面提到的torch::set_num_threads(N)在初始化时设置即可。
第二,确认是否启用了指令集优化。CPU版LibTorch默认启用了AVX/AVX2等指令集,运行日志里偶尔能看到相关的初始化信息。如果你的CPU比较老,不支持这些指令集,程序可能直接崩溃或报非法指令。这时候需要换用更早版本的LibTorch,或者手动编译一个没有AVX优化的版本。这个情况在嵌入式工控机上比较常见,普通办公电脑一般不会遇到。
第三,对单次推理耗时敏感的场景,可以用torch::NoGradGuard确保推理时不创建计算图:
{ torch::NoGradGuard no_grad; auto output = module.forward(inputs).toTensor(); }环境都搭好之后,这些优化点在真实部署时能直观感受到差别。
6.4 交付时的目录清单和运行库注意事项
最后说一下交付。如果你的程序需要给其他机器使用,最简单的目录组织是:
C:\MyApp\ ├── MyApp.exe ├── torch.dll ├── torch_cpu.dll ├── c10.dll ├── asmjit.dll ├── fbgemm.dll ├── dnnl.dll └── ... 其他dll如果你的目标机器没有安装较新的Microsoft Visual C++ Redistributable,程序可能启动就报"VCRUNTIME140.dll缺失"。解决方法是把VS2026安装目录下的VC\Redist\MSVC\...\vc_redist.x64.exe也一起打包进安装程序,或者在部署文档里明确写上安装前置运行库。这一步很多人在开发机上意识不到,因为开发机装了VS2026,运行库齐全,目标机器上就原形毕露了。
我在做交付时还会把LibTorch的版本号、VS编译的MSVC工具集版本号写进程序的About窗口或版本信息里,方便后续排查问题。别小看这个,半年后你自己回看项目时,省下的时间不是一点半点。
当年我第一次把这套环境跑通,前后折腾了大半天,大部分时间花在两个地方:一是版本选择没弄明白,二是被x86/x64平台问题坑了一遍。现在回头看,环境搭建本身并不复杂,核心就三句话:选对CPU版Release包,配全头文件路径和lib依赖项,处理好dll运行路径。把这三件事做对,剩下的就是写代码的时候了。希望这篇记录能帮你把大半天时间省成半小时。