Skip to content

Commit f93d241

Browse files
committed
feat(gemma3): add Gemma-3 LoRA end-to-end (E2E) post-training and serving integration
1 parent cf9b093 commit f93d241

7 files changed

Lines changed: 316 additions & 13 deletions

File tree

src/maxtext/configs/inference/vllm.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ logical_axis_rules: [
8888
['mlp', ['attn_dp', 'model']],
8989
['embed', []],
9090
['norm', []],
91+
['layers', []],
92+
['dense_layers', []],
93+
['moe_layers', []],
9194
# ==========================================
9295
# Inference(Prefill, Decode, Cache)
9396
# ==========================================

src/maxtext/inference/vllm_decode.py

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
from maxtext.utils import model_creation_utils
4242
from maxtext.utils import max_logging
43+
from maxtext.utils import lora_utils
4344
from maxtext.utils.globals import MAXTEXT_CONFIGS_DIR
4445
from maxtext.common.common_types import Config
4546
from maxtext.common import profiler
@@ -94,11 +95,13 @@ def decode_with_vllm(config: Config) -> None:
9495
"additional_config": {
9596
"maxtext_config": {
9697
"model_name": config.model_name,
97-
"weight_dtype": "bfloat16",
98+
"weight_dtype": config.weight_dtype,
9899
"allow_split_physical_axes": True,
99100
"debug_sharding": config.debug_sharding,
100101
"prefuse_moe_weights": config.prefuse_moe_weights,
101102
"scan_layers": config.scan_layers,
103+
"enable_nnx": config.enable_nnx,
104+
"pure_nnx_decoder": config.pure_nnx_decoder,
102105
},
103106
"sharding": {
104107
"sharding_strategy": {
@@ -195,7 +198,18 @@ def decode_with_tunix(
195198
mesh: The JAX mesh for parallelism.
196199
"""
197200
# Wrap the model for Tunix
198-
tunix_model = TunixMaxTextAdapter(base_model=model)
201+
use_no_op_mappings = False
202+
if hasattr(config, "vllm_hf_overrides") and config.vllm_hf_overrides:
203+
overrides = config.vllm_hf_overrides
204+
if isinstance(overrides, str) and "MaxTextForCausalLM" in overrides:
205+
use_no_op_mappings = True
206+
elif isinstance(overrides, dict) and "MaxTextForCausalLM" in overrides.get("architectures", []):
207+
use_no_op_mappings = True
208+
209+
tunix_model = TunixMaxTextAdapter(
210+
base_model=model,
211+
use_no_op_mappings=use_no_op_mappings,
212+
)
199213

200214
# Load the tokenizer
201215
tokenizer = transformers.AutoTokenizer.from_pretrained(
@@ -223,13 +237,48 @@ def decode_with_tunix(
223237
f"max_target_length ({config.max_target_length}) must be greater than max_prompt_length ({max_prompt_length})"
224238
)
225239

240+
# MaxText uses -1 to mean "disabled"; vLLM requires top_p in (0, 1].
241+
top_p = config.decode_sampling_nucleus_p if config.decode_sampling_nucleus_p > 0 else 1.0
242+
top_k = config.decode_sampling_top_k if config.decode_sampling_top_k > 0 else -1
243+
244+
rollout_vllm_additional_config = {
245+
"maxtext_config": {
246+
"model_name": config.model_name,
247+
"weight_dtype": config.weight_dtype,
248+
"allow_split_physical_axes": True,
249+
"debug_sharding": config.debug_sharding,
250+
"prefuse_moe_weights": config.prefuse_moe_weights,
251+
"scan_layers": config.scan_layers,
252+
"enable_nnx": config.enable_nnx,
253+
"pure_nnx_decoder": config.pure_nnx_decoder,
254+
}
255+
}
256+
257+
if config.lora.enable_lora:
258+
rollout_vllm_additional_config["maxtext_config"]["lora"] = {
259+
"enable_lora": config.lora.enable_lora,
260+
"lora_restore_path": config.lora.lora_restore_path,
261+
"lora_rank": config.lora.lora_rank,
262+
"lora_alpha": config.lora.lora_alpha,
263+
"lora_module_path": config.lora.lora_module_path,
264+
}
265+
226266
# Create vLLM rollout for inference
227267
rollout_config = base_rollout.RolloutConfig(
228268
max_tokens_to_generate=max_tokens_to_generate,
229269
max_prompt_length=max_prompt_length,
230270
temperature=config.decode_sampling_temperature,
231-
top_p=config.decode_sampling_nucleus_p,
232-
top_k=config.decode_sampling_top_k,
271+
top_p=top_p,
272+
top_k=top_k,
273+
rollout_vllm_model_version=config.tokenizer_path,
274+
rollout_vllm_hbm_utilization=config.hbm_utilization_vllm,
275+
rollout_vllm_init_with_random_weights=True,
276+
rollout_vllm_tpu_backend_type="jax",
277+
tensor_parallel_size=config.ici_tensor_parallelism if config.ici_tensor_parallelism > 0 else 1,
278+
data_parallel_size=jax.device_count()
279+
// (config.ici_tensor_parallelism if config.ici_tensor_parallelism > 0 else 1),
280+
rollout_vllm_additional_config=rollout_vllm_additional_config,
281+
rollout_vllm_kwargs={"hf_overrides": config.vllm_hf_overrides},
233282
)
234283
vllm_rollout = VllmRollout(
235284
model=tunix_model,
@@ -239,12 +288,7 @@ def decode_with_tunix(
239288
# other special formatting, which is not part of max_prompt_length.
240289
cache_config_or_size=max_prompt_length + max_tokens_to_generate + 256,
241290
mesh=mesh,
242-
model_version=config.tokenizer_path,
243-
hbm_utilization=0.8,
244-
# Initialize vllm model with random weights to speed up bootstrap time.
245-
# Actual model weights will be loaded later.
246-
init_with_random_weights=True,
247-
tpu_backend_type="jax",
291+
rollout_config=rollout_config,
248292
)
249293

250294
# Generate text
@@ -271,6 +315,10 @@ def main(argv: Sequence[str]) -> None:
271315

272316
if FLAGS.use_tunix:
273317
maxtext_model, mesh = model_creation_utils.from_pretrained(config)
318+
if config.lora.enable_lora:
319+
maxtext_model = lora_utils.apply_lora_to_model(maxtext_model, mesh, config)
320+
if config.lora.lora_restore_path:
321+
lora_utils.restore_lora_from_path(maxtext_model, config)
274322
decode_with_tunix(config, model=maxtext_model, mesh=mesh)
275323
else:
276324
decode_with_vllm(config)

src/maxtext/integration/tunix/utils.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,49 @@ def to_hf_hook_fns(self):
153153
return {}
154154

155155
def lora_to_hf_mappings(self):
156-
if self.use_standalone_mappings:
157-
return STANDALONE_VLLM_WEIGHT_MAPPING[self.model_name].lora_to_hf_mappings()
156+
"""Dynamically generate LoRA mappings from base model weights mappings."""
157+
base_mappings = self.to_hf_mapping()
158+
if not base_mappings:
159+
return None
160+
161+
lora_mapping = {}
162+
for maxtext_key, (hf_key, sharding_spec) in base_mappings.items():
163+
segments = set(maxtext_key.split("."))
164+
is_input_proj = any(
165+
p in segments for p in ["wi_0", "wi_1", "query", "key", "value", "wq_a", "wq_b", "wkv_a", "wkv_b"]
166+
)
167+
is_output_proj = any(p in segments for p in ["wo", "out"])
168+
169+
if not (is_input_proj or is_output_proj):
170+
continue
171+
172+
# Derive MaxText LoRA keys
173+
maxtext_lora_a = maxtext_key + "_lora_a"
174+
maxtext_lora_b = maxtext_key + "_lora_b"
175+
176+
# Derive HF/vLLM LoRA keys
177+
if hf_key.endswith(".kernel"):
178+
hf_lora_a = hf_key.replace(".kernel", ".kernel_lora_a")
179+
hf_lora_b = hf_key.replace(".kernel", ".kernel_lora_b")
180+
elif hf_key.endswith(".weight"):
181+
hf_lora_a = hf_key.replace(".weight", ".weight_lora_a")
182+
hf_lora_b = hf_key.replace(".weight", ".weight_lora_b")
183+
else:
184+
hf_lora_a = hf_key + "_lora_a"
185+
hf_lora_b = hf_key + "_lora_b"
186+
187+
# Derive sharding specifications for Qwix LoRA parameters
188+
if is_input_proj:
189+
sharding_a = (None, "layer", None) # Input -> Rank (unsharded)
190+
sharding_b = sharding_spec # Rank -> Output (same as base)
191+
else:
192+
sharding_a = sharding_spec # Input -> Rank (same as base)
193+
sharding_b = (None, "layer", None) # Rank -> Output (unsharded)
194+
195+
lora_mapping[maxtext_lora_a] = (hf_lora_a, sharding_a)
196+
lora_mapping[maxtext_lora_b] = (hf_lora_b, sharding_b)
158197

159-
return None
198+
return lora_mapping
160199

161200
def _generalize_maxtext_key(self, maxtext_key):
162201
"""Generalizes the MaxText key to a common vLLM format."""

src/maxtext/integration/tunix/weight_mapping/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
model name. This allows for easy extension to support new models.
2020
"""
2121
from maxtext.integration.tunix.weight_mapping.deepseek3 import DEEPSEEK_VLLM_MAPPING
22+
from maxtext.integration.tunix.weight_mapping.gemma3 import GEMMA3_VLLM_MAPPING
2223
from maxtext.integration.tunix.weight_mapping.gpt_oss import GPT_OSS_VLLM_MAPPING
2324
from maxtext.integration.tunix.weight_mapping.llama3 import LLAMA3_VLLM_MAPPING
2425
from maxtext.integration.tunix.weight_mapping.qwen2 import QWEN2_VLLM_MAPPING
@@ -35,6 +36,8 @@ def __getattr__(self, name):
3536
return QWEN2_VLLM_MAPPING
3637
elif name.startswith("qwen3"):
3738
return QWEN3_VLLM_MAPPING
39+
elif name.startswith("gemma3"):
40+
return GEMMA3_VLLM_MAPPING
3841
elif name.startswith("deepseek3"):
3942
return DEEPSEEK_VLLM_MAPPING
4043
elif name.startswith("gpt-oss"):
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Copyright 2023–2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Defines the weight mapping from MaxText's Gemma3 model to a vLLM-compatible format."""
16+
17+
from dataclasses import dataclass
18+
19+
import numpy as np
20+
21+
22+
@dataclass
23+
class GEMMA3_VLLM_MAPPING:
24+
"""Mapping MaxText Gemma3 weights to vLLM's Gemma3 weights."""
25+
26+
@staticmethod
27+
def to_hf_hook_fns():
28+
"""Returns a dictionary of hook functions to be applied to MaxText weights."""
29+
30+
def scale_embedding(arr):
31+
hidden_size = arr.shape[1]
32+
normalizer = np.dtype(arr.dtype).type(hidden_size**0.5)
33+
return arr / normalizer
34+
35+
return {
36+
"base.token_embedder.embedding": scale_embedding,
37+
}
38+
39+
@staticmethod
40+
def to_hf_transpose_keys():
41+
"""Returns a list of keys for weights that need to be transposed."""
42+
return {}
43+
44+
@staticmethod
45+
def lora_to_hf_mappings():
46+
"""Provides the mapping for LoRA (Low-Rank Adaptation) weights."""
47+
return None
48+
49+
@staticmethod
50+
def to_hf_mapping():
51+
"""Mapping from MaxText model to HuggingFace vLLM model.
52+
53+
Returns:
54+
A dictionary mapping MaxText parameter names to HuggingFace parameter names and sharding.
55+
"""
56+
return {
57+
# Token embeddings - shard vocab dimension
58+
"base.token_embedder.embedding": (
59+
"model.language_model.embed_tokens.kernel",
60+
("model", None),
61+
),
62+
# Final layer norm - no sharding needed
63+
"base.decoder.decoder_norm.scale": (
64+
"model.language_model.norm.scale",
65+
(None,),
66+
),
67+
# Layer norms - no sharding needed
68+
"base.decoder.layers.pre_self_attention_norm.scale": (
69+
"model.language_model.layers.*.input_layernorm.scale",
70+
(None, "layer"),
71+
),
72+
"base.decoder.layers.post_self_attention_norm.scale": (
73+
"model.language_model.layers.*.post_attention_layernorm.scale",
74+
(None, "layer"),
75+
),
76+
"base.decoder.layers.self_attention.query_norm.scale": (
77+
"model.language_model.layers.*.self_attn.q_norm.scale",
78+
(None, "layer"),
79+
),
80+
"base.decoder.layers.self_attention.key_norm.scale": (
81+
"model.language_model.layers.*.self_attn.k_norm.scale",
82+
(None, "layer"),
83+
),
84+
"base.decoder.layers.pre_ffw_norm.scale": (
85+
"model.language_model.layers.*.pre_feedforward_layernorm.scale",
86+
(None, "layer"),
87+
),
88+
"base.decoder.layers.post_ffw_norm.scale": (
89+
"model.language_model.layers.*.post_feedforward_layernorm.scale",
90+
(None, "layer"),
91+
),
92+
# MLP components - shard hidden dimensions
93+
"base.decoder.layers.mlp.wi_0.kernel": (
94+
"model.language_model.layers.*.mlp.gate_proj.kernel",
95+
(None, "layer", "model"),
96+
),
97+
"base.decoder.layers.mlp.wi_1.kernel": (
98+
"model.language_model.layers.*.mlp.up_proj.kernel",
99+
(None, "layer", "model"),
100+
),
101+
"base.decoder.layers.mlp.wo.kernel": (
102+
"model.language_model.layers.*.mlp.down_proj.kernel",
103+
("model", "layer", None),
104+
),
105+
# Attention components - shard head dimensions
106+
"base.decoder.layers.self_attention.query.kernel": (
107+
"model.language_model.layers.*.self_attn.q_proj.kernel",
108+
(None, "layer", "model", None),
109+
),
110+
"base.decoder.layers.self_attention.key.kernel": (
111+
"model.language_model.layers.*.self_attn.k_proj.kernel",
112+
(None, "layer", "model", None),
113+
),
114+
"base.decoder.layers.self_attention.value.kernel": (
115+
"model.language_model.layers.*.self_attn.v_proj.kernel",
116+
(None, "layer", "model", None),
117+
),
118+
"base.decoder.layers.self_attention.out.kernel": (
119+
"model.language_model.layers.*.self_attn.o_proj.kernel",
120+
("model", "layer", None, None),
121+
),
122+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from maxtext.common.common_types import MODEL_MODE_AUTOREGRESSIVE
2929
from maxtext.utils import max_logging
3030
from maxtext.utils import model_creation_utils
31+
from maxtext.utils import lora_utils
3132

3233

3334
try:
@@ -345,6 +346,10 @@ def load_weights(self, rng_key: jax.Array) -> None:
345346
model = model_creation_utils.from_pretrained(
346347
self.maxtext_config, mesh=self.mesh, model_mode=self.model_mode, rng_key=rng_key
347348
)
349+
if self.maxtext_config.lora.enable_lora:
350+
model = lora_utils.apply_lora_to_model(model, self.mesh, self.maxtext_config)
351+
if self.maxtext_config.lora.lora_restore_path:
352+
lora_utils.restore_lora_from_path(model, self.maxtext_config)
348353
self.model = nnx.data(model)
349354

350355
def get_mrope_input_positions(

0 commit comments

Comments
 (0)