<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[4090 48G 实测：Qwen3.8-27B 从 FP8+MTP 换成 W4A16+DFlash2，代码解码 76 → 245 tok/s]]></title><description><![CDATA[<p dir="auto">折腾了一整天，把单卡 4090 48G 上的 Qwen3.8-27B 部署从 SGLang 0.5.16 + 官方 FP8 换到了 0.5.19 + W4A16 + DFlash2。一共跑了七组对照，（这是按照论坛大神的作业抄的，让claudecode部署并且做了矩阵测试选定参数）。</p>
<p dir="auto"><strong>结论先放</strong>：W4A16 单独换是<strong>负优化</strong>，比 FP8 慢 35%；但配上 DFlash2 之后代码解码从 76 涨到 245 tok/s。这两件事是一体的，分开看会得出完全相反的结论。</p>
<h2>1. 硬件与环境</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>项目</th>
<th>配置</th>
</tr>
</thead>
<tbody>
<tr>
<td>GPU</td>
<td>RTX 4090 48G（sm_89）</td>
</tr>
<tr>
<td>系统</td>
<td>Ubuntu 24.04</td>
</tr>
<tr>
<td>框架</td>
<td>SGLang 0.5.19</td>
</tr>
<tr>
<td>用途</td>
<td>agent、编程、工具调用、扫描件 OCR</td>
</tr>
</tbody>
</table>
<h2>2. 改动前后</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>项目</th>
<th>改动前</th>
<th>改动后</th>
</tr>
</thead>
<tbody>
<tr>
<td>SGLang</td>
<td>0.5.16</td>
<td>0.5.19</td>
</tr>
<tr>
<td>主权重</td>
<td>官方 FP8（29G）</td>
<td>W4A16（19.6G）</td>
</tr>
<tr>
<td>投机解码</td>
<td>MTP</td>
<td>DFlash2</td>
</tr>
<tr>
<td>KV</td>
<td>FP8</td>
<td>FP8</td>
</tr>
<tr>
<td>mem-fraction</td>
<td>0.93</td>
<td>0.75</td>
</tr>
</tbody>
</table>
<p dir="auto">主权重是 <code>philbert440/Qwen3.8-27B-W4A16-AWQ</code>，draft 是 <code>tcclaviger/Qwen3.8-27B-DFlash2-FP8</code>（2.12G，比 BF16 版省 1.5G）。</p>
<h2>3. 性能对比</h2>
<p dir="auto">同 prompt、temperature=0、同上下文，单位 tok/s：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>任务</th>
<th>FP8+MTP</th>
<th>W4A16+DFlash2</th>
</tr>
</thead>
<tbody>
<tr>
<td>代码生成</td>
<td>75.7</td>
<td><strong>245.6</strong></td>
</tr>
<tr>
<td>工具调用</td>
<td>91.5</td>
<td><strong>239.9</strong></td>
</tr>
<tr>
<td>数学（thinking）</td>
<td>64.9</td>
<td><strong>167.4</strong></td>
</tr>
<tr>
<td>中文散文</td>
<td>47.3</td>
<td><strong>56.3</strong></td>
</tr>
<tr>
<td>双并发聚合</td>
<td>136</td>
<td><strong>349</strong></td>
</tr>
</tbody>
</table>
<p dir="auto">长上下文 prefill 是反过来的：8.5 万 token 冷 prefill，FP8 是 32.2 秒，W4A16 是 41 秒，<strong>慢 27%</strong>。W4A16 的反量化开销在 prefill 阶段躲不掉。</p>
<p dir="auto">质量项两边都过：8.5 万 token 大海捞针三项全中，工具调用 <code>finish_reason=tool_calls</code> 参数正确，A4 300dpi 扫描件 VLM 识别四项全中。<strong>速度差异不伴随质量退化</strong>。</p>
<h2>4. 为什么 W4A16 单独换反而更慢</h2>
<p dir="auto">这是我觉得最值得分享的一点。先看中间那组数据：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>组合</th>
<th>代码解码</th>
</tr>
</thead>
<tbody>
<tr>
<td>FP8 + MTP</td>
<td>75.7</td>
</tr>
<tr>
<td>W4A16 + MTP</td>
<td><strong>48.9</strong></td>
</tr>
<tr>
<td>W4A16 + DFlash2</td>
<td>245.6</td>
</tr>
</tbody>
</table>
<p dir="auto">W4A16 配 MTP 是全场最差。原因是 <strong>4090 有原生 FP8 Tensor Core</strong>，FP8 权重几乎零反量化开销；而 W4A16 要走 Marlin 把 int4 解包成 BF16。</p>
<p dir="auto">算一下显存带宽账（4090 是 1008 GB/s）就很清楚：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>权重</th>
<th>理论上限</th>
<th>实测</th>
</tr>
</thead>
<tbody>
<tr>
<td>W4A16（17.75G）</td>
<td>51 tok/s</td>
<td>48.9</td>
</tr>
<tr>
<td>FP8（28.5G）</td>
<td>35 tok/s</td>
<td>75.7</td>
</tr>
</tbody>
</table>
<p dir="auto">W4A16 贴着带宽上限跑，说明 <strong>MTP 在它身上基本没产生收益</strong>；FP8 跑到上限的 2.2 倍，说明 MTP 收益很大。</p>
<p dir="auto">DFlash2 改变了这个局面。<strong>投机解码的验证阶段是一次 batch=8 的 forward，要把整个主模型权重读一遍</strong>，所以权重体积直接决定验证频率上限：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>权重</th>
<th>验证频率上限</th>
<th>实测吞吐</th>
</tr>
</thead>
<tbody>
<tr>
<td>W4A16（17.75G）</td>
<td>56.8 次/s</td>
<td>245.6</td>
</tr>
<tr>
<td>FP8（28.5G）</td>
<td>35.4 次/s</td>
<td>156.4</td>
</tr>
</tbody>
</table>
<p dir="auto">理论比值 1.60，实测 1.51，对得上。<strong>投机解码把"读一遍权重"的成本摊到 5~6 个 token 上，权重越小越快，反量化开销也一起被摊薄了。</strong> 没有投机解码时每次 forward 只出 1 个 token，开销无处可摊，W4A16 就成了最慢的。</p>
<p dir="auto">我还测了 FP8 + DFlash2，代码 156.4 tok/s，比 W4A16 + DFlash2 慢 34%，正好印证这条规律。</p>
<h2>5. DFlash2 对中文收益很小</h2>
<p dir="auto">服务端 <code>accept len</code> / <code>accept rate</code> 直接说明问题（block_size=8）：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>场景</th>
<th>accept rate</th>
<th>实测吞吐</th>
</tr>
</thead>
<tbody>
<tr>
<td>结构化输出</td>
<td>0.57~0.76</td>
<td>152~247</td>
</tr>
<tr>
<td>中文自由写作</td>
<td><strong>0.11</strong></td>
<td>57</td>
</tr>
</tbody>
</table>
<p dir="auto">代码 245 对中文 56，差 4.3 倍。<strong>如果主力用途是中文长文写作而不是 agent / 编程，DFlash2 不值得上</strong>，FP8 + DFlash2 那组的中文甚至只有 30.4 tok/s，比完全不用投机解码的 47.3 还慢 36%。</p>
<h2>6. 几个坑</h2>
<h3>6.1 HiCache 的 L3 必须配 page-size 64</h3>
<p dir="auto">默认 <code>page_size=1</code> 时，L3 文件后端的 key 粒度细到每个 token 一条。我这边写了 15.4G、40 万个文件，但 <code>#cached-token</code> 恒为 0，<strong>从来没命中过一次</strong>。线上那个 hicache 目录一度涨到 440G，全是白写的。</p>
<p dir="auto">改成 64 之后跨重启命中 82%，TTFT 从 24.5 秒掉到 5.8 秒，落盘量反而降到 2.7G。约束是 <code>mamba_track_interval</code>（默认 256）必须能被它整除。</p>
<h3>6.2 改 KV dtype 不清缓存会静默输出错误答案</h3>
<p dir="auto"><strong>这个最阴险。</strong> L3 文件后端跨重启保留，而它的 key 不包含 <code>kv_cache_dtype</code>。我把 BF16 KV 换成 FP8 KV 后没清缓存，结果：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>项目</th>
<th>脏缓存</th>
<th>清空后</th>
</tr>
</thead>
<tbody>
<tr>
<td>工具调用</td>
<td>finish=stop，0 个调用</td>
<td>正常，5 个</td>
</tr>
<tr>
<td>大海捞针</td>
<td>三项全错</td>
<td>三项全中</td>
</tr>
<tr>
<td>捞针 TTFT</td>
<td>1.8 秒</td>
<td>41 秒</td>
</tr>
</tbody>
</table>
<p dir="auto"><strong>服务健康、不报错、不崩溃，但答案是错的。</strong> 我一开始以为是 FP8 KV 精度不行，差点否掉整个方案。救回来的线索是 TTFT——8.5 万 token 的 prefill 不可能 1.8 秒，那必然是假命中。</p>
<p dir="auto">所以换权重或改 KV dtype 之后，一定要清掉 hicache 目录。</p>
<h3>6.3 0.5.19 会吃掉一块 KV 预算</h3>
<p dir="auto">同参数从 0.5.16 升到 0.5.19，<code>max_total_num_tokens</code> 从 141673 掉到 114983。启动日志里能看到原因：</p>
<pre><code class="language-text">Mamba Cache: ssm_state 2.67GB
           + intermediate_ssm_state_cache 1.69GB   ← 0.5.19 新增
           + intermediate_conv_window_cache 0.02GB
</code></pre>
<p dir="auto">新增的中间状态缓存 1.71G 直接从 KV 预算扣。把 <code>--max-mamba-cache-size</code> 从 18 降到 14 就回来了。</p>
<p dir="auto">注意两条弯路：关掉 prefill CUDA graph 省了 1.3G 显存但 <strong>KV 一个 token 都没涨</strong>（KV 池是按 mem-fraction 预先算定的，不吃剩余显存）；把 mem-fraction 提到 0.95 直接在 KV 分配阶段 OOM。</p>
<h3>6.4 CUDA 工具链版本要对齐</h3>
<p dir="auto"><code>uv pip install sglang[all]==0.5.19</code> 会带进 nvcc 13.4.59，而 runtime 头文件是 13.0.96。启动时 FlashInfer JIT 编到第 8 个内核直接报错：</p>
<pre><code class="language-text">cuda/std/__cccl/cuda_toolkit.h:41: error: "CUDA compiler and CUDA toolkit headers
are incompatible, please check your include paths"
</code></pre>
<p dir="auto">把三个包钉回一致版本就好：</p>
<pre><code class="language-bash">uv pip install 'nvidia-cuda-nvcc==13.0.88' 'nvidia-cuda-crt==13.0.88' 'nvidia-nvvm==13.0.88'
rm -rf ~/.cache/sglang/.cache/flashinfer
</code></pre>
<p dir="auto"><strong>验这个坑不能用裸 nvcc 编译 <code>cuda_runtime_api.h</code></strong>，那样能通过、会得出"工具链没问题"的错误结论。真正的判据在 FlashInfer 自带的 CCCL 头文件里，它对 <code>__CUDACC_VER__</code> 和 <code>CUDART_VERSION</code> 做严格一致性检查。</p>
<p dir="auto">另外 0.5.19 换了 JIT 缓存路径，是 <code>~/.cache/sglang/.cache/flashinfer</code>，不再是 <code>~/.cache/flashinfer</code>。</p>
<h3>6.5 单并发省不出显存</h3>
<p dir="auto">想给同卡上的 OCR 腾显存，我试过降并发，结果<strong>只省了 16 MiB</strong>。</p>
<p dir="auto"><code>mem-fraction-static</code> 是总量阀门，先圈定框架能用多少显存；降并发省下的 mamba cache 会<strong>原封不动转给 KV 池</strong>。要给 GPU 上其它进程腾地方，唯一有效的杠杆就是 mem-fraction。</p>
<p dir="auto">顺带一提，FP8 KV 比 BF16 KV 更划算：同样容量只要一半显存，于是 mem-fraction 能从 0.85 压到 0.75，而 KV 容量反而从 155840 涨到 199680，性能一分不损。</p>
<h2>7. 最终配置</h2>
<pre><code class="language-bash">--model-path Qwen3.8-27B-W4A16-AWQ \
--speculative-algorithm DFLASH \
--speculative-draft-model-path Qwen3.8-27B-DFlash2-FP8 \
--speculative-num-draft-tokens 8 \
--kv-cache-dtype fp8_e4m3 \
--mem-fraction-static 0.75 \
--context-length 131072 \
--max-running-requests 2 \
--max-mamba-cache-size 14 \
--page-size 64 \
--enable-hierarchical-cache --hicache-size 32 \
--hicache-storage-backend file \
--disable-prefill-cuda-graph
</code></pre>
<p dir="auto">KV 容量 199680，空载显存 38.4G，留了 10G 给同卡上的 OCR，实测两者可以并存不互相挤爆。</p>
<h2>8. 适用场景提醒</h2>
<p dir="auto">这套配置的收益高度集中在<strong>结构化输出</strong>上，选之前先看自己的负载：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>主力用途</th>
<th>建议</th>
</tr>
</thead>
<tbody>
<tr>
<td>agent / 编程 / 工具调用</td>
<td>W4A16 + DFlash2，收益 3 倍以上</td>
</tr>
<tr>
<td>中文长文写作</td>
<td>收益只有 20%，不如省事用 FP8</td>
</tr>
<tr>
<td>长文档一次性灌入</td>
<td>FP8 更好，prefill 快 27%</td>
</tr>
</tbody>
</table>
<p dir="auto">另外 256K 原生上下文我也试了，能跑满，但要 <code>mem-fraction-static 0.99</code> + 单路并发，24 万 token 的请求 TTFT 是 157 秒。不建议做常驻档。</p>
]]></description><link>https://lcz.me/topic/1712</link><generator>RSS for Node</generator><lastBuildDate>Fri, 23 Oct 2026 13:08:00 GMT</lastBuildDate><atom:link href="https://lcz.me/topic/1712.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 15 Sep 2026 03:24:23 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to 4090 48G 实测：Qwen3.8-27B 从 FP8+MTP 换成 W4A16+DFlash2，代码解码 76 → 245 tok/s on Tue, 15 Sep 2026 19:22:37 GMT]]></title><description><![CDATA[<p dir="auto">这个作业抄的太晚了，大家早就抄完了.....</p>
]]></description><link>https://lcz.me/post/18444</link><guid isPermaLink="true">https://lcz.me/post/18444</guid><dc:creator><![CDATA[terry]]></dc:creator><pubDate>Tue, 15 Sep 2026 19:22:37 GMT</pubDate></item><item><title><![CDATA[Reply to 4090 48G 实测：Qwen3.8-27B 从 FP8+MTP 换成 W4A16+DFlash2，代码解码 76 → 245 tok/s on Tue, 15 Sep 2026 04:03:13 GMT]]></title><description><![CDATA[<p dir="auto">补两点，帮你把「W4A16 + DFlash2 是一体的」这个结论做硬：</p>
<ol>
<li>W4A16 单独变慢是合理的：batch=1 的 decode 是访存瓶颈，权重从 29G 降到 19.6G，但每步多了一次 dequant；如果 kernel 没把 dequant 融进 GEMV，省下的带宽会被反算开销吃回去，负优化正常。</li>
<li>245 tok/s 对 27B dense 单卡，已经超过 4090 显存带宽的纯自回归上限（约 1TB/s ÷ 19.6G ≈ 50 tok/s 量级）。所以这个数字一定来自「每步出多个 token」——MTP/投机解码/并行验证，而不是单 token 解码变快了。真正把吞吐拉起来的是 DFlash2 的接受率和每步 token 数，W4A16 只是帮它把权重带宽腾出来。</li>
</ol>
<p dir="auto">想坐实因果，建议再补三组：</p>
<ul>
<li>FP8 + DFlash2：把 DFlash2 单独拉出来，看它是净增益，还是只补回了 W4A16 的坑；</li>
<li>W4A16 + DFlash2 但关掉 MTP/投机，看纯 decode 基线；</li>
<li>记录每步接受的 draft token 数 / acceptance length，和 tok/s 对得上才算闭环。</li>
</ul>
<p dir="auto">你那七组里有没有 FP8+DFlash2 这组？有的话贴出来，结论会更清楚。</p>
]]></description><link>https://lcz.me/post/18279</link><guid isPermaLink="true">https://lcz.me/post/18279</guid><dc:creator><![CDATA[Xiaote]]></dc:creator><pubDate>Tue, 15 Sep 2026 04:03:13 GMT</pubDate></item></channel></rss>