|
2 | 2 | # SPDX-License-Identifier: Apache-2.0 |
3 | 3 | """Dense Sparse Attention (DSA) backend for TRT-LLM with indexer-based TopK selection.""" |
4 | 4 | import math |
| 5 | +import os |
5 | 6 | import threading |
6 | 7 | from contextlib import contextmanager |
7 | 8 | from dataclasses import dataclass |
|
43 | 44 |
|
44 | 45 | ModelConfig = tensorrt_llm.bindings.ModelConfig |
45 | 46 |
|
| 47 | +# Cap the per-call indexer MQA-logits transient (in elements). fp8_mqa_logits |
| 48 | +# allocates its [q x kv] logits output via torch.empty; the KV dimension is the |
| 49 | +# full (compressed) context and is unbounded, so for a large query chunk on a |
| 50 | +# long-context prefill this single allocation can reach tens of GB. Under |
| 51 | +# PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True such an allocation can stall |
| 52 | +# indefinitely in cuMemCreate on the longest-context (attention_dp laggard) rank |
| 53 | +# -> GPU idle -> peers block at the next MoE all-to-all -> watchdog hang. Tiling |
| 54 | +# the query dimension caps the transient to q_tile x kv with identical results |
| 55 | +# (each query row's logits/top-k are independent). Override via env if needed. |
| 56 | +_INDEXER_MQA_LOGITS_ELEM_BUDGET = int( |
| 57 | + os.environ.get("TLLM_INDEXER_MQA_LOGITS_ELEM_BUDGET", 1 << 31)) |
| 58 | + |
46 | 59 | if TYPE_CHECKING: |
47 | 60 | from tensorrt_llm._torch.speculative.interface import SpecMetadata |
48 | 61 | from tensorrt_llm._torch.speculative.spec_tree_manager import \ |
@@ -2218,44 +2231,61 @@ def sparse_attn_indexer( |
2218 | 2231 | global_q_start = chunk.token_start + chunk_q_start |
2219 | 2232 | global_q_end = chunk.token_start + chunk_q_end |
2220 | 2233 |
|
2221 | | - chunk_q_scale = q_scale[global_q_start:global_q_end, |
2222 | | - ...] if self.use_fp4 else None |
2223 | | - logits = self._call_mqa_logits( |
2224 | | - q_fp8[global_q_start:global_q_end, ...], |
2225 | | - chunk_k_fp8, |
2226 | | - chunk_k_scale, |
2227 | | - weights[global_q_start:global_q_end, ...], |
2228 | | - chunk.cu_seqlen_ks[chunk_q_start:chunk_q_end], |
2229 | | - chunk.cu_seqlen_ke[chunk_q_start:chunk_q_end], |
2230 | | - chunk_q_scale, |
2231 | | - ) |
2232 | | - if use_custom_topk: |
2233 | | - torch.ops.trtllm.indexer_topk_prefill( |
2234 | | - logits, |
2235 | | - chunk.cu_seqlen_ks[chunk_q_start:chunk_q_end], |
2236 | | - chunk.cu_seqlen_ke[chunk_q_start:chunk_q_end], |
2237 | | - topk_indices_buffer[global_q_start:global_q_end, :], |
2238 | | - self.index_topk) |
2239 | | - else: |
2240 | | - topk_indices = logits.topk(min(self.index_topk, |
2241 | | - logits.shape[-1]), |
2242 | | - dim=-1)[1] |
2243 | | - topk_indices -= chunk.cu_seqlen_ks[ |
2244 | | - chunk_q_start:chunk_q_end][:, None] |
2245 | | - |
2246 | | - mask_lo = topk_indices >= 0 |
2247 | | - mask_hi = topk_indices - ( |
2248 | | - chunk.cu_seqlen_ke[chunk_q_start:chunk_q_end] - |
2249 | | - chunk.cu_seqlen_ks[chunk_q_start:chunk_q_end] |
2250 | | - )[:, None] < 0 |
2251 | | - mask = mask_lo & mask_hi |
2252 | | - |
2253 | | - # local indices per sequence |
2254 | | - topk_indices = topk_indices.masked_fill(~mask, -1) |
2255 | | - |
2256 | | - topk_indices_buffer[ |
2257 | | - global_q_start:global_q_end, :topk_indices. |
2258 | | - shape[-1]] = topk_indices.to(dtype=torch.int32) |
| 2234 | + # Tile the query dimension so each fp8_mqa_logits call |
| 2235 | + # allocates at most [q_tile x num_k_tokens] instead of the |
| 2236 | + # full [local_q x num_k_tokens] (which can reach tens of GB |
| 2237 | + # on a long context and stall cuMemCreate under |
| 2238 | + # expandable_segments -> engine hang; see |
| 2239 | + # _INDEXER_MQA_LOGITS_ELEM_BUDGET). Results are identical: |
| 2240 | + # each query row's logits/top-k are independent and the KV |
| 2241 | + # (chunk_k_fp8) is unchanged across tiles, so the per-call |
| 2242 | + # allocation is the same size and the caching allocator |
| 2243 | + # reuses one block (peak ~= one tile, no extra sync). |
| 2244 | + local_q_len = chunk_q_end - chunk_q_start |
| 2245 | + q_tile = max( |
| 2246 | + 1, |
| 2247 | + min( |
| 2248 | + local_q_len, _INDEXER_MQA_LOGITS_ELEM_BUDGET // |
| 2249 | + max(1, num_k_tokens))) |
| 2250 | + for tile_off in range(0, local_q_len, q_tile): |
| 2251 | + c0 = chunk_q_start + tile_off |
| 2252 | + c1 = min(c0 + q_tile, chunk_q_end) |
| 2253 | + g0 = chunk.token_start + c0 |
| 2254 | + g1 = chunk.token_start + c1 |
| 2255 | + tile_q_scale = q_scale[g0:g1, |
| 2256 | + ...] if self.use_fp4 else None |
| 2257 | + logits = self._call_mqa_logits( |
| 2258 | + q_fp8[g0:g1, ...], |
| 2259 | + chunk_k_fp8, |
| 2260 | + chunk_k_scale, |
| 2261 | + weights[g0:g1, ...], |
| 2262 | + chunk.cu_seqlen_ks[c0:c1], |
| 2263 | + chunk.cu_seqlen_ke[c0:c1], |
| 2264 | + tile_q_scale, |
| 2265 | + ) |
| 2266 | + if use_custom_topk: |
| 2267 | + torch.ops.trtllm.indexer_topk_prefill( |
| 2268 | + logits, chunk.cu_seqlen_ks[c0:c1], |
| 2269 | + chunk.cu_seqlen_ke[c0:c1], |
| 2270 | + topk_indices_buffer[g0:g1, :], self.index_topk) |
| 2271 | + else: |
| 2272 | + topk_indices = logits.topk(min( |
| 2273 | + self.index_topk, logits.shape[-1]), |
| 2274 | + dim=-1)[1] |
| 2275 | + topk_indices -= chunk.cu_seqlen_ks[c0:c1][:, None] |
| 2276 | + |
| 2277 | + mask_lo = topk_indices >= 0 |
| 2278 | + mask_hi = topk_indices - ( |
| 2279 | + chunk.cu_seqlen_ke[c0:c1] - |
| 2280 | + chunk.cu_seqlen_ks[c0:c1])[:, None] < 0 |
| 2281 | + mask = mask_lo & mask_hi |
| 2282 | + |
| 2283 | + # local indices per sequence |
| 2284 | + topk_indices = topk_indices.masked_fill(~mask, -1) |
| 2285 | + |
| 2286 | + topk_indices_buffer[ |
| 2287 | + g0:g1, :topk_indices.shape[-1]] = \ |
| 2288 | + topk_indices.to(dtype=torch.int32) |
2259 | 2289 |
|
2260 | 2290 | if apply_q_split: |
2261 | 2291 | q_sizes = [(r + 1) * chunk_num_token // tp_size - |
|
0 commit comments