vllm.v1.attention.ops.shardq
¶
ShardQ: Ulysses-style sequence sharding of sparse-MLA prefill under TP.
TP shards Q by heads, so every rank runs the indexer for all tokens and the
sparse-MLA kernel pads its few local heads. ShardQ gives each TP rank a slice of
the query tokens instead: the indexer scores only the rank's tokens, an
all-to-all moves Q from [T, H / tp, D] to [T_rank, H, D], attention runs with
all heads over the replicated KV cache, and a second all-to-all restores
[T, H / tp, D_v]. Tokens split zigzag: each request cuts into 2 * tp
chunks and rank r owns chunks r and 2 * tp - 1 - r, which balances
the causal work.
Both exchanges push tokens straight into peers' symmetric-memory buffers with one Triton kernel, or fall back to NCCL when the TP group has no symmetric memory.
Classes:
-
ShardQMetadata–This step's ShardQ: which TP rank attends which of the tokens after
-
ShardQSplit–Which TP rank attends which of the tokens after
num_decode_tokens -
ShardQWorkspace–The exchange buffers in symmetric memory, with one slot per ubatch.
Functions:
-
allocate_shardq_buffers–Allocate the worst-case exchange buffers, if the TP group has symmetric
-
build_shardq_split–This step's
zigzag_splitover the TP group of the tokens after the -
shard_q–[T, H, D] head shards -> the decode tokens, still head-sharded, and
-
unshard_out–This rank's token shard of the attention output [T_rank, world * H, D]
-
zigzag_split–Token ids grouped by owning rank (ascending within a rank), and each
ShardQMetadata
dataclass
¶
This step's ShardQ: which TP rank attends which of the tokens after
num_decode_tokens head-sharded decode tokens (ids relative to the first
of them), and the forward_mqa metadata of this rank's tokens.
Attributes:
-
local_token_ids(Tensor) –This rank's tokens, ascending.
Source code in vllm/v1/attention/ops/shardq.py
local_token_ids
property
¶
This rank's tokens, ascending.
ShardQSplit
¶
Bases: NamedTuple
Which TP rank attends which of the tokens after num_decode_tokens
head-sharded decode tokens (ids relative to the first of them).
Attributes:
-
local_token_ids(ndarray) –This TP rank's tokens, ascending.
Source code in vllm/v1/attention/ops/shardq.py
local_token_ids
property
¶
This TP rank's tokens, ascending.
ShardQWorkspace
¶
Bases: DirectCPWorkspace
The exchange buffers in symmetric memory, with one slot per ubatch.
Source code in vllm/v1/attention/ops/shardq.py
_shard_q_kernel(q, q_stride, token_ids, rank_offsets, peer_ptrs, dst_stride, dst_col, width, BLOCK)
¶
Push the q of token token_ids[rank_offsets[peer] + i] to token
i of rank peer, at column dst_col.
Source code in vllm/v1/attention/ops/shardq.py
_unshard_out_kernel(out, out_stride, local_token_ids, peer_ptrs, num_decode_tokens, world, width, BLOCK)
¶
Push head block j of local token p to rank j, at the
token's index after the num_decode_tokens decode tokens.
Source code in vllm/v1/attention/ops/shardq.py
allocate_shardq_buffers(vllm_config, num_heads, query_head_dim, output_head_dim, query_dtype, output_dtype)
¶
Allocate the worst-case exchange buffers, if the TP group has symmetric
memory. num_heads are a TP rank's.
Source code in vllm/v1/attention/ops/shardq.py
build_shardq_split(common_attn_metadata, vllm_config)
¶
This step's zigzag_split over the TP group of the tokens after the
decodes (requests of at most 1 + num_speculative_tokens tokens). None when
too few tokens follow the decodes, none of their requests is longer than
the top-k, or a rank would own no tokens.
Source code in vllm/v1/attention/ops/shardq.py
shard_q(q, metadata)
¶
[T, H, D] head shards -> the decode tokens, still head-sharded, and this rank's token shard with every rank's heads, [T_rank, world * H, D].
Source code in vllm/v1/attention/ops/shardq.py
unshard_out(out, metadata, decode_out)
¶
This rank's token shard of the attention output [T_rank, world * H, D]
-> [T, H, D] head shards, after the head-sharded decode_out.
Source code in vllm/v1/attention/ops/shardq.py
zigzag_split(query_lens, world)
¶
Token ids grouped by owning rank (ascending within a rank), and each rank's token count.
world=4 splits each request into eight chunks, chunk i spanning
[floor(i * len / 8), floor((i + 1) * len / 8)), and each rank gets an
early and a late chunk:
full: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
rank 0: 0 7
rank 1: 1 6
rank 2: 2 5
rank 3: 3 4