Skip to content

Commit f5fca22

Browse files
committed
batched rpa kernel
1 parent bff4616 commit f5fca22

11 files changed

Lines changed: 204 additions & 21 deletions

File tree

src/maxtext/configs/pyconfig_deprecated.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def validate_attention_kernel(s: str) -> None:
106106
"cudnn_flash_te",
107107
"cudnn_flash_jax",
108108
"vllm_rpa",
109+
"vllm_batched_rpa",
109110
)
110111
if s not in valid_attention_kernels: # currently supported attention
111112
raise ValueError("Invalid attention kernel was passed. Valid options ", valid_attention_kernels)

src/maxtext/configs/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ class Attention(BaseModel):
561561

562562
attention: str = Field(
563563
"autoselected",
564-
description="The attention algorithm to use (dot_product, flash, etc).",
564+
description="The attention algorithm to use (dot_product, flash, cudnn_flash_te, vllm_rpa, vllm_batched_rpa, etc).",
565565
)
566566
attention_type: Literal["global", "local_sliding", "chunk", "mla", "full", "compressed"] = Field(
567567
"global", description="The variant of attention to use."

src/maxtext/integration/vllm/maxtext_vllm_adapter/adapter.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ def generate_maxtext_config(vllm_config: VllmConfig) -> pyconfig.HyperParameters
9090
)
9191
overrides["load_parameters_path"] = None
9292

93+
if overrides.get("attention") == "vllm_batched_rpa" or overrides.get("use_batched_rpa", False):
94+
os.environ["USE_BATCHED_RPA_KERNEL"] = "1"
95+
9396
# Add base config path to positional args
9497
base_config_path = os.path.join(MAXTEXT_CONFIGS_DIR, "inference", "vllm.yml")
9598
argv_list = ["", str(base_config_path)]

