Skip to content

Commit d067468

Browse files
committed
Implement text encoder offloading for Z-Image pipeline and resolve linter checks
1 parent 9d19ec6 commit d067468

7 files changed

Lines changed: 45 additions & 14 deletions

File tree

src/maxdiffusion/checkpointing/z_image_checkpointer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ def _assemble(self, transformer, vae, vae_params, tokenizer, text_encoder, vae_s
336336
mesh=self.mesh,
337337
logical_axis_rules=self.config.logical_axis_rules,
338338
vae_shift_factor=vae_shift_factor,
339+
offload_encoders=getattr(self.config, "offload_encoders", False),
339340
)
340341

341342
def _place(self, params, shardings):

src/maxdiffusion/configs/base_zimage.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ activations_dtype: 'bfloat16'
3434

3535
# Maximum sequence length for the text encoder
3636
max_sequence_length: 512
37+
# offloads text encoder after text encoding to save memory.
38+
offload_encoders: True
3739

3840
# Attention
3941
attention: 'flash' # Supported attention: dot_product, flash, tokamax_flash, cudnn_flash_te, ring, tokamax_ring, ulysses, ulysses_custom, ulysses_ring

src/maxdiffusion/configs/base_zimage_turbo.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ activations_dtype: 'bfloat16'
3333

3434
# Maximum sequence length for the text encoder
3535
max_sequence_length: 512
36+
# offloads text encoder after text encoding to save memory.
37+
offload_encoders: True
3638

3739
# Attention
3840
attention: 'flash' # Supported attention: dot_product, flash, tokamax_flash, cudnn_flash_te, ring, tokamax_ring, ulysses, ulysses_custom, ulysses_ring

src/maxdiffusion/models/z_image/transformer_z_image.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,6 @@ def __call__(
564564
if len(x) != len(cap_feats):
565565
raise ValueError("x and cap_feats must have one item per batch element.")
566566
adaln = self.t_embedder(t * self.t_scale).astype(x[0].dtype)
567-
batch = len(x)
568567

569568
# 1. Pad and patchify each element locally (on its device)
570569
caption_features, caption_positions, caption_masks = [], [], []

src/maxdiffusion/pipelines/z_image/z_image_pipeline.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def __init__(
7575
mesh=None,
7676
logical_axis_rules=(),
7777
vae_shift_factor: Optional[float] = None,
78+
offload_encoders: bool = False,
7879
):
7980
self.transformer = transformer
8081
self.vae = vae
@@ -96,6 +97,12 @@ def __init__(
9697
self._text_encoder_split = nnx.split(self.text_encoder, nnx.Param, ...)
9798
self._transformer_split = nnx.split(self.transformer, nnx.Param, ...)
9899

100+
self.offload_encoders = offload_encoders
101+
if self.offload_encoders:
102+
# Save the original shardings of the text encoder parameters
103+
graphdef, state, rest = self._text_encoder_split
104+
self._text_encoder_shardings = jax.tree_util.tree_map(lambda x: x.sharding, state)
105+
99106
def _decode_fn(self):
100107
"""Jitted VAE decode, including the latent denormalization."""
101108
if self._jitted_decoder is None:
@@ -127,6 +134,15 @@ def encode_prompt(self, prompt: Union[str, list[str]], max_sequence_length: int
127134
chats, padding="max_length", max_length=max_sequence_length, truncation=True, return_tensors="np"
128135
)
129136
graphdef, state, rest = self._text_encoder_split
137+
138+
if self.offload_encoders:
139+
# Restore parameters from CPU back to original device shardings
140+
state = jax.tree_util.tree_map(
141+
lambda x, sharding: jax.device_put(x, sharding),
142+
state,
143+
self._text_encoder_shardings
144+
)
145+
130146
with self.mesh if self.mesh is not None else nullcontext():
131147
embeddings = encode_step(
132148
graphdef,
@@ -136,6 +152,15 @@ def encode_prompt(self, prompt: Union[str, list[str]], max_sequence_length: int
136152
jnp.asarray(inputs["attention_mask"], jnp.int32),
137153
)
138154
lengths = np.asarray(inputs["attention_mask"]).sum(axis=-1)
155+
156+
if self.offload_encoders:
157+
from maxdiffusion import max_logging
158+
s_offload = time.perf_counter()
159+
cpus = jax.devices("cpu")
160+
offloaded_state = jax.tree_util.tree_map(lambda x: jax.device_put(x, device=cpus[0]), state)
161+
self._text_encoder_split = (graphdef, offloaded_state, rest)
162+
max_logging.log(f"Offloaded Qwen3 text encoder parameters to CPU in {(time.perf_counter() - s_offload):.4f}s.")
163+
139164
return [jnp.asarray(embeddings[index, : lengths[index]], dtype=self.dtype) for index in range(len(lengths))]
140165

141166
@staticmethod

src/maxdiffusion/pyconfig.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ def user_init(raw_keys):
204204
raw_keys["names_which_can_be_saved"] = []
205205
if "names_which_can_be_offloaded" not in raw_keys:
206206
raw_keys["names_which_can_be_offloaded"] = []
207+
if "offload_encoders" not in raw_keys:
208+
raw_keys["offload_encoders"] = False
207209

208210
raw_keys["weights_dtype"] = jax.numpy.dtype(raw_keys["weights_dtype"])
209211
raw_keys["activations_dtype"] = jax.numpy.dtype(raw_keys["activations_dtype"])

src/maxdiffusion/tests/z_image/z_image_module_parity_test.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -127,19 +127,19 @@ def test_transformer_block_parity(self):
127127
)
128128

129129
def test_full_transformer_parity(self):
130-
config = dict(
131-
all_patch_size=(2,),
132-
all_f_patch_size=(1,),
133-
in_channels=4,
134-
dim=32,
135-
n_layers=1,
136-
n_refiner_layers=1,
137-
n_heads=4,
138-
n_kv_heads=4,
139-
cap_feat_dim=8,
140-
axes_dims=[2, 2, 4],
141-
axes_lens=[64, 64, 64],
142-
)
130+
config = {
131+
"all_patch_size": (2,),
132+
"all_f_patch_size": (1,),
133+
"in_channels": 4,
134+
"dim": 32,
135+
"n_layers": 1,
136+
"n_refiner_layers": 1,
137+
"n_heads": 4,
138+
"n_kv_heads": 4,
139+
"cap_feat_dim": 8,
140+
"axes_dims": [2, 2, 4],
141+
"axes_lens": [64, 64, 64],
142+
}
143143
hf = HFZImageTransformer2DModel(**config).eval()
144144
local = copy_parameters(ZImageTransformer2DModel(rngs=self.rngs, attention_kernel="dot_product", **config), hf)
145145
images = [torch.randn(4, 1, 4, 4)]

0 commit comments

Comments
 (0)