Spaces:
Running on Zero
Running on Zero
Upload h3_dpmpp_2s_ancestral.py
Browse files- h3_dpmpp_2s_ancestral.py +267 -0
h3_dpmpp_2s_ancestral.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
|
| 25 |
+
from diffusers.modular_pipelines.minimax_h3.before_denoise import MiniMaxH3SetTimestepsStep
|
| 26 |
+
from diffusers.modular_pipelines.minimax_h3.denoise import MiniMaxH3DenoiseLoopWrapper
|
| 27 |
+
from diffusers.modular_pipelines.minimax_h3.modular_pipeline import MiniMaxH3ModularPipeline
|
| 28 |
+
from diffusers.modular_pipelines.modular_pipeline import BlockState, ModularPipelineBlocks
|
| 29 |
+
from diffusers.modular_pipelines.modular_pipeline_utils import ComponentSpec, InputParam
|
| 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
|
| 36 |
+
intermediate sigma/sample to evaluate (`sigma_s`, `u`) and the scalars the combine step needs once the second
|
| 37 |
+
call's `D_i` comes back. `x`/`denoised` are this stream's generated rows only (conditioning rows excluded,
|
| 38 |
+
same slice `MiniMaxH3LoopSchedulerStep` already steps). Mirrors k-diffusion's `sample_dpmpp_2s_ancestral_RF`
|
| 39 |
+
body exactly, split at the point the real function makes its second `model(...)` call.
|
| 40 |
+
"""
|
| 41 |
+
downstep_ratio = 1 + (sigma_ip1 / sigma_i - 1) * eta
|
| 42 |
+
sigma_down = sigma_ip1 * downstep_ratio
|
| 43 |
+
alpha_ip1 = 1 - sigma_ip1
|
| 44 |
+
alpha_down = 1 - sigma_down
|
| 45 |
+
renoise_coeff = (sigma_ip1**2 - sigma_down**2 * alpha_ip1**2 / alpha_down**2).clamp_min(0).sqrt()
|
| 46 |
+
|
| 47 |
+
if float(sigma_i) >= 1.0:
|
| 48 |
+
# k-diffusion's own first-step special case: `lambda_fn` divides by `sigma_i` inside a `log`, which blows
|
| 49 |
+
# up at exactly 1.0 (MiniMax-H3's real first sigma). Same nudge `_er_sde_step` uses for its own division.
|
| 50 |
+
sigma_s = sigma_i.new_tensor(0.9999)
|
| 51 |
+
else:
|
| 52 |
+
lambda_fn = lambda sigma: ((1 - sigma) / sigma).log()
|
| 53 |
+
t_i, t_down = lambda_fn(sigma_i), lambda_fn(sigma_down)
|
| 54 |
+
h = t_down - t_i
|
| 55 |
+
s = t_i + 0.5 * h
|
| 56 |
+
sigma_s = (s.exp() + 1) ** -1
|
| 57 |
+
|
| 58 |
+
sigma_s_i_ratio = sigma_s / sigma_i
|
| 59 |
+
u = sigma_s_i_ratio * x + (1 - sigma_s_i_ratio) * denoised
|
| 60 |
+
return u, sigma_s, sigma_down, alpha_ip1, alpha_down, renoise_coeff
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _dpmpp_2s_ancestral_combine(
|
| 64 |
+
generator, x: torch.Tensor, sigma_ip1: torch.Tensor, d_i: torch.Tensor, sigma_i: torch.Tensor,
|
| 65 |
+
sigma_down: torch.Tensor, alpha_ip1: torch.Tensor, alpha_down: torch.Tensor, renoise_coeff: torch.Tensor,
|
| 66 |
+
eta: float = 1.0, s_noise: float = 1.0,
|
| 67 |
+
):
|
| 68 |
+
"""The DPM-Solver++(2S) combine and ancestral-noise-injection half, once the second call's `d_i` (already
|
| 69 |
+
converted to a denoised/x0 estimate, same as the first call) is in hand."""
|
| 70 |
+
sigma_down_i_ratio = sigma_down / sigma_i
|
| 71 |
+
x = sigma_down_i_ratio * x + (1 - sigma_down_i_ratio) * d_i
|
| 72 |
+
if float(sigma_ip1) > 0 and eta > 0:
|
| 73 |
+
noise = torch.randn(x.shape, dtype=x.dtype, device=x.device, generator=generator)
|
| 74 |
+
x = (alpha_ip1 / alpha_down) * x + noise * s_noise * renoise_coeff
|
| 75 |
+
return x
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class MiniMaxH3DPMpp2SAncestralStep(ModularPipelineBlocks):
|
| 79 |
+
"""One `dpmpp_2s_ancestral` iteration: real forward pass, intermediate forward pass, DPM-Solver++(2S) combine
|
| 80 |
+
with ancestral noise — video and audio computed independently, each still one packed transformer call.
|
| 81 |
+
"""
|
| 82 |
+
|
| 83 |
+
model_name = "minimax-h3"
|
| 84 |
+
|
| 85 |
+
def __init__(self, transformer_name: str = "transformer", video_generator=None, audio_generator=None):
|
| 86 |
+
self.transformer_name = transformer_name
|
| 87 |
+
self.video_generator = video_generator
|
| 88 |
+
self.audio_generator = audio_generator
|
| 89 |
+
super().__init__()
|
| 90 |
+
|
| 91 |
+
@property
|
| 92 |
+
def description(self) -> str:
|
| 93 |
+
return (
|
| 94 |
+
"Runs one `dpmpp_2s_ancestral` denoising iteration in place of `MiniMaxH3LoopDenoiser` + "
|
| 95 |
+
"`MiniMaxH3LoopSchedulerStep`: the real forward pass, a second forward pass at a constructed "
|
| 96 |
+
"intermediate sample/sigma, and the DPM-Solver++(2S) combine with ancestral noise injection."
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
@property
|
| 100 |
+
def expected_components(self) -> list[ComponentSpec]:
|
| 101 |
+
return [
|
| 102 |
+
ComponentSpec(self.transformer_name, MiniMaxH3Transformer3DModel),
|
| 103 |
+
ComponentSpec("scheduler", MiniMaxH3Scheduler),
|
| 104 |
+
ComponentSpec("audio_scheduler", MiniMaxH3Scheduler),
|
| 105 |
+
]
|
| 106 |
+
|
| 107 |
+
@property
|
| 108 |
+
def inputs(self) -> list[InputParam]:
|
| 109 |
+
return [
|
| 110 |
+
InputParam(name="latents", type_hint=torch.Tensor, required=True),
|
| 111 |
+
InputParam(name="audio_latents", type_hint=torch.Tensor, required=True),
|
| 112 |
+
InputParam.template("prompt_embeds"),
|
| 113 |
+
InputParam(name="row_timestep_plan", type_hint=list, required=True),
|
| 114 |
+
InputParam(name="audio_timesteps", type_hint=torch.Tensor, required=True),
|
| 115 |
+
InputParam(name="video_indices", type_hint=torch.Tensor, required=True),
|
| 116 |
+
InputParam(name="audio_indices", type_hint=torch.Tensor, required=True),
|
| 117 |
+
InputParam(name="text_indices", type_hint=torch.Tensor, required=True),
|
| 118 |
+
InputParam(name="num_condition_video_rows", type_hint=int, default=0),
|
| 119 |
+
InputParam(name="num_condition_audio_rows", type_hint=int, default=0),
|
| 120 |
+
InputParam(kwargs_type="denoiser_input_fields"),
|
| 121 |
+
InputParam.template("attention_kwargs"),
|
| 122 |
+
]
|
| 123 |
+
|
| 124 |
+
@property
|
| 125 |
+
def intermediate_outputs(self) -> list:
|
| 126 |
+
return [] # writes block_state.latents / audio_latents in place, same as MiniMaxH3LoopSchedulerStep
|
| 127 |
+
|
| 128 |
+
def _forward(self, components: MiniMaxH3ModularPipeline, block_state: BlockState, hidden_states, audio_hidden_states, unique_timesteps, timestep_indices):
|
| 129 |
+
transformer = getattr(components, self.transformer_name)
|
| 130 |
+
layout_kwargs = {
|
| 131 |
+
name: value
|
| 132 |
+
for name, value in block_state.denoiser_input_fields.items()
|
| 133 |
+
if name in inspect.signature(transformer.forward).parameters
|
| 134 |
+
}
|
| 135 |
+
return transformer(
|
| 136 |
+
hidden_states=hidden_states[None],
|
| 137 |
+
audio_hidden_states=audio_hidden_states[None],
|
| 138 |
+
encoder_hidden_states=block_state.prompt_embeds,
|
| 139 |
+
timestep=unique_timesteps,
|
| 140 |
+
timestep_indices=timestep_indices,
|
| 141 |
+
attention_kwargs=block_state.attention_kwargs,
|
| 142 |
+
return_dict=False,
|
| 143 |
+
**layout_kwargs,
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
@torch.no_grad()
|
| 147 |
+
def __call__(self, components: MiniMaxH3ModularPipeline, block_state: BlockState, i: int, t: torch.Tensor):
|
| 148 |
+
ncv = block_state.num_condition_video_rows
|
| 149 |
+
nca = block_state.num_condition_audio_rows
|
| 150 |
+
is_last_step = i == len(block_state.row_timestep_plan) - 1
|
| 151 |
+
|
| 152 |
+
# --- first (real) evaluation, identical to MiniMaxH3LoopDenoiser ---
|
| 153 |
+
unique_timesteps, timestep_indices = block_state.row_timestep_plan[i]
|
| 154 |
+
noise_pred, audio_noise_pred = self._forward(
|
| 155 |
+
components, block_state, block_state.latents, block_state.audio_latents, unique_timesteps, timestep_indices
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
video_timestep_i = float(t)
|
| 159 |
+
|
| 160 |
+
x_video = block_state.latents[ncv:]
|
| 161 |
+
x_audio = block_state.audio_latents[nca:]
|
| 162 |
+
compute_dtype = torch.float32 if x_video.dtype in (torch.float16, torch.bfloat16) else x_video.dtype
|
| 163 |
+
|
| 164 |
+
sigma_i_video = components.scheduler.sigmas[i].to(device=x_video.device, dtype=compute_dtype)
|
| 165 |
+
sigma_ip1_video = components.scheduler.sigmas[i + 1].to(device=x_video.device, dtype=compute_dtype)
|
| 166 |
+
sigma_i_audio = components.audio_scheduler.sigmas[i].to(device=x_audio.device, dtype=compute_dtype)
|
| 167 |
+
sigma_ip1_audio = components.audio_scheduler.sigmas[i + 1].to(device=x_audio.device, dtype=compute_dtype)
|
| 168 |
+
|
| 169 |
+
x_video_c = x_video.to(dtype=compute_dtype)
|
| 170 |
+
x_audio_c = x_audio.to(dtype=compute_dtype)
|
| 171 |
+
# velocity -> denoised (x0 estimate), same conversion `_euler_ancestral_step`/`_er_sde_step` already use.
|
| 172 |
+
denoised_video = x_video_c + sigma_i_video * noise_pred[0, ncv:].to(dtype=compute_dtype)
|
| 173 |
+
denoised_audio = x_audio_c + sigma_i_audio * audio_noise_pred[0, nca:].to(dtype=compute_dtype)
|
| 174 |
+
|
| 175 |
+
if is_last_step:
|
| 176 |
+
# k-diffusion's own Euler fallback (`sigmas[i+1] == 0`) collapses to `x = denoised` at eta=1, the only
|
| 177 |
+
# eta this port exposes — skip the second call entirely rather than compute a no-op intermediate step.
|
| 178 |
+
new_video, new_audio = denoised_video, denoised_audio
|
| 179 |
+
else:
|
| 180 |
+
u_video, sigma_s_video, sigma_down_video, alpha_ip1_video, alpha_down_video, renoise_video = (
|
| 181 |
+
_dpmpp_2s_ancestral_prepare(x_video_c, sigma_i_video, sigma_ip1_video, denoised_video)
|
| 182 |
+
)
|
| 183 |
+
u_audio, sigma_s_audio, sigma_down_audio, alpha_ip1_audio, alpha_down_audio, renoise_audio = (
|
| 184 |
+
_dpmpp_2s_ancestral_prepare(x_audio_c, sigma_i_audio, sigma_ip1_audio, denoised_audio)
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
# --- second (intermediate) evaluation: full sequence, only the generated rows replaced by u ---
|
| 188 |
+
latents_2 = block_state.latents.clone()
|
| 189 |
+
latents_2[ncv:] = u_video.to(dtype=block_state.latents.dtype)
|
| 190 |
+
audio_latents_2 = block_state.audio_latents.clone()
|
| 191 |
+
audio_latents_2[nca:] = u_audio.to(dtype=block_state.audio_latents.dtype)
|
| 192 |
+
|
| 193 |
+
# Conditioning rows stay pinned exactly as the real step pinned them (not re-derived from the
|
| 194 |
+
# intermediate sigma) — the fixed floor `MiniMaxH3SetTimestepsStep.__call__` itself uses. Text rows
|
| 195 |
+
# inherit whichever video timestep is passed, here the intermediate one, same rule as the real step.
|
| 196 |
+
unique_timesteps_2, timestep_indices_2 = tuple(
|
| 197 |
+
tensor.to(block_state.latents.device)
|
| 198 |
+
for tensor in MiniMaxH3SetTimestepsStep.build_row_timesteps(
|
| 199 |
+
block_state.video_indices,
|
| 200 |
+
block_state.audio_indices,
|
| 201 |
+
ncv,
|
| 202 |
+
nca,
|
| 203 |
+
block_state.text_indices.numel(),
|
| 204 |
+
1.0 - float(sigma_s_video),
|
| 205 |
+
1.0 - float(sigma_s_audio),
|
| 206 |
+
max(video_timestep_i, components.keyframe_noise_aug),
|
| 207 |
+
1.0,
|
| 208 |
+
)
|
| 209 |
+
)
|
| 210 |
+
noise_pred_2, audio_noise_pred_2 = self._forward(
|
| 211 |
+
components, block_state, latents_2, audio_latents_2, unique_timesteps_2, timestep_indices_2
|
| 212 |
+
)
|
| 213 |
+
d_i_video = u_video + sigma_s_video * noise_pred_2[0, ncv:].to(dtype=compute_dtype)
|
| 214 |
+
d_i_audio = u_audio + sigma_s_audio * audio_noise_pred_2[0, nca:].to(dtype=compute_dtype)
|
| 215 |
+
|
| 216 |
+
new_video = _dpmpp_2s_ancestral_combine(
|
| 217 |
+
self.video_generator, x_video_c, sigma_ip1_video, d_i_video, sigma_i_video,
|
| 218 |
+
sigma_down_video, alpha_ip1_video, alpha_down_video, renoise_video,
|
| 219 |
+
)
|
| 220 |
+
new_audio = _dpmpp_2s_ancestral_combine(
|
| 221 |
+
self.audio_generator, x_audio_c, sigma_ip1_audio, d_i_audio, sigma_i_audio,
|
| 222 |
+
sigma_down_audio, alpha_ip1_audio, alpha_down_audio, renoise_audio,
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
block_state.latents[ncv:] = new_video.to(dtype=block_state.latents.dtype)
|
| 226 |
+
block_state.audio_latents[nca:] = new_audio.to(dtype=block_state.audio_latents.dtype)
|
| 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
|
| 233 |
+
manager from `use_schedule` — nest them (`use_schedule` still owns the sigma grid; this only owns which block
|
| 234 |
+
walks it). No-op when `enabled=False`, so it's safe to wrap every request unconditionally.
|
| 235 |
+
|
| 236 |
+
Reaches the running block tree at `pipe._blocks.sub_blocks["denoise"].sub_blocks["denoise"]`:
|
| 237 |
+
`MiniMaxH3GeneratorBlocks`'s own "denoise" step is `MiniMaxH3CoreDenoiseStep`, whose own "denoise" step is
|
| 238 |
+
`MiniMaxH3DenoiseStep` — the `LoopSequentialPipelineBlocks` this class temporarily replaces outright.
|
| 239 |
+
"""
|
| 240 |
+
|
| 241 |
+
def __init__(self, pipe, seed: int = 0, transformer_name: str = "transformer", enabled: bool = True):
|
| 242 |
+
self.pipe = pipe
|
| 243 |
+
self.seed = int(seed)
|
| 244 |
+
self.transformer_name = transformer_name
|
| 245 |
+
self.enabled = enabled
|
| 246 |
+
self._core_denoise = None
|
| 247 |
+
self._original = None
|
| 248 |
+
|
| 249 |
+
def __enter__(self):
|
| 250 |
+
if not self.enabled:
|
| 251 |
+
return self
|
| 252 |
+
self._core_denoise = self.pipe._blocks.sub_blocks["denoise"]
|
| 253 |
+
self._original = self._core_denoise.sub_blocks["denoise"]
|
| 254 |
+
video_generator = torch.Generator(device="cpu").manual_seed(self.seed)
|
| 255 |
+
audio_generator = torch.Generator(device="cpu").manual_seed(self.seed + 1)
|
| 256 |
+
step = MiniMaxH3DPMpp2SAncestralStep(
|
| 257 |
+
transformer_name=self.transformer_name, video_generator=video_generator, audio_generator=audio_generator
|
| 258 |
+
)
|
| 259 |
+
self._core_denoise.sub_blocks["denoise"] = MiniMaxH3DenoiseLoopWrapper.from_blocks_dict(
|
| 260 |
+
{"dpmpp_2s_ancestral": step}
|
| 261 |
+
)
|
| 262 |
+
return self
|
| 263 |
+
|
| 264 |
+
def __exit__(self, *_):
|
| 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
|