dagloop5 commited on
Commit
2a45e65
·
verified ·
1 Parent(s): cb9b4f6

Update pk_workflow.py

Browse files
Files changed (1) hide show
  1. pk_workflow.py +121 -14
pk_workflow.py CHANGED
@@ -81,34 +81,141 @@ def time_shift_sigma(sigma: torch.Tensor, from_shift: float, to_shift: float) ->
81
  return to_shift * base / (1.0 + (to_shift - 1.0) * base)
82
 
83
 
84
- class use_linear_quadratic:
85
- """Force MiniMax-H3's two schedulers onto the `linear_quadratic` grid for one pipeline call.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- A context manager rather than a pipeline-block subclass on purpose: `MiniMaxH3Scheduler.set_timesteps` already
88
- takes a fully-formed `sigmas=` schedule as public API, so nothing here reaches into the modular blocks, and the
89
- override lives and dies inside one request.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  """
91
 
92
- def __init__(self, pipe, steps: int, threshold_noise: float = 0.025, enabled: bool = True):
93
- self.schedulers = [pipe.scheduler, pipe.audio_scheduler] if enabled else []
 
 
94
  self.steps = int(steps)
95
  self.threshold_noise = float(threshold_noise)
 
96
 
97
  def __enter__(self):
98
- video_sigmas = linear_quadratic_sigmas(self.steps, self.threshold_noise)
99
- for scheduler in self.schedulers:
100
- sigmas = time_shift_sigma(video_sigmas, VIDEO_SHIFT, float(scheduler.shift))
101
- unbound = type(scheduler).set_timesteps
 
 
 
 
 
 
102
 
103
- def forced(num_inference_steps=None, device=None, sigmas=None, _s=scheduler, _grid=sigmas, _f=unbound):
104
- return _f(_s, None, device, _grid)
105
 
106
- scheduler.set_timesteps = forced
107
  return self
108
 
109
  def __exit__(self, *_):
110
  for scheduler in self.schedulers:
111
  scheduler.__dict__.pop("set_timesteps", None)
 
 
112
  return False
113
 
114
 
 
81
  return to_shift * base / (1.0 + (to_shift - 1.0) * base)
82
 
83
 
84
+ # ----------------------------------------------------------------------------------------------------------------
85
+ # BasicScheduler(sgm_uniform / simple / beta / ddim_uniform / normal)
86
+ # ----------------------------------------------------------------------------------------------------------------
87
+ # Five more of ComfyUI's `BasicScheduler` names, ported from `comfy/samplers.py`. Each is computed at the
88
+ # *reference* shift (1.0 — where `time_snr_shift` is the identity, so `sigma(t) == t`) and reprojected onto each
89
+ # scheduler's real shift by `time_shift_sigma`, exactly like `linear_quadratic_sigmas` already is and for the same
90
+ # reason: it keeps the video and audio streams pinned to the same underlying denoising progress at each step,
91
+ # which computing each stream's schedule independently at its own shift would not.
92
+ #
93
+ # `FLOW_TIMESTEPS` mirrors ComfyUI's `ModelSamplingDiscreteFlow`/`ModelSamplingAV` default of 1000 discrete steps
94
+ # (`comfy/model_sampling.py`). Unverified specifically for MiniMax-H3's own `sampling_settings` — if a ported
95
+ # schedule's shape looks visibly different from ComfyUI's own render at the same steps/seed, this is the first
96
+ # thing to check.
97
+ FLOW_TIMESTEPS = 1000
98
+
99
+
100
+ def _reference_sigma(index_1based: int) -> float:
101
+ """`ModelSamplingAV.sigma(timestep)` at shift == 1.0: the shift formula is the identity, so this is just the
102
+ plain fraction `index / FLOW_TIMESTEPS`. `index_1based` matches ComfyUI's 1-based table construction
103
+ (`torch.arange(1, timesteps + 1) / timesteps`)."""
104
+ return index_1based / FLOW_TIMESTEPS
105
+
106
+
107
+ def sgm_uniform_sigmas(steps: int) -> torch.Tensor:
108
+ """ComfyUI's `sgm_uniform`. Uniform in *timestep* space between the max and min sigma, dropping the point
109
+ that would land exactly on the minimum, then appending an exact 0.0. `steps + 1` sigmas."""
110
+ steps = int(steps)
111
+ timesteps = torch.linspace(float(FLOW_TIMESTEPS), 1.0, steps + 1)[:-1]
112
+ sigmas = (timesteps / FLOW_TIMESTEPS).tolist() + [0.0]
113
+ return torch.tensor(sigmas, dtype=torch.float32)
114
+
115
+
116
+ def normal_sigmas(steps: int) -> torch.Tensor:
117
+ """ComfyUI's `normal`. Same idea as `sgm_uniform` but the linspace includes both endpoints (the minimum
118
+ sigma is reached exactly, not dropped), with 0.0 still appended."""
119
+ steps = int(steps)
120
+ timesteps = torch.linspace(float(FLOW_TIMESTEPS), 1.0, steps)
121
+ sigmas = (timesteps / FLOW_TIMESTEPS).tolist() + [0.0]
122
+ return torch.tensor(sigmas, dtype=torch.float32)
123
+
124
+
125
+ def simple_sigmas(steps: int) -> torch.Tensor:
126
+ """ComfyUI's `simple`: evenly-spaced *indices* into the 1000-entry sigma table, walked from the high-noise
127
+ end, then 0.0 appended."""
128
+ steps = int(steps)
129
+ stride = FLOW_TIMESTEPS / steps
130
+ sigmas = [_reference_sigma(FLOW_TIMESTEPS - int(x * stride)) for x in range(steps)]
131
+ sigmas.append(0.0)
132
+ return torch.tensor(sigmas, dtype=torch.float32)
133
+
134
 
