Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/blossom-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ jobs:
"JunyiXu-nv",
"JyChang012",
"kaiyux",
"Kambili",
"kanghui0204",
"karljang",
"karthikvetrivel",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1301,8 +1301,13 @@ def copy_batch_block_offsets(
beam_width: int,
num_contexts: int,
num_seqs: int,
max_blocks: Optional[int] = None,
) -> None:
"""For compatibility with AttentionOp, copy only the SWA block offsets."""
"""For compatibility with AttentionOp, copy only the SWA block offsets.

max_blocks is accepted for signature parity with KVCacheManager; the
copy below is already bounded by the precomputed SWA table width.
"""
assert beam_width == 1, "DSV4 only supports beam width 1 now"
assert dst_tensor.is_cuda, "copy_batch_block_offsets expects a CUDA destination"
dst_tensor.fill_(BAD_PAGE_INDEX)
Expand Down
44 changes: 34 additions & 10 deletions tensorrt_llm/_torch/attention_backend/trtllm.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from tensorrt_llm._utils import get_sm_version, maybe_pin_memory, prefer_pinned
from tensorrt_llm.bindings.internal import thop
from tensorrt_llm.functional import AttentionMaskType
from tensorrt_llm.math_utils import ceil_div
from tensorrt_llm.models.modeling_utils import QuantConfig

