📚 双 RTX 5060 Ti 部署 Qwen3.8 系列 · 4/6
💡 导读:
llama.cpp 和 vLLM 并不是简单的“谁更快”。前者把个人硬件能触碰到的模型规模往上推,后者则更适合在模型可以完整驻留 GPU 后继续追求 Serving 效率。这篇从 Qwen3.8-Flash-Next 的 CPU Offload 讲到 NVFP4、Kernel、Compile 和 CUDA Graph,也解释为什么我的 27B 生产部署默认从 vLLM 开始。
前面三篇已经解决了三个问题:
27B 怎么装进双 16GB
↓
两张 GPU 到底怎么并行
↓
TP2 为什么能加速、又为什么达不到理想 2×
到了真正部署服务时,我现在有一个比较明确的默认选择:
只要是 NVIDIA GPU、模型能够合理地完全驻留 GPU,并且目标是长期提供推理服务,我会优先从 vLLM 开始。
原因并不只是 PagedAttention,也不只是 KV Cache。
真正让我倾向 vLLM 的,是它把整个 GPU 推理执行链放在同一个系统里优化:
NVFP4 / FP8 等低精度格式
↓
Optimized GEMM / Attention Kernel
↓
torch.compile / Inductor
↓
CUDA Graph
↓
TP / PP
↓
Scheduler / Continuous Batching
↓
Cache / Prefix Cache
这些东西单独拿出来都只是一个优化点。
组合起来以后,才是一套真正面向 GPU Serving 的推理引擎。
不过在谈 vLLM 之前,先要把一个经常被混在一起的问题说清楚:
“模型能不能跑”和“这个模型适不适合做高性能 GPU Serving”,不是同一个问题。
Qwen3.8-Flash-Next 恰好是一个非常好的例子。
1. Qwen3.8-Flash-Next:CPU Offload 为什么依然有价值
写这组文章的时候,Qwen3.8-27B 才开放权重大约两周。
然后 2026 年 8 月 26 日,Qwen 又开放了 Qwen3.8-Flash-Next。
它的体量已经完全不是 27B 这一级别:
Language Model:125B 参数,约 6B activated
+
51B n-gram embedding
+
4B MTP
总体参数规模大约到了 180B 量级。
直接看权重文件更直观:
官方 BF16:约 360GB官方 FP8:约 186GBUnsloth UD-Q4_K_XL GGUF:约 111GB
这时“显存不够”已经不是双 16GB 能不能稍微规划一下的问题了。
甚至假设有:
8 × RTX 3090 24GB = 192GB VRAM
这种对个人用户已经非常夸张的配置,仅从总量上看,官方 FP8 的约 186GB 权重就已经快把聚合显存吃满了。
而真正运行模型显然还要给:
- Runtime
- Cache
- Activation
- Workspace
- CUDA Graph
- 通信 Buffer
留下空间。
更不用说 8 张 3090 的拓扑、供电、散热和整机成本,本身就已经脱离绝大多数个人开发者的现实环境。
我当然也不会为了试一个刚发布的模型,先准备一台 8×3090 的机器。
这时候 llama.cpp 的另一种价值就非常明显了:
它可以把“GPU 显存不够”这个硬边界,扩展成“GPU VRAM + System RAM 能不能一起把模型跑起来”。
例如一个 100GB 级 GGUF,可以让一部分权重留在 GPU,一部分权重放在系统内存,通过 CPU + GPU 的混合路径运行。
原本的问题是:
模型 > 总 VRAM → 根本放不下
现在变成:
GPU VRAM
+
System RAM
→ 有机会真正运行起来
对于:
- 刚发布的超大模型
- 能力测试
- 本地研究
- 个人消费级硬件
- 显存容量不规则的机器
这种能力非常有价值。
所以我并不认同一种简单的判断:
“CPU Offload 很慢,所以没有意义。”
它当然有意义。
它改变的是:
“我能够接触和测试多大的模型”。
但另一方面,也不能把 RAM 当成“更便宜的显存”。
如果 Forward 需要频繁从系统内存取热点权重,那么整个性能模型就会受到:
CPU 内存带宽
+
CPU-GPU 数据路径
+
PCIe
限制。
GPU 本地 GDDR 能提供数百 GB/s 级带宽,而系统 RAM 与 CPU↔GPU 的 PCIe 路径在有效带宽、延迟和访问方式上都明显不同。
Offload 越重,Decode 越不可能维持纯 GPU Resident 模型的性能特征。
所以我现在更愿意这么定义它:
CPU / RAM Offload 很擅长扩大“能跑的模型范围”,但通常不是扩大“高性能生产 Serving 范围”的办法。
这也正好解释了 llama.cpp 和 vLLM 为什么不是简单的“谁替代谁”。
2. 对 27B 来说,我反而会优先追求 GPU Resident
Flash-Next 这种百 GB 级模型,RAM Offload 很合理。
但回到前面一直测试的 Qwen3.8-27B,情况完全不同。
它不是“无论如何都放不下”。
通过合适的量化,双 16GB 本来就有机会让权重完整驻留 GPU。
这时候我的优化顺序会变成:
选择质量可以接受的量化
↓
尽量让权重完整 GPU Resident
↓
给 Cache / Context 留空间
↓
选择 TP / PP
↓
再优化 Kernel、Compile、CUDA Graph 和调度
这也是为什么前面最终更倾向 NVFP4。
FP8 约 31GB:
32GB 总 VRAM
-
约 31GB 权重
≈
几乎没有运行余量
NVFP4 约 23GB:
32GB 总 VRAM
-
约 23GB 权重
≈
还有约 9GB 名义空间
第一篇主要解释的是 NVFP4 的容量价值。
但到了 vLLM 这一篇,NVFP4 还有另一半更重要的意义:
它不只是“存储格式”,还可以直接参与低精度计算。
3. NVFP4:容量只是第一层价值
如果一个 4bit 量化只解决存储,那么执行路径可能是:
4bit Weight
↓
读取 / 解码
↓
反量化到更高精度
↓
再执行通用 GEMM
这依然能节省:
- 权重显存
- Weight Traffic
但真正更有意思的是硬件原生低精度路径:
NVFP4 Weight
+
低精度 Activation / Scale
↓
FP4 适配的 Tensor Core / MMA 路径
↓
NVFP4 GEMM
这时量化同时改变了两件事:
Memory Traffic ↓ + 低精度计算吞吐 ↑
NVIDIA 的 NVFP4 本身就是为 Blackwell 低精度计算设计的格式,而不是单纯“把 FP16 截成 4bit”。
所以:
NVFP4 在 Blackwell 上的价值,不能只用“省了多少 GB 显存”解释。
如果框架真的走到了合适的 FP4 Kernel,它还会改变 GEMM 本身的执行路径。
RTX 5060 Ti 属于 Blackwell,这件事对 NVFP4 很重要
RTX 5060 Ti 属于 Blackwell 消费级 GPU,CUDA Compute Capability 为 12.0,也就是常说的 SM120。
这意味着这张卡并不是“只能保存 NVFP4,然后全部转回旧精度算”。
在合适的软件栈下,它有对应的 Blackwell 低精度计算能力可以利用。
于是可以形成一条完整链路:
RTX 5060 Ti / SM120
↓
NVFP4 Checkpoint
↓
NVFP4-aware Kernel
↓
Blackwell 低精度 Tensor Core 路径
但这里有一个很重要的前提:
模型文件写着 NVFP4,不代表执行时一定走到了最优 NVFP4 Kernel。
这也是我开始真正关心 vLLM Kernel Dispatch 的原因。
“支持 NVFP4”和“NVFP4 跑得快”是两回事
假设两个框架都能成功加载:
Qwen3.8-27B NVFP4
这只能证明格式兼容。
真正执行时,可能存在完全不同的路径。
例如:
路径 A
NVFP4
↓
额外转换 / 反量化
↓
较通用的 GEMM
和:
路径 B
NVFP4
↓
专用 NVFP4 GEMM
↓
适合当前 Blackwell GPU 的 Kernel
模型格式一样,最终性能完全可能不同。
vLLM 当前的 NVFP4 执行路径会根据平台与可用组件选择对应 GEMM backend,例如 FlashInfer / CUTLASS 一类实现,并在不同硬件能力下选择合适的 fallback。
所以现在看量化模型,我会同时问两个问题:
这个框架能不能加载它?
以及:
加载以后,Linear 最终 Dispatch 到哪个 Kernel?
第二个问题对性能往往更重要。
4. 为什么 Kernel 对单流 Decode 特别重要?
一颗 token 的生成不是“一次巨大的 GPU 运算”。
更像是几十层不断重复:
Attention
↓
Linear
↓
Norm
↓
Linear
↓
Activation
↓
Linear
↓
下一层
前面 TP2 的单流实测大约是:
33.5 token/s
也就是一颗 token:
≈ 30ms
在这个时间尺度里,每层多一点:
- 量化转换
- 不合适的 GEMM Shape
- Memory Access
- Kernel Launch
- 同步
乘上几十层以后,就会变成很明显的最终差距。
所以现代 LLM 推理框架真正优化的不是某一个“神奇开关”。
而是:
大量重复的小执行路径能不能全部落到更合适的 Kernel 上。
这也是为什么“模型能加载”离“模型跑得好”还差很远。
5. CUDA Graph:Kernel 快了以后,还要减少 Launch
即使每个 CUDA Kernel 本身已经很快,单 token Decode 还有另一个问题。
普通执行更接近:
CPU → Launch Kernel A
CPU → Launch Kernel B
CPU → Launch Kernel C
CPU → Launch Kernel D
...
GPU 做完一个小任务,CPU 再继续提交后面的工作。
对于非常大的矩阵,这点 Launch Overhead 往往不突出。
但 Decode 每颗 token 都要重复大量小操作。
这时“启动 GPU 工作”本身也开始变得有成本。
CUDA Graph 的思路就是把一串 GPU 操作提前 Capture:
普通模式
CPU → A
CPU → B
CPU → C
CPU → D
变成:
CPU
↓
Replay Graph
↓
A → B → C → D
从而减少重复的 CPU 调度与 Kernel Launch 开销。
所以对单 token Decode 来说:
CUDA Graph 不是锦上添花的小功能,而是很自然的一层低延迟优化。
生产 Serving 的 CUDA Graph 不是简单的 ON / OFF
如果只是跑一个固定 Shape Benchmark,Capture 一张 Graph 很容易理解。
但真正 Serving 时,Batch 是动态的:
有时全是 Decode
有时有 Prefill
有时是 Prefill + Decode 混合
Batch Size 也一直变化
所以实际问题不是:
“支不支持 CUDA Graph?”
而是:
不同 Runtime Batch 到底应该走哪种 Graph 模式?
vLLM 当前把 CUDA Graph 分成多个运行模式,例如:
NONE
PIECEWISE
FULL
FULL_DECODE_ONLY
FULL_AND_PIECEWISE
其中在支持对应能力的 V1 配置里,纯 Decode 可以使用更完整的 Graph,而 Prefill / Mixed Batch 可以走 Piecewise 路径,再由 Runtime 根据当前 Batch 类型选择。
这件事对我来说很重要。
因为 CUDA Graph 已经不再只是:
Benchmark 时手动打开的优化开关
而开始变成:
Serving Runtime 自己的一部分执行策略。
CUDA Graph 上面还有 Compile / Inductor
再往上一层还有:
torch.compile / Inductor
vLLM V1 并不是简单把整个模型丢给一个默认 torch.compile(model) 就结束。
它会围绕自身 Serving 执行方式处理:
- Graph capture / partition
- Compilation cache
- Shape specialization
- Piecewise compilation
- Inductor
- 自定义优化 pass
于是整个执行链可以粗略理解成:
Model
↓
Graph / Compile
↓
Inductor / Custom Pass
↓
Optimized Kernel
↓
CUDA Graph
↓
Runtime Replay
走到这一层以后,再把 vLLM 理解成“一个用了 PagedAttention 的 API Server”已经明显不够了。
它真正做的是:
把模型执行、Kernel、Graph 和 Serving Runtime 连在一起。
6. TP、NVFP4、Kernel、CUDA Graph 应该放在一条链里看
前面几篇是分开讲的:
NVFP4
→ 让权重更小
TP2
→ 两张 GPU 同时参与同一层
Kernel
→ 当前矩阵到底怎么执行
CUDA Graph
→ 减少 Launch / CPU 调度
但生产部署里它们其实是一条链。
以这次双 5060 Ti 为例:
Qwen3.8-27B NVFP4
↓
权重完整 GPU Resident
↓
TP2
↓
两卡同时处理权重分片
↓
NVFP4 / Attention Kernel
↓
Compile
↓
CUDA Graph
任何一层没有走到理想路径,最后的 token/s 都会受到影响。
所以我现在不太喜欢只问:
- NVFP4 能快多少?
- CUDA Graph 能快多少?
- TP 能快多少?
更值得看的问题是:
这几个优化组合以后,整条执行链有没有真正工作。
7. vLLM 的优势最后还会延伸到 Serving 层
GPU 执行链做好以后,生产服务还有另一类问题:
请求 A:正在 Decode
请求 B:100K Prefill
请求 C:刚到达
请求 D:和上一轮共享大量 Prefix
这时单纯让一个 GEMM 再快一点已经不够。
还需要:
- Scheduler
- Continuous Batching
- Chunked Prefill
- Paged Cache / Block 管理
- Prefix Cache
来决定:
GPU 当前这一轮到底应该算谁,以及哪些东西根本不用重新算。
这也是 vLLM 让我更愿意拿来做长期 Serving 的另一半原因。
但这部分我不会在这一篇展开。
因为第六篇会专门讨论:
100K Context 装得下以后,Prefix Cache、Chunked Prefill、Continuous Batching 和 MTP 到底分别解决什么问题。
这里先记住一点就够了:
vLLM 的优化不只发生在单颗 token 的 Kernel 内,也发生在请求之间。
8. 那 llama.cpp 呢?
这并不是说 llama.cpp 没有 GPU Kernel、Batching 或 CUDA Graph 一类优化。
它同样是一套非常成熟、而且极其重要的推理 Runtime。
我认为二者真正的区别,不应该写成:
llama.cpp 慢 / vLLM 快
这种过度简单的判断。
更准确的定位是:
llama.cpp 更擅长把“可部署硬件范围”做得非常宽
例如:
- GGUF
- 大量量化格式
- CPU-only
- CPU + GPU
- RAM Offload
- 异构 GPU
- 不同显存容量
- 本地桌面与个人设备
像 Flash-Next 这种模型,如果个人硬件根本不可能全 GPU Resident,那么 llama.cpp 的 CPU + GPU 路线反而可能是最现实的入口。
vLLM 更适合在“模型可以 GPU Resident”以后继续追求 Serving 效率
例如:
- TP / PP
- 高性能量化 Kernel
- Compile
- CUDA Graph
- Continuous Batching
- Scheduler
- Prefix Cache
- 高吞吐 API Serving
所以我更愿意用一句话概括两者:
llama.cpp 把个人硬件能触碰到的模型上限往上推;vLLM 则在模型能够 GPU Resident 以后,把 NVIDIA GPU Serving 的性能上限继续往上推。
这两个价值并不冲突。
9. 为什么我的 27B 生产部署默认选 vLLM?
回到这套实际硬件:
2 × RTX 5060 Ti 16GB + Qwen3.8-27B
我们已经找到一种方式,让模型能够:
NVFP4
+
完整 GPU Resident
+
给 Cache 留空间
这时候我就没有太强的理由主动把热点权重再搬回 CPU。
我的关注点已经从:
怎么把模型塞进去?
转向:
怎么让两张 GPU 同时工作?
怎么让 NVFP4 走正确 Kernel?
怎么减少 Kernel Launch?
怎么提高 Serving 吞吐?
怎么避免重复 Prefill?
而这些问题正好是 vLLM 的主场。
所以对这类场景,我现在的默认选择是:
先用 vLLM,把完整 GPU Serving 路线跑通。
如果模型大到根本做不到合理 GPU Resident,再回到 llama.cpp / GGUF / RAM Offload 的容量路线。
10. 我的生产推理优化顺序
现在让我重新部署一个 27B 服务,我大概会按这个顺序:
1. 选择模型与可接受的量化
2. 尽量让权重完整 GPU Resident
3. 确认量化格式真的有当前 GPU 的有效计算路径
4. 选择合适的 TP / PP
5. 确认 GEMM / Attention Kernel
6. Compile / CUDA Graph
7. Context / Cache 规划
8. Scheduler / Continuous Batching
9. Prefix Cache
10. 最后再评估 MTP-based Speculative Decoding
其中最重要的变化是:
我已经不把“模型成功启动”当成部署完成。
真正想要的是:
- 模型质量合理
- 权重尽量 GPU Resident
- 低精度硬件能力被正确利用
- Kernel 走在合适路径
- GPU 尽量少空转
- 重复计算尽量少做
- 多卡和多请求都能有效调度
这时候 NVFP4 就不只是“省了几 GB 显存”。
vLLM 也不只是“KV Cache 管得比较好”。
它们最终都属于同一件事:
把有限的 GPU 资源尽可能转化成真正可用的 Token。
接下来就进入剩余显存里最大的一块变量之一:长上下文 Cache。
下一篇:
《200K Context 到底需要多少显存?从 KV Cache 算到 Qwen3.8 的真实部署》
系列导航: ← 上一篇:两张 RTX 5060 Ti 为什么没有两倍推理速度?显存带宽、PCIe 与 P2P · 下一篇:200K Context 到底需要多少显存?从 KV Cache 算到 Qwen3.8 的真实部署 →