Skip to content

Commit 64d2976

Browse files
committed
Optimize WAN pipeline: hoist timesteps, batched text encoding, SenCache to jax.lax.cond, I2V concat/transpose churn reduction, replace prints with max_logging
1 parent 5e884cf commit 64d2976

8 files changed

Lines changed: 219 additions & 128 deletions

File tree

src/maxdiffusion/loaders/ltx2_lora_nnx_loader.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ def translate_fn(nnx_path_str):
7676
# the merge_fn warns about unmatched keys in each dict, so we only warn about any leftovers
7777
unmatched_keys = set(h_state_dict) - set(transformer_state_dict) - set(connector_state_dict)
7878
if unmatched_keys:
79-
max_logging.log(
80-
f"{len(unmatched_keys)} key(s) in LoRA dictionary routed to no merge target: {unmatched_keys}"
81-
)
79+
max_logging.log(f"{len(unmatched_keys)} key(s) in LoRA dictionary routed to no merge target: {unmatched_keys}")
8280

8381
return pipeline

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: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,8 @@ def _prepare_model_inputs_i2v(
997997

998998
prompt_embeds = jax.device_put(prompt_embeds, data_sharding)
999999
negative_prompt_embeds = jax.device_put(negative_prompt_embeds, data_sharding)
1000-
image_embeds = jax.device_put(image_embeds, data_sharding)
1000+
if image_embeds is not None:
1001+
image_embeds = jax.device_put(image_embeds, data_sharding)
10011002

10021003
return prompt_embeds, negative_prompt_embeds, image_embeds, effective_batch_size
10031004

@@ -1332,7 +1333,8 @@ def magcache_step(
13321333
skip_warmup: Warmup steps threshold.
13331334
use_magcache: Optional manual override boolean to enable/disable cache for this step.
13341335
"""
1335-
import numpy as np
1336+
import jax.numpy as jnp
1337+
import jax
13361338

13371339
(
13381340
accumulated_ratio_cond,
@@ -1347,38 +1349,97 @@ def magcache_step(
13471349
cur_mag_ratio_uncond = mag_ratios[step * 2 + 1]
13481350

13491351
if use_magcache is None:
1350-
use_magcache = True
1351-
if step < skip_warmup:
1352-
use_magcache = False
1352+
use_magcache = step >= skip_warmup
13531353

1354-
skip_blocks = False
1355-
if use_magcache:
1354+
def compute_magcache_hit(args):
1355+
(
1356+
accumulated_ratio_cond,
1357+
accumulated_ratio_uncond,
1358+
accumulated_err_cond,
1359+
accumulated_err_uncond,
1360+
accumulated_steps_cond,
1361+
accumulated_steps_uncond,
1362+
cur_mag_ratio_cond,
1363+
cur_mag_ratio_uncond,
1364+
) = args
13561365
new_ratio_cond = accumulated_ratio_cond * cur_mag_ratio_cond
13571366
new_ratio_uncond = accumulated_ratio_uncond * cur_mag_ratio_uncond
13581367

1359-
err_cond = np.abs(1.0 - new_ratio_cond)
1360-
err_uncond = np.abs(1.0 - new_ratio_uncond)
1361-
1362-
if (
1363-
accumulated_err_cond + err_cond < magcache_thresh
1364-
and accumulated_steps_cond < magcache_K
1365-
and accumulated_err_uncond + err_uncond < magcache_thresh
1366-
and accumulated_steps_uncond < magcache_K
1367-
):
1368-
skip_blocks = True
1369-
accumulated_ratio_cond = new_ratio_cond
1370-
accumulated_ratio_uncond = new_ratio_uncond
1371-
accumulated_err_cond += err_cond
1372-
accumulated_err_uncond += err_uncond
1373-
accumulated_steps_cond += 1
1374-
accumulated_steps_uncond += 1
1375-
else:
1376-
accumulated_ratio_cond = 1.0
1377-
accumulated_ratio_uncond = 1.0
1378-
accumulated_err_cond = 0.0
1379-
accumulated_err_uncond = 0.0
1380-
accumulated_steps_cond = 0
1381-
accumulated_steps_uncond = 0
1368+
err_cond = jnp.abs(1.0 - new_ratio_cond)
1369+
err_uncond = jnp.abs(1.0 - new_ratio_uncond)
1370+
1371+
cond = jnp.logical_and(accumulated_err_cond + err_cond < magcache_thresh, accumulated_steps_cond < magcache_K)
1372+
cond = jnp.logical_and(cond, accumulated_err_uncond + err_uncond < magcache_thresh)
1373+
cond = jnp.logical_and(cond, accumulated_steps_uncond < magcache_K)
1374+
1375+
def hit_fn(_):
1376+
return (
1377+
True,
1378+
new_ratio_cond,
1379+
new_ratio_uncond,
1380+
accumulated_err_cond + err_cond,
1381+
accumulated_err_uncond + err_uncond,
1382+
accumulated_steps_cond + 1,
1383+
accumulated_steps_uncond + 1,
1384+
)
1385+
1386+
def miss_fn(_):
1387+
return (
1388+
False,
1389+
jnp.array(1.0, dtype=jnp.float32),
1390+
jnp.array(1.0, dtype=jnp.float32),
1391+
jnp.array(0.0, dtype=jnp.float32),
1392+
jnp.array(0.0, dtype=jnp.float32),
1393+
jnp.array(0, dtype=jnp.int32),
1394+
jnp.array(0, dtype=jnp.int32),
1395+
)
1396+
1397+
return jax.lax.cond(cond, hit_fn, miss_fn, None)
1398+
1399+
def skip_magcache(args):
1400+
(
1401+
accumulated_ratio_cond,
1402+
accumulated_ratio_uncond,
1403+
accumulated_err_cond,
1404+
accumulated_err_uncond,
1405+
accumulated_steps_cond,
1406+
accumulated_steps_uncond,
1407+
_,
1408+
_,
1409+
) = args
1410+
return (
1411+
False,
1412+
accumulated_ratio_cond,
1413+
accumulated_ratio_uncond,
1414+
accumulated_err_cond,
1415+
accumulated_err_uncond,
1416+
accumulated_steps_cond,
1417+
accumulated_steps_uncond,
1418+
)
1419+
1420+
(
1421+
skip_blocks,
1422+
accumulated_ratio_cond,
1423+
accumulated_ratio_uncond,
1424+
accumulated_err_cond,
1425+
accumulated_err_uncond,
1426+
accumulated_steps_cond,
1427+
accumulated_steps_uncond,
1428+
) = jax.lax.cond(
1429+
use_magcache,
1430+
compute_magcache_hit,
1431+
skip_magcache,
1432+
(
1433+
accumulated_ratio_cond,
1434+
accumulated_ratio_uncond,
1435+
accumulated_err_cond,
1436+
accumulated_err_uncond,
1437+
accumulated_steps_cond,
1438+
accumulated_steps_uncond,
1439+
cur_mag_ratio_cond,
1440+
cur_mag_ratio_uncond,
1441+
),
1442+
)
13821443

13831444
new_state = (
13841445
accumulated_ratio_cond,

src/maxdiffusion/pipelines/wan/wan_pipeline_2_1.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def __call__(
101101
magcache_K: Optional[int] = None,
102102
retention_ratio: Optional[float] = None,
103103
use_kv_cache: bool = False,
104+
output_type: str = "pil",
104105
):
105106
config = getattr(self, "config", None)
106107
if max_sequence_length is None:
@@ -170,6 +171,9 @@ def __call__(
170171
latents.block_until_ready()
171172
trace["denoise_total"] = time.perf_counter() - t_denoise_start
172173

174+
if output_type == "latent":
175+
return latents, trace
176+
173177
t_decode_start = time.perf_counter()
174178
video = self._decode_latents_to_video(latents, trace=trace)
175179
if hasattr(video, "block_until_ready"):
@@ -306,10 +310,9 @@ def run_inference_2_1(
306310
)
307311

308312
scan_diffusion_loop = getattr(config, "scan_diffusion_loop", False) if config else False
313+
timesteps = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)
309314

310315
if scan_diffusion_loop and not use_magcache and not use_cfg_cache:
311-
timesteps = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)
312-
313316
scheduler_state = scheduler_state.replace(last_sample=jnp.zeros_like(latents), step_index=jnp.array(0, dtype=jnp.int32))
314317

315318
def scan_body(carry, t):
@@ -365,7 +368,7 @@ def scan_body(carry, t):
365368
profiler = max_utils.Profiler(config)
366369
profiler.start()
367370

368-
t = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)[step]
371+
t = timesteps[step]
369372

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

src/maxdiffusion/pipelines/wan/wan_pipeline_2_2.py

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def __call__(
118118
use_cfg_cache: bool = False,
119119
use_sen_cache: bool = False,
120120
use_kv_cache: bool = False,
121+
output_type: str = "pil",
121122
):
122123
config = getattr(self, "config", None)
123124
if max_sequence_length is None:
@@ -203,6 +204,9 @@ def __call__(
203204
latents.block_until_ready()
204205
trace["denoise_total"] = time.perf_counter() - t_denoise_start
205206

207+
if output_type == "latent":
208+
return latents, trace
209+
206210
t_decode_start = time.perf_counter()
207211
video = self._decode_latents_to_video(latents, trace=trace)
208212
if hasattr(video, "block_until_ready"):
@@ -279,6 +283,8 @@ def run_inference_2_2(
279283
high_transformer = nnx.merge(high_noise_graphdef, high_noise_state, high_noise_rest)
280284
kv_cache_high, encoder_attention_mask_high = high_transformer.compute_kv_cache(prompt_embeds_combined)
281285

286+
timesteps = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)
287+
282288
# ── SenCache path (arXiv:2602.24208) ──
283289
if use_sen_cache and do_classifier_free_guidance:
284290
timesteps_np = np.array(scheduler_state.timesteps, dtype=np.int32)
@@ -303,16 +309,18 @@ def run_inference_2_2(
303309
num_train_timesteps = float(scheduler.config.num_train_timesteps)
304310

305311
# SenCache state
306-
ref_noise_pred = None # y^r: cached denoiser output
307-
ref_latent = None # x^r: latent at last cache refresh
308-
ref_timestep = 0.0 # t^r: timestep (normalized to [0,1]) at last cache refresh
309-
accum_dx = 0.0 # accumulated ||Δx|| since last refresh
310-
accum_dt = 0.0 # accumulated |Δt| since last refresh
311-
reuse_count = 0 # consecutive cache reuses
312-
cache_count = 0
312+
ref_noise_pred = jnp.zeros(
313+
(bsz * 2, latents.shape[1], latents.shape[2], latents.shape[3], latents.shape[4]), dtype=latents.dtype
314+
)
315+
ref_latent = jnp.zeros_like(latents)
316+
ref_timestep = jnp.array(0.0, dtype=jnp.float32)
317+
accum_dx = jnp.array(0.0, dtype=jnp.float32)
318+
accum_dt = jnp.array(0.0, dtype=jnp.float32)
319+
reuse_count = jnp.array(0, dtype=jnp.int32)
320+
cache_count = jnp.array(0, dtype=jnp.int32)
313321

314322
for step in range(num_inference_steps):
315-
t = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)[step]
323+
t = timesteps[step]
316324
t_float = float(timesteps_np[step]) / num_train_timesteps # normalize to [0, 1]
317325

318326
# Select transformer and guidance scale
@@ -358,29 +366,30 @@ def run_inference_2_2(
358366
)
359367
ref_noise_pred = noise_pred
360368
ref_latent = latents
361-
ref_timestep = t_float
362-
accum_dx = 0.0
363-
accum_dt = 0.0
364-
reuse_count = 0
369+
ref_timestep = jnp.array(t_float, dtype=jnp.float32)
370+
accum_dx = jnp.array(0.0, dtype=jnp.float32)
371+
accum_dt = jnp.array(0.0, dtype=jnp.float32)
372+
reuse_count = jnp.array(0, dtype=jnp.int32)
365373
latents, scheduler_state = scheduler.step(scheduler_state, noise_pred, t, latents).to_tuple()
366374
continue
367375

368376
# Accumulate deltas since last full compute
369-
dx_norm = float(jnp.sqrt(jnp.mean((latents - ref_latent) ** 2)))
370-
dt = abs(t_float - ref_timestep)
377+
dx_norm = jnp.sqrt(jnp.mean((latents - ref_latent) ** 2))
378+
dt = jnp.abs(t_float - ref_timestep)
371379
accum_dx += dx_norm
372380
accum_dt += dt
373381

374382
# Sensitivity score (Eq. 9)
375383
score = alpha_x * accum_dx + alpha_t * accum_dt
376384

377-
if score <= sen_epsilon and reuse_count < max_reuse:
378-
# Cache hit: reuse previous output
379-
noise_pred = ref_noise_pred
380-
reuse_count += 1
381-
cache_count += 1
382-
else:
383-
# Cache miss: full CFG forward pass
385+
cond = jnp.logical_and(score <= sen_epsilon, reuse_count < max_reuse)
386+
387+
def hit_fn(args):
388+
ref_noise_pred, ref_latent, ref_timestep, accum_dx, accum_dt, reuse_count, cache_count = args
389+
return ref_noise_pred, ref_latent, ref_timestep, accum_dx, accum_dt, reuse_count + 1, cache_count + 1
390+
391+
def miss_fn(args):
392+
_, _, _, _, _, _, cache_count = args
384393
latents_doubled = jnp.concatenate([latents] * 2)
385394
timestep = jnp.broadcast_to(t, bsz * 2)
386395
noise_pred, _, _ = transformer_forward_pass_full_cfg(
@@ -395,12 +404,19 @@ def run_inference_2_2(
395404
rotary_emb=rotary_emb,
396405
encoder_attention_mask=encoder_attention_mask,
397406
)
398-
ref_noise_pred = noise_pred
399-
ref_latent = latents
400-
ref_timestep = t_float
401-
accum_dx = 0.0
402-
accum_dt = 0.0
403-
reuse_count = 0
407+
return (
408+
noise_pred,
409+
latents,
410+
jnp.array(t_float, dtype=jnp.float32),
411+
jnp.array(0.0, dtype=jnp.float32),
412+
jnp.array(0.0, dtype=jnp.float32),
413+
jnp.array(0, dtype=jnp.int32),
414+
cache_count,
415+
)
416+
417+
noise_pred, ref_latent, ref_timestep, accum_dx, accum_dt, reuse_count, cache_count = jax.lax.cond(
418+
cond, hit_fn, miss_fn, (ref_noise_pred, ref_latent, ref_timestep, accum_dx, accum_dt, reuse_count, cache_count)
419+
)
404420

405421
latents, scheduler_state = scheduler.step(scheduler_state, noise_pred, t, latents).to_tuple()
406422

@@ -470,7 +486,7 @@ def run_inference_2_2(
470486
cached_noise_uncond = None
471487

472488
for step in range(num_inference_steps):
473-
t = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)[step]
489+
t = timesteps[step]
474490
is_cache_step = step_is_cache[step]
475491

476492
# Select transformer and guidance scale based on precomputed schedule
@@ -607,8 +623,6 @@ def low_noise_branch(operands):
607623
)
608624

609625
if scan_diffusion_loop:
610-
timesteps = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)
611-
612626
scheduler_state = scheduler_state.replace(last_sample=jnp.zeros_like(latents), step_index=jnp.array(0, dtype=jnp.int32))
613627

614628
def scan_body(carry, t):
@@ -657,7 +671,7 @@ def scan_body(carry, t):
657671
profiler = max_utils.Profiler(config)
658672
profiler.start()
659673

660-
t = jnp.array(scheduler_state.timesteps, dtype=jnp.int32)[step]
674+
t = timesteps[step]
661675

662676
if step_uses_high[step]:
663677
graphdef, state, rest = (

0 commit comments

Comments
 (0)