dagloop5 commited on
Commit
65a7ea3
·
verified ·
1 Parent(s): d478bcc

Update h3_dpmpp_2s_ancestral.py

Browse files
Files changed (1) hide show
  1. h3_dpmpp_2s_ancestral.py +384 -18
h3_dpmpp_2s_ancestral.py CHANGED
@@ -1,21 +1,25 @@
1
- """A `dpmpp_2s_ancestral` denoise block for MiniMax-H3, replacing `MiniMaxH3LoopDenoiser` + `MiniMaxH3LoopSchedulerStep`.
2
-
3
- `dpmpp_2s_ancestral` is a two-evaluation-per-step sampler: it runs the real forward pass exactly as
4
- `MiniMaxH3LoopDenoiser` does, builds an intermediate sample partway to the next noise level, runs a *second*
5
- forward pass against it, and combines both predictions (plus fresh ancestral noise) into the step's real output.
6
- `scheduler.step()` never sees the second call nothing routed through it could reach `block_state.latents` (full,
7
- conditioning rows included), `row_timestep_plan`, or the indices `build_row_timesteps` needs. So this is one
8
- combined `ModularPipelineBlocks`, declared with those as real inputs, standing in for both original sub-blocks at
9
- once not a `scheduler.step()` monkeypatch like `euler_ancestral`/`er_sde`/the `dpmpp_*m_sde` pair.
10
-
11
- Ports k-diffusion's `sample_dpmpp_2s_ancestral_RF` (the flow-matching branch `sample_dpmpp_2s_ancestral` dispatches
12
- to for `CONST`-style model sampling MiniMax-H3's `[0, 1]` sigma space, same family `_euler_ancestral_step` and
13
- `_er_sde_step` already port) onto the video and audio streams independently, each in one packed transformer call
14
- per evaluation, since one forward always serves every modality's rows at once.
15
-
16
- Wired in via `use_dpmpp_2s_ancestral`, a context manager that temporarily swaps `pipe`'s live `denoise` sub-block
17
- for the request's duration and restores the original after the same enter/exit shape `use_schedule` already uses
18
- for `set_timesteps`/`step`, just swapping a block reference instead.
 
 
 
 
19
  """
20
 
21
  import inspect
@@ -30,6 +34,8 @@ from diffusers.modular_pipelines.modular_pipeline_utils import ComponentSpec, In
30
  from diffusers.models import MiniMaxH3Transformer3DModel
31
  from diffusers.schedulers import MiniMaxH3Scheduler
32
 
 
 
33
 
34
  def _dpmpp_2s_ancestral_prepare(x: torch.Tensor, sigma_i: torch.Tensor, sigma_ip1: torch.Tensor, denoised: torch.Tensor, eta: float = 1.0):
35
  """The half of one stream's `dpmpp_2s_ancestral_RF` step computable before the second model call: the
@@ -227,6 +233,334 @@ class MiniMaxH3DPMpp2SAncestralStep(ModularPipelineBlocks):
227
  return components, block_state
228
 
229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  class use_dpmpp_2s_ancestral:
231
  """Swaps `pipe`'s live `denoise` sub-block for `MiniMaxH3DPMpp2SAncestralStep` for one request, restoring the
232
  original block on exit. A block swap rather than a `scheduler.step()` patch, and so a separate context
@@ -265,3 +599,35 @@ class use_dpmpp_2s_ancestral:
265
  if self._core_denoise is not None and self._original is not None:
266
  self._core_denoise.sub_blocks["denoise"] = self._original
267
  return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Denoise blocks for MiniMax-H3's two-evaluation-per-step samplers, replacing `MiniMaxH3LoopDenoiser` +
