<?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[Conatus AI Engineering]]></title><description><![CDATA[Conatus AI Engineering]]></description><link>https://conatusai.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Conatus AI Engineering</title><link>https://conatusai.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 21 Sep 2026 08:07:20 GMT</lastBuildDate><atom:link href="https://conatusai.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[SGLang outputs endless repetition on NVFP4 models: the FP8 lm_head bug]]></title><description><![CDATA[Your NVFP4 model serves fine on vLLM but outputs an endlessly repeated phrase on SGLang, from the very first token, even on a trivial prompt. The response content comes back empty, every request ends ]]></description><link>https://conatusai.hashnode.dev/sglang-outputs-endless-repetition-on-nvfp4-models-the-fp8-lm-head-bug</link><guid isPermaLink="true">https://conatusai.hashnode.dev/sglang-outputs-endless-repetition-on-nvfp4-models-the-fp8-lm-head-bug</guid><category><![CDATA[SGLang]]></category><category><![CDATA[llm]]></category><dc:creator><![CDATA[Jahn]]></dc:creator><pubDate>Wed, 26 Aug 2026 04:32:44 GMT</pubDate><content:encoded><![CDATA[<p>Your NVFP4 model serves fine on vLLM but outputs an endlessly repeated phrase on SGLang, from the very first token, even on a trivial prompt. The response <code>content</code> comes back empty, every request ends with <code>finish_reason: length</code>, and reasoning traces look like this:</p>
<pre><code class="language-plaintext">need analysis there need analysis there need analysis there ...
</code></pre>
<p>If that matches, check the server load log for one line:</p>
<pre><code class="language-plaintext">Parameter lm_head.weight_scale not found in params_dict
</code></pre>
<p>That warning plus degenerate repetition is a specific, known failure with a specific fix. This post is the short version of an A/B verification I ran on a single RTX PRO 6000 Blackwell (SM120, 96 GB).</p>
<h2>What is actually broken</h2>
<p>Mixed-precision compressed-tensors checkpoints such as <code>unsloth/Qwen3.8-27B-NVFP4</code> quantize most of the network to NVFP4 but keep a few parts, including <code>lm_head</code>, in FP8 W8A8 with per-channel weight scales. The quant config names the head explicitly with a <code>re:.*lm_head</code> target.</p>
<p>SGLang's <code>CompressedTensorsConfig.get_quant_method()</code> used to dispatch only <code>LinearBase</code> and <code>FusedMoE</code>. <code>ParallelLMHead</code> fell through, so the head loaded via the unquantized embedding path and the FP8 <code>weight_scale</code> tensor was never applied. Raw FP8 weights (values up to around 448) were consumed as if they were bf16. With per-channel scales averaging 1.55e-4 and a 12x row-to-row spread, the logit ranking is corrupted badly enough that greedy decoding locks into a repetition loop immediately.</p>
<p>The same checkpoint works on vLLM because vLLM fixed the identical gap earlier (vllm-project/vllm#37291).</p>
<h2>The fix, and where it is not</h2>
<p>SGLang fixed this on main on 2026-08-19: PR #35228 added a dedicated <code>get_lm_head_scheme</code> resolver, so a head named by the quant config now routes through the compressed-tensors linear method and its scale loads.</p>
<p>Two things worth knowing as of late August 2026:</p>
<ul>
<li><p>The fix is in no released version. v0.5.18, the latest release, was cut from a branch point that predates the fix, so pip-installed SGLang still has the bug. You need main at or past commit <code>5375babb</code>.</p>
</li>
<li><p>Separately, <code>sglang[all]==0.5.18</code> currently does not resolve from PyPI at all (a <code>cuda-tile==1.6.0rc5</code> pin with no matching distribution), so you could not stay on the release even if you wanted to.</p>
</li>
</ul>
<h2>The A/B, one variable</h2>
<p>Same machine, same venv, same launch flags, TP=1, <code>--attention-backend flashinfer</code>, temperature 0. The only difference is the git checkout.</p>
<table>
<thead>
<tr>
<th></th>
<th>pre-fix (parent of #35228)</th>
<th>current main</th>
</tr>
</thead>
<tbody><tr>
<td><code>weight_scale not found</code> warnings</td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>"Which is larger, 9.11 or 9.9?"</td>
<td>repetition, empty content</td>
<td><code>9.9</code></td>
</tr>
<tr>
<td>"Capital of Australia?"</td>
<td>repetition, empty content</td>
<td>Canberra, plus a correct fact</td>
</tr>
<tr>
<td>finish_reason</td>
<td><code>length</code>, both prompts</td>
<td><code>stop</code>, both prompts</td>
</tr>
</tbody></table>
<p>Full table and logs are in the issue thread: <a href="https://github.com/sgl-project/sglang/issues/34895#issuecomment-5420022002">sgl-project/sglang#34895</a>. The before and after rows also live in a public <a href="https://github.com/jahnclawdmonet/blackwell-serving-matrix">Blackwell serving matrix</a> I maintain of verified model x engine x GPU cells.</p>
<h2>Practical guidance</h2>
<ul>
<li><p>On SGLang releases up to v0.5.18 with an FP8-lm_head checkpoint: you will hit this. Either build from main past <code>5375babb</code> or serve that checkpoint on vLLM until an SGLang release ships the fix.</p>
</li>
<li><p>The failure is silent at startup. The server boots cleanly and the health endpoint is green; only generation is garbage. If you automate deployments, grep load logs for <code>weight_scale not found</code> and fail the deploy on it.</p>
</li>
<li><p>To identify your GPU and stack cell quickly, <code>blackwell-doctor</code> prints a stable environment key you can match against the serving matrix: <a href="https://github.com/jahnclawdmonet/blackwell-doctor">github.com/jahnclawdmonet/blackwell-doctor</a>.</p>
</li>
</ul>
<p>I keep an index of exact Blackwell serving error signatures mapped to reproductions <a href="https://conatus.jahn.ai/ai-engineering/blackwell-serving-errors/">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[The sm_120 shared-memory cliff: why FP8 KV cache crashes vLLM on workstation Blackwell]]></title><description><![CDATA[If you run vLLM with `--kv-cache-dtype fp8` on a DeepSeek-family (MLA) model and your GPU is a GB10, an RTX PRO 6000, or any workstation or consumer Blackwell card, there is a decent chance the engine]]></description><link>https://conatusai.hashnode.dev/the-sm-120-shared-memory-cliff-why-fp8-kv-cache-crashes-vllm-on-workstation-blackwell</link><guid isPermaLink="true">https://conatusai.hashnode.dev/the-sm-120-shared-memory-cliff-why-fp8-kv-cache-crashes-vllm-on-workstation-blackwell</guid><category><![CDATA[vllmvllm]]></category><category><![CDATA[GPU]]></category><category><![CDATA[llm]]></category><dc:creator><![CDATA[Jahn]]></dc:creator><pubDate>Tue, 25 Aug 2026 23:27:01 GMT</pubDate><content:encoded><![CDATA[<p>If you run vLLM with `--kv-cache-dtype fp8` on a DeepSeek-family (MLA) model and your GPU is a GB10, an RTX PRO 6000, or any workstation or consumer Blackwell card, there is a decent chance the engine dies on startup with:</p>
<p>```plaintext</p>
<p>triton.runtime.errors.OutOfResources: out of resource: shared memory,</p>
<p>Required: 102400, Hardware limit: 101376. Reducing block sizes or `num_stages` may help.</p>
<p>```</p>
<p>We hit this, measured exactly where the 1,024 missing bytes come from, and posted the measurements on the upstream issue (vllm-project/vllm#53748). This post is the diagnostic method, because it applies to any Triton kernel on this hardware class, not just this one bug.</p>
<p>## The hardware fact underneath</p>
<p>Datacenter Blackwell parts advertise a larger per-block shared-memory budget than the workstation and consumer silicon. On an RTX PRO 6000 Blackwell (sm_120), `torch.cuda.get_device_properties` reports:</p>
<p>| property | bytes |</p>
<p>|---|---:|</p>
<p>| `shared_memory_per_block_optin` | 101,376 |</p>
<p>| `shared_memory_per_multiprocessor` | 102,400 |</p>
<p>GB10 (DGX Spark) reports the same 101,376-byte per-block opt-in limit. So a kernel tuned to fit 102,400 bytes fits the SM but not the block, and the whole workstation Blackwell class fails together. If your kernel config was validated on H100 or B200 numbers, this is the cliff you fall off when someone runs it on a desk machine.</p>
<p>## The five-minute probe</p>
<p>Do not debug this inside the serving engine. Launch the suspect kernel directly with the exact tile configuration the engine would pin, sweep `num_stages`, and read the compiled kernel's shared-memory size. For vLLM's MLA decode kernel (`_fwd_grouped_kernel_stage1`) with the DeepSeek tile shape (`BLOCK_DMODEL=512, BLOCK_DPE=64, BLOCK_DV=512, BLOCK_N=32, BLOCK_H=16, num_warps=4`):</p>
<p>| num_stages | KV bf16 | KV fp8e4m3 |</p>
<p>|---|---|---|</p>
<p>| 1 | 59,392 (launches) | 83,968 (launches) |</p>
<p>| 2 | 63,488 (launches) | 102,400 (OutOfResources) |</p>
<p>| 3 | 63,616 (launches) | 102,656 (OutOfResources) |</p>
<p>| 4 | 63,616 (launches) | 102,656 (OutOfResources) |</p>
<p>The table says everything. With bf16 KV, an extra pipeline stage costs about 4 KB and even four stages sit far under the limit. With fp8 KV, a stage costs about 18 KB, and stage two lands on 102,400 bytes: exactly the full per-SM budget, and 1,024 bytes over the per-block cap. The crash is specific to the fp8 KV path; MLA with bf16 KV is fine at these tile sizes.</p>
<p>The probe is ordinary Triton: import the kernel function, build dummy tensors for the tile shape, launch once per (dtype, num_stages) combination, and catch `OutOfResources`. The compiled size is on `kernel.metadata` after a successful launch. No server, no model download, under a minute per configuration.</p>
<p>## Reproducing end to end</p>
<p>DeepSeek-V2-Lite-Chat is the smallest convenient MLA model with the same `Lk=576` head layout (512 latent plus 64 rope):</p>
<p>```shell</p>
<p>vllm serve deepseek-ai/DeepSeek-V2-Lite-Chat --trust-remote-code \</p>
<p>--enforce-eager --kv-cache-dtype fp8 --max-model-len 4096</p>
<p>```</p>
<p>On sm_120 the backend selector reports `TRITON_MLA` as the only MLA decode option, so there is no fallback path: the engine dies during the startup dummy run (vLLM 0.27.1, torch 2.13.0+cu130, triton 3.7.1, CUDA 13.1).</p>
<p>## What a durable fix looks like</p>
<p>The upstream patch direction pins `num_stages=1` for MLA kernels on non-HIP devices, which makes the server start and generate correctly (we validated greedy output on sm_120 after applying it). But the sweep shows why a blanket `is_mla` condition costs something: bf16 KV at two stages fits comfortably (63,488 &lt; 101,376), and the second stage exists for throughput. The robust rule is to compute the tile's actual shared-memory requirement and compare it against `shared_memory_per_block_optin` for the device you are actually on. That handles GB10, RTX PRO 6000, and whatever ships next, without a special case per chip.</p>
<p>The general lesson for anyone shipping Triton kernels: your shared-memory budget is a per-block, per-device property, not a compile-time constant, and the workstation cards your users actually own report smaller numbers than the datacenter parts your CI runs on. Probe first; it takes five minutes.</p>
<p>This came out of inference engineering work on Blackwell workstations. The benchmark that surrounds it, with raw data and reproduction scripts, is here: <a href="https://conatus.jahn.ai/ai-engineering/sample-report">https://conatus.jahn.ai/ai-engineering/sample-report</a></p>
]]></content:encoded></item></channel></rss>