dagloop5 commited on
Commit
204d27d
·
verified ·
1 Parent(s): 0d0957b

Upload h3_dpmpp_2s_ancestral.py

Browse files
Files changed (1) hide show
  1. 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