2
+ `MiniMaxH3LoopSchedulerStep`: `dpmpp_2s_ancestral`, `dpmpp_sde_gpu`, and `seeds_2`.
3
+
4
+ Each of these runs the real forward pass exactly as `MiniMaxH3LoopDenoiser` does, builds an intermediate sample
5
+ partway to the next noise level, runs a *second* forward pass against it, and combines both predictions (plus
6
+ fresh noise, ancestral or Brownian-tree depending on the sampler) into the step's real output. `scheduler.step()`
7
+ never sees the second call nothing routed through it could reach `block_state.latents` (full, conditioning rows
8
+ included), `row_timestep_plan`, or the indices `build_row_timesteps` needs. So each is one combined
9
+ `ModularPipelineBlocks`, declared with those as real inputs, standing in for both original sub-blocks at once — not
10
+ a `scheduler.step()` monkeypatch like `euler_ancestral`/`er_sde`/the `dpmpp_*m_sde` pair.
11
+
12
+ Ports, one function pair per sampler: k-diffusion's `sample_dpmpp_2s_ancestral_RF` (the flow-matching branch
13
+ `sample_dpmpp_2s_ancestral` dispatches to for `CONST`-style model sampling MiniMax-H3's `[0, 1]` sigma space,
14
+ same family `_euler_ancestral_step`/`_er_sde_step` already port); `sample_dpmpp_sde`/`sample_dpmpp_sde_gpu`, written
15
+ generically against `CONST`'s half-log-SNR specialization rather than a hand-derived `_RF` branch, reused here via
16
+ `_lambda_const`/`_sigma_from_lambda_const`; and `sample_seeds_2`'s `phi_1` path, the same half-log-SNR family.
17
+ Each runs the video and audio streams independently, each still one packed transformer call per evaluation, since
18
+ one forward always serves every modality's rows at once.
19
+
20
+ Wired in via three context managers (`use_dpmpp_2s_ancestral`, `use_dpmpp_sde_gpu`, `use_seeds_2`) that temporarily
21
+ swap `pipe`'s live `denoise` sub-block for the request's duration and restore the original after — the same
22
+ enter/exit shape `use_schedule` already uses for `set_timesteps`/`step`, just swapping a block reference instead.
23
  """
24
 
25
  import inspect
 
34
  from diffusers.models import MiniMaxH3Transformer3DModel
35
  from diffusers.schedulers import MiniMaxH3Scheduler
36
 
37
+ from pk_workflow import _BrownianTreeNoiseSampler
38
+
39
 
40
  def _dpmpp_2s_ancestral_prepare(x: torch.Tensor, sigma_i: torch.Tensor, sigma_ip1: torch.Tensor, denoised: torch.Tensor, eta: float = 1.0):
41
  """The half of one stream's `dpmpp_2s_ancestral_RF` step computable before the second model call: the
 
233
  return components, block_state
234
 
235
 
