Skip to content
Open
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
8 changes: 8 additions & 0 deletions vllm/envs.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@
VLLM_USE_OINK_OPS: bool = False
VLLM_MXFP8_EMULATION_DEQUANT_AT_LOAD: bool = True
VLLM_ROCM_USE_AITER: bool = False
VLLM_ROCM_USE_AITER_CP_INDEXER: bool = False
VLLM_ROCM_USE_AITER_CP_INDEXER_STRIPE_SIZE: int = 512
VLLM_ROCM_USE_AITER_CUSTOM_AR: bool = True
VLLM_ROCM_USE_AITER_LINEAR: bool = True
VLLM_ROCM_USE_AITER_LINEAR_HIPBMM: bool = False
Expand Down Expand Up @@ -1321,6 +1323,12 @@ def _resolve_rust_cli_path() -> str | None:
["auto", "gluon", "asm"],
case_sensitive=False,
),
"VLLM_ROCM_USE_AITER_CP_INDEXER": lambda: (
os.getenv("VLLM_ROCM_USE_AITER_CP_INDEXER", "False").lower() in ("true", "1")
),
"VLLM_ROCM_USE_AITER_CP_INDEXER_STRIPE_SIZE": lambda: int(
os.getenv("VLLM_ROCM_USE_AITER_CP_INDEXER_STRIPE_SIZE", "512")
),
# Whether to use aiter mha ops.
# By default is enabled.
"VLLM_ROCM_USE_AITER_MHA": lambda: (
Expand Down
156 changes: 124 additions & 32 deletions vllm/v1/attention/ops/rocm_aiter_mla_sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import torch
import torch.nn.functional as F
import torch.distributed as dist

import vllm.envs as envs
from vllm.compilation.breakable_cudagraph import eager_break_during_capture
Expand All @@ -19,13 +20,21 @@
from vllm.v1.attention.backends.mla.indexer import DeepseekV32IndexerMetadata
from vllm.v1.attention.ops.common import pack_seq_triton, unpack_seq_triton
from vllm.v1.worker.workspace import current_workspace_manager
from vllm.distributed.parallel_state import get_tp_group

if current_platform.is_rocm():
from vllm.platforms.rocm import _ON_GFX942, _ON_GFX950
else:
_ON_GFX942 = False
_ON_GFX950 = False

# Stripe size for interleaved M-split. Each TP rank processes stripes of
# this many query rows, interleaved across ranks. Distributes causal-mask
# work evenly (each rank gets ~M²/2/tp total KV tokens to score), giving
# ~5x wall-clock speedup vs the baseline replicated Indexer on TP=8.
# Tuned on MI300X TP=8 M=32768: 512 is the crossover between work-balance
# gains and kernel-launch overhead.
_STRIPE_SIZE = envs.VLLM_ROCM_USE_AITER_CP_INDEXER_STRIPE_SIZE

@functools.cache
def _get_aiter_topk_ops() -> tuple[Callable[..., None], Callable[..., None]] | None:
Expand Down Expand Up @@ -928,6 +937,10 @@ def rocm_aiter_sparse_attn_indexer(
)

if has_prefill:
tp_group = get_tp_group()
tp_rank = tp_group.rank_in_group
tp_world_size = tp_group.world_size

prefill_metadata = layer_attn_metadata.prefill
assert prefill_metadata is not None

Expand All @@ -947,44 +960,123 @@ def rocm_aiter_sparse_attn_indexer(
chunk.cu_seq_lens,
token_to_seq=chunk.token_to_seq,
)
logits = rocm_fp8_mqa_logits(
q_fp8[chunk.token_start : chunk.token_end],
(k_fp8, k_scale.view(torch.float32)),
weights[chunk.token_start : chunk.token_end],
chunk.cu_seqlen_ks,
chunk.cu_seqlen_ke,
)
topk_indices = topk_indices_buffer[
chunk.token_start : chunk.token_end, :topk_tokens
]

num_rows = logits.shape[0]

aiter_topk_kernel = _get_aiter_top_k_kernel(
is_prefill=True,
compress_ratio=compress_ratio,
num_rows=num_rows,
)
if aiter_topk_kernel is not None:
_launch_aiter_top_k_per_row_prefill(
aiter_topk_kernel,
logits,
chunk_m = chunk.token_end - chunk.token_start
if envs.VLLM_ROCM_USE_AITER_CP_INDEXER and tp_world_size > 1 and chunk_m >= tp_world_size:
# Interleaved M-split: each rank takes stripes of size
# _STRIPE_SIZE across the full M range. Distributes causal
# work evenly (rank 0 gets cheap early rows, rank 7 gets
# expensive late rows, but each rank's TOTAL work ≈ M²/2/tp).
stripe = max(1, min(_STRIPE_SIZE, chunk_m // tp_world_size))
block = tp_world_size * stripe

for block_start in range(0, chunk_m, block):
local_off = block_start + tp_rank * stripe
if local_off >= chunk_m:
break
local_m = min(stripe, chunk_m - local_off)
local_start = chunk.token_start + local_off
local_end = local_start + local_m
logits = rocm_fp8_mqa_logits(
q_fp8[local_start:local_end],
(k_fp8, k_scale.view(torch.float32)),
weights[local_start:local_end],
chunk.cu_seqlen_ks[local_off:local_off + local_m],
chunk.cu_seqlen_ke[local_off:local_off + local_m],
)

num_rows = logits.shape[0]
topk_indices = topk_indices_buffer[
local_start:local_end, :topk_tokens
]
# A stripe can cover only a subset of rows from each
# request, so localized top-k cannot reconstruct the
# per-request logits windows safely here.
aiter_topk_kernel = _get_aiter_top_k_kernel(
is_prefill=True,
compress_ratio=compress_ratio,
num_rows=num_rows,
)
if aiter_topk_kernel is not None:
_launch_aiter_top_k_per_row_prefill(
aiter_topk_kernel,
logits,
chunk.cu_seqlen_ks[local_off:local_off + local_m],
chunk.cu_seqlen_ke[local_off:local_off + local_m],
topk_indices,
topk_tokens,
)
else:
torch.ops._C.top_k_per_row_prefill(
logits,
chunk.cu_seqlen_ks[local_off:local_off + local_m],
chunk.cu_seqlen_ke[local_off:local_off + local_m],
topk_indices,
num_rows,
logits.stride(0),
logits.stride(1),
topk_tokens,
)
else:
# Fallback: single rank or chunk too small to split
logits = rocm_fp8_mqa_logits(
q_fp8[chunk.token_start : chunk.token_end],
(k_fp8, k_scale.view(torch.float32)),
weights[chunk.token_start : chunk.token_end],
chunk.cu_seqlen_ks,
chunk.cu_seqlen_ke,
topk_indices,
topk_tokens,
)
topk_indices = topk_indices_buffer[
chunk.token_start : chunk.token_end, :topk_tokens
]

num_rows = logits.shape[0]

aiter_topk_kernel = _get_aiter_top_k_kernel(
is_prefill=True,
compress_ratio=compress_ratio,
num_rows=num_rows,
)
if aiter_topk_kernel is not None:
_launch_aiter_top_k_per_row_prefill(
aiter_topk_kernel,
logits,
chunk.cu_seqlen_ks,
chunk.cu_seqlen_ke,
topk_indices,
topk_tokens,
)
else:
torch.ops._C.top_k_per_row_prefill(
logits,
chunk.cu_seqlen_ks,
chunk.cu_seqlen_ke,
topk_indices,
num_rows,
logits.stride(0),
logits.stride(1),
topk_tokens,
)

# AllReduce(MAX): unwritten positions = -1, MAX recovers full result
if envs.VLLM_ROCM_USE_AITER_CP_INDEXER and tp_world_size > 1:
prefill_start = num_decode_tokens
prefill_end = hidden_states.shape[0]
buf_slice = topk_indices_buffer[
prefill_start:prefill_end, :topk_tokens
]
if buf_slice.is_contiguous():
dist.all_reduce(
buf_slice, op=dist.ReduceOp.MAX,
group=tp_group.device_group,
)
else:
torch.ops._C.top_k_per_row_prefill(
logits,
chunk.cu_seqlen_ks,
chunk.cu_seqlen_ke,
topk_indices,
num_rows,
logits.stride(0),
logits.stride(1),
topk_tokens,
tmp = buf_slice.contiguous()
dist.all_reduce(
tmp, op=dist.ReduceOp.MAX,
group=tp_group.device_group,
)
buf_slice.copy_(tmp)

if has_decode:
decode_metadata = layer_attn_metadata.decode
Expand Down