Skip to content

Commit c3d164f

Browse files
Merge pull request #4068 from AI-Hypercomputer:jackyf/gemma3-lora-e2e-integration
PiperOrigin-RevId: 941673310
2 parents 4a932fb + f93d241 commit c3d164f

6 files changed

Lines changed: 357 additions & 28 deletions

File tree

src/maxtext/inference/vllm_decode.py

Lines changed: 84 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,26 +36,25 @@
3636
from absl import flags
3737
from flax.linen import partitioning as nn_partitioning
3838
import jax
39-
import transformers
40-
41-
from maxtext.utils import model_creation_utils
42-
from maxtext.utils import max_logging
43-
from maxtext.utils.globals import MAXTEXT_CONFIGS_DIR
44-
from maxtext.common.common_types import Config
4539
from maxtext.common import profiler
40+
from maxtext.common.common_types import Config
41+
from maxtext.configs import pyconfig
4642
from maxtext.integration.tunix.tunix_adapter import TunixMaxTextAdapter
43+
import maxtext.integration.vllm.maxtext_vllm_adapter as adapter
44+
from maxtext.utils import lora_utils
45+
from maxtext.utils import max_logging
46+
from maxtext.utils import model_creation_utils
47+
from maxtext.utils.globals import MAXTEXT_CONFIGS_DIR
48+
import transformers
4749
from tunix.rl.rollout import base_rollout
4850
from tunix.rl.rollout.vllm_rollout import VllmRollout
4951
from vllm import LLM
50-
from vllm.sampling_params import SamplingParams
51-
from maxtext.configs import pyconfig
52-
import maxtext.integration.vllm.maxtext_vllm_adapter as adapter
53-
5452
# Force uses_mrope to False to disable 3D multimodal position IDs in text-only runs.
5553
# TODO(b/520142315): Class-level monkey patching is required here because ModelConfig.uses_mrope
5654
# is evaluated during the internal initialization of the LLM engine, making instance-level
5755
# configuration impossible. Check if a cleaner configuration option can be added in upstream vLLM.
5856
from vllm.config import ModelConfig
57+
from vllm.sampling_params import SamplingParams
5958

6059
ModelConfig.uses_mrope = property(lambda _: False)
6160

