📚 双 RTX 5060 Ti 部署 Qwen3.8 系列 · 6/6
💡 导读:
100K Context 能装下,不代表 100K Context 真正用起来就快。对长代码 Agent 来说,真正值得优先优化的是重复 Prefill、长 Prompt 对 Decode 的阻塞,以及多请求之间的 GPU 利用率;MTP 则属于更靠后的 Decode 优化。
前一篇最终把双 RTX 5060 Ti 的长上下文配置落在了:
2 × RTX 5060 Ti 16GB
Qwen3.8-27B NVFP4
TP2
FP8 KV
CUDA Graph ON
100K Context
Maximum Concurrency ≈ 1.07x
到这里,显存问题基本解决了。
但“能够容纳 100K Context”和“100K Context 真正用起来足够快”,其实是两个问题。
对于代码 Agent,一次请求经常包含:
- System Prompt
- Tool Definitions
- 项目规则
- Repository Context
- 历史对话
- Tool Result
- 当前用户输入
其中大量内容在连续几轮请求里几乎不会变化。
如果每一轮都重新 Prefill 几十 K、甚至接近 100K Token,那么即使 Decode 已经有 30 多 token/s,整体体验仍然可能很慢。
所以长 Context 解决容量以后,我真正开始关心的是另外三个问题:
重复内容能不能不重新计算?
超长 Prefill 会不会阻塞正在 Decode 的请求?
多个请求能不能更高效地共享 GPU?
它们分别对应:
- Prefix Cache
- Chunked Prefill
- Continuous Batching
至于 MTP,它解决的是更后面的 Decode 串行问题。
这几个功能经常一起出现在“LLM 推理加速”列表里,但实际优化的是完全不同的阶段。
对于长代码 Agent,我现在的优先级非常明确:
Prefix Cache
↓
Chunked Prefill / Scheduler
↓
Continuous Batching
↓
最后才评估 MTP
1. Prefill 和 Decode 是两种完全不同的负载
一次 LLM 请求大致可以拆成:
Prompt
↓
Prefill
↓
第一个输出 Token
↓
Decode
↓
Decode
↓
...
Prefill 一次处理大量输入 Token。
如果 Prompt 是 100K,那么模型首先要把这 100K Token 处理一遍,建立 Attention KV、Linear Attention State 等运行状态。
这个阶段矩阵规模较大,通常更偏 Compute-heavy,也更容易把 Tensor Core 利用起来。
Decode 则不同。
每一步只新增一颗 Token,但它仍然要经过整个模型。
单流 Decode 更容易受到:
- Weight Traffic
- 显存带宽
- 小 GEMM
- Kernel Launch
- TP Collective
- Scheduler
影响。
所以:
Prefill 快,不代表 Decode 快;Decode 很快,也不能解决一个 100K Prompt 首次加载很慢的问题。
后面的优化,第一步就是先分清楚自己到底在优化哪一段。
2. Prefix Cache:先把重复 Prefill 消掉
假设第一次请求是:
| 内容 | Token |
|---|---|
| System Prompt | 5K |
| Tool Definitions | 5K |
| Repository Context | 80K |
| History | 5K |
| User Prompt | 5K |
| 总计 | 100K |
第一次没有办法,这 100K 基本都要经历 Prefill。
但 Agent 完成一次 Tool Call 后,第二轮请求很可能只是末尾发生变化:
System Prompt 5K ← 不变
Tool Definitions 5K ← 不变
Repository Context 80K ← 大部分不变
History 8K
Tool Result 3K
如果没有 Prefix Cache,第二轮还是要从头处理整段 Prompt。
第三轮又重新来一次。
Prefix Cache 的思路是:
已经计算过、并且 Token Prefix 完全相同的部分,直接复用对应的 Cache Block / 状态。
例如:
100K Prompt
其中 90K Prefix 已命中
↓
真正重新 Prefill
≈ 10K
这带来的收益不是:
“把 100K Prefill 优化 20%。”
而更接近:
90K Token 这次根本不重新计算。
对于长 Context Agent,这通常比继续挤一点 Kernel 性能更有价值。
它改善的是 TTFT,不是 Decode TPS
假设当前单流 Decode 是:
≈ 33.5 token/s
打开 Prefix Cache 以后,即使命中了 90K Prefix,也不会因此让后面的 Decode 自动变成 60 token/s。
已经进入 Decode 阶段以后,每生成一颗 Token,该走的模型计算仍然要走。
Prefix Cache 节省的是进入 Decode 之前的重复 Prefill。
所以它最明显影响的是:
TTFT——Time To First Token。
测试 Prefix Cache 时,我更关心:
- TTFT
- Cached Tokens
- 实际重新 Prefill 的 Token 数
- Prefix Cache Hit Rate
而不是只看 Decode token/s。
代码 Agent 天生适合 Prefix Cache,Prompt Layout 也会直接影响命中率
普通聊天历史会持续变化。
代码 Agent 则经常有一大段非常稳定的 Context:
- System Prompt
- Tool Schema
- 项目规则
- AGENTS.md / CLAUDE.md 一类规则文件
- Repository 文件
- 大量已经加载的代码
- 长时间不变的环境说明
真正经常变化的通常只是末尾:
- 当前 User Prompt
- Tool Result
- 新生成的 Assistant 内容
这意味着代码 Agent 天然具有很高的 Prefix 重复率。
假设总 Prompt 是 100K,其中 80K~90K 能稳定命中,那么真正需要重新 Prefill 的可能只有最后一小段。
但 Prefix Cache 不是语义缓存。
两段 Prompt “意思一样”,不代表能够命中。它要求前面的 Token Prefix 真正一致。
例如:
当前时间:10:01
这里是 80K 项目代码……
下一轮变成:
当前时间:10:02
这里是同样的 80K 项目代码……
虽然后面的 Repository Context 完全没变,但 Prefix 在很靠前的位置已经不同。
因此我更倾向于让 Agent Prompt 按稳定程度排列:
最稳定
↓
System Prompt
↓
Tool Definitions
↓
固定规则
↓
Repository Context
↓
历史内容
↓
Tool Result
↓
当前 User Input
↓
最动态
而不是把当前时间、随机 ID、动态状态放在巨大固定 Context 的前面。
对于 100K 代码上下文:
Prompt Layout 已经不只是 Prompt Engineering,也属于 Serving Optimization。
一个小动态字段放错位置,可能直接让后面几十 K Token 的缓存价值消失。
不过对 Qwen3.8-27B 还要多加一个 Hybrid Model 的前提。
它不是纯 Full Attention,而是:
48 × Gated DeltaNet + 16 × Full Attention
因此 vLLM 做 Prefix Cache 时,不只是管理传统 Full-Attention KV Block,还要处理 Linear Attention 的 recurrent state / state checkpoint。
这意味着 Hybrid Model 的缓存命中粒度、State 保留位置和 Block 对齐,会比纯 Transformer 更复杂。
所以上面:
100K Prompt / 90K Prefix Hit
应该理解成一个便于建立直觉的例子,而不是“只要前 90K Token 相同,就一定能以任意粒度完整命中”。
真实部署里,我会直接看当前 vLLM 版本实际报告的:
- Cached Tokens
- Prefix Cache Hit Rate
- TTFT
来判断 Hybrid Prefix Cache 到底复用了多少计算。
Prefix Cache 不是无限容量,多租户还要考虑隔离
Prefix Cache 最终仍然占用实际 Cache Pool。
假设当前同时存在:
Session A:80K Prefix
Session B:90K Prefix
Session C:70K Prefix
Session D:100K Prefix
...
消费级 GPU 不可能永久保留所有 Prefix。
当 Cache Pool 需要空间给新的请求时,旧 Block 最终会被淘汰和复用。
所以 Prefix Cache 的真实收益会受到:
- Cache Capacity
- 活跃 Session 数量
- Prefix 大小
- 请求是否集中在同一个项目
- 请求局部性
影响。
这也是为什么它特别适合我的这类 workload:
长时间围绕同一个 Repository 连续工作。
如果变成几百个用户,每个人都有完全不同的 100K Prefix,命中率自然会下降。
另外,如果是公网多租户 API,还要考虑 Cache 隔离和 Timing Side Channel。
vLLM 提供 cache_salt 一类机制,让不同租户可以控制 Prefix Cache 的共享边界。
个人 Coding Agent 不一定需要重点考虑这一点,但正式多租户服务不能完全忽略。
3. Chunked Prefill:Cache Miss 时别让长 Prompt 霸占 GPU
Prefix Cache 很强,但它解决不了第一次请求。
第一次打开一个大型 Repository:
100K New Prompt
没有旧 Prefix 可以复用,完整 Prefill 还是必须发生。
问题随之变成:
一个 100K Prefill 会不会长时间霸占 GPU?
假设:
Request A
已经进入 Decode,正在持续输出
Request B
突然进来一个 100K Prompt
如果 B 一次性把整个 Prefill 做完,再让 A 继续 Decode,那么 A 的 Inter-Token Latency 很可能突然升高。
用户看到的现象就是:本来流式输出很顺,突然卡了一段时间。
Chunked Prefill 会把一个很大的 Prefill 拆成多个 Chunk:
100K Prefill
↓
Chunk 1
Chunk 2
Chunk 3
Chunk 4
...
Scheduler 就有机会在这些 Chunk 之间继续安排其他 Decode 工作。
更接近:
Decode A
+
Prefill B 的一部分
↓
Decode A
+
Prefill B 的下一部分
而不是:
先让 B 完整 Prefill 100K
↓
A 一直等
↓
B 算完以后 A 再继续
vLLM V1 的调度思路本身就会优先照顾正在 Decode 的 Sequence,再把剩余 Token Budget 分给 Prefill;Prefill 太大时就拆 Chunk。
这对 Serving 很有价值,因为 Prefill 和 Decode 的资源特征并不完全相同:
Prefill
更偏大矩阵 / Compute-heavy
Decode
更偏 Weight Traffic / Memory-bound
把两种 workload 更合理地交错调度,有机会同时改善用户延迟和 GPU 利用率。
max_num_batched_tokens:本质上是 TTFT、ITL 和吞吐的交换
Chunked Prefill / Scheduler 有一个很重要的 Token Budget 概念:
max_num_batched_tokens
可以粗略理解成:
一次 Scheduler Iteration 最多允许处理多少 Token。
如果 Budget 较小,大型 Prefill 会被切成更多 Chunk。
优点是:
- Decode 更容易持续插入
- ITL 往往更友好
- 长 Prefill 不容易长时间阻塞其他请求
代价则是:
- Prefill 被切得更碎
- 调度次数更多
- 单个大 Prompt 完成 Prefill 可能更慢
反过来,Budget 更大时,一轮可以吃下更多 Prefill Token,通常更有利于大 Prompt 吞吐和 TTFT,但也可能让其他 Decode 等得更久。
所以它不是“越大越好”,也不是“越小越低延迟就一定最好”。
它本质上是在三项指标之间做取舍:
TTFT
vs
ITL
vs
Aggregate Throughput
个人单用户 Agent 和高并发 API,最优值很可能不一样。
这类参数最终还是应该用真实 workload Benchmark,而不是只看一个离线吞吐数字。
4. Continuous Batching:把多个请求持续填进 GPU
假设同时有三个请求:
A:生成 100 TokenB:生成 500 TokenC:生成 50 Token
如果它们被绑定成一个传统静态 Batch,C 很快结束以后,它的位置可能空着;A 随后也结束,最后只剩 B 一直跑。
Batch 利用率会不断降低。
Continuous Batching 的思路是:
A B C
C 完成
↓
D 加入
A B D
A 完成
↓
E 加入
E B D
新的 Request 可以持续进入正在运行的 Batch,完成的 Request 及时退出。
它主要改善的是:
多请求 Aggregate Throughput 和 GPU 利用率。
所以和 Prefix Cache 一样,不能拿错指标。
打开 Continuous Batching 后,一条请求原来是 33.5 token/s,并不意味着它一定会突然变成 50、60 token/s。
真正应该看的,是多条请求同时存在时:
整张 GPU 每秒总共能够完成多少有效 Token,以及请求吞吐能不能持续保持。
Prefix Cache、Chunked Prefill 和 Continuous Batching 是一起工作的
真实 Agent Serving 很可能同时出现三种请求。
Request A
- 已有 90K Prefix Cache
- 只新增 5K
Request B
- 第一次进入系统
- 需要完整 Prefill 100K
Request C
- 已经完成 Prefill
- 正在 Decode
这时候三种机制刚好各自解决一个问题。
A:Prefix Cache
90K 不再重复计算。
B:Chunked Prefill
100K 不一次霸占整个调度周期。
C:Continuous Batching / Scheduler
继续参与 Decode,不需要等其他请求全部处理完。
所以我更喜欢把它们记成三句话:
Prefix Cache:重复的别再算。
Chunked Prefill:太长的别一次算完。
Continuous Batching:不同请求尽量一起算。
这三项放到一起,才真正体现出 Serving Runtime 和“单条请求模型推理程序”的区别。
5. MTP 用到 Speculative Decoding 里,才真正开始减少 Decode 串行性
前面的优化主要集中在:
Prefill
+
Scheduler
+
多请求利用率
MTP 则是另一条路线。
传统自回归 Decode:
Token N
↓
Forward
↓
Token N+1
↓
Forward
↓
Token N+2
一次 Target Forward 通常只让最终输出前进一颗 Token。
MTP 和 Speculative Decoding 其实不是同一个概念。
MTP 提供的是“预测后续多颗 Token”的能力;在推理阶段把 MTP Head 当成 Speculative Proposer,再由 Target Model 验证这些候选,才形成真正的 speculative decoding 路径:
MTP Proposal
↓
Target Verification
↓
接受若干 Token
如果一次能够稳定接受多颗 Token,就可能减少平均每颗最终 Token 所需要的串行 Decode Step。
所以它真正优化的是:
Decode 串行深度和 ITL。
但它不是一个:
ON = 固定 +30% TPS
的开关。
实际收益更接近:
节省的 Target Decode Step
-
Proposal 开销
-
Verification 开销
=
最终收益
最关键的运行指标包括:
- Acceptance Rate
- Mean Accepted Tokens / Step
而且 Serving 还要考虑:
- Tool Calling
- Structured Output
- Sampling 配置
- 长 Context
- Kernel / CUDA Graph 兼容性
- 当前模型与框架版本的稳定性
所以我没有把 MTP 放进这套部署的默认基础配置。
不是因为它理论上“用质量换速度”——标准 Speculative Decoding 本来就会让 Target Model 做验证。
而是因为它属于:
模型、Cache、TP、Kernel、CUDA Graph 和 Scheduler 都已经工作以后,再单独 A/B Benchmark 的高级优化。
对于我的长代码 Agent workload,前面几个问题的优先级明显更高。
尤其如果一次请求是:
Prompt:100KOutput:2K
MTP-based Speculative Decoding 能优化的是后面的 2K Decode。
如果下一轮有 90K 相同 Prefix,Prefix Cache 则可以直接让 90K 重复 Prefill 不再计算。
所以我的判断一直是:
先解决“不必要的计算”,再解决“剩下的计算怎么更快”。
6. 做到这一层以后,Benchmark 不能再只看 token/s
只记录:
token/s
已经明显不够了。
至少应该把性能拆成四类。
TTFT
Time To First Token
主要受:
- Prefill
- Prefix Cache
- Scheduler
影响。
ITL
Inter-Token Latency
主要反映流式输出是否顺滑,会受到:
- Decode
- TP
- Kernel
- CUDA Graph
- Scheduler
- MTP
影响。
Aggregate Throughput
Total Output Tokens / Second
更适合观察:
- Continuous Batching
- Scheduler
- 并发
- Batch 利用率
Prefix Cache Hit Rate / Cached Tokens
对长 Context Agent 来说,这甚至可能成为最值得长期记录的指标之一。
如果每轮都能稳定命中 80%、90% 甚至更高,那么系统真实成本和“每次完整 Prefill 100K”已经完全不是一回事。
7. 双 5060 Ti 的最终优化顺序
回到整个系列一直使用的这套部署:
2 × RTX 5060 Ti 16GB
Qwen3.8-27B NVFP4
TP2
FP8 KV
CUDA Graph ON
100K Context
前几篇已经分别解决了:
NVFP4
→ 让权重完整 GPU Resident
TP2
→ 聚合双卡计算与显存带宽
PCIe / P2P 分析
→ 判断 TP 的通信成本是否值得
Kernel / CUDA Graph
→ 降低模型执行开销
FP8 KV
→ 把 100K Context 放进显存
到了最后一层,我真正优先做的是:
Prefix Cache
→ 减少长代码重复 Prefill
Chunked Prefill
→ Cache Miss 时避免长 Prompt 阻塞 Decode
Continuous Batching
→ 多个 Agent / Session 共享 GPU 时提高总吞吐
MTP 则保留成可选项:
只有实际 A/B Benchmark 证明 Acceptance、稳定性和功能兼容性都足够好,才值得加入默认配置。
它不是这套部署架构成立的前提。
8. 结论
大模型推理不存在一个统一的“加速开关”。
不同优化对应不同阶段:
| 优化 | 主要解决的问题 |
|---|---|
| Prefix Cache | 避免重复 Prefill |
| Chunked Prefill | 长 Prefill 不要一次阻塞整个调度 |
| Continuous Batching | 多请求持续填充 GPU |
| MTP-based Speculative Decoding | 减少 Decode 串行 Step |
对于长 Context 代码 Agent,我最终更认可这样的顺序:
先减少不必要的计算
↓
再改善 GPU 调度
↓
再提高多请求利用率
↓
最后再优化自回归 Decode
也就是:
Prefix Cache
>
Chunked Prefill / Scheduler
>
Continuous Batching
>
MTP
这个系列从最开始的问题:
“两张 5060 Ti 能不能跑 27B?”
一路走到了:
模型精度
↓
显存容量
↓
多卡并行
↓
跨卡通信
↓
Kernel / CUDA Graph
↓
长 Context Cache
↓
Prefix / Scheduler / Decode 优化
到最后,我觉得真正值得记住的已经不是某一个启动参数,而是一种部署思路:
在可接受的模型质量下,让有限的显存、显存带宽和计算能力尽可能花在真正需要重新计算的 Token 上。
这也是我这一周折腾双 RTX 5060 Ti + Qwen3.8-27B 以后,最终得到的最有价值的结论。
系列导航: ← 上一篇:200K Context 到底需要多少显存?从 KV Cache 算到 Qwen3.8 的真实部署