src/maxtext/layers/attention_op.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,14 @@ def __init__(
481481
self.kv_quant = kv_quant
482482
self.attention_type = attention_type
483483
# Block sizes are only used by TPU splash attention kernels. Exclude non-splash kernels
484-
if self.attention_kernel not in ("dot_product", "paged", "vllm_rpa", "cudnn_flash_te", "cudnn_flash_jax"):
484+
if self.attention_kernel not in (
485+
"dot_product",
486+
"paged",
487+
"vllm_rpa",
488+
"vllm_batched_rpa",
489+
"cudnn_flash_te",
490+
"cudnn_flash_jax",
491+
):
485492
if self.attention_type == AttentionType.LOCAL_SLIDING:
486493
self.block_q = self.config.local_sa_block_q
487494
self.block_kv = self.config.local_sa_block_kv
@@ -1069,7 +1076,7 @@ def apply_attention(
10691076
or (self.attention_kernel == "autoselected" and model_mode == MODEL_MODE_AUTOREGRESSIVE)
10701077
or (self.attention_kernel == "autoselected" and length < 128)
10711078
or (self.attention_kernel == "paged")
1072-
or (self.attention_kernel == "vllm_rpa")
1079+
or (self.attention_kernel in ("vllm_rpa", "vllm_batched_rpa"))
10731080
):
10741081
return self.apply_attention_dot(
10751082
query,

src/maxtext/layers/attentions.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import dataclasses
1818
import functools
19+
import os
1920
from typing import Any, Iterable, Optional, Tuple, Union, cast
2021

2122
from jax.ad_checkpoint import checkpoint_name
@@ -436,7 +437,9 @@ def __init__(
436437
# Module attribute names must match names previously passed to Linen for checkpointing
437438
self.KVCache_0 = (
438439
self.init_kv_caches(inputs_kv_shape=inputs_kv_shape)
439-
if self.model_mode != MODEL_MODE_TRAIN and base_kv_cache and config.attention != "vllm_rpa"
440+
if self.model_mode != MODEL_MODE_TRAIN
441+
and base_kv_cache
442+
and config.attention not in ("vllm_rpa", "vllm_batched_rpa")
440443
else None
441444
)
442445

@@ -1014,6 +1017,8 @@ def forward_serve_vllm(
10141017
rpa_metadata: dict[str, Any] | None = None,
10151018
) -> tuple[Array, list[Array]]:
10161019
"""Forward function for vLLM serving with RPA attention."""
1020+
if self.config.attention == "vllm_batched_rpa":
1021+
os.environ["USE_BATCHED_RPA_KERNEL"] = "1"
10171022
try:
10181023
# pylint: disable=import-outside-toplevel
10191024
# pytype: disable=import-error
@@ -1214,7 +1219,7 @@ def __call__(
12141219

12151220
assert not self.config.quantize_kvcache or self.kv_quant
12161221

1217-
if self.config.attention == "vllm_rpa" and model_mode != MODEL_MODE_TRAIN:
1222+
if self.config.attention in ("vllm_rpa", "vllm_batched_rpa") and model_mode != MODEL_MODE_TRAIN:
12181223
batch, seq_len, num_heads, head_dim = query.shape
12191224
attn_out, updated_kv = self.forward_serve_vllm(
12201225
query, key, value, rpa_kv_cache=kv_cache, rpa_metadata=attention_metadata

src/maxtext/layers/decoders.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,11 +1269,11 @@ def __call__(
12691269
# When initializing with vLLM RPA attention, we need to run the output head to
12701270
# initialize any parameters associated with it.
12711271
# Same case applicable to vocab tiling
1272-
if self.is_initializing() and (cfg.num_vocab_tiling > 1 or cfg.attention == "vllm_rpa"):
1272+
if self.is_initializing() and (cfg.num_vocab_tiling > 1 or cfg.attention in ("vllm_rpa", "vllm_batched_rpa")):
12731273
_ = self.apply_output_head(shared_embedding, hidden_state, deterministic, model_mode)
12741274

12751275
# When invoking from vLLM with RPA attention, logit computation is deferred to a later stage.
1276-
if cfg.attention == "vllm_rpa":
1276+
if cfg.attention in ("vllm_rpa", "vllm_batched_rpa"):
12771277
logits = None
12781278
# When in the Indexer Dense Warm-up stage, skip the expensive output head projection
12791279
# for efficiency, as the main model is frozen and the LM loss is not needed.

src/maxtext/layers/moe.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -453,13 +453,13 @@ def __init__(
453453
self.wi_kernel_axes = ("exp", "embed_moe", "mlp_moe")
454454
self.wo_kernel_axes = ("exp", "mlp_moe", "embed_moe")
455455

456-
if self.config.attention == "vllm_rpa":
456+
if self.config.attention in ("vllm_rpa", "vllm_batched_rpa"):
457457
# vLLM uses 'model' as the tensor parallelism axis name
458458
self._tensor_parallelism_name = ("model", "attn_dp")
459459
else:
460460
self._tensor_parallelism_name = "tensor"
461461

462-
if self.config.attention == "vllm_rpa" and self.config.enable_dp_attention:
462+
if self.config.attention in ("vllm_rpa", "vllm_batched_rpa") and self.config.enable_dp_attention:
463463
self._expert_parallelism_name = "attn_dp_expert"
464464
elif self.config.custom_mesh_and_rule == ctypes.CustomRule.CP_AS_EP:
465465
# when custom mesh and rule is cp-as-ep, context axis is same with expert in MoE component
@@ -481,7 +481,7 @@ def __init__(
481481
# tpu-inference applies the score function in the fused_moe_gmm kernel,
482482
# so we don't apply it here to avoid redundant computation.
483483
# See https://github.com/vllm-project/tpu-inference/blob/main/tpu_inference/layers/common/fused_moe_gmm.py#L58.
484-
score_func="" if self.config.attention == "vllm_rpa" else self.config.routed_score_func,
484+
score_func="" if self.config.attention in ("vllm_rpa", "vllm_batched_rpa") else self.config.routed_score_func,
485485
matmul_precision=self.config.matmul_precision,
486486
shard_mode=config.shard_mode,
487487
rngs=self.rngs,
@@ -1426,7 +1426,7 @@ def jax_ragged_dot_gmm(inputs, kernel, tiling, group_sizes, expert_assignments,
14261426
def get_tokamax_group_sizes(group_sizes, inputs, _kernel):
14271427
if self.config.use_qwix_quantization:
14281428
return group_sizes
1429-
elif self.config.attention == "vllm_rpa":
1429+
elif self.config.attention in ("vllm_rpa", "vllm_batched_rpa"):
14301430
return group_sizes
14311431
else:
14321432
return tokamax.RaggedDotGroupSizes(
@@ -3066,7 +3066,7 @@ def __call__(
30663066
fused_kernel = None
30673067
w0_kernel = None
30683068
w1_kernel = None
3069-
if cfg.prefuse_moe_weights and cfg.attention == "vllm_rpa" and not self.is_hash_routing:
3069+
if cfg.prefuse_moe_weights and cfg.attention in ("vllm_rpa", "vllm_batched_rpa") and not self.is_hash_routing:
30703070
fused_kernel = jnp.asarray(self.wi[...], self.dtype)
30713071
elif cfg.prefuse_moe_weights:
30723072
wi = jnp.asarray(self.wi[...], self.dtype)
@@ -3096,7 +3096,7 @@ def __call__(
30963096
# The fused MoE kernel currently only supports standard Top-K routing with associated
30973097
# weights. Hash routed layers bypass this kernel and fall back
30983098
# to the sparse matmul implementation.
3099-
if cfg.attention == "vllm_rpa" and not self.is_hash_routing:
3099+
if cfg.attention in ("vllm_rpa", "vllm_batched_rpa") and not self.is_hash_routing:
31003100
output, lb_loss, bias_updates = self.fused_moe_matmul(
31013101
inputs,
31023102
gate_logits,

src/maxtext/layers/nnx_decoders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1833,7 +1833,7 @@ def pure_layer_fn(graphdef, state_in, y_in, kv_in):
18331833
hidden_state = y
18341834

18351835
# When invoking from vLLM with RPA attention, logit computation is deferred to a later stage.
1836-
if cfg.attention == "vllm_rpa":
1836+
if cfg.attention in ("vllm_rpa", "vllm_batched_rpa"):
18371837
logits = None
18381838

18391839
# When in the Indexer Dense Warm-up stage, skip the expensive output head projection

src/maxtext/models/models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def __call__(
251251
model_mode=model_mode,
252252
)
253253

254-
if self.config.attention == "vllm_rpa":
254+
if self.config.attention in ("vllm_rpa", "vllm_batched_rpa"):
255255
# In vLLM, logits are computed separately after updating the KV cache.
256256
return hidden_state, kv_caches
257257

@@ -370,7 +370,7 @@ def __init__(
370370
dummy_decoder_input_tokens = jnp.ones((batch_size, seq_len), dtype=jnp.int32)
371371
dummy_decoder_positions = jnp.ones((batch_size, seq_len), dtype=jnp.int32)
372372

373-
if self.config.attention == "vllm_rpa":
373+
if self.config.attention in ("vllm_rpa", "vllm_batched_rpa"):
374374
try:
375375
# pylint: disable=import-outside-toplevel
376376
from tpu_inference.layers.common.attention_metadata import AttentionMetadata # pytype: disable=import-error
@@ -608,7 +608,7 @@ def __call__(
608608
model_mode=model_mode,
609609
)
610610

611-
if self.config.attention == "vllm_rpa":
611+
if self.config.attention in ("vllm_rpa", "vllm_batched_rpa"):
612612
# In vLLM, logits are computed separately after updating the KV cache.
613613
return hidden_state, kv_caches
614614

tests/inference/benchmark_vllm.sh

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/bin/bash
2+
# Copyright 2026 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# Generic benchmark script for MaxText models using vLLM on TPU.
17+
#
18+
# Usage:
19+
# bash tests/inference/benchmark_vllm.sh <MODEL_NAME> [ATTENTION] [TP_SIZE]
20+
#
21+
# Arguments:
22+
# $1 (MODEL_NAME) : MaxText model name (e.g., "qwen3-0.6b", "llama3.1-8b") [Required]
23+
# $2 (ATTENTION) : Attention kernel to benchmark (default: "vllm_rpa")
24+
# $3 (TP_SIZE) : Tensor parallelism size (default: 4)
25+
#
26+
# Environment Variable Overrides:
27+
# TOKENIZER : HuggingFace tokenizer/model path (default: "Qwen/Qwen3-0.6B")
28+
#
29+
# Examples:
30+
# bash tests/inference/benchmark_vllm.sh qwen3-0.6b
31+
# bash tests/inference/benchmark_vllm.sh qwen3-0.6b vllm_batched_rpa
32+
# TOKENIZER=meta-llama/Llama-3.1-8B bash tests/inference/benchmark_vllm.sh llama3.1-8b vllm_batched_rpa 4
33+
34+
set -e
35+
36+
if [ -z "$1" ]; then
37+
echo "Error: MODEL_NAME is required as the first argument."
38+
echo "Usage: bash tests/inference/benchmark_vllm.sh <MODEL_NAME> [ATTENTION] [TP_SIZE]"
39+
echo "Example: bash tests/inference/benchmark_vllm.sh qwen3-0.6b"
40+
exit 1
41+
fi
42+
43+
MODEL_NAME=$1
44+
ATTENTION=${2:-${ATTENTION:-"vllm_rpa"}}
45+
TP_SIZE=${3:-${TP_SIZE:-4}}
46+
TOKENIZER=${TOKENIZER:-"Qwen/Qwen3-0.6B"}
47+
48+
INPUT_LEN=${INPUT_LEN:-512}
49+
OUTPUT_LEN=${OUTPUT_LEN:-128}
50+
NUM_PROMPTS=${NUM_PROMPTS:-32}
51+
LOAD_FORMAT=${LOAD_FORMAT:-"dummy"}
52+
GPU_MEM_UTIL=${GPU_MEM_UTIL:-0.6}
53+
PYTHON_EXEC=${PYTHON_EXEC:-"python3"}
54+
55+
echo "================================================================="
56+
echo "Running vLLM Benchmark for MaxText"
57+
echo "Model Name : $MODEL_NAME"
58+
echo "Tokenizer Path : $TOKENIZER"
59+
echo "Attention Kernel : $ATTENTION"
60+
echo "Tensor Parallel : $TP_SIZE"
61+
echo "Input / Output : $INPUT_LEN in / $OUTPUT_LEN out ($NUM_PROMPTS prompts)"
62+
echo "Load Format : $LOAD_FORMAT"
63+
echo "================================================================="
64+
65+
# Set standard TPU & vLLM environment variables
66+
export PYTHONPATH=$(pwd)/src:${PYTHONPATH}
67+
export SKIP_JAX_PRECOMPILE=1
68+
export NEW_MODEL_DESIGN=1
69+
export VLLM_ENABLE_V1_MULTIPROCESSING=0
70+
71+
if [ "$ATTENTION" = "vllm_batched_rpa" ]; then
72+
export USE_BATCHED_RPA_KERNEL=1
73+
else
74+
export USE_BATCHED_RPA_KERNEL=0
75+
fi
76+
77+
# Run offline throughput benchmark
78+
$PYTHON_EXEC -m vllm.entrypoints.cli.main bench throughput \
79+
--model "$TOKENIZER" \
80+
--tensor-parallel-size "$TP_SIZE" \
81+
--gpu-memory-utilization "$GPU_MEM_UTIL" \
82+
--load-format "$LOAD_FORMAT" \
83+
--hf-overrides '{"architectures": ["MaxTextForCausalLM"]}' \
84+
--additional-config "{\"maxtext_config\": {\"model_name\": \"$MODEL_NAME\", \"weight_dtype\": \"bfloat16\", \"attention\": \"$ATTENTION\", \"allow_split_physical_axes\": true, \"scan_layers\": true, \"enable_nnx\": true, \"pure_nnx_decoder\": true}}" \
85+
--dataset-name random \
86+
--random-input-len "$INPUT_LEN" \
87+
--random-output-len "$OUTPUT_LEN" \
88+
--num-prompts "$NUM_PROMPTS"
89+
90+
echo "================================================================="
91+
echo "Benchmark completed successfully for $MODEL_NAME ($ATTENTION)!"
92+
echo "================================================================="

0 commit comments

Comments
 (0)