Windows 原生编译 SGLang(4/8):环境关——VS 版本、venv 顺序、CUDA 多版本、生成器缓存

Windows 原生编译 SGLang(4/8):环境关------VS 版本、venv 顺序、CUDA 多版本、生成器缓存

这是攻关系列的第一篇实战。很多人以为编译的难点全在源码,其实最容易被低估、却最容易让人卡在第一步的,是环境。本篇要讲的四个坑,没有一个跟 sglang 本身有关------它们是所有人在 Windows 上编译 CUDA 扩展都可能撞上的共性问题。把这一关过了,才谈得上碰源码。

本篇所有解法都来自实战中真实踩过、并逐一验证过的过程。每个坑都按"现象 → 成因 → 解法 → 验证"来讲,方便你照着自查。


坑一:明明装了 VS2022,CMake 却报"No CUDA toolset found"

现象

配置阶段直接失败,报错很唬人:

复制代码
-- Building for: Visual Studio 18 2026
CMake Error: No CUDA toolset found.

成因

这台机器上同时装着两个 Visual Studio:正式的 VS2022 ,以及一个 VS18 Insiders(2026 预览版)。CMake 在自动探测生成器时,默认会挑它认为"最新"的那个------于是选中了 VS18。

但问题在于:CUDA 13.1 只与 VS2022 做了 MSBuild 集成,没有为 VS18 提供集成文件。CMake 用 VS18 去找 CUDA 工具集,自然找不到,于是报"No CUDA toolset found"。报错信息本身有误导性------它看起来像"CUDA 没装好",实际是"用错了编译器版本去找 CUDA"。

解法

有两条路,本系列两条都用上了,各自解决一半问题。

解法 A(根上绕开):强制走 Ninja 生成器。 最干净的办法不是去修 VS 版本问题,而是改用 Ninja------Ninja 直接调用 nvcc,不依赖 Visual Studio 的 MSBuild CUDA 集成,从根上绕开"哪个 VS 认 CUDA"这件事:

cmd 复制代码
set CMAKE_GENERATOR=Ninja

设好之后,配置阶段开头应显示 -- Building for: Ninja,而不再是 Visual Studio 18 2026。本系列全程用 Ninja,它也比 MSBuild 更快、更省心。

解法 B(显式锁定):用 vswhere 把版本钉死在 VS2022。 如果你确实要用 VS 工程而非 Ninja,就不能让 CMake / vswhere 自动挑,必须加版本区间约束,把范围限定在 17.x(VS2022 的主版本号是 17),排除掉 18.x:

cmd 复制代码
vswhere -version "[17.0,18.0)" -property installationPath

顺带一个易错点:如果用了 vswhere -prerelease,它会优先选中预览版(VS18 Insiders),正好踩中这个坑。去掉 -prerelease,并用上面的版本区间约束,才能稳定锁到 VS2022。

一个相关的历史残留

排查这个问题时还发现一个隐藏因素:之前有一次中途中止的 CUDA 13.2 升级,在两个 VS 安装目录里都留下了孤儿的 MSBuild 集成文件。这些残留会进一步干扰 CMake 对 CUDA 版本的正确探测。把这些孤儿文件清掉之后,CMake 才能干净地识别到 v13.1。如果你也经历过 CUDA 的反复升降级,这一点值得检查。


坑二:vcvarsall 显示成功,cl.exe 却凭空消失

这是本系列最隐蔽、也最反直觉的一个环境坑,值得细讲。

现象

在一个新开的命令行窗口里,按"常规直觉"的顺序搭环境:

cmd 复制代码
:: 先跑 vcvarsall 初始化 MSVC 环境
"D:\...\VC\Auxiliary\Build\vcvarsall.bat" x64
:: 再激活项目的 venv
K:\...\.venv\Scripts\activate.bat

vcvarsall 明明打印了成功提示:

复制代码
[vcvarsall.bat] Environment initialized for: 'x64'

但紧接着检查编译器,却什么都找不到:

