Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
174 commits
Select commit Hold shift + click to select a range
c7b19da
Fix DeepSeek V4 MLA prefix cache reuse
jasl May 6, 2026
0cc91bf
Add Blackwell tuning config aliases
jasl May 5, 2026
b7c8123
Add portable sparse MLA Triton kernels
jasl May 6, 2026
d3c3739
Add DeepSeek V4 SM12x fallback ops
jasl May 6, 2026
f8f18ce
Route SM12x DeepGEMM fallbacks
jasl May 6, 2026
cbb6995
Wire SM12x sparse MLA into DeepSeek V4
jasl May 6, 2026
b32ed20
Reduce DeepSeek V4 load overhead on GB10
jasl May 6, 2026
e46bd0d
Apply weight filter to fast safetensors loading
jasl May 6, 2026
f809e14
Warm DeepSeek V4 startup kernels
jasl May 5, 2026
e47bdd1
Add SM12x sparse MLA direct decode kernels
jasl May 6, 2026
14be6ce
Stabilize DeepSeek V4 MTP scheduling
jasl May 5, 2026
28714ae
Warm DeepSeek V4 MTP spec-decode kernels
jasl May 8, 2026
8102228
Tune dense FP8 block-scaled GEMM configs for SM12x DSv4
jasl May 11, 2026
919365f
T1-D: adaptive BLOCK_M for _fp8_paged_mqa_logits_kernel (SM12x)
jasl May 11, 2026
326b480
T2-A: clamp BLOCK_D in sparse MLA finish kernel to head_dim
jasl May 11, 2026
5530c37
Extend DeepSeek V4 prefill warmup to max single-chunk size
jasl May 12, 2026
c7653d8
Extend DeepSeek V4 warmup coverage to multi-request shapes
jasl May 12, 2026
a6c292a
Restore rowwise paged-MQA logits kernel for SM12x long context
jasl May 13, 2026
a042a41
reasoning: defensive implicit </think> for DeepSeek V4 tool-call stre…
jasl May 13, 2026
411a2de
sm12x: keep @torch.compile on HC head reduction via free-function wra…
jasl May 14, 2026
21d46ac
sm12x: drop multi-request prefill warmup that crashes CUTeDSL kv-gather
jasl May 14, 2026
455c282
sm12x: drop vestigial cudagraph kill-switch on Triton sparse MLA
jasl May 14, 2026
6e4a037
sm12x: harden sparse_attn_indexer seq_lens slice with .contiguous()
jasl May 14, 2026
00ea0da
sm12x: autotune num_warps on fp8_einsum + fused_indexer_q kernels
jasl May 14, 2026
4ed8452
sm12x: autotune num_warps/num_stages on 3 sparse MLA accumulate kernels
jasl May 14, 2026
e8b14e2
sm12x: add 3 dense FP8 W8A8 Block configs for RTX PRO 6000 WS Edition
jasl May 14, 2026
1b69e3d
sm12x: cap C128A metadata kernel loop at effective_topk (no shape cha…
jasl May 14, 2026
5632e82
sm12x: per-token early-loop-exit on sparse MLA accumulate inner candi…
jasl May 15, 2026
47cc4c3
sm12x: docs cleanup pass 1 — clarify metadata + MLA manager docstrings
jasl May 15, 2026
dbed306
sm12x: docs cleanup pass 2 — dedupe _upcast_e8m0_to_fp32 + simplify s…
jasl May 15, 2026
1cf15d2
sm12x: docs cleanup pass 3 — drop tautological is_valid in 7 accumula…
jasl May 15, 2026
922e64b
sm12x: multi-head prefill accumulate kernel + drop fp8 einsum autotune
alexbi29 May 16, 2026
9c37c22
sm12x: add fused-MoE FP8 W8A8 Block configs for RTX PRO 6000 (4 shape…
jasl May 17, 2026
ff98c4b
sm120: use Triton MQA logits for direct topk fallback
jasl May 18, 2026
ef37fc5
sm120: use custom row topk for MQA fallback indices
jasl May 18, 2026
2ebb26f
sm120: widen FP8 MQA logits tile
jasl May 18, 2026
e072e20
sm120: increase FP8 MQA logits row tile
jasl May 18, 2026
1028229
Fix DeepSeek V4 MTP sparse SWA reordering
jasl May 19, 2026
c429c77
sm12x: update DeepSeek V4 fallback imports
jasl May 19, 2026
2fb99cc
tests: update DeepSeek V4 MegaMoE refactor assumptions
jasl May 19, 2026
f30a7af
Fix DeepSeek V4 MLA prompt cache protection
jasl May 19, 2026
0101ca2
Clean up DeepSeek V4 upstream rebase leftovers
jasl May 19, 2026
ca2dd98
Fix CUTeDSL availability probe
jasl May 19, 2026
95fc907
Fix DeepSeek V4 MTP small-batch graph hangs
jasl May 19, 2026
16c1667
Remove ineffective DeepSeek V4 mHC warmup
jasl May 19, 2026
97b7143
Tune SM120 FP8 MQA logits row tile
jasl May 19, 2026
a81282b
Clean up SM120 rebase leftovers
jasl May 20, 2026
52a0c19
Remove unused SM120 splitKV decode experiment
jasl May 20, 2026
a8bdc00
Limit long prefill chunks behind active decode
jasl May 21, 2026
129e129
Tighten mixed prefill cap for very long prompts
jasl May 21, 2026
a962bf1
Improve SM120 mixed prefill scheduling
jasl May 21, 2026
b02f683
Clean up DeepSeek V4 reasoning parser lint
jasl May 22, 2026
54cdf34
Add DeepSeek V4 prefix cache pressure regression
jasl May 23, 2026
75ba377
Keep hybrid prefix cache tail blocks
jasl May 23, 2026
ea52914
Stabilize SM12x sparse MLA long prefill
jasl May 24, 2026
44c90d2
Tune SM12x sparse MLA single prefill topk
jasl May 24, 2026
1059c81
Protect active decode from very long prefill
jasl May 25, 2026
650de06
Clean sparse SWA imports after rebase
jasl May 27, 2026
f169084
Guard SM120 FP4 sparse indexer dependency
jasl May 27, 2026
d727d81
Absorb SM120 external Marlin fixes
jasl May 27, 2026
93400dd
sm120: keep optimized MHC prenorm path without DeepGEMM
jasl May 28, 2026
fb9c4d3
sm12x: prune fallback tests and tuned config duplicates
jasl May 28, 2026
d6e4dcd
sm12x: clear MXFP4 loading cache after setup
jasl May 29, 2026
aaef91a
sm12x: drop obsolete MHC CustomOp wrapper
jasl May 29, 2026
ad26f8f
Protect running prefills from long prefill starvation
jasl May 31, 2026
6bce5c9
Add chunked SM120 direct MQA top-k fallback
jasl May 31, 2026
db3a71f
Protect later running decodes from long prefill starvation
jasl Jun 1, 2026
6dac492
Protect very-long prefill fairness
jasl Jun 1, 2026
1381809
sm12x: avoid MHC prenorm GEMM JIT per token count
jasl Jun 1, 2026
56897a2
test: adapt DS4 prefix cache tests to scheduler block size
jasl Jun 2, 2026
6a576fa
fix: export DeepSeek V4 FusedMoE metadata
jasl Jun 3, 2026
52c549e
sched: defer very long prefill under decode pressure
jasl Jun 3, 2026
6e44ffa
sm12x: add sparse MLA prefill D512 split prototype
jasl Jun 2, 2026
42aade9
sm12x: warm high-concurrency MTP decode workspace
jasl Jun 3, 2026
1a3e89c
sm12x: enable indexed D512 sparse MLA prefill by default
jasl Jun 4, 2026
e4b8347
sm12x: retune D512 sparse MLA split tiles
jasl Jun 4, 2026
087c961
fix: align prefix cache manager signatures after rebase
jasl Jun 4, 2026
d3373c5
sm12x: skip empty D512 sparse MLA tail blocks
jasl Jun 4, 2026
dad43ed
sm12x: clean sparse MLA rebase leftovers
jasl Jun 5, 2026
05e4a1d
sm12x: restore DeepSeek V4 O-proj FP8 einsum layout
jasl Jun 5, 2026
25824b8
sm12x: restore Triton sparse MLA decode dispatch
jasl Jun 5, 2026
aac3bdf
config: skip breakable cudagraph auto-enable on SM121
jasl Jun 5, 2026
e1d6dc8
deepseek-v4: preserve ubatch prefill metadata
jasl Jun 5, 2026
08b329c
deepseek-v4: defunctionalize fused MLA insert op
jasl Jun 5, 2026
2aa21fb
deepseek-v4: enable chunked D512 sparse MLA prefill
jasl Jun 6, 2026
ed520ea
deepseek-v4: align sparse MLA metadata after upstream split
jasl Jun 7, 2026
03913e2
fix: sync DeepSeek V4 MoE metadata after runner refactor
jasl Jun 8, 2026
574905a
sched: admit cached long-prompt tails behind long prefill
jasl Jun 8, 2026
c601168
fix: clear stale prefix-cache block hashes on reuse
jasl Jun 8, 2026
79232ca
test: cover DeepSeek V4 RoutedExperts MXFP4 quant dispatch
jasl Jun 9, 2026
c828ef4
deepseek-v4: keep small C128A prefills on D512 path
jasl Jun 9, 2026
fde655c
fix: scale DeepSeek MLA prefix retention by sequence limit
jasl Jun 9, 2026
58d8270
fix: buffer split DeepSeek V4 DSML tool markers
jasl Jun 9, 2026
4b102a0
test: adapt DeepSeek V4 MoE metadata fixture to EPLB
jasl Jun 9, 2026
6ad66c4
sched: keep trace import out of stable path
jasl Jun 11, 2026
237e7e7
fix: restore DeepSeek V4 sparse MLA stats env
jasl Jun 11, 2026
2349559
fix: allow DeepSeek V4 chat stray think end
jasl Jun 13, 2026
c13cae0
sm12x: support FlashInfer CUTLASS MXFP4 opt-in
jasl Jun 2, 2026
99a9f10
deepseek_v4: route NVFP4-modelopt experts to ModelOptNvFp4FusedMoE
vedcsolution Jun 7, 2026
b4c1cab
deepseek_v4: allow flashinfer_cutlass NVFP4 MoE for SwiGLU-clamp models
jasl Jun 15, 2026
2c547ec
deepseek-v4: integrate upstream adaptive prefill chunk planning (#45061)
jasl Jun 15, 2026
a1135c0
deepseek-v4: guard sparse MLA decode metadata against None (mypy)
jasl Jun 15, 2026
59c7918
chore: restore mypy + ruff cleanliness after upstream rebase
jasl Jun 15, 2026
f91caa0
deepseek-v4: drop dead VLLM_DEEPSEEK_V4_SPARSE_MLA_STATS_PATH env
jasl Jun 15, 2026
627adee
feat: gated FlashInfer SM120 packed sparse-MLA decode for DeepSeek V4
jasl Jun 14, 2026
9ab2980
fix: pre-compile DeepSeek-V4 D512-split sparse-MLA prefill kernels at…
jasl Jun 16, 2026
dce4b3b
feat: restore DeepSeek-V4 API semantics on the SM120 min-enable base
jasl Jun 17, 2026
a8280db
fix: correct SM12x indexer prefill top-k non-contiguous output corrup…
jasl Jun 17, 2026
667205e
perf: skip throwaway copy in SM12x indexer top-k fallback
jasl Jun 17, 2026
edf0d37
feat: re-enable breakable cudagraph auto-enable on SM121 for DeepSeek-V4
jasl Jun 18, 2026
3476a25
chore: wrap SM12x indexer fallback line under ruff line-length
jasl Jun 18, 2026
285b542
fix(sm12x): eager-break DeepSeek-V4 attention under FULL cudagraph fo…
jasl Jun 19, 2026
20e1472
fix(v1): write-completion fence for prefix-cache block sharing
jasl Jun 19, 2026
6c92b09
perf(sm12x): default DeepSeek-V4 to FULL_AND_PIECEWISE (drop breakabl…
jasl Jun 19, 2026
f108fa9
fix(sm12x): COW shared final block for DeepSeek-V4 writable cache gro…
jasl Jun 19, 2026
67e060d
perf(sm12x): default indexed-D512 prefill min-token gate to 4096 (env…
jasl Jun 19, 2026
e57276b
revert: drop_eagle_block broadening — breaks prefix caching under MTP
jasl Jun 19, 2026
197d21e
fix(sm12x): int64 block offsets in indexer paged-MQA-logits kernel
jasl Jun 20, 2026
d782c62
feat(sm12x): env-gated packed FlashInfer sparse-MLA prefill for DeepS…
jasl Jun 20, 2026
c736caf
fix(sm12x): rebase compressed top-k per-request in FlashMLA prefill c…
jasl Jun 20, 2026
88ec87e
fix(sm12x): int64 block offsets in the non-rowwise paged-MQA-logits k…
jasl Jun 20, 2026
cefb2ea
chore(sm12x-audit): restore upstream multi-line form for VLLM_ENFORCE…
jasl Jun 20, 2026
21cc9ba
refactor(sm12x-audit): express breakable-cudagraph auto-enable as a r…
jasl Jun 20, 2026
a3615f7
chore(sm12x-audit): move out VLLM_NVFP4_GEMM_BACKEND b12x research lever
jasl Jun 20, 2026
7a4503a
refactor(sm12x-audit): move out scheduler prefill-fairness heuristics…
jasl Jun 20, 2026
364fd5c
refactor(sm12x-audit): remove prefix-cache write fence (int64 fix hol…
jasl Jun 20, 2026
dbcc0ff
fix(sm12x): slice packed-prefill output to num_prefill_tokens (84-vs-…
jasl Jun 22, 2026
4e2fd5a
fix(sm12x): cast MTP draft logits to float32 before top-k/top-p sampling
jasl Jun 22, 2026
5c778bb
Merge remote-tracking branch 'upstream/main' into reconcile/43477-merge
jasl Jun 22, 2026
3adf732
fix(sm120): don't auto-enable DeepGEMM on SM120 (pinned ref asserts)
jasl Jun 22, 2026
41c58a2
fix(sm120): gate #43477 prefill-SWA launch + clamp kernel OOB (infere…
jasl Jun 23, 2026
83f44fd
fix(sm12x): restore DeepSeek-V4 D512-split prefill warmup (stale import)
jasl Jun 23, 2026
3b8b81d
Merge upstream/main into reconcile (DSv4 SM12x sync 20260626)
jasl Jun 26, 2026
333ed0d
perf(sm12x): drop per-step positions.max().item() device sync in C128…
jasl Jun 26, 2026
2a9a806
fix(weight-loader): only set `yielded` after a tensor is emitted (GDS…
jasl Jun 26, 2026
fd76d65
feat(sm12x): default packed prefill on when SM120 FI backend active
jasl Jun 27, 2026
76dff54
fix(spec-decode): expand seeded draft generators for parallel drafting
jasl Jun 27, 2026
78e0a1b
fix(sm12x): warm full D512-split prefill width range to avoid first-p…
jasl Jun 27, 2026
8571496
fix(sm12x): de-constexpr o_proj einsum num_tokens to fix unaligned-pr…
jasl Jun 28, 2026
7c57303
feat(sm12x): warm DSv4-Flash MTP shared-head RMSNorm + greedy rejecti…
jasl Jun 28, 2026
367ed7c
fix(sm12x): address Codex P2 findings (NVFP4 scale load, DSML non-str…
jasl Jun 28, 2026
5d0e372
Merge upstream/main into reconcile (DSv4 SM12x sync 20260629)
jasl Jun 29, 2026
a5ebb5f
fix(sm12x): self-size C128A prefill workspace + de-dup warmup backend…
jasl Jun 29, 2026
486ceaf
Merge upstream/main (00eb7cefa3) into codex/ds4-sm120-min-enable
jasl Jul 1, 2026
d4bc5e2
Merge upstream/main (4787f2dd1b) into codex/ds4-sm120-min-enable
jasl Jul 1, 2026
ae76acb
Add DeepSeek V4 DSpark proposer
alexbi29 Jul 1, 2026
26a86f5
Share target embeddings with DSpark draft model
alexbi29 Jul 1, 2026
2c670df
Default DSpark fused Markov sampling
alexbi29 Jul 1, 2026
432d4ce
Address DSpark fused sampler review nits
alexbi29 Jul 1, 2026
5f2f019
Make DSpark fast path the default
alexbi29 Jul 1, 2026
d55a877
Trim unused speculative buffers
alexbi29 Jul 1, 2026
71a0383
Replace DSpark attention v1 kernel
alexbi29 Jul 1, 2026
c95e38c
Fix DSpark rebase integration
alexbi29 Jul 1, 2026
1273219
Fix DSpark worker integration after rebase
alexbi29 Jul 1, 2026
575106c
Trim DSpark scratch buffers
alexbi29 Jul 1, 2026
ed7bdcc
Skip incomplete CUDA runtime stubs
alexbi29 Jul 1, 2026
3d1b9b3
Merge pull request #25 from alexbi29/codex/dspark-pr-minimal-20260701
jasl Jul 2, 2026
ded0ec0
feat(dspark): default DSpark to the V1 runner as opt-in, fix V1 coexi…
jasl Jul 2, 2026
d01691a
Merge upstream/main (11 commits) into ds4-sm120 DSpark stack
jasl Jul 2, 2026
53b6d11
fix(dspark): reserve V2 padded Q scratch
alexbi29 Jul 2, 2026
9bccfd1
fix(sm12x): exact non-cooperative persistent_topk for <128KB-smem par…
jasl Jul 2, 2026
5b1b452
Merge commit '53b6d1110c' into test/pr26-v1-validation
jasl Jul 2, 2026
8197c0b
fix(dspark): wire padded-Q scratch reservation into V1 runner
jasl Jul 2, 2026
444fe3a
Merge upstream/main (22 commits) into codex/ds4-sm120-min-enable
jasl Jul 3, 2026
15434cb
fix(sm12x): stop per-shape Triton recompile leak in MQA-logits + rest…
jasl Jul 3, 2026
b43470e
fix(sm12x): also make tf32 prenorm gemm stride_outs/stride_sqs runtime
jasl Jul 3, 2026
9f18be7
perf(sm12x): 2-pass finer-histogram top-k for GB10 long-context (halv…
jasl Jul 4, 2026
616a572
perf(sm12x): drive sparse-MLA decode with valid top-k counts as seq_lens
jasl Jul 4, 2026
30283b9
feat(sm12x): default DSv4 sparse-MLA to the FlashInfer SM120 path
jasl Jul 5, 2026
fe486bf
[Bugfix][Structured Output][Spec Decode] Constrain bitmask and trim g…
yuyue0225sc Jul 4, 2026
2c4d620
Merge upstream/main (9fde043f54) into codex/ds4-sm120-min-enable
jasl Jul 6, 2026
ecdb060
Merge remote-tracking branch 'upstream/main' into codex/ds4-sm120-min…
jasl Jul 7, 2026
f41d6c6
chore(sm12x): pin flashinfer 0.6.14 (0.6.13 lacks SM120 sparse-MLA ke…
jasl Jul 7, 2026
d71b9aa
docs(sm12x): fix stale 'Default off' for DSv4 SM120 FI decode
jasl Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -942,19 +942,21 @@ static void launchFullCacheKernel(
// ────────────────────────────────────────────────────────────────────────────
// Torch op wrapper
// ────────────────────────────────────────────────────────────────────────────
torch::stable::Tensor fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert(
void fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert(
torch::stable::Tensor const& q_in, // [N, num_heads_q, 512] bf16
torch::stable::Tensor const& kv, // [N, 512] bf16 (read-only)
torch::stable::Tensor& q_out, // [N, q_head_padded, 512] bf16
torch::stable::Tensor& k_cache, // [num_blocks, block_bytes] uint8
torch::stable::Tensor const& slot_mapping, // [N] int64
torch::stable::Tensor const& position_ids, // [N] int64
torch::stable::Tensor const& cos_sin_cache, // [max_pos, rope_dim] bf16
int64_t q_head_padded, // padded Q head count for output
double eps, int64_t cache_block_size) {
STD_TORCH_CHECK(q_in.device().is_cuda() && q_in.is_contiguous(),
"q_in must be contiguous CUDA");
STD_TORCH_CHECK(kv.device().is_cuda() && kv.is_contiguous(),
"kv must be contiguous CUDA");
STD_TORCH_CHECK(q_out.device().is_cuda() && q_out.is_contiguous(),
"q_out must be contiguous CUDA");
STD_TORCH_CHECK(k_cache.device().is_cuda(), "k_cache must be CUDA");
STD_TORCH_CHECK(slot_mapping.device().is_cuda() &&
slot_mapping.scalar_type() ==
Expand All @@ -970,8 +972,13 @@ torch::stable::Tensor fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert(
STD_TORCH_CHECK(kv.dim() == 2 && kv.size(1) == 512, "kv shape [N, 512]");
STD_TORCH_CHECK(q_in.scalar_type() == kv.scalar_type(),
"q_in and kv dtype must match");
STD_TORCH_CHECK(q_head_padded >= q_in.size(1),
"q_head_padded must be >= q_in.size(1) (num_heads_q)");
STD_TORCH_CHECK(q_out.scalar_type() == q_in.scalar_type(),
"q_out dtype must match q_in");
STD_TORCH_CHECK(q_out.dim() == 3 && q_out.size(0) == q_in.size(0) &&
q_out.size(1) >= q_in.size(1) &&
q_out.size(2) == q_in.size(2),
"q_out shape [N, q_head_padded, 512] with "
"q_head_padded >= num_heads_q");
STD_TORCH_CHECK(k_cache.scalar_type() == torch::headeronly::ScalarType::Byte,
"k_cache must be uint8");
STD_TORCH_CHECK(cos_sin_cache.dim() == 2 && cos_sin_cache.size(1) == 64,
Expand All @@ -991,19 +998,14 @@ torch::stable::Tensor fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert(
STD_TORCH_CHECK(num_tokens_insert <= num_tokens_full,
"slot_mapping must not exceed q row count");
int const num_heads_q = static_cast<int>(q_in.size(1));
int const num_heads_q_padded = static_cast<int>(q_head_padded);
int const num_heads_q_padded = static_cast<int>(q_out.size(1));
int const cache_block_size_i = static_cast<int>(cache_block_size);
int const kv_block_stride = static_cast<int>(k_cache.stride(0));

const torch::stable::accelerator::DeviceGuard device_guard(
q_in.get_device_index());
const cudaStream_t stream = get_current_cuda_stream(q_in.get_device_index());

// Allocate the padded q output. The kernel writes every element (live
// region gets RMSNorm+RoPE; pad region gets zeros), so `empty` is safe.
auto q_out = torch::stable::new_empty(
q_in, {q_in.size(0), q_head_padded, q_in.size(2)}, q_in.scalar_type());

VLLM_STABLE_DISPATCH_HALF_TYPES(
q_in.scalar_type(), "fused_deepseek_v4_qnorm_rope_kv_insert", [&] {
using qkv_scalar_t = scalar_t;
Expand All @@ -1020,7 +1022,6 @@ torch::stable::Tensor fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert(
num_heads_q_padded, cache_block_size_i, kv_block_stride,
stream);
});
return q_out;
}

// ────────────────────────────────────────────────────────────────────────────
Expand Down
10 changes: 5 additions & 5 deletions csrc/libtorch_stable/moe/marlin_moe_wna16/ops.cu
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ void marlin_mm(const void* A, const void* B, void* C, void* C_tmp, void* b_bias,
cudaDeviceGetAttribute(&max_shared_mem,
cudaDevAttrMaxSharedMemoryPerBlockOptin, dev);
STD_TORCH_CHECK(max_shared_mem > 0);
int device_max_shared_mem = max_shared_mem;

int major_capability, minor_capability;
cudaDeviceGetAttribute(&major_capability, cudaDevAttrComputeCapabilityMajor,
Expand Down Expand Up @@ -527,10 +528,10 @@ void marlin_mm(const void* A, const void* B, void* C, void* C_tmp, void* b_bias,
}

cudaFuncSetAttribute(kernel, cudaFuncAttributeMaxDynamicSharedMemorySize,
max_shared_mem);
device_max_shared_mem);
// avoid ">>>" being formatted to "> > >"
// clang-format off
kernel<<<blocks, num_threads, max_shared_mem, stream>>>(
kernel<<<blocks, num_threads, sh_cache_size, stream>>>(
A_ptr, B_ptr, C_ptr, C_tmp_ptr, bias_ptr, a_s_ptr, b_s_ptr, g_s_ptr, zp_ptr, g_idx_ptr,
sorted_token_ids_ptr, expert_ids_ptr, num_tokens_past_padded_ptr,
topk_weights_ptr, top_k, mul_topk_weights, num_groups, prob_m,
Expand Down Expand Up @@ -708,9 +709,8 @@ torch::stable::Tensor moe_wna16_marlin_gemm(
torch::stable::Tensor c_tmp;
if (use_fp32_reduce && !use_atomic_add) {
// max num of threadblocks is sms * 4
long max_c_tmp_size = min(
(long)size_n * sorted_token_ids.size(0),
(long)sms * 4 * moe_block_size * MARLIN_NAMESPACE_NAME::max_thread_n);
long max_c_tmp_size =
(long)sms * 4 * moe_block_size * MARLIN_NAMESPACE_NAME::max_thread_n;
if (moe_block_size == 8) max_c_tmp_size *= 2;
c_tmp = torch::stable::new_empty(a, {max_c_tmp_size}, kFloat);
} else {
Expand Down
9 changes: 5 additions & 4 deletions csrc/libtorch_stable/ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,12 +262,13 @@ void fused_qk_norm_rope(torch::stable::Tensor& qkv, int64_t num_heads_q,
torch::stable::Tensor& position_ids,
int64_t forced_token_heads_per_warp);

torch::stable::Tensor fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert(
void fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert(
torch::stable::Tensor const& q_in, torch::stable::Tensor const& kv,
torch::stable::Tensor& k_cache, torch::stable::Tensor const& slot_mapping,
torch::stable::Tensor& q_out, torch::stable::Tensor& k_cache,
torch::stable::Tensor const& slot_mapping,
torch::stable::Tensor const& position_ids,
torch::stable::Tensor const& cos_sin_cache, int64_t q_head_padded,
double eps, int64_t cache_block_size);
torch::stable::Tensor const& cos_sin_cache, double eps,
int64_t cache_block_size);

void fused_deepseek_v4_qnorm_rope_kv_rope_full_cache_bf16_insert(
torch::stable::Tensor& q, torch::stable::Tensor const& kv,
Expand Down
191 changes: 183 additions & 8 deletions csrc/libtorch_stable/persistent_topk.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ struct PersistentTopKParams {
uint32_t chunk_size; // large path: elements per CTA
uint32_t ctas_per_group; // 1=medium, >1=large
uint32_t max_seq_len; // max seq_len across all rows (for early CTA exit)
// <128KB-smem parts (GB10/consumer-Blackwell): run every row on one CTA via
// the medium/decode selector, never the multi-CTA cooperative radix barrier.
bool force_noncooperative = false;
};

// ============================================================================
Expand All @@ -156,7 +159,21 @@ __device__ __forceinline__ uint32_t decode_bin(float x) {
return key >> 5;
}

// Forward declaration: the exact 4-round streaming radix (defined below) is the
// overflow fallback for the 2-pass (CacheBins=false) long-context path when the
// fp16 coarse bins cluster too densely to fit the tie buffer.
template <int TopK>
__device__ __noinline__ void histogram_streaming_topk(
const float* __restrict__ logits, int* __restrict__ output_indices,
int logits_offset, int seq_len);

// CacheBins=true (default, seq_len <= HIST2048_THRESHOLD): a single pass that
// caches every element's bin in per-thread registers (reg_bins), so Phase 2
// collects without re-reading global -- but reg_bins caps seq_len at ~8K.
// CacheBins=false: Phase 2 re-reads the row from global (no register cache), so
// there is no seq_len cap; used for the force_noncooperative GB10 long-context
// path as a 2-full-read exact top-k (vs the streaming radix's 4 reads).
template <int TopK, bool CacheBins = true>
__device__ __noinline__ void histogram_2048_topk(
const float* __restrict__ logits, int32_t* __restrict__ output_indices,
int32_t seq_len) {
Expand Down Expand Up @@ -206,10 +223,14 @@ __device__ __noinline__ void histogram_2048_topk(
const uint16_t b1 = static_cast<uint16_t>(decode_bin(v1));
const uint16_t b2 = static_cast<uint16_t>(decode_bin(v2));
const uint16_t b3 = static_cast<uint16_t>(decode_bin(v3));
reg_bins[nitems++] = b0;
reg_bins[nitems++] = b1;
reg_bins[nitems++] = b2;
reg_bins[nitems++] = b3;
if constexpr (CacheBins) {
// Only the register-cached (short-seq) path retains bins; the 2-pass
// path re-reads in Phase 2, so skip (reg_bins would overflow at long seq).
reg_bins[nitems++] = b0;
reg_bins[nitems++] = b1;
reg_bins[nitems++] = b2;
reg_bins[nitems++] = b3;
}
atomicAdd(&histo[b0], 1);
atomicAdd(&histo[b1], 1);
atomicAdd(&histo[b2], 1);
Expand Down Expand Up @@ -245,6 +266,19 @@ __device__ __noinline__ void histogram_2048_topk(

const int threshold = decode_smem[SBASE + sTHR];

if constexpr (!CacheBins) {
// The equal-to-threshold ties are buffered (capacity DBUF). histo[threshold]
// (still intact here -- Phase 2's bufs overlap it and haven't run) is the
// exact tie count; if it can't fit, the buffer would drop excess and
// under-select, so fall back to the exact streaming radix. On GB10 (max
// ~1.9M ctx, 2048 fp16 bins) this effectively never triggers, but it makes
// the 2-pass path provably exact for any distribution / seq_len.
if (histo[threshold] >= DBUF) {
histogram_streaming_topk<TopK>(logits, output_indices, 0, seq_len);
return;
}
}

// ---- Phase 2: Collection with warp-aggregated atomicAdds ----
int* bufs[2] = {decode_smem + BOFF, decode_smem + BOFF + DBUF};
const int sOUT_abs = SBASE + sOUT;
Expand All @@ -260,11 +294,32 @@ __device__ __noinline__ void histogram_2048_topk(
const bool vec_valid = (i < n_vec);
const int base_idx = i << 2;

// 2-pass path: re-load the row from global (Phase 1 didn't cache bins).
float rv0, rv1, rv2, rv3;
if constexpr (!CacheBins) {
if (vec_valid) {
if (row_aligned && base_idx + 3 < seq_len) {
load_float4(logits + base_idx, rv0, rv1, rv2, rv3);
} else {
load_float4_predicated(logits + base_idx, base_idx, seq_len, rv0,
rv1, rv2, rv3);
}
}
}

#pragma unroll 4
for (int sub = 0; sub < 4; sub++) {
const int elem_idx = base_idx + sub;
uint32_t bin = 0;
if (vec_valid) bin = reg_bins[item++];
if (vec_valid) {
if constexpr (CacheBins) {
bin = reg_bins[item++];
} else {
const float rv =
(sub == 0) ? rv0 : (sub == 1) ? rv1 : (sub == 2) ? rv2 : rv3;
bin = static_cast<uint16_t>(decode_bin(rv));
}
}
const bool is_above = vec_valid && (bin > uthr);
const bool is_equal = vec_valid && (bin == uthr);

Expand Down Expand Up @@ -592,6 +647,113 @@ __device__ __noinline__ void histogram_256_topk(
}
}

// Exact, non-cooperative, single-CTA streaming top-k for an arbitrarily long
// row. histogram_256_topk buffers equal-to-threshold candidates in a bounded
// smem list (MAX_BUFFERED_ITEMS); at a dense pivot that buffer overflows and
// the excess is dropped from both the buffer and the refinement histogram,
// yielding an approximate top-k. This variant never buffers: every radix round
// and the final collection re-stream the row from global, filtered by the
// accumulated prefix, so the result is EXACT (bit-identical value multiset to a
// full top-k; ties at the exact pivot value are broken arbitrarily, matching
// the cooperative radix path's contract). Fixed ~3 KB smem, no per-row cap.
// Used for the force_noncooperative (<128 KB smem, e.g. GB10) long-context path
// where the multi-CTA cooperative radix cannot fit its co-resident barrier.
template <int TopK>
__device__ __noinline__ void histogram_streaming_topk(
const float* __restrict__ logits, int* __restrict__ output_indices,
int logits_offset, int seq_len) {
extern __shared__ char medium_smem[];
int (*hist)[RADIX + 128] =
reinterpret_cast<int (*)[RADIX + 128]>(medium_smem);
int* scalars = reinterpret_cast<int*>(medium_smem + kMediumHistBytes);
int& shared_threshold_bin = scalars[0];
int& shared_output_count = scalars[1];
int& shared_final_k = scalars[2];

const int tid = threadIdx.x;
uint32_t prefix = 0u; // high bits of the pivot key pinned so far
int remaining_k = TopK; // rank (1-indexed) still sought within `prefix`

// 4 MSD-radix rounds over the 32-bit order-preserving key (8 bits/round).
for (int round = 0; round < 4; ++round) {
const int bit_offset = 24 - round * 8;
// Bits above the current byte (already pinned in prefix); 0 on round 0.
const uint32_t hi_mask =
(bit_offset + 8 >= 32) ? 0u : (~0u << (bit_offset + 8));

if (tid < RADIX + 1) hist[0][tid] = 0;
__syncthreads();

// Histogram this byte over elements whose higher bits match the prefix.
for (int idx = tid; idx < seq_len; idx += kThreadsPerBlock) {
const uint32_t key = convert_to_uint32_v2(logits[idx + logits_offset]);
if ((key & hi_mask) == (prefix & hi_mask)) {
atomicAdd(&hist[0][(key >> bit_offset) & 0xFF], 1);
}
}
__syncthreads();

// Suffix scan: hist[0][b] -> count of matching elements with byte >= b
// (Hillis-Steele over RADIX bins, ping-pong hist[0]/hist[1]).
#pragma unroll 8
for (int i = 0; i < 8; ++i) {
if (__builtin_expect(tid < RADIX, 1)) {
const int stride = 1 << i;
const int src = i & 1;
const int dst = src ^ 1;
int value = hist[src][tid];
if (tid < RADIX - stride) value += hist[src][tid + stride];
hist[dst][tid] = value;
}
__syncthreads();
}

// Strict pivot: the unique bin b with count(>=b) >= k && count(>=b+1) < k,
// so remaining_k -= count(>=b+1) stays >= 1 (no early-exit boundary case).
if (tid < RADIX && hist[0][tid] >= remaining_k &&
hist[0][tid + 1] < remaining_k) {
shared_threshold_bin = tid;
}
__syncthreads();

const int threshold_bin = shared_threshold_bin;
remaining_k -= hist[0][threshold_bin + 1]; // pin the strictly-greater byte
prefix |= (static_cast<uint32_t>(threshold_bin) << bit_offset);
__syncthreads();
}

// prefix == exact 32-bit pivot key; remaining_k == #elements equal to the
// pivot to emit (>= 1). Stream once more to collect (no buffer).
if (tid == 0) {
shared_output_count = 0;
shared_final_k = remaining_k;
}
__syncthreads();

// (1) elements strictly greater than the pivot -- all in the top-k.
for (int idx = tid; idx < seq_len; idx += kThreadsPerBlock) {
const uint32_t key = convert_to_uint32_v2(logits[idx + logits_offset]);
if (key > prefix) {
const int pos = atomicAdd(&shared_output_count, 1);
if (pos < TopK) output_indices[pos] = idx;
}
}
__syncthreads();

// (2) elements equal to the pivot, capped at remaining_k (arbitrary ties).
for (int idx = tid; idx < seq_len; idx += kThreadsPerBlock) {
const uint32_t key = convert_to_uint32_v2(logits[idx + logits_offset]);
if (key == prefix) {
const int slot = atomicAdd(&shared_final_k, -1);
if (slot > 0) {
const int pos = atomicAdd(&shared_output_count, 1);
if (pos < TopK) output_indices[pos] = idx;
}
}
}
__syncthreads();
}

// ============================================================================
// Inter-CTA sync primitives
// ============================================================================
Expand Down Expand Up @@ -881,7 +1043,11 @@ __global__ void __launch_bounds__(kThreadsPerBlock, 2)
if (blockIdx.x >= num_groups * ctas_per_group) return;

// Early exit: non-CTA-0 threads are never needed if no large rows exist
if (cta_in_group != 0 && params.max_seq_len <= RADIX_THRESHOLD) return;
// (force_noncooperative always launches ctas_per_group==1, so cta_in_group is
// always 0 there; the extra clause is defensive).
if (cta_in_group != 0 &&
(params.max_seq_len <= RADIX_THRESHOLD || params.force_noncooperative))
return;

uint32_t* local_histogram = reinterpret_cast<uint32_t*>(smem_raw);
uint32_t* suffix_sum = local_histogram + RADIX;
Expand Down Expand Up @@ -909,7 +1075,7 @@ __global__ void __launch_bounds__(kThreadsPerBlock, 2)
int32_t* row_output = params.output + row_idx * params.top_k;
const float* row_input = params.input + row_idx * params.stride;

if (seq_len <= RADIX_THRESHOLD) {
if (seq_len <= RADIX_THRESHOLD || params.force_noncooperative) {
if (cta_in_group == 0) {
if (seq_len <= static_cast<uint32_t>(TopK)) {
// Trivial case: seq_len <= TopK
Expand All @@ -919,8 +1085,17 @@ __global__ void __launch_bounds__(kThreadsPerBlock, 2)
}
} else if (seq_len <= static_cast<uint32_t>(HIST2048_THRESHOLD)) {
histogram_2048_topk<TopK>(row_input, row_output, seq_len);
} else {
} else if (seq_len <= RADIX_THRESHOLD) {
histogram_256_topk<TopK>(row_input, row_output, 0, seq_len);
} else {
// Only reachable when force_noncooperative (otherwise seq_len >
// RADIX_THRESHOLD takes the cooperative radix path below). 2-pass
// finer-histogram top-k (2048 bins, Phase 2 re-reads from global):
// 2 full-row reads + a bounded in-smem tie refinement, vs the
// streaming radix's 4 full-row reads. Exact; falls back to the
// streaming radix internally if the fp16 tie bin overflows the buffer.
histogram_2048_topk<TopK, /*CacheBins=*/false>(row_input, row_output,
seq_len);
}
}
continue;
Expand Down
Loading
Loading