Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 77 additions & 20 deletions src/maxtext/layers/nnx_decoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -2259,32 +2259,89 @@ def _apply_gemma4_small_layers(

# Donor layers expose their rotated, normed K/V to downstream shared layers, and reuse the
# just-computed K/V in their own forward to avoid double-computing the K/V projection.
if is_donor:
donor_k, donor_v = layer.compute_shared_kv(y, decoder_positions)
shared_kv_states[lyr] = (donor_k, donor_v)
shared_key, shared_value = donor_k, donor_v
extract_donor_kv = is_donor

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The variable extract_donor_kv is assigned but never used anywhere in the function. It can be safely removed.


ple_slice = per_layer_inputs[..., lyr, :] if per_layer_inputs is not None else None

cache_idx = cache_index_of[lyr]
kv_cache = kv_caches[cache_idx] if kv_caches is not None else None
y, kv_cache = layer(
y,
decoder_segment_ids,
decoder_positions,
deterministic,
model_mode,
previous_chunk=previous_chunk,
slot=slot,
bidirectional_mask=bidirectional_mask_value,
kv_cache=kv_cache,
attention_metadata=attention_metadata,
per_layer_input=ple_slice,
shared_key=shared_key,
shared_value=shared_value,

from jax.experimental import xla_metadata

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The import from jax.experimental import xla_metadata is executed in every iteration of the loop. Moving it to the top of the file (or before the loop) is cleaner and avoids redundant import lookups in each iteration.


graphdef_l, intermediate_l, other_l = nnx.split(layer, nnx.Intermediate, ...)
intermediate_xs = jax.tree.map(lambda x: x[None], intermediate_l)

def scan_body(carry, intermediate_slice):
y_c, other_c, kv_c = carry
l_merged = nnx.merge(graphdef_l, intermediate_slice, other_c)

current_shared_key = shared_key
current_shared_value = shared_value

donor_kv_out = ()
if is_donor:
donor_k, donor_v = l_merged.compute_shared_kv(y_c, decoder_positions)
donor_kv_out = (donor_k, donor_v)
current_shared_key = donor_k
current_shared_value = donor_v

out = l_merged(
y_c,
decoder_segment_ids,
decoder_positions,
deterministic,
model_mode,
previous_chunk=previous_chunk,
slot=slot,
bidirectional_mask=bidirectional_mask_value,
kv_cache=kv_c,
attention_metadata=attention_metadata,
per_layer_input=ple_slice,
shared_key=current_shared_key,
shared_value=current_shared_value,
)
if isinstance(out, tuple):
new_y = out[0]
new_kv = out[1] if len(out) > 1 else None
else:
new_y = out
new_kv = None

_, new_intermediate, new_other = nnx.split(l_merged, nnx.Intermediate, ...)

return (new_y, new_other, new_kv), (new_intermediate, donor_kv_out)

global_remat_policy = self.get_remat_policy()
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))

prevent_cse = maxtext_utils.should_prevent_cse_in_remat(cfg)
Comment on lines +2314 to +2320

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The computations for global_remat_policy and prevent_cse are loop-invariant as they only depend on self and cfg, which do not change across iterations. Computing them inside the loop for lyr in range(cfg.num_decoder_layers): introduces redundant overhead for all 35 layers.

Please move these definitions outside and before the loop (e.g., before line 2245):

global_remat_policy = self.get_remat_policy()
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))

prevent_cse = maxtext_utils.should_prevent_cse_in_remat(cfg)

run_global_layer = jax.checkpoint(
scan_body,
policy=global_remat_policy,
prevent_cse=prevent_cse,
)
if kv_caches is not None and kv_cache is not None:
kv_caches[cache_idx] = kv_cache

with xla_metadata.set_xla_metadata(**{"skip-simplify-while-loops_trip-count-one": "true"}):
(y, final_other, updated_kv_cache), (stacked_intermediate, stacked_donor_kv) = jax.lax.scan(
run_global_layer,
(y, other_l, kv_cache),
intermediate_xs,
length=1,
)

intermediate_state = jax.tree.map(lambda x: x[0], stacked_intermediate)
nnx.update(layer, final_other, intermediate_state)

if is_donor and stacked_donor_kv:
donor_kv = jax.tree.map(lambda x: x[0], stacked_donor_kv)
shared_kv_states[lyr] = donor_kv
shared_key, shared_value = donor_kv

if kv_caches is not None and updated_kv_cache is not None:
kv_caches[cache_idx] = updated_kv_cache

return y, kv_caches

Expand Down
Loading