cmd 复制代码
where cl
INFO: Could not find files for the given pattern(s).

cl.exe(MSVC 编译器本体)凭空消失了------这跟"环境初始化成功"那句话直接矛盾。

成因

关键在 venv 的创建方式 。这个项目的 venv 是用 uv 创建的,而 uv 生成的 activate.bat 在处理 PATH 时,是"重置"而非"前置追加" :它会把 PATH 恢复到某个缓存下来的旧值,再把 venv 自己的 Scripts 目录加进去。

于是,如果vcvarsall(它往 PATH 里塞进了 MSVC 工具链、Windows SDK、MSBuild 等一整批路径), 激活 venv------激活动作会把刚才 vcvarsall 辛苦塞进去的那一整段路径整个冲掉vcvarsall 自己确实成功了,但它的成果被随后的 venv 激活覆盖了,表现就是"报成功、但 cl.exe 找不到"。

验证这个推断很简单:打印完整 PATH,会发现里面完全没有 VC\Tools\MSVC\<版本号>\bin\Hostx64\x64 这一段------vcvarsall 该加的全没了,只剩 venv 激活前的原始 PATH 加上 venv 的 Scripts。

解法

把顺序反过来:先激活 venv,最后再跑 vcvarsall。vcvarsall 的修改是最后一个生效的,就不会被任何后续步骤覆盖:

cmd 复制代码
:: 1. 先激活 venv
K:\PythonProjects5\Unlimited-OCR\.venv\Scripts\activate.bat
:: 2. 再跑 vcvarsall(它的 PATH 修改最后生效,不会被冲掉)
"D:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvarsall.bat" x64
:: 3. 其余环境变量
set CMAKE_GENERATOR=Ninja
set DISTUTILS_USE_SDK=1
set MAX_JOBS=4
cd /d K:\PythonProjects5\Unlimited-OCR\sglang\sgl-kernel

验证

cmd 复制代码
where cl

正确时应指向 VS2022 的 MSVC 工具链:

复制代码
D:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.42.34433\bin\Hostx64\x64\cl.exe

只要 where cl 能找到这个路径,就说明环境搭对了。这一步务必显式验证 ,不要因为 vcvarsall 报了成功就默认环境就绪------这个坑的全部杀伤力,就在于"报成功"和"实际可用"之间的那道裂缝。


坑三:生成器选择被缓存写死,改对环境也不生效

现象

环境明明已经按坑二的方法搭对了(where cl 能找到、CMAKE_GENERATOR=Ninja 也设了),重新配置却还是 报"No CUDA toolset found",而且这次的输出里少了 Building for: ... 那一行------直接从"加载初始缓存文件"跳到报错。

成因

这是 CMake 一个经典且代价高昂的陷阱:生成器的选择,一旦写进某个构建目录的缓存(CMakeCache.txt),后续即便环境变量改对了,CMake 也不会主动重新选择,只会沿用第一次缓存下来的那个。

也就是说:某次环境还没搭对时跑过一次配置,在构建目录里留下了一份记着"用 VS18"的缓存。之后哪怕你把环境彻底修对,只要还在同一个构建目录里继续,CMake 读到的仍是那份带毒的旧缓存------它根本不重新探测,所以"少了 Building for 那一行"正是它跳过探测、直接读缓存的标志。

解法

改动任何与生成器相关的东西后,必须物理删除整个构建目录,强制 CMake 从零重新配置:

cmd 复制代码
rmdir /s /q Z:\b

删除后,务必再确认一次它真的没了,不要因为"提示找不到文件"就默认已清空:

cmd 复制代码
dir Z:\b
:: 期望看到 "File Not Found",才算真清干净

确认彻底清空后,在搭好环境的窗口里重新配置,CMake 会从零开始、正确选用 Ninja。配置成功的标志是开头出现:

复制代码
-- Building for: Ninja

而不再是 Visual Studio 18 2026