@@ -94,11 +93,13 @@ def decode_with_vllm(config: Config) -> None:
9493
"additional_config": {
9594
"maxtext_config": {
9695
"model_name": config.model_name,
97-
"weight_dtype": "bfloat16",
96+
"weight_dtype": config.weight_dtype,
9897
"allow_split_physical_axes": True,
9998
"debug_sharding": config.debug_sharding,
10099
"prefuse_moe_weights": config.prefuse_moe_weights,
101100
"scan_layers": config.scan_layers,
101+
"enable_nnx": config.enable_nnx,
102+
"pure_nnx_decoder": config.pure_nnx_decoder,
102103
},
103104
"sharding": {
104105
"sharding_strategy": {
@@ -195,7 +196,20 @@ def decode_with_tunix(
195196
mesh: The JAX mesh for parallelism.
196197
"""
197198
# Wrap the model for Tunix
198-
tunix_model = TunixMaxTextAdapter(base_model=model)
199+
use_no_op_mappings = False
200+
if hasattr(config, "vllm_hf_overrides") and config.vllm_hf_overrides:
201+
overrides = config.vllm_hf_overrides
202+
if isinstance(overrides, str) and "MaxTextForCausalLM" in overrides:
203+
use_no_op_mappings = True
204+
elif isinstance(overrides, dict) and "MaxTextForCausalLM" in overrides.get(
205+
"architectures", []
206+
):
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,62 @@ 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 = (
242+
config.decode_sampling_nucleus_p
243+
if config.decode_sampling_nucleus_p > 0
244+
else 1.0
245+
)
246+
top_k = (
247+
config.decode_sampling_top_k if config.decode_sampling_top_k > 0 else -1
248+
)
249+
250+
rollout_vllm_additional_config = {
251+
"maxtext_config": {
252+
"model_name": config.model_name,
253+
"weight_dtype": config.weight_dtype,
254+
"allow_split_physical_axes": True,
255+
"debug_sharding": config.debug_sharding,
256+
"prefuse_moe_weights": config.prefuse_moe_weights,
257+
"scan_layers": config.scan_layers,
258+
"enable_nnx": config.enable_nnx,
259+
"pure_nnx_decoder": config.pure_nnx_decoder,
260+
}
261+
}
262+
263+
if config.lora.enable_lora:
264+
rollout_vllm_additional_config["maxtext_config"]["lora"] = {
265+
"enable_lora": config.lora.enable_lora,
266+
"lora_restore_path": config.lora.lora_restore_path,
267+
"lora_rank": config.lora.lora_rank,
268+
"lora_alpha": config.lora.lora_alpha,
269+
"lora_module_path": config.lora.lora_module_path,
270+
}
271+
226272
# Create vLLM rollout for inference
227273
rollout_config = base_rollout.RolloutConfig(
228274
max_tokens_to_generate=max_tokens_to_generate,
229275
max_prompt_length=max_prompt_length,
230276
temperature=config.decode_sampling_temperature,
231-
top_p=config.decode_sampling_nucleus_p,
232-
top_k=config.decode_sampling_top_k,
277+
top_p=top_p,
278+
top_k=top_k,
279+
rollout_vllm_model_version=config.tokenizer_path,
280+
rollout_vllm_hbm_utilization=config.hbm_utilization_vllm,
281+
rollout_vllm_init_with_random_weights=True,
282+
rollout_vllm_tpu_backend_type="jax",
283+
tensor_parallel_size=(
284+
config.ici_tensor_parallelism
285+
if config.ici_tensor_parallelism > 0
286+
else 1
287+
),
288+
data_parallel_size=jax.device_count()
289+
// (
290+
config.ici_tensor_parallelism
291+
if config.ici_tensor_parallelism > 0
292+
else 1
293+
),
294+
rollout_vllm_additional_config=rollout_vllm_additional_config,
295+
rollout_vllm_kwargs={"hf_overrides": config.vllm_hf_overrides},
233296
)
234297
vllm_rollout = VllmRollout(
235298
model=tunix_model,
@@ -239,12 +302,7 @@ def decode_with_tunix(
239302
# other special formatting, which is not part of max_prompt_length.
240303
cache_config_or_size=max_prompt_length + max_tokens_to_generate + 256,
241304
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",
305+
rollout_config=rollout_config,
248306
)
249307

250308
# Generate text
@@ -271,6 +329,12 @@ def main(argv: Sequence[str]) -> None:
271329

272330
if FLAGS.use_tunix:
273331
maxtext_model, mesh = model_creation_utils.from_pretrained(config)
332+
if config.lora.enable_lora:
333+
maxtext_model = lora_utils.apply_lora_to_model(
334+
maxtext_model, mesh, config
335+
)
336+
if config.lora.lora_restore_path:
337+
lora_utils.restore_lora_from_path(maxtext_model, config)
274338
decode_with_tunix(config, model=maxtext_model, mesh=mesh)
275339
else:
276340
decode_with_vllm(config)

src/maxtext/integration/tunix/utils.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,60 @@ 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
166+
for p in [
167+
"wi_0",
168+
"wi_1",
169+
"query",
170+
"key",
171+
"value",
172+
"wq_a",
173+
"wq_b",
174+
"wkv_a",
175+
"wkv_b",
176+
]
177+
)
178+
is_output_proj = any(p in segments for p in ["wo", "out"])
179+
180+
if not (is_input_proj or is_output_proj):
181+
continue
182+
183+
# Derive MaxText LoRA keys
184+
maxtext_lora_a = maxtext_key + "_lora_a"
185+
maxtext_lora_b = maxtext_key + "_lora_b"
186+
187+
# Derive HF/vLLM LoRA keys
188+
if hf_key.endswith(".kernel"):
189+
hf_lora_a = hf_key.replace(".kernel", ".kernel_lora_a")
190+
hf_lora_b = hf_key.replace(".kernel", ".kernel_lora_b")
191+
elif hf_key.endswith(".weight"):
192+
hf_lora_a = hf_key.replace(".weight", ".weight_lora_a")
193+
hf_lora_b = hf_key.replace(".weight", ".weight_lora_b")
194+
else:
195+
hf_lora_a = hf_key + "_lora_a"
196+
hf_lora_b = hf_key + "_lora_b"
197+
198+
# Derive sharding specifications for Qwix LoRA parameters
199+
if is_input_proj:
200+
sharding_a = (None, "layer", None) # Input -> Rank (unsharded)
201+
sharding_b = sharding_spec # Rank -> Output (same as base)
202+
else:
203+
sharding_a = sharding_spec # Input -> Rank (same as base)
204+
sharding_b = (None, "layer", None) # Rank -> Output (unsharded)
205+
206+
lora_mapping[maxtext_lora_a] = (hf_lora_a, sharding_a)
207+
lora_mapping[maxtext_lora_b] = (hf_lora_b, sharding_b)
158208

159-
return None
209+
return lora_mapping
160210

161211
def _generalize_maxtext_key(self, maxtext_key):
162212
"""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: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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+
import dataclasses
18+
19+
import numpy as np
20+
21+
22+
@dataclasses.dataclass
23+
# pylint: disable=invalid-name
24+
class GEMMA3_VLLM_MAPPING:
25+
"""Mapping MaxText Gemma3 weights to vLLM's Gemma3 weights."""
26+
27+
@staticmethod
28+
def to_hf_hook_fns():
29+
"""Returns a dictionary of hook functions to be applied to MaxText weights."""
30+
31+
def scale_embedding(arr):
32+
hidden_size = arr.shape[1]
33+
normalizer = np.dtype(arr.dtype).type(hidden_size**0.5)
34+
return arr / normalizer
35+
36+
return {
37+
"base.token_embedder.embedding": scale_embedding,
38+
}
39+
40+
@staticmethod
41+
def to_hf_transpose_keys():
42+
"""Returns a list of keys for weights that need to be transposed."""
43+
return {}
44+
45+
@staticmethod
46+
def lora_to_hf_mappings():
47+
"""Provides the mapping for LoRA (Low-Rank Adaptation) weights."""
48+
return None
49+
50+
@staticmethod
51+
def to_hf_mapping():
52+
"""Mapping from MaxText model to HuggingFace vLLM model.
53+
54+
Returns:
55+
A dictionary mapping MaxText parameter names to HuggingFace parameter
56+
names and sharding.
57+
"""
58+
return {
59+
# Token embeddings - shard vocab dimension
60+
"base.token_embedder.embedding": (
61+
"model.language_model.embed_tokens.kernel",
62+
("model", None),
63+
),
64+
# Final layer norm - no sharding needed
65+
"base.decoder.decoder_norm.scale": (
66+
"model.language_model.norm.scale",
67+
(None,),
68+
),
69+
# Layer norms - no sharding needed
70+
"base.decoder.layers.pre_self_attention_norm.scale": (
71+
"model.language_model.layers.*.input_layernorm.scale",
72+
(None, "layer"),
73+
),
74+
"base.decoder.layers.post_self_attention_norm.scale": (
75+
"model.language_model.layers.*.post_attention_layernorm.scale",
76+
(None, "layer"),
77+
),
78+
"base.decoder.layers.self_attention.query_norm.scale": (
79+
"model.language_model.layers.*.self_attn.q_norm.scale",
80+
(None, "layer"),
81+
),
82+
"base.decoder.layers.self_attention.key_norm.scale": (
83+
"model.language_model.layers.*.self_attn.k_norm.scale",
84+
(None, "layer"),
85+
),
86+
"base.decoder.layers.pre_ffw_norm.scale": (
87+
"model.language_model.layers.*.pre_feedforward_layernorm.scale",
88+
(None, "layer"),
89+
),
90+
"base.decoder.layers.post_ffw_norm.scale": (
91+
"model.language_model.layers.*.post_feedforward_layernorm.scale",
92+
(None, "layer"),
93+
),
94+
# MLP components - shard hidden dimensions
95+
"base.decoder.layers.mlp.wi_0.kernel": (
96+
"model.language_model.layers.*.mlp.gate_proj.kernel",
97+
(None, "layer", "model"),
98+
),
99+
"base.decoder.layers.mlp.wi_1.kernel": (
100+
"model.language_model.layers.*.mlp.up_proj.kernel",
101+
(None, "layer", "model"),
102+
),
103+
"base.decoder.layers.mlp.wo.kernel": (
104+
"model.language_model.layers.*.mlp.down_proj.kernel",
105+
("model", "layer", None),
106+
),
107+
# Attention components - shard head dimensions
108+
"base.decoder.layers.self_attention.query.kernel": (
109+
"model.language_model.layers.*.self_attn.q_proj.kernel",
110+
(None, "layer", "model", None),
111+
),
112+
"base.decoder.layers.self_attention.key.kernel": (
113+
"model.language_model.layers.*.self_attn.k_proj.kernel",
114+
(None, "layer", "model", None),
115+
),
116+
"base.decoder.layers.self_attention.value.kernel": (
117+
"model.language_model.layers.*.self_attn.v_proj.kernel",
118+
(None, "layer", "model", None),
119+
),
120+
"base.decoder.layers.self_attention.out.kernel": (
121+
"model.language_model.layers.*.self_attn.o_proj.kernel",
122+
("model", "layer", None, None),
123+
),
124+
}

0 commit comments

Comments
 (0)