Spaces:
Running on Zero
Running on Zero
Update h3_dpmpp_2s_ancestral.py
Browse files- h3_dpmpp_2s_ancestral.py +384 -18
h3_dpmpp_2s_ancestral.py
CHANGED
|
@@ -1,21 +1,25 @@
|
|
| 1 |
-
"""
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
forward pass against it, and combines both predictions (plus
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
`
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|