一个容易混淆的点:并不是每次都要清构建目录。只改源码、只改编译选项(不涉及生成器)时,保留缓存增量编译反而更快。只有当你改动了生成器、或改动了 CMAKE_CUDA_FLAGS 这类 Configure 阶段的全局变量时,才必须清缓存重配。 判断标准是:这次改的东西,CMake 是在"配置阶段"读它,还是在"编译阶段"读它------前者必须清。


坑四:CUDA 多版本共存,与 subst 短路径

这两个不算"故障",但属于会反复绊到人的环境特性,一并说清。

CUDA 多版本

这台机器上同时装了 5 个版本的 CUDA(12.6 / 12.8 / 12.9 / 13.0 / 13.1),where nvcc 会一口气列出全部:

cmd 复制代码
where nvcc
C:\...\CUDA\v13.1\bin\nvcc.exe
C:\...\CUDA\v12.6\bin\nvcc.exe
... (其余版本)

这通常是 NVIDIA 安装程序每装一个版本就往系统 PATH 追加一条、长年累积的结果。它不致命 ------只要 CMake 配置时已明确指向 v13.1(本项目通过环境与 CMake 设置锁定),它就会用对版本。但它是一个有用的旁证:PATH 里塞着多个 nvcc,说明这台机器的环境层比较"拥挤",排查其他问题时要意识到这一点。

如果你也维护多版本 CUDA,建议配合一套显式的版本切换机制(例如把目标版本的 bin 目录前置到 PATH),让"当前生效版本"始终唯一、可控,而不是依赖 PATH 里谁排前面这种隐式顺序。

subst 短路径不跨会话

为了缩短构建路径(CUDA + CUTLASS 的深层模板路径很容易触及 Windows 路径长度限制),我们用 subst 把构建目录映射成一个短盘符:

cmd 复制代码
subst Z: K:\sgk_build

要注意:subst 的映射不跨重启、不跨重新登录 。每开一个新的工作会话,都可能需要重新挂一次。如果某次构建突然报找不到 Z:\...,先检查这个映射是不是失效了------这跟下一个要提的"环境变量只在当前窗口有效"是同一类"会话级状态"问题。


贯穿四坑的一条主线:警惕"会话级状态"

回头看这四个坑,坑二、坑三、坑四其实共享同一个底层教训:很多关键状态是"会话级"或"目录级"的,不会自动跨越窗口、重启或目录留存下来。

  • set CMAKE_GENERATOR=Ninja 只在当前命令行窗口有效------新开一个窗口,它就没了,CMake 退回默认探测、又选中 VS18(这正是坑一与坑三联动复发的根源);
  • subst Z: 只在当前登录会话有效------重启或重新登录就失效;
  • 生成器选择被写进构建目录的缓存------换了环境也不重选,除非物理删目录;
  • venv 激活会重置当前窗口的 PATH------顺序错了就冲掉 vcvarsall 的成果。

所以,本系列后续每次"新开窗口续编",都遵循一套固定的、顺序严格的环境搭建流程(见下),把这些会话级状态一次性、按正确顺序重新建立起来。漏掉其中任何一步,或顺序错了,都可能让你莫名其妙地回到第一步的报错。

标准环境搭建流程(每个新窗口都照此执行)

cmd 复制代码
:: 顺序严格,不可调换 ------ venv 必须在 vcvarsall 之前
K:\PythonProjects5\Unlimited-OCR\.venv\Scripts\activate.bat
"D:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvarsall.bat" x64
set CMAKE_GENERATOR=Ninja
set DISTUTILS_USE_SDK=1
set MAX_JOBS=4
subst Z: K:\sgk_build
cd /d K:\PythonProjects5\Unlimited-OCR\sglang\sgl-kernel

:: 三项必查,缺一不可:
where cl      :: 应指向 VS2022 的 cl.exe
where nvcc    :: 应能找到 v13.1 的 nvcc
python -c "import torch; print(torch.__version__, torch.version.cuda)"  :: 应是 cu130

小结与下一篇