from ..utils import (compute_swizzled_sf_shape, get_global_attrs,
Expand Down Expand Up @@ -586,24 +587,47 @@ def prepare(self) -> None:
# kv block offsets
assert self.request_ids is not None
if self.kv_cache_manager is not None:
self.kv_cache_manager.copy_batch_block_offsets(
self.kv_cache_block_offsets, self.request_ids, self.beam_width,
self.num_contexts, self.num_seqs)

error_message = (
f"The max KV cache length of input sequences ({self.kv_lens[:self.num_seqs].max()}) "
max_kv_len = int(self.kv_lens[:self.num_seqs].max())
assert max_kv_len <= self.kv_cache_manager.max_seq_len, (
f"The max KV cache length of input sequences ({max_kv_len}) "
f"exceeds the KV cache manager's maximum supported length "
f"({self.kv_cache_manager.max_seq_len}).")

assert self.kv_lens[:self.num_seqs].max(
) <= self.kv_cache_manager.max_seq_len, error_message
# On the non-speculative path the host kv_lens snapshot bounds
# every block-table access, so the staged/H2D width can be capped
# at the batch's maximum instead of max_seq_len's worth of
# columns. Speculative decoding must stage the full width:
# draft/tree sub-steps and the overlap scheduler advance
# kv_lens_cuda on device past the host snapshot, and their
# kernels dereference block columns a host-derived cap would
# leave unstaged (uninitialized in this buffer).
spec_active = (self.draft_kv_cache_manager is not None
or self.is_spec_decoding_enabled
or bool(self.kv_cache_params.num_extra_kv_tokens) or
(self.runtime_features is not None and
self.runtime_features.has_speculative_draft_tokens))
max_blocks = None
if not spec_active and self.kv_cache_manager.tokens_per_block:
max_blocks = ceil_div(max_kv_len,
self.kv_cache_manager.tokens_per_block)
self.kv_cache_manager.copy_batch_block_offsets(
self.kv_cache_block_offsets,
self.request_ids,
self.beam_width,
self.num_contexts,
self.num_seqs,
max_blocks=max_blocks)

# Also prepare draft KV cache block offsets if draft_kv_cache_manager exists
if self.draft_kv_cache_manager is not None:
# Use the wrapper method which works for both V1 and V2
self.draft_kv_cache_manager.copy_batch_block_offsets(
self.draft_kv_cache_block_offsets, self.request_ids,
self.beam_width, self.num_contexts, self.num_seqs)
self.draft_kv_cache_block_offsets,
self.request_ids,
self.beam_width,
self.num_contexts,
self.num_seqs,
max_blocks=max_blocks)

# Don't pass self.kv_lens as kv_lens here because it includes extra
# tokens. Use the actual KV length (without extra tokens) for
Expand Down
24 changes: 16 additions & 8 deletions tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -7241,10 +7241,14 @@ def forward(
SPLIT_KV = compute_block_kv * 2 # NUM_MATH_WG = 2
aligned_max_ctx = (
(max_context_len + SPLIT_KV - 1) // SPLIT_KV) * SPLIT_KV
logits = torch.empty(
(B * next_n, aligned_max_ctx),
device=q.device,
dtype=output_dtype,
# Use a persistent arena buffer instead of a per-forward torch.empty
# so the output address stays stable across CUDA-graph replays.
_reserve = torch.cuda.is_current_stream_capturing()
logits = get_memory_buffers().get_buffer(
[B * next_n, aligned_max_ctx],
output_dtype,
buffer_name="cute_dsl_mqa_logits",
reserve_buffer=_reserve,
)
logits = logits[:, :max_context_len]

Expand Down Expand Up @@ -8089,10 +8093,14 @@ def forward(
SPLIT_KV = compute_block_kv * 2 # NUM_MATH_WG = 2
aligned_max_ctx = (
(max_context_len + SPLIT_KV - 1) // SPLIT_KV) * SPLIT_KV
logits = torch.empty(
(B * next_n, aligned_max_ctx),
device=q.device,
dtype=output_dtype,
# Use a persistent arena buffer instead of a per-forward torch.empty
# so the output address stays stable across CUDA-graph replays.
_reserve = torch.cuda.is_current_stream_capturing()
logits = get_memory_buffers().get_buffer(
[B * next_n, aligned_max_ctx],
output_dtype,
buffer_name="cute_dsl_mqa_logits",
reserve_buffer=_reserve,
)
logits = logits[:, :max_context_len]

Expand Down
5 changes: 4 additions & 1 deletion tensorrt_llm/_torch/models/modeling_gpt_oss.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ def __init__(
beta_fast=pretrained_config.rope_scaling['beta_fast'],
beta_slow=pretrained_config.rope_scaling['beta_slow'],
duplicate_data=False),
is_neox=False,
# GPT-OSS applies NeoX-style (rotate-half) RoPE, matching the HF
# reference. The fused kernel ignores this flag for yarn (which
# masked the wrong value); the unfused path honors it.
is_neox=True,
)

super().__init__(
Expand Down
11 changes: 8 additions & 3 deletions tensorrt_llm/_torch/modules/mamba/mamba2_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,9 +369,14 @@ def prepare(self, attn_metadata: AttentionMetadata):
self.state_indices_cpu[:batch_size], non_blocking=True)
else:
# indices is a Python sequence (e.g. List[int]); data
# already lives on host, CPU staging is fine.
for i, idx in enumerate(indices):
self.state_indices_cpu[i] = idx
# already lives on host, CPU staging is fine. One bulk
# conversion instead of a per-element tensor write.
assert len(indices) == batch_size, (
f"get_state_indices() returned {len(indices)} entries for "
f"a batch of {batch_size} requests.")
self.state_indices_cpu[:batch_size].copy_(
torch.as_tensor(indices,
dtype=self.state_indices_cpu.dtype))
self.state_indices[:batch_size].copy_(
self.state_indices_cpu[:batch_size], non_blocking=True)

Expand Down
3 changes: 3 additions & 0 deletions tensorrt_llm/_torch/pyexecutor/kv_cache_manager_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3194,7 +3194,10 @@ def copy_batch_block_offsets(
beam_width: int,
num_contexts: int,
num_seqs: int,
max_blocks: Optional[int] = None,
):
# max_blocks is accepted for signature parity with KVCacheManager; the
# device-side copy op here already scales with allocated blocks only.
assert beam_width == 1, "beam_width must be 1 for KVCacheManagerV2"

copy_idx = self.index_mapper.get_copy_index(request_ids, num_contexts, beam_width)
Expand Down
13 changes: 8 additions & 5 deletions tensorrt_llm/_torch/pyexecutor/mamba_cache_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1984,15 +1984,18 @@ def _setup_state_indices(self, requests=None) -> None:

self.cuda_state_indices.copy_(self._host_state_indices,
non_blocking=True)
is_dummy = [req.is_dummy for req in requests]
self._refresh_dummy_request_mask(
[req.is_dummy for req in self.requests])
is_dummy if requests is
self.requests else [req.is_dummy for req in self.requests])

# Build request_id → pool block offset mapping so that
# get_state_indices can return indices in arbitrary request order.
for i, req in enumerate(requests):
self._request_id_to_state_index[
req.py_request_id] = self._host_state_indices[i].item()
self._request_id_to_is_dummy[req.py_request_id] = req.is_dummy
# Bulk tolist avoids a per-request tensor-index + .item() round-trip.
state_values = self._host_state_indices[:n].tolist()
for req, value, dummy in zip(requests, state_values, is_dummy):
self._request_id_to_state_index[req.py_request_id] = value
self._request_id_to_is_dummy[req.py_request_id] = dummy

def get_state_indices(self,
request_ids: Optional[List[int]] = None,
Expand Down
Loading
Loading