Skip to content

Commit 2ba8b3b

Browse files
committed
fix: gemma gibberish characters and scan_layers default to False for vllm_decoder
1 parent 682d884 commit 2ba8b3b

8 files changed

Lines changed: 425 additions & 89 deletions

File tree

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ def __init__(self, vllm_config: VllmConfig, rng_key: jax.Array, mesh: Mesh):
201201
elif self.maxtext_config.load_parameters_path is None:
202202
max_logging.log("Warning: No load_parameters_path provided. The model will be initialized with random weights.")
203203

204+
def modules(self):
205+
"""Dummy method to satisfy vLLM's internal cleanup logic."""
206+
return []
207+
204208
def __call__(
205209
self,
206210
kv_caches: list[jax.Array],
@@ -345,9 +349,7 @@ def load_weights(self, rng_key: jax.Array) -> None:
345349
self.maxtext_config, mesh=self.mesh, model_mode=self.model_mode, rng_key=rng_key
346350
)
347351
if self.maxtext_config.lora.enable_lora:
348-
model = lora_utils.apply_lora_to_model(
349-
model, self.mesh, self.maxtext_config
350-
)
352+
model = lora_utils.apply_lora_to_model(model, self.mesh, self.maxtext_config)
351353
if self.maxtext_config.lora.lora_restore_path:
352354
lora_utils.restore_lora_from_path(model, self.maxtext_config)
353355
self.model = nnx.data(model)

src/maxtext/integration/vllm/maxtext_vllm_rollout.py