135
+ def ddim_uniform_sigmas(steps: int) -> torch.Tensor:
136
+ """ComfyUI's `ddim_uniform`: a fixed-stride walk through the sigma table starting one index in, reversed so
137
+ the highest sigma comes first, ending at 0.0."""
138
+ steps = int(steps)
139
+ stride = max(FLOW_TIMESTEPS // steps, 1)
140
+ sigmas = [0.0]
141
+ index = 1
142
+ while index < FLOW_TIMESTEPS:
143
+ sigmas.append(_reference_sigma(index))
144
+ index += stride
145
+ sigmas.reverse()
146
+ return torch.tensor(sigmas, dtype=torch.float32)
147
+
148
+
149
+ def beta_sigmas(steps: int, alpha: float = 0.6, beta: float = 0.6) -> torch.Tensor:
150
+ """ComfyUI's `beta` (arxiv.org/abs/2407.12173): table indices drawn from a Beta(alpha, beta) inverse CDF
151
+ instead of an even stride, biasing samples toward one end of the trajectory. Needs `scipy`."""
152
+ import numpy
153
+ import scipy.stats
154
+
155
+ steps = int(steps)
156
+ total = FLOW_TIMESTEPS - 1
157
+ positions = 1.0 - numpy.linspace(0.0, 1.0, steps, endpoint=False)
158
+ indices = numpy.rint(scipy.stats.beta.ppf(positions, alpha, beta) * total)
159
+ sigmas = []
160
+ last = -1
161
+ for value in indices:
162
+ if value != last:
163
+ sigmas.append(_reference_sigma(int(value) + 1))
164
+ last = value
165
+ sigmas.append(0.0)
166
+ return torch.tensor(sigmas, dtype=torch.float32)
167
+
168
+
169
+ SCHEDULE_SIGMA_FUNCS = {
170
+ "linear_quadratic": linear_quadratic_sigmas,
171
+ "sgm_uniform": sgm_uniform_sigmas,
172
+ "simple": simple_sigmas,
173
+ "beta": beta_sigmas,
174
+ "ddim_uniform": ddim_uniform_sigmas,
175
+ "normal": normal_sigmas,
176
+ }
177
+
178
+
179
+ class use_schedule:
180
+ """Set each scheduler's shift for one request, and — for anything but `native` — force its sigma grid onto
181
+ one of `SCHEDULE_SIGMA_FUNCS`'s named schedules.
182
+ Shift is applied unconditionally, including under `native`, so the shift sliders affect the pipeline's own
183
+ default schedule too and not just the custom ones — and it is always restored on exit, since
184
+ `pipe.scheduler`/`pipe.audio_scheduler` are shared, request-spanning objects that must not carry one
185
+ request's shift into the next.
186
  """
187
 
188
+ def __init__(self, pipe, steps: int, schedule_name: str, video_shift: float, audio_shift: float, threshold_noise: float = 0.025):
189
+ self.schedulers = [pipe.scheduler, pipe.audio_scheduler]
190
+ self.shifts = [float(video_shift), float(audio_shift)]
191
+ self.schedule_name = schedule_name
192
  self.steps = int(steps)
193
  self.threshold_noise = float(threshold_noise)
194
+ self._originals = []
195
 
196
  def __enter__(self):
197
+ self._originals = [(scheduler, scheduler.shift) for scheduler in self.schedulers]
198
+ for scheduler, shift in zip(self.schedulers, self.shifts):
199
+ scheduler.shift = shift
200
+
201
+ if self.schedule_name != "native":
202
+ sigma_func = SCHEDULE_SIGMA_FUNCS[self.schedule_name]
203
+ base = sigma_func(self.steps, self.threshold_noise) if sigma_func is linear_quadratic_sigmas else sigma_func(self.steps)
204
+ for scheduler in self.schedulers:
205
+ sigmas = time_shift_sigma(base, 1.0, float(scheduler.shift))
206
+ unbound = type(scheduler).set_timesteps
207
 
208
+ def forced(num_inference_steps=None, device=None, sigmas=None, _s=scheduler, _grid=sigmas, _f=unbound):
209
+ return _f(_s, None, device, _grid)
210
 
211
+ scheduler.set_timesteps = forced
212
  return self
213
 
214
  def __exit__(self, *_):
215
  for scheduler in self.schedulers:
216
  scheduler.__dict__.pop("set_timesteps", None)
217
+ for scheduler, original_shift in self._originals:
218
+ scheduler.shift = original_shift
219
  return False
220
 
221