From 1a58fac14a7b432c3362672fb4e307550bafccf8 Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Mon, 19 Oct 2026 10:13:09 -0700 Subject: [PATCH 01/13] [None][perf] Prefer TRTLLM-Gen MoE for GLM-5.3-Flash on Blackwell moe_config.backend AUTO resolved GLM-5.3-Flash NVFP4 checkpoints to CUTLASS; only FP8 block-scale checkpoints reached TRTLLM-Gen. Resolve AUTO to TRTLLM for Glm5Next architectures on SM100-family GPUs, like the DeepSeek-V4 and Kimi K3 preferences. Layers TRTLLM-Gen cannot serve (the unquantized MTP layer) still degrade through resolve_moe_impl. Signed-off-by: Ruocheng Jia --- tensorrt_llm/_torch/model_config.py | 8 ++++++++ .../modeling/test_glm5_next_contracts.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/tensorrt_llm/_torch/model_config.py b/tensorrt_llm/_torch/model_config.py index 0162c9a7d5fd..25ac615f0879 100644 --- a/tensorrt_llm/_torch/model_config.py +++ b/tensorrt_llm/_torch/model_config.py @@ -105,6 +105,11 @@ def _get_kv_cache_layer_specs( "KimiLinearForCausalLM", } +_GLM5_NEXT_ARCHITECTURES = { + "Glm5NextForCausalLM", + "Glm5NextForConditionalGeneration", +} + _MINIMAX_M3_ARCHITECTURES = { "MiniMaxM3SparseForCausalLM", "MiniMaxM3SparseForConditionalGeneration", @@ -569,6 +574,9 @@ def resolve_moe_backend(moe_backend: str, if 100 <= sm_version < 120: return "TRTLLM" + if architecture in _GLM5_NEXT_ARCHITECTURES and is_sm_100f(): + return "TRTLLM" + is_w4a16_nvfp4 = (quant_config is not None and quant_config.quant_algo in (QuantAlgo.W4A16_NVFP4, "W4A16_NVFP4")) if is_w4a16_nvfp4: diff --git a/tests/unittest/_torch/modeling/test_glm5_next_contracts.py b/tests/unittest/_torch/modeling/test_glm5_next_contracts.py index eeed1be64210..23ab4e527715 100644 --- a/tests/unittest/_torch/modeling/test_glm5_next_contracts.py +++ b/tests/unittest/_torch/modeling/test_glm5_next_contracts.py @@ -375,6 +375,24 @@ def test_allreduce_defaults_honor_user_strategy(override): reduction.large.assert_called_once() +@pytest.mark.cpu_only +@pytest.mark.parametrize( + "architecture", ["Glm5NextForCausalLM", "Glm5NextForConditionalGeneration"] +) +@pytest.mark.parametrize("sm_100f, expected", [(True, "TRTLLM"), (False, "CUTLASS")]) +def test_moe_auto_backend_prefers_trtllm_gen_on_blackwell( + monkeypatch, architecture, sm_100f, expected +): + from tensorrt_llm.models.modeling_utils import QuantConfig + from tensorrt_llm.quantization.mode import QuantAlgo + + monkeypatch.setattr("tensorrt_llm._torch.model_config.is_sm_100f", lambda: sm_100f) + nvfp4 = QuantConfig(quant_algo=QuantAlgo.NVFP4, group_size=16) + assert ModelConfig.resolve_moe_backend("AUTO", architecture) == expected + assert ModelConfig.resolve_moe_backend("AUTO", architecture, quant_config=nvfp4) == expected + assert ModelConfig.resolve_moe_backend("CUTLASS", architecture, quant_config=nvfp4) == "CUTLASS" + + @pytest.mark.cpu_only def test_mtp_rejects_unsupported_attention_dp_lm_head_sharding(): from tensorrt_llm.llmapi import MTPDecodingConfig From 6c014f97d1ec9e7c3c6b21cc9588679a4f288f78 Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Mon, 19 Oct 2026 10:31:08 -0700 Subject: [PATCH 02/13] [None][perf] Score GLM-5.3-Flash context indexer pools in FP8 Packed context rows scored their pools with the BF16 Triton kernel, about 0.84 ms per sparse layer for a 13K-token prompt. On SM100-family GPUs, quantize the pool keys per row and the queries per (row, head), fold the query scales into the head weights, and score with DeepGEMM fp8_mqa_logits. TRTLLM_GLM_INDEXER_BF16=1 keeps the BF16 scorer. Generation and verification rows are unchanged. Signed-off-by: Ruocheng Jia --- .../backends/sparse/glm_kpool/backend.py | 29 +++++++- .../backends/sparse/glm_kpool/kernels.py | 69 +++++++++++++++++++ .../sparse/glm_kpool/test_kernels.py | 59 ++++++++++++++++ 3 files changed, 155 insertions(+), 2 deletions(-) diff --git a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py index 13937deed749..9480ea5f325c 100644 --- a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py +++ b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py @@ -27,11 +27,12 @@ from __future__ import annotations +import os from dataclasses import dataclass import torch -from tensorrt_llm._utils import get_sm_version +from tensorrt_llm._utils import get_sm_version, is_sm_100f from ...interface import ( AttentionForwardArgs, @@ -46,6 +47,7 @@ gather_fp8_kv_rows, kpool_expand, kpool_score, + kpool_score_fp8, kpool_update, ) from .metadata import Glm5NextMamba2Metadata @@ -210,6 +212,9 @@ def __init__( #: construction). A float lets the kernel take it as an argument, without a #: per-call device multiply. self._native_bmm1_scale: float | None = None + #: Packed context rows score pools in FP8 with DeepGEMM MQA logits on SM100-family + #: GPUs; TRTLLM_GLM_INDEXER_BF16=1 keeps the BF16 Triton scorer for them. + self._indexer_fp8 = is_sm_100f() and os.environ.get("TRTLLM_GLM_INDEXER_BF16", "0") != "1" @classmethod def support_fused_rope(cls) -> bool: @@ -393,12 +398,32 @@ def score_pools( Incomplete/invisible pools receive the FP32 minimum before top-k. Eager callers that know a host bound on the visible lengths pass max_kv_len so only reachable pools are scored; columns past it are left unwritten and - the top-k must scan only each row's complete pools. + the top-k must scan only each row's complete pools. Packed context rows + with such a bound use the FP8 scorer on SM100-family GPUs, whose result + may be a strided view. """ state = self._cache_state(metadata) tables, gen_lens = self._rows(state, rows_per_request, request_ids=request_ids) capacity = state.block_tables.shape[1] * state.tokens_per_block kpool = self.sparse_params.index_kpool + if self._indexer_fp8 and request_ids is not None and max_kv_len is not None: + scores = kpool_score_fp8( + q, + weights, + state.index_pool, + # Packed context rows only reference the context requests' tables. + tables[: state.num_contexts], + kv_lens, + state.tokens_per_block, + head_dim=self.sparse_params.index_head_dim, + kpool=kpool, + q_scale=q_scale, + w_scale=w_scale, + request_ids=request_ids, + max_kv_len=max_kv_len, + ) + if scores is not None: + return scores return kpool_score( q, weights, diff --git a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/kernels.py b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/kernels.py index 02297052194f..56368dc681ff 100644 --- a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/kernels.py +++ b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/kernels.py @@ -512,6 +512,75 @@ def kpool_score( return out +# Largest FP8 logits transient (elements) kpool_score_fp8 allocates. +_FP8_SCORE_MAX_ELEMENTS = 1 << 29 + + +def kpool_score_fp8( + q: torch.Tensor, + weights: torch.Tensor, + index_pool: torch.Tensor, + block_tables: torch.Tensor, + kv_lens: torch.Tensor, + tokens_per_block: int, + *, + head_dim: int, + kpool: int, + q_scale: float, + w_scale: float, + request_ids: torch.Tensor, + max_kv_len: int, +) -> torch.Tensor | None: + """FP8 :func:`kpool_score` for packed context rows, via DeepGEMM ``fp8_mqa_logits``. + + The pool keys of every block table are gathered into ``[tables * pools, head_dim]`` + and quantized per row; queries are quantized per (row, head), with their scales + folded into the head weights, so each score is ``sum_h w * relu(q . key)`` as in + :func:`kpool_score`. ``max_kv_len`` bounds every row's ``kv_lens``. Row ``r`` + scores only the first ``kv_lens[r] // kpool`` pools of table ``request_ids[r]``; + later columns are not meaningful, so readers must stop there. The result may be + a strided view with an aligned row stride. Returns ``None`` when the logits + transient would exceed ``_FP8_SCORE_MAX_ELEMENTS``. + """ + from tensorrt_llm.deep_gemm import fp8_mqa_logits + + n = q.shape[0] + pools = max(0, int(max_kv_len)) // kpool + tables = block_tables.shape[0] + if n == 0 or pools == 0 or n * tables * pools > _FP8_SCORE_MAX_ELEMENTS: + return None + positions = torch.arange(pools, device=q.device) * kpool + page = positions // tokens_per_block + offset = positions - page * tokens_per_block + slots = block_tables[:, page].long() # [tables, pools] + keys = index_pool[slots, offset, 2 * head_dim : 3 * head_dim].reshape(-1, head_dim) + half = head_dim // 2 + keys_fp8, key_scale = torch.ops.trtllm.fused_cat_fp8(keys[:, :half], keys[:, half:], False) + q_rows = q.reshape(-1, head_dim) + q_fp8, q_row_scale = torch.ops.trtllm.fused_cat_fp8(q_rows[:, :half], q_rows[:, half:], False) + head_weights = weights.float() * q_row_scale.view(n, -1) * (w_scale * q_scale) + start = request_ids.to(torch.int32) * pools + end = start + (kv_lens // kpool).to(torch.int32) + logits = fp8_mqa_logits( + q_fp8.view(q.shape), + (keys_fp8, key_scale.view(-1)), + head_weights, + start, + end, + clean_logits=False, + ) + if tables == 1: + # DeepGEMM pads the logits rows to an aligned stride, as the pool top-k needs. + return logits[:, :pools] + # Keep the copy's rows aligned and wider than the selection, like the logits arena. + out = torch.empty( + n, max(1024, triton.cdiv(pools, 64) * 64), dtype=torch.float32, device=q.device + ) + rows = torch.arange(n, device=q.device) + out[:, :pools] = logits[:, : tables * pools].view(n, tables, pools)[rows, request_ids.long()] + return out + + @triton.jit def _kpool_expand_kernel( SEL, diff --git a/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py b/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py index de5148df592b..85effda739fc 100644 --- a/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py +++ b/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py @@ -22,6 +22,7 @@ compact_sparse_rows, kpool_expand, kpool_score, + kpool_score_fp8, kpool_update, ) from tensorrt_llm._utils import get_sm_version @@ -320,6 +321,64 @@ def test_pool_scores_broadcast_table_single_context_request(): ) +@pytest.mark.skipif( + get_sm_version() not in (100, 103), reason="DeepGEMM MQA logits require SM100/103" +) +@pytest.mark.parametrize("single_request", [True, False], ids=["one_table", "two_tables"]) +def test_fp8_pool_scores_track_bf16_selection(single_request): + """FP8 context scoring keeps the BF16 scorer's top pools on packed context rows.""" + gen = torch.Generator(device="cuda").manual_seed(10) + heads, tpb, slots = 32, 32, 400 + lens = [6000] if single_request else [6000, 2500] + pages = [(length + tpb - 1) // tpb for length in lens] + pool = torch.randn(slots, 2, tpb, 3 * HD, generator=gen, device="cuda").to(torch.bfloat16)[:, 1] + perm = torch.randperm(slots, generator=gen, device="cuda") + tables = torch.zeros(len(lens), max(pages), dtype=torch.int64, device="cuda") + start = 0 + for i, count in enumerate(pages): + tables[i, :count] = perm[start : start + count] + start += count + request_ids = torch.cat( + [torch.full((length,), i, dtype=torch.int32) for i, length in enumerate(lens)] + ).cuda() + kv_lens = torch.cat([torch.arange(1, length + 1, dtype=torch.int32) for length in lens]).cuda() + n = kv_lens.shape[0] + q = torch.randn(n, heads, HD, generator=gen, device="cuda").to(torch.bfloat16) + w = torch.randn(n, heads, generator=gen, device="cuda").to(torch.bfloat16) + kwargs = dict( + head_dim=HD, + kpool=KPOOL, + q_scale=HD**-0.5, + w_scale=heads**-0.5, + request_ids=request_ids, + max_kv_len=max(lens), + ) + want = kpool_score( + q, + w, + pool, + tables, + kv_lens, + tpb, + num_pools_max=max(pages) * tpb // KPOOL, + precision="bf16", + rows_per_program=16, + **kwargs, + ) + got = kpool_score_fp8(q, w, pool, tables, kv_lens, tpb, **kwargs) + assert got is not None and got.stride(1) == 1 and got.stride(0) % 4 == 0 + overlaps = [] + for row in torch.randint(0, n, (64,), generator=gen, device="cuda").tolist(): + visible = int(kv_lens[row]) // KPOOL + if visible < 64: + continue + k = visible // 2 + a = set(want[row, :visible].topk(k).indices.tolist()) + b = set(got[row, :visible].topk(k).indices.tolist()) + overlaps.append(len(a & b) / k) + assert overlaps and sum(overlaps) / len(overlaps) > 0.97 and min(overlaps) > 0.9 + + def test_pool_expansion_graph_replay_tracks_request_tables_and_tails(): # Requests occupy disjoint slots, with gaps between slots from coalesced buffers. tables = torch.tensor([[3, 1], [5, 2]], dtype=torch.int64, device="cuda") From 07b808a6d896c6b11ee871880b685a22097b0b5a Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Mon, 19 Oct 2026 11:20:45 -0700 Subject: [PATCH 03/13] [None][perf] Share GLM-5.3-Flash generation row lengths across sparse layers Every sparse-attention layer rebuilt the same generation-row positions, visible lengths and complete-pool counts from kv_lens, about five small kernels per layer and step. Build them once per forward in the runtime context and pass the context to the decode and verify paths, which still derive them locally when called without one. Signed-off-by: Ruocheng Jia --- .../_torch/models/modeling_glm5_next.py | 89 +++++++++++++++++-- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/tensorrt_llm/_torch/models/modeling_glm5_next.py b/tensorrt_llm/_torch/models/modeling_glm5_next.py index dcc02d2098ab..3437963808a7 100644 --- a/tensorrt_llm/_torch/models/modeling_glm5_next.py +++ b/tensorrt_llm/_torch/models/modeling_glm5_next.py @@ -283,6 +283,39 @@ def build( ) +@dataclass(frozen=True) +class Glm5NextGenerationRows: + """Device-side rows of the generation requests of one forward. + + Built once per forward from the live ``kv_lens`` and shared by every sparse + layer, so each derived length tensor is one kernel per forward rather than + per layer. ``positions`` is ``[B]`` for plain decode and ``[B, T]`` while a + target verifies ``T - 1`` drafts; ``visible[i]`` is row ``i``'s visible + prefix length and ``num_candidates[i]`` its complete-pool count (int32). + """ + + positions: torch.Tensor + visible: torch.Tensor + num_candidates: torch.Tensor + + @classmethod + def build( + cls, kv_lens: torch.Tensor, tokens_per_request: int, kpool: int + ) -> Glm5NextGenerationRows: + if tokens_per_request <= 1: + positions = kv_lens - 1 + visible = kv_lens + else: + steps = torch.arange(tokens_per_request, device=kv_lens.device) + positions = (kv_lens - tokens_per_request).unsqueeze(1) + steps + visible = (positions + 1).reshape(-1) + return cls( + positions=positions, + visible=visible, + num_candidates=(visible // kpool).to(torch.int32), + ) + + @dataclass class Glm5NextRuntimeContext: """Per-forward schedule shared by all layers of a context-first packed batch. @@ -312,6 +345,9 @@ class Glm5NextRuntimeContext: #: Context-row schedules by pool size, built on first use and shared by #: all sparse layers of this forward (prefill only, never captured). ctx_rows_cache: dict[int, Glm5NextContextRows] = field(default_factory=dict) + #: Generation-row lengths by pool size, built on first use and shared by + #: all sparse layers of this forward (inside a captured graph, too). + gen_rows_cache: dict[int, Glm5NextGenerationRows] = field(default_factory=dict) def context_rows(self, kpool: int, device: torch.device) -> Glm5NextContextRows: rows = self.ctx_rows_cache.get(kpool) @@ -322,6 +358,15 @@ def context_rows(self, kpool: int, device: torch.device) -> Glm5NextContextRows: self.ctx_rows_cache[kpool] = rows return rows + def generation_rows(self, kpool: int) -> Glm5NextGenerationRows: + rows = self.gen_rows_cache.get(kpool) + if rows is None: + rows = Glm5NextGenerationRows.build( + self.kv_lens[self.num_contexts :], self.gen_tokens_per_request, kpool + ) + self.gen_rows_cache[kpool] = rows + return rows + def _glm5_gen_tokens_per_request(attn_metadata: AttentionMetadata, num_generations: int) -> int: """Tokens per generation request, from the metadata's host token counts. @@ -1272,13 +1317,15 @@ def _select_and_attend_paged( reduce: bool = True, request_ids: torch.Tensor | None = None, max_visible: int | None = None, + num_candidates: torch.Tensor | None = None, ) -> torch.Tensor: """Score pools, select members and tail, then attend to paged latent rows. visible gives each query's prefix length. Packed context uses request_ids; verification uses rows_per_request. Single-token decode reads live lengths from metadata. All device work uses fixed buffer shapes for graph replay. - max_visible is an optional host bound on visible that limits scoring work. + max_visible is an optional host bound on visible that limits scoring work; + num_candidates, if given, is visible // index_kpool as int32. """ indexer = self.indexer rows = q_resid.shape[0] @@ -1298,7 +1345,11 @@ def _select_and_attend_paged( request_ids=request_ids, max_kv_len=max_visible, ) - num_cand = (visible // indexer.index_kpool).to(torch.int32) + num_cand = ( + (visible // indexer.index_kpool).to(torch.int32) + if num_candidates is None + else num_candidates + ) selected = torch.empty(rows, indexer.select_k, dtype=torch.int32, device=scores.device) indexer.pool_top_k( scores, @@ -1384,6 +1435,7 @@ def forward_decode( kv_lens: torch.Tensor, metadata: AttentionMetadata, reduce: bool = True, + runtime_ctx: Glm5NextRuntimeContext | None = None, ) -> torch.Tensor: """Decode one token per request using device-resident visible lengths. @@ -1395,8 +1447,13 @@ def forward_decode( the incomplete tail pool or its final complete pool. """ batch = hidden_states.shape[0] - positions = kv_lens - 1 # [B] indexer = self.indexer + rows = ( + Glm5NextGenerationRows.build(kv_lens, 1, indexer.index_kpool) + if runtime_ctx is None + else runtime_ctx.generation_rows(indexer.index_kpool) + ) + positions = rows.positions # [B] q_resid, latent, packed, index_weights = self._project_attention_and_indexer(hidden_states) self.attn_backend.append_paged_state( latent.unsqueeze(1), packed.unsqueeze(1), positions.unsqueeze(1), metadata @@ -1413,6 +1470,7 @@ def forward_decode( metadata, AttentionInputType.generation_only, reduce=reduce, + num_candidates=rows.num_candidates, ) def forward( @@ -1449,7 +1507,11 @@ def forward( lengths = ctx.kv_lens[ctx.num_contexts :] if ctx.gen_tokens_per_request == 1: out = self.forward_decode( - generation, kv_lens=lengths, metadata=ctx.metadata, reduce=False + generation, + kv_lens=lengths, + metadata=ctx.metadata, + reduce=False, + runtime_ctx=ctx, ) else: out = self.forward_verify( @@ -1458,6 +1520,7 @@ def forward( metadata=ctx.metadata, tokens_per_request=ctx.gen_tokens_per_request, reduce=False, + runtime_ctx=ctx, ) parts.append(out) out = parts[0] if len(parts) == 1 else torch.cat(parts, dim=0) @@ -1470,6 +1533,7 @@ def forward_verify( metadata: AttentionMetadata, tokens_per_request: int, reduce: bool = True, + runtime_ctx: Glm5NextRuntimeContext | None = None, ) -> torch.Tensor: """Verify packed [batch * tokens_per_request, hidden] generation rows. @@ -1480,7 +1544,9 @@ def forward_verify( """ tokens_per_request = int(tokens_per_request) if tokens_per_request <= 1: - return self.forward_decode(hidden_states, kv_lens, metadata, reduce=reduce) + return self.forward_decode( + hidden_states, kv_lens, metadata, reduce=reduce, runtime_ctx=runtime_ctx + ) num_tokens = hidden_states.shape[0] batch = num_tokens // tokens_per_request if batch * tokens_per_request != num_tokens or kv_lens.shape[0] != batch: @@ -1488,9 +1554,13 @@ def forward_verify( f"glm5_next sparse verify: {num_tokens} tokens for {kv_lens.shape[0]} requests " f"at {tokens_per_request} tokens per request" ) - device = hidden_states.device - steps = torch.arange(tokens_per_request, device=device) - positions = (kv_lens - tokens_per_request).unsqueeze(1) + steps # [B, T] + kpool = self.indexer.index_kpool + rows = ( + Glm5NextGenerationRows.build(kv_lens, tokens_per_request, kpool) + if runtime_ctx is None + else runtime_ctx.generation_rows(kpool) + ) + positions = rows.positions # [B, T] q_resid, latent, packed, index_weights = self._project_attention_and_indexer(hidden_states) self.attn_backend.append_paged_state( latent.view(batch, tokens_per_request, -1), @@ -1509,11 +1579,12 @@ def forward_verify( q_resid, query, index_weights, - (positions + 1).reshape(-1), + rows.visible, metadata, AttentionInputType.generation_only, rows_per_request=tokens_per_request, reduce=reduce, + num_candidates=rows.num_candidates, ) From 52e15a51e60a30f83bafbfc8373e129e383dc4b7 Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Mon, 19 Oct 2026 11:27:02 -0700 Subject: [PATCH 04/13] [None][perf] Write GLM-5.3-Flash absorbed V output in o_proj layout The absorbed V projection produced [H, T, v_head] and o_proj then needed a transposed copy of it in every sparse-attention layer. Let the batched GEMM write through a transposed view of a [T, H, v_head] buffer instead; the result is bit-identical and the copy kernel is gone. Signed-off-by: Ruocheng Jia --- tensorrt_llm/_torch/models/modeling_glm5_next.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tensorrt_llm/_torch/models/modeling_glm5_next.py b/tensorrt_llm/_torch/models/modeling_glm5_next.py index 3437963808a7..0202cbf3d6cf 100644 --- a/tensorrt_llm/_torch/models/modeling_glm5_next.py +++ b/tensorrt_llm/_torch/models/modeling_glm5_next.py @@ -1299,8 +1299,10 @@ def project_output_latent(self, out_latent: torch.Tensor, reduce: bool = True) - _, w_v_t = self.absorbed_kv_b() tokens = out_latent.shape[0] per_head = out_latent.view(tokens, self.num_heads, self.kv_lora_rank) - out = torch.bmm(per_head.transpose(0, 1), w_v_t) # [H, T, v_head] - out = self.o_proj(out.transpose(0, 1).reshape(tokens, -1)) + # Write the per-head products straight into [T, H, v_head] for o_proj. + out = per_head.new_empty(tokens, self.num_heads, self.v_head_dim) + torch.bmm(per_head.transpose(0, 1), w_v_t, out=out.transpose(0, 1)) + out = self.o_proj(out.view(tokens, -1)) if not reduce or self.tp_all_reduce is None: return out return self.tp_all_reduce(out) From 3fe8b6670d2c4e25faad79ae66b83d8c3976f778 Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Mon, 19 Oct 2026 11:51:32 -0700 Subject: [PATCH 05/13] [None][perf] Trim per-layer kernels from GLM-5.3-Flash sparse decode Each sparse-attention layer launched small kernels whose inputs are fixed for the whole forward: - Verification copied each request's block-table row once per draft token for both pool scoring and expansion. Give verification rows their tables' batch rows as request ids, built once per forward, and let the kernels index the tables instead. - Discarding empty rows used torch.where with a Python 0, which fills a scalar tensor per call. Use a cached device zero. Scores, selections and outputs are unchanged. Signed-off-by: Ruocheng Jia --- .../backends/sparse/glm_kpool/backend.py | 18 +++++--- .../sparse/glm_kpool/native_decode.py | 8 +++- .../_torch/models/modeling_glm5_next.py | 29 ++++++++++-- .../sparse/glm_kpool/test_kernels.py | 44 +++++++++++++++++++ 4 files changed, 87 insertions(+), 12 deletions(-) diff --git a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py index 9480ea5f325c..ca3f587b971a 100644 --- a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py +++ b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py @@ -337,10 +337,12 @@ def _rows( rows_per_request: int = 1, request_ids: torch.Tensor | None = None, ) -> tuple[torch.Tensor, torch.Tensor | None]: - """Select generation tables, or all tables for packed context request IDs. + """Select generation tables, or all tables for per-row request IDs. - Verification repeats generation tables per token; callers supply each - query's visible length for verification and packed context rows. + Rows with request_ids (packed context, or verification rows given their + tables' batch rows) index all tables; otherwise verification repeats + generation tables per token. Callers supply each query's visible length + for verification and packed context rows. """ if request_ids is not None: return state.block_tables, None @@ -393,7 +395,8 @@ def score_pools( """Score complete pools into FP32 [rows, pool_capacity]. q is [rows, heads, head_dim], weights is [rows, heads]. Packed context uses - request_ids; verification repeats generation tables rows_per_request times. + request_ids; verification either does too or repeats generation tables + rows_per_request times. Both supply per-query kv_lens. Single-token decode uses live metadata lengths. Incomplete/invisible pools receive the FP32 minimum before top-k. Eager callers that know a host bound on the visible lengths pass max_kv_len so @@ -440,9 +443,10 @@ def score_pools( # dot is an fp32 accumulation of exact products, like tf32 on the # widened operands (measured: identical selections). precision="bf16", - # Context rows: the query tokens of a request share its block - # table, so a program gathers each pool-key block once for 16 rows. - rows_per_program=1 if request_ids is None else 16, + # Packed context rows share their request's block table, so a program + # gathers each pool-key block once for 16 rows. Verification batches + # are small; one row per program keeps more programs in flight. + rows_per_program=16 if request_ids is not None and rows_per_request == 1 else 1, request_ids=request_ids, max_kv_len=max_kv_len, ) diff --git a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/native_decode.py b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/native_decode.py index af623c12226a..f623b11d05a6 100644 --- a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/native_decode.py +++ b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/native_decode.py @@ -29,6 +29,8 @@ def __init__(self) -> None: # Largest query rows and heads the sizes cover. self._covered_rows = 0 self._covered_heads = 0 + # Device zero for discarding empty rows; a Python 0 would be filled per call. + self._zero: torch.Tensor | None = None def prepare_workspace( self, @@ -70,6 +72,10 @@ def prepare_workspace( ) # TRTLLM-GEN resets its counters after each invocation. self._counter = torch.zeros(self._counter_size, dtype=torch.uint8, device=q.device) + if self._zero is None: + if capturing: + raise RuntimeError("glm_kpool native decode zero must be allocated before capture") + self._zero = torch.zeros((), dtype=torch.bfloat16, device=q.device) def __call__( self, @@ -112,4 +118,4 @@ def __call__( ).squeeze(1) # Empty (padded) requests use a dummy selection to keep the kernel's # length positive; discard that output, matching FlashMLA's empty row. - return torch.where(nonempty[:, None, None], output, 0) + return torch.where(nonempty[:, None, None], output, self._zero) diff --git a/tensorrt_llm/_torch/models/modeling_glm5_next.py b/tensorrt_llm/_torch/models/modeling_glm5_next.py index 0202cbf3d6cf..b0a34844546c 100644 --- a/tensorrt_llm/_torch/models/modeling_glm5_next.py +++ b/tensorrt_llm/_torch/models/modeling_glm5_next.py @@ -292,27 +292,43 @@ class Glm5NextGenerationRows: per layer. ``positions`` is ``[B]`` for plain decode and ``[B, T]`` while a target verifies ``T - 1`` drafts; ``visible[i]`` is row ``i``'s visible prefix length and ``num_candidates[i]`` its complete-pool count (int32). + With ``first_table`` (the batch's block-table row of the first generation + request), verification rows also get ``request_ids[i]``, row ``i``'s + block-table row (int32), so kernels index tables instead of copying them. """ positions: torch.Tensor visible: torch.Tensor num_candidates: torch.Tensor + request_ids: torch.Tensor | None = None @classmethod def build( - cls, kv_lens: torch.Tensor, tokens_per_request: int, kpool: int + cls, + kv_lens: torch.Tensor, + tokens_per_request: int, + kpool: int, + first_table: int | None = None, ) -> Glm5NextGenerationRows: + request_ids = None if tokens_per_request <= 1: positions = kv_lens - 1 visible = kv_lens else: - steps = torch.arange(tokens_per_request, device=kv_lens.device) + device = kv_lens.device + steps = torch.arange(tokens_per_request, device=device) positions = (kv_lens - tokens_per_request).unsqueeze(1) + steps visible = (positions + 1).reshape(-1) + if first_table is not None: + tables = torch.arange( + first_table, first_table + kv_lens.shape[0], dtype=torch.int32, device=device + ) + request_ids = tables.repeat_interleave(tokens_per_request) return cls( positions=positions, visible=visible, num_candidates=(visible // kpool).to(torch.int32), + request_ids=request_ids, ) @@ -362,7 +378,10 @@ def generation_rows(self, kpool: int) -> Glm5NextGenerationRows: rows = self.gen_rows_cache.get(kpool) if rows is None: rows = Glm5NextGenerationRows.build( - self.kv_lens[self.num_contexts :], self.gen_tokens_per_request, kpool + self.kv_lens[self.num_contexts :], + self.gen_tokens_per_request, + kpool, + first_table=self.num_contexts, ) self.gen_rows_cache[kpool] = rows return rows @@ -1324,7 +1343,8 @@ def _select_and_attend_paged( """Score pools, select members and tail, then attend to paged latent rows. visible gives each query's prefix length. Packed context uses request_ids; - verification uses rows_per_request. Single-token decode reads live lengths + verification uses rows_per_request, and request_ids too when its rows' + tables are known. Single-token decode reads live lengths from metadata. All device work uses fixed buffer shapes for graph replay. max_visible is an optional host bound on visible that limits scoring work; num_candidates, if given, is visible // index_kpool as int32. @@ -1586,6 +1606,7 @@ def forward_verify( AttentionInputType.generation_only, rows_per_request=tokens_per_request, reduce=reduce, + request_ids=rows.request_ids, num_candidates=rows.num_candidates, ) diff --git a/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py b/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py index 85effda739fc..afc8fc55e644 100644 --- a/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py +++ b/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py @@ -285,6 +285,50 @@ def select(scores): score(slice(lens[0], lens[0] + KPOOL - 1), KPOOL - 1) +def test_verify_rows_index_tables_like_repeated_tables(): + """Verification rows addressing their tables by request id score and expand + exactly like rows reading per-row copies of the generation tables.""" + gen = torch.Generator(device="cuda").manual_seed(4) + requests, tokens, heads, pages = 3, 4, 8, 16 + slots = (requests + 1) * pages + 8 + pool = _strided_index_pool(slots, gen) + # Row 0 is a context request's table; the generation tables follow it. + tables = _paged_tables(requests + 1, pages, slots, gen) + repeated = tables[1:].repeat_interleave(tokens, dim=0) + request_ids = torch.arange(1, requests + 1, dtype=torch.int32, device="cuda") + request_ids = request_ids.repeat_interleave(tokens) + first = torch.randint(KPOOL, pages * TPB - tokens, (requests, 1), generator=gen, device="cuda") + visible = (first + torch.arange(1, tokens + 1, device="cuda")).reshape(-1) + n = requests * tokens + q = torch.randn(n, heads, HD, generator=gen, device="cuda").to(torch.bfloat16) + w = torch.rand(n, heads, generator=gen, device="cuda").to(torch.bfloat16) + kwargs = dict( + num_pools_max=pages * TPB // KPOOL, head_dim=HD, kpool=KPOOL, q_scale=HD**-0.5, w_scale=1.0 + ) + want = kpool_score(q, w, pool, repeated, visible, TPB, precision="bf16", **kwargs) + got = kpool_score( + q, + w, + pool, + tables, + visible, + TPB, + precision="bf16", + request_ids=request_ids, + **kwargs, + ) + torch.testing.assert_close(got, want, atol=0, rtol=0) + + selected = torch.randint(-1, 32, (n, 8), generator=gen, device="cuda", dtype=torch.int32) + expand = dict(base_row=2, rows_per_slot=TPB + 4, kpool=KPOOL, out_width=64) + torch.testing.assert_close( + kpool_expand(selected, visible, tables, TPB, request_ids=request_ids, **expand), + kpool_expand(selected, visible, repeated, TPB, **expand), + atol=0, + rtol=0, + ) + + def test_pool_scores_broadcast_table_single_context_request(): """One context request: a stride-0 broadcast table with rows_per_program=16.""" gen = torch.Generator(device="cuda").manual_seed(3) From 6b9d51c547ede72b42b7f616e49a324be6da0859 Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Mon, 19 Oct 2026 19:44:09 -0700 Subject: [PATCH 06/13] [None][perf] Return GLM-5.3-Flash text prompt ids as a list The text-only input path asked the tokenizer for a tensor, cast it to int32 and converted it back to a list. For a 13K-token prompt that round trip added about 4 ms to every request's time to first token; take the tokenizer's list directly. Signed-off-by: Ruocheng Jia --- tensorrt_llm/_torch/models/modeling_glm5_next_vision.py | 5 +++-- tests/unittest/_torch/modeling/test_glm5_next_contracts.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tensorrt_llm/_torch/models/modeling_glm5_next_vision.py b/tensorrt_llm/_torch/models/modeling_glm5_next_vision.py index 74ee8e7b25b9..60a1aea3e113 100644 --- a/tensorrt_llm/_torch/models/modeling_glm5_next_vision.py +++ b/tensorrt_llm/_torch/models/modeling_glm5_next_vision.py @@ -936,8 +936,9 @@ def call_with_text_prompt( if not images and not video_datas: # Text-only fast path: identical to the tokenizer and no # multimodal payload, so the tower is never touched. - input_ids = self._tokenizer(text_prompt, return_tensors="pt").input_ids - return input_ids[0].to(torch.int32).tolist(), None + # A Python list straight from the tokenizer: a tensor round trip costs + # milliseconds on long prompts. + return self._tokenizer(text_prompt).input_ids, None # Media loaders may deliver pre-rescaled float tensors (the server # default) instead of PIL / uint8; tell the HF processor not to diff --git a/tests/unittest/_torch/modeling/test_glm5_next_contracts.py b/tests/unittest/_torch/modeling/test_glm5_next_contracts.py index 23ab4e527715..ed1d7ac0ee3f 100644 --- a/tests/unittest/_torch/modeling/test_glm5_next_contracts.py +++ b/tests/unittest/_torch/modeling/test_glm5_next_contracts.py @@ -113,7 +113,8 @@ def test_text_processing_without_native_processor(monkeypatch): from tensorrt_llm.llmapi import SamplingParams monkeypatch.delitem(PROCESSOR_MAPPING_NAMES, "glm5_next", raising=False) - tokenizer = Mock(return_value=SimpleNamespace(input_ids=torch.tensor([[1, 2, 3]]))) + # A tokenizer called on one string without return_tensors returns a flat list. + tokenizer = Mock(return_value=SimpleNamespace(input_ids=[1, 2, 3])) with patch( "tensorrt_llm._torch.models.modeling_glm5_next_vision.AutoProcessor.from_pretrained" ) as load_processor: @@ -121,6 +122,7 @@ def test_text_processing_without_native_processor(monkeypatch): tokens, extra = processor.call_with_text_prompt({"prompt": "hello"}, SamplingParams()) assert tokens == [1, 2, 3] assert extra is None + assert "return_tensors" not in tokenizer.call_args.kwargs load_processor.assert_not_called() with pytest.raises(RuntimeError, match="transformers==5.17.0"): processor.get_mm_max_tokens_per_item() From ffa7f1d2a0e89ed6d9a1d5129d80dc3185e65cc7 Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Tue, 20 Oct 2026 07:38:35 -0700 Subject: [PATCH 07/13] [None][fix] Bound GLM-5.3-Flash context pool-scoring memory FP8 context scoring allocated logits for every packed row against the pools of every context request, plus the gathered keys and a row copy, so its transient grew with the batch's request count and visible prefixes. Startup profiling scores a fresh prompt and never reached that growth; neither did the capacity-wide BF16 scorer once FP8 replaced it in profiling. A batch with long cached prefixes could exceed the memory left after KV-cache sizing. Context rows are now scored and selected in calls planned on the host: consecutive requests share a call while its logits, row copy and gathered keys fit 2^26 FP32 elements, each call gathers only its own requests' tables, and a request that does not fit alone is split by rows. The BF16 scorer is tiled to the same bound. KV-cache sizing reserves that bound through runtime_workspace_fixed_bytes. A fresh prompt of up to 16K tokens still scores in one call. Signed-off-by: Ruocheng Jia --- .../backends/sparse/glm_kpool/backend.py | 89 ++++++++++++++++++- .../_torch/models/modeling_glm5_next.py | 66 +++++++++----- tensorrt_llm/_torch/pyexecutor/_util.py | 11 ++- .../sparse/glm_kpool/test_glm_kpool.py | 82 +++++++++++++++++ .../sparse/glm_kpool/test_kernels.py | 45 ++++++++++ 5 files changed, 263 insertions(+), 30 deletions(-) diff --git a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py index ca3f587b971a..4265de6ff516 100644 --- a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py +++ b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py @@ -28,6 +28,7 @@ from __future__ import annotations import os +from collections.abc import Sequence from dataclasses import dataclass import torch @@ -138,6 +139,27 @@ class GlmKpoolSparseAttention(TrtllmAttention): # At the published 2112-entry selection this uses 132 MiB of BF16 rows. _FP8_QUERY_CHUNK_SIZE = 64 + # FP32 elements one packed-context scoring call may hold: logits, their row + # copy and gathered pool keys grow with the visible pools, which a fresh-prompt + # profiling forward does not reach. plan_context_scores tiles calls to this + # bound and runtime_workspace_fixed_bytes reserves it in KV-cache sizing. + _CONTEXT_SCORE_ELEMENTS = 1 << 26 + + @classmethod + def runtime_workspace_fixed_bytes( + cls, + model_config, + mapping, + *, + max_batch_size: int, + max_num_tokens: int, + max_seq_len: int, + enable_cuda_graph: bool, + ) -> int: + if max_num_tokens <= 0 or max_seq_len <= 0: + return 0 + return min(cls._CONTEXT_SCORE_ELEMENTS, max_num_tokens * max_seq_len) * 4 + def __init__( self, layer_idx: int, @@ -379,6 +401,62 @@ def update_pool_keys( request_ids=request_ids, ) + def plan_context_scores( + self, metadata, spans: Sequence[tuple[int, int]] + ) -> list[tuple[int, int, int, int, int]]: + """Split packed context scoring into calls within ``_CONTEXT_SCORE_ELEMENTS``. + + spans lists ``(rows, visible bound)`` of each context request in packed + order. Each returned ``(row_start, row_end, table_start, table_end, + max_kv_len)`` scores rows ``row_start:row_end`` against context tables + ``table_start:table_end``. Consecutive requests share a call while it + fits; a request that does not fit alone is split by rows. Costs count + only the transients that grow with the visible pools. + """ + state = self._cache_state(metadata) + kpool = self.sparse_params.index_kpool + budget = self._CONTEXT_SCORE_ELEMENTS + # The BF16 scorer writes every pool column of the configured capacity. + capacity_pools = -(-state.block_tables.shape[1] * state.tokens_per_block // kpool) + # Gathered BF16 pool keys plus their FP8 copy, in FP32 elements. + key_elements = self.sparse_params.index_head_dim * 3 // 4 + + def cost(rows: int, tables: int, pools: int) -> int: + if not self._indexer_fp8 or pools == 0: + return rows * capacity_pools + logits = -(-tables * pools // 256) * 256 + copy = max(1024, -(-pools // 64) * 64) if tables > 1 else 0 + return rows * (logits + copy) + tables * pools * key_elements + + calls: list[tuple[int, int, int, int, int]] = [] + group: list[int] | None = None # [row_start, rows, table_start, tables, max visible] + row_start = 0 + for table, (rows, visible) in enumerate(spans): + if group is not None: + merged = max(group[4], visible) + if cost(group[1] + rows, group[3] + 1, merged // kpool) <= budget: + group[1] += rows + group[3] += 1 + group[4] = merged + row_start += rows + continue + calls.append( + (group[0], group[0] + group[1], group[2], group[2] + group[3], group[4]) + ) + group = None + if cost(rows, 1, visible // kpool) <= budget: + group = [row_start, rows, table, 1, visible] + else: + fixed = cost(0, 1, visible // kpool) + tile = max(1, (budget - fixed) // max(1, cost(1, 1, visible // kpool) - fixed)) + for start in range(row_start, row_start + rows, tile): + end = min(start + tile, row_start + rows) + calls.append((start, end, table, table + 1, visible)) + row_start += rows + if group is not None: + calls.append((group[0], group[0] + group[1], group[2], group[2] + group[3], group[4])) + return calls + def score_pools( self, q: torch.Tensor, @@ -391,6 +469,7 @@ def score_pools( rows_per_request: int = 1, request_ids: torch.Tensor | None = None, max_kv_len: int | None = None, + table_range: tuple[int, int] | None = None, ) -> torch.Tensor: """Score complete pools into FP32 [rows, pool_capacity]. @@ -403,26 +482,28 @@ def score_pools( only reachable pools are scored; columns past it are left unwritten and the top-k must scan only each row's complete pools. Packed context rows with such a bound use the FP8 scorer on SM100-family GPUs, whose result - may be a strided view. + may be a strided view. table_range, from :meth:`plan_context_scores`, + names the context tables those rows' request_ids fall in. """ state = self._cache_state(metadata) tables, gen_lens = self._rows(state, rows_per_request, request_ids=request_ids) capacity = state.block_tables.shape[1] * state.tokens_per_block kpool = self.sparse_params.index_kpool if self._indexer_fp8 and request_ids is not None and max_kv_len is not None: + # Packed context rows only reference the context requests' tables. + first, last = (0, state.num_contexts) if table_range is None else table_range scores = kpool_score_fp8( q, weights, state.index_pool, - # Packed context rows only reference the context requests' tables. - tables[: state.num_contexts], + tables[first:last], kv_lens, state.tokens_per_block, head_dim=self.sparse_params.index_head_dim, kpool=kpool, q_scale=q_scale, w_scale=w_scale, - request_ids=request_ids, + request_ids=request_ids - first if first else request_ids, max_kv_len=max_kv_len, ) if scores is not None: diff --git a/tensorrt_llm/_torch/models/modeling_glm5_next.py b/tensorrt_llm/_torch/models/modeling_glm5_next.py index b0a34844546c..8a2e7b0ccf85 100644 --- a/tensorrt_llm/_torch/models/modeling_glm5_next.py +++ b/tensorrt_llm/_torch/models/modeling_glm5_next.py @@ -1339,6 +1339,7 @@ def _select_and_attend_paged( request_ids: torch.Tensor | None = None, max_visible: int | None = None, num_candidates: torch.Tensor | None = None, + context_spans: Sequence[tuple[int, int]] | None = None, ) -> torch.Tensor: """Score pools, select members and tail, then attend to paged latent rows. @@ -1347,7 +1348,9 @@ def _select_and_attend_paged( tables are known. Single-token decode reads live lengths from metadata. All device work uses fixed buffer shapes for graph replay. max_visible is an optional host bound on visible that limits scoring work; - num_candidates, if given, is visible // index_kpool as int32. + num_candidates, if given, is visible // index_kpool as int32. Packed context + passes context_spans, each request's (rows, visible bound), so scoring + runs in calls of bounded memory. """ indexer = self.indexer rows = q_resid.shape[0] @@ -1356,30 +1359,42 @@ def _select_and_attend_paged( # each request contributes several rows (context or verification). plain_decode = request_ids is None and rows_per_request == 1 kv_lens = None if plain_decode else visible - scores = self.attn_backend.score_pools( - q_index, - index_weights, - metadata, - q_scale=indexer.softmax_scale, - w_scale=indexer.head_mix_scale, - kv_lens=kv_lens, - rows_per_request=rows_per_request, - request_ids=request_ids, - max_kv_len=max_visible, - ) num_cand = ( (visible // indexer.index_kpool).to(torch.int32) if num_candidates is None else num_candidates ) - selected = torch.empty(rows, indexer.select_k, dtype=torch.int32, device=scores.device) - indexer.pool_top_k( - scores, - selected, - is_prefill=False, - sequence_lengths=num_cand, - scan_lengths=num_cand, - ) + selected = torch.empty(rows, indexer.select_k, dtype=torch.int32, device=q_index.device) + # Each call scores and selects its own rows; scores are dropped before the next. + if context_spans is None: + calls = [(slice(None), max_visible, None)] + else: + calls = [ + (slice(start, end), max_kv_len, (first, last)) + for start, end, first, last, max_kv_len in self.attn_backend.plan_context_scores( + metadata, context_spans + ) + ] + for part, max_kv_len, table_range in calls: + scores = self.attn_backend.score_pools( + q_index[part], + index_weights[part], + metadata, + q_scale=indexer.softmax_scale, + w_scale=indexer.head_mix_scale, + kv_lens=None if kv_lens is None else kv_lens[part], + rows_per_request=rows_per_request, + request_ids=None if request_ids is None else request_ids[part], + max_kv_len=max_kv_len, + table_range=table_range, + ) + indexer.pool_top_k( + scores, + selected[part], + is_prefill=False, + sequence_lengths=num_cand[part], + scan_lengths=num_cand[part], + ) topk_rows = self.attn_backend.expand_selection( selected, metadata, @@ -1435,10 +1450,14 @@ def forward_prefill( request_ids=rows.final_request_ids, ) query = self.q_b_proj(q_resid).view(-1, self.num_heads, self.qk_head_dim) - max_visible = max( - int(cached_lens[i]) + int(cu_seqlens[i + 1]) - int(cu_seqlens[i]) + spans = [ + ( + int(cu_seqlens[i + 1]) - int(cu_seqlens[i]), + int(cached_lens[i]) + int(cu_seqlens[i + 1]) - int(cu_seqlens[i]), + ) for i in range(len(cu_seqlens) - 1) - ) + ] + max_visible = max(visible for _, visible in spans) return self._select_and_attend_paged( q_resid, query, @@ -1449,6 +1468,7 @@ def forward_prefill( reduce=reduce, request_ids=rows.request_ids, max_visible=max_visible, + context_spans=spans, ) def forward_decode( diff --git a/tensorrt_llm/_torch/pyexecutor/_util.py b/tensorrt_llm/_torch/pyexecutor/_util.py index 492d6cc6cd77..5442ecd42848 100644 --- a/tensorrt_llm/_torch/pyexecutor/_util.py +++ b/tensorrt_llm/_torch/pyexecutor/_util.py @@ -564,9 +564,14 @@ def get_attention_workspace_fixed_bytes(model_config, mapping, **capacities) -> int: """Capacity-sized reserve declared by the model's selected backend.""" from ..attention.backends.utils import get_attention_backend - return get_attention_backend( - model_config.attn_backend).runtime_workspace_fixed_bytes( - model_config, mapping, **capacities) + backend = get_attention_backend(model_config.attn_backend) + if config_utils.is_glm5_next(model_config.pretrained_config): + # glm5_next layers select the glm_kpool sparse backend themselves, so + # model_config carries no sparse_attention_config naming it. + from ..attention.backends.sparse.glm_kpool import \ + GlmKpoolSparseAttention as backend + return backend.runtime_workspace_fixed_bytes(model_config, mapping, + **capacities) def get_overlap_headroom_penalty_workspace_bytes( diff --git a/tests/unittest/_torch/attention/sparse/glm_kpool/test_glm_kpool.py b/tests/unittest/_torch/attention/sparse/glm_kpool/test_glm_kpool.py index b859d7514e5a..7045684355b6 100644 --- a/tests/unittest/_torch/attention/sparse/glm_kpool/test_glm_kpool.py +++ b/tests/unittest/_torch/attention/sparse/glm_kpool/test_glm_kpool.py @@ -502,3 +502,85 @@ def test_latent_pool_rows_rejects_layouts_without_a_uniform_row_view(): ) with pytest.raises(ValueError, match="not multiples of dim"): latent_pool_rows(odd_stride) + + +def _planner(fp8: bool, pages: int = 4096, tokens_per_block: int = 32): + backend = object.__new__(GlmKpoolSparseAttention) + backend.sparse_params = GlmKpoolSparseParams() + backend._indexer_fp8 = fp8 + state = SimpleNamespace( + block_tables=torch.zeros(8, pages, dtype=torch.long), tokens_per_block=tokens_per_block + ) + backend._cache_state = Mock(return_value=state) + return backend + + +def _check_plan(calls, spans, budget, fp8): + """Calls cover every row in order, name each row's table, and fit the FP8 logits budget.""" + request_starts = [0] + for rows, _ in spans: + request_starts.append(request_starts[-1] + rows) + assert calls[0][0] == 0 and calls[-1][1] == request_starts[-1] + for (_, end, _, _, _), (start, _, _, _, _) in zip(calls, calls[1:]): + assert end == start + kpool = GlmKpoolSparseParams().index_kpool + for start, end, first, last, max_kv_len in calls: + assert start < end and first < last + assert request_starts[first] <= start and end <= request_starts[last] + assert max_kv_len >= max(visible for _, visible in spans[first:last]) + if fp8: + assert (end - start) * (last - first) * (max_kv_len // kpool) <= budget + + +@pytest.mark.parametrize( + "spans", + [ + [(13324, 13324)], # one fresh long prompt + [(512, 16384 + 512)] * 4, # cached prefixes + [(16384, 131072)], # last chunk of a 128K prompt + [(3, 3)] * 64 + [(4000, 4000)], # tiny prompts beside a long one + ], + ids=["fresh", "prefixes", "long-chunk", "tiny-and-long"], +) +@pytest.mark.parametrize("fp8", [False, True], ids=["bf16", "fp8"]) +def test_context_score_plan_stays_within_the_reserved_bound(spans, fp8): + backend = _planner(fp8) + budget = GlmKpoolSparseAttention._CONTEXT_SCORE_ELEMENTS + calls = backend.plan_context_scores(object(), spans) + _check_plan(calls, spans, budget, fp8) + if not fp8: + capacity_pools = 4096 * 32 // GlmKpoolSparseParams().index_kpool + assert all((end - start) * capacity_pools <= budget for start, end, *_ in calls) + + +def test_context_score_plan_keeps_a_fresh_prompt_in_one_call(): + calls = _planner(True).plan_context_scores(object(), [(13324, 13324)]) + assert calls == [(0, 13324, 0, 1, 13324)] + + +def test_context_score_plan_groups_short_requests_and_splits_a_long_one(): + backend = _planner(True) + grouped = backend.plan_context_scores(object(), [(100, 100)] * 3) + assert grouped == [(0, 300, 0, 3, 100)] + split = backend.plan_context_scores(object(), [(16384, 131072)]) + assert len(split) > 1 and {call[2:] for call in split} == {(0, 1, 131072)} + + +def test_kv_cache_sizing_reserves_the_context_score_bound(): + from tensorrt_llm._torch.pyexecutor._util import get_attention_workspace_fixed_bytes + + capacities = dict( + max_batch_size=64, max_num_tokens=16384, max_seq_len=131072, enable_cuda_graph=True + ) + + def reserve(model_type): + config = SimpleNamespace( + attn_backend="TRTLLM", + sparse_attention_config=None, + pretrained_config=SimpleNamespace(model_type=model_type), + ) + return get_attention_workspace_fixed_bytes(config, None, **capacities) + + bound = GlmKpoolSparseAttention._CONTEXT_SCORE_ELEMENTS * 4 + assert reserve("glm5_next") == reserve("glm5_next_text") == bound + assert reserve("llama") == 0 diff --git a/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py b/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py index afc8fc55e644..1e16af14518b 100644 --- a/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py +++ b/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py @@ -423,6 +423,51 @@ def test_fp8_pool_scores_track_bf16_selection(single_request): assert overlaps and sum(overlaps) / len(overlaps) > 0.97 and min(overlaps) > 0.9 +@pytest.mark.skipif( + get_sm_version() not in (100, 103), reason="DeepGEMM MQA logits require SM100/103" +) +def test_fp8_pool_scores_of_a_table_range_match_the_packed_call(): + """Scoring one request against its own table equals its rows of a multi-table call.""" + gen = torch.Generator(device="cuda").manual_seed(11) + heads, tpb, slots = 32, 32, 400 + lens = [3000, 2500, 1200] + pages = [(length + tpb - 1) // tpb for length in lens] + pool = torch.randn(slots, 2, tpb, 3 * HD, generator=gen, device="cuda").to(torch.bfloat16)[:, 1] + perm = torch.randperm(slots, generator=gen, device="cuda") + tables = torch.zeros(len(lens), max(pages), dtype=torch.int64, device="cuda") + start = 0 + for i, count in enumerate(pages): + tables[i, :count] = perm[start : start + count] + start += count + request_ids = torch.cat( + [torch.full((length,), i, dtype=torch.int32) for i, length in enumerate(lens)] + ).cuda() + kv_lens = torch.cat([torch.arange(1, length + 1, dtype=torch.int32) for length in lens]).cuda() + q = torch.randn(kv_lens.shape[0], heads, HD, generator=gen, device="cuda").to(torch.bfloat16) + w = torch.randn(kv_lens.shape[0], heads, generator=gen, device="cuda").to(torch.bfloat16) + kwargs = dict(head_dim=HD, kpool=KPOOL, q_scale=HD**-0.5, w_scale=heads**-0.5) + packed = kpool_score_fp8( + q, w, pool, tables, kv_lens, tpb, request_ids=request_ids, max_kv_len=max(lens), **kwargs + ) + first = lens[0] + rows = slice(first, first + lens[1]) + alone = kpool_score_fp8( + q[rows], + w[rows], + pool, + tables[1:2], + kv_lens[rows], + tpb, + request_ids=request_ids[rows] - 1, + max_kv_len=lens[1], + **kwargs, + ) + assert packed is not None and alone is not None + for row in range(0, lens[1], 97): + visible = (row + 1) // KPOOL + torch.testing.assert_close(alone[row, :visible], packed[first + row, :visible]) + + def test_pool_expansion_graph_replay_tracks_request_tables_and_tails(): # Requests occupy disjoint slots, with gaps between slots from coalesced buffers. tables = torch.tensor([[3, 1], [5, 2]], dtype=torch.int64, device="cuda") From 1d7e6c0d10156614f76b366f7502e9cdb82cb83e Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Wed, 21 Oct 2026 08:24:07 -0700 Subject: [PATCH 08/13] [None][chore] Simplify GLM-5.3-Flash verification row addressing Verification rows now always carry request_ids, their block-table rows, on the eager path as on the CUDA-graph path, so score_pools and expand_selection no longer repeat the generation tables per draft token. The FP8 context scorer is gated on packed context rows (rows_per_request == 1), and its 2^29-element cap is gone because plan_context_scores bounds every call. Signed-off-by: Ruocheng Jia --- .../backends/sparse/glm_kpool/backend.py | 25 +++++++-------- .../backends/sparse/glm_kpool/kernels.py | 10 ++---- .../_torch/models/modeling_glm5_next.py | 32 ++++++++++--------- 3 files changed, 31 insertions(+), 36 deletions(-) diff --git a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py index 4265de6ff516..211887e39255 100644 --- a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py +++ b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py @@ -356,21 +356,17 @@ def append_paged_state( def _rows( self, state: _GlmKpoolCacheState, - rows_per_request: int = 1, request_ids: torch.Tensor | None = None, ) -> tuple[torch.Tensor, torch.Tensor | None]: """Select generation tables, or all tables for per-row request IDs. Rows with request_ids (packed context, or verification rows given their - tables' batch rows) index all tables; otherwise verification repeats - generation tables per token. Callers supply each query's visible length - for verification and packed context rows. + tables' batch rows) index all tables and supply each query's visible + length; single-token decode rows use the generation tables and live lengths. """ if request_ids is not None: return state.block_tables, None gen = slice(state.num_contexts, None) - if rows_per_request > 1: - return state.block_tables[gen].repeat_interleave(rows_per_request, dim=0), None return state.block_tables[gen], state.kv_lens[gen] def update_pool_keys( @@ -473,10 +469,9 @@ def score_pools( ) -> torch.Tensor: """Score complete pools into FP32 [rows, pool_capacity]. - q is [rows, heads, head_dim], weights is [rows, heads]. Packed context uses - request_ids; verification either does too or repeats generation tables - rows_per_request times. - Both supply per-query kv_lens. Single-token decode uses live metadata lengths. + q is [rows, heads, head_dim], weights is [rows, heads]. Packed context and + verification (rows_per_request rows per request) use request_ids and + per-query kv_lens. Single-token decode uses live metadata lengths. Incomplete/invisible pools receive the FP32 minimum before top-k. Eager callers that know a host bound on the visible lengths pass max_kv_len so only reachable pools are scored; columns past it are left unwritten and @@ -485,11 +480,14 @@ def score_pools( may be a strided view. table_range, from :meth:`plan_context_scores`, names the context tables those rows' request_ids fall in. """ + if rows_per_request > 1 and request_ids is None: + raise ValueError("glm_kpool verification rows need request_ids") state = self._cache_state(metadata) - tables, gen_lens = self._rows(state, rows_per_request, request_ids=request_ids) + tables, gen_lens = self._rows(state, request_ids) capacity = state.block_tables.shape[1] * state.tokens_per_block kpool = self.sparse_params.index_kpool - if self._indexer_fp8 and request_ids is not None and max_kv_len is not None: + packed_context = request_ids is not None and rows_per_request == 1 + if self._indexer_fp8 and packed_context and max_kv_len is not None: # Packed context rows only reference the context requests' tables. first, last = (0, state.num_contexts) if table_range is None else table_range scores = kpool_score_fp8( @@ -538,7 +536,6 @@ def expand_selection( metadata, *, kv_lens: torch.Tensor | None = None, - rows_per_request: int = 1, request_ids: torch.Tensor | None = None, ) -> torch.Tensor: """Selected pools ``[N, select_k]`` -> latent row ids for the kernel. @@ -549,7 +546,7 @@ def expand_selection( row addressing as :meth:`score_pools`. """ state = self._cache_state(metadata) - tables, gen_lens = self._rows(state, rows_per_request, request_ids=request_ids) + tables, gen_lens = self._rows(state, request_ids) _, base_row, rows_per_slot = latent_pool_rows(state.latent_pool) return kpool_expand( selected, diff --git a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/kernels.py b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/kernels.py index 56368dc681ff..86037408ca0d 100644 --- a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/kernels.py +++ b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/kernels.py @@ -512,10 +512,6 @@ def kpool_score( return out -# Largest FP8 logits transient (elements) kpool_score_fp8 allocates. -_FP8_SCORE_MAX_ELEMENTS = 1 << 29 - - def kpool_score_fp8( q: torch.Tensor, weights: torch.Tensor, @@ -539,15 +535,15 @@ def kpool_score_fp8( :func:`kpool_score`. ``max_kv_len`` bounds every row's ``kv_lens``. Row ``r`` scores only the first ``kv_lens[r] // kpool`` pools of table ``request_ids[r]``; later columns are not meaningful, so readers must stop there. The result may be - a strided view with an aligned row stride. Returns ``None`` when the logits - transient would exceed ``_FP8_SCORE_MAX_ELEMENTS``. + a strided view with an aligned row stride. Returns ``None`` when there are no + rows or no complete pools. """ from tensorrt_llm.deep_gemm import fp8_mqa_logits n = q.shape[0] pools = max(0, int(max_kv_len)) // kpool tables = block_tables.shape[0] - if n == 0 or pools == 0 or n * tables * pools > _FP8_SCORE_MAX_ELEMENTS: + if n == 0 or pools == 0: return None positions = torch.arange(pools, device=q.device) * kpool page = positions // tokens_per_block diff --git a/tensorrt_llm/_torch/models/modeling_glm5_next.py b/tensorrt_llm/_torch/models/modeling_glm5_next.py index 8a2e7b0ccf85..39b2da22d698 100644 --- a/tensorrt_llm/_torch/models/modeling_glm5_next.py +++ b/tensorrt_llm/_torch/models/modeling_glm5_next.py @@ -292,9 +292,9 @@ class Glm5NextGenerationRows: per layer. ``positions`` is ``[B]`` for plain decode and ``[B, T]`` while a target verifies ``T - 1`` drafts; ``visible[i]`` is row ``i``'s visible prefix length and ``num_candidates[i]`` its complete-pool count (int32). - With ``first_table`` (the batch's block-table row of the first generation - request), verification rows also get ``request_ids[i]``, row ``i``'s - block-table row (int32), so kernels index tables instead of copying them. + Verification rows also get ``request_ids[i]``, row ``i``'s block-table row + (int32, counted from ``first_table``, the batch row of the first generation + request), so kernels index tables instead of copying them. """ positions: torch.Tensor @@ -308,7 +308,7 @@ def build( kv_lens: torch.Tensor, tokens_per_request: int, kpool: int, - first_table: int | None = None, + first_table: int, ) -> Glm5NextGenerationRows: request_ids = None if tokens_per_request <= 1: @@ -319,11 +319,10 @@ def build( steps = torch.arange(tokens_per_request, device=device) positions = (kv_lens - tokens_per_request).unsqueeze(1) + steps visible = (positions + 1).reshape(-1) - if first_table is not None: - tables = torch.arange( - first_table, first_table + kv_lens.shape[0], dtype=torch.int32, device=device - ) - request_ids = tables.repeat_interleave(tokens_per_request) + tables = torch.arange( + first_table, first_table + kv_lens.shape[0], dtype=torch.int32, device=device + ) + request_ids = tables.repeat_interleave(tokens_per_request) return cls( positions=positions, visible=visible, @@ -1344,9 +1343,9 @@ def _select_and_attend_paged( """Score pools, select members and tail, then attend to paged latent rows. visible gives each query's prefix length. Packed context uses request_ids; - verification uses rows_per_request, and request_ids too when its rows' - tables are known. Single-token decode reads live lengths - from metadata. All device work uses fixed buffer shapes for graph replay. + verification uses rows_per_request and request_ids. Single-token decode + reads live lengths from metadata. All device work uses fixed buffer shapes + for graph replay. max_visible is an optional host bound on visible that limits scoring work; num_candidates, if given, is visible // index_kpool as int32. Packed context passes context_spans, each request's (rows, visible bound), so scoring @@ -1399,7 +1398,6 @@ def _select_and_attend_paged( selected, metadata, kv_lens=kv_lens, - rows_per_request=rows_per_request, request_ids=request_ids, ) out_latent = self.attn_backend.forward( @@ -1491,7 +1489,9 @@ def forward_decode( batch = hidden_states.shape[0] indexer = self.indexer rows = ( - Glm5NextGenerationRows.build(kv_lens, 1, indexer.index_kpool) + Glm5NextGenerationRows.build( + kv_lens, 1, indexer.index_kpool, first_table=int(metadata.num_contexts) + ) if runtime_ctx is None else runtime_ctx.generation_rows(indexer.index_kpool) ) @@ -1598,7 +1598,9 @@ def forward_verify( ) kpool = self.indexer.index_kpool rows = ( - Glm5NextGenerationRows.build(kv_lens, tokens_per_request, kpool) + Glm5NextGenerationRows.build( + kv_lens, tokens_per_request, kpool, first_table=int(metadata.num_contexts) + ) if runtime_ctx is None else runtime_ctx.generation_rows(kpool) ) From c347231b738a24dbeb963b6f41f70c5f5c929978 Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Wed, 21 Oct 2026 08:26:47 -0700 Subject: [PATCH 09/13] [None][doc] Note the low-M GEMM option in the GLM-5.3-Flash guide TRTLLM_LOW_M_GEMM_BACKEND=auto shortens concurrency-1 decode slightly but adds autotuning time at startup, so the guide describes it as an opt-in for latency rather than a default. Signed-off-by: Ruocheng Jia --- .../deployment-guide-for-glm-5.3-flash-on-trtllm.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md b/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md index 5c11f00bf8ba..38ffe3ba0620 100644 --- a/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md +++ b/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md @@ -94,6 +94,8 @@ If you want to use the latest main branch, you can build from source: [https://n Use these configurations as starting points for 4x B200 with FP8 weights and a BF16 KV cache. Tune the batch size and token budget for your workload. +For single-request latency, setting `TRTLLM_LOW_M_GEMM_BACKEND=auto` before launching the server routes BF16 projections with at most 32 rows to autotuned low-M GEMM kernels. With MTP on 4x B200 it lowered inter-token latency by about 2% at concurrency 1 and left concurrency 8 unchanged, but tuning those kernels adds about two minutes to server startup. + #### B200 FP8 Config ```bash From b2bc7ffdef8975a3ab7ff2e9eb3b2186cd1eef87 Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Thu, 22 Oct 2026 00:16:01 -0700 Subject: [PATCH 10/13] [None][fix] Count every GLM-5.3-Flash context-scoring buffer in its bound The context-score bound undercounted what a scoring call holds: - Each tile's scores stayed referenced while the next call allocated, and the last tile lived on into attention. Release them after the top-k. - The FP8 cost model missed the extra 256 columns fp8_mqa_logits pads each row with, the [rows, pools] gather temporary of a multi-table call, and the key scales and slot indices. The planner and the KV-cache reserve now share one cost function that counts them. - The reserve was min(bound, max_num_tokens * max_seq_len), which ignores how many request tables a call can gather: 64 one-row continuations of 4K prompts need about 43 MB against a 1 MiB reserve. Reserve the largest call the planner can emit for the serving capacities instead. Signed-off-by: Ruocheng Jia --- .../backends/sparse/glm_kpool/backend.py | 55 ++++++++++++++--- .../_torch/models/modeling_glm5_next.py | 4 +- .../sparse/glm_kpool/test_glm_kpool.py | 59 ++++++++++++++++--- .../sparse/glm_kpool/test_kernels.py | 50 ++++++++++++++++ 4 files changed, 150 insertions(+), 18 deletions(-) diff --git a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py index 211887e39255..cf306b142f42 100644 --- a/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py +++ b/tensorrt_llm/_torch/attention/backends/sparse/glm_kpool/backend.py @@ -33,6 +33,7 @@ import torch +from tensorrt_llm._torch.pyexecutor.config_utils import unwrap_glm5_next_text_config from tensorrt_llm._utils import get_sm_version, is_sm_100f from ...interface import ( @@ -145,6 +146,27 @@ class GlmKpoolSparseAttention(TrtllmAttention): # bound and runtime_workspace_fixed_bytes reserves it in KV-cache sizing. _CONTEXT_SCORE_ELEMENTS = 1 << 26 + @staticmethod + def _context_score_cost( + rows: int, tables: int, pools: int, *, fp8: bool, head_dim: int, capacity_pools: int + ) -> int: + """FP32 elements a scoring call of ``rows`` rows over ``tables`` tables holds at once. + + Counts the buffers that grow with the visible ``pools``: the BF16 scorer's + capacity-wide output, or every buffer :func:`kpool_score_fp8` keeps alive + together -- the padded logits, a multi-table call's gather temporary and + aligned row copy, and per gathered key its BF16 row, FP8 row, scale and + int64 slot index. + """ + if not fp8 or pools == 0: + return rows * capacity_pools + # fp8_mqa_logits pads each row to a multiple of 256 columns, plus 256. + logits = -(-tables * pools // 256) * 256 + 256 + copy = max(1024, -(-pools // 64) * 64) + pools if tables > 1 else 0 + keys = tables * pools * (head_dim // 2 + head_dim // 4 + 3) + # int64 pool positions, pages and offsets. + return rows * (logits + copy) + keys + 6 * pools + @classmethod def runtime_workspace_fixed_bytes( cls, @@ -156,9 +178,25 @@ def runtime_workspace_fixed_bytes( max_seq_len: int, enable_cuda_graph: bool, ) -> int: + """Bytes of the largest scoring call plan_context_scores can emit for these capacities.""" if max_num_tokens <= 0 or max_seq_len <= 0: return 0 - return min(cls._CONTEXT_SCORE_ELEMENTS, max_num_tokens * max_seq_len) * 4 + config = unwrap_glm5_next_text_config(model_config.pretrained_config) + defaults = GlmKpoolSparseParams() + kpool = getattr(config, "index_kpool", defaults.index_kpool) + head_dim = getattr(config, "index_head_dim", defaults.index_head_dim) + pools = -(-max_seq_len // kpool) + tables = max(1, min(max_batch_size, max_num_tokens)) + + def cost(rows: int, tables: int, fp8: bool) -> int: + return cls._context_score_cost( + rows, tables, pools, fp8=fp8, head_dim=head_dim, capacity_pools=pools + ) + + largest = max(cost(max_num_tokens, tables, True), cost(max_num_tokens, 1, False)) + # Calls fit the budget, except that a single row is never split. + bound = max(cls._CONTEXT_SCORE_ELEMENTS, cost(1, 1, True), cost(1, 1, False)) + return min(largest, bound) * 4 def __init__( self, @@ -414,15 +452,16 @@ def plan_context_scores( budget = self._CONTEXT_SCORE_ELEMENTS # The BF16 scorer writes every pool column of the configured capacity. capacity_pools = -(-state.block_tables.shape[1] * state.tokens_per_block // kpool) - # Gathered BF16 pool keys plus their FP8 copy, in FP32 elements. - key_elements = self.sparse_params.index_head_dim * 3 // 4 def cost(rows: int, tables: int, pools: int) -> int: - if not self._indexer_fp8 or pools == 0: - return rows * capacity_pools - logits = -(-tables * pools // 256) * 256 - copy = max(1024, -(-pools // 64) * 64) if tables > 1 else 0 - return rows * (logits + copy) + tables * pools * key_elements + return self._context_score_cost( + rows, + tables, + pools, + fp8=self._indexer_fp8, + head_dim=self.sparse_params.index_head_dim, + capacity_pools=capacity_pools, + ) calls: list[tuple[int, int, int, int, int]] = [] group: list[int] | None = None # [row_start, rows, table_start, tables, max visible] diff --git a/tensorrt_llm/_torch/models/modeling_glm5_next.py b/tensorrt_llm/_torch/models/modeling_glm5_next.py index 39b2da22d698..a1af634b26e3 100644 --- a/tensorrt_llm/_torch/models/modeling_glm5_next.py +++ b/tensorrt_llm/_torch/models/modeling_glm5_next.py @@ -1364,7 +1364,7 @@ def _select_and_attend_paged( else num_candidates ) selected = torch.empty(rows, indexer.select_k, dtype=torch.int32, device=q_index.device) - # Each call scores and selects its own rows; scores are dropped before the next. + # Each call scores and selects its own rows. if context_spans is None: calls = [(slice(None), max_visible, None)] else: @@ -1394,6 +1394,8 @@ def _select_and_attend_paged( sequence_lengths=num_cand[part], scan_lengths=num_cand[part], ) + # Release this call's scores before the next call allocates its own. + del scores topk_rows = self.attn_backend.expand_selection( selected, metadata, diff --git a/tests/unittest/_torch/attention/sparse/glm_kpool/test_glm_kpool.py b/tests/unittest/_torch/attention/sparse/glm_kpool/test_glm_kpool.py index 7045684355b6..8ae3b7a00c1f 100644 --- a/tests/unittest/_torch/attention/sparse/glm_kpool/test_glm_kpool.py +++ b/tests/unittest/_torch/attention/sparse/glm_kpool/test_glm_kpool.py @@ -515,21 +515,37 @@ def _planner(fp8: bool, pages: int = 4096, tokens_per_block: int = 32): return backend -def _check_plan(calls, spans, budget, fp8): - """Calls cover every row in order, name each row's table, and fit the FP8 logits budget.""" +def _call_costs(backend, calls): + """Each planned call's FP32-element cost under the backend's cost model.""" + state = backend._cache_state(None) + kpool = backend.sparse_params.index_kpool + capacity_pools = state.block_tables.shape[1] * state.tokens_per_block // kpool + return [ + GlmKpoolSparseAttention._context_score_cost( + end - start, + last - first, + max_kv_len // kpool, + fp8=backend._indexer_fp8, + head_dim=backend.sparse_params.index_head_dim, + capacity_pools=capacity_pools, + ) + for start, end, first, last, max_kv_len in calls + ] + + +def _check_plan(backend, calls, spans, budget): + """Calls cover every row in order, name each row's table, and fit the budget.""" request_starts = [0] for rows, _ in spans: request_starts.append(request_starts[-1] + rows) assert calls[0][0] == 0 and calls[-1][1] == request_starts[-1] for (_, end, _, _, _), (start, _, _, _, _) in zip(calls, calls[1:]): assert end == start - kpool = GlmKpoolSparseParams().index_kpool for start, end, first, last, max_kv_len in calls: assert start < end and first < last assert request_starts[first] <= start and end <= request_starts[last] assert max_kv_len >= max(visible for _, visible in spans[first:last]) - if fp8: - assert (end - start) * (last - first) * (max_kv_len // kpool) <= budget + assert max(_call_costs(backend, calls)) <= budget @pytest.mark.parametrize( @@ -547,10 +563,7 @@ def test_context_score_plan_stays_within_the_reserved_bound(spans, fp8): backend = _planner(fp8) budget = GlmKpoolSparseAttention._CONTEXT_SCORE_ELEMENTS calls = backend.plan_context_scores(object(), spans) - _check_plan(calls, spans, budget, fp8) - if not fp8: - capacity_pools = 4096 * 32 // GlmKpoolSparseParams().index_kpool - assert all((end - start) * capacity_pools <= budget for start, end, *_ in calls) + _check_plan(backend, calls, spans, budget) def test_context_score_plan_keeps_a_fresh_prompt_in_one_call(): @@ -566,6 +579,34 @@ def test_context_score_plan_groups_short_requests_and_splits_a_long_one(): assert len(split) > 1 and {call[2:] for call in split} == {(0, 1, 131072)} +@pytest.mark.parametrize( + "capacities", + [ + dict(max_batch_size=64, max_num_tokens=64, max_seq_len=4096), + dict(max_batch_size=8, max_num_tokens=2048, max_seq_len=32768), + dict(max_batch_size=64, max_num_tokens=16384, max_seq_len=131072), + ], + ids=["small-token-budget", "mid", "large"], +) +@pytest.mark.parametrize("fp8", [False, True], ids=["bf16", "fp8"]) +def test_kv_cache_reserve_covers_every_planned_call(capacities, fp8): + tokens, seq_len = capacities["max_num_tokens"], capacities["max_seq_len"] + requests = min(capacities["max_batch_size"], tokens) + config = SimpleNamespace(pretrained_config=SimpleNamespace(model_type="glm5_next_text")) + reserve = GlmKpoolSparseAttention.runtime_workspace_fixed_bytes( + config, None, enable_cuda_graph=True, **capacities + ) + backend = _planner(fp8, pages=seq_len // 32) + batches = [ + [(1, seq_len)] * requests, # one continuation row per request + [(tokens, seq_len)], # one request takes the whole token budget + [(tokens // requests, seq_len)] * requests, # the budget split across requests + ] + for spans in batches: + calls = backend.plan_context_scores(object(), spans) + assert max(_call_costs(backend, calls)) * 4 <= reserve + + def test_kv_cache_sizing_reserves_the_context_score_bound(): from tensorrt_llm._torch.pyexecutor._util import get_attention_workspace_fixed_bytes diff --git a/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py b/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py index 1e16af14518b..42a225841264 100644 --- a/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py +++ b/tests/unittest/_torch/attention/sparse/glm_kpool/test_kernels.py @@ -423,6 +423,56 @@ def test_fp8_pool_scores_track_bf16_selection(single_request): assert overlaps and sum(overlaps) / len(overlaps) > 0.97 and min(overlaps) > 0.9 +@pytest.mark.skipif( + get_sm_version() not in (100, 103), reason="DeepGEMM MQA logits require SM100/103" +) +@pytest.mark.parametrize("lens", [[16384], [16000, 12000, 8000]], ids=["one-table", "three-tables"]) +def test_fp8_pool_score_peak_memory_fits_the_planned_cost(lens): + """kpool_score_fp8's peak allocation beyond its row-sized buffers fits the planner's cost.""" + from tensorrt_llm._torch.attention.backends.sparse.glm_kpool import GlmKpoolSparseAttention + + gen = torch.Generator(device="cuda").manual_seed(13) + heads, tpb, rows_per_table = 32, 32, 256 + pages = [(length + tpb - 1) // tpb for length in lens] + pool = torch.randn(sum(pages) + 8, 2, tpb, 3 * HD, generator=gen, device="cuda") + pool = pool.to(torch.bfloat16)[:, 1] + tables = torch.zeros(len(lens), max(pages), dtype=torch.int64, device="cuda") + start = 0 + for i, count in enumerate(pages): + tables[i, :count] = torch.arange(start, start + count, device="cuda") + start += count + # The last rows_per_table queries of each prompt, so most pools are visible. + request_ids = torch.cat( + [torch.full((rows_per_table,), i, dtype=torch.int32) for i in range(len(lens))] + ).cuda() + kv_lens = torch.cat( + [ + torch.arange(length - rows_per_table + 1, length + 1, dtype=torch.int32) + for length in lens + ] + ).cuda() + n = kv_lens.shape[0] + q = torch.randn(n, heads, HD, generator=gen, device="cuda").to(torch.bfloat16) + w = torch.randn(n, heads, generator=gen, device="cuda").to(torch.bfloat16) + kwargs = dict(head_dim=HD, kpool=KPOOL, q_scale=HD**-0.5, w_scale=heads**-0.5) + args = (q, w, pool, tables, kv_lens, tpb) + kpool_score_fp8(*args, request_ids=request_ids, max_kv_len=max(lens), **kwargs) # JIT warmup + torch.cuda.synchronize() + torch.cuda.reset_peak_memory_stats() + base = torch.cuda.memory_allocated() + scores = kpool_score_fp8(*args, request_ids=request_ids, max_kv_len=max(lens), **kwargs) + torch.cuda.synchronize() + peak = torch.cuda.max_memory_allocated() - base + del scores + cost = GlmKpoolSparseAttention._context_score_cost( + n, len(lens), max(lens) // KPOOL, fp8=True, head_dim=HD, capacity_pools=0 + ) + # Quantized queries (with the quantizer's scratch), head weights and per-row + # indices grow only with rows; startup profiling measures them. + row_bytes = n * heads * (2 * HD + 16) + 64 * n + assert peak <= cost * 4 + row_bytes + (64 << 10) + + @pytest.mark.skipif( get_sm_version() not in (100, 103), reason="DeepGEMM MQA logits require SM100/103" ) From d002501707a9d891286b8ac923f11db0de21036c Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Thu, 22 Oct 2026 02:16:53 -0700 Subject: [PATCH 11/13] [None][doc] Recommend EP1 for low-concurrency GLM-5.3-Flash serving With long prompts at concurrency 1 and 8, running the routed experts tensor-parallel (EP1) was faster than EP4 on 4x B200 FP8. Correct the low-M GEMM note, whose measured effect on the FP8 checkpoint is too small to recommend it. Signed-off-by: Ruocheng Jia --- .../deployment-guide-for-glm-5.3-flash-on-trtllm.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md b/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md index 38ffe3ba0620..55a758830088 100644 --- a/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md +++ b/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md @@ -94,7 +94,9 @@ If you want to use the latest main branch, you can build from source: [https://n Use these configurations as starting points for 4x B200 with FP8 weights and a BF16 KV cache. Tune the batch size and token budget for your workload. -For single-request latency, setting `TRTLLM_LOW_M_GEMM_BACKEND=auto` before launching the server routes BF16 projections with at most 32 rows to autotuned low-M GEMM kernels. With MTP on 4x B200 it lowered inter-token latency by about 2% at concurrency 1 and left concurrency 8 unchanged, but tuning those kernels adds about two minutes to server startup. +For low-concurrency serving of long prompts, run the routed experts tensor-parallel with `--ep_size 1` instead of `--ep_size 4`. With prompts of more than 10K tokens, short outputs, MTP (`max_draft_len: 3`) and an FP8 KV cache on 4x B200, EP1 raised per-user output speed by about 6% at concurrency 1 and output throughput by about 12% at concurrency 8, because it avoids the expert-parallel load imbalance in the MoE all-reduce. This applies to the tensor-parallel configurations; keep `--ep_size 4` with attention data parallelism, which targets high concurrency, where expert-parallel load evens out. The throughput chart in [Performance](#performance) was measured with EP4. + +Setting `TRTLLM_LOW_M_GEMM_BACKEND=auto` before launching the server routes BF16 projections with at most 32 rows to autotuned low-M GEMM kernels. For this checkpoint it is not recommended: in the workload above it raised per-user output speed at concurrency 1 by only about 1%, while adding about 30 ms to time to first token, lowering concurrency-8 throughput by about 5%, and adding several minutes of kernel tuning to server startup. #### B200 FP8 Config From d485384812e6996e11253df2a32844414c157cd6 Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Thu, 22 Oct 2026 22:08:39 -0700 Subject: [PATCH 12/13] [None][doc] Update the GLM-5.3-Flash performance chart to B300 long-prompt serving Replace the 4x B200 ISL 1024 / OSL 1024 chart with the Pareto frontier measured on 4x B300 at ISL 10240 / OSL 1024 (FP8 checkpoint, FP8 KV cache, MTP 3) over TP4/EP1, TP4/EP4 and attention DP4/EP4. Restate the EP1 and low-M GEMM notes with same-node B300 measurements: EP1 raises concurrency-1 speed by about 6% with concurrency-8 throughput unchanged, and low-M GEMM gains about 1% with time to first token unchanged. Signed-off-by: Ruocheng Jia --- ...yment-guide-for-glm-5.3-flash-on-trtllm.md | 8 ++++---- docs/source/media/glm_5_3_flash_fp8_perf.png | Bin 73606 -> 75040 bytes 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md b/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md index 55a758830088..6858a463632e 100644 --- a/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md +++ b/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md @@ -94,9 +94,9 @@ If you want to use the latest main branch, you can build from source: [https://n Use these configurations as starting points for 4x B200 with FP8 weights and a BF16 KV cache. Tune the batch size and token budget for your workload. -For low-concurrency serving of long prompts, run the routed experts tensor-parallel with `--ep_size 1` instead of `--ep_size 4`. With prompts of more than 10K tokens, short outputs, MTP (`max_draft_len: 3`) and an FP8 KV cache on 4x B200, EP1 raised per-user output speed by about 6% at concurrency 1 and output throughput by about 12% at concurrency 8, because it avoids the expert-parallel load imbalance in the MoE all-reduce. This applies to the tensor-parallel configurations; keep `--ep_size 4` with attention data parallelism, which targets high concurrency, where expert-parallel load evens out. The throughput chart in [Performance](#performance) was measured with EP4. +For low-concurrency serving of long prompts, run the routed experts tensor-parallel with `--ep_size 1` instead of `--ep_size 4`. With prompts of more than 10K tokens, short outputs, MTP (`max_draft_len: 3`) and an FP8 KV cache, EP1 raised per-user output speed at concurrency 1 by about 6% on 4x B300, with concurrency-8 throughput about the same, because it avoids the expert-parallel load imbalance in the MoE all-reduce. This applies to the tensor-parallel configurations; keep `--ep_size 4` with attention data parallelism, which targets high concurrency, where expert-parallel load evens out. The chart in [Performance](#performance) shows where each configuration is fastest. -Setting `TRTLLM_LOW_M_GEMM_BACKEND=auto` before launching the server routes BF16 projections with at most 32 rows to autotuned low-M GEMM kernels. For this checkpoint it is not recommended: in the workload above it raised per-user output speed at concurrency 1 by only about 1%, while adding about 30 ms to time to first token, lowering concurrency-8 throughput by about 5%, and adding several minutes of kernel tuning to server startup. +Setting `TRTLLM_LOW_M_GEMM_BACKEND=auto` before launching the server routes BF16 projections with at most 32 rows to autotuned low-M GEMM kernels. For this checkpoint the gain is small: in the workload above it raised per-user output speed at concurrency 1 by about 1%, with time to first token and concurrency-8 throughput unchanged, but kernel tuning adds a few minutes to server startup. #### B200 FP8 Config @@ -449,6 +449,6 @@ $$ ## Performance -The chart below shows the Pareto frontier of output-token throughput per GPU versus per-user output speed on 4x B200 with GLM-5.3-Flash FP8, a BF16 KV cache, and MTP enabled, at ISL 1024 / OSL 1024. The frontier is the efficient boundary of the measured TP4 / EP4 and attention DP4 / EP4 configurations with MTP: no other measured point is better on both axes. Other settings or workloads can give different results. Benchmarks were run using the `benchmark_serving` client above against TensorRT LLM 1.3.0rc29. +The chart below shows the Pareto frontier of output-token throughput per GPU versus per-user output speed on 4x B300 with GLM-5.3-Flash FP8, an FP8 KV cache, and MTP enabled (`max_draft_len: 3`), at ISL 10240 / OSL 1024. The frontier is the efficient boundary of the measured TP4 / EP1, TP4 / EP4 and attention DP4 / EP4 configurations: no other measured point is better on both axes. TP4 / EP1 forms it up to concurrency 32 and attention DP4 / EP4 from concurrency 64. Other settings or workloads can give different results. Benchmarks were run with the aiperf client against a TensorRT LLM development build that includes these changes. -![GLM-5.3-Flash FP8 Performance on 4x B200 with MTP](../media/glm_5_3_flash_fp8_perf.png) +![GLM-5.3-Flash FP8 Performance on 4x B300 with MTP](../media/glm_5_3_flash_fp8_perf.png) diff --git a/docs/source/media/glm_5_3_flash_fp8_perf.png b/docs/source/media/glm_5_3_flash_fp8_perf.png index a660575017db4a37d5a968bcf3e19f737d05517f..9f8fbbc830fae8bfb5e3508ad7cbfad38d73eb88 100644 GIT binary patch literal 75040 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 z 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 literal 73606 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 z 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 W-PMu{TaF|V&0I56ipgEFQ_Nq}56^c1 From 76ad49ecc2733bec9df8316d6dfd6a221ab5135b Mon Sep 17 00:00:00 2001 From: Ruocheng Jia Date: Mon, 26 Oct 2026 01:42:23 -0700 Subject: [PATCH 13/13] [None][doc] Say when the low-M GEMM option helps GLM-5.3-Flash The note read as a verdict on the size of the gain. Point at the condition instead: the flag only reaches decoding steps that run with few tokens in flight, so it is worth trying for latency-sensitive serving, and its kernel tuning suits long-running servers. Signed-off-by: Ruocheng Jia --- .../deployment-guide-for-glm-5.3-flash-on-trtllm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md b/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md index 6858a463632e..3631e7442f58 100644 --- a/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md +++ b/docs/source/deployment-guide/deployment-guide-for-glm-5.3-flash-on-trtllm.md @@ -96,7 +96,7 @@ Use these configurations as starting points for 4x B200 with FP8 weights and a B For low-concurrency serving of long prompts, run the routed experts tensor-parallel with `--ep_size 1` instead of `--ep_size 4`. With prompts of more than 10K tokens, short outputs, MTP (`max_draft_len: 3`) and an FP8 KV cache, EP1 raised per-user output speed at concurrency 1 by about 6% on 4x B300, with concurrency-8 throughput about the same, because it avoids the expert-parallel load imbalance in the MoE all-reduce. This applies to the tensor-parallel configurations; keep `--ep_size 4` with attention data parallelism, which targets high concurrency, where expert-parallel load evens out. The chart in [Performance](#performance) shows where each configuration is fastest. -Setting `TRTLLM_LOW_M_GEMM_BACKEND=auto` before launching the server routes BF16 projections with at most 32 rows to autotuned low-M GEMM kernels. For this checkpoint the gain is small: in the workload above it raised per-user output speed at concurrency 1 by about 1%, with time to first token and concurrency-8 throughput unchanged, but kernel tuning adds a few minutes to server startup. +For latency-sensitive serving, consider `TRTLLM_LOW_M_GEMM_BACKEND=auto` before launching the server. It routes BF16 projections with at most 32 rows to autotuned low-M GEMM kernels, so it is worth trying when decoding steps run with few tokens in flight. The kernel tuning it triggers adds minutes to every server start, so it suits long-running servers rather than ones that restart often. #### B200 FP8 Config