Skip to content

Commit 5dc1b5e

Browse files
committed
Optimize WAN pipelines and address review feedback
1 parent ed9ac9d commit 5dc1b5e

7 files changed

Lines changed: 132 additions & 76 deletions

File tree

src/maxdiffusion/models/quantizations.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import jax.numpy as jnp
2626
from jax.tree_util import tree_flatten_with_path, tree_unflatten
2727
from typing import Tuple, Sequence
28+
from maxdiffusion import max_logging
2829

2930
# Params used to define mixed precision quantization configs
3031
DEFAULT = "__default__" # default config
@@ -139,7 +140,7 @@ def _get_quant_config(config):
139140
else:
140141
drhs_bits = 8
141142
drhs_accumulator_dtype = jnp.int32
142-
print(config.quantization_local_shard_count) # -1
143+
max_logging.log(config.quantization_local_shard_count) # -1
143144
drhs_local_aqt = aqt_config.LocalAqt(contraction_axis_shard_count=config.quantization_local_shard_count)
144145
return aqt_config.config_v4(
145146
fwd_bits=8,

src/maxdiffusion/pipelines/wan/wan_pipeline.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1212,7 +1212,8 @@ def _prepare_model_inputs_i2v(
12121212

12131213
prompt_embeds = jax.device_put(prompt_embeds, data_sharding)
12141214
negative_prompt_embeds = jax.device_put(negative_prompt_embeds, data_sharding)
1215-
image_embeds = jax.device_put(image_embeds, data_sharding)
1215+
if image_embeds is not None:
1216+
image_embeds = jax.device_put(image_embeds, data_sharding)
12161217

12171218
return prompt_embeds, negative_prompt_embeds, image_embeds, effective_batch_size
12181219

src/maxdiffusion/pipelines/wan/wan_pipeline_2_1.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import numpy as np
3535
import time
3636
from ... import max_utils
37+
from maxdiffusion import max_logging
3738

3839

3940
class WanPipeline2_1(WanPipeline):
@@ -125,8 +126,12 @@ def __call__(
125126
magcache_K: Optional[int] = None,
126127
retention_ratio: Optional[float] = None,
127128
use_kv_cache: bool = False,
129+
output_type: str = "np",
128130
):
129131
config = getattr(self, "config", None)
132+
if output_type not in ["np", "latent"]:
133+
raise ValueError(f"output_type must be one of ['np', 'latent'], got {output_type}")
134+
130135
if max_sequence_length is None:
131136
max_sequence_length = getattr(config, "max_sequence_length", 512)
132137
if magcache_thresh is None:
@@ -194,6 +199,9 @@ def __call__(
194199
latents.block_until_ready()
195200
trace["denoise_total"] = time.perf_counter() - t_denoise_start
196201

202+
if output_type == "latent":
203+
return latents, trace
204+
197205
t_decode_start = time.perf_counter()
198206
video = self._decode_latents_to_video(latents, trace=trace)
199207
if hasattr(video, "block_until_ready"):
@@ -246,6 +254,18 @@ def run_inference_2_1(
246254
do_cfg = guidance_scale > 1.0
247255
bsz = latents.shape[0]
248256

257+
data_shards = 1
258+
try:
259+
if hasattr(latents, "sharding") and hasattr(latents.sharding, "mesh"):
260+
data_shards = latents.sharding.mesh.shape["data"] * latents.sharding.mesh.shape.get("fsdp", 1)
261+
except Exception:
262+
pass
263+
264+
if use_cfg_cache and do_cfg and bsz % data_shards != 0:
265+
max_logging.log(
266+
f"Warning: Disabling CFG cache because batch size {bsz} is not divisible by data shards {data_shards}. This often happens with data_parallelism > 1 and per_device_batch_size = 1."
267+
)
268+
use_cfg_cache = False
249269
# Resolution-dependent CFG cache config (FasterCache / MixCache guidance)
250270
if height >= 720:
251271
# 720p: conservative — protect last 40%, interval=5
@@ -330,10 +350,9 @@ def run_inference_2_1(
330350
)
331351

332352
scan_diffusion_loop = getattr(config, "scan_diffusion_loop", False) if config else False
353+
timesteps = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)
333354

334355
if scan_diffusion_loop and not use_magcache and not use_cfg_cache:
335-
timesteps = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)
336-
337356
scheduler_state = scheduler_state.replace(last_sample=jnp.zeros_like(latents), step_index=jnp.array(0, dtype=jnp.int32))
338357

339358
def scan_body(carry, t):
@@ -389,7 +408,7 @@ def scan_body(carry, t):
389408
profiler = max_utils.Profiler(config)
390409
profiler.start()
391410

392-
t = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)[step]
411+
t = timesteps[step]
393412

394413
if use_magcache and do_cfg:
395414
timestep = jnp.broadcast_to(t, bsz * 2 if do_cfg else bsz)

src/maxdiffusion/pipelines/wan/wan_pipeline_2_2.py

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,12 @@ def __call__(
158158
magcache_thresh: float = 0.04,
159159
magcache_K: int = 2,
160160
retention_ratio: float = 0.2,
161+
output_type: str = "np",
161162
):
162163
config = getattr(self, "config", None)
164+
if output_type not in ["np", "latent"]:
165+
raise ValueError(f"output_type must be one of ['np', 'latent'], got {output_type}")
166+
163167
if max_sequence_length is None:
164168
max_sequence_length = getattr(config, "max_sequence_length", 512)
165169

@@ -254,6 +258,9 @@ def __call__(
254258
latents.block_until_ready()
255259
trace["denoise_total"] = time.perf_counter() - t_denoise_start
256260

261+
if output_type == "latent":
262+
return latents, trace
263+
257264
t_decode_start = time.perf_counter()
258265
video = self._decode_latents_to_video(latents, trace=trace)
259266
if hasattr(video, "block_until_ready"):
@@ -307,6 +314,19 @@ def run_inference_2_2(
307314
do_classifier_free_guidance = guidance_scale_low > 1.0 or guidance_scale_high > 1.0
308315
bsz = latents.shape[0]
309316

317+
data_shards = 1
318+
try:
319+
if hasattr(latents, "sharding") and hasattr(latents.sharding, "mesh"):
320+
data_shards = latents.sharding.mesh.shape["data"] * latents.sharding.mesh.shape.get("fsdp", 1)
321+
except Exception:
322+
pass
323+
324+
if use_cfg_cache and do_classifier_free_guidance and bsz % data_shards != 0:
325+
max_logging.log(
326+
f"Warning: Disabling CFG cache because batch size {bsz} is not divisible by data shards {data_shards}. This often happens with data_parallelism > 1 and per_device_batch_size = 1."
327+
)
328+
use_cfg_cache = False
329+
310330
prompt_embeds_combined = (
311331
jnp.concatenate([prompt_embeds, negative_prompt_embeds], axis=0) if do_classifier_free_guidance else prompt_embeds
312332
)
@@ -333,7 +353,6 @@ def run_inference_2_2(
333353

334354
high_transformer = nnx.merge(high_noise_graphdef, high_noise_state, high_noise_rest)
335355
kv_cache_high, encoder_attention_mask_high = high_transformer.compute_kv_cache(prompt_embeds_combined)
336-
337356
# ── MagCache path (Ma et al., https://github.com/Zehong-Ma/MagCache) ──
338357
# Skips the transformer blocks on steps whose accumulated magnitude-ratio error
339358
# stays below `magcache_thresh`, reusing the cached block residual instead.
@@ -439,6 +458,8 @@ def run_inference_2_2(
439458
)
440459
return latents
441460

461+
timesteps = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)
462+
442463
# ── SenCache path (arXiv:2602.24208) ──
443464
if use_sen_cache and do_classifier_free_guidance:
444465
timesteps_np = np.array(scheduler_state.timesteps, dtype=np.int32)
@@ -463,16 +484,18 @@ def run_inference_2_2(
463484
num_train_timesteps = float(scheduler.config.num_train_timesteps)
464485

465486
# SenCache state
466-
ref_noise_pred = None # y^r: cached denoiser output
467-
ref_latent = None # x^r: latent at last cache refresh
468-
ref_timestep = 0.0 # t^r: timestep (normalized to [0,1]) at last cache refresh
469-
accum_dx = 0.0 # accumulated ||Δx|| since last refresh
470-
accum_dt = 0.0 # accumulated |Δt| since last refresh
471-
reuse_count = 0 # consecutive cache reuses
472-
cache_count = 0
487+
ref_noise_pred = jnp.zeros(
488+
(bsz * 2, latents.shape[1], latents.shape[2], latents.shape[3], latents.shape[4]), dtype=latents.dtype
489+
)
490+
ref_latent = jnp.zeros_like(latents)
491+
ref_timestep = jnp.array(0.0, dtype=jnp.float32)
492+
accum_dx = jnp.array(0.0, dtype=jnp.float32)
493+
accum_dt = jnp.array(0.0, dtype=jnp.float32)
494+
reuse_count = jnp.array(0, dtype=jnp.int32)
495+
cache_count = jnp.array(0, dtype=jnp.int32)
473496

474497
for step in range(num_inference_steps):
475-
t = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)[step]
498+
t = timesteps[step]
476499
t_float = float(timesteps_np[step]) / num_train_timesteps # normalize to [0, 1]
477500

478501
# Select transformer and guidance scale
@@ -518,10 +541,10 @@ def run_inference_2_2(
518541
)
519542
ref_noise_pred = noise_pred
520543
ref_latent = latents
521-
ref_timestep = t_float
522-
accum_dx = 0.0
523-
accum_dt = 0.0
524-
reuse_count = 0
544+
ref_timestep = jnp.array(t_float, dtype=jnp.float32)
545+
accum_dx = jnp.array(0.0, dtype=jnp.float32)
546+
accum_dt = jnp.array(0.0, dtype=jnp.float32)
547+
reuse_count = jnp.array(0, dtype=jnp.int32)
525548
latents, scheduler_state = scheduler.step(scheduler_state, noise_pred, t, latents).to_tuple()
526549
continue
527550

@@ -535,12 +558,10 @@ def run_inference_2_2(
535558
score = alpha_x * accum_dx + alpha_t * accum_dt
536559

537560
if score <= sen_epsilon and reuse_count < max_reuse:
538-
# Cache hit: reuse previous output
539561
noise_pred = ref_noise_pred
540562
reuse_count += 1
541563
cache_count += 1
542564
else:
543-
# Cache miss: full CFG forward pass
544565
latents_doubled = jnp.concatenate([latents] * 2)
545566
timestep = jnp.broadcast_to(t, bsz * 2)
546567
noise_pred, _, _ = transformer_forward_pass_full_cfg(
@@ -630,7 +651,7 @@ def run_inference_2_2(
630651
cached_noise_uncond = None
631652

632653
for step in range(num_inference_steps):
633-
t = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)[step]
654+
t = timesteps[step]
634655
is_cache_step = step_is_cache[step]
635656

636657
# Select transformer and guidance scale based on precomputed schedule
@@ -767,8 +788,6 @@ def low_noise_branch(operands):
767788
)
768789

769790
if scan_diffusion_loop:
770-
timesteps = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)
771-
772791
scheduler_state = scheduler_state.replace(last_sample=jnp.zeros_like(latents), step_index=jnp.array(0, dtype=jnp.int32))
773792

774793
def scan_body(carry, t):
@@ -817,7 +836,7 @@ def scan_body(carry, t):
817836
profiler = max_utils.Profiler(config)
818837
profiler.start()
819838

820-
t = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)[step]
839+
t = timesteps[step]
821840

822841
if step_uses_high[step]:
823842
graphdef, state, rest = (

src/maxdiffusion/pipelines/wan/wan_pipeline_i2v_2p1.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,17 @@ def __call__(
213213
last_image,
214214
)
215215

216-
def _process_image_input(img_input, height, width, num_videos_per_prompt):
216+
def _process_image_input(img_input, height, width):
217217
if img_input is None:
218218
return None
219219
tensor = self.video_processor.preprocess(img_input, height=height, width=width)
220220
jax_array = jnp.array(tensor.cpu().numpy())
221221
if jax_array.ndim == 3:
222222
jax_array = jax_array[None, ...] # Add batch dimension
223-
if num_videos_per_prompt > 1:
224-
jax_array = jnp.repeat(jax_array, num_videos_per_prompt, axis=0)
225223
return jax_array
226224

227-
image_tensor = _process_image_input(image, height, width, effective_batch_size)
228-
last_image_tensor = _process_image_input(last_image, height, width, effective_batch_size)
225+
image_tensor = _process_image_input(image, height, width)
226+
last_image_tensor = _process_image_input(last_image, height, width)
229227

230228
if rng is None:
231229
rng = jax.random.key(self.config.seed)
@@ -352,6 +350,8 @@ def run_inference_2_1_i2v(
352350
image_embeds_combined = image_embeds
353351
condition_combined = condition
354352

353+
condition_combined = jnp.transpose(condition_combined, (0, 4, 1, 2, 3))
354+
355355
transformer_obj = nnx.merge(graphdef, sharded_state, rest_of_state)
356356

357357
# Compute RoPE once as it only depends on shape
@@ -373,10 +373,9 @@ def run_inference_2_1_i2v(
373373
)
374374

375375
scan_diffusion_loop = getattr(config, "scan_diffusion_loop", False) if config else False
376+
timesteps = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)
376377

377378
if scan_diffusion_loop and not use_magcache:
378-
timesteps = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)
379-
380379
scheduler_state = scheduler_state.replace(last_sample=jnp.zeros_like(latents), step_index=jnp.array(0, dtype=jnp.int32))
381380

382381
def scan_body(carry, t):
@@ -386,9 +385,9 @@ def scan_body(carry, t):
386385
if do_cfg:
387386
latents_input = jnp.concatenate([current_latents, current_latents], axis=0)
388387

389-
latent_model_input = jnp.concatenate([latents_input, condition_combined], axis=-1)
388+
latents_input = jnp.transpose(latents_input, (0, 4, 1, 2, 3))
389+
latent_model_input = jnp.concatenate([latents_input, condition_combined], axis=1)
390390
timestep = jnp.broadcast_to(t, latents_input.shape[0])
391-
latent_model_input = jnp.transpose(latent_model_input, (0, 4, 1, 2, 3))
392391

393392
outputs = transformer_forward_pass(
394393
graphdef,
@@ -429,7 +428,7 @@ def scan_body(carry, t):
429428
profiler = max_utils.Profiler(config)
430429
profiler.start()
431430

432-
t = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)[step]
431+
t = timesteps[step]
433432

434433
skip_blocks = False
435434
if use_magcache and do_cfg:
@@ -446,9 +445,9 @@ def scan_body(carry, t):
446445
if do_cfg:
447446
latents_input = jnp.concatenate([latents, latents], axis=0)
448447

449-
latent_model_input = jnp.concatenate([latents_input, condition_combined], axis=-1)
448+
latents_input = jnp.transpose(latents_input, (0, 4, 1, 2, 3))
449+
latent_model_input = jnp.concatenate([latents_input, condition_combined], axis=1)
450450
timestep = jnp.broadcast_to(t, latents_input.shape[0])
451-
latent_model_input = jnp.transpose(latent_model_input, (0, 4, 1, 2, 3))
452451

453452
outputs = transformer_forward_pass(
454453
graphdef,

0 commit comments

Comments
 (0)