2525import numpy as np
2626from ...schedulers .scheduling_unipc_multistep_flax import FlaxUniPCMultistepScheduler
2727from ... import max_utils
28- from maxdiffusion import max_logging
2928
3029
3130class WanPipeline2_2 (WanPipeline ):
@@ -118,6 +117,7 @@ def __call__(
118117 use_cfg_cache : bool = False ,
119118 use_sen_cache : bool = False ,
120119 use_kv_cache : bool = False ,
120+ output_type : str = "pil" ,
121121 ):
122122 config = getattr (self , "config" , None )
123123 if max_sequence_length is None :
@@ -203,6 +203,9 @@ def __call__(
203203 latents .block_until_ready ()
204204 trace ["denoise_total" ] = time .perf_counter () - t_denoise_start
205205
206+ if output_type == "latent" :
207+ return latents , trace
208+
206209 t_decode_start = time .perf_counter ()
207210 video = self ._decode_latents_to_video (latents , trace = trace )
208211 if hasattr (video , "block_until_ready" ):
@@ -252,6 +255,22 @@ def run_inference_2_2(
252255 do_classifier_free_guidance = guidance_scale_low > 1.0 or guidance_scale_high > 1.0
253256 bsz = latents .shape [0 ]
254257
258+ data_shards = 1
259+ try :
260+ if hasattr (latents , "sharding" ) and hasattr (latents .sharding , "mesh" ):
261+ data_shards = latents .sharding .mesh .shape ["data" ] * latents .sharding .mesh .shape .get ("fsdp" , 1 )
262+ except Exception :
263+ pass
264+
265+ if use_cfg_cache and do_classifier_free_guidance and bsz % data_shards != 0 :
266+ from maxdiffusion .max_logging import MaxLogging
267+
268+ max_logging = MaxLogging ()
269+ max_logging .log (
270+ 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."
271+ )
272+ use_cfg_cache = False
273+
255274 prompt_embeds_combined = (
256275 jnp .concatenate ([prompt_embeds , negative_prompt_embeds ], axis = 0 ) if do_classifier_free_guidance else prompt_embeds
257276 )
@@ -279,6 +298,8 @@ def run_inference_2_2(
279298 high_transformer = nnx .merge (high_noise_graphdef , high_noise_state , high_noise_rest )
280299 kv_cache_high , encoder_attention_mask_high = high_transformer .compute_kv_cache (prompt_embeds_combined )
281300
301+ timesteps = jnp .array (scheduler_state .timesteps , dtype = jnp .int32 )
302+
282303 # ── SenCache path (arXiv:2602.24208) ──
283304 if use_sen_cache and do_classifier_free_guidance :
284305 timesteps_np = np .array (scheduler_state .timesteps , dtype = np .int32 )
@@ -303,16 +324,18 @@ def run_inference_2_2(
303324 num_train_timesteps = float (scheduler .config .num_train_timesteps )
304325
305326 # 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
327+ ref_noise_pred = jnp .zeros (
328+ (bsz * 2 , latents .shape [1 ], latents .shape [2 ], latents .shape [3 ], latents .shape [4 ]), dtype = latents .dtype
329+ )
330+ ref_latent = jnp .zeros_like (latents )
331+ ref_timestep = jnp .array (0.0 , dtype = jnp .float32 )
332+ accum_dx = jnp .array (0.0 , dtype = jnp .float32 )
333+ accum_dt = jnp .array (0.0 , dtype = jnp .float32 )
334+ reuse_count = jnp .array (0 , dtype = jnp .int32 )
335+ cache_count = jnp .array (0 , dtype = jnp .int32 )
313336
314337 for step in range (num_inference_steps ):
315- t = jnp . array ( scheduler_state . timesteps , dtype = jnp . int32 ) [step ]
338+ t = timesteps [step ]
316339 t_float = float (timesteps_np [step ]) / num_train_timesteps # normalize to [0, 1]
317340
318341 # Select transformer and guidance scale
@@ -358,10 +381,10 @@ def run_inference_2_2(
358381 )
359382 ref_noise_pred = noise_pred
360383 ref_latent = latents
361- ref_timestep = t_float
362- accum_dx = 0.0
363- accum_dt = 0.0
364- reuse_count = 0
384+ ref_timestep = jnp . array ( t_float , dtype = jnp . float32 )
385+ accum_dx = jnp . array ( 0.0 , dtype = jnp . float32 )
386+ accum_dt = jnp . array ( 0.0 , dtype = jnp . float32 )
387+ reuse_count = jnp . array ( 0 , dtype = jnp . int32 )
365388 latents , scheduler_state = scheduler .step (scheduler_state , noise_pred , t , latents ).to_tuple ()
366389 continue
367390
@@ -375,12 +398,10 @@ def run_inference_2_2(
375398 score = alpha_x * accum_dx + alpha_t * accum_dt
376399
377400 if score <= sen_epsilon and reuse_count < max_reuse :
378- # Cache hit: reuse previous output
379401 noise_pred = ref_noise_pred
380402 reuse_count += 1
381403 cache_count += 1
382404 else :
383- # Cache miss: full CFG forward pass
384405 latents_doubled = jnp .concatenate ([latents ] * 2 )
385406 timestep = jnp .broadcast_to (t , bsz * 2 )
386407 noise_pred , _ , _ = transformer_forward_pass_full_cfg (
@@ -470,7 +491,7 @@ def run_inference_2_2(
470491 cached_noise_uncond = None
471492
472493 for step in range (num_inference_steps ):
473- t = jnp . array ( scheduler_state . timesteps , dtype = jnp . int32 ) [step ]
494+ t = timesteps [step ]
474495 is_cache_step = step_is_cache [step ]
475496
476497 # Select transformer and guidance scale based on precomputed schedule
@@ -607,8 +628,6 @@ def low_noise_branch(operands):
607628 )
608629
609630 if scan_diffusion_loop :
610- timesteps = jnp .array (scheduler_state .timesteps , dtype = jnp .int32 )
611-
612631 scheduler_state = scheduler_state .replace (last_sample = jnp .zeros_like (latents ), step_index = jnp .array (0 , dtype = jnp .int32 ))
613632
614633 def scan_body (carry , t ):
@@ -657,7 +676,7 @@ def scan_body(carry, t):
657676 profiler = max_utils .Profiler (config )
658677 profiler .start ()
659678
660- t = jnp . array ( scheduler_state . timesteps , dtype = jnp . int32 ) [step ]
679+ t = timesteps [step ]
661680
662681 if step_uses_high [step ]:
663682 graphdef , state , rest = (
0 commit comments