本篇的四个坑,全部与 sglang 源码无关,却足以让人在真正开始编译之前就反复受挫:

  • VS 版本:多版本共存时显式锁定 VS2022,别让 CMake 自动选到预览版;
  • vcvarsall 与 venv 顺序:uv 的 venv 激活会重置 PATH,必须"先 venv,后 vcvarsall";
  • 生成器缓存:改生成器/Configure 级变量后必须物理清空构建目录;
  • 会话级状态 :环境变量、subst、缓存都不会自动跨窗口/重启留存,新窗口要按固定流程重建。

把环境这一关稳住之后,我们才真正开始碰源码。第 4 篇 进入源码移植的上半场:那些"GCC/Clang 能过、MSVC 不认"的方言问题,以及 MSVC 预处理器的严格性------从 __builtin_clz__asm__,从 __attribute__ 到宏参数里的裸 # 指令。



系列导航(全 14 篇)

编译移植篇(怎么把 sglang 从源码编出来)

  • 00 · 系列总览
  • 01 · EPGF 环境地基与岔路口
  • 02 · 结论与可行性:三铁证 + --no-deps
  • 03 · 编译篇·前置:FlashInfer Windows 源码编译
  • 04 · 编译篇·环境关:VS 版本、venv 顺序、CUDA 多版本、生成器缓存
  • 05 · 移植篇(上):GCC 方言与 MSVC 预处理器严格性
  • 06 · 移植篇(下):常量求值、重载决议与编译器崩溃
  • 07 · 编译篇·收尾:架构裁剪与 LNK2019 链接收尾
  • 08 · 方法论:台账、幂等补丁脚本与多 AI 协作

部署运行篇(怎么跑起来并排障)

  • 09 · 正确启动 SGLang + Unlimited-OCR
  • 10 · 排障①:推理输出乱码/数值错误根因定位
  • 11 · 排障②:环境变量块超限导致 spawn 子进程崩溃
  • 12 · 性能调优:RTX 3090 MoE triton autotune config
  • 13 · 长文档验证 + 代理/端口冲突坑 + 使用指南

编译移植篇讲"能不能编出来、怎么编";部署运行篇讲"编出来之后怎么跑通、怎么排障、怎么调优"。

两篇之间最关键的交叉点:本机实际编译用的是 第 07 篇 产物 sglang_kernel-0.4.3-cp310-abi3-win_amd64.whl

而 第 09 篇 的启动命令正是加载它 + Unlimited-OCR 模型。


参考资料与延伸阅读

以下为本文涉及的官方仓库、文档与规格站,建议发布前点一遍确认可达:

相关推荐
山河不见老2 小时前
【Cursor 、Qoder安装问题】Cursor 、Qoder安装卡在“正在准备安装”问题排查及解决
人工智能·windows·编辑器
love530love2 小时前
Windows 原生编译 SGLang(2/8):三铁证判定可行 + --no-deps 外科手术式安装
windows·sglang
changshuaihua0013 小时前
Redis 详解:Redis 与 MySQL 的区别、数据类型与缓存三大问题
数据库·redis·缓存
2601_962364974 小时前
Windows Hello 密钥生命周期科普:注册、鉴权、重置加密逻辑
windows·电脑
脚踏实地皮皮晨4 小时前
003003002_WPF Grid 基类官方类定义逐行深度解析
开发语言·windows·算法·c#·wpf·visual studio
笨鸟先飞,勤能补拙15 小时前
AI 赋能网络安全领域深度剖析
网络·人工智能·windows·安全·web安全·网络安全·github
我上去就是一套QWER然后等待复活16 小时前
Windows 清理 CC Switch 并恢复官方 Codex CLI 登录流程(完整记录)
windows
吾儿良辰16 小时前
一个被BCL遗忘的高性能集合:C# CircularBuffer<T>深度解析
开发语言·windows·c#
哎呦喂我去去去16 小时前
C#实现屏幕墙:同时监控多个电脑桌面(支持Windows、信创Linux、银河麒麟、统信UOS)
linux·windows·c#