Support gemma4 decoder under explicit sharding mode - #4766
Conversation
Enables tensor-parallel serving/inference for gemma4 by threading out_sharding hints through Gemma4DecoderLayer's attention and MoE MLP calls, and replacing raw nn.with_logical_constraint calls with a mesh/shard_mode-aware helper that reshards under Explicit sharding instead of relying on with_sharding_constraint's auto-resolve (which Explicit mode does not support). Adds gemma4 to the set of decoders allowed under shard_mode=explicit.
There was a problem hiding this comment.
Code Review
This pull request enables explicit sharding support for the Gemma4 model by adding "gemma4" to the supported decoders list and refactoring gemma4.py to use custom logical constraints (maybe_shard_with_logical) instead of relying on flax.linen.with_logical_constraint. The reviewer feedback recommends a more robust approach for retrieving logical axis rules: instead of calling get_logical_axis_rules(), which relies on thread-local context, the code should directly reference the configured rules via config.logical_axis_rules across the newly added sharding calls.
| self.out_sharding = ( | ||
| create_sharding(self.mesh, self.activation_axis_names, rules=get_logical_axis_rules()) if self.mesh else None | ||
| ) |
There was a problem hiding this comment.
Using config.logical_axis_rules directly is more robust than relying on get_logical_axis_rules(), which reads from the thread-local context. During initialization (__init__), the thread-local context rules might not be fully established or could differ from the configured rules (e.g., in unit tests or setup phases). If you apply this change across all occurrences, you can also remove the import of get_logical_axis_rules.
| self.out_sharding = ( | |
| create_sharding(self.mesh, self.activation_axis_names, rules=get_logical_axis_rules()) if self.mesh else None | |
| ) | |
| self.out_sharding = ( | |
| create_sharding(self.mesh, self.activation_axis_names, rules=config.logical_axis_rules) if self.mesh else None | |
| ) |
| return maybe_shard_with_logical( | ||
| x, | ||
| logical_axes=self.activation_axis_names, | ||
| mesh=self.mesh, | ||
| shard_mode=getattr(self.config, "shard_mode", ShardMode.AUTO), | ||
| debug_sharding=getattr(self.config, "debug_sharding", False), | ||
| extra_stack_level=1, | ||
| rules=get_logical_axis_rules(), | ||
| ) |
There was a problem hiding this comment.
Passing self.config.logical_axis_rules directly is more robust and consistent than relying on get_logical_axis_rules().
| return maybe_shard_with_logical( | |
| x, | |
| logical_axes=self.activation_axis_names, | |
| mesh=self.mesh, | |
| shard_mode=getattr(self.config, "shard_mode", ShardMode.AUTO), | |
| debug_sharding=getattr(self.config, "debug_sharding", False), | |
| extra_stack_level=1, | |
| rules=get_logical_axis_rules(), | |
| ) | |
| return maybe_shard_with_logical( | |
| x, | |
| logical_axes=self.activation_axis_names, | |
| mesh=self.mesh, | |
| shard_mode=getattr(self.config, "shard_mode", ShardMode.AUTO), | |
| debug_sharding=getattr(self.config, "debug_sharding", False), | |
| extra_stack_level=1, | |
| rules=self.config.logical_axis_rules, | |
| ) |
| inputs = maybe_shard_with_logical( | ||
| inputs, | ||
| logical_axes=("activation_batch", "activation_norm_length", "activation_embed"), | ||
| mesh=self.mesh, | ||
| shard_mode=getattr(self.config, "shard_mode", ShardMode.AUTO), | ||
| debug_sharding=getattr(self.config, "debug_sharding", False), | ||
| rules=get_logical_axis_rules(), | ||
| ) |
There was a problem hiding this comment.
Use cfg.logical_axis_rules directly to ensure robustness and consistency with the other sharding constraint calls.
| inputs = maybe_shard_with_logical( | |
| inputs, | |
| logical_axes=("activation_batch", "activation_norm_length", "activation_embed"), | |
| mesh=self.mesh, | |
| shard_mode=getattr(self.config, "shard_mode", ShardMode.AUTO), | |
| debug_sharding=getattr(self.config, "debug_sharding", False), | |
| rules=get_logical_axis_rules(), | |
| ) | |
| inputs = maybe_shard_with_logical( | |
| inputs, | |
| logical_axes=("activation_batch", "activation_norm_length", "activation_embed"), | |
| mesh=self.mesh, | |
| shard_mode=getattr(self.config, "shard_mode", ShardMode.AUTO), | |
| debug_sharding=getattr(self.config, "debug_sharding", False), | |
| rules=cfg.logical_axis_rules, | |
| ) |
Summary
shard_mode=explicit(needed for tensor parallelism) with the gemma4 decoder, which previously hit a hardValueError(Decoder 'gemma4' is not supported with 'explicit' sharding).Gemma4DecoderLayernow threadsout_shardinghints through its attention and MoE MLP calls, and uses a mesh/shard_mode-awarewith_logical_constrainthelper (maybe_shard_with_logical) instead of rawnn.with_logical_constraint, which only auto-resolves sharding mismatches underAutomode, notExplicit."gemma4"to the set of decoders permitted undershard_mode=explicitintypes.py.Test plan
rollout_tensor_parallelism=4,shard_mode=explicit, including a captured profiler trace and checkpoint save.