Lines changed: 101 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
import logging
2929
import time
3030
import jax
31+
import jax.numpy as jnp
3132
from flax import nnx
33+
from flax.traverse_util import flatten_dict, unflatten_dict
34+
3235
from pathwaysutils.experimental import reshard as _experimental_reshard
3336
from tunix.generate import mappings
3437
from tunix.generate.vllm_sampler import VllmConfig, VllmSampler
@@ -45,7 +48,83 @@ def _create_model_converter(model_name: str, config: Any, mesh: jax.sharding.Mes
4548
elif model_name in {"qwen3.5-35b-a3b"}:
4649
return Qwen35MaxTextToVLLMConverter(config=config, mesh=mesh)
4750

48-
raise ValueError(f"No MaxText->vLLM converter registered for model {model_name!r}.")
51+
return None
52+
53+
54+
def unroll_gemma_scanned_weights(weights):
55+
"""Workaround for tunix unstacking bug with Gemma 3/4 scanned blocks.
56+
57+
tunix fails to map nested layers like `layers.layers_0` to `layers_X`.
58+
We manually unroll them here if we detect the structure.
59+
"""
60+
if not hasattr(weights, "to_pure_dict"):
61+
return weights
62+
63+
flat_w = flatten_dict(weights.to_pure_dict(), sep="/")
64+
new_flat_w = {}
65+
66+
# Check if this is actually a scanned Gemma 3/4 checkpoint
67+
# by looking for the scanned nested structure.
68+
is_gemma_scanned = any("decoder/layers/layers_0/" in k or "decoder/scanned_blocks/layers_0/" in k for k in flat_w)
69+
70+
if not is_gemma_scanned:
71+
return weights
72+
73+
logging.info("MaxTextVllmSampler: Detected Gemma scanned weights structure. Unrolling along axis 1...")
74+
75+
# Determine attention pattern length and scan length
76+
pattern_keys = set()
77+
scan_length = 0
78+
for k, v in flat_w.items():
79+
if "decoder/layers/layers_" in k or "decoder/scanned_blocks/layers_" in k:
80+
layer_sub_idx = k.split("layers_")[-1].split("/")[0]
81+
pattern_keys.add(int(layer_sub_idx))
82+
# In MaxText, Gemma uses param_scan_axis=1, so the scan dimension is at axis 1
83+
if hasattr(v, "shape") and len(v.shape) > 1:
84+
scan_length = max(scan_length, v.shape[1])
85+
86+
pattern_length = max(pattern_keys) + 1 if pattern_keys else 0
87+
logging.info("MaxTextVllmSampler: Discovered scan_length=%d, pattern_length=%d", scan_length, pattern_length)
88+
89+
unrolled_count = 0
90+
for k, v in flat_w.items():
91+
if "decoder/layers/layers_" in k or "decoder/scanned_blocks/layers_" in k:
92+
# Unstack the array along the 1st axis
93+
if "decoder/scanned_blocks/layers_" in k:
94+
parts = k.split("decoder/scanned_blocks/layers_")
95+
else:
96+
parts = k.split("decoder/layers/layers_")
97+
98+
layer_sub_idx = int(parts[1].split("/")[0])
99+
suffix = "/" + "/".join(parts[1].split("/")[1:])
100+
101+
if hasattr(v, "shape") and len(v.shape) > 1:
102+
v_swapped = jnp.swapaxes(v, 1, 0)
103+
unstacked = [v_swapped[i] for i in range(scan_length)]
104+
else:
105+
unstacked = [v] * scan_length
106+
107+
for i in range(scan_length):
108+
global_idx = i * pattern_length + layer_sub_idx
109+
# Map back to nnx.List format which uses layers/X/ instead of layers_X
110+
new_flat_w[f"decoder/layers/{global_idx}{suffix}"] = unstacked[i]
111+
unrolled_count += 1
112+
113+
elif "decoder/layers_remainder/layers_" in k:
114+
layer_sub_idx = int(k.split("decoder/layers_remainder/layers_")[1].split("/")[0])
115+
suffix = "/" + "/".join(k.split("decoder/layers_remainder/layers_")[1].split("/")[1:])
116+
117+
global_idx = scan_length * pattern_length + layer_sub_idx
118+
new_flat_w[f"decoder/layers/{global_idx}{suffix}"] = v
119+
unrolled_count += 1
120+
else:
121+
new_flat_w[k] = v
122+
123+
logging.info(
124+
"MaxTextVllmSampler: Successfully unrolled %d scanned tensor components into vLLM-compatible nnx.List format.",
125+
unrolled_count,
126+
)
127+
return unflatten_dict(new_flat_w, sep="/")
49128

50129

51130
class MaxTextVllmSampler(VllmSampler):
@@ -73,6 +152,12 @@ def update_params(
73152
):
74153
"""Update the vLLM runner weights from a MaxText state tree."""
75154
if self._converter is None:
155+
# --- Workaround for tunix unstacking bug with Gemma 3/4 scanned blocks ---
156+
# tunix fails to map nested layers like `layers.layers_0` to `layers_X`.
157+
# We manually unroll them here if we detect the structure.
158+
updated_weights = unroll_gemma_scanned_weights(updated_weights)
159+
# --- End Workaround ---
160+
76161
super().update_params(updated_weights, filter_types)
77162
return None
78163

@@ -182,6 +267,19 @@ def __init__(
182267
model=rollout_actor,
183268
backend="vllm_jax",
184269
)
270+
engine_kwargs = {
271+
"max_model_len": cache_config_or_size,
272+
"model": rollout_config.rollout_vllm_model_version,
273+
"swap_space": getattr(rollout_config, "rollout_vllm_swap_space_size_gb", maxtext_config.swap_space_vllm_gb),
274+
# Async scheduling causes KeyError in dp_scheduler on slow models
275+
# (30B+) where inference latency exceeds the scheduler's window.
276+
"async_scheduling": rollout_config.rollout_vllm_async_scheduling,
277+
}
278+
279+
# Merge additional kwargs like dtype and hf_overrides provided by train_rl.py
280+
if hasattr(rollout_config, "rollout_vllm_kwargs") and rollout_config.rollout_vllm_kwargs:
281+
engine_kwargs.update(rollout_config.rollout_vllm_kwargs)
282+
185283
self._sampler = MaxTextVllmSampler(
186284
tokenizer=tokenizer,
187285
config=VllmConfig( # pylint: disable=unexpected-keyword-arg,no-value-for-parameter
@@ -195,14 +293,8 @@ def __init__(
195293
tensor_parallel_size=rollout_config.tensor_parallel_size,
196294
data_parallel_size=rollout_config.data_parallel_size,
197295
enable_dp_attention=rollout_config.rollout_vllm_enable_dp_attention,
198-
engine_kwargs={
199-
"max_model_len": cache_config_or_size,
200-
"model": rollout_config.rollout_vllm_model_version,
201-
"swap_space": rollout_config.rollout_vllm_swap_space_size_gb,
202-
# Async scheduling causes KeyError in dp_scheduler on slow models
203-
# (30B+) where inference latency exceeds the scheduler's window.
204-
"async_scheduling": rollout_config.rollout_vllm_async_scheduling,
205-
},
296+
engine_kwargs=engine_kwargs,
297+
additional_config=getattr(rollout_config, "rollout_vllm_additional_config", None),
206298
),
207299
converter=converter,
208300
)

src/maxtext/layers/decoders.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,18 +1374,19 @@ def _apply_gemma3_scanned_blocks(
13741374
start_idx = scan_length * attention_pattern_length
13751375
remainder_kv = tuple(kv_caches[start_idx : start_idx + num_remaining_layers])
13761376

1377-
y_and_kv = layer(
1378-
y,
1377+
remainder_args = (
13791378
decoder_segment_ids,
13801379
decoder_positions,
13811380
deterministic,
13821381
model_mode,
1383-
previous_chunk=previous_chunk,
1384-
slot=slot,
1385-
bidirectional_mask=bidirectional_mask,
1386-
kv_cache=remainder_kv,
1387-
attention_metadata=attention_metadata,
1382+
slot,
1383+
None, # page_state
1384+
previous_chunk,
1385+
bidirectional_mask,
1386+
remainder_kv,
1387+
attention_metadata,
13881388
)
1389+
y_and_kv = layer(y, *remainder_args)
13891390

13901391
if isinstance(y_and_kv, tuple):
13911392
y = y_and_kv[0]

0 commit comments

Comments
 (0)