Attention¶
Compute paged-KV attention for the decode stage. |
|
Variable-length BF16 attention forward for prefill with GQA and optional causal/custom mask. |
|
Compute actual_cu_seq_k, seq_lens_kv, and return max/total token statistics for subsequent attention kernels. |
|
Process the current decode token fused QKV, generate Q output for decode attention, and append K/V to paged KV cache. |
|
Split fused QKV, apply RoPE and optional RMSNorm to Q/K, output contiguous Q/K/V, and simultaneously write K/V to paged KV cache. |
decoder_attention¶
- decoder_attention(
- q_input,
- cu_seq_q,
- seq_lens_kv,
- seq_len_encoder,
- seq_len_decoder,
- cache_k,
- cache_v,
- block_tables,
- attn_out,
- q_dequant_scale=None,
- kv_dequant_scale=None,
- head_num=0,
- kv_head_num=0,
- head_dim=128,
- max_seq_k=0,
- mtp_steps=0,
- cache_quant_type='none',
Compute paged-KV attention for the decode stage.
Reads the current-step query together with the paged K/V cache, attends over each sample’s visible KV range located through block_tables, and writes the attention result into attn_out. Supports grouped-query attention (GQA), multi-token prediction (MTP), and both a BF16 cache and a block-wise FP8 cache.
- Parameters:
q_input (Tensor) – Query, shape [batch, kv_head_num, 64, head_dim]. Dtype is bfloat16 when cache_quant_type is “none” and float8_e4m3fn when it is “block_wise_fp8”. The 64 rows hold the GQA-folded query heads padded to a fixed width.
cu_seq_q (Tensor) – Prefix-sum of decode query tokens per sample, shape [batch + 1], dtype int32.
seq_lens_kv (Tensor) – Total visible KV length per sample, shape [batch], dtype int32.
seq_len_encoder (Tensor) – Encoder/prefill length per sample, shape [batch], dtype int32.
seq_len_decoder (Tensor) – Decode prefix length per sample, shape [batch], dtype int32.
cache_k (Tensor) – Paged K cache, shape [num_blocks, kv_head_num, block_size, head_dim]. Dtype is bfloat16 for the “none” cache and uint8 (packed FP8) for the “block_wise_fp8” cache.
cache_v (Tensor) – Paged V cache, same shape and dtype as cache_k.
block_tables (Tensor) – Mapping from each sample to its physical cache blocks, shape [batch, max_blocks_per_seq], dtype int32.
attn_out (Tensor) – Output buffer written in place, shape [q_token_num, head_num * head_dim], dtype bfloat16.
q_dequant_scale (Tensor, optional) – Query dequantization scale used only by the FP8 path, shape [batch, kv_head_num], dtype float32. Ignored when cache_quant_type is “none”. Default: None.
kv_dequant_scale (Tensor, optional) – Block-wise K/V dequantization scale used only by the FP8 path, shape [num_blocks, kv_head_num, 4], dtype float32. Ignored when cache_quant_type is “none”. Default: None.
head_num (int) – Number of query heads.
kv_head_num (int) – Number of key/value heads.
head_dim (int) – Per-head dimension. Default: 128.
max_seq_k (int) – Maximum visible KV length across the current batch.
mtp_steps (int, optional) – Number of extra multi-token-prediction tokens; 0 means ordinary single-token decode. Default: 0.
cache_quant_type (str, optional) – KV cache quantization mode. Supported values are “none” (BF16 cache) and “block_wise_fp8” (block-wise FP8 cache). Default: “none”.
- Returns:
None. The attention result is written in place into attn_out.
Example
import torch from halops import ( write_cache_kv_encoder, write_cache_kv_decoder, decoder_attention, ) head_num, kv_head_num, head_dim = 14, 1, 128 batch, seq_len_enc, block_size = 1, 128, 128 max_seq_len = 4096 num_blocks = batch * max_seq_len // block_size total_kv = seq_len_enc + 1 # prefill tokens + 1 decode token # Shared paged KV cache and block table. cache_k = torch.zeros(num_blocks, kv_head_num, block_size, head_dim, dtype=torch.bfloat16, device="cuda") cache_v = torch.zeros_like(cache_k) block_tables = torch.arange( num_blocks, dtype=torch.int32, device="cuda" ).reshape(batch, max_seq_len // block_size) # Identity RoPE table (cos=1, sin=0) so Q/K pass through unchanged. rotary_embs = torch.zeros(2, max_seq_len, head_dim // 2, dtype=torch.float32, device="cuda") rotary_embs[0] = 1 # Step 1: prefill. Write the encoder Q/K/V and populate the KV cache. qkv_enc = torch.randn(batch * seq_len_enc, head_num + 2 * kv_head_num, head_dim, dtype=torch.bfloat16, device="cuda") cu_seq_enc = torch.tensor([0, seq_len_enc], dtype=torch.int32, device="cuda") seq_len_enc_t = torch.tensor([seq_len_enc], dtype=torch.int32, device="cuda") seq_len_dec_t = torch.zeros(batch, dtype=torch.int32, device="cuda") q_enc = torch.zeros(batch * seq_len_enc, head_num, head_dim, dtype=torch.bfloat16, device="cuda") k_enc = torch.zeros(batch * seq_len_enc, kv_head_num, head_dim, dtype=torch.bfloat16, device="cuda") v_enc = torch.zeros_like(k_enc) write_cache_kv_encoder( qkv_enc, cu_seq_enc, cu_seq_enc, rotary_embs, seq_len_enc_t, seq_len_dec_t, cache_k, cache_v, block_tables, q_enc, k_enc, v_enc, head_num=head_num, kv_head_num=kv_head_num, head_dim=head_dim, max_seq_len_q=seq_len_enc, max_seq_len_k=seq_len_enc, max_input_len=max_seq_len, cache_quant_type="none", ) # Step 2: decode. Write the current token's K/V and build the folded # q_output of shape [batch, kv_head_num, 64, head_dim]. qkv_dec = torch.randn(batch, head_num + 2 * kv_head_num, head_dim, dtype=torch.bfloat16, device="cuda") cu_seq_q = torch.tensor([0, 1], dtype=torch.int32, device="cuda") seq_len_enc_t2 = torch.zeros(batch, dtype=torch.int32, device="cuda") seq_len_dec_t2 = torch.tensor([seq_len_enc], dtype=torch.int32, device="cuda") q_output, _ = write_cache_kv_decoder( qkv_dec, cu_seq_q, seq_len_enc_t2, seq_len_dec_t2, rotary_embs, cache_k, cache_v, block_tables, head_num=head_num, kv_head_num=kv_head_num, head_dim=head_dim, max_input_len=max_seq_len, cache_quant_type="none", ) # Step 3: decode attention over the paged cache. seq_lens_kv = torch.tensor([total_kv], dtype=torch.int32, device="cuda") attn_out = torch.zeros(batch, head_num * head_dim, dtype=torch.bfloat16, device="cuda") decoder_attention( q_output, cu_seq_q, seq_lens_kv, seq_len_enc_t2, seq_len_dec_t2, cache_k, cache_v, block_tables, attn_out, head_num=head_num, kv_head_num=kv_head_num, head_dim=head_dim, max_seq_k=total_kv, cache_quant_type="none", )
encoder_attention¶
- encoder_attention(
- q_input,
- k_input,
- v_input,
- cu_seq_q,
- cu_seq_k,
- attn_out,
- mask=None,
Variable-length BF16 attention forward for prefill with GQA and optional causal/custom mask.
- Parameters:
q_input (torch.Tensor) – [total_q_tokens, head_num, head_dim] BF16.
k_input (torch.Tensor) – [total_k_tokens, kv_head_num, head_dim] BF16.
v_input (torch.Tensor) – [total_k_tokens, kv_head_num, head_dim] BF16.
cu_seq_q (torch.Tensor) – [batch_size+1] int32, Q token prefix-sum.
cu_seq_k (torch.Tensor) – [batch_size+1] int32, K token prefix-sum.
attn_out (torch.Tensor) – [total_q_tokens, head_num, head_dim] BF16, output tensor allocated by caller. Written in-place.
mask (torch.Tensor | None) – Optional int32 mask; when passed, uses CustomMask template branch. When
None, the kernel applies the default causal mask (bottom-right aligned) per sequence.
- Returns:
None. The result is written in-place to
attn_out.
Example
import torch from halops import encoder_attention head_num, kv_head_num, head_dim = 56, 4, 128 seq_len = 1024 cu_seq_q = torch.tensor([0, seq_len], dtype=torch.int32, device="cuda") cu_seq_k = torch.tensor([0, seq_len], dtype=torch.int32, device="cuda") q = torch.randn(seq_len, head_num, head_dim, dtype=torch.bfloat16, device="cuda") k = torch.randn(seq_len, kv_head_num, head_dim, dtype=torch.bfloat16, device="cuda") v = torch.randn(seq_len, kv_head_num, head_dim, dtype=torch.bfloat16, device="cuda") attn_out = torch.empty(seq_len, head_num, head_dim, dtype=torch.bfloat16, device="cuda") encoder_attention(q, k, v, cu_seq_q, cu_seq_k, attn_out)
get_qk_token¶
- get_qk_token(
- seq_lens_encoder,
- seq_lens_decoder,
- cu_seq_q,
- cu_seq_k,
- actual_cu_seq_k,
- seq_lens_kv,
- kv_head_num=0,
Compute actual_cu_seq_k, seq_lens_kv, and return max/total token statistics for subsequent attention kernels.
- Parameters:
seq_lens_encoder (torch.Tensor) – [batch_size] int32, encoder token count per sample.
seq_lens_decoder (torch.Tensor) – [batch_size] int32, existing decode prefix length per sample.
cu_seq_q (torch.Tensor) – [batch_size+1] int32, Q token prefix-sum.
cu_seq_k (torch.Tensor) – [batch_size+1] int32, reserved for interface compatibility.
actual_cu_seq_k (torch.Tensor) – [batch_size+1] int32 output, actual K/V prefix-sum.
seq_lens_kv (torch.Tensor) – [max_batch_size] int32 output, per-sample KV length for decode.
kv_head_num (int) – Number of KV heads.
- Returns:
A list containing a single CPU tensor with contents
[max_enc, max_dec, total_q, total_k].- Return type:
List[torch.Tensor]
Example
import torch from halops import get_qk_token kv_head_num = 1 batch, max_batch = 2, 8 seq_lens_encoder = torch.tensor( [128, 0], dtype=torch.int32, device="cuda" ) seq_lens_decoder = torch.tensor( [0, 512], dtype=torch.int32, device="cuda" ) cu_seq_q = torch.tensor([0, 128, 129], dtype=torch.int32, device="cuda") cu_seq_k = torch.zeros(batch + 1, dtype=torch.int32, device="cuda") actual_cu_seq_k = torch.zeros( batch + 1, dtype=torch.int32, device="cuda" ) seq_lens_kv = torch.zeros(max_batch, dtype=torch.int32, device="cuda") (max_token_num,) = get_qk_token( seq_lens_encoder, seq_lens_decoder, cu_seq_q, cu_seq_k, actual_cu_seq_k, seq_lens_kv, kv_head_num, ) max_token_num.tolist() # [max_enc, max_dec, total_q, total_k]
[128, 513, 129, 129]
write_cache_kv_decoder¶
- write_cache_kv_decoder(
- qkv_input,
- cu_seq_q,
- seq_len_encoder,
- seq_len_decoder,
- rotary_embs,
- cache_k,
- cache_v,
- block_tables,
- kv_dequant_scale=None,
- q_norm_gamma=None,
- k_norm_gamma=None,
- head_num=0,
- kv_head_num=0,
- head_dim=128,
- max_input_len=0,
- cache_quant_type='none',
Process the current decode token fused QKV, generate Q output for decode attention, and append K/V to paged KV cache.
- Parameters:
qkv_input (torch.Tensor) – [q_token_num, head_num + 2*kv_head_num, head_dim] BF16.
cu_seq_q (torch.Tensor) – [batch_size+1] int32, decode Q token prefix-sum.
seq_len_encoder (torch.Tensor) – [batch_size] int32; samples with >0 skip decoder-only cache write.
seq_len_decoder (torch.Tensor) – [batch_size] int32, existing decode prefix length before write.
rotary_embs (torch.Tensor) – RoPE cos/sin table.
cache_k (torch.Tensor) – paged K cache, BF16 or FP8/uint8.
cache_v (torch.Tensor) – paged V cache, BF16 or FP8/uint8.
block_tables (torch.Tensor) – [batch_size, max_blocks_per_seq] int32.
kv_dequant_scale (torch.Tensor | None) – Required for FP8 cache path, K/V block-wise dequant scale.
q_norm_gamma (torch.Tensor | None) – Optional Q RMSNorm gamma.
k_norm_gamma (torch.Tensor | None) – Optional K RMSNorm gamma.
head_num (int) – Number of Q heads.
kv_head_num (int) – Number of KV heads.
head_dim (int) – Per-head dimension (primarily targets 128).
max_input_len (int) – Maximum length of the RoPE table.
cache_quant_type (str) –
"none"for BF16 cache;"block_wise_fp8"for FP8 cache.
- Returns:
A list
[q_output, q_dequant_scale]where –q_output: [batch, kv_head_num, 64, head_dim], BF16 for BF16 path or float8_e4m3fn for FP8 path.q_dequant_scale: [batch, kv_head_num] float32. In the FP8 path it holds the Q dequantization scale. In the BF16 path it is still allocated (neverNone) but left uninitialized and must not be used.
- Return type:
List[torch.Tensor]
Example
import torch from halops import write_cache_kv_decoder head_num, kv_head_num, head_dim = 14, 1, 128 batch, prefix_len, block_size = 1, 128, 128 max_input_len = 4096 num_blocks = batch * max_input_len // block_size # one new token per sequence for the current decode step qkv = torch.randn(batch, head_num + 2 * kv_head_num, head_dim, dtype=torch.bfloat16, device="cuda") cu_seq_q = torch.tensor([0, 1], dtype=torch.int32, device="cuda") seq_len_encoder = torch.zeros(batch, dtype=torch.int32, device="cuda") seq_len_decoder = torch.tensor( [prefix_len], dtype=torch.int32, device="cuda" ) rotary_embs = torch.zeros(2, max_input_len, head_dim // 2, dtype=torch.float32, device="cuda") rotary_embs[0] = 1 # identity RoPE cache_k = torch.zeros(num_blocks, kv_head_num, block_size, head_dim, dtype=torch.bfloat16, device="cuda") cache_v = torch.zeros_like(cache_k) block_tables = torch.arange( num_blocks, dtype=torch.int32, device="cuda" ).reshape(batch, max_input_len // block_size) q_output, q_dequant_scale = write_cache_kv_decoder( qkv, cu_seq_q, seq_len_encoder, seq_len_decoder, rotary_embs, cache_k, cache_v, block_tables, head_num=head_num, kv_head_num=kv_head_num, head_dim=head_dim, max_input_len=max_input_len, cache_quant_type="none", ) q_output.shape
torch.Size([1, 1, 64, 128])
write_cache_kv_encoder¶
- write_cache_kv_encoder(
- qkv_input,
- cu_seq_q,
- cu_seq_k,
- rotary_embs,
- seq_len_encoder,
- seq_len_decoder,
- cache_k,
- cache_v,
- block_tables,
- q_input,
- k_input,
- v_input,
- kv_dequant_scale=None,
- q_norm_gamma=None,
- k_norm_gamma=None,
- head_num=0,
- kv_head_num=0,
- head_dim=128,
- max_seq_len_q=0,
- max_seq_len_k=0,
- max_input_len=0,
- cache_quant_type='none',
Split fused QKV, apply RoPE and optional RMSNorm to Q/K, output contiguous Q/K/V, and simultaneously write K/V to paged KV cache.
Supports BF16 cache and block-wise FP8 cache.
- Parameters:
qkv_input (torch.Tensor) – [total_q_tokens, head_num + 2*kv_head_num, head_dim] BF16.
cu_seq_q (torch.Tensor) – [batch_size+1] int32, Q token prefix-sum.
cu_seq_k (torch.Tensor) – [batch_size+1] int32, K/V token prefix-sum.
rotary_embs (torch.Tensor) – RoPE cos/sin table, first half is cos, second half is sin.
seq_len_encoder (torch.Tensor) – [batch_size] int32, encoder/prefill token count.
seq_len_decoder (torch.Tensor) – [batch_size] int32, existing decode prefix length.
cache_k (torch.Tensor) – paged K cache, BF16 or FP8/uint8.
cache_v (torch.Tensor) – paged V cache, BF16 or FP8/uint8.
block_tables (torch.Tensor) – [batch_size, max_blocks_per_seq] int32.
q_input (torch.Tensor) – [total_q_tokens, head_num, head_dim] BF16 output, Q after RoPE.
k_input (torch.Tensor) – [total_k_tokens, kv_head_num, head_dim] BF16 output.
v_input (torch.Tensor) – [total_k_tokens, kv_head_num, head_dim] BF16 output.
kv_dequant_scale (torch.Tensor | None) – Required for FP8 cache path, K/V block-wise dequant scale.
q_norm_gamma (torch.Tensor | None) – Optional Q RMSNorm gamma.
k_norm_gamma (torch.Tensor | None) – Optional K RMSNorm gamma.
head_num (int) – Number of Q heads.
kv_head_num (int) – Number of KV heads.
head_dim (int) – Per-head dimension (primarily targets 128).
max_seq_len_q (int) – Max Q length in current batch.
max_seq_len_k (int) – Max K length in current batch.
max_input_len (int) – Max RoPE table length.
cache_quant_type (str) – “none” for BF16 cache; “block_wise_fp8” for FP8 cache.
- Returns:
None. Writes the RoPE-applied Q/K/V into q_input/k_input/v_input and appends K/V into the paged cache_k/cache_v in-place.
Example
import torch from halops import write_cache_kv_encoder head_num, kv_head_num, head_dim = 14, 1, 128 batch, seq_len, block_size = 1, 128, 128 max_seq_len = 4096 num_blocks = batch * max_seq_len // block_size total_q = batch * seq_len qkv = torch.randn(total_q, head_num + 2 * kv_head_num, head_dim, dtype=torch.bfloat16, device="cuda") cu_seq_q = torch.tensor([0, seq_len], dtype=torch.int32, device="cuda") cu_seq_k = cu_seq_q.clone() # identity RoPE table: cos=1, sin=0. The BF16 encoder applies # partial NeoX RoPE with rope_dim = head_dim // 4. rotary_embs = torch.zeros(2, max_seq_len, head_dim // 4, dtype=torch.float32, device="cuda") rotary_embs[0] = 1 seq_len_encoder = torch.tensor([seq_len], dtype=torch.int32, device="cuda") seq_len_decoder = torch.zeros(batch, dtype=torch.int32, device="cuda") cache_k = torch.zeros(num_blocks, kv_head_num, block_size, head_dim, dtype=torch.bfloat16, device="cuda") cache_v = torch.zeros_like(cache_k) block_tables = torch.arange( num_blocks, dtype=torch.int32, device="cuda" ).reshape(batch, max_seq_len // block_size) q_out = torch.zeros(total_q, head_num, head_dim, dtype=torch.bfloat16, device="cuda") k_out = torch.zeros(total_q, kv_head_num, head_dim, dtype=torch.bfloat16, device="cuda") v_out = torch.zeros_like(k_out) write_cache_kv_encoder( qkv, cu_seq_q, cu_seq_k, rotary_embs, seq_len_encoder, seq_len_decoder, cache_k, cache_v, block_tables, q_out, k_out, v_out, head_num=head_num, kv_head_num=kv_head_num, head_dim=head_dim, max_seq_len_q=seq_len, max_seq_len_k=seq_len, max_input_len=max_seq_len, cache_quant_type="none", )