[GH-ISSUE #2213] Support for Deepseek V4.1 Flash #1304

Open
opened 2026-10-29 14:51:01 +00:00 by bsakaguchi · 18 comments
Owner

Originally created by @RobertoFMBarreto on GitHub (Sep 21, 2026).
Original GitHub issue: https://github.com/kvcache-ai/ktransformers/issues/2213

Reminder

  • I have read the above rules and searched the existing issues.

Description

When will you guys develop Deepseek V4.1 Flash support?

Pull Request

No response

Originally created by @RobertoFMBarreto on GitHub (Sep 21, 2026). Original GitHub issue: https://github.com/kvcache-ai/ktransformers/issues/2213 ### Reminder - [x] I have read the above rules and searched the existing issues. ### Description When will you guys develop Deepseek V4.1 Flash support? ### Pull Request _No response_
Author
Owner

@05yuki commented on GitHub (Oct 7, 2026):

In case it helps while this is open: DeepSeek V4.1-Flash runs on the KT hybrid today, and the whole recipe is public:

https://github.com/05yuki/dsv41-flash-dual-epyc

What it is: SGLang's dsv4.1 tree (1aa0e962) plus one tree patch, kt-kernel (95009ea) plus one tree patch, and an engram adapter that serves the two 101 GB engram tables straight from the NVMe instead of RAM. Measured on 2x RTX 5070 Ti 16 GB and 2x EPYC 7452 (512 GB), MXFP4 experts on the CPU, Marlin hot experts on the GPUs:

  • 1M-token context, KV pool 1,048,576 tokens; a 989,154-token prompt prefills to the end
  • prefill 576 tok/s at 38K and 596 at 114K (chunk 6144 with the lend from #2227), decode 27.6-28.0 tok/s
  • cold launch 243 s for the 476 GB checkpoint (the loader fixes are in the README's finding 18)

The README lists the 18 things that had to change, in the order they were found (SWA pool sizing, the two-level indexer's memory at long prompts, arena pinning, the loader), each with its patch and its numbers. Two of them look upstreamable on their own (the kt-kernel loader prefetch and the CPU-expert skip in load_weights); I asked about those in #2227.

<!-- gh-comment-id:6033557178 --> @05yuki commented on GitHub (Oct 7, 2026): In case it helps while this is open: DeepSeek V4.1-Flash runs on the KT hybrid today, and the whole recipe is public: https://github.com/05yuki/dsv41-flash-dual-epyc What it is: SGLang's `dsv4.1` tree (1aa0e962) plus one tree patch, kt-kernel (95009ea) plus one tree patch, and an engram adapter that serves the two 101 GB engram tables straight from the NVMe instead of RAM. Measured on 2x RTX 5070 Ti 16 GB and 2x EPYC 7452 (512 GB), MXFP4 experts on the CPU, Marlin hot experts on the GPUs: - 1M-token context, KV pool 1,048,576 tokens; a 989,154-token prompt prefills to the end - prefill 576 tok/s at 38K and 596 at 114K (chunk 6144 with the lend from #2227), decode 27.6-28.0 tok/s - cold launch 243 s for the 476 GB checkpoint (the loader fixes are in the README's finding 18) The README lists the 18 things that had to change, in the order they were found (SWA pool sizing, the two-level indexer's memory at long prompts, arena pinning, the loader), each with its patch and its numbers. Two of them look upstreamable on their own (the kt-kernel loader prefetch and the CPU-expert skip in `load_weights`); I asked about those in #2227.
Author
Owner

@andreasgru commented on GitHub (Oct 18, 2026):

Thanks for the recipe, we build on it. Recipe-state launcher (18 Sep) on 1 RTX PRO 6000 96 GB + 2× EPYC 7302, 1017 weights, 1 stream (median of 20): 23.6 tok/s. Our tuned path, same card: 64.2 to 64.7. Other expert map and memory cap: not kernel-for-kernel.

<!-- gh-comment-id:6252457871 --> @andreasgru commented on GitHub (Oct 18, 2026): Thanks for the recipe, we build on it. Recipe-state launcher (18 Sep) on 1 RTX PRO 6000 96 GB + 2× EPYC 7302, 1017 weights, 1 stream (median of 20): 23.6 tok/s. Our tuned path, same card: 64.2 to 64.7. Other expert map and memory cap: not kernel-for-kernel.
Author
Owner

@disdrix commented on GitHub (Oct 21, 2026):

I just wanted to add that I tried the above method with DS4.1 1017 Q4_K_XL (Unsloth). Hardware is RTX 6000 Pro, Threadripper 7965WX, 512GB DDR5 6000. Engrams are loaded on m.2 drive.

Llama.cpp was around 24-32 tok/s decode with dspark and 310 tok/s prompt processing with 8k chunks.

I threw the latest version of ktransformers into opus 5.5 in claude code and let it cook. I've got ktransformers working fully in Windows 10. Prefill is running roughly 641 tok/s at 2,600 tokens, 1,135 tok/s at 10,000, and 1,438 tok/s at 20,500. Decode varies between 25 and 55. This depends on dspark and expert swapping. 40–55 tok/s in long sessions. 25–34 tok/s on fresh short prompts, while the GPU expert cache is still learning which experts get used.

I just starting doing rounds of optimization, so I should be able to get more out of it. So far, Opus 5.5 reports that accuracy is not impacted, so it should be good. It is kind of like we don't need devs anymore since AI just does it all now. I am not even sure what the point of contributing is anymore these days.

<!-- gh-comment-id:6306380484 --> @disdrix commented on GitHub (Oct 21, 2026): I just wanted to add that I tried the above method with DS4.1 1017 Q4_K_XL (Unsloth). Hardware is RTX 6000 Pro, Threadripper 7965WX, 512GB DDR5 6000. Engrams are loaded on m.2 drive. Llama.cpp was around 24-32 tok/s decode with dspark and 310 tok/s prompt processing with 8k chunks. I threw the latest version of ktransformers into opus 5.5 in claude code and let it cook. I've got ktransformers working fully in Windows 10. Prefill is running roughly 641 tok/s at 2,600 tokens, 1,135 tok/s at 10,000, and 1,438 tok/s at 20,500. Decode varies between 25 and 55. This depends on dspark and expert swapping. 40–55 tok/s in long sessions. 25–34 tok/s on fresh short prompts, while the GPU expert cache is still learning which experts get used. I just starting doing rounds of optimization, so I should be able to get more out of it. So far, Opus 5.5 reports that accuracy is not impacted, so it should be good. It is kind of like we don't need devs anymore since AI just does it all now. I am not even sure what the point of contributing is anymore these days.
Author
Owner

@05yuki commented on GitHub (Oct 21, 2026):

For 1017 specifically there is a newer recipe: https://github.com/05yuki/deepseek-v4.1-flash-1017-ktransformers

It is SGLang main (ed0b8f43) plus format-patch tiers, and kt-kernel 95009ea plus one tree patch. setup.sh builds everything, and serve.sh starts it.

On 2× RTX 5070 Ti 16 GB and 2× EPYC 7452 (512 GB DDR4), with a 1M-token context:

  • Decode: 44 tok/s on Japanese prose, 34–39 tok/s on code, JSON, math, English and Chinese (1,024-token replies).
  • Prefill: 759 tok/s at 35K tokens and 558 tok/s at 1M tokens.

@disdrix, your note on expert swapping matches what we measured. A fixed hot set recorded on one kind of text caught only 3–6% of the routes of code, JSON or math.

The GPU experts in that repo now follow the text being written. Every 8 decode steps, all 21 a layer are swapped by decayed route counts: the 5 that always stay, and 16 more in the KV pool's free pages.

<!-- gh-comment-id:6307304883 --> @05yuki commented on GitHub (Oct 21, 2026): For 1017 specifically there is a newer recipe: https://github.com/05yuki/deepseek-v4.1-flash-1017-ktransformers It is SGLang main (ed0b8f43) plus format-patch tiers, and kt-kernel 95009ea plus one tree patch. `setup.sh` builds everything, and `serve.sh` starts it. On 2× RTX 5070 Ti 16 GB and 2× EPYC 7452 (512 GB DDR4), with a 1M-token context: - Decode: 44 tok/s on Japanese prose, 34–39 tok/s on code, JSON, math, English and Chinese (1,024-token replies). - Prefill: 759 tok/s at 35K tokens and 558 tok/s at 1M tokens. @disdrix, your note on expert swapping matches what we measured. A fixed hot set recorded on one kind of text caught only 3–6% of the routes of code, JSON or math. The GPU experts in that repo now follow the text being written. Every 8 decode steps, all 21 a layer are swapped by decayed route counts: the 5 that always stay, and 16 more in the KV pool's free pages. - The experts are copied from kt-kernel's own expert memory, so no extra host copy is needed. - Details: https://github.com/05yuki/deepseek-v4.1-flash-1017-ktransformers/blob/main/docs/tier1.md#16-hot-lend
Author
Owner

@robertofmb commented on GitHub (Oct 21, 2026):

This is @RobertoFMBarreto company account

<head></head>

Thanks @05yuki for sharing your recipe and documenting your experiments. I originally opened this issue, and wanted to share where I have got to using your earlier recipe with some local changes.

My setup:

  • 2× Intel Xeon Platinum 8362: 64 physical cores total, 1 TB RAM.

  • One NVIDIA RTX PRO 6000 Blackwell Max-Q with 96 GB VRAM dedicated to this model.

  • Linux and Docker, GPU TP=1, two CPU thread pools with 32 workers each.

  • deepseek-ai/DeepSeek-V4.1-Flash-1017, with 104 GPU-resident experts using Marlin and the remaining experts on CPU.

  • Engram tables in RAM, prefix caching enabled, and prefill chunks of 2,048 tokens.

  • Context configured to 270,336 tokens, with one active request. We have not validated the entire configured context window.

Latest results, using medians of three runs:

Test Result
512-token generation, including request overhead ~29.8 output tokens/s
~4K prompt, uncached: time to first output 13.08 s
~16K prompt, uncached: time to first output 50.40 s
Same ~16K prompt with prefix cache 1.14 s

Switching the CPU MXFP4 path from AVX2 to the AVX-512 implementation reduced uncached time to first output by approximately 23–24%, while generation throughput stayed almost unchanged. These Xeons do not support AMX; this uses the AVX-512 fallback in AMXFP4_KGroup_MOE.

Our targeted context-retrieval and tool-call round-trip tests passed, and I am also using it through Hermes Agent. These are functional checks, rather than a comprehensive accuracy evaluation.

Uncached prefill remains the main bottleneck. In the earlier zero-copy streaming code, we found that with two CPU partitions and GPU TP=1, the arena mapping selects only one CPU partition. We are experimenting with assembling both partitions correctly, but that change is still unvalidated.

I noticed you have now shared a newer recipe specifically for 1017—thanks for continuing to share this work. Have you tried two CPU partitions feeding a single GPU, particularly with the AVX-512 CPU backend?

If you have already tackled something similar in the newer recipe, I would be interested in comparing approaches. Happy to share our configuration and test results if useful.

Next, I plan to validate streamed prefill with both CPU partitions feeding the single GPU. After that, I want to compare expert placement based on representative Hermes workloads and investigate whether DSpark can improve generation speed in this setup. These are planned experiments; the results above do not include them.

<!-- gh-comment-id:6312549289 --> @robertofmb commented on GitHub (Oct 21, 2026): This is @RobertoFMBarreto company account <head></head><p style="caret-color: rgb(255, 255, 255); color: rgb(255, 255, 255); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><span>Thanks @05yuki for sharing your recipe and documenting your experiments. I originally opened this issue, and wanted to share where I have got to using your earlier recipe with some local changes.</span></p><p style="caret-color: rgb(255, 255, 255); color: rgb(255, 255, 255); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><span>My setup:</span></p><ul style="caret-color: rgb(255, 255, 255); color: rgb(255, 255, 255); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><li><p><span>2× Intel Xeon Platinum 8362: 64 physical cores total, 1 TB RAM.</span></p></li><li><p><span>One NVIDIA RTX PRO 6000 Blackwell Max-Q with 96 GB VRAM dedicated to this model.</span></p></li><li><p><span>Linux and Docker, GPU TP=1, two CPU thread pools with 32 workers each.</span></p></li><li><p><code><span>deepseek-ai/DeepSeek-V4.1-Flash-1017</span></code><span>, with 104 GPU-resident experts using Marlin and the remaining experts on CPU.</span></p></li><li><p><span>Engram tables in RAM, prefix caching enabled, and prefill chunks of 2,048 tokens.</span></p></li><li><p><span>Context configured to 270,336 tokens, with one active request. We have not validated the entire configured context window.</span></p></li></ul><p style="caret-color: rgb(255, 255, 255); color: rgb(255, 255, 255); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><span>Latest results, using medians of three runs:</span></p> <table> <tr> <th>Test</th> <th>Result</th> </tr> <tr> <td>512-token generation, including request overhead</td> <td>~29.8 output tokens/s</td> </tr> <tr> <td>~4K prompt, uncached: time to first output</td> <td>13.08 s</td> </tr> <tr> <td>~16K prompt, uncached: time to first output</td> <td>50.40 s</td> </tr> <tr> <td>Same ~16K prompt with prefix cache</td> <td>1.14 s</td> </tr> </table> <p style="caret-color: rgb(255, 255, 255); color: rgb(255, 255, 255); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><span>Switching the CPU MXFP4 path from AVX2 to the AVX-512 implementation reduced uncached time to first output by approximately 23–24%, while generation throughput stayed almost unchanged. These Xeons do not support AMX; this uses the AVX-512 fallback in<span class="Apple-converted-space"> </span></span><code><span>AMXFP4_KGroup_MOE</span></code><span>.</span></p><p style="caret-color: rgb(255, 255, 255); color: rgb(255, 255, 255); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><span>Our targeted context-retrieval and tool-call round-trip tests passed, and I am also using it through Hermes Agent. These are functional checks, rather than a comprehensive accuracy evaluation.</span></p><p style="caret-color: rgb(255, 255, 255); color: rgb(255, 255, 255); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><span>Uncached prefill remains the main bottleneck. In the earlier zero-copy streaming code, we found that with two CPU partitions and GPU TP=1, the arena mapping selects only one CPU partition. We are experimenting with assembling both partitions correctly, but that change is still unvalidated.</span></p><p style="caret-color: rgb(255, 255, 255); color: rgb(255, 255, 255); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><span>I noticed you have now shared a newer recipe specifically for 1017—thanks for continuing to share this work. Have you tried two CPU partitions feeding a single GPU, particularly with the AVX-512 CPU backend?</span></p><p style="caret-color: rgb(255, 255, 255); color: rgb(255, 255, 255); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"><span>If you have already tackled something similar in the newer recipe, I would be interested in comparing approaches. Happy to share our configuration and test results if useful.</span></p> Next, I plan to validate streamed prefill with both CPU partitions feeding the single GPU. After that, I want to compare expert placement based on representative Hermes workloads and investigate whether DSpark can improve generation speed in this setup. These are planned experiments; the results above do not include them.
Author
Owner

@05yuki commented on GitHub (Oct 21, 2026):

@RobertoFMBarreto thanks for the detailed numbers. Yes: one GPU fed by two CPU partitions is exactly what we fixed today.

The two-partition fix

  • In the earlier code, each GPU rank mapped one kt-kernel part. With TP=1 and two NUMA pools, the zero-copy W16 path read half of each expert and went out of bounds.
  • In commit b5ad85e of the 1017 repo, each rank reads every part its rows of the intermediate size sit in, and the W16 dequant writes them in place: gate/up by rows, down by columns. The patch is patches/sglang-tier1/14-stream-prefill.patch (_rank_segments in kt_stream_prefill.py).
  • The hot lend's dynamic slots read the same way.
  • Checked on one 16 GB card with two parts: an 11.8K-token document was summarised correctly, and a 35K-token prompt prefilled at about 210 tok/s (bounded by that card's memory). Also checked with four parts on two GPUs.

Two other things in that repo may fit your setup

  • Placement from workloads. The GPU experts now follow the text being written: decayed route counts, swaps every 8 decode steps, copied from kt-kernel's arenas. No placement has to be recorded. On our cards this gave code, JSON and math decode +15–23% over a fixed placement.
    • The dynamic slots need the GPU MoE in the checkpoint's layout (gpu-moe-fp32). With Marlin's repacked layout, their self test will not match, and the slots stay static (not tried with Marlin here).
    • With 96 GB, the slot count follows the free VRAM (KT_KV_HOT_EXTRA=auto).
  • Chunk sizing. The prefill plan sizes each request's chunks from the memory free at that moment. On a 96 GB card, 2,048-token chunks leave most of the card unused, and every chunk streams the experts again.

We only have AVX2 (Zen 2) CPUs, so the AVX-512 path is untested here. Your 23–24% on uncached time to first output is useful to know. Happy to compare configs.

Repo: https://github.com/05yuki/deepseek-v4.1-flash-1017-ktransformers

<!-- gh-comment-id:6312782592 --> @05yuki commented on GitHub (Oct 21, 2026): @RobertoFMBarreto thanks for the detailed numbers. Yes: one GPU fed by two CPU partitions is exactly what we fixed today. **The two-partition fix** - In the earlier code, each GPU rank mapped one kt-kernel part. With TP=1 and two NUMA pools, the zero-copy W16 path read half of each expert and went out of bounds. - In commit b5ad85e of the 1017 repo, each rank reads every part its rows of the intermediate size sit in, and the W16 dequant writes them in place: gate/up by rows, down by columns. The patch is `patches/sglang-tier1/14-stream-prefill.patch` (`_rank_segments` in `kt_stream_prefill.py`). - The hot lend's dynamic slots read the same way. - Checked on one 16 GB card with two parts: an 11.8K-token document was summarised correctly, and a 35K-token prompt prefilled at about 210 tok/s (bounded by that card's memory). Also checked with four parts on two GPUs. **Two other things in that repo may fit your setup** - **Placement from workloads.** The GPU experts now follow the text being written: decayed route counts, swaps every 8 decode steps, copied from kt-kernel's arenas. No placement has to be recorded. On our cards this gave code, JSON and math decode +15–23% over a fixed placement. - The dynamic slots need the GPU MoE in the checkpoint's layout (`gpu-moe-fp32`). With Marlin's repacked layout, their self test will not match, and the slots stay static (not tried with Marlin here). - With 96 GB, the slot count follows the free VRAM (`KT_KV_HOT_EXTRA=auto`). - **Chunk sizing.** The prefill plan sizes each request's chunks from the memory free at that moment. On a 96 GB card, 2,048-token chunks leave most of the card unused, and every chunk streams the experts again. We only have AVX2 (Zen 2) CPUs, so the AVX-512 path is untested here. Your 23–24% on uncached time to first output is useful to know. Happy to compare configs. Repo: https://github.com/05yuki/deepseek-v4.1-flash-1017-ktransformers
Author
Owner

@disdrix commented on GitHub (Oct 21, 2026):

I was able to work on decode a bit by getting expert usage up. There is a lot of optimization to be done to get expert usage on GPU over 90%, which gives a huge boost to decode. I've got the average decode up to 42 tok/s with the minimum at 34 now. PCIe 5.0 instead of 4.0 if you have it might matter here more for this.

Best bet is probably have a frontier model like Opus 5.5 or similar just run overnight optimization with quality and accuracy checks at each step. I plan to do this tonight as I sleep.

<!-- gh-comment-id:6315167117 --> @disdrix commented on GitHub (Oct 21, 2026): I was able to work on decode a bit by getting expert usage up. There is a lot of optimization to be done to get expert usage on GPU over 90%, which gives a huge boost to decode. I've got the average decode up to 42 tok/s with the minimum at 34 now. PCIe 5.0 instead of 4.0 if you have it might matter here more for this. Best bet is probably have a frontier model like Opus 5.5 or similar just run overnight optimization with quality and accuracy checks at each step. I plan to do this tonight as I sleep.
Author
Owner

@robertofmb commented on GitHub (Oct 21, 2026):

@05yuki Thanks! We ported your b5ad85e segmented W16 fix into our existing SGLang build and adapted the AVX-512 backend to stream directly from its native CPU buffers, without another full weight copy.

On our dual Xeon 8362 + RTX PRO 6000 96 GB, the best configuration measured so far is 96 GPU experts, streaming groups of four and 8,192-token chunks:

  • ~16K uncached, first output: 50.40 → 27.69 s (~45% less waiting).
  • ~32K uncached: 55.16 s; cached: 1.50 s.
  • Generation remains around 29.2 tok/s, versus 29.8 previously.

Streaming-versus-CPU checks on four layers gave cosine ≥0.99999 and relative L2 error of 0.30–0.40%. Targeted retrieval tests through ~32K and three tool-call round trips passed.

We have not migrated to the full new recipe or tested dynamic placement/DSpark yet. The configured 270K context remains unvalidated. Happy to share the AVX-512 changes if useful!

Next, I plan to test larger streaming expert groups, then investigate your dynamic expert placement and newer FP8 decode kernels. I’ll compare latency and generation throughput while repeating the numerical and functional checks.

<!-- gh-comment-id:6315310199 --> @robertofmb commented on GitHub (Oct 21, 2026): @05yuki Thanks! We ported your `b5ad85e` segmented W16 fix into our existing SGLang build and adapted the AVX-512 backend to stream directly from its native CPU buffers, without another full weight copy. On our dual Xeon 8362 + RTX PRO 6000 96 GB, the best configuration measured so far is 96 GPU experts, streaming groups of four and 8,192-token chunks: - ~16K uncached, first output: **50.40 → 27.69 s** (~45% less waiting). - ~32K uncached: **55.16 s**; cached: **1.50 s**. - Generation remains around **29.2 tok/s**, versus 29.8 previously. Streaming-versus-CPU checks on four layers gave cosine ≥0.99999 and relative L2 error of 0.30–0.40%. Targeted retrieval tests through ~32K and three tool-call round trips passed. We have not migrated to the full new recipe or tested dynamic placement/DSpark yet. The configured 270K context remains unvalidated. Happy to share the AVX-512 changes if useful! Next, I plan to test larger streaming expert groups, then investigate your dynamic expert placement and newer FP8 decode kernels. I’ll compare latency and generation throughput while repeating the numerical and functional checks.
Author
Owner

@05yuki commented on GitHub (Oct 21, 2026):

@RobertoFMBarreto great result on the prefill (50.4 → 27.7 s), and thanks for the cosine and L2 checks.

On decode: 29 tok/s with 96 GPU experts on an RTX PRO 6000 looks bound by something other than the CPU experts. With a quarter of each layer's experts on the GPU, the CPU side of a token should be small.

Likely cause: the dense FP8 GEMMs. The RTX PRO 6000 is SM120, like our 5070 Ti. SM120 has no DeepGEMM build, so the dense FP8 GEMMs of decode take a slow generic path at batch 1. On our cards that was the longer side of a token.

What fixed it on our side:

How to check: an nsys trace of a few decode steps shows it. If the dense FP8 GEMMs dominate the token, rather than the wait for kt-kernel, that is the bound.

fp8-decode has no prerequisites and applies alone on tier 0 (SGLANG_FEATURES=fp8-decode), so it can be tried without the rest.

<!-- gh-comment-id:6315441874 --> @05yuki commented on GitHub (Oct 21, 2026): @RobertoFMBarreto great result on the prefill (50.4 → 27.7 s), and thanks for the cosine and L2 checks. On decode: 29 tok/s with 96 GPU experts on an RTX PRO 6000 looks bound by something other than the CPU experts. With a quarter of each layer's experts on the GPU, the CPU side of a token should be small. **Likely cause: the dense FP8 GEMMs.** The RTX PRO 6000 is SM120, like our 5070 Ti. SM120 has no DeepGEMM build, so the dense FP8 GEMMs of decode take a slow generic path at batch 1. On our cards that was the longer side of a token. **What fixed it on our side:** - `fp8-decode` (block-FP8 GEMV and split-K for decode-sized GEMMs) alone took a token from 62.7 to 36.6 ms (15.9 → 27.3 tok/s). - With `engram-row-prefetch` and `wo-a-fp8` on top, it reached 28.2–28.3 tok/s before any hot experts. - Details: https://github.com/05yuki/deepseek-v4.1-flash-1017-ktransformers/blob/main/docs/tier1.md#01-fp8-decode **How to check:** an nsys trace of a few decode steps shows it. If the dense FP8 GEMMs dominate the token, rather than the wait for kt-kernel, that is the bound. `fp8-decode` has no prerequisites and applies alone on tier 0 (`SGLANG_FEATURES=fp8-decode`), so it can be tried without the rest.
Author
Owner

@disdrix commented on GitHub (Oct 21, 2026):

Okay, I let Opus 5.5 work on it for 8 hours as planned. The results were phenomenal. At the bottom is everything that was changed from ktransformers. Should help anyone that wants to optimize theirs too.

DeepSeek-V4.1-Flash on one workstation with KTransformers, native on Windows

Summary: about 80 tok/s decode on code, 55-61 tok/s on prose, and 950-1,140 tok/s prefill on a new 16-24K-token prompt. One user at a time, with 313 of each layer's 384 routed experts running on the CPU.

Hardware: AMD Threadripper PRO 7965WX (24 cores), 512 GB RAM, one NVIDIA RTX PRO 6000 Blackwell Workstation Edition (96 GB). Windows, running natively (no WSL).

Model: DeepSeek-V4.1-Flash-1017 from Unsloth's GGUFs: UD-Q4_K_XL (MXFP4 routed experts, Q8_0 dense weights converted to FP8 at load), mmproj-BF16 for vision, and the DSpark draft in Q8_0.

Server: SGLang with DeepSeek-V4.1 support, plus kt-kernel for the CPU experts.

  • 71 of the 384 routed experts per layer live on the GPU (Marlin MXFP4, --vram-gib 80). The other 313 run on the CPU in kt-kernel (24 threads, AVX-512 BF16, MXFP4 read straight from the GGUF).
  • DSpark speculative decoding with 2 draft tokens, so one step can emit up to 3 tokens.
  • FP8 KV cache, 128K context, chunked prefill 8192, streamed prefill from 1024 tokens, breakable CUDA graphs for decode.

Results

One request at a time, thinking off, streamed through the OpenAI-compatible API over the LAN. Runs are listed in the order they ran.

# Test Prompt tokens Time to first token Prefill (tok/s) Decode (tok/s) Tokens per step
1 Short code: LRU cache + tests 92 1.06 s – 69.4 2.68
2 Short prose: 2,000-word story 73 0.86 s – 55.2 1.82
3 Long code A: tests for a 59 KB Python file 15,940 14.41 s 1,106 79.2 2.74
4 Long code B: same file again 15,940 11.55 s 1,380 79.3 2.72
5 Long prose A: essay from 90 KB of docs 23,218 24.33 s 954 61.4 2.10
6 Long prose B: same text again 23,219 17.52 s 1,325 61.3 2.04
7 Long code C: a different 59 KB file 16,145 14.17 s 1,139 81.7 2.73
8 Long prose C: a different 90 KB of docs 23,869 23.84 s 1,001 61.2 2.11
9 Short code, 2nd run 92 0.81 s – 74.8 2.71
10 Short code, 3rd run 89 0.69 s – 79.2 2.77
  • Decode: about 80 tok/s on code and 55-61 tok/s on prose once warm, with prompts from 90 to 24K tokens. Each step takes 33-35 ms whatever the content. Code is faster because DSpark gets more tokens accepted per step: about 2.7, against 1.8-2.1 for prose.
  • Prefill: a new 16-24K-token prompt runs at about 950-1,140 tok/s. That is 14 s for 16K tokens of code and 24 s for 23-24K tokens of prose. Sending the exact same text again was faster (1,325-1,380 tok/s), even though the nonce keeps the prefix cache out of it.
  • Warm-up: the short code runs went from 69 to 75 to 79 tok/s while the time per step fell from 38.6 to 35.0 ms. That fits the GPU expert slots adapting to code during generation. Long prompts retune the slots during prefill, so the long code runs were at full speed from the start.
How these were measured
  • Time to first token is measured at the client, from sending the request to the first streamed token. Prefill = prompt tokens ÷ time to first token.
  • Decode = (output tokens − 1) ÷ time from the first to the last streamed token.
  • Tokens per step = output tokens ÷ streamed chunks. The server streams every step (stream_interval=1), so this is DSpark's mean accept length including the bonus token (max 3).
  • Every prompt starts with a random nonce, so the prefix cache can't reuse an earlier run.
  • Every run stopped at its output cap: 3,000 tokens for the short prompts, 4,000 for the long ones.
  • Long code: SGLang's disaggregation/prefill.py (59 KB) plus "write a comprehensive pytest suite for this module". Run C used managers/mm_utils.py, cut to 59 KB.
  • Long prose: about 90 KB of technical documentation plus "retell this as an essay of at least 3,000 words". Run C used a different 90 KB from the same documentation set.
  • Each B run repeats its A run's text exactly, apart from the nonce.

What's different from upstream KTransformers 0.7.2

  • Native Windows, no WSL. Single-rank torch.distributed (no NCCL). Triton attention and PyTorch sampling replace the Linux-only FlashInfer, FA3, CUTLASS, DeepGEMM and TileLang. sgl-kernel's CUDA ops are reimplemented in PyTorch/Triton and checked against sgl-kernel's own tests. JIT kernels build with nvcc and MSVC, and kt-kernel builds with clang-cl.
  • Breakable CUDA graphs. The CPU experts are called from the host thread instead of a CUDA stream callback. Decode replays graphs that are captured everywhere except each MoE layer's hand-off to the CPU and back, and the Engram row reads. GPU experts stay inside the graph and run while the CPU computes its share.
  • Unsloth's GGUFs load directly. Q8_0 dense weights become FP8 at load, kt-kernel reads the MXFP4 experts straight from the GGUF, and the vision mmproj and DSpark draft load too. The Engram tables (about 190 GiB) are never loaded; each lookup reads its rows from the memory-mapped file.
  • A GPU expert cache that follows routing. Each layer keeps its most-used experts in the GPU slots. Swaps happen in the background on a second CUDA stream, so decoding never pauses for them.
  • Streamed prefill. Prompt chunks of 1024+ tokens run all experts on the GPU. kt-kernel stages them 16 at a time in pinned memory, and the GPU uploads each group over PCIe (about 54 GB/s) while it computes the previous one. Only experts that aren't already on the GPU get uploaded.
  • Speed work for this setup. A tensor-core prefill attention kernel, 16-bit activations in the decode-sized FP8 linears, a pinned-memory hand-off to the CPU experts, kt-kernel threads that spin between layers instead of sleeping, and a chained hand-off that queues each layer's CPU work behind a GPU-written ticket instead of a sync.
  • LAN-only. The server refuses public binds, returns 403 to clients outside private networks, keeps admin endpoints local, blocks outbound internet and runs Hugging Face offline.
  • Packaging. A portable runtime, a double-click launcher with llama-server-style flags, and a self-test.
<!-- gh-comment-id:6326251986 --> @disdrix commented on GitHub (Oct 21, 2026): Okay, I let Opus 5.5 work on it for 8 hours as planned. The results were phenomenal. At the bottom is everything that was changed from ktransformers. Should help anyone that wants to optimize theirs too. ## DeepSeek-V4.1-Flash on one workstation with KTransformers, native on Windows **Summary:** about 80 tok/s decode on code, 55-61 tok/s on prose, and 950-1,140 tok/s prefill on a new 16-24K-token prompt. One user at a time, with 313 of each layer's 384 routed experts running on the CPU. **Hardware:** AMD Threadripper PRO 7965WX (24 cores), 512 GB RAM, one NVIDIA RTX PRO 6000 Blackwell Workstation Edition (96 GB). Windows, running natively (no WSL). **Model:** `DeepSeek-V4.1-Flash-1017` from Unsloth's GGUFs: `UD-Q4_K_XL` (MXFP4 routed experts, Q8_0 dense weights converted to FP8 at load), `mmproj-BF16` for vision, and the DSpark draft in Q8_0. **Server:** SGLang with DeepSeek-V4.1 support, plus kt-kernel for the CPU experts. - 71 of the 384 routed experts per layer live on the GPU (Marlin MXFP4, `--vram-gib 80`). The other 313 run on the CPU in kt-kernel (24 threads, AVX-512 BF16, MXFP4 read straight from the GGUF). - DSpark speculative decoding with 2 draft tokens, so one step can emit up to 3 tokens. - FP8 KV cache, 128K context, chunked prefill 8192, streamed prefill from 1024 tokens, breakable CUDA graphs for decode. ### Results One request at a time, thinking off, streamed through the OpenAI-compatible API over the LAN. Runs are listed in the order they ran. | # | Test | Prompt tokens | Time to first token | Prefill (tok/s) | Decode (tok/s) | Tokens per step | |---:|---|---:|---:|---:|---:|---:| | 1 | Short code: LRU cache + tests | 92 | 1.06 s | – | 69.4 | 2.68 | | 2 | Short prose: 2,000-word story | 73 | 0.86 s | – | 55.2 | 1.82 | | 3 | Long code A: tests for a 59 KB Python file | 15,940 | 14.41 s | 1,106 | 79.2 | 2.74 | | 4 | Long code B: same file again | 15,940 | 11.55 s | 1,380 | 79.3 | 2.72 | | 5 | Long prose A: essay from 90 KB of docs | 23,218 | 24.33 s | 954 | 61.4 | 2.10 | | 6 | Long prose B: same text again | 23,219 | 17.52 s | 1,325 | 61.3 | 2.04 | | 7 | Long code C: a different 59 KB file | 16,145 | 14.17 s | 1,139 | 81.7 | 2.73 | | 8 | Long prose C: a different 90 KB of docs | 23,869 | 23.84 s | 1,001 | 61.2 | 2.11 | | 9 | Short code, 2nd run | 92 | 0.81 s | – | 74.8 | 2.71 | | 10 | Short code, 3rd run | 89 | 0.69 s | – | 79.2 | 2.77 | - **Decode:** about 80 tok/s on code and 55-61 tok/s on prose once warm, with prompts from 90 to 24K tokens. Each step takes 33-35 ms whatever the content. Code is faster because DSpark gets more tokens accepted per step: about 2.7, against 1.8-2.1 for prose. - **Prefill:** a new 16-24K-token prompt runs at about 950-1,140 tok/s. That is 14 s for 16K tokens of code and 24 s for 23-24K tokens of prose. Sending the exact same text again was faster (1,325-1,380 tok/s), even though the nonce keeps the prefix cache out of it. - **Warm-up:** the short code runs went from 69 to 75 to 79 tok/s while the time per step fell from 38.6 to 35.0 ms. That fits the GPU expert slots adapting to code during generation. Long prompts retune the slots during prefill, so the long code runs were at full speed from the start. <details> <summary>How these were measured</summary> - **Time to first token** is measured at the client, from sending the request to the first streamed token. **Prefill** = prompt tokens ÷ time to first token. - **Decode** = (output tokens − 1) ÷ time from the first to the last streamed token. - **Tokens per step** = output tokens ÷ streamed chunks. The server streams every step (`stream_interval=1`), so this is DSpark's mean accept length including the bonus token (max 3). - Every prompt starts with a random nonce, so the prefix cache can't reuse an earlier run. - Every run stopped at its output cap: 3,000 tokens for the short prompts, 4,000 for the long ones. - Long code: SGLang's `disaggregation/prefill.py` (59 KB) plus "write a comprehensive pytest suite for this module". Run C used `managers/mm_utils.py`, cut to 59 KB. - Long prose: about 90 KB of technical documentation plus "retell this as an essay of at least 3,000 words". Run C used a different 90 KB from the same documentation set. - Each B run repeats its A run's text exactly, apart from the nonce. </details> ### What's different from upstream KTransformers 0.7.2 - **Native Windows, no WSL.** Single-rank `torch.distributed` (no NCCL). Triton attention and PyTorch sampling replace the Linux-only FlashInfer, FA3, CUTLASS, DeepGEMM and TileLang. sgl-kernel's CUDA ops are reimplemented in PyTorch/Triton and checked against sgl-kernel's own tests. JIT kernels build with nvcc and MSVC, and kt-kernel builds with clang-cl. - **Breakable CUDA graphs.** The CPU experts are called from the host thread instead of a CUDA stream callback. Decode replays graphs that are captured everywhere except each MoE layer's hand-off to the CPU and back, and the Engram row reads. GPU experts stay inside the graph and run while the CPU computes its share. - **Unsloth's GGUFs load directly.** Q8_0 dense weights become FP8 at load, kt-kernel reads the MXFP4 experts straight from the GGUF, and the vision `mmproj` and DSpark draft load too. The Engram tables (about 190 GiB) are never loaded; each lookup reads its rows from the memory-mapped file. - **A GPU expert cache that follows routing.** Each layer keeps its most-used experts in the GPU slots. Swaps happen in the background on a second CUDA stream, so decoding never pauses for them. - **Streamed prefill.** Prompt chunks of 1024+ tokens run all experts on the GPU. kt-kernel stages them 16 at a time in pinned memory, and the GPU uploads each group over PCIe (about 54 GB/s) while it computes the previous one. Only experts that aren't already on the GPU get uploaded. - **Speed work for this setup.** A tensor-core prefill attention kernel, 16-bit activations in the decode-sized FP8 linears, a pinned-memory hand-off to the CPU experts, kt-kernel threads that spin between layers instead of sleeping, and a chained hand-off that queues each layer's CPU work behind a GPU-written ticket instead of a sync. - **LAN-only.** The server refuses public binds, returns 403 to clients outside private networks, keeps admin endpoints local, blocks outbound internet and runs Hugging Face offline. - **Packaging.** A portable runtime, a double-click launcher with llama-server-style flags, and a self-test.
Author
Owner

@disdrix commented on GitHub (Oct 22, 2026):

I had Opus 5.5 optimize for 8 hours again to squeeze more speed out of it. I am going to call these results final. More information on what I did is included so you can try to copy what I did with your specific setup. Since this is Windows 10, you could expect an increase of 10-15% on Linux.

Update: faster prompt processing, slightly faster decode (same hardware, same tests)

Summary: on a new 16-24K-token prompt, prefill now runs at 1,530-2,220 tok/s (was 950-1,140), and time to first token is 37-49% shorter. Decode is up to 6% faster (9 of the 10 runs faster, one even): about 82-84 tok/s on code and 62-64 tok/s on prose. Output quality is unchanged.

Setup changes since the first post (hardware, model and the rest of the server are the same):

  • 70 of the 384 routed experts per layer on the GPU (was 71; the bigger prompt chunks need the memory).
  • Chunked prefill 16384 (was 8192).
  • 128K context as before. The launcher's default is now the model's full 1M; the numbers below use -c 131072 to match the first post.

Results

Same harness and prompts as the first post: one request at a time, thinking off, streamed through the OpenAI-compatible API over the LAN, a fresh nonce line per request, runs in the order they ran, on a freshly started server.

# Test Prompt tokens Time to first token Prefill (tok/s) Decode (tok/s) Tokens per step
1 Short code: LRU cache + tests 93 1.06 → 1.03 s – 69.4 → 72.3 2.68 → 2.78
2 Short prose: 2,000-word story 72 0.86 → 0.82 s – 55.2 → 56.1 1.82 → 1.80
3 Long code A: tests for a 59 KB Python file 15,940 14.41 → 7.53 s 1,106 → 2,118 79.2 → 84.2 2.74 → 2.85
4 Long code B: same file again 15,939 11.55 → 6.27 s 1,380 → 2,541 79.3 → 84.4 2.72 → 2.81
5 Long prose A: essay from 90 KB of docs 23,218 24.33 → 15.17 s 954 → 1,531 61.4 → 62.2 2.10 → 2.07
6 Long prose B: same text again 23,219 17.52 → 11.04 s 1,325 → 2,103 61.3 → 62.0 2.04 → 2.04
7 Long code C: a different 59 KB file 16,148 14.17 → 7.27 s 1,139 → 2,221 81.7 → 82.2 2.73 → 2.79
8 Long prose C: a different 90 KB of docs 23,870 23.84 → 14.37 s 1,001 → 1,661 61.2 → 63.7 2.11 → 2.17
9 Short code, 2nd run 91 0.81 → 0.79 s – 74.8 → 78.6 2.71 → 2.76
10 Short code, 3rd run 93 0.69 → 0.78 s – 79.2 → 78.8 2.77 → 2.71
  • Prefill: a new 16-24K-token prompt now runs at 1,530-2,220 tok/s, so 16K tokens of code take about 7.5 s (was 14 s) and 23-24K tokens of prose about 15 s (was 24 s). The same text again runs at 2,100-2,540 tok/s.
  • Decode: each step takes 32-38.5 ms (was 33-38.6 ms), and code still gets about 2.8 tokens per step from DSpark against 1.8-2.2 for prose.
  • Longer prompts on real text: about 2,300-2,400 tok/s from 32K up to 77K tokens, best of two runs (a 77K-token prompt in about 32 s). In a longer coding session, a 31K-token prompt went in at about 2,100 tok/s and the 40K-token answer averaged about 90 tok/s.
  • Quality: identical answers on a 42-question check (exact-answer questions, Python tasks run against unit tests, math with thinking on), with decode-vs-prefill log-prob agreement and perplexity unchanged.

What changed

  • 16K-token prompt chunks. Streaming the experts to the GPU costs about the same per chunk whatever its size, so bigger chunks are faster. Fitting them in the 80 GiB budget took memory work: attention writes its output over its query, each layer's residual is freed as soon as it is used, expert batches are bounded, and fragmented cached memory is returned once per chunk.
  • Faster prefill kernels. A fused Triton kernel computes the sparse-attention indexer's scores without the large intermediate. Block-FP8 GEMM tiles are tuned for the RTX PRO 6000, taking the dense projections from about 111 to 310-347 TFLOPS with bit-identical output. The streamed pipeline overlaps more: experts already on the GPU run between uploads, and the next layer's first expert groups are staged ahead.
  • Engram lookups off the critical path. During prefill a worker thread reads each Engram layer's rows from the memory-mapped GGUF while the GPU runs the layers before it.
  • Decode. kt-kernel splits each layer's CPU expert work per expert and slice for better load balance, and the decode-sized FP8 GEMV tiles are tuned. Decode CUDA graphs are also captured bounded by context length: the indexer used to score every position up to the configured context on every step, so a large context setting slowed decoding even for short conversations. Each step now replays the smallest graph that fits the conversation (16K, 64K, 256K or full). This matters most at large context settings: at 1M it cut the time per decode step by 6-8%.
<!-- gh-comment-id:6344507159 --> @disdrix commented on GitHub (Oct 22, 2026): I had Opus 5.5 optimize for 8 hours again to squeeze more speed out of it. I am going to call these results final. More information on what I did is included so you can try to copy what I did with your specific setup. Since this is Windows 10, you could expect an increase of 10-15% on Linux. **Update: faster prompt processing, slightly faster decode (same hardware, same tests)** Summary: on a new 16-24K-token prompt, prefill now runs at 1,530-2,220 tok/s (was 950-1,140), and time to first token is 37-49% shorter. Decode is up to 6% faster (9 of the 10 runs faster, one even): about 82-84 tok/s on code and 62-64 tok/s on prose. Output quality is unchanged. Setup changes since the first post (hardware, model and the rest of the server are the same): - 70 of the 384 routed experts per layer on the GPU (was 71; the bigger prompt chunks need the memory). - Chunked prefill 16384 (was 8192). - 128K context as before. The launcher's default is now the model's full 1M; the numbers below use `-c 131072` to match the first post. ### Results Same harness and prompts as the first post: one request at a time, thinking off, streamed through the OpenAI-compatible API over the LAN, a fresh nonce line per request, runs in the order they ran, on a freshly started server. | # | Test | Prompt tokens | Time to first token | Prefill (tok/s) | Decode (tok/s) | Tokens per step | |---|---|---|---|---|---|---| | 1 | Short code: LRU cache + tests | 93 | 1.06 → 1.03 s | – | 69.4 → 72.3 | 2.68 → 2.78 | | 2 | Short prose: 2,000-word story | 72 | 0.86 → 0.82 s | – | 55.2 → 56.1 | 1.82 → 1.80 | | 3 | Long code A: tests for a 59 KB Python file | 15,940 | 14.41 → 7.53 s | 1,106 → 2,118 | 79.2 → 84.2 | 2.74 → 2.85 | | 4 | Long code B: same file again | 15,939 | 11.55 → 6.27 s | 1,380 → 2,541 | 79.3 → 84.4 | 2.72 → 2.81 | | 5 | Long prose A: essay from 90 KB of docs | 23,218 | 24.33 → 15.17 s | 954 → 1,531 | 61.4 → 62.2 | 2.10 → 2.07 | | 6 | Long prose B: same text again | 23,219 | 17.52 → 11.04 s | 1,325 → 2,103 | 61.3 → 62.0 | 2.04 → 2.04 | | 7 | Long code C: a different 59 KB file | 16,148 | 14.17 → 7.27 s | 1,139 → 2,221 | 81.7 → 82.2 | 2.73 → 2.79 | | 8 | Long prose C: a different 90 KB of docs | 23,870 | 23.84 → 14.37 s | 1,001 → 1,661 | 61.2 → 63.7 | 2.11 → 2.17 | | 9 | Short code, 2nd run | 91 | 0.81 → 0.79 s | – | 74.8 → 78.6 | 2.71 → 2.76 | | 10 | Short code, 3rd run | 93 | 0.69 → 0.78 s | – | 79.2 → 78.8 | 2.77 → 2.71 | - Prefill: a new 16-24K-token prompt now runs at 1,530-2,220 tok/s, so 16K tokens of code take about 7.5 s (was 14 s) and 23-24K tokens of prose about 15 s (was 24 s). The same text again runs at 2,100-2,540 tok/s. - Decode: each step takes 32-38.5 ms (was 33-38.6 ms), and code still gets about 2.8 tokens per step from DSpark against 1.8-2.2 for prose. - Longer prompts on real text: about 2,300-2,400 tok/s from 32K up to 77K tokens, best of two runs (a 77K-token prompt in about 32 s). In a longer coding session, a 31K-token prompt went in at about 2,100 tok/s and the 40K-token answer averaged about 90 tok/s. - Quality: identical answers on a 42-question check (exact-answer questions, Python tasks run against unit tests, math with thinking on), with decode-vs-prefill log-prob agreement and perplexity unchanged. ### What changed - **16K-token prompt chunks.** Streaming the experts to the GPU costs about the same per chunk whatever its size, so bigger chunks are faster. Fitting them in the 80 GiB budget took memory work: attention writes its output over its query, each layer's residual is freed as soon as it is used, expert batches are bounded, and fragmented cached memory is returned once per chunk. - **Faster prefill kernels.** A fused Triton kernel computes the sparse-attention indexer's scores without the large intermediate. Block-FP8 GEMM tiles are tuned for the RTX PRO 6000, taking the dense projections from about 111 to 310-347 TFLOPS with bit-identical output. The streamed pipeline overlaps more: experts already on the GPU run between uploads, and the next layer's first expert groups are staged ahead. - **Engram lookups off the critical path.** During prefill a worker thread reads each Engram layer's rows from the memory-mapped GGUF while the GPU runs the layers before it. - **Decode.** kt-kernel splits each layer's CPU expert work per expert and slice for better load balance, and the decode-sized FP8 GEMV tiles are tuned. Decode CUDA graphs are also captured bounded by context length: the indexer used to score every position up to the configured context on every step, so a large context setting slowed decoding even for short conversations. Each step now replays the smallest graph that fits the conversation (16K, 64K, 256K or full). This matters most at large context settings: at 1M it cut the time per decode step by 6-8%.
Author
Owner

@05yuki commented on GitHub (Oct 22, 2026):

A finding from checking our 1017 setup against the checkpoint's own reference implementation, in case it affects yours.

Routed experts scaled twice under KTransformers (SGLang main after sgl-project/sglang#44993, 2026-10-20).

  • Since #44993, the top-k folds routed_scaling_factor into its weights whenever the quant method says so. KTEPWrapperMethod forwards that question to its GPU method, and the MXFP4 methods answer yes.
  • DeepseekV2MoE still multiplied the routed output by the factor for every KT layer. So on V4.1 every MoE layer added its routed experts at 2.25x instead of 1.5x.
  • The model still writes fluently, which is why it went unnoticed. Mean NLL on 24 × 480 tokens was 2.878, against 2.594 from the reference; with the fix it is 2.596.
  • Fix: sgl-project/sglang#45810 (one condition). It has been in https://github.com/05yuki/deepseek-v4.1-flash-1017-ktransformers since tag 2026-10-22-2112.
  • Quick check of a build: deepseek_v2.py still has or isinstance(self.experts.quant_method, KTEPWrapperMethod) without and not self.routed_scaling_factor_fused_in_topk.

How we checked. inference/model.py, streamed one layer at a time through a 16 GB card, scores those 11.5K tokens in about 5 minutes. We dumped the hidden states of piece 0 at matching points in both engines and compared them layer by layer.

<!-- gh-comment-id:6344748055 --> @05yuki commented on GitHub (Oct 22, 2026): A finding from checking our 1017 setup against the checkpoint's own reference implementation, in case it affects yours. **Routed experts scaled twice under KTransformers (SGLang main after sgl-project/sglang#44993, 2026-10-20).** - Since #44993, the top-k folds `routed_scaling_factor` into its weights whenever the quant method says so. `KTEPWrapperMethod` forwards that question to its GPU method, and the MXFP4 methods answer yes. - `DeepseekV2MoE` still multiplied the routed output by the factor for every KT layer. So on V4.1 every MoE layer added its routed experts at 2.25x instead of 1.5x. - The model still writes fluently, which is why it went unnoticed. Mean NLL on 24 × 480 tokens was 2.878, against 2.594 from the reference; with the fix it is 2.596. - Fix: sgl-project/sglang#45810 (one condition). It has been in https://github.com/05yuki/deepseek-v4.1-flash-1017-ktransformers since tag `2026-10-22-2112`. - Quick check of a build: `deepseek_v2.py` still has `or isinstance(self.experts.quant_method, KTEPWrapperMethod)` without `and not self.routed_scaling_factor_fused_in_topk`. **How we checked.** `inference/model.py`, streamed one layer at a time through a 16 GB card, scores those 11.5K tokens in about 5 minutes. We dumped the hidden states of piece 0 at matching points in both engines and compared them layer by layer.
Author
Owner

@disdrix commented on GitHub (Oct 22, 2026):

Thanks for the detailed write-up! We hit the same double scaling on 10-20 and fixed it on our side by having the KT wrapper keep the top-k weights unscaled (it answers "no" to fusing routed_scaling_factor), so DeepseekV2MoE applies the 1.5x once after the CPU+GPU sum. All the numbers in both posts are with that fix. Note that your quick check would flag our build anyway, since we kept the KTEPWrapperMethod line in deepseek_v2.py and fixed it in the wrapper instead. Would you share the 24 x 480-token text you scored? I'd like to run it on our build and compare with your reference NLL of 2.594.

<!-- gh-comment-id:6345117319 --> @disdrix commented on GitHub (Oct 22, 2026): Thanks for the detailed write-up! We hit the same double scaling on 10-20 and fixed it on our side by having the KT wrapper keep the top-k weights unscaled (it answers "no" to fusing routed_scaling_factor), so DeepseekV2MoE applies the 1.5x once after the CPU+GPU sum. All the numbers in both posts are with that fix. Note that your quick check would flag our build anyway, since we kept the KTEPWrapperMethod line in deepseek_v2.py and fixed it in the wrapper instead. Would you share the 24 x 480-token text you scored? I'd like to run it on our build and compare with your reference NLL of 2.594.
Author
Owner

@05yuki commented on GitHub (Oct 22, 2026):

A second finding, now in the 1017 repo (tag 2026-10-23-0135): on SM120 (RTX 50, RTX PRO 6000), SGLang runs V4.1 with the V4 KV layout.

  • What V4.1 defines. The window KV is all 512 values in FP8 blocks of 32, RoPE included. The ratio-1/2 compressed KV is FP4 blocks of 16. The checkpoint's reference and DeepSeek's DeepInfer agree on both.
  • What SGLang does. It has both layouts but allows them only on SM100. SM120 keeps RoPE in bf16 and uses FP8 blocks of 64.
  • The fix. The new tier-0 patch 11-v41-kv-layout (sgl-project/sglang#45851) reads the V4.1 layouts on SM120 through FlashInfer's SM120 sparse MLA. It uses FlashInfer's full-BF16 route; the default route rounds Q and P to MXFP8.
  • Results.
    • Layer 0's attention output moves from 3.8% off the reference to 1.0%.
    • A compressed-KV token takes 288 bytes instead of 584.
    • Decode attention takes 10-11 µs a call instead of 16-18.
  • One thing to watch if you use a free-memory floor for GPU experts. The smaller pool leaves fewer free KV pages. Our hot lend counted free pages alone against its floor and lent nothing until we counted the driver's idle memory too.

On 2× RTX 5070 Ti, decode went from 45.6 to 47.1 tok/s on Japanese prose and rose on every other text. On one card it went from 28.5 to 32.0.

<!-- gh-comment-id:6346479165 --> @05yuki commented on GitHub (Oct 22, 2026): A second finding, now in the 1017 repo (tag `2026-10-23-0135`): **on SM120 (RTX 50, RTX PRO 6000), SGLang runs V4.1 with the V4 KV layout.** - **What V4.1 defines.** The window KV is all 512 values in FP8 blocks of 32, RoPE included. The ratio-1/2 compressed KV is FP4 blocks of 16. The checkpoint's reference and DeepSeek's DeepInfer agree on both. - **What SGLang does.** It has both layouts but allows them only on SM100. SM120 keeps RoPE in bf16 and uses FP8 blocks of 64. - **The fix.** The new tier-0 patch `11-v41-kv-layout` (sgl-project/sglang#45851) reads the V4.1 layouts on SM120 through FlashInfer's SM120 sparse MLA. It uses FlashInfer's full-BF16 route; the default route rounds Q and P to MXFP8. - **Results.** - Layer 0's attention output moves from 3.8% off the reference to 1.0%. - A compressed-KV token takes 288 bytes instead of 584. - Decode attention takes 10-11 µs a call instead of 16-18. - **One thing to watch if you use a free-memory floor for GPU experts.** The smaller pool leaves fewer free KV pages. Our hot lend counted free pages alone against its floor and lent nothing until we counted the driver's idle memory too. On 2× RTX 5070 Ti, decode went from 45.6 to 47.1 tok/s on Japanese prose and rose on every other text. On one card it went from 28.5 to 32.0.
Author
Owner

@disdrix commented on GitHub (Oct 22, 2026):

Our Windows build also runs V4.1 with the V4 KV layout on the RTX PRO 6000 (584 bytes/token, FP8 blocks of 64, bf16 RoPE), with our own Triton attention kernels since FlashInfer isn't available there.

<!-- gh-comment-id:6346882332 --> @disdrix commented on GitHub (Oct 22, 2026): Our Windows build also runs V4.1 with the V4 KV layout on the RTX PRO 6000 (584 bytes/token, FP8 blocks of 64, bf16 RoPE), with our own Triton attention kernels since FlashInfer isn't available there.
Author
Owner

@mtcl commented on GitHub (Oct 25, 2026):

I'd like to try this on my 2x6kpro with Intel Xeon 3495x and 512gb ddr5. What would be the easiest way to follow this?

<!-- gh-comment-id:6394418969 --> @mtcl commented on GitHub (Oct 25, 2026): I'd like to try this on my 2x6kpro with Intel Xeon 3495x and 512gb ddr5. What would be the easiest way to follow this?
Author
Owner

@05yuki commented on GitHub (Oct 25, 2026):

@mtcl The self-contained recipe is https://github.com/05yuki/deepseek-v4.1-flash-1017-ktransformers: download the checkpoint, then MODEL=<checkpoint dir> ./setup.sh builds the patched SGLang + kt-kernel and MODEL=<checkpoint dir> ./serve.sh starts it. serve.sh sets tensor parallelism from the GPUs it sees, so two cards give TP2; RTX PRO 6000 is SM120, the same architecture the results were measured on.

One caveat: every number in the README comes from AMD Zen 2. On Sapphire Rapids the experts run on the same AVX2 kernels (the CPU has them too), but the recipe hasn't been run on Intel yet, so please open an issue there with the serve.sh log if anything misbehaves.

<!-- gh-comment-id:6395951763 --> @05yuki commented on GitHub (Oct 25, 2026): @mtcl The self-contained recipe is https://github.com/05yuki/deepseek-v4.1-flash-1017-ktransformers: download the checkpoint, then `MODEL=<checkpoint dir> ./setup.sh` builds the patched SGLang + kt-kernel and `MODEL=<checkpoint dir> ./serve.sh` starts it. serve.sh sets tensor parallelism from the GPUs it sees, so two cards give TP2; RTX PRO 6000 is SM120, the same architecture the results were measured on. One caveat: every number in the README comes from AMD Zen 2. On Sapphire Rapids the experts run on the same AVX2 kernels (the CPU has them too), but the recipe hasn't been run on Intel yet, so please open an issue there with the serve.sh log if anything misbehaves.
Author
Owner

@disdrix commented on GitHub (Oct 25, 2026):

17. I'd like to try this on my 2x6kpro with Intel Xeon 3495x and 512gb ddr5. What would be the easiest way to follow this?

I ended up throwing what was available in to Opus 5.5 with claude code and had it do it all for me. I then had it run around 16 hours of optimizations trying different things. It would be much faster for you if you did it since I basically give you a recipe with all the things I did.

<!-- gh-comment-id:6397643864 --> @disdrix commented on GitHub (Oct 25, 2026): > 17\. I'd like to try this on my 2x6kpro with Intel Xeon 3495x and 512gb ddr5. What would be the easiest way to follow this? I ended up throwing what was available in to Opus 5.5 with claude code and had it do it all for me. I then had it run around 16 hours of optimizations trying different things. It would be much faster for you if you did it since I basically give you a recipe with all the things I did.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kvcache-ai/ktransformers#1304
No description provided.