@@ -646,9 +646,19 @@ def _init_scanned_gemma4(self, decoder_block_classes, rngs, mesh):
646646 attention_pattern_length = len (gemma4 .GEMMA4_ATTENTION_PATTERN )
647647 scan_length = config .num_decoder_layers // attention_pattern_length
648648 num_remaining_layers = config .num_decoder_layers % attention_pattern_length
649- layer_kwargs = {"num_of_layers" : attention_pattern_length }
650-
651- rem_layer_kwargs = {"num_of_layers" : num_remaining_layers }
649+ policy = self .get_remat_policy ()
650+ # The pure-NNX decoder skips block-level remat (skip_block_remat=True below),
651+ # so the block rematerializes its own local/global layers instead.
652+ layer_kwargs = {
653+ "num_of_layers" : attention_pattern_length ,
654+ "remat_policy_fn" : policy ,
655+ "apply_internal_remat" : True ,
656+ }
657+ rem_layer_kwargs = {
658+ "num_of_layers" : num_remaining_layers ,
659+ "remat_policy_fn" : policy ,
660+ "apply_internal_remat" : True ,
661+ }
652662
653663 RemattedGemma4Block = gemma4 .Gemma4ScannableBlock
654664
@@ -870,7 +880,17 @@ def pure_layer_fn(state_in, y_in):
870880
871881 return out
872882
873- def _apply_layers_sequentially (self , layers , x_in , * args , length : int , kv_caches_stacked = None , ** kwargs ):
883+ def _apply_layers_sequentially (
884+ self ,
885+ layers ,
886+ x_in ,
887+ * args ,
888+ length : int ,
889+ kv_caches_stacked = None ,
890+ skip_block_remat : bool = False ,
891+ unroll : int = 1 ,
892+ ** kwargs ,
893+ ):
874894 """Runs the layer stack using nnx.scan.
875895
876896 Args:
@@ -881,6 +901,11 @@ def _apply_layers_sequentially(self, layers, x_in, *args, length: int, kv_caches
881901 kv_caches_stacked: Optional pytree whose leaves have shape [num_layers, ...].
882902 When provided, the i-th slice is passed as `kv_cache=` to layer i and the
883903 updated caches are returned as a third element of the tuple.
904+ skip_block_remat: When True, do not wrap the scanned body in jax.checkpoint.
905+ Used when the scanned module already applies its own (finer-grained,
906+ e.g. per-layer) remat internally, to avoid double rematerialization.
907+ unroll: Number of scan iterations to unroll into straight-line code
908+ (forwarded to jax.lax.scan). unroll >= length fully unrolls the loop.
884909 **kwargs: Keyword args forwarded to the layer (filtered by the layer signature).
885910
886911 Returns:
@@ -963,7 +988,12 @@ def layer_fn(carry, scanned_vars):
963988 return new_carry , (new_current_state , updated_kv )
964989 return new_carry , new_current_state
965990
966- layer_fn_wrapped = jax .checkpoint (layer_fn , policy = policy , prevent_cse = prevent_cse )
991+ if skip_block_remat :
992+ # The scanned module applies its own remat internally; wrapping the whole
993+ # body again would double-remat and recompute the entire block.
994+ layer_fn_wrapped = layer_fn
995+ else :
996+ layer_fn_wrapped = jax .checkpoint (layer_fn , policy = policy , prevent_cse = prevent_cse )
967997
968998 if use_kv :
969999 # If kv_caches is provided (e.g., from vLLM), we CANNOT use jax.lax.scan
@@ -995,7 +1025,7 @@ def layer_fn(carry, scanned_vars):
9951025 params = maxtext_utils_nnx .nnx_ensure_scan_leading_axis (params , length )
9961026 state = maxtext_utils_nnx .nnx_ensure_scan_leading_axis (state , length )
9971027
998- final_carry , scanned_state = jax .lax .scan (layer_fn_wrapped , x_in , (params , state ))
1028+ final_carry , scanned_state = jax .lax .scan (layer_fn_wrapped , x_in , (params , state ), unroll = unroll )
9991029 returned_kv_stacked = None
10001030
10011031 # Ensure metadata rank matches the stacked values
@@ -1938,13 +1968,25 @@ def _apply_gemma4_scanned_blocks(
19381968 attention_pattern_length = len (gemma4 .GEMMA4_ATTENTION_PATTERN )
19391969 scan_length = cfg .num_decoder_layers // attention_pattern_length
19401970
1941- # Apply the main scan over the full blocks
1971+ # Apply the main scan over the full blocks. Gemma4ScannableBlock applies
1972+ # per-layer remat internally (local scan + global layer), so skip the
1973+ # block-level remat here to avoid double rematerialization. Unrolling the
1974+ # block loop (one iteration per repeated block) lets XLA pipeline/free block
1975+ # activations across iterations (memory + overlap knob).
1976+ block_unroll = max (1 , scan_length )
19421977 if scan_length > 0 :
19431978 grouped_kv_caches = maxtext_utils .prepare_kv_caches_for_scan (
19441979 kv_caches , scan_length , attention_pattern_length , stack = False
19451980 )
19461981 y , self .scanned_blocks , _ = self ._apply_layers_sequentially (
1947- self .scanned_blocks , y , * layer_args , length = scan_length , kv_caches_stacked = grouped_kv_caches , ** layer_kwargs
1982+ self .scanned_blocks ,
1983+ y ,
1984+ * layer_args ,
1985+ length = scan_length ,
1986+ kv_caches_stacked = grouped_kv_caches ,
1987+ skip_block_remat = True ,
1988+ unroll = block_unroll ,
1989+ ** layer_kwargs ,
19481990 )
19491991 maxtext_utils .update_kv_caches_after_scan (
19501992 kv_caches , grouped_kv_caches , scan_length , attention_pattern_length , stacked = False
0 commit comments