Windows 原生部署 NInfer:RTX 3080 Ti 20G 跑 Qwen3.8-27B + MTP + Vision 全流程 + gguf-to-ninfer 单流100k/s 双流150k/s
-
我和网页版的chatgpt聊天,本地编译,部署,测试后。chatgpt总结的内容如下(可以照抄作业,最终210k上下文显存占用17.7g,另有kvmem-v0.18.0-ninfer-windows-rtx30-sm86项目,显存可压倒15g不到,没在这篇):
Windows 原生部署 NInfer:RTX 3080 Ti 跑 Qwen3.8-27B + MTP + Vision 全流程
这篇记录一下我在 Windows 下原生编译部署
iamwavecut/ninfer-all的完整过程。最终目标是:
- Windows 原生运行,不走 WSL
- RTX 3080 Ti 20GB
- CUDA SM86
- Qwen3.8-27B
- GSQ-RCO IQ3_S
- MTP speculative decoding
- Vision
- 长上下文
- GGUF 自己转换成
.ninfer
最终已经成功跑通。
一、环境
我的环境:
GPU:RTX 3080 Ti 20GB 架构:Ampere / SM86 系统:Windows CUDA:13.0 NInfer:iamwavecut/ninfer-all Commit:5ed86df 构建系统:CMake + Ninja Visual Studio:Visual Studio 18 Insiders MSVC Toolset:v143 / 14.44NInfer 源码:
D:\AI\NInfer\ninfer-allvcpkg:
D:\AI\NInfer\vcpkgCUDA:
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.0
二、为什么要强制使用 MSVC 14.44
这里是第一个坑。
Visual Studio Insiders 自带的新 MSVC 版本和 CUDA 13 的 host compiler 检查存在兼容问题。
最后使用的是:
MSVC 19.44.35229 Toolset 14.44先打开 CMD,然后执行:
call "C:\Program Files\Microsoft Visual Studio\18\Insiders\VC\Auxiliary\Build\vcvarsall.bat" x64 -vcvars_ver=14.44确认:
where cl cl应该优先看到类似:
...\VC\Tools\MSVC\14.44.35207\bin\HostX64\x64\cl.exe而不是 VS Insiders 最新的 19.5x 编译器。
三、准备 vcpkg 自定义 triplet
创建:
D:\AI\NInfer\vcpkg\triplets\x64-windows-v143.cmake内容:
set(VCPKG_TARGET_ARCHITECTURE x64) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_LIBRARY_LINKAGE dynamic) set( VCPKG_VISUAL_STUDIO_PATH "C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders" ) set(VCPKG_PLATFORM_TOOLSET v143) set(VCPKG_PLATFORM_TOOLSET_VERSION 14.44)这个步骤很重要,否则 vcpkg 里面的依赖可能又偷偷用回最新 Toolset。
四、配置 NInfer
进入:
cd /d D:\AI\NInfer\ninfer-all配置:
cmake -S . -B build-sm86 -G Ninja ^ -DCMAKE_BUILD_TYPE=Release ^ -DCMAKE_CUDA_ARCHITECTURES=86 ^ -DCMAKE_TOOLCHAIN_FILE="D:/AI/NInfer/vcpkg/scripts/buildsystems/vcpkg.cmake" ^ -DVCPKG_TARGET_TRIPLET=x64-windows-v143 ^ -DVCPKG_MANIFEST_MODE=ON成功时应该看到:
C compiler:MSVC 19.44 CXX compiler:MSVC 19.44 CUDA compiler:NVIDIA 13.0 CUDA architecture:86
五、编译 ninfer-serve
执行:
cmake --build build-sm86 --target ninfer-serve -j 1我一开始使用旧 commit 时,在:
small_t_i8_launch.cuh附近遇到 MSVC lambda capture / dynamic shared memory 相关编译问题。
后来把仓库更新到:
5ed86df之后上游已经修复。
建议直接更新:
git fetch git checkout master git pull --ff-only查看:
git log -1 --oneline我最后使用的是:
5ed86df Merge pull request #106 ...之后:
cmake --build build-sm86 --target ninfer-serve -j 1成功。
生成:
D:\AI\NInfer\ninfer-all\build-sm86\apps\ninfer-serve.exe
六、编译并运行硬件校准
NInfer 有一个很重要的设备校准机制。
先编译:
cmake --build build-sm86 --target ninfer-calibrate -j 1然后执行校准。
校准完成后我的结果大概是:
89 keys measured 59 routed off compiled route hardware class:SM86 SM 数量:82默认 profile 会写到:
C:\Users\<用户名>\AppData\Local\ninfer\device-profiles.json我后来直接复制到:
D:\AI\NInfer\ninfer-all\build-sm86\apps\device-profiles.json启动服务器时显式指定:
--device-profile-path "D:\AI\NInfer\ninfer-all\build-sm86\apps\device-profiles.json"正常启动会看到:
device profile nvidia-graphics-device-sm86: 89 routed keys说明校准配置生效。
七、Qwen3.8-27B 推荐启动配置
我主要用途是:
- Coding
- Agent
- 长上下文
- Vision
- 偶尔同时开第二个请求
最终我比较倾向:
KV dtype:rk8v4 KV capacity:约 210K MTP:2 draft tokens Vision:overlay Host KV:关闭一个可直接使用的 BAT:
@echo off setlocal chcp 65001 >nul set "SERVER=D:\AI\NInfer\ninfer-all\build-sm86\apps\ninfer-serve.exe" set "PROFILE=D:\AI\NInfer\ninfer-all\build-sm86\apps\device-profiles.json" set "MODEL=D:\AI\NInfer\models\Qwen3.8-27B-GSQ-RCO-IQ3_S-ninfer-v3.ninfer" "%SERVER%" "%MODEL%" ^ --model-id qwen3.8-27b ^ --device-profile-path "%PROFILE%" ^ --host 127.0.0.1 ^ --port 18201 ^ --max-concurrency 2 ^ --max-context 210000 ^ --kv-capacity 210000 ^ --kv-dtype rk8v4 ^ --gdn-state-fp16 ^ --spec mtp ^ --draft-tokens 2 ^ --vision ^ --vision-residency overlay ^ --vision-max-merged 12288 ^ --host-kv-mib 0 pause
八、为什么最后选择 210K rk8v4
我实际测试过几个配置。
rk8v4 230K
服务器最终分配:
KV 230,016 tokens runtime 7.07 GiB free 452 MiB能跑,但是只剩:
452 MiBVision 或两个请求一起上时风险比较大。
rk8v4 210K
缩到大约:
KV 210,048 tokens runtime 6.56 GiB free 980 MiB这个明显舒服很多。
对于 20GB 卡,我觉得:
rk8v4 + 210K是长上下文、精度和显存之间比较好的平衡。
九、rk4v4 和 rk8v4
如果更追求:
速度 更大上下文 更低显存可以用:
--kv-dtype rk4v4我测试时速度很好。
但我主要跑 Coding Agent,而且经常会堆很长上下文,所以最后偏向:
rk8v4因为 K 保留 8bit,在超长上下文 Key 匹配上理论上更保守。
如果主要是普通聊天或者追求容量:
rk4v4也完全可以。
十、MTP2 / MTP3
我自己的测试里,中短上下文:
MTP2表现很好。
例如:
--spec mtp --draft-tokens 2单请求实际 decode 可以到大约:
110~115 tok/s部分滚动窗口会更高。
我也测试了:
MTP3但在当前这些测试里并没有明显赢 MTP2。
所以目前直接使用:
MTP2
十一、两个并发请求
我试过:
--max-concurrency 2两个请求同时 decode。
单请求速度大约:
74 tok/s 77 tok/s合计:
约 150 tok/s需要注意:
--kv-capacity是整个服务共享的 KV pool。
例如:
kv-capacity = 210K max-concurrency = 2并不代表:
2 × 210K而是两个请求共享这 210K。
十二、Host KV
默认情况下 NInfer 可能会分配较大的 Host KV。
我最开始看到类似:
host KV pinned:8 GiB如果不需要 CPU KV spill,可以:
--host-kv-mib 0这样能明显减少主机 pinned memory。
不过 Windows 任务管理器仍然可能看到一点 Shared GPU Memory。
这是正常的。
即使:
host-kv-mib = 0仍然可能有:
- host state
- Vision/media buffer
- CUDA pinned/mapped memory
- WDDM driver mapping
不代表 KV 正在偷偷 spill 到系统内存。
十三、Vision
启用:
--vision ^ --vision-residency overlay ^ --vision-max-merged 12288 ^我选择:
overlay主要目的是减少 Vision 和 speculative 组件同时常驻 GPU 的显存压力。
但是 Vision 真正进行 image encode 时还是会产生额外临时显存。
所以:
230K rk8v4 + 452 MiB free我认为过于激进。
而:
210K rk8v4 + 约 980 MiB free安全很多。
十四、把 GGUF 转成 NInfer
后面我又测试了自己转换一个 Huihui 的 Qwen3.8 模型:
Huihui-Qwen3.8-27B-abliterated-GSQ-RCO-IQ3_S-mtp.gguf目录:
D:\AI\llama\models\huihui-aiVision:
D:\AI\llama\models\RCO\mmproj-Qwen3.8-27B-BF16.gguf
十五、创建独立 Python 转换环境
不要污染其他 Python 环境。
进入:
cd /d D:\AI\NInfer\ninfer-all创建:
py -3.11 -m venv .venv-convertCMD 激活:
call .\.venv-convert\Scripts\activate.bat注意:
PowerShell 才是:
.\.venv-convert\Scripts\Activate.ps1如果在 CMD 里执行
Activate.ps1是不会正常激活的。确认:
where python应该指向:
D:\AI\NInfer\ninfer-all\.venv-convert\Scripts\python.exe
十六、安装转换依赖
我使用 CPU Torch:
python -m pip install torch --index-url https://download.pytorch.org/whl/cpu确认:
python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"我的结果:
2.14.1+cpu False这是正常的。
转换模型并不需要 CUDA Torch。
然后:
python -m pip install gguf numpy safetensors transformers huggingface_hub其中:
gguf是必须的。
不装会报:
ImportError: reading a .gguf source requires the 'gguf' package
十七、准备 Qwen3.8 metadata
建立:
D:\AI\llama\models\huihui-ai\metadata从官方:
Qwen/Qwen3.8-27B下载这些文件:
config.json generation_config.json tokenizer.json tokenizer_config.json preprocessor_config.json video_preprocessor_config.json最终目录:
metadata\ config.json generation_config.json tokenizer.json tokenizer_config.json preprocessor_config.json video_preprocessor_config.json这里我一开始漏了:
generation_config.json于是转换器报:
FileNotFoundError: metadata\generation_config.json补上之后即可。
十八、一个重要发现:MTP 可以直接从同一个 GGUF 里拿
一开始我以为要单独下载:
Q6_K MTP GGUF后来发现这个 Huihui 文件:
Huihui-Qwen3.8-27B-abliterated-GSQ-RCO-IQ3_S-mtp.gguf本身已经内嵌 MTP。
只是 NInfer converter 要求:
gguf和:
mtp是两个逻辑 source。
所以可以把同一个物理文件同时传两次:
--source "gguf=xxx.gguf" ^ --source "mtp=xxx.gguf" ^这招成功了。
转换日志明确识别出:
mtp/layers/0/attention/query: gguf_q6_k mtp/layers/0/mlp/gate: gguf_q6_k mtp/input_projection: gguf_q6_k mtp/layers/0/attention/output: gguf_q6_k mtp/layers/0/mlp/down: gguf_q6_k也就是说:
这个 GGUF 的 MTP 本身就是 Q6_K不需要另外下载 MTP。
十九、最终成功的 GGUF → NInfer 命令
进入:
cd /d D:\AI\NInfer\ninfer-all然后:
python -u -m tools.convert ^ --model "D:\AI\llama\models\huihui-ai\metadata" ^ --recipe qwen3_8_27b_gguf ^ --source "gguf=D:\AI\llama\models\huihui-ai\Huihui-Qwen3.8-27B-abliterated-GSQ-RCO-IQ3_S-mtp.gguf" ^ --source "mtp=D:\AI\llama\models\huihui-ai\Huihui-Qwen3.8-27B-abliterated-GSQ-RCO-IQ3_S-mtp.gguf" ^ --source "vision=D:\AI\llama\models\RCO\mmproj-Qwen3.8-27B-BF16.gguf" ^ --components text,vision,mtp ^ --resource "chat_template.jinja=tools/chat_templates/qwen3_8.jinja" ^ --device cpu ^ --rows-per-chunk 512 ^ --name "huihui-qwen3.8-27b-abliterated-gsq-rco-iq3-s" ^ --out "D:\AI\NInfer\models\Huihui-Qwen3.8-27B-abliterated-GSQ-RCO-IQ3_S-MTP-Vision-ninfer-v3.ninfer"最后成功:
1174/1174然后:
wrote D:\AI\NInfer\models\ Huihui-Qwen3.8-27B-abliterated-GSQ-RCO-IQ3_S-MTP-Vision-ninfer-v3.ninfer 1181 objects 1 files 34.0s
二十、为什么第一次生成的模型不能开 MTP
第一次转换时我用了:
--components text,vision虽然源 GGUF 文件名带:
-mtp.gguf但输出
.ninfer里并不会自动包含 MTP component。所以启动:
--spec mtp会报:
Qwen3.5 config: missing component mtp正确方式必须是:
--components text,vision,mtp并且:
--source "mtp=..."也要显式提供。
即使
mtp=和gguf=指向的是同一个文件,也必须写。
二十一、以后转换模型可以直接用模板 BAT
核心变量只需要改:
set "META=..." set "GGUF=..." set "MTP=..." set "VISION=..." set "OUTNAME=..." set "MODEL_NAME=..."如果 MTP 就在主 GGUF 里面:
set "GGUF=D:\models\xxx-mtp.gguf" set "MTP=D:\models\xxx-mtp.gguf"即可。
组件:
set "COMPONENTS=text,vision,mtp"如果没有 Vision:
set "COMPONENTS=text,mtp"如果没有 MTP:
set "COMPONENTS=text"
二十二、最终效果
在 RTX 3080 Ti 20GB 上,当前这套 Windows 原生 NInfer 的体验比我之前使用的打包 runtime 明显好。
大致测试:
Qwen3.8-27B GSQ-RCO IQ3_S MTP2单流 decode 大约:
100~115 tok/s部分 rolling window 可以更高。
双请求时总吞吐可以到:
约 150 tok/s而长上下文配置:
rk8v4 约 210K KV Vision MTP2在 20GB 显存上也可以正常使用。
目前我个人最终配置是:
RTX 3080 Ti 20GB KV: rk8v4 Context: 约 210K Speculative: MTP2 Vision: 开启 overlay Host KV: 0 Concurrency: 2如果纯粹追求速度和最大上下文,可以换成:
rk4v4如果更重视长上下文 Coding / Agent,我目前更倾向:
rk8v4
总结
Windows 原生部署 NInfer 真正比较容易踩坑的主要是这几个地方:
- CUDA 13 和 VS 最新 MSVC 的兼容问题,要强制使用 MSVC 14.44。
- vcpkg 也要指定 v143 / 14.44,不能只改当前 shell。
- NInfer 更新到较新的 commit,旧版本 SM86 编译可能有问题。
- 一定运行
ninfer-calibrate。 - 20GB 卡不要把 rk8v4 KV 塞得太满,210K 比 230K 安全很多。
- GGUF 转换必须安装 Python
gguf包。 - metadata 不要漏
generation_config.json。 - Vision 需要
preprocessor_config.json和video_preprocessor_config.json。 - MTP 即使和主模型在同一个 GGUF 里,也要显式写
--source mtp=同一个文件。 --components text,vision不会自动生成 MTP;需要明确使用text,vision,mtp。
到这里 Windows + RTX 30 系 + Qwen3.8-27B + MTP + Vision + 长上下文这条链路就完整跑通了。
-
这个速度相当满意:
2026-10-19 23:03:43.715 INFO req#39 started | openai-chat stream | 2 messages | max output 13,107 | thinking medium
2026-10-19 23:03:45.487 INFO throughput | 5.0s | prefill 960.3 tok/s (4,808 tok) | running 1 (prefill 1) | host 46.0% (2.3s)
2026-10-19 23:03:50.482 INFO throughput | 5.0s | prefill 1.54k tok/s (7,680 tok) | running 1 (prefill 1) | host 42.2% (2.1s)
2026-10-19 23:03:55.475 INFO throughput | 5.0s | prefill 1.22k tok/s (6,082 tok) | running 1 (prefill 1) | host 35.4% (1.8s)
2026-10-19 23:04:00.487 INFO throughput | 5.0s | prefill 1.53k tok/s (7,680 tok) | running 1 (prefill 1) | host 45.3% (2.3s)
2026-10-19 23:04:05.483 INFO throughput | 5.0s | prefill 617.3 tok/s (3,084 tok) | decode 57.0 tok/s (285 tok) | running 1 (decode-ready 1) | batch 1.00 | host 21.1% (1.1s)
2026-10-19 23:04:10.489 INFO throughput | 5.0s | decode 94.9 tok/s (475 tok) | running 1 (decode-ready 1) | batch 1.00 | host 1.2% (58.9 ms)
2026-10-19 23:04:15.484 INFO throughput | 5.0s | decode 227.6 tok/s (1,137 tok) | running 1 (decode-ready 1) | batch 1.00 | host 1.0% (51.0 ms)
2026-10-19 23:04:20.474 INFO throughput | 5.0s | decode 180.4 tok/s (900 tok) | running 1 (decode-ready 1) | batch 1.00 | host 1.1% (53.6 ms)
2026-10-19 23:04:25.489 INFO throughput | 5.0s | decode 258.2 tok/s (1,295 tok) | running 1 (decode-ready 1) | batch 1.00 | host 1.0% (50.2 ms)
2026-10-19 23:04:30.480 INFO throughput | 5.0s | decode 111.6 tok/s (557 tok) | running 1 (decode-ready 1) | batch 1.00 | host 1.1% (54.3 ms)
2026-10-19 23:04:35.486 INFO throughput | 5.0s | decode 115.5 tok/s (578 tok) | running 1 (decode-ready 1) | batch 1.00 | host 1.1% (57.2 ms)
2026-10-19 23:04:40.485 INFO throughput | 5.0s | decode 136.8 tok/s (684 tok) | running 1 (decode-ready 1) | batch 1.00 | host 1.1% (57.4 ms)
2026-10-19 23:04:45.484 INFO throughput | 5.0s | decode 153.8 tok/s (769 tok) | running 1 (decode-ready 1) | batch 1.00 | host 1.1% (54.3 ms)
2026-10-19 23:04:50.482 INFO throughput | 5.0s | decode 121.9 tok/s (609 tok) | running 1 (decode-ready 1) | batch 1.00 | host 1.1% (55.9 ms)
2026-10-19 23:04:55.480 INFO throughput | 5.0s | decode 119.7 tok/s (598 tok) | running 1 (decode-ready 1) | batch 1.00 | host 1.1% (55.6 ms)