Skip to content

[unified-memory] Stride the KV translate kernel and route every translate through it (6/7) - #40330

Merged
ch-wan merged 5 commits into
sgl-project:mainfrom
caihuali95:mainline/consolidate-translate-kv-loc
Oct 2, 2026
Merged

ch-wan merged 5 commits into
sgl-project:mainfrom
caihuali95:mainline/consolidate-translate-kv-loc

Conversation

@caihuali95

@caihuali95 caihuali95 commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

This is one of seven PRs split out of #38592. That PR was +2105/−1366 across 41
files, twelve of them attention backends on the serving hot path, and it interleaved
hardening, a layout change and dead-code deletion in a single diff — a reviewer could not
tell which hunk was safe-by-construction and which changed behaviour. #38592 is retained as the token-major
layout PR (3/7), which is the core of the series.

Stack — each PR is based on the one above it, and every one is green on its own:

# PR what it does size
1 #40326 derive KV row addresses from strides, not shapes 21 files, +733/−118
2 #40327 build paged KV views through one helper 13 files, +704/−47
3 #38592 token-major layout 36 files, +1215/−1108
4 #40328 mark the write loc physical and check it at every write door 45 files, +935/−132
5 #40329 remove the kernel-page multiplier plumbing 17 files, +117/−230
6 #40330 stride the KV translate kernel and route every translate through it 11 files, +361/−159
7 #40331 name the fused KV translate for what it computes 28 files, +119/−121

Review order is the table order. 1 and 2 are no-ops on today's contiguous pools (2 also
fixes how HND pools with more than one KV head are read); 3 is where the behaviour changes
and deserves the most attention; 4 adds the physical write-loc check; 5 is deletion; 6
consolidates the translate; 7 is naming only.

6/7 — stride the KV translate kernel and route every translate through it

Base: #40329 (5/7). 5 commits, 11 files, +361/−159.

Why