236
+ def _nudge_first_sigma(sigma: torch.Tensor, shift: float) -> torch.Tensor:
237
+ """ComfyUI's `offset_first_sigma_for_snr`, `CONST` branch, for one already-known-to-be-`>= 1.0` sigma: nudges
238
+ it to `percent_to_sigma(1e-4)` — the same `0.9999`-below-1.0 value `_er_sde_step`/`_dpmpp_2m_sde_step` already
239
+ nudge to, re-derived here through the shift formula since `dpmpp_sde_gpu`/`seeds_2` need it as a tensor
240
+ they can keep differentiating through rather than a hardcoded constant.
241
+ """
242
+ base = sigma.new_tensor(1.0 - 1e-4)
243
+ return shift * base / (1 + (shift - 1) * base)
244
+
245
+
246
+ def _lambda_const(sigma: torch.Tensor) -> torch.Tensor:
247
+ """`sigma_to_half_log_snr`'s `CONST` branch: `log((1 - sigma) / sigma)`."""
248
+ return ((1 - sigma) / sigma).log()
249
+
250
+
251
+ def _sigma_from_lambda_const(half_log_snr: torch.Tensor) -> torch.Tensor:
252
+ """`half_log_snr_to_sigma`'s `CONST` branch: `1 / (1 + exp(half_log_snr))`."""
253
+ return (half_log_snr.exp() + 1) ** -1
254
+
255
+
256
+ def _get_ancestral_step(sigma_from: torch.Tensor, sigma_to: torch.Tensor, eta: float = 1.0):
257
+ """k-diffusion's `get_ancestral_step`, unmodified — operates on whatever space the caller hands it (each
258
+ sampler below calls it in `exp(-half_log_snr)` space, per its own source)."""
259
+ sigma_up = torch.minimum(sigma_to, eta * (sigma_to**2 * (sigma_from**2 - sigma_to**2) / sigma_from**2).clamp_min(0).sqrt())
260
+ sigma_down = (sigma_to**2 - sigma_up**2).clamp_min(0).sqrt()
261
+ return sigma_down, sigma_up
262
+
263
+
264
+ def _dpmpp_sde_gpu_prepare(x: torch.Tensor, sigma_i: torch.Tensor, sigma_ip1: torch.Tensor, denoised: torch.Tensor, shift: float, is_first_step: bool, noise_sampler):
265
+ """Step 1 of `sample_dpmpp_sde`'s `CONST` (half-log-SNR) path for one stream: the intermediate sigma/sample
266
+ to evaluate (`sigma_s_1`, `u`) plus what `_dpmpp_sde_gpu_combine` needs once the second call's `denoised_2`
267
+ is in hand. `eta = 1.0`, `r = 0.5` — the only values this port exposes, matching ComfyUI's own `dpmpp_sde_gpu`
268
+ registration. `noise_sampler` is the request-lifetime, per-stream `_BrownianTreeNoiseSampler` — both of a
269
+ step's noise draws start from the same `sigma_i`, which is what correlates them.
270
+ """
271
+ sigma_i_dpm = _nudge_first_sigma(sigma_i, shift) if is_first_step and float(sigma_i) >= 1.0 else sigma_i
272
+ lambda_s = _lambda_const(sigma_i_dpm)
273
+ lambda_t = _lambda_const(sigma_ip1)
274
+ h = lambda_t - lambda_s
275
+ lambda_s_1 = lambda_s + 0.5 * h
276
+ sigma_s_1 = _sigma_from_lambda_const(lambda_s_1)
277
+ alpha_s, alpha_s_1 = 1 - sigma_i_dpm, 1 - sigma_s_1
278
+
279
+ w_i, w_s1 = (-lambda_s).exp(), (-lambda_s_1).exp()
280
+ sd, su = _get_ancestral_step(w_i, w_s1)
281
+ h_ = -sd.log() - lambda_s
282
+ u = (alpha_s_1 / alpha_s) * (-h_).exp() * x - alpha_s_1 * torch.expm1(-h_) * denoised
283
+ u = u + alpha_s_1 * noise_sampler(sigma_i_dpm, sigma_s_1) * su
284
+ return u, sigma_s_1, sigma_i_dpm, lambda_s
285
+
286
+
287
+ def _dpmpp_sde_gpu_combine(noise_sampler, x: torch.Tensor, sigma_i_dpm: torch.Tensor, lambda_s: torch.Tensor, sigma_ip1: torch.Tensor, denoised_2: torch.Tensor):
288
+ """Step 2, once the second call's `denoised_2` is in hand. The `fac = 1` collapse at `r = 0.5` means the
289
+ combine uses `denoised_2` alone — the same simplification `dpmpp_2s_ancestral`'s port already relies on."""
290
+ lambda_t = _lambda_const(sigma_ip1)
291
+ alpha_s, alpha_t = 1 - sigma_i_dpm, 1 - sigma_ip1
292
+ w_i, w_t = (-lambda_s).exp(), (-lambda_t).exp()
293
+ sd, su = _get_ancestral_step(w_i, w_t)
294
+ h_ = -sd.log() - lambda_s
295
+ x = (alpha_t / alpha_s) * (-h_).exp() * x - alpha_t * torch.expm1(-h_) * denoised_2
296
+ x = x + alpha_t * noise_sampler(sigma_i_dpm, sigma_ip1) * su
297
+ return x
298
+
299
+
300
+ def _seeds2_prepare(x: torch.Tensor, sigma_i: torch.Tensor, sigma_ip1: torch.Tensor, denoised: torch.Tensor, shift: float, is_first_step: bool, generator):
301
+ """Step 1 of `sample_seeds_2`'s `phi_1` path (its registered default) for one stream. Unlike `dpmpp_sde_gpu`,
302
+ `seeds_2` draws fresh, independent Gaussian noise per call rather than a correlated Brownian-tree draw —
303
+ `default_noise_sampler` in the k-diffusion source ignores its sigma arguments entirely, so `generator` here is
304
+ just the request-lifetime per-stream `torch.Generator`, same pattern `euler_ancestral`/`er_sde` already use.
305
+ """
306
+ sigma_i_dpm = _nudge_first_sigma(sigma_i, shift) if is_first_step and float(sigma_i) >= 1.0 else sigma_i
307
+ lambda_s = _lambda_const(sigma_i_dpm)
308
+ lambda_t = _lambda_const(sigma_ip1)
309
+ h = lambda_t - lambda_s
310
+ lambda_s_1 = lambda_s + 0.5 * h
311
+ sigma_s_1 = _sigma_from_lambda_const(lambda_s_1)
312
+ alpha_s_1 = 1 - sigma_s_1
313
+
314
+ u = (sigma_s_1 / sigma_i_dpm) * (-0.5 * h).exp() * x - alpha_s_1 * torch.expm1(-h) * denoised
315
+ noise_1 = torch.randn(x.shape, dtype=x.dtype, device="cpu", generator=generator).to(x.device)
316
+ sde_noise = torch.sqrt(-torch.expm1(-h)) * noise_1
317
+ u = u + sde_noise * sigma_s_1
318
+ return u, sigma_s_1, sde_noise, h, sigma_i_dpm
319
+
320
+
321
+ def _seeds2_combine(generator, x: torch.Tensor, sigma_i_dpm: torch.Tensor, sigma_ip1: torch.Tensor, denoised_2: torch.Tensor, sde_noise: torch.Tensor, h: torch.Tensor):
322
+ """Step 2 of `sample_seeds_2`'s `phi_1` path, once `denoised_2` is in hand — including the second, independent
323
+ noise draw the source layers on top of the first (`sde_noise` carries the first draw's contribution forward,
324
+ scaled, rather than being discarded)."""
325
+ alpha_t = 1 - sigma_ip1
326
+ x = (sigma_ip1 / sigma_i_dpm) * (-h).exp() * x - alpha_t * torch.expm1(-2 * h) * denoised_2
327
+
328
+ segment_factor = -0.5 * h # (r - 1) * h * eta, r = 0.5, eta = 1.0
329
+ sde_noise = sde_noise * segment_factor.exp()
330
+ noise_2 = torch.randn(x.shape, dtype=x.dtype, device="cpu", generator=generator).to(x.device)
331
+ sde_noise = sde_noise + torch.sqrt(-torch.expm1(2 * segment_factor)) * noise_2
332
+ x = x + sde_noise * sigma_ip1
333
+ return x
334
+
335
+
336
+ class MiniMaxH3DPMppSdeStep(ModularPipelineBlocks):
337
+ """One `dpmpp_sde_gpu` iteration, same role as `MiniMaxH3DPMpp2SAncestralStep`: real forward pass, intermediate
338
+ forward pass, DPM-Solver++(SDE) combine with Brownian-tree-correlated noise."""
339
+
340
+ model_name = "minimax-h3"
341
+
342
+ def __init__(self, transformer_name: str = "transformer", seed: int = 0):
343
+ self.transformer_name = transformer_name
344
+ self.seed = int(seed)
345
+ self._video_noise_sampler = None
346
+ self._audio_noise_sampler = None
347
+ super().__init__()
348
+
349
+ @property
350
+ def description(self) -> str:
351
+ return (
352
+ "Runs one `dpmpp_sde_gpu` denoising iteration in place of `MiniMaxH3LoopDenoiser` + "
353
+ "`MiniMaxH3LoopSchedulerStep`: the real forward pass, a second forward pass at a constructed "
354
+ "intermediate sample/sigma, and the DPM-Solver++(SDE) combine with Brownian-tree noise."
355
+ )
356
+
357
+ @property
358
+ def expected_components(self) -> list[ComponentSpec]:
359
+ return [
360
+ ComponentSpec(self.transformer_name, MiniMaxH3Transformer3DModel),
361
+ ComponentSpec("scheduler", MiniMaxH3Scheduler),
362
+ ComponentSpec("audio_scheduler", MiniMaxH3Scheduler),
363
+ ]
364
+
365
+ inputs = MiniMaxH3DPMpp2SAncestralStep.inputs # same input contract as the dpmpp_2s_ancestral block
366
+
367
+ @property
368
+ def intermediate_outputs(self) -> list:
369
+ return []
370
+
371
+ _forward = MiniMaxH3DPMpp2SAncestralStep._forward
372
+
373
+ @torch.no_grad()
374
+ def __call__(self, components: MiniMaxH3ModularPipeline, block_state: BlockState, i: int, t: torch.Tensor):
375
+ ncv = block_state.num_condition_video_rows
376
+ nca = block_state.num_condition_audio_rows
377
+ is_last_step = i == len(block_state.row_timestep_plan) - 1
378
+ is_first_step = i == 0
379
+
380
+ unique_timesteps, timestep_indices = block_state.row_timestep_plan[i]
381
+ noise_pred, audio_noise_pred = self._forward(
382
+ components, block_state, block_state.latents, block_state.audio_latents, unique_timesteps, timestep_indices
383
+ )
384
+
385
+ video_timestep_i = float(t)
386
+
387
+ x_video = block_state.latents[ncv:]
388
+ x_audio = block_state.audio_latents[nca:]
389
+ compute_dtype = torch.float32 if x_video.dtype in (torch.float16, torch.bfloat16) else x_video.dtype
390
+
391
+ sigma_i_video = components.scheduler.sigmas[i].to(device=x_video.device, dtype=compute_dtype)
392
+ sigma_ip1_video = components.scheduler.sigmas[i + 1].to(device=x_video.device, dtype=compute_dtype)
393
+ sigma_i_audio = components.audio_scheduler.sigmas[i].to(device=x_audio.device, dtype=compute_dtype)
394
+ sigma_ip1_audio = components.audio_scheduler.sigmas[i + 1].to(device=x_audio.device, dtype=compute_dtype)
395
+
396
+ x_video_c = x_video.to(dtype=compute_dtype)
397
+ x_audio_c = x_audio.to(dtype=compute_dtype)
398
+ denoised_video = x_video_c + sigma_i_video * noise_pred[0, ncv:].to(dtype=compute_dtype)
399
+ denoised_audio = x_audio_c + sigma_i_audio * audio_noise_pred[0, nca:].to(dtype=compute_dtype)
400
+
401
+ if is_last_step:
402
+ new_video, new_audio = denoised_video, denoised_audio
403
+ else:
404
+ if self._video_noise_sampler is None:
405
+ # Lazily built on the first step, same lifetime as the request (this block instance is discarded
406
+ # after) — the real, un-nudged schedule bounds are the tree's span; per-step nudging happens
407
+ # only in the query sigmas passed to it, same convention `_dpmpp_2m_sde_step` already uses.
408
+ video_sigmas = components.scheduler.sigmas.to(device=x_video.device, dtype=compute_dtype)
409
+ audio_sigmas = components.audio_scheduler.sigmas.to(device=x_audio.device, dtype=compute_dtype)
410
+ self._video_noise_sampler = _BrownianTreeNoiseSampler(
411
+ x_video_c, video_sigmas[video_sigmas > 0].min(), video_sigmas.max(), seed=self.seed, cpu=False
412
+ )
413
+ self._audio_noise_sampler = _BrownianTreeNoiseSampler(
414
+ x_audio_c, audio_sigmas[audio_sigmas > 0].min(), audio_sigmas.max(), seed=self.seed + 1, cpu=False
415
+ )
416
+
417
+ u_video, sigma_s_video, sigma_i_dpm_video, lambda_s_video = _dpmpp_sde_gpu_prepare(
418
+ x_video_c, sigma_i_video, sigma_ip1_video, denoised_video, float(components.scheduler.shift), is_first_step, self._video_noise_sampler
419
+ )
420
+ u_audio, sigma_s_audio, sigma_i_dpm_audio, lambda_s_audio = _dpmpp_sde_gpu_prepare(
421
+ x_audio_c, sigma_i_audio, sigma_ip1_audio, denoised_audio, float(components.audio_scheduler.shift), is_first_step, self._audio_noise_sampler
422
+ )
423
+
424
+ latents_2 = block_state.latents.clone()
425
+ latents_2[ncv:] = u_video.to(dtype=block_state.latents.dtype)
426
+ audio_latents_2 = block_state.audio_latents.clone()
427
+ audio_latents_2[nca:] = u_audio.to(dtype=block_state.audio_latents.dtype)
428
+
429
+ unique_timesteps_2, timestep_indices_2 = tuple(
430
+ tensor.to(block_state.latents.device)
431
+ for tensor in MiniMaxH3SetTimestepsStep.build_row_timesteps(
432
+ block_state.video_indices,
433
+ block_state.audio_indices,
434
+ ncv,
435
+ nca,
436
+ block_state.text_indices.numel(),
437
+ 1.0 - float(sigma_s_video),
438
+ 1.0 - float(sigma_s_audio),
439
+ max(video_timestep_i, components.keyframe_noise_aug),
440
+ 1.0,
441
+ )
442
+ )
443
+ noise_pred_2, audio_noise_pred_2 = self._forward(
444
+ components, block_state, latents_2, audio_latents_2, unique_timesteps_2, timestep_indices_2
445
+ )
446
+ d_i_video = u_video + sigma_s_video * noise_pred_2[0, ncv:].to(dtype=compute_dtype)
447
+ d_i_audio = u_audio + sigma_s_audio * audio_noise_pred_2[0, nca:].to(dtype=compute_dtype)
448
+
449
+ new_video = _dpmpp_sde_gpu_combine(self._video_noise_sampler, x_video_c, sigma_i_dpm_video, lambda_s_video, sigma_ip1_video, d_i_video)
450
+ new_audio = _dpmpp_sde_gpu_combine(self._audio_noise_sampler, x_audio_c, sigma_i_dpm_audio, lambda_s_audio, sigma_ip1_audio, d_i_audio)
451
+
452
+ block_state.latents[ncv:] = new_video.to(dtype=block_state.latents.dtype)
453
+ block_state.audio_latents[nca:] = new_audio.to(dtype=block_state.audio_latents.dtype)
454
+ return components, block_state
455
+
456
+
457
+ class MiniMaxH3Seeds2Step(ModularPipelineBlocks):
458
+ """One `seeds_2` iteration, same role as `MiniMaxH3DPMpp2SAncestralStep`: real forward pass, intermediate
459
+ forward pass, SEEDS-2 (`phi_1`) combine with independent Gaussian noise."""
460
+
461
+ model_name = "minimax-h3"
462
+
463
+ def __init__(self, transformer_name: str = "transformer", video_generator=None, audio_generator=None):
464
+ self.transformer_name = transformer_name
465
+ self.video_generator = video_generator
466
+ self.audio_generator = audio_generator
467
+ super().__init__()
468
+
469
+ @property
470
+ def description(self) -> str:
471
+ return (
472
+ "Runs one `seeds_2` denoising iteration in place of `MiniMaxH3LoopDenoiser` + "
473
+ "`MiniMaxH3LoopSchedulerStep`: the real forward pass, a second forward pass at a constructed "
474
+ "intermediate sample/sigma, and the SEEDS-2 (`phi_1`) combine with independent Gaussian noise."
475
+ )
476
+
477
+ @property
478
+ def expected_components(self) -> list[ComponentSpec]:
479
+ return [
480
+ ComponentSpec(self.transformer_name, MiniMaxH3Transformer3DModel),
481
+ ComponentSpec("scheduler", MiniMaxH3Scheduler),
482
+ ComponentSpec("audio_scheduler", MiniMaxH3Scheduler),
483
+ ]
484
+
485
+ inputs = MiniMaxH3DPMpp2SAncestralStep.inputs # same input contract as the dpmpp_2s_ancestral block
486
+
487
+ @property
488
+ def intermediate_outputs(self) -> list:
489
+ return []
490
+
491
+ _forward = MiniMaxH3DPMpp2SAncestralStep._forward
492
+
493
+ @torch.no_grad()
494
+ def __call__(self, components: MiniMaxH3ModularPipeline, block_state: BlockState, i: int, t: torch.Tensor):
495
+ ncv = block_state.num_condition_video_rows
496
+ nca = block_state.num_condition_audio_rows
497
+ is_last_step = i == len(block_state.row_timestep_plan) - 1
498
+ is_first_step = i == 0
499
+
500
+ unique_timesteps, timestep_indices = block_state.row_timestep_plan[i]
501
+ noise_pred, audio_noise_pred = self._forward(
502
+ components, block_state, block_state.latents, block_state.audio_latents, unique_timesteps, timestep_indices
503
+ )
504
+
505
+ video_timestep_i = float(t)
506
+
507
+ x_video = block_state.latents[ncv:]
508
+ x_audio = block_state.audio_latents[nca:]
509
+ compute_dtype = torch.float32 if x_video.dtype in (torch.float16, torch.bfloat16) else x_video.dtype
510
+
511
+ sigma_i_video = components.scheduler.sigmas[i].to(device=x_video.device, dtype=compute_dtype)
512
+ sigma_ip1_video = components.scheduler.sigmas[i + 1].to(device=x_video.device, dtype=compute_dtype)
513
+ sigma_i_audio = components.audio_scheduler.sigmas[i].to(device=x_audio.device, dtype=compute_dtype)
514
+ sigma_ip1_audio = components.audio_scheduler.sigmas[i + 1].to(device=x_audio.device, dtype=compute_dtype)
515
+
516
+ x_video_c = x_video.to(dtype=compute_dtype)
517
+ x_audio_c = x_audio.to(dtype=compute_dtype)
518
+ denoised_video = x_video_c + sigma_i_video * noise_pred[0, ncv:].to(dtype=compute_dtype)
519
+ denoised_audio = x_audio_c + sigma_i_audio * audio_noise_pred[0, nca:].to(dtype=compute_dtype)
520
+
521
+ if is_last_step:
522
+ new_video, new_audio = denoised_video, denoised_audio
523
+ else:
524
+ u_video, sigma_s_video, sde_noise_video, h_video, sigma_i_dpm_video = _seeds2_prepare(
525
+ x_video_c, sigma_i_video, sigma_ip1_video, denoised_video, float(components.scheduler.shift), is_first_step, self.video_generator
526
+ )
527
+ u_audio, sigma_s_audio, sde_noise_audio, h_audio, sigma_i_dpm_audio = _seeds2_prepare(
528
+ x_audio_c, sigma_i_audio, sigma_ip1_audio, denoised_audio, float(components.audio_scheduler.shift), is_first_step, self.audio_generator
529
+ )
530
+
531
+ latents_2 = block_state.latents.clone()
532
+ latents_2[ncv:] = u_video.to(dtype=block_state.latents.dtype)
533
+ audio_latents_2 = block_state.audio_latents.clone()
534
+ audio_latents_2[nca:] = u_audio.to(dtype=block_state.audio_latents.dtype)
535
+
536
+ unique_timesteps_2, timestep_indices_2 = tuple(
537
+ tensor.to(block_state.latents.device)
538
+ for tensor in MiniMaxH3SetTimestepsStep.build_row_timesteps(
539
+ block_state.video_indices,
540
+ block_state.audio_indices,
541
+ ncv,
542
+ nca,
543
+ block_state.text_indices.numel(),
544
+ 1.0 - float(sigma_s_video),
545
+ 1.0 - float(sigma_s_audio),
546
+ max(video_timestep_i, components.keyframe_noise_aug),
547
+ 1.0,
548
+ )
549
+ )
550
+ noise_pred_2, audio_noise_pred_2 = self._forward(
551
+ components, block_state, latents_2, audio_latents_2, unique_timesteps_2, timestep_indices_2
552
+ )
553
+ d_i_video = u_video + sigma_s_video * noise_pred_2[0, ncv:].to(dtype=compute_dtype)
554
+ d_i_audio = u_audio + sigma_s_audio * audio_noise_pred_2[0, nca:].to(dtype=compute_dtype)
555
+
556
+ new_video = _seeds2_combine(self.video_generator, x_video_c, sigma_i_dpm_video, sigma_ip1_video, d_i_video, sde_noise_video, h_video)
557
+ new_audio = _seeds2_combine(self.audio_generator, x_audio_c, sigma_i_dpm_audio, sigma_ip1_audio, d_i_audio, sde_noise_audio, h_audio)
558
+
559
+ block_state.latents[ncv:] = new_video.to(dtype=block_state.latents.dtype)
560
+ block_state.audio_latents[nca:] = new_audio.to(dtype=block_state.audio_latents.dtype)
561
+ return components, block_state
562
+
563
+
564
  class use_dpmpp_2s_ancestral:
565
  """Swaps `pipe`'s live `denoise` sub-block for `MiniMaxH3DPMpp2SAncestralStep` for one request, restoring the
566
  original block on exit. A block swap rather than a `scheduler.step()` patch, and so a separate context
 
599
  if self._core_denoise is not None and self._original is not None:
600
  self._core_denoise.sub_blocks["denoise"] = self._original
601
  return False
602
+
603
+
604
+ class use_dpmpp_sde_gpu(use_dpmpp_2s_ancestral):
605
+ """Same swap as `use_dpmpp_2s_ancestral`, for `MiniMaxH3DPMppSdeStep` instead. Its own subclass rather than a
606
+ shared parameterized manager, since `MiniMaxH3DPMppSdeStep` takes `seed` directly (its noise samplers are
607
+ lazily built from `x`'s shape/device, unlike the ancestral samplers' plain `torch.Generator`)."""
608
+
609
+ def __enter__(self):
610
+ if not self.enabled:
611
+ return self
612
+ self._core_denoise = self.pipe._blocks.sub_blocks["denoise"]
613
+ self._original = self._core_denoise.sub_blocks["denoise"]
614
+ step = MiniMaxH3DPMppSdeStep(transformer_name=self.transformer_name, seed=self.seed)
615
+ self._core_denoise.sub_blocks["denoise"] = MiniMaxH3DenoiseLoopWrapper.from_blocks_dict({"dpmpp_sde_gpu": step})
616
+ return self
617
+
618
+
619
+ class use_seeds_2(use_dpmpp_2s_ancestral):
620
+ """Same swap as `use_dpmpp_2s_ancestral`, for `MiniMaxH3Seeds2Step` instead."""
621
+
622
+ def __enter__(self):
623
+ if not self.enabled:
624
+ return self
625
+ self._core_denoise = self.pipe._blocks.sub_blocks["denoise"]
626
+ self._original = self._core_denoise.sub_blocks["denoise"]
627
+ video_generator = torch.Generator(device="cpu").manual_seed(self.seed)
628
+ audio_generator = torch.Generator(device="cpu").manual_seed(self.seed + 1)
629
+ step = MiniMaxH3Seeds2Step(
630
+ transformer_name=self.transformer_name, video_generator=video_generator, audio_generator=audio_generator
631
+ )
632
+ self._core_denoise.sub_blocks["denoise"] = MiniMaxH3DenoiseLoopWrapper.from_blocks_dict({"seeds_2": step})
633
+ return self