Rewrite Gemma4 scannable block#4530
Conversation
🤖 CI Failure Investigation ReportI have analyzed the recent test failures in the CI pipeline and identified the following: 🔍 What Failed
🪵 Error Details & Stack Trace# Missing function docstring in Gemma4ScannableBlock helper method
src/maxtext/models/gemma4.py:496:2: C0116: Missing function or method docstring (missing-function-docstring)
# Line too long violations in model_test.py
tests/unit/model_test.py:238:0: C0301: Line too long (131/125) (line-too-long)
tests/unit/model_test.py:283:0: C0301: Line too long (131/125) (line-too-long)
# Formatting non-compliance reported by pyink
would reformat tests/unit/model_test.py
would reformat src/maxtext/models/gemma4.py
would reformat tests/unit/nnx_scan_test.py
would reformat tests/unit/nnx_decoders_test.py💡 Root Cause Analysis & ContextConfidence: high (confirmed cause) The failure is caused by code style and quality violations introduced in this PR. Specifically:
These issues are purely style/quality regressions and do not affect behavioral correctness. They have been fully corrected and verified locally using 🛠️ Recommended FixApply the formatting changes and add the missing docstring to diff --git a/src/maxtext/models/gemma4.py b/src/maxtext/models/gemma4.py
index b974ec5..f626b8e 100644
--- a/src/maxtext/models/gemma4.py
+++ b/src/maxtext/models/gemma4.py
@@ -453,9 +453,7 @@ class Gemma4ScannableBlock(nnx.Module):
pattern_length = len(GEMMA4_ATTENTION_PATTERN)
if not 0 <= num_of_layers <= pattern_length:
- raise ValueError(
- f"Gemma4ScannableBlock must contain between 0 and {pattern_length} layers; got {num_of_layers}."
- )
+ raise ValueError(f"Gemma4ScannableBlock must contain between 0 and {pattern_length} layers; got {num_of_layers}.")
# Pattern is 5 local, 1 global.
self.num_local = min(5, num_of_layers)
@@ -505,6 +503,8 @@ class Gemma4ScannableBlock(nnx.Module):
bidirectional_mask=None,
attention_metadata=None,
):
+ """Applies local attention layers sequentially using scan."""
+
def apply_layer(layer, carry):
layer_out = layer(
carry,
@@ -654,9 +652,7 @@ class Gemma4ScannableBlock(nnx.Module):
offload_names = maxtext_utils.get_save_and_offload_names(cfg)
if offload_names[0] or offload_names[1]:
save_names, offload_to_device = offload_names
- global_remat_policy = jax.checkpoint_policies.save_only_these_names(
- *(save_names + offload_to_device)
- )
+ global_remat_policy = jax.checkpoint_policies.save_only_these_names(*(save_names + offload_to_device))
if self.apply_internal_remat and self.config.remat_policy != "none":
prevent_cse = maxtext_utils.should_prevent_cse_in_remat(self.config)
@@ -669,9 +667,7 @@ class Gemma4ScannableBlock(nnx.Module):
# Carry state through the loop instead of returning a stacked [1, ...]
# scan result: slicing that result previously introduced a bitcast
# between device and pinned-host memory under offload remat.
- with xla_metadata.set_xla_metadata(
- **{"skip-simplify-while-loops_trip-count-one": "true"}
- ):
+ with xla_metadata.set_xla_metadata(**{"skip-simplify-while-loops_trip-count-one": "true"}):
(y, global_state), _ = jax.lax.scan(
scan_global_layer,
(y, state_g),
diff --git a/tests/unit/model_test.py b/tests/unit/model_test.py
index 7c3d4ea..dd679ff 100644
--- a/tests/unit/model_test.py
+++ b/tests/unit/model_test.py
@@ -235,7 +235,9 @@ class TestModel(unittest.TestCase):
enable_dropout=False,
model_mode=MODEL_MODE_TRAIN,
)
- self.assertEqual(logits.shape, (new_config.global_batch_size_to_train_on, new_config.max_target_length, new_config.vocab_size))
+ self.assertEqual(
+ logits.shape, (new_config.global_batch_size_to_train_on, new_config.max_target_length, new_config.vocab_size)
+ )
def test_gemma4_model_linen(self):
"""Test the shared Gemma4 scannable block on the linen (ToLinen) path.
@@ -280,7 +282,9 @@ class TestModel(unittest.TestCase):
rngs={"aqt": self.rng},
)
logits = logits[0] if isinstance(logits, tuple) else logits
- self.assertEqual(logits.shape, (new_config.global_batch_size_to_train_on, new_config.max_target_length, new_config.vocab_size))
+ self.assertEqual(
+ logits.shape, (new_config.global_batch_size_to_train_on, new_config.max_target_length, new_config.vocab_size)
+ ) |
cc77a05 to
2805c03
Compare
2805c03 to
ef9cc43
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
I add pull ready to generate internal cl for testing. Won't submit until PR approved |
|
🤖 Hi @RissyRan, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
|
🤖 I'm sorry @RissyRan, but I was unable to process your request. Please see the logs for more details. |
RissyRan
left a comment
There was a problem hiding this comment.
Thanks for the change! Overall it looks good. I am a little bit lost in gemma4.py if/else branches, wondering if we could simplify a little bit?
| if not 0 <= num_of_layers <= pattern_length: | ||
| raise ValueError(f"Gemma4ScannableBlock must contain between 0 and {pattern_length} layers; got {num_of_layers}.") | ||
|
|
||
| # Pattern is 5 local, 1 global. |
There was a problem hiding this comment.
It seems the pattern has been defined. Will something like this work?
active_pattern = GEMMA4_ATTENTION_PATTERN[:num_of_layers]
self.num_local = sum(1 for t in active_pattern if t == AttentionType.LOCAL_SLIDING)
self.num_global = sum(1 for t in active_pattern if t == AttentionType.GLOBAL)
| metadata_axis_name="local_layers", | ||
| rngs=self.rngs, | ||
| ) | ||
| else: |
There was a problem hiding this comment.
Will this be triggered for Gemma4?
| current_kv = kv_cache[layer_id] if kv_cache is not None else None | ||
| y, new_kv = getattr(self, f"layers_{layer_id}")( | ||
|
|
||
| if kv_cache is not None: |
There was a problem hiding this comment.
I guess you meet issue when calling kv_cache[layer_id]?
Wondering if you tested for decoding with the change end-to-end and see reasonable outputs?
| stacked_params = jax.tree.map(lambda x: jnp.moveaxis(x, 0, scan_axis), stacked_params) | ||
| stacked_state = nnx.State.merge(stacked_params, stacked_other) | ||
| nnx.update(self.local_layers, stacked_state) | ||
| elif self.local_layers is not None: |
There was a problem hiding this comment.
Is this the same as line 564?
Description
Currently theimplementation of Gemma4ScannableBlock makes XLA treat the whole 6-layer block as a "big layer". The all-gathers for all 6 layers are run at the beginning of each block. Similarly, in backward pass, remat runs the whole block. This causes elevated HBM usage and more exposed collectives.
After this rewrite:
Tests
Checklist
Before submitting this PR, please make sure (put X in square brackets):
gemini-reviewlabel.