main computes v2p[t // ps] * ps + t % ps in two places: the fused Triton kernel
(_translate_loc_fused, used by the read table, the SWA read and the write translate) and a
torch body (translate_kv_loc, used by the composites and the PD / HiCache transfer
translates). They disagree on the ids nobody looks at: the torch body faults on a negative or
past-the-table id and on 2-D input, while the kernel sends them to the page-0 sink. The
kernel could not take every caller, because it flat-addresses its input and so needs it
contiguous, while the SWA read path hands down page_table[:bs, :max_seq_len], a column
slice of the capture-stable buffer.

Commit 1 — walk the kernel by its strides

loc and out are walked as a (rows, cols) view with row and column strides passed per
launch. The decomposition sits behind a STRIDED constexpr, so a contiguous pair still takes
the flat path with no div/mod; that is what the per-forward write translate under cuda-graph
capture uses.

  • The strides are runtime arguments, not tl.constexpr. A page table's live width
    changes every few decode steps, and a constexpr stride would recompile with it.
  • Offsets are computed in int64. A row stride times a row count can exceed 2^31, and an
    int32 overflow here would mis-address silently.
  • out_width stays on the flat path, by assert. That mode writes a packed lane range
    [0, W) with a zero tail; a non-contiguous 1-D loc would send the tail lanes over the
    live prefix, so the assert is load-bearing.
  • The out=None allocation no longer mirrors a strided loc, so the caller gets a
    contiguous result.

Commit 2 — one implementation

translate_kv_loc calls the fused kernel, and the torch body and translate_kv_loc_for_kernel
are deleted. For the callers that were on the torch path — the composites' translate_kv_loc,
the PD transfer translates, retraction's get_cpu_copy / load_cpu_copy and
_materialize_inflight_write_set (HiCache's transfer translate was already on the kernel) — a negative or past-the-table id now resolves to the
page-0 sink instead of faulting; 2-D, int32 and strided inputs are accepted; and each call is
one launch instead of up to six. Trading a fault for the sink is deliberate: these calls can
run inside a captured graph, where a device-side assert is not a diagnostic. Paths already on
the fused kernel — the read table, the SWA read, the pool's FULL→SWA — do not change, and
every id the torch body accepted translates to the same value.

Tests

  • test_matches_reference_on_a_strided_view and test_translate_accepts_a_strided_page_table
    fail on 5/7 (loc must be contiguous) and pass here.
  • test_a_loc_past_the_sentinel_lands_on_the_sink, test_an_out_of_range_loc_lands_on_the_sink
    and test_padding_loc_lands_on_the_sink pin the sink semantics.
  • test_out_width_needs_a_packed_loc pins the branch that would otherwise regress silently.
  • kernels/ops/memory/test_write_loc_to_kernel_ids_strided.py (GPU) is the only test that
    enters the kernel, so it is the one that can catch a wrong row * rs + col * cs; its oracle
    is the kernel on a contiguous copy, plus the CPU reference. Its lanes cover non-zero
    in-page offsets, the -1 sink, a tombstoned page, a loc below the sentinel and a page past
    the table, in int32 and int64. Dropping the past-the-table guard or the in-page offset from
    the kernel fails it.

Validation

A GPU differential of translate_kv_loc against the closed-form definition matches in all 114
cases: int32 / int64; page sizes 1, 4, 64; 1-D, 2-D, stride-2, column-slice, transposed and
empty inputs; tombstoned pages; −1, −2, −(ps + 1) and past-the-table locs; out=None, packed,
column-slice (neighbours untouched) and in-place out=. The 5/7 implementation agrees on all 50
inputs it accepts. UnifiedSWAKVPool.translate_loc_from_full_to_swa is one CUDA launch at 5/7,
at this PR and at 7/7.

Unit tests

At this head on the H200, the strided kernel test and test_unified_mla_views.py pass (24
tests), and the kernel test fails when the past-the-table guard or the in-page offset is
removed from the kernel. The stack tip (#40331) was run over the wider unit scope on the H200 and
end to end on B200; see its description.

Follow-ups this PR does not do

  • Lift the fa3/fa4 local-attention refusal under --enable-unified-memory. Commit 1 removes
    one technical reason for it, but validating the lift needs a local-attention model.
  • Fold the Mamba id translate. UnifiedMambaPool.translate is still a plain torch gather; at
    page size 1 it computes the same value but does not clamp, so a tombstoned row returns -1
    rather than the sink.

CI States

Latest PR Test (Base): ⏳ Run #36944767639
Latest PR Test (Extra): ⏳ Run #36944767374
Latest PR Test (AMD ROCm 10): ⏳ Run #36944767640

@caihuali95
caihuali95 force-pushed the mainline/consolidate-translate-kv-loc branch from d2eee4f to 2f2efb9 Compare September 24, 2026 02:35
@github-actions github-actions Bot added the amd label Sep 24, 2026
@caihuali95
caihuali95 force-pushed the mainline/consolidate-translate-kv-loc branch 2 times, most recently from 88d5cc0 to 0654006 Compare September 25, 2026 05:49
@github-actions github-actions Bot added the hicache Hierarchical Caching for SGLang label Sep 25, 2026
@ch-wan
ch-wan force-pushed the mainline/consolidate-translate-kv-loc branch from 0654006 to c0cd042 Compare September 28, 2026 19:53
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 28, 2026
@ch-wan
ch-wan force-pushed the mainline/consolidate-translate-kv-loc branch from c0cd042 to a099985 Compare September 28, 2026 21:32
@ch-wan ch-wan changed the title [unified-memory] Fold the kernel translate onto translate_kv_loc (6/7) [unified-memory] Stride the KV translate kernel and route every translate through it (6/7) Sep 28, 2026
@ch-wan
ch-wan force-pushed the mainline/consolidate-translate-kv-loc branch 3 times, most recently from 1f4e99e to 1faa3af Compare October 1, 2026 06:21
Caihua Li and others added 5 commits October 1, 2026 17:11
`write_loc_to_kernel_ids` addressed `loc` and `out` as `ptr + offs`, so it had
to assert both were contiguous. Walk them as a (rows, cols) view instead, with
the row and column strides passed per launch, and a strided slice addresses
correctly.

The decomposition sits behind a STRIDED constexpr: a contiguous pair still
takes the flat path and pays no div/mod, which is what the per-forward write
translate under cuda-graph capture uses. The strides are runtime arguments, not
constexpr, because a page table's live width changes every few decode steps and
a constexpr stride would recompile with it. Offsets are computed in int64: a row
stride times a row count can pass 2^31.

Nothing feeds this kernel a strided tensor today, so this changes no behaviour
on its own; it is the precondition for folding `translate_kv_loc` onto this
path, where the SWA read path's `page_table[:bs, :max_seq_len]` arrives.

Two details that are not stride arithmetic:

  * `out_width` writes a packed lane range and zero-fills the tail past the
    batch, which is a flat notion -- lanes past `loc`'s own rectangle would
    wrap onto live ids. That mode now asserts a packed `loc` instead of
    relying on the blanket assert this commit removes.
  * the `out=None` allocation moves off `empty_like`, which would mirror a
    strided `loc` and hand the caller a non-contiguous result.

`test_matches_reference_on_a_strided_view` is red before this commit
(`AssertionError: loc must be contiguous`) and green after.
`test_out_width_needs_a_packed_loc` passes both sides -- before, on the blanket
assert; after, on the narrow one -- and pins the branch that would otherwise
regress silently. The Triton addressing itself is reachable only on a GPU, so
it is covered by test_write_loc_to_kernel_ids_strided.py, which compares a
strided view against a contiguous copy of the same values.
…kernel

With the kernel-page multiplier gone, `translate_kv_loc` (several torch ops
over `index_select`) and `translate_kv_loc_for_kernel` (one Triton launch)
computed the same id for any mapped virtual token. Keep the fused one:
`translate_kv_loc` calls it, and the torch body and
`translate_kv_loc_for_kernel` go. The read-table path
(`KVIndexTranslator._translate_full`), the SWA read path and
`UnifiedSWAKVPool.translate_loc_from_full_to_swa` were already on the fused
kernel and see no change; the SWA / Mamba composites' unused delegates are
deleted.

What changes for the callers that were on the torch path -- the composites'
`translate_kv_loc` and the PD / HiCache transfer translates:

  * a negative loc (the padding `-1` included) or an id past the table used
    to fault -- `index_select` takes neither, and on a GPU it is a
    device-side assert. It now resolves to 0, the sink, as the fused path
    already did. This runs inside captured graphs, where a fault is not a
    diagnostic.
  * 2-D, int32 and (after the previous commit) strided inputs are accepted;
    the result is int64 and shaped like the input.
  * one launch instead of up to six.

Unchanged: every id the torch path accepted translates to the same value,
including the tombstoned-v2p clamp, `out=` identity and data_ptr stability,
and `translate(x, out=x)` aliasing.
…story

Name the padding-loc test for what it pins (a `-1` read-table slot resolves
to the sink) and drop two tests that pinned nothing new: an int32 2-D page
table test that repeats `test_translate_accepts_an_int32_page_table`, and a
compaction test that compared the translate against its own formula.

The SWA transfer translate's docstring said `translate_loc_from_full_to_swa`
takes different ids than the transfer engine hands over; both take virtual
ids and translate through the same SWA table.
The fa3 full-CG prefill comment said that re-running full->swa on the
rebound out_cache_loc indexes past the swa v2p table and lands on the
sink. It doesn't: the swa v2p has one row per full virtual page, and a
full physical page number is always below that, so the map would
resolve it to an unrelated token's swa slot without any error. Say so,
and stop citing that misuse as the scenario of the past-the-table sink
test.
The CPU-side sink tests take the torch branch, so this file is the only
coverage of the Triton kernel itself, and every lane it built was a
page-aligned int64 loc inside the table: a wrong in-page offset, a
missing past-the-table guard or a broken int32 load would all have
passed. The lanes now include non-zero offsets, a loc below the
sentinel and a page past the table, the column-slice and CPU-reference
tests run int32 as well as int64, and the CPU-reference test checks
that every sink lane resolves to 0.
@ch-wan
ch-wan force-pushed the mainline/consolidate-translate-kv-loc branch from 1faa3af to b8927d3 Compare October 2, 2026 00:11
@ch-wan
ch-wan merged commit df53047 into sgl-project:main Oct 2, 2026
30 of 40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

amd blackwell SM100/SM120 documentation Improvements or additions to documentation hicache Hierarchical Caching for SGLang jit-kernel memory-pool

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants