Skip to content

sm_75: the prompt path's top-k with one full read of the scores (identical ids) - #2093

Closed
rafatxf wants to merge 2 commits into
Niko1221:mainfrom
rafatxf:pr/sm75-prompt-topk-sample
Closed

rafatxf wants to merge 2 commits into
Niko1221:mainfrom
rafatxf:pr/sm75-prompt-topk-sample

Conversation

@rafatxf

@rafatxf rafatxf commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Builds on #2072 (it sits next to that PR's split top-k in qsa_select.cu); the first commit here is #2072's, the second is this one.

Past ~115K cells the prompt path's top-k is DRAM traffic on Turing: block_topk_wide_kernel reads a query's scores six times (four radix passes, a count, the emit), 400 KB a query at 400K, 256 queries a call.

block_topk_sample_kernel reads them once:

  1. a bound LB: the KS-th largest key among every 32nd block (KS = 2 × the selection's blocks / 32 + 16);
  2. one read of all blocks, keeping those with key ≥ LB in shared memory;
  3. when those hold at least width cells, the width-th largest cell key (block_topk_kernel's threshold) is ≥ LB, so every block at or above the threshold is a candidate: the threshold, above and the tie budget computed over the candidates with block_topk_kernel's rule are the reference's, and the candidates sorted by block (bitonic) emit the reference's cells in its order;
  4. otherwise (too few cells above LB, or more than 4,096 candidates) the query is flagged and block_topk_wide_kernel<true> runs it - the all-equal-scores case of the parity test takes that path.

Used for prompt calls above 28,672 active blocks (below, the wide kernel is as fast); STRATA_TOPK_SAMPLE=0 keeps the wide kernel.

Setup

RTX 2080 Ti 22 GB (sm_75), i7-6700, CUDA 13.0, driver 595, SM 1650 MHz, 240 W; v0.1.43 + #2072, -DSTRATA_BUILD_TESTS=ON. Model Q2_0, --max-context 524288, --kv int8 --kv-resident 32768, STRATA_SELECT_SIMT=2 (#2071).

qsa_topk_parity CTX 256 20 524288 1 (a prompt call), dispatched time

context wide sample
98K 0.397 ms 0.396 (wide: below the bound)
131K 0.513 0.452
262K 0.952 0.589
400K 1.476 0.706
520K 1.840 0.787

qsa_topk_parity --selftest: PASS (continuous, 64-level and equal scores; identical ids). qsa_topk_active_parity (STRATA_TOPK_ACTIVE_ANY=1): 20 cases, identical.

Engine

First logits after a 196K prompt: bitwise identical to the run without it (STRATA_DUMP_FIRST_LOGITS). The gain sits at depth: with this, #2071 and #2091 and #2092, appending 16K tokens to a 390K conversation (--serve) went 15.9 → 12.9 s here.

🤖 Generated with Claude Code

rafatxf and others added 2 commits October 12, 2026 08:04
…ing scorer (bitwise)

A verify window's 1-8 queries scored and selected on a handful of SMs; at
420K cells on an RTX 2080 Ti the two took ~6 ms of a ~52 ms window.

- block_scores_halve_kernel: the decode scorer's warp sums as one
  recursive-halving reduction (each value pairs lane l with l ^ o at offsets
  16..1, the butterfly's additions operand for operand), the lane's query
  values in registers (shared-memory reads were the limit, ncu mio_throttle),
  5-8 queries as two calls of up to 4. Same bits (qsa_select_bench
  QSA_BENCH_DECODE=1 hashes, 32K-524K, 1-8 queries). 420K: 1 / 3 / 4 / 6 / 8
  queries 117 / 265 / 347 / 510 / 774 -> 101 / 118 / 125 / 238 / 255 us.
  STRATA_SCORES_HALVE=0 keeps the old kernel.
- qsa_block_topk_split: the decode top-k over chunks of 2,048 blocks in six
  launches (four radix passes into per-query global histograms, a count, the
  emit); every CTA takes each digit from the complete histogram with
  block_topk_kernel's rule, so the ids are identical (qsa_topk_parity
  --selftest: continuous, 64-level and equal scores, 2K-524K). Decode calls
  at 420K: 0.21 -> 0.033-0.064 ms; short contexts unchanged within 0.01 ms.
  STRATA_TOPK_SPLIT=0 keeps the wide kernel (=1: on any card, for tests).

Both on sm_75 only by default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tical ids)

block_topk_wide_kernel reads a query's scores six times (four radix passes,
a count, the emit): past ~115K cells that is DRAM traffic. block_topk_sample_kernel
takes a bound from every 32nd block (the KS-th largest of those keys), reads
all blocks once keeping those at or above it, and when they hold at least
`width` cells computes block_topk_kernel's threshold, `above` and tie budget
over them and emits them in block order (bitonic sort): every block at or
above the threshold is a candidate, so the ids are the reference's.
Otherwise the query is flagged and block_topk_wide_kernel<true> runs it.
qsa_topk_parity --selftest and qsa_topk_active_parity: identical ids
(continuous, 64-level, equal scores - the last ones take the fallback).

RTX 2080 Ti, 256 prompt queries under a 512K capacity: 131K 0.51 -> 0.45 ms,
262K 0.95 -> 0.59, 400K 1.48 -> 0.71, 520K 1.84 -> 0.79. Used above 28,672
active blocks; STRATA_TOPK_SAMPLE=0 keeps the wide kernel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xrip added a commit to xrip/Strata that referenced this pull request Oct 12, 2026
…mpt read, first logits byte-identical

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Niko1221 pushed a commit that referenced this pull request Oct 17, 2026
… (identical ids)


block_topk_wide_kernel reads a query's scores six times (four radix passes,
a count, the emit): past ~115K cells that is DRAM traffic. block_topk_sample_kernel
takes a bound from every 32nd block (the KS-th largest of those keys), reads
all blocks once keeping those at or above it, and when they hold at least
`width` cells computes block_topk_kernel's threshold, `above` and tie budget
over them and emits them in block order (bitonic sort): every block at or
above the threshold is a candidate, so the ids are the reference's.
Otherwise the query is flagged and block_topk_wide_kernel<true> runs it.
qsa_topk_parity --selftest and qsa_topk_active_parity: identical ids
(continuous, 64-level, equal scores - the last ones take the fallback).

RTX 2080 Ti, 256 prompt queries under a 512K capacity: 131K 0.51 -> 0.45 ms,
262K 0.95 -> 0.59, 400K 1.48 -> 0.71, 520K 1.84 -> 0.79. Used above 28,672
active blocks; STRATA_TOPK_SAMPLE=0 keeps the wide kernel.
Niko1221 added a commit that referenced this pull request Oct 17, 2026
#2222) are opt-in with STRATA_SM75_PERF=1


No sm_75 card is available here to confirm the series, so the kernels that were on by default for compute capability 7.5
in the submitted PRs now follow one switch, include/strata/core/sm75_series.hpp.  Each keeps its own variable, which
wins in both directions.  The one-read prompt top-k (#2093) was on for every CUDA card as submitted; it is now Turing only
like the rest.  STRATA_EMULATE_CC=75 counts as a Turing card, so a 75-virtual build on an sm_86 GPU runs the series for tests.
@Niko1221

Copy link
Copy Markdown
Owner

Thanks rafatxf, good work. We ported this with your authorship kept. We have no Turing card, so we could not confirm the speed. What we did check: the default path is byte-identical to before (identity gate on RTX 3060, P100 and A4000), and with the new path forced on, including a build that runs the real sm_75 device code on an sm_86 card, the gate is identical too. Because we cannot measure it on sm_75 it ships as opt-in (STRATA_SM75_PERF=1, plus your own variable STRATA_TOPK_SAMPLE), documented in docs/DETAILS.md. If a Turing owner confirms the gain we will consider turning it on by default.

xrip added a commit to xrip/Strata that referenced this pull request Oct 17, 2026
Conflicts: upstream took the Turing kernels this fork had cherry-picked (Niko1221#1821 Niko1221#1825 Niko1221#2069-Niko1221#2072 Niko1221#2081 Niko1221#2091 Niko1221#2093),
now opt-in with STRATA_SM75_PERF=1: their versions kept (native_mmvq, qsa_select, qsa_prompt_attn, prefill, gemm,
kernels). Upstream's sm_75 table replaces ours (STRATA_SM75_TABLE gone; STRATA_MMVQ_IL_ROWS can set rows).
conversation_cache.hpp: the disk tier's on_evict on upstream's serial-aware evict_oldest. generate.cpp: the disk
tier's read-back before Niko1221#1164's borrow (a disk restore never borrows), --conversation-cache-park-gain beside the
disk options. CMakeLists: prefill_mmq_bench kept beside upstream's new tests. Tokenizer: our long-text cache beside
max_special_len.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rafatxf

rafatxf commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Ported in 0.1.45, thanks! Turing numbers for STRATA_SM75_PERF (2x RTX 2080 Ti, Q2_0 and IQ3_S) are in #2069. Closing.

@rafatxf rafatxf closed this Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants