2828 3. fill it, either from safetensors or from the Orbax cache, placing each
2929 parameter directly into its target sharding.
3030
31- `_component ` does 1 and 2 for the Flax modules; the denoiser needs its own
32- version of the same because it is NNX rather than Linen.
31+ `_nnx_component ` does 1 and 2 for the denoiser and the text encoder;
32+ `_linen_component` does the same for the Diffusers VAE, which is still Linen.
3333"""
3434
3535import inspect
5050
5151from .. import max_logging , max_utils
5252from ..models import FlaxAutoencoderKL
53- from ..models .qwen3_flax import FlaxQwen3Config , FlaxQwen3Model , load_qwen3_weights
53+ from ..models .qwen3_flax import FlaxQwen3Config , NNXFlaxQwen3Model
54+ from ..models .qwen3_utils import load_qwen3_weights
5455from ..models .z_image .transformer_z_image import ZImageTransformer2DModel
5556from ..models .z_image .z_image_utils import load_z_image_transformer
5657from ..pipelines .z_image import ZImagePipeline
@@ -91,12 +92,25 @@ def _abstract_tree(shapes, shardings):
9192 )
9293
9394
94- def _component ( module , init_args , mesh , axis_rules ):
95- """Abstract params of a Linen module, plus their target shardings.
95+ def _nnx_component ( factory , mesh , axis_rules , seed = 0 ):
96+ """Build an NNX module abstractly: its graph, params and target shardings.
9697
97- Shardings come from the module's own logical annotations. A module with no
98- annotations (`vae_flax.FlaxAutoencoderKL` has none) resolves to fully
99- replicated, which is exactly what it used to be given explicitly.
98+ Shardings come from the module's own `nnx.with_partitioning` annotations,
99+ resolved against the mesh.
100+ """
101+ graphdef , state , rest = nnx .split (nnx .eval_shape (factory , nnx .Rngs (jax .random .key (seed ))), nnx .Param , ...)
102+ shardings = None
103+ if mesh is not None :
104+ sharding_tree = nn .logical_to_mesh_sharding (nnx .get_partition_spec (state ), mesh , axis_rules )
105+ shardings = {path : variable .value for path , variable in nnx .to_flat_state (sharding_tree )}
106+ return graphdef , state , rest , shardings
107+
108+
109+ def _linen_component (module , init_args , mesh , axis_rules ):
110+ """The Linen equivalent of `_nnx_component`, for the Diffusers VAE.
111+
112+ A module with no logical annotations (`vae_flax.FlaxAutoencoderKL` has none)
113+ resolves to fully replicated.
100114 """
101115 abstract = jax .eval_shape (lambda : module .init (jax .random .key (0 ), * init_args ))
102116 shapes = _plain (abstract ["params" ])
@@ -106,9 +120,15 @@ def _component(module, init_args, mesh, axis_rules):
106120 return shapes , dict (flatten_dict (_plain (mesh_shardings ["params" ])))
107121
108122
109- def _transformer_component (transformer_config , config , mesh ):
110- """The NNX equivalent of `_component`, plus the graph needed to rebuild it."""
123+ def _fill (graphdef , state , rest , params ):
124+ """Put loaded params into an abstract NNX state and rebuild the module."""
125+ flat_state = dict (nnx .to_flat_state (state ))
126+ for path , value in flatten_dict (params ).items ():
127+ flat_state [path ].value = value
128+ return nnx .merge (graphdef , nnx .from_flat_state (flat_state ), rest )
129+
111130
131+ def _transformer_factory (transformer_config , config , mesh ):
112132 def factory (rngs ):
113133 return ZImageTransformer2DModel (
114134 rngs = rngs ,
@@ -120,26 +140,15 @@ def factory(rngs):
120140 ** transformer_config ,
121141 )
122142
123- graphdef , state , rest = nnx .split (nnx .eval_shape (factory , nnx .Rngs (jax .random .key (config .seed ))), nnx .Param , ...)
124- shardings = None
125- if mesh is not None :
126- sharding_tree = nn .logical_to_mesh_sharding (nnx .get_partition_spec (state ), mesh , config .logical_axis_rules )
127- shardings = {path : variable .value for path , variable in nnx .to_flat_state (sharding_tree )}
128- return graphdef , state , rest , shardings
129-
130-
131- def _fill (graphdef , state , rest , params ):
132- """Put loaded params into an abstract NNX state and rebuild the module."""
133- flat_state = dict (nnx .to_flat_state (state ))
134- for path , value in flatten_dict (params ).items ():
135- flat_state [path ].value = value
136- return nnx .merge (graphdef , nnx .from_flat_state (flat_state ), rest )
143+ return factory
137144
138145
139146def create_z_image_transformer (model_id : str , config , mesh : Optional [jax .sharding .Mesh ] = None ):
140147 """Instantiate and stream a Diffusers Z-Image checkpoint into an NNX model."""
141148 transformer_config = ZImageTransformer2DModel .load_config (model_id , subfolder = "transformer" )
142- graphdef , state , rest , shardings = _transformer_component (transformer_config , config , mesh )
149+ graphdef , state , rest , shardings = _nnx_component (
150+ _transformer_factory (transformer_config , config , mesh ), mesh , config .logical_axis_rules , config .seed
151+ )
143152 params = load_z_image_transformer (model_id , state .to_pure_dict (), target_shardings = shardings )
144153 return _fill (graphdef , state , rest , params )
145154
@@ -196,27 +205,31 @@ def build_vae(self, vae_config: dict):
196205 """VAE module, its abstract params and their target shardings."""
197206 vae = FlaxAutoencoderKL (** vae_config , dtype = self .config .activations_dtype , weights_dtype = self .config .weights_dtype )
198207 sample = jnp .ones ((1 , vae .config .in_channels , 64 , 64 ), jnp .float32 )
199- return vae , * _component (vae , (sample ,), self .mesh , self ._vae_axis_rules ())
208+ return vae , * _linen_component (vae , (sample ,), self .mesh , self ._vae_axis_rules ())
200209
201210 def build_text_encoder (self , text_encoder_config : dict ):
202- """Qwen3 module, its abstract params and their target shardings."""
203- model = FlaxQwen3Model (
204- FlaxQwen3Config (
205- vocab_size = text_encoder_config ["vocab_size" ],
206- hidden_size = text_encoder_config ["hidden_size" ],
207- intermediate_size = text_encoder_config ["intermediate_size" ],
208- num_hidden_layers = text_encoder_config ["num_hidden_layers" ],
209- num_attention_heads = text_encoder_config ["num_attention_heads" ],
210- num_key_value_heads = text_encoder_config ["num_key_value_heads" ],
211- head_dim = text_encoder_config ["head_dim" ],
212- rms_norm_eps = text_encoder_config ["rms_norm_eps" ],
213- rope_theta = text_encoder_config ["rope_theta" ],
214- max_position_embeddings = text_encoder_config ["max_position_embeddings" ],
215- dtype = self .config .weights_dtype ,
216- )
211+ """Qwen3 graph, its abstract params and their target shardings."""
212+ qwen3_config = FlaxQwen3Config (
213+ vocab_size = text_encoder_config ["vocab_size" ],
214+ hidden_size = text_encoder_config ["hidden_size" ],
215+ intermediate_size = text_encoder_config ["intermediate_size" ],
216+ num_hidden_layers = text_encoder_config ["num_hidden_layers" ],
217+ num_attention_heads = text_encoder_config ["num_attention_heads" ],
218+ num_key_value_heads = text_encoder_config ["num_key_value_heads" ],
219+ head_dim = text_encoder_config ["head_dim" ],
220+ rms_norm_eps = text_encoder_config ["rms_norm_eps" ],
221+ rope_theta = text_encoder_config ["rope_theta" ],
222+ max_position_embeddings = text_encoder_config ["max_position_embeddings" ],
223+ dtype = self .config .weights_dtype ,
224+ )
225+ # The norms declare float32 scales, so the abstract params already carry the
226+ # right dtypes for both the loader and the Orbax restore target.
227+ return _nnx_component (
228+ lambda rngs : NNXFlaxQwen3Model (rngs = rngs , config = qwen3_config ),
229+ self .mesh ,
230+ self ._text_encoder_axis_rules (),
231+ self .config .seed ,
217232 )
218- ids = jnp .ones ((1 , 8 ), jnp .int32 )
219- return model , * _component (model , (ids , ids ), self .mesh , self ._text_encoder_axis_rules ())
220233
221234 def _vae_axis_rules (self ):
222235 """`vae_logical_axis_rules` if the config declares them, as WAN does."""
@@ -248,18 +261,18 @@ def load_diffusers_pipeline(self) -> ZImagePipeline:
248261 )
249262 vae_params = self ._place (vae_params , vae_shardings )
250263
251- text_encoder , shapes , text_encoder_shardings = self .build_text_encoder (
264+ graphdef , state , rest , shardings = self .build_text_encoder (
252265 AutoConfig .from_pretrained (model_id , subfolder = "text_encoder" ).to_dict ()
253266 )
254- text_encoder_params = load_qwen3_weights (_text_encoder_dir (model_id ), shapes , target_shardings = text_encoder_shardings )
267+ params = load_qwen3_weights (_text_encoder_dir (model_id ), state .to_pure_dict (), target_shardings = shardings )
268+ text_encoder = _fill (graphdef , state , rest , params )
255269
256270 return self ._assemble (
257271 transformer ,
258272 vae ,
259273 vae_params ,
260274 AutoTokenizer .from_pretrained (model_id , subfolder = "tokenizer" , use_fast = True ),
261275 text_encoder ,
262- text_encoder_params ,
263276 raw_vae_config .get ("shift_factor" , _DEFAULT_VAE_SHIFT_FACTOR ),
264277 )
265278
@@ -278,9 +291,14 @@ def _restore_pipeline(self, orbax_dir: str) -> Optional[ZImagePipeline]:
278291 return None
279292
280293 max_logging .log (f"Loading Z-Image pipeline from orbax checkpoint step { step } in { orbax_dir } " )
281- graphdef , state , rest , shardings = _transformer_component (metadata ["transformer_config" ], self .config , self .mesh )
294+ graphdef , state , rest , shardings = _nnx_component (
295+ _transformer_factory (metadata ["transformer_config" ], self .config , self .mesh ),
296+ self .mesh ,
297+ self .config .logical_axis_rules ,
298+ self .config .seed ,
299+ )
282300 vae , vae_shapes , vae_shardings = self .build_vae (metadata ["vae_config" ])
283- text_encoder , text_shapes , text_shardings = self .build_text_encoder (metadata ["text_encoder_config" ])
301+ text_graphdef , text_state , text_rest , text_shardings = self .build_text_encoder (metadata ["text_encoder_config" ])
284302
285303 # Shapes come from the models themselves, so every item lands directly in
286304 # the sharding its component was built for -- no host staging.
@@ -290,7 +308,7 @@ def _restore_pipeline(self, orbax_dir: str) -> Optional[ZImagePipeline]:
290308 ** {
291309 "transformer_state" : ocp .args .StandardRestore (_abstract_tree (state .to_pure_dict (), shardings )),
292310 "vae_state" : ocp .args .StandardRestore (_abstract_tree (vae_shapes , vae_shardings )),
293- "text_encoder_state" : ocp .args .StandardRestore (_abstract_tree (text_shapes , text_shardings )),
311+ "text_encoder_state" : ocp .args .StandardRestore (_abstract_tree (text_state . to_pure_dict () , text_shardings )),
294312 }
295313 ),
296314 )
@@ -300,22 +318,20 @@ def _restore_pipeline(self, orbax_dir: str) -> Optional[ZImagePipeline]:
300318 vae ,
301319 restored ["vae_state" ],
302320 self ._load_tokenizer (orbax_dir ),
303- text_encoder ,
304- restored ["text_encoder_state" ],
321+ _fill (text_graphdef , text_state , text_rest , restored ["text_encoder_state" ]),
305322 metadata ["vae_shift_factor" ],
306323 )
307324 except Exception as e : # pylint: disable=broad-except
308325 max_logging .log (f"Failed to load orbax checkpoint from { orbax_dir } , falling back to Diffusers: { e } " )
309326 return None
310327
311- def _assemble (self , transformer , vae , vae_params , tokenizer , text_encoder , text_encoder_params , vae_shift_factor ):
328+ def _assemble (self , transformer , vae , vae_params , tokenizer , text_encoder , vae_shift_factor ):
312329 return ZImagePipeline (
313330 transformer ,
314331 vae ,
315332 vae_params ,
316333 tokenizer ,
317334 text_encoder ,
318- text_encoder_params ,
319335 dtype = self .config .activations_dtype ,
320336 mesh = self .mesh ,
321337 logical_axis_rules = self .config .logical_axis_rules ,
@@ -370,7 +386,9 @@ def _save_pipeline(self, orbax_dir: str, pipeline: ZImagePipeline):
370386 ** {
371387 "transformer_state" : ocp .args .StandardSave (transformer_state .to_pure_dict ()),
372388 "vae_state" : ocp .args .StandardSave (_plain (pipeline .vae_params )),
373- "text_encoder_state" : ocp .args .StandardSave (_plain (pipeline .text_encoder_params )),
389+ "text_encoder_state" : ocp .args .StandardSave (
390+ nnx .split (pipeline .text_encoder , nnx .Param , ...)[1 ].to_pure_dict ()
391+ ),
374392 CONFIG_ITEM : ocp .args .JsonSave (json .loads (json .dumps (metadata , default = str ))),
375393 }
376394 ),
0 commit comments