mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 09:14:35 +02:00
add hidream-o1
Co-authored-by: Copilot <copilot@github.com> Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
@@ -78,6 +78,10 @@ Use these repo-local skills for recurring SD.Next model integration work:
|
||||
File: `.github/skills/port-model/SKILL.md`
|
||||
Use when adding a new model family, porting a standalone script into a Diffusers pipeline, or wiring an upstream Diffusers model into SD.Next.
|
||||
|
||||
- `port-pipeline`
|
||||
File: `.github/skills/port-pipeline/SKILL.md`
|
||||
Use when porting a custom model pipeline implementation to a Diffusers pipeline class with behavior parity and no hard-coded device or attention assumptions.
|
||||
|
||||
- `debug-model`
|
||||
File: `.github/skills/debug-model/SKILL.md`
|
||||
Use when a new or existing SD.Next/Diffusers model integration fails during detection, loading, prompt encoding, sampling, or output handling.
|
||||
|
||||
@@ -8,6 +8,10 @@ This folder contains repo-local Copilot skills for recurring SD.Next tasks.
|
||||
File: `port-model/SKILL.md`
|
||||
Use when adding or porting a model family into SD.Next and Diffusers.
|
||||
|
||||
- `port-pipeline`
|
||||
File: `port-pipeline/SKILL.md`
|
||||
Use when porting a custom pipeline implementation into a Diffusers pipeline class while preserving behavior and avoiding hard-coded runtime assumptions.
|
||||
|
||||
- `debug-model`
|
||||
File: `debug-model/SKILL.md`
|
||||
Use when a new or existing SD.Next/Diffusers model integration fails during detect, load, prompt encode, sample, or output handling.
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
name: port-pipeline
|
||||
description: "Port custom model pipeline implementations to Diffusers. Use when migrating custom or non-Diffusers pipeline code into SD.Next repo-local pipeline files such as pipelines/model_<name>.py or pipelines/<model>/pipeline.py while preserving behavior, avoiding new dependencies, and keeping device/attention handling configurable."
|
||||
argument-hint: "Provide source pipeline path, target SD.Next destination path, and target pipeline class name"
|
||||
---
|
||||
|
||||
# Port Custom Pipeline To Diffusers
|
||||
|
||||
Port an existing custom model pipeline implementation into a Diffusers-compatible pipeline class with behavior parity and SD.Next-friendly conventions.
|
||||
This skill targets SD.Next repo-local pipeline ports only.
|
||||
|
||||
## When To Use
|
||||
|
||||
- A user has a custom pipeline implementation and wants it ported to Diffusers
|
||||
- Existing model code is runnable but not structured as a Diffusers pipeline
|
||||
- The destination is SD.Next pipeline code under `pipelines/model_*.py` or `pipelines/<model>/`
|
||||
- The task requires preserving generation behavior without introducing new dependencies
|
||||
- The task requires removing hard-coded runtime assumptions (device or attention backend)
|
||||
|
||||
## Mandatory Clarification Gate
|
||||
|
||||
Before implementation, confirm these required inputs with the user:
|
||||
|
||||
1. Path to the source custom pipeline implementation
|
||||
2. Destination path in this SD.Next repository (typically under `pipelines/`)
|
||||
3. Target pipeline class name
|
||||
|
||||
If any of the above are missing or ambiguous, stop and ask concise clarification questions before writing code.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not add new dependencies
|
||||
- Do not hard-code device type (`cpu`, `cuda`, `mps`, etc.)
|
||||
- Do not hard-code attention type or backend assumptions
|
||||
- Preserve externally visible behavior of the source pipeline unless the user asks for intentional changes
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Collect Inputs
|
||||
- Ask for source path, destination path, and target pipeline name.
|
||||
- Confirm destination is an SD.Next repo-local pipeline location, not an upstream Diffusers repository path.
|
||||
- Confirm runtime assumptions and expected task type (text-to-image, image-to-image, inpaint, etc.).
|
||||
|
||||
2. Analyze Source Pipeline
|
||||
- Inspect model loading, prompt processing, denoising or sampling loop, scheduler interactions, and output post-processing.
|
||||
- Identify all components that must be ported: models, tokenizers or processors, schedulers, adapters, preprocessors, postprocessors, callbacks, and output dataclasses.
|
||||
- Note any hidden global state, side effects, or implicit defaults that must become explicit parameters.
|
||||
|
||||
3. Map To Diffusers Interfaces
|
||||
- Choose the most appropriate Diffusers base class and output type.
|
||||
- Define `__init__`, module registration, `from_pretrained` and `__call__` signatures aligned with existing Diffusers patterns.
|
||||
- Keep parameter names and behavior as close as possible to upstream conventions.
|
||||
- Identify any custom classes needed beyond the pipeline itself: transformer blocks, attention processors, custom schedulers, or output types. Plan a separate module file for each.
|
||||
|
||||
4. Implement Supporting Classes
|
||||
- If the pipeline requires custom model classes (e.g., a custom transformer block, attention module, or other model component), implement each in a **separate module** located in the **same directory** as the main pipeline file (e.g., `pipelines/<model>/transformer.py`, `pipelines/<model>/scheduler.py`).
|
||||
- If the pipeline requires a custom scheduler class, implement it in its own module (e.g., `pipelines/<model>/scheduler_<name>.py`) following Diffusers scheduler conventions (`step`, `add_noise`, `scale_model_input`, etc.).
|
||||
- Each supporting class module must be self-contained: no circular imports, no hidden global state, and no hard-coded device or attention assumptions.
|
||||
- Import supporting classes into the main pipeline module from their respective sibling modules.
|
||||
|
||||
5. Implement Pipeline Class
|
||||
- Create the destination pipeline classes at the user-provided path.
|
||||
- Port logic in small, testable sections: initialization, input validation, prompt encoding, latent preparation, denoising loop, decoding, and output packaging.
|
||||
- Replace hard-coded device and attention logic with runtime-configurable behavior.
|
||||
- Keep imports limited to existing project and Diffusers dependencies.
|
||||
|
||||
6. Lint And Fix
|
||||
- Activate the project venv: `source venv/bin/activate`
|
||||
- Run `ruff` on all newly written files: `pnpm ruff` (or `ruff check <file> --fix` for targeted runs).
|
||||
- Run `pylint` on all newly written files: `pnpm pylint` (or `pylint <file>` for targeted runs).
|
||||
- Fix every reported error or warning that is not explicitly marked with a `TODO` suppression comment in the source.
|
||||
- Re-run both linters after fixes to confirm a clean result before proceeding.
|
||||
|
||||
7. Validate Behavior Parity
|
||||
- Compare source and ported implementations for input-output shape handling, dtype flow, scheduler step ordering, and guidance behavior.
|
||||
- Run focused checks or smoke tests if available in the workspace.
|
||||
- Call out any known differences that were required for Diffusers compatibility.
|
||||
|
||||
8. Report Results
|
||||
- Summarize what was ported and where.
|
||||
- List any unresolved assumptions, risks, or TODOs.
|
||||
- Provide minimal follow-up steps for integration and testing.
|
||||
|
||||
## Review Checklist
|
||||
|
||||
- Required inputs were collected before edits
|
||||
- No new dependency was introduced
|
||||
- No hard-coded device or attention backend remains
|
||||
- Core components from source pipeline were fully mapped
|
||||
- Pipeline class is in requested destination with requested name
|
||||
- Each custom supporting class (transformer, scheduler, etc.) is in its own sibling module
|
||||
- Supporting modules have no circular imports or hidden global state
|
||||
- `ruff` and `pylint` both pass cleanly on all newly written files (venv activated)
|
||||
- Main inference path behavior matches the source implementation
|
||||
|
||||
## Output Expectations
|
||||
|
||||
Final response should include:
|
||||
- Source path, destination path, and final pipeline class name
|
||||
- Brief parity summary of key components ported
|
||||
- Validation performed and any gaps
|
||||
- Explicit note of any assumptions requiring user confirmation
|
||||
@@ -32,6 +32,12 @@ For full details, see [ChangeLog](https://github.com/vladmandic/automatic/blob/m
|
||||
still a popular method for upscaling, but has not been updated nor maintained for a while
|
||||
so now its modernized and fully integrated as a built-in script!
|
||||
- **Models**
|
||||
HiDream-O1-Image is a natively unified image generative foundation model built on a Pixel-level Unified Transformer (UiT) without external VAEs or disjoint text encoders, which natively encodes raw pixels, text, and task-specific conditions in a single shared token space — supporting text-to-image, image editing, and subject-driven personalization at up to 2,048 × 2,048.
|
||||
- [HiDream-O1-Image](https://huggingface.co/HiDream-ai/HiDream-O1-Image) pixel-level unified transformer model support
|
||||
HiDream-O1 is based on a single custom *Qwen3-VL* 8.8B 35GB component
|
||||
includes both **HiDream-O1-Image** *(base)* and **HiDream-O1-Image-Dev** *(distilled*)* variants
|
||||
includes *T2I* and *I2I edit* capabilities and resolutions up to 2048px
|
||||
*note*: use steps:50 for base and steps:28 for dev variants
|
||||
- [JoyAI Image Edit](https://huggingface.co/jdopensource/JoyAI-Image-Edit-Diffusers) image-editing model support
|
||||
includes multimodal conditioning using *Qwen3-VL* with a dedicated *JoyImageEdit* diffusion transformer
|
||||
*note* this is a large model at 50GB so use of agressive quantization is recommended
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
### Assigned
|
||||
|
||||
- Check Outpaint, @vladmandic
|
||||
- Chat-based interface, @vladmandic
|
||||
- Control tab verify overrides handling, @vladmandic
|
||||
- Reimplement `llama` remover for Kanvas, @vladmandic
|
||||
|
||||
@@ -36,6 +36,16 @@
|
||||
"skip": true,
|
||||
"extras": "sampler: Default, cfg_scale: 4.5"
|
||||
},
|
||||
"HiDream-O1 Image Dev": {
|
||||
"path": "HiDream-ai/HiDream-O1-Image-Dev",
|
||||
"preview": "HiDream-ai--HiDream-O1-Image-Dev.jpg",
|
||||
"desc": "HiDream-O1-Image-Dev is the distilled 8B HiDream-O1 variant tuned for 28-step fast generation using flash flow scheduling.",
|
||||
"skip": true,
|
||||
"extras": "sampler: Flash, steps: 28, cfg_scale: 0.0",
|
||||
"size": 35.2,
|
||||
"tags": "distilled",
|
||||
"date": "2026 May"
|
||||
},
|
||||
"Qwen-Image-Lightning": {
|
||||
"path": "vladmandic/Qwen-Lightning",
|
||||
"preview": "vladmandic--Qwen-Lightning.jpg",
|
||||
|
||||
@@ -669,6 +669,15 @@
|
||||
"size": 58.4,
|
||||
"date": "2025 April"
|
||||
},
|
||||
"HiDream-O1 Image": {
|
||||
"path": "HiDream-ai/HiDream-O1-Image",
|
||||
"desc": "HiDream-O1-Image is an 8B pixel-level unified transformer model for text-to-image generation, instruction editing, and multi-reference personalization up to 2048x2048.",
|
||||
"preview": "HiDream-ai--HiDream-O1-Image.jpg",
|
||||
"skip": true,
|
||||
"extras": "sampler: Default",
|
||||
"size": 35.2,
|
||||
"date": "2026 May"
|
||||
},
|
||||
"HiDream-E1 Full": {
|
||||
"path": "HiDream-ai/HiDream-E1-Full",
|
||||
"desc": "HiDream-E1 is an image editing model built on HiDream-I1.",
|
||||
|
||||
@@ -281,7 +281,7 @@ def control_process(p: StableDiffusionProcessingControl,
|
||||
input_image: Image.Image = None, # only used for tiling, otherwise processor.preprocess_image set p params
|
||||
):
|
||||
debug_log(f'Control exec pipeline: task={sd_models.get_diffusers_task(pipe)} class={pipe.__class__}')
|
||||
if sd_models.get_diffusers_task(pipe) != sd_models.DiffusersTaskType.TEXT_2_IMAGE: # force vae back to gpu if not in txt2img mode
|
||||
if sd_models.get_diffusers_task(pipe) != sd_models.DiffusersTaskType.TEXT_2_IMAGE and hasattr(pipe, 'vae'): # force vae back to gpu if not in txt2img mode
|
||||
sd_models.move_model(pipe.vae, devices.device)
|
||||
|
||||
# what are we doing?
|
||||
|
||||
@@ -72,6 +72,8 @@ def get_model_type(pipe):
|
||||
model_type = 'sana'
|
||||
elif 'VIBE' in name:
|
||||
model_type = 'sana'
|
||||
elif "HiDreamO1" in name:
|
||||
model_type = 'o1'
|
||||
elif "HiDream" in name:
|
||||
model_type = 'h1'
|
||||
elif name.startswith("Anima") and "AnimateDiff" not in name:
|
||||
|
||||
@@ -59,6 +59,23 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict | No
|
||||
torch.xpu.synchronize(devices.device)
|
||||
elif devices.backend in {"cuda", "zluda", "rocm"}:
|
||||
torch.cuda.synchronize(devices.device)
|
||||
|
||||
if shared.state.paused:
|
||||
log.debug('Sampling paused')
|
||||
while shared.state.paused:
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
raise AssertionError('Interrupted...')
|
||||
time.sleep(0.1)
|
||||
|
||||
image = kwargs.get('image', None)
|
||||
if image is not None:
|
||||
shared.state.current_image = image
|
||||
shared.state.current_latent = None
|
||||
shared.state.step() # increase step
|
||||
shared.state.preview_job = -1 # indicate that preview image has changed
|
||||
debug_callback(f'Callback: step={step} timestep={timestep} image={image if image is not None else None} kwargs={list(kwargs)}')
|
||||
return kwargs
|
||||
|
||||
latents = kwargs.get('latents', None)
|
||||
if debug:
|
||||
debug_callback(f'Callback: step={step} timestep={timestep} latents={latents.shape if latents is not None else None} kwargs={list(kwargs)}')
|
||||
@@ -67,12 +84,6 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict | No
|
||||
shared.state.step()
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
raise AssertionError('Interrupted...')
|
||||
if shared.state.paused:
|
||||
log.debug('Sampling paused')
|
||||
while shared.state.paused:
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
raise AssertionError('Interrupted...')
|
||||
time.sleep(0.1)
|
||||
if latents is None:
|
||||
return kwargs
|
||||
elif shared.opts.nan_skip:
|
||||
|
||||
@@ -212,6 +212,8 @@ def _count_steps_below(pipe, threshold):
|
||||
|
||||
|
||||
def correction_callback(p, timestep, kwargs, pipe=None, initial: bool = False, step: int = 0):
|
||||
if pipe and pipe.__class__.__name__ in ['HiDreamO1Pipeline', 'HiDreamO1ImagePipeline']:
|
||||
return kwargs
|
||||
if initial:
|
||||
if not any([p.hdr_clamp, p.hdr_mode, p.hdr_maximize, p.hdr_sharpen, p.hdr_color, p.hdr_brightness, p.hdr_tint_ratio]):
|
||||
p.correction_skip = True
|
||||
|
||||
@@ -184,8 +184,8 @@ class FlashFlowMatchEulerDiscreteScheduler(SchedulerMixin, ConfigMixin):
|
||||
|
||||
def set_timesteps(
|
||||
self,
|
||||
num_inference_steps: int = None,
|
||||
device: Union[str, torch.device] = None,
|
||||
num_inference_steps: Optional[int] = None,
|
||||
device: Optional[Union[str, torch.device]] = None,
|
||||
sigmas: Optional[List[float]] = None,
|
||||
mu: Optional[float] = None,
|
||||
):
|
||||
@@ -288,6 +288,7 @@ class FlashFlowMatchEulerDiscreteScheduler(SchedulerMixin, ConfigMixin):
|
||||
s_tmin: float = 0.0,
|
||||
s_tmax: float = float("inf"),
|
||||
s_noise: float = 1.0,
|
||||
noise_clip_std: float = 0.0,
|
||||
generator: Optional[torch.Generator] = None,
|
||||
return_dict: bool = True,
|
||||
) -> Union[FlashFlowMatchEulerDiscreteSchedulerOutput, Tuple]:
|
||||
@@ -352,7 +353,9 @@ class FlashFlowMatchEulerDiscreteScheduler(SchedulerMixin, ConfigMixin):
|
||||
device=model_output.device,
|
||||
dtype=denoised.dtype,
|
||||
)
|
||||
sample = sigma_next * noise + (1.0 - sigma_next) * denoised
|
||||
if noise_clip_std > 0.0:
|
||||
noise = noise.clamp(-noise_clip_std, noise_clip_std)
|
||||
sample = sigma_next * s_noise * noise + (1.0 - sigma_next) * denoised
|
||||
|
||||
self._step_index += 1
|
||||
sample = sample.to(model_output.dtype)
|
||||
|
||||
@@ -654,8 +654,14 @@ class FlowUniPCMultistepScheduler(SchedulerMixin, ConfigMixin):
|
||||
model_output: torch.Tensor,
|
||||
timestep: Union[int, torch.Tensor],
|
||||
sample: torch.Tensor,
|
||||
s_churn: float = 0.0,
|
||||
s_tmin: float = 0.0,
|
||||
s_tmax: float = float("inf"),
|
||||
s_noise: float = 1.0,
|
||||
noise_clip_std: float = 0.0,
|
||||
return_dict: bool = True,
|
||||
generator=None) -> Union[SchedulerOutput, Tuple]:
|
||||
generator=None,
|
||||
**kwargs) -> Union[SchedulerOutput, Tuple]:
|
||||
"""
|
||||
Predict the sample from the previous timestep by reversing the SDE. This function propagates the sample with
|
||||
the multistep UniPC.
|
||||
|
||||
@@ -87,6 +87,10 @@ def guess_by_name(fn, current_guess):
|
||||
new_guess = 'OmniGen'
|
||||
elif 'sd3' in fn.lower():
|
||||
new_guess = 'Stable Diffusion 3'
|
||||
elif 'hidream-o1' in fn.lower():
|
||||
new_guess = 'HiDreamO1'
|
||||
elif 'hidream' in fn.lower():
|
||||
new_guess = 'HiDream'
|
||||
elif 'hidream' in fn.lower():
|
||||
new_guess = 'HiDream'
|
||||
elif 'zeta-chroma' in fn.lower() or 'zetachroma' in fn.lower():
|
||||
|
||||
@@ -433,6 +433,10 @@ def load_diffuser_force(detected_model_type, checkpoint_info, diffusers_load_con
|
||||
from pipelines.model_omnigen import load_omnigen
|
||||
sd_model = load_omnigen(checkpoint_info, diffusers_load_config)
|
||||
allow_post_quant = False
|
||||
elif model_type in ['HiDreamO1']:
|
||||
from pipelines.model_hidream import load_hidream_o1
|
||||
sd_model = load_hidream_o1(checkpoint_info, diffusers_load_config)
|
||||
allow_post_quant = False
|
||||
elif model_type in ['HiDream']:
|
||||
from pipelines.model_hidream import load_hidream
|
||||
sd_model = load_hidream(checkpoint_info, diffusers_load_config)
|
||||
@@ -1285,6 +1289,8 @@ def set_diffuser_pipe(pipe, new_pipe_type):
|
||||
fn = f'{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access
|
||||
log.trace(f"Pipeline class change requested: target={new_pipe_type} fn={fn}") # pylint: disable=protected-access
|
||||
log.warning(f'Pipeline class change failed: type={new_pipe_type} pipeline={cls} {e}')
|
||||
if debug_load:
|
||||
errors.display(e, 'Pipeline switch')
|
||||
has_errors = True
|
||||
if not hasattr(pipe, 'config') or has_errors:
|
||||
try: # maybe a wrapper pipeline so just change the class
|
||||
@@ -1302,6 +1308,8 @@ def set_diffuser_pipe(pipe, new_pipe_type):
|
||||
return pipe
|
||||
except Exception as e: # pylint: disable=unused-variable
|
||||
log.warning(f'Pipeline class set failed: type={new_pipe_type} pipeline={cls} {e}')
|
||||
if debug_load:
|
||||
errors.display(e, 'Pipeline switch')
|
||||
has_errors = True
|
||||
return pipe
|
||||
|
||||
|
||||
+25
-19
@@ -270,7 +270,7 @@ class State:
|
||||
|
||||
def do_set_current_image(self):
|
||||
from modules import shared, images, sd_samplers_common
|
||||
if (self.current_latent is None) or self.disable_preview or (self.preview_job == self.job_no):
|
||||
if self.disable_preview or (self.preview_job == self.job_no):
|
||||
return False
|
||||
self.preview_job = self.job_no
|
||||
|
||||
@@ -280,27 +280,33 @@ class State:
|
||||
self.preview_job = -1
|
||||
return True
|
||||
|
||||
try:
|
||||
sample = self.current_latent
|
||||
self.current_image_sampling_step = self.sampling_step
|
||||
if self.current_latent is not None:
|
||||
try:
|
||||
if self.current_noise_pred is not None and self.current_sigma is not None and self.current_sigma_next is not None:
|
||||
original_sample = sample - (self.current_noise_pred * (self.current_sigma_next-self.current_sigma))
|
||||
if self.prediction_type in {"epsilon", "flow_prediction"}:
|
||||
sample = original_sample - (self.current_noise_pred * self.current_sigma)
|
||||
elif self.prediction_type == "v_prediction":
|
||||
sample = self.current_noise_pred * (-self.current_sigma / (self.current_sigma**2 + 1) ** 0.5) + (original_sample / (self.current_sigma**2 + 1)) # pylint: disable=invalid-unary-operand-type
|
||||
except Exception:
|
||||
pass # ignore sigma errors
|
||||
image = sd_samplers_common.samples_to_image_grid(sample)
|
||||
self.assign_current_image(image)
|
||||
sample = self.current_latent
|
||||
self.current_image_sampling_step = self.sampling_step
|
||||
try:
|
||||
if self.current_noise_pred is not None and self.current_sigma is not None and self.current_sigma_next is not None:
|
||||
original_sample = sample - (self.current_noise_pred * (self.current_sigma_next-self.current_sigma))
|
||||
if self.prediction_type in {"epsilon", "flow_prediction"}:
|
||||
sample = original_sample - (self.current_noise_pred * self.current_sigma)
|
||||
elif self.prediction_type == "v_prediction":
|
||||
sample = self.current_noise_pred * (-self.current_sigma / (self.current_sigma**2 + 1) ** 0.5) + (original_sample / (self.current_sigma**2 + 1)) # pylint: disable=invalid-unary-operand-type
|
||||
except Exception:
|
||||
pass # ignore sigma errors
|
||||
image = sd_samplers_common.samples_to_image_grid(sample)
|
||||
self.assign_current_image(image)
|
||||
self.preview_job = -1
|
||||
return True
|
||||
except Exception as e:
|
||||
self.preview_job = -1
|
||||
log.error(f'State image: last={self.id_live_preview} step={self.sampling_step} {e}')
|
||||
display(e, 'State image')
|
||||
return False
|
||||
elif self.current_image is not None:
|
||||
self.assign_current_image(self.current_image)
|
||||
self.preview_job = -1
|
||||
return True
|
||||
except Exception as e:
|
||||
self.preview_job = -1
|
||||
log.error(f'State image: last={self.id_live_preview} step={self.sampling_step} {e}')
|
||||
display(e, 'State image')
|
||||
return False
|
||||
return False
|
||||
|
||||
def assign_current_image(self, image):
|
||||
self.current_image = image
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Dict, List, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import PIL.Image
|
||||
import torch
|
||||
from tqdm.rich import tqdm
|
||||
|
||||
from diffusers.pipelines.pipeline_utils import DiffusionPipeline
|
||||
from diffusers.utils import BaseOutput
|
||||
from diffusers.utils.torch_utils import randn_tensor
|
||||
|
||||
from pipelines.hidream.scheduler_flashfloweuler import FlashFlowMatchEulerDiscreteScheduler
|
||||
from pipelines.hidream.scheduler_flowunipc import FlowUniPCMultistepScheduler
|
||||
from pipelines.hidream.hidream_o1_utils import (
|
||||
CONDITION_IMAGE_SIZE,
|
||||
DEFAULT_TIMESTEPS,
|
||||
NOISE_SCALE,
|
||||
PATCH_SIZE,
|
||||
TIMESTEP_TOKEN_NUM,
|
||||
T_EPS,
|
||||
_calculate_dimensions,
|
||||
_ensure_special_tokens,
|
||||
_image_to_patch_tensor,
|
||||
_patches_to_np,
|
||||
_pil_to_normalized_tensor,
|
||||
_resize_pilimage,
|
||||
build_t2i_text_sample,
|
||||
get_rope_index_fix_point,
|
||||
)
|
||||
|
||||
use_flash_attn = False
|
||||
try:
|
||||
import flash_attn # pylint: disable=unused-import
|
||||
use_flash_attn = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class HiDreamO1PipelineOutput(BaseOutput):
|
||||
images: Union[List[PIL.Image.Image], np.ndarray]
|
||||
|
||||
|
||||
class HiDreamO1Pipeline(DiffusionPipeline):
|
||||
model_cpu_offload_seq = "transformer"
|
||||
_callback_tensor_inputs = ["latents"]
|
||||
vae_scale_factor = 1
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transformer,
|
||||
processor,
|
||||
tokenizer,
|
||||
scheduler,
|
||||
):
|
||||
super().__init__()
|
||||
self.register_modules(transformer=transformer, processor=processor, scheduler=scheduler, tokenizer=tokenizer)
|
||||
_ensure_special_tokens(self.tokenizer)
|
||||
|
||||
def _build_scheduler(
|
||||
self,
|
||||
num_inference_steps: int,
|
||||
shift: float,
|
||||
device: torch.device,
|
||||
):
|
||||
if num_inference_steps <= 28:
|
||||
self.scheduler = FlashFlowMatchEulerDiscreteScheduler(num_train_timesteps=1000, shift=shift, use_dynamic_shifting=False)
|
||||
timesteps_list = DEFAULT_TIMESTEPS if num_inference_steps == 28 else None
|
||||
else:
|
||||
self.scheduler = FlowUniPCMultistepScheduler(use_dynamic_shifting=False, shift=shift)
|
||||
timesteps_list = None
|
||||
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
||||
if timesteps_list is not None:
|
||||
self.scheduler.timesteps = torch.tensor(timesteps_list, device=device, dtype=torch.long)
|
||||
sigmas = [t.item() / 1000.0 for t in self.scheduler.timesteps]
|
||||
sigmas.append(0.0)
|
||||
self.scheduler.sigmas = torch.tensor(sigmas, device=device)
|
||||
|
||||
def _prepare_reference_paths(
|
||||
self,
|
||||
image: Optional[Union[PIL.Image.Image, List[PIL.Image.Image]]],
|
||||
ref_images: Optional[Union[PIL.Image.Image, List[PIL.Image.Image]]],
|
||||
) -> List[PIL.Image.Image]:
|
||||
refs: List[PIL.Image.Image] = []
|
||||
if image is not None:
|
||||
if isinstance(image, list):
|
||||
refs.extend(image)
|
||||
else:
|
||||
refs.append(image)
|
||||
if ref_images is not None:
|
||||
if isinstance(ref_images, list):
|
||||
refs.extend(ref_images)
|
||||
else:
|
||||
refs.append(ref_images)
|
||||
return refs
|
||||
|
||||
@torch.no_grad()
|
||||
def __call__(
|
||||
self,
|
||||
prompt: str,
|
||||
negative_prompt: Optional[str] = None,
|
||||
image: Optional[Union[PIL.Image.Image, List[PIL.Image.Image]]] = None,
|
||||
ref_images: Optional[Union[PIL.Image.Image, List[PIL.Image.Image]]] = None,
|
||||
height: int = 1440,
|
||||
width: int = 2560,
|
||||
num_inference_steps: int = 50,
|
||||
guidance_scale: float = 5.0,
|
||||
shift: float = 3.0,
|
||||
timesteps_list: Optional[List[int]] = None,
|
||||
scheduler: Optional[Union[FlashFlowMatchEulerDiscreteScheduler, FlowUniPCMultistepScheduler]] = None,
|
||||
generator: Optional[torch.Generator] = None,
|
||||
seed: Optional[int] = None,
|
||||
noise_scale_start: float = NOISE_SCALE,
|
||||
noise_scale_end: float = NOISE_SCALE,
|
||||
noise_clip_std: float = 0.0,
|
||||
keep_original_aspect: bool = True,
|
||||
callback_on_step_end: Optional[Callable[[DiffusionPipeline, int, int, Dict[str, torch.Tensor]], Dict[str, torch.Tensor]]] = None,
|
||||
callback_on_step_end_tensor_inputs: Optional[List[str]] = None,
|
||||
callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
|
||||
callback_steps: int = 1,
|
||||
output_type: str = "pil",
|
||||
return_dict: bool = True,
|
||||
**kwargs,
|
||||
) -> Union[HiDreamO1PipelineOutput, tuple]:
|
||||
model = self.transformer
|
||||
processor = self.processor
|
||||
tokenizer = self.tokenizer
|
||||
model_config = model.config
|
||||
|
||||
if isinstance(prompt, list):
|
||||
prompt = prompt[0] if len(prompt) > 0 else ""
|
||||
if isinstance(negative_prompt, list):
|
||||
negative_prompt = negative_prompt[0] if len(negative_prompt) > 0 else ""
|
||||
if num_inference_steps <= 28:
|
||||
guidance_scale = 1.0
|
||||
|
||||
device = self._execution_device
|
||||
try:
|
||||
dtype = next(model.parameters()).dtype
|
||||
except (StopIteration, AttributeError, TypeError):
|
||||
dtype = torch.bfloat16
|
||||
|
||||
if callback_on_step_end_tensor_inputs is not None:
|
||||
invalid_inputs = [name for name in callback_on_step_end_tensor_inputs if name not in self._callback_tensor_inputs]
|
||||
if invalid_inputs:
|
||||
raise ValueError(
|
||||
f"callback_on_step_end_tensor_inputs has to be in {self._callback_tensor_inputs}, but found {invalid_inputs}"
|
||||
)
|
||||
|
||||
refs = [img.convert("RGB") for img in self._prepare_reference_paths(image, ref_images)]
|
||||
preresized_ref_pil = None
|
||||
|
||||
if keep_original_aspect and len(refs) >= 1:
|
||||
preresized_ref_pil = _resize_pilimage(refs[0], 2048, PATCH_SIZE)
|
||||
width, height = preresized_ref_pil.size
|
||||
else:
|
||||
width = max(PATCH_SIZE, int(round(width / PATCH_SIZE)) * PATCH_SIZE)
|
||||
height = max(PATCH_SIZE, int(round(height / PATCH_SIZE)) * PATCH_SIZE)
|
||||
|
||||
h_patches = height // PATCH_SIZE
|
||||
w_patches = width // PATCH_SIZE
|
||||
|
||||
if len(refs) == 0:
|
||||
cond_sample = build_t2i_text_sample(prompt, height, width, tokenizer, processor, model_config)
|
||||
uncond_sample = None
|
||||
if guidance_scale > 1.0:
|
||||
uncond_sample = build_t2i_text_sample(negative_prompt or " ", height, width, tokenizer, processor, model_config)
|
||||
|
||||
def to_device(sample):
|
||||
return {k: (v.to(device) if torch.is_tensor(v) else v) for k, v in sample.items()}
|
||||
|
||||
cond_sample = to_device(cond_sample)
|
||||
if uncond_sample is not None:
|
||||
uncond_sample = to_device(uncond_sample)
|
||||
|
||||
ref_patches = None
|
||||
tgt_image_len = (height // PATCH_SIZE) * (width // PATCH_SIZE)
|
||||
samples = [cond_sample]
|
||||
if uncond_sample is not None:
|
||||
samples.append(uncond_sample)
|
||||
else:
|
||||
image_token_id = model_config.image_token_id
|
||||
video_token_id = model_config.video_token_id
|
||||
vision_start_token_id = model_config.vision_start_token_id
|
||||
spatial_merge_size = model_config.vision_config.spatial_merge_size
|
||||
|
||||
ref_pils = [preresized_ref_pil] if preresized_ref_pil is not None else refs
|
||||
k_refs = len(ref_pils)
|
||||
|
||||
max_size = max(height, width)
|
||||
if k_refs == 2:
|
||||
max_size = max_size * 48 // 64
|
||||
elif k_refs <= 4:
|
||||
max_size = max_size // 2
|
||||
elif k_refs <= 8:
|
||||
max_size = max_size * 24 // 64
|
||||
elif k_refs > 8:
|
||||
max_size = max_size // 4
|
||||
|
||||
ref_pils_resized, ref_patches_list = [], []
|
||||
for pil in ref_pils:
|
||||
pil_r = pil if (preresized_ref_pil is not None and pil is preresized_ref_pil) else _resize_pilimage(pil, max_size, PATCH_SIZE)
|
||||
ref_pils_resized.append(pil_r)
|
||||
x = _pil_to_normalized_tensor(pil_r).unsqueeze(0)
|
||||
x = _image_to_patch_tensor(x, patch_size=PATCH_SIZE).squeeze(0)
|
||||
ref_patches_list.append(x)
|
||||
|
||||
ref_image_lens = [img.shape[0] for img in ref_patches_list]
|
||||
total_ref_len = sum(ref_image_lens)
|
||||
ref_patches = torch.cat(ref_patches_list, dim=0).unsqueeze(0).to(device, dtype)
|
||||
|
||||
tgt_image_len = (height // PATCH_SIZE) * (width // PATCH_SIZE)
|
||||
|
||||
cond_img_size = CONDITION_IMAGE_SIZE
|
||||
if k_refs > 4 and k_refs <= 8:
|
||||
cond_img_size = CONDITION_IMAGE_SIZE * 48 // 64
|
||||
elif k_refs > 8:
|
||||
cond_img_size = CONDITION_IMAGE_SIZE // 2
|
||||
|
||||
ref_pils_vlm = []
|
||||
for pil_r in ref_pils_resized:
|
||||
cond_w, cond_h = _calculate_dimensions(cond_img_size, pil_r.width / pil_r.height)
|
||||
ref_pils_vlm.append(pil_r.resize((cond_w, cond_h), resample=PIL.Image.Resampling.LANCZOS))
|
||||
|
||||
image_grid_thw_tgt = torch.tensor([1, height // PATCH_SIZE, width // PATCH_SIZE], dtype=torch.int64).unsqueeze(0)
|
||||
image_grid_thw_ref = torch.zeros((k_refs, 3), dtype=torch.int64)
|
||||
for i, pil_r in enumerate(ref_pils_resized):
|
||||
rw, rh = pil_r.size
|
||||
image_grid_thw_ref[i] = torch.tensor([1, rh // PATCH_SIZE, rw // PATCH_SIZE], dtype=torch.int64)
|
||||
|
||||
samples = []
|
||||
captions = [prompt]
|
||||
if guidance_scale > 1.0:
|
||||
captions.append(negative_prompt or " ")
|
||||
|
||||
for caption in captions:
|
||||
boi_token = getattr(tokenizer, "boi_token", "<|boi_token|>")
|
||||
tms_token = getattr(tokenizer, "tms_token", "<|tms_token|>")
|
||||
|
||||
content = [{"type": "image"} for _ in range(k_refs)]
|
||||
content.append({"type": "text", "text": caption})
|
||||
messages = [{"role": "user", "content": content}]
|
||||
|
||||
template_caption = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
||||
proc = processor(text=[template_caption], images=ref_pils_vlm, padding="longest", return_tensors="pt")
|
||||
|
||||
input_ids_2 = tokenizer.encode(boi_token + tms_token * TIMESTEP_TOKEN_NUM, return_tensors="pt", add_special_tokens=False)
|
||||
input_ids = torch.cat([proc.input_ids, input_ids_2], dim=-1)
|
||||
|
||||
igthw_cond = proc.image_grid_thw.clone()
|
||||
for i in range(k_refs):
|
||||
igthw_cond[i, 1] //= spatial_merge_size
|
||||
igthw_cond[i, 2] //= spatial_merge_size
|
||||
igthw_all = torch.cat([igthw_cond, image_grid_thw_tgt, image_grid_thw_ref], dim=0)
|
||||
|
||||
vision_tokens_list = []
|
||||
vt_tgt = torch.full((1, tgt_image_len), image_token_id, dtype=input_ids.dtype)
|
||||
vt_tgt[0, 0] = vision_start_token_id
|
||||
vision_tokens_list.append(vt_tgt)
|
||||
for ref_len in ref_image_lens:
|
||||
vt_ref = torch.full((1, ref_len), image_token_id, dtype=input_ids.dtype)
|
||||
vt_ref[0, 0] = vision_start_token_id
|
||||
vision_tokens_list.append(vt_ref)
|
||||
vision_tokens = torch.cat(vision_tokens_list, dim=1)
|
||||
input_ids_pad = torch.cat([input_ids, vision_tokens], dim=-1)
|
||||
|
||||
position_ids, _ = get_rope_index_fix_point(
|
||||
1,
|
||||
image_token_id,
|
||||
video_token_id,
|
||||
vision_start_token_id,
|
||||
input_ids=input_ids_pad,
|
||||
image_grid_thw=igthw_all,
|
||||
video_grid_thw=None,
|
||||
attention_mask=None,
|
||||
skip_vision_start_token=[0] * k_refs + [1] + [1] * k_refs,
|
||||
)
|
||||
|
||||
txt_seq_len = input_ids.shape[-1]
|
||||
all_seq_len = position_ids.shape[-1]
|
||||
|
||||
token_types_raw = torch.zeros((1, all_seq_len), dtype=input_ids.dtype)
|
||||
bgn = txt_seq_len - TIMESTEP_TOKEN_NUM
|
||||
end = bgn + tgt_image_len + TIMESTEP_TOKEN_NUM
|
||||
token_types_raw[0, bgn:end] = 1
|
||||
token_types_raw[0, end : end + total_ref_len] = 2
|
||||
token_types_raw[0, txt_seq_len - TIMESTEP_TOKEN_NUM : txt_seq_len] = 3
|
||||
|
||||
vinput_mask = torch.logical_or(token_types_raw == 1, token_types_raw == 2)
|
||||
token_types_bin = (token_types_raw > 0).to(token_types_raw.dtype)
|
||||
|
||||
samples.append(
|
||||
{
|
||||
"input_ids": input_ids_pad.to(device),
|
||||
"position_ids": position_ids.to(device),
|
||||
"token_types": token_types_bin.to(device),
|
||||
"vinput_mask": vinput_mask.to(device),
|
||||
"pixel_values": proc.pixel_values.to(device, dtype),
|
||||
"image_grid_thw": proc.image_grid_thw.to(device),
|
||||
}
|
||||
)
|
||||
|
||||
if generator is None:
|
||||
generator = torch.Generator()
|
||||
if seed is not None:
|
||||
generator.manual_seed(seed + 1)
|
||||
|
||||
noise = noise_scale_start * randn_tensor((1, 3, height, width), generator=generator, device=device, dtype=dtype)
|
||||
z = _image_to_patch_tensor(noise, patch_size=PATCH_SIZE)
|
||||
|
||||
|
||||
if scheduler is None:
|
||||
self._build_scheduler(
|
||||
num_inference_steps=num_inference_steps,
|
||||
shift=shift,
|
||||
device=device,
|
||||
)
|
||||
|
||||
num_steps = len(self.scheduler.timesteps)
|
||||
if num_steps > 1:
|
||||
noise_scale_schedule = [noise_scale_start + (noise_scale_end - noise_scale_start) * i / (num_steps - 1) for i in range(num_steps)]
|
||||
else:
|
||||
noise_scale_schedule = [noise_scale_start]
|
||||
|
||||
def forward_once(sample: Dict[str, torch.Tensor], z_in: torch.Tensor, t_pixeldit: torch.Tensor):
|
||||
kwargs: Dict[str, Any] = {
|
||||
"input_ids": sample["input_ids"],
|
||||
"position_ids": sample["position_ids"],
|
||||
"vinputs": z_in,
|
||||
"timestep": t_pixeldit.reshape(-1).to(device),
|
||||
"token_types": sample["token_types"],
|
||||
}
|
||||
if use_flash_attn is not None:
|
||||
kwargs["use_flash_attn"] = use_flash_attn
|
||||
if "pixel_values" in sample:
|
||||
kwargs["pixel_values"] = sample["pixel_values"]
|
||||
if "image_grid_thw" in sample:
|
||||
kwargs["image_grid_thw"] = sample["image_grid_thw"]
|
||||
|
||||
outputs = model(**kwargs)
|
||||
x_pred = outputs.x_pred
|
||||
if ref_patches is None:
|
||||
return x_pred[0, sample["vinput_mask"][0]].unsqueeze(0)
|
||||
return x_pred[0, sample["vinput_mask"][0]][:tgt_image_len].unsqueeze(0)
|
||||
|
||||
preview_x0 = None
|
||||
for step_idx, step_t in enumerate(tqdm(self.scheduler.timesteps, desc="Processing", unit="it")):
|
||||
t_pixeldit = 1.0 - step_t.float() / 1000.0
|
||||
sigma = (step_t.float() / 1000.0).to(dtype=torch.float32).clamp_min(T_EPS)
|
||||
|
||||
if ref_patches is None:
|
||||
x_pred_cond = forward_once(samples[0], z.clone(), t_pixeldit)
|
||||
v_cond = (x_pred_cond.to(dtype=torch.float32) - z.to(dtype=torch.float32)) / sigma
|
||||
if len(samples) > 1:
|
||||
x_pred_uncond = forward_once(samples[1], z.clone(), t_pixeldit)
|
||||
v_uncond = (x_pred_uncond.to(dtype=torch.float32) - z.to(dtype=torch.float32)) / sigma
|
||||
v_guided = v_uncond + guidance_scale * (v_cond - v_uncond)
|
||||
else:
|
||||
v_guided = v_cond
|
||||
preview_x0 = x_pred_cond
|
||||
else:
|
||||
vinputs = torch.cat([z, ref_patches], dim=1)
|
||||
x_vis_list = [forward_once(sample, vinputs, t_pixeldit) for sample in samples]
|
||||
x_vis_stacked = torch.cat(x_vis_list, dim=0)
|
||||
|
||||
z_rep = z.expand(len(samples), -1, -1)
|
||||
v_pred = (x_vis_stacked.to(dtype=torch.float32) - z_rep.to(dtype=torch.float32)) / sigma
|
||||
v_cond = v_pred[0:1]
|
||||
if len(samples) > 1:
|
||||
v_uncond = v_pred[1:]
|
||||
v_guided = v_uncond + guidance_scale * (v_cond - v_uncond)
|
||||
else:
|
||||
v_guided = v_cond
|
||||
preview_x0 = x_vis_list[0]
|
||||
|
||||
model_output = -v_guided
|
||||
if num_inference_steps <= 28:
|
||||
z = self.scheduler.step(
|
||||
model_output.float(),
|
||||
step_t.to(dtype=torch.float32),
|
||||
z.float(),
|
||||
s_noise=noise_scale_schedule[step_idx],
|
||||
noise_clip_std=noise_clip_std,
|
||||
return_dict=False,
|
||||
)[0].to(dtype)
|
||||
else:
|
||||
z = self.scheduler.step(model_output.float(), step_t.to(dtype=torch.float32), z.float(), return_dict=False)[0].to(dtype)
|
||||
|
||||
if callback_on_step_end is not None:
|
||||
pil_image = PIL.Image.fromarray(_patches_to_np(preview_x0, h_patches, w_patches, invert=False))
|
||||
callback_on_step_end(self, step_idx, int(step_t.item()), {"image": pil_image})
|
||||
|
||||
if output_type == "pil":
|
||||
output_images = [PIL.Image.fromarray(_patches_to_np(preview_x0, h_patches, w_patches, invert=False, rescale=False))]
|
||||
elif output_type == "np":
|
||||
output_images = [_patches_to_np(preview_x0, h_patches, w_patches, invert=True, rescale=True)]
|
||||
else:
|
||||
raise ValueError(f"Unsupported output_type={output_type!r}; supported values are 'pil' and 'np'")
|
||||
|
||||
if not return_dict:
|
||||
return (output_images,)
|
||||
return HiDreamO1PipelineOutput(images=output_images)
|
||||
|
||||
|
||||
class HiDreamO1ImagePipeline(HiDreamO1Pipeline):
|
||||
def __call__(
|
||||
self,
|
||||
prompt: str,
|
||||
negative_prompt: Optional[str] = None,
|
||||
image: Optional[Union[PIL.Image.Image, List[PIL.Image.Image]]] = None,
|
||||
height: int = 1440,
|
||||
width: int = 2560,
|
||||
num_inference_steps: int = 50,
|
||||
guidance_scale: float = 5.0,
|
||||
shift: float = 3.0,
|
||||
timesteps_list: Optional[List[int]] = None,
|
||||
scheduler: Optional[Union[FlashFlowMatchEulerDiscreteScheduler, FlowUniPCMultistepScheduler]] = None,
|
||||
generator: Optional[torch.Generator] = None,
|
||||
seed: Optional[int] = None,
|
||||
noise_scale_start: float = NOISE_SCALE,
|
||||
noise_scale_end: float = NOISE_SCALE,
|
||||
noise_clip_std: float = 0.0,
|
||||
keep_original_aspect: bool = True,
|
||||
use_flash_attn: Optional[bool] = None,
|
||||
callback: Optional[Callable[[int, int, Callable[[], PIL.Image.Image]], None]] = None,
|
||||
output_type: str = "pil",
|
||||
return_dict: bool = True,
|
||||
**kwargs,
|
||||
) -> Union[HiDreamO1PipelineOutput, tuple]:
|
||||
# image is list, first entry should go to image and remaining to ref_images
|
||||
ref_images = []
|
||||
if isinstance(image, list):
|
||||
ref_images = image[1:]
|
||||
image = image[0] if len(image) > 0 else None
|
||||
return super().__call__(
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
image=image,
|
||||
ref_images=ref_images,
|
||||
height=height,
|
||||
width=width,
|
||||
num_inference_steps=num_inference_steps,
|
||||
guidance_scale=guidance_scale,
|
||||
shift=shift,
|
||||
timesteps_list=timesteps_list,
|
||||
scheduler=scheduler,
|
||||
generator=generator,
|
||||
seed=seed,
|
||||
noise_scale_start=noise_scale_start,
|
||||
noise_scale_end=noise_scale_end,
|
||||
noise_clip_std=noise_clip_std,
|
||||
keep_original_aspect=keep_original_aspect,
|
||||
callback=callback,
|
||||
output_type=output_type,
|
||||
return_dict=return_dict,
|
||||
**kwargs,
|
||||
)
|
||||
@@ -0,0 +1,369 @@
|
||||
import math
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
import PIL.Image
|
||||
import torch
|
||||
|
||||
|
||||
TIMESTEP_TOKEN_NUM = 1
|
||||
NOISE_SCALE = 8.0
|
||||
T_EPS = 0.001
|
||||
CONDITION_IMAGE_SIZE = 384
|
||||
PATCH_SIZE = 32
|
||||
|
||||
DEFAULT_TIMESTEPS = [
|
||||
999,
|
||||
987,
|
||||
974,
|
||||
960,
|
||||
945,
|
||||
929,
|
||||
913,
|
||||
895,
|
||||
877,
|
||||
857,
|
||||
836,
|
||||
814,
|
||||
790,
|
||||
764,
|
||||
737,
|
||||
707,
|
||||
675,
|
||||
640,
|
||||
602,
|
||||
560,
|
||||
515,
|
||||
464,
|
||||
409,
|
||||
347,
|
||||
278,
|
||||
199,
|
||||
110,
|
||||
8,
|
||||
]
|
||||
|
||||
PREDEFINED_RESOLUTIONS = [
|
||||
(2048, 2048),
|
||||
(2304, 1728),
|
||||
(1728, 2304),
|
||||
(2560, 1440),
|
||||
(1440, 2560),
|
||||
(2496, 1664),
|
||||
(1664, 2496),
|
||||
(3104, 1312),
|
||||
(1312, 3104),
|
||||
(2304, 1792),
|
||||
(1792, 2304),
|
||||
]
|
||||
|
||||
|
||||
def _ensure_special_tokens(tokenizer):
|
||||
if not hasattr(tokenizer, "boi_token"):
|
||||
tokenizer.boi_token = "<|boi_token|>"
|
||||
if not hasattr(tokenizer, "bor_token"):
|
||||
tokenizer.bor_token = "<|bor_token|>"
|
||||
if not hasattr(tokenizer, "eor_token"):
|
||||
tokenizer.eor_token = "<|eor_token|>"
|
||||
if not hasattr(tokenizer, "bot_token"):
|
||||
tokenizer.bot_token = "<|bot_token|>"
|
||||
if not hasattr(tokenizer, "tms_token"):
|
||||
tokenizer.tms_token = "<|tms_token|>"
|
||||
|
||||
|
||||
def _find_closest_resolution(width: int, height: int):
|
||||
img_ratio = width / height
|
||||
best_res = PREDEFINED_RESOLUTIONS[0]
|
||||
min_diff = float("inf")
|
||||
for w, h in PREDEFINED_RESOLUTIONS:
|
||||
diff = abs((w / h) - img_ratio)
|
||||
if diff < min_diff:
|
||||
min_diff = diff
|
||||
best_res = (w, h)
|
||||
return best_res
|
||||
|
||||
|
||||
def _resize_pilimage(
|
||||
pil_image: PIL.Image.Image,
|
||||
image_size: int,
|
||||
patch_size: int = 16,
|
||||
resampler: PIL.Image.Resampling = PIL.Image.Resampling.BICUBIC,
|
||||
):
|
||||
while min(*pil_image.size) >= 2 * image_size:
|
||||
pil_image = pil_image.resize(tuple(x // 2 for x in pil_image.size), resample=PIL.Image.Resampling.BOX)
|
||||
|
||||
width, height = pil_image.width, pil_image.height
|
||||
max_area = image_size * image_size
|
||||
scale = math.sqrt(max_area / (width * height))
|
||||
|
||||
m = patch_size
|
||||
new_sizes = [
|
||||
(round(width * scale) // m * m, round(height * scale) // m * m),
|
||||
(round(width * scale) // m * m, math.floor(height * scale) // m * m),
|
||||
(math.floor(width * scale) // m * m, round(height * scale) // m * m),
|
||||
(math.floor(width * scale) // m * m, math.floor(height * scale) // m * m),
|
||||
]
|
||||
new_sizes = sorted(new_sizes, key=lambda x: x[0] * x[1], reverse=True)
|
||||
|
||||
new_size = new_sizes[-1]
|
||||
for candidate in new_sizes:
|
||||
if candidate[0] * candidate[1] <= max_area:
|
||||
new_size = candidate
|
||||
break
|
||||
|
||||
s1 = width / new_size[0]
|
||||
s2 = height / new_size[1]
|
||||
if s1 < s2:
|
||||
pil_image = pil_image.resize([new_size[0], round(height / s1)], resample=resampler)
|
||||
top = (round(height / s1) - new_size[1]) // 2
|
||||
pil_image = pil_image.crop((0, top, new_size[0], top + new_size[1]))
|
||||
else:
|
||||
pil_image = pil_image.resize([round(width / s2), new_size[1]], resample=resampler)
|
||||
left = (round(width / s2) - new_size[0]) // 2
|
||||
pil_image = pil_image.crop((left, 0, left + new_size[0], new_size[1]))
|
||||
|
||||
return pil_image
|
||||
|
||||
|
||||
def _calculate_dimensions(max_size: int, ratio: float):
|
||||
width = math.sqrt(max_size * max_size * ratio)
|
||||
height = width / ratio
|
||||
width = int(width / 32) * 32
|
||||
height = int(height / 32) * 32
|
||||
return width, height
|
||||
|
||||
|
||||
def _image_to_patch_tensor(x: torch.Tensor, patch_size: int = PATCH_SIZE) -> torch.Tensor:
|
||||
b, c, h, w = x.shape
|
||||
h_patch = h // patch_size
|
||||
w_patch = w // patch_size
|
||||
x = x.reshape(b, c, h_patch, patch_size, w_patch, patch_size)
|
||||
x = x.permute(0, 2, 4, 1, 3, 5)
|
||||
return x.reshape(b, h_patch * w_patch, c * patch_size * patch_size)
|
||||
|
||||
|
||||
def _patch_tensor_to_image(x: torch.Tensor, h_patches: int, w_patches: int, patch_size: int = PATCH_SIZE) -> torch.Tensor:
|
||||
b = x.shape[0]
|
||||
c = x.shape[-1] // (patch_size * patch_size)
|
||||
x = x.reshape(b, h_patches, w_patches, c, patch_size, patch_size)
|
||||
x = x.permute(0, 3, 1, 4, 2, 5)
|
||||
return x.reshape(b, c, h_patches * patch_size, w_patches * patch_size)
|
||||
|
||||
|
||||
def _pil_to_normalized_tensor(image: PIL.Image.Image) -> torch.Tensor:
|
||||
arr = np.array(image.convert("RGB"), dtype=np.float32) / 255.0
|
||||
x = torch.from_numpy(arr).permute(2, 0, 1)
|
||||
return x * 2.0 - 1.0
|
||||
|
||||
|
||||
def _patches_to_np(
|
||||
z: torch.Tensor,
|
||||
h_patches: int,
|
||||
w_patches: int,
|
||||
invert: bool = True,
|
||||
rescale: bool = False,
|
||||
) -> np.ndarray:
|
||||
z = z.float()
|
||||
if rescale:
|
||||
clip_ratio = (torch.abs(z) > 1.0).float().mean().item() # hidream-o1 often has outlines and clipping
|
||||
if clip_ratio > 0.10: # Balanced quantiles and headroom: compress outliers while preserving contrast.
|
||||
lo_q, hi_q, headroom = 0.03, 0.97, 1.33
|
||||
elif clip_ratio > 0.03:
|
||||
lo_q, hi_q, headroom = 0.02, 0.98, 1.22
|
||||
else:
|
||||
lo_q, hi_q, headroom = 0.01, 0.99, 1.15
|
||||
z_flat = z.reshape(-1)
|
||||
q_lo = torch.quantile(z_flat, lo_q)
|
||||
q_hi = torch.quantile(z_flat, hi_q)
|
||||
center = (q_hi + q_lo) * 0.5
|
||||
half_range = (q_hi - q_lo) * 0.5
|
||||
if half_range > 0:
|
||||
z = (z - center) / torch.clamp(half_range * headroom, min=0.35) # Balanced robust remap: moderate compression to handle outliers without losing contrast.
|
||||
z = z.clamp(-1.0, 1.0)
|
||||
image = (1.0 - z) / 2.0 if invert else (z + 1.0) / 2.0
|
||||
image = _patch_tensor_to_image(image, h_patches=h_patches, w_patches=w_patches, patch_size=PATCH_SIZE)
|
||||
np_image = image[0].cpu().numpy().transpose(1, 2, 0)
|
||||
np_image = np.round(255.0 * np_image).astype(np.uint8)
|
||||
return np_image
|
||||
|
||||
|
||||
def get_rope_index_fix_point(
|
||||
spatial_merge_size,
|
||||
image_token_id,
|
||||
video_token_id,
|
||||
vision_start_token_id,
|
||||
input_ids: Optional[torch.LongTensor] = None,
|
||||
image_grid_thw: Optional[torch.LongTensor] = None,
|
||||
video_grid_thw: Optional[torch.LongTensor] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
skip_vision_start_token=None,
|
||||
fix_point=4096,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
if video_grid_thw is not None:
|
||||
video_grid_thw = torch.repeat_interleave(video_grid_thw, video_grid_thw[:, 0], dim=0)
|
||||
video_grid_thw[:, 0] = 1
|
||||
|
||||
mrope_position_deltas = []
|
||||
if input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None):
|
||||
total_input_ids = input_ids
|
||||
if attention_mask is None:
|
||||
attention_mask = torch.ones_like(total_input_ids)
|
||||
position_ids = torch.ones(
|
||||
3,
|
||||
input_ids.shape[0],
|
||||
input_ids.shape[1],
|
||||
dtype=input_ids.dtype,
|
||||
device=input_ids.device,
|
||||
)
|
||||
image_index, video_index = 0, 0
|
||||
attention_mask = attention_mask.to(total_input_ids.device)
|
||||
for i, input_ids_i in enumerate(total_input_ids):
|
||||
input_ids_i = input_ids_i[attention_mask[i] == 1]
|
||||
image_nums, video_nums = 0, 0
|
||||
vision_start_indices = torch.argwhere(input_ids_i == vision_start_token_id).squeeze(1)
|
||||
vision_tokens = input_ids_i[vision_start_indices + 1]
|
||||
image_nums = (vision_tokens == image_token_id).sum()
|
||||
video_nums = (vision_tokens == video_token_id).sum()
|
||||
input_tokens = input_ids_i.tolist()
|
||||
llm_pos_ids_list: list = []
|
||||
st = 0
|
||||
remain_images, remain_videos = image_nums, video_nums
|
||||
for _ in range(image_nums + video_nums):
|
||||
if image_token_id in input_tokens and remain_images > 0:
|
||||
ed_image = input_tokens.index(image_token_id, st)
|
||||
else:
|
||||
ed_image = len(input_tokens) + 1
|
||||
if video_token_id in input_tokens and remain_videos > 0:
|
||||
ed_video = input_tokens.index(video_token_id, st)
|
||||
else:
|
||||
ed_video = len(input_tokens) + 1
|
||||
if ed_image < ed_video:
|
||||
t, h, w = image_grid_thw[image_index]
|
||||
image_index += 1
|
||||
remain_images -= 1
|
||||
ed = ed_image
|
||||
else:
|
||||
t, h, w = video_grid_thw[video_index]
|
||||
video_index += 1
|
||||
remain_videos -= 1
|
||||
ed = ed_video
|
||||
|
||||
llm_grid_t, llm_grid_h, llm_grid_w = (
|
||||
t.item(),
|
||||
h.item() // spatial_merge_size,
|
||||
w.item() // spatial_merge_size,
|
||||
)
|
||||
text_len = ed - st
|
||||
|
||||
text_len -= skip_vision_start_token[image_index - 1]
|
||||
text_len = max(0, text_len)
|
||||
|
||||
st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
|
||||
llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx)
|
||||
|
||||
t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten()
|
||||
h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten()
|
||||
w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten()
|
||||
|
||||
if skip_vision_start_token[image_index - 1]:
|
||||
if fix_point > 0:
|
||||
fix_point = fix_point - st_idx
|
||||
llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + fix_point + st_idx)
|
||||
fix_point = 0
|
||||
else:
|
||||
llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx)
|
||||
st = ed + llm_grid_t * llm_grid_h * llm_grid_w
|
||||
|
||||
if st < len(input_tokens):
|
||||
st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
|
||||
text_len = len(input_tokens) - st
|
||||
llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx)
|
||||
|
||||
llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1)
|
||||
position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device)
|
||||
mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i]))
|
||||
|
||||
mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1)
|
||||
return position_ids, mrope_position_deltas
|
||||
|
||||
if attention_mask is not None:
|
||||
position_ids = attention_mask.long().cumsum(-1) - 1
|
||||
position_ids.masked_fill_(attention_mask == 0, 1)
|
||||
position_ids = position_ids.unsqueeze(0).expand(3, -1, -1).to(attention_mask.device)
|
||||
max_position_ids = position_ids.max(0, keepdim=False)[0].max(-1, keepdim=True)[0]
|
||||
mrope_position_deltas = max_position_ids + 1 - attention_mask.shape[-1]
|
||||
else:
|
||||
position_ids = torch.arange(input_ids.shape[1], device=input_ids.device).view(1, 1, -1).expand(3, input_ids.shape[0], -1)
|
||||
mrope_position_deltas = torch.zeros([input_ids.shape[0], 1], device=input_ids.device, dtype=input_ids.dtype)
|
||||
return position_ids, mrope_position_deltas
|
||||
|
||||
|
||||
def build_t2i_text_sample(prompt, height, width, tokenizer, processor, model_config):
|
||||
image_token_id = model_config.image_token_id
|
||||
video_token_id = model_config.video_token_id
|
||||
vision_start_token_id = model_config.vision_start_token_id
|
||||
image_len = (height // PATCH_SIZE) * (width // PATCH_SIZE)
|
||||
|
||||
boi_token = getattr(tokenizer, "boi_token", "<|boi_token|>")
|
||||
tms_token = getattr(tokenizer, "tms_token", "<|tms_token|>")
|
||||
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
template_caption = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) + boi_token + tms_token * TIMESTEP_TOKEN_NUM
|
||||
input_ids = tokenizer.encode(template_caption, return_tensors="pt", add_special_tokens=False)
|
||||
|
||||
image_grid_thw = torch.tensor([1, height // PATCH_SIZE, width // PATCH_SIZE], dtype=torch.int64).unsqueeze(0)
|
||||
|
||||
vision_tokens = torch.zeros((1, image_len), dtype=input_ids.dtype) + image_token_id
|
||||
vision_tokens[0, 0] = vision_start_token_id
|
||||
input_ids_pad = torch.cat([input_ids, vision_tokens], dim=-1)
|
||||
|
||||
position_ids, _ = get_rope_index_fix_point(
|
||||
1,
|
||||
image_token_id,
|
||||
video_token_id,
|
||||
vision_start_token_id,
|
||||
input_ids=input_ids_pad,
|
||||
image_grid_thw=image_grid_thw,
|
||||
video_grid_thw=None,
|
||||
attention_mask=None,
|
||||
skip_vision_start_token=[1],
|
||||
)
|
||||
|
||||
txt_seq_len = input_ids.shape[-1]
|
||||
all_seq_len = position_ids.shape[-1]
|
||||
|
||||
token_types = torch.zeros((1, all_seq_len), dtype=input_ids.dtype)
|
||||
bgn = txt_seq_len - TIMESTEP_TOKEN_NUM
|
||||
token_types[0, bgn : bgn + image_len + TIMESTEP_TOKEN_NUM] = 1
|
||||
token_types[0, txt_seq_len - TIMESTEP_TOKEN_NUM : txt_seq_len] = 3
|
||||
|
||||
vinput_mask = token_types == 1
|
||||
token_types_bin = (token_types > 0).to(token_types.dtype)
|
||||
|
||||
return {
|
||||
"input_ids": input_ids_pad,
|
||||
"position_ids": position_ids,
|
||||
"token_types": token_types_bin,
|
||||
"vinput_mask": vinput_mask,
|
||||
}
|
||||
|
||||
|
||||
__all__ = [
|
||||
"CONDITION_IMAGE_SIZE",
|
||||
"DEFAULT_TIMESTEPS",
|
||||
"NOISE_SCALE",
|
||||
"PATCH_SIZE",
|
||||
"PREDEFINED_RESOLUTIONS",
|
||||
"TIMESTEP_TOKEN_NUM",
|
||||
"T_EPS",
|
||||
"_calculate_dimensions",
|
||||
"_ensure_special_tokens",
|
||||
"_find_closest_resolution",
|
||||
"_image_to_patch_tensor",
|
||||
"_patch_tensor_to_image",
|
||||
"_patches_to_np",
|
||||
"_pil_to_normalized_tensor",
|
||||
"_resize_pilimage",
|
||||
"build_t2i_text_sample",
|
||||
"get_rope_index_fix_point",
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,445 @@
|
||||
# Copyright 2024 Stability AI, Katherine Crowson and The HuggingFace Team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
||||
from diffusers.utils import BaseOutput, is_scipy_available, logging
|
||||
from diffusers.utils.torch_utils import randn_tensor
|
||||
|
||||
if is_scipy_available():
|
||||
import scipy.stats
|
||||
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def hack_randn_like(*args, **kwargs):
|
||||
# for dist broadcast
|
||||
value = torch.randn_like(*args, **kwargs)
|
||||
return value
|
||||
|
||||
|
||||
def hack_randn_tensor(*args, **kwargs):
|
||||
# for dist broadcast
|
||||
value = randn_tensor(*args, **kwargs)
|
||||
return value
|
||||
|
||||
|
||||
@dataclass
|
||||
class FlashFlowMatchEulerDiscreteSchedulerOutput(BaseOutput):
|
||||
"""
|
||||
Output class for the scheduler's `step` function output.
|
||||
|
||||
Args:
|
||||
prev_sample (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)` for images):
|
||||
Computed sample `(x_{t-1})` of previous timestep. `prev_sample` should be used as next model input in the
|
||||
denoising loop.
|
||||
"""
|
||||
|
||||
prev_sample: torch.FloatTensor
|
||||
|
||||
|
||||
class FlashFlowMatchEulerDiscreteScheduler(SchedulerMixin, ConfigMixin):
|
||||
"""
|
||||
Euler scheduler.
|
||||
|
||||
This model inherits from [`SchedulerMixin`] and [`ConfigMixin`]. Check the superclass documentation for the generic
|
||||
methods the library implements for all schedulers such as loading and saving.
|
||||
|
||||
Args:
|
||||
num_train_timesteps (`int`, defaults to 1000):
|
||||
The number of diffusion steps to train the model.
|
||||
timestep_spacing (`str`, defaults to `"linspace"`):
|
||||
The way the timesteps should be scaled. Refer to Table 2 of the [Common Diffusion Noise Schedules and
|
||||
Sample Steps are Flawed](https://huggingface.co/papers/2305.08891) for more information.
|
||||
shift (`float`, defaults to 1.0):
|
||||
The shift value for the timestep schedule.
|
||||
"""
|
||||
|
||||
_compatibles = []
|
||||
order = 1
|
||||
|
||||
@register_to_config
|
||||
def __init__(
|
||||
self,
|
||||
num_train_timesteps: int = 1000,
|
||||
shift: float = 1.0,
|
||||
use_dynamic_shifting=True,
|
||||
base_shift: Optional[float] = 0.5,
|
||||
max_shift: Optional[float] = 1.15,
|
||||
base_image_seq_len: Optional[int] = 256,
|
||||
max_image_seq_len: Optional[int] = 4096,
|
||||
invert_sigmas: bool = False,
|
||||
use_karras_sigmas: Optional[bool] = False,
|
||||
use_exponential_sigmas: Optional[bool] = False,
|
||||
use_beta_sigmas: Optional[bool] = False,
|
||||
):
|
||||
if self.config.use_beta_sigmas and not is_scipy_available():
|
||||
raise ImportError("Make sure to install scipy if you want to use beta sigmas.")
|
||||
if sum([self.config.use_beta_sigmas, self.config.use_exponential_sigmas, self.config.use_karras_sigmas]) > 1:
|
||||
raise ValueError(
|
||||
"Only one of `config.use_beta_sigmas`, `config.use_exponential_sigmas`, `config.use_karras_sigmas` can be used."
|
||||
)
|
||||
timesteps = np.linspace(1, num_train_timesteps, num_train_timesteps, dtype=np.float32)[::-1].copy()
|
||||
timesteps = torch.from_numpy(timesteps).to(dtype=torch.float32)
|
||||
|
||||
sigmas = timesteps / num_train_timesteps
|
||||
if not use_dynamic_shifting:
|
||||
# when use_dynamic_shifting is True, we apply the timestep shifting on the fly based on the image resolution
|
||||
sigmas = shift * sigmas / (1 + (shift - 1) * sigmas)
|
||||
|
||||
self.timesteps = sigmas * num_train_timesteps
|
||||
|
||||
self._step_index = None
|
||||
self._begin_index = None
|
||||
|
||||
self.sigmas = sigmas.to("cpu") # to avoid too much CPU/GPU communication
|
||||
self.sigma_min = self.sigmas[-1].item()
|
||||
self.sigma_max = self.sigmas[0].item()
|
||||
|
||||
@property
|
||||
def step_index(self):
|
||||
"""
|
||||
The index counter for current timestep. It will increase 1 after each scheduler step.
|
||||
"""
|
||||
return self._step_index
|
||||
|
||||
@property
|
||||
def begin_index(self):
|
||||
"""
|
||||
The index for the first timestep. It should be set from pipeline with `set_begin_index` method.
|
||||
"""
|
||||
return self._begin_index
|
||||
|
||||
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.set_begin_index
|
||||
def set_begin_index(self, begin_index: int = 0):
|
||||
"""
|
||||
Sets the begin index for the scheduler. This function should be run from pipeline before the inference.
|
||||
|
||||
Args:
|
||||
begin_index (`int`):
|
||||
The begin index for the scheduler.
|
||||
"""
|
||||
self._begin_index = begin_index
|
||||
|
||||
def scale_noise(
|
||||
self,
|
||||
sample: torch.FloatTensor,
|
||||
timestep: Union[float, torch.FloatTensor],
|
||||
noise: Optional[torch.FloatTensor] = None,
|
||||
) -> torch.FloatTensor:
|
||||
"""
|
||||
Forward process in flow-matching
|
||||
|
||||
Args:
|
||||
sample (`torch.FloatTensor`):
|
||||
The input sample.
|
||||
timestep (`int`, *optional*):
|
||||
The current timestep in the diffusion chain.
|
||||
|
||||
Returns:
|
||||
`torch.FloatTensor`:
|
||||
A scaled input sample.
|
||||
"""
|
||||
# Make sure sigmas and timesteps have the same device and dtype as original_samples
|
||||
sigmas = self.sigmas.to(device=sample.device, dtype=sample.dtype)
|
||||
|
||||
if sample.device.type == "mps" and torch.is_floating_point(timestep):
|
||||
# mps does not support float64
|
||||
schedule_timesteps = self.timesteps.to(sample.device, dtype=torch.float32)
|
||||
timestep = timestep.to(sample.device, dtype=torch.float32)
|
||||
else:
|
||||
schedule_timesteps = self.timesteps.to(sample.device)
|
||||
timestep = timestep.to(sample.device)
|
||||
|
||||
# self.begin_index is None when scheduler is used for training, or pipeline does not implement set_begin_index
|
||||
if self.begin_index is None:
|
||||
step_indices = [self.index_for_timestep(t, schedule_timesteps) for t in timestep]
|
||||
elif self.step_index is not None:
|
||||
# add_noise is called after first denoising step (for inpainting)
|
||||
step_indices = [self.step_index] * timestep.shape[0]
|
||||
else:
|
||||
# add noise is called before first denoising step to create initial latent(img2img)
|
||||
step_indices = [self.begin_index] * timestep.shape[0]
|
||||
|
||||
sigma = sigmas[step_indices].flatten()
|
||||
while len(sigma.shape) < len(sample.shape):
|
||||
sigma = sigma.unsqueeze(-1)
|
||||
|
||||
sample = sigma * noise + (1.0 - sigma) * sample
|
||||
|
||||
return sample
|
||||
|
||||
def _sigma_to_t(self, sigma):
|
||||
return sigma * self.config.num_train_timesteps
|
||||
|
||||
def time_shift(self, mu: float, sigma: float, t: torch.Tensor):
|
||||
return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma)
|
||||
|
||||
def set_timesteps(
|
||||
self,
|
||||
num_inference_steps: int = None,
|
||||
device: Union[str, torch.device] = None,
|
||||
sigmas: Optional[List[float]] = None,
|
||||
mu: Optional[float] = None,
|
||||
):
|
||||
"""
|
||||
Sets the discrete timesteps used for the diffusion chain (to be run before inference).
|
||||
|
||||
Args:
|
||||
num_inference_steps (`int`):
|
||||
The number of diffusion steps used when generating samples with a pre-trained model.
|
||||
device (`str` or `torch.device`, *optional*):
|
||||
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
|
||||
"""
|
||||
if self.config.use_dynamic_shifting and mu is None:
|
||||
raise ValueError(" you have a pass a value for `mu` when `use_dynamic_shifting` is set to be `True`")
|
||||
|
||||
if sigmas is None:
|
||||
timesteps = np.linspace(
|
||||
self._sigma_to_t(self.sigma_max), self._sigma_to_t(self.sigma_min), num_inference_steps
|
||||
)
|
||||
|
||||
sigmas = timesteps / self.config.num_train_timesteps
|
||||
else:
|
||||
sigmas = np.array(sigmas).astype(np.float32)
|
||||
num_inference_steps = len(sigmas)
|
||||
self.num_inference_steps = num_inference_steps
|
||||
|
||||
if self.config.use_dynamic_shifting:
|
||||
sigmas = self.time_shift(mu, 1.0, sigmas)
|
||||
else:
|
||||
sigmas = self.config.shift * sigmas / (1 + (self.config.shift - 1) * sigmas)
|
||||
|
||||
if self.config.use_karras_sigmas:
|
||||
sigmas = self._convert_to_karras(in_sigmas=sigmas, num_inference_steps=num_inference_steps)
|
||||
|
||||
elif self.config.use_exponential_sigmas:
|
||||
sigmas = self._convert_to_exponential(in_sigmas=sigmas, num_inference_steps=num_inference_steps)
|
||||
|
||||
elif self.config.use_beta_sigmas:
|
||||
sigmas = self._convert_to_beta(in_sigmas=sigmas, num_inference_steps=num_inference_steps)
|
||||
|
||||
sigmas = torch.from_numpy(sigmas).to(dtype=torch.float32, device=device)
|
||||
timesteps = sigmas * self.config.num_train_timesteps
|
||||
|
||||
if self.config.invert_sigmas:
|
||||
sigmas = 1.0 - sigmas
|
||||
timesteps = sigmas * self.config.num_train_timesteps
|
||||
sigmas = torch.cat([sigmas, torch.ones(1, device=sigmas.device)])
|
||||
else:
|
||||
sigmas = torch.cat([sigmas, torch.zeros(1, device=sigmas.device)])
|
||||
|
||||
self.timesteps = timesteps.to(device=device)
|
||||
self.sigmas = sigmas
|
||||
self._step_index = None
|
||||
self._begin_index = None
|
||||
|
||||
def index_for_timestep(self, timestep, schedule_timesteps=None):
|
||||
if schedule_timesteps is None:
|
||||
schedule_timesteps = self.timesteps
|
||||
|
||||
indices = (schedule_timesteps == timestep).nonzero()
|
||||
|
||||
# The sigma index that is taken for the **very** first `step`
|
||||
# is always the second index (or the last index if there is only 1)
|
||||
# This way we can ensure we don't accidentally skip a sigma in
|
||||
# case we start in the middle of the denoising schedule (e.g. for image-to-image)
|
||||
pos = 1 if len(indices) > 1 else 0
|
||||
|
||||
return indices[pos].item()
|
||||
|
||||
def _init_step_index(self, timestep):
|
||||
if self.begin_index is None:
|
||||
if isinstance(timestep, torch.Tensor):
|
||||
timestep = timestep.to(self.timesteps.device)
|
||||
self._step_index = self.index_for_timestep(timestep)
|
||||
else:
|
||||
self._step_index = self._begin_index
|
||||
|
||||
def step(
|
||||
self,
|
||||
model_output: torch.FloatTensor,
|
||||
timestep: Union[float, torch.FloatTensor],
|
||||
sample: torch.FloatTensor,
|
||||
s_churn: float = 0.0,
|
||||
s_tmin: float = 0.0,
|
||||
s_tmax: float = float("inf"),
|
||||
s_noise: float = 1.0,
|
||||
noise_clip_std: float = 0.0,
|
||||
generator: Optional[torch.Generator] = None,
|
||||
return_dict: bool = True,
|
||||
) -> Union[FlashFlowMatchEulerDiscreteSchedulerOutput, Tuple]:
|
||||
"""
|
||||
Predict the sample from the previous timestep by reversing the SDE. This function propagates the diffusion
|
||||
process from the learned model outputs (most often the predicted noise).
|
||||
|
||||
Args:
|
||||
model_output (`torch.FloatTensor`):
|
||||
The direct output from learned diffusion model.
|
||||
timestep (`float`):
|
||||
The current discrete timestep in the diffusion chain.
|
||||
sample (`torch.FloatTensor`):
|
||||
A current instance of a sample created by the diffusion process.
|
||||
s_churn (`float`):
|
||||
s_tmin (`float`):
|
||||
s_tmax (`float`):
|
||||
s_noise (`float`, defaults to 1.0):
|
||||
Scaling factor for noise added to the sample.
|
||||
generator (`torch.Generator`, *optional*):
|
||||
A random number generator.
|
||||
return_dict (`bool`):
|
||||
Whether or not to return a [`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] or
|
||||
tuple.
|
||||
|
||||
Returns:
|
||||
[`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] or `tuple`:
|
||||
If return_dict is `True`, [`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] is
|
||||
returned, otherwise a tuple is returned where the first element is the sample tensor.
|
||||
"""
|
||||
|
||||
if (
|
||||
isinstance(timestep, int)
|
||||
or isinstance(timestep, torch.IntTensor)
|
||||
or isinstance(timestep, torch.LongTensor)
|
||||
):
|
||||
raise ValueError(
|
||||
(
|
||||
"Passing integer indices (e.g. from `enumerate(timesteps)`) as timesteps to"
|
||||
" `EulerDiscreteScheduler.step()` is not supported. Make sure to pass"
|
||||
" one of the `scheduler.timesteps` as a timestep."
|
||||
),
|
||||
)
|
||||
|
||||
if self.step_index is None:
|
||||
self._init_step_index(timestep)
|
||||
|
||||
# Upcast to avoid precision issues when computing prev_sample
|
||||
|
||||
sigma = self.sigmas[self.step_index]
|
||||
|
||||
# Upcast to avoid precision issues when computing prev_sample
|
||||
sample = sample.to(torch.float32)
|
||||
|
||||
denoised = sample - model_output * sigma
|
||||
|
||||
if self.step_index < self.num_inference_steps:
|
||||
sigma_next = self.sigmas[self.step_index + 1]
|
||||
noise = hack_randn_tensor(
|
||||
model_output.shape,
|
||||
generator=generator,
|
||||
device=model_output.device,
|
||||
dtype=denoised.dtype,
|
||||
)
|
||||
if noise_clip_std > 0:
|
||||
noise_std = noise.std().item()
|
||||
clip_val = noise_clip_std * noise_std
|
||||
noise = noise.clamp(min=-clip_val, max=clip_val)
|
||||
sample = sigma_next * noise * s_noise + (1.0 - sigma_next) * denoised
|
||||
|
||||
self._step_index += 1
|
||||
sample = sample.to(model_output.dtype)
|
||||
|
||||
if not return_dict:
|
||||
return (sample,)
|
||||
|
||||
return FlashFlowMatchEulerDiscreteSchedulerOutput(prev_sample=sample)
|
||||
|
||||
# Copied from diffusers.schedulers.scheduling_euler_discrete.EulerDiscreteScheduler._convert_to_karras
|
||||
def _convert_to_karras(self, in_sigmas: torch.Tensor, num_inference_steps) -> torch.Tensor:
|
||||
"""Constructs the noise schedule of Karras et al. (2022)."""
|
||||
|
||||
# Hack to make sure that other schedulers which copy this function don't break
|
||||
# TODO: Add this logic to the other schedulers
|
||||
if hasattr(self.config, "sigma_min"):
|
||||
sigma_min = self.config.sigma_min
|
||||
else:
|
||||
sigma_min = None
|
||||
|
||||
if hasattr(self.config, "sigma_max"):
|
||||
sigma_max = self.config.sigma_max
|
||||
else:
|
||||
sigma_max = None
|
||||
|
||||
sigma_min = sigma_min if sigma_min is not None else in_sigmas[-1].item()
|
||||
sigma_max = sigma_max if sigma_max is not None else in_sigmas[0].item()
|
||||
|
||||
rho = 7.0 # 7.0 is the value used in the paper
|
||||
ramp = np.linspace(0, 1, num_inference_steps)
|
||||
min_inv_rho = sigma_min ** (1 / rho)
|
||||
max_inv_rho = sigma_max ** (1 / rho)
|
||||
sigmas = (max_inv_rho + ramp * (min_inv_rho - max_inv_rho)) ** rho
|
||||
return sigmas
|
||||
|
||||
# Copied from diffusers.schedulers.scheduling_euler_discrete.EulerDiscreteScheduler._convert_to_exponential
|
||||
def _convert_to_exponential(self, in_sigmas: torch.Tensor, num_inference_steps: int) -> torch.Tensor:
|
||||
"""Constructs an exponential noise schedule."""
|
||||
|
||||
# Hack to make sure that other schedulers which copy this function don't break
|
||||
# TODO: Add this logic to the other schedulers
|
||||
if hasattr(self.config, "sigma_min"):
|
||||
sigma_min = self.config.sigma_min
|
||||
else:
|
||||
sigma_min = None
|
||||
|
||||
if hasattr(self.config, "sigma_max"):
|
||||
sigma_max = self.config.sigma_max
|
||||
else:
|
||||
sigma_max = None
|
||||
|
||||
sigma_min = sigma_min if sigma_min is not None else in_sigmas[-1].item()
|
||||
sigma_max = sigma_max if sigma_max is not None else in_sigmas[0].item()
|
||||
|
||||
sigmas = np.exp(np.linspace(math.log(sigma_max), math.log(sigma_min), num_inference_steps))
|
||||
return sigmas
|
||||
|
||||
# Copied from diffusers.schedulers.scheduling_euler_discrete.EulerDiscreteScheduler._convert_to_beta
|
||||
def _convert_to_beta(
|
||||
self, in_sigmas: torch.Tensor, num_inference_steps: int, alpha: float = 0.6, beta: float = 0.6
|
||||
) -> torch.Tensor:
|
||||
"""From "Beta Sampling is All You Need" [arXiv:2407.12173] (Lee et. al, 2024)"""
|
||||
|
||||
# Hack to make sure that other schedulers which copy this function don't break
|
||||
# TODO: Add this logic to the other schedulers
|
||||
if hasattr(self.config, "sigma_min"):
|
||||
sigma_min = self.config.sigma_min
|
||||
else:
|
||||
sigma_min = None
|
||||
|
||||
if hasattr(self.config, "sigma_max"):
|
||||
sigma_max = self.config.sigma_max
|
||||
else:
|
||||
sigma_max = None
|
||||
|
||||
sigma_min = sigma_min if sigma_min is not None else in_sigmas[-1].item()
|
||||
sigma_max = sigma_max if sigma_max is not None else in_sigmas[0].item()
|
||||
|
||||
sigmas = np.array(
|
||||
[
|
||||
sigma_min + (ppf * (sigma_max - sigma_min))
|
||||
for ppf in [
|
||||
scipy.stats.beta.ppf(timestep, alpha, beta)
|
||||
for timestep in 1 - np.linspace(0, 1, num_inference_steps)
|
||||
]
|
||||
]
|
||||
)
|
||||
return sigmas
|
||||
|
||||
def __len__(self):
|
||||
return self.config.num_train_timesteps
|
||||
@@ -0,0 +1,778 @@
|
||||
# Copied from https://github.com/huggingface/diffusers/blob/v0.31.0/src/diffusers/schedulers/scheduling_unipc_multistep.py
|
||||
# Convert unipc for flow matching
|
||||
# Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
|
||||
|
||||
import math
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
||||
from diffusers.schedulers.scheduling_utils import (KarrasDiffusionSchedulers,
|
||||
SchedulerMixin,
|
||||
SchedulerOutput)
|
||||
|
||||
|
||||
class FlowUniPCMultistepScheduler(SchedulerMixin, ConfigMixin):
|
||||
"""
|
||||
`UniPCMultistepScheduler` is a training-free framework designed for the fast sampling of diffusion models.
|
||||
|
||||
This model inherits from [`SchedulerMixin`] and [`ConfigMixin`]. Check the superclass documentation for the generic
|
||||
methods the library implements for all schedulers such as loading and saving.
|
||||
|
||||
Args:
|
||||
num_train_timesteps (`int`, defaults to 1000):
|
||||
The number of diffusion steps to train the model.
|
||||
solver_order (`int`, default `2`):
|
||||
The UniPC order which can be any positive integer. The effective order of accuracy is `solver_order + 1`
|
||||
due to the UniC. It is recommended to use `solver_order=2` for guided sampling, and `solver_order=3` for
|
||||
unconditional sampling.
|
||||
prediction_type (`str`, defaults to "flow_prediction"):
|
||||
Prediction type of the scheduler function; must be `flow_prediction` for this scheduler, which predicts
|
||||
the flow of the diffusion process.
|
||||
thresholding (`bool`, defaults to `False`):
|
||||
Whether to use the "dynamic thresholding" method. This is unsuitable for latent-space diffusion models such
|
||||
as Stable Diffusion.
|
||||
dynamic_thresholding_ratio (`float`, defaults to 0.995):
|
||||
The ratio for the dynamic thresholding method. Valid only when `thresholding=True`.
|
||||
sample_max_value (`float`, defaults to 1.0):
|
||||
The threshold value for dynamic thresholding. Valid only when `thresholding=True` and `predict_x0=True`.
|
||||
predict_x0 (`bool`, defaults to `True`):
|
||||
Whether to use the updating algorithm on the predicted x0.
|
||||
solver_type (`str`, default `bh2`):
|
||||
Solver type for UniPC. It is recommended to use `bh1` for unconditional sampling when steps < 10, and `bh2`
|
||||
otherwise.
|
||||
lower_order_final (`bool`, default `True`):
|
||||
Whether to use lower-order solvers in the final steps. Only valid for < 15 inference steps. This can
|
||||
stabilize the sampling of DPMSolver for steps < 15, especially for steps <= 10.
|
||||
disable_corrector (`list`, default `[]`):
|
||||
Decides which step to disable the corrector to mitigate the misalignment between `epsilon_theta(x_t, c)`
|
||||
and `epsilon_theta(x_t^c, c)` which can influence convergence for a large guidance scale. Corrector is
|
||||
usually disabled during the first few steps.
|
||||
solver_p (`SchedulerMixin`, default `None`):
|
||||
Any other scheduler that if specified, the algorithm becomes `solver_p + UniC`.
|
||||
use_karras_sigmas (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Karras sigmas for step sizes in the noise schedule during the sampling process. If `True`,
|
||||
the sigmas are determined according to a sequence of noise levels {σi}.
|
||||
use_exponential_sigmas (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use exponential sigmas for step sizes in the noise schedule during the sampling process.
|
||||
timestep_spacing (`str`, defaults to `"linspace"`):
|
||||
The way the timesteps should be scaled. Refer to Table 2 of the [Common Diffusion Noise Schedules and
|
||||
Sample Steps are Flawed](https://huggingface.co/papers/2305.08891) for more information.
|
||||
steps_offset (`int`, defaults to 0):
|
||||
An offset added to the inference steps, as required by some model families.
|
||||
final_sigmas_type (`str`, defaults to `"zero"`):
|
||||
The final `sigma` value for the noise schedule during the sampling process. If `"sigma_min"`, the final
|
||||
sigma is the same as the last sigma in the training schedule. If `zero`, the final sigma is set to 0.
|
||||
"""
|
||||
|
||||
_compatibles = [e.name for e in KarrasDiffusionSchedulers]
|
||||
order = 1
|
||||
|
||||
@register_to_config
|
||||
def __init__(
|
||||
self,
|
||||
num_train_timesteps: int = 1000,
|
||||
solver_order: int = 2,
|
||||
prediction_type: str = "flow_prediction",
|
||||
shift: Optional[float] = 1.0,
|
||||
use_dynamic_shifting=False,
|
||||
thresholding: bool = False,
|
||||
dynamic_thresholding_ratio: float = 0.995,
|
||||
sample_max_value: float = 1.0,
|
||||
predict_x0: bool = True,
|
||||
solver_type: str = "bh2",
|
||||
lower_order_final: bool = True,
|
||||
disable_corrector: List[int] = [],
|
||||
solver_p: SchedulerMixin = None,
|
||||
timestep_spacing: str = "linspace",
|
||||
steps_offset: int = 0,
|
||||
final_sigmas_type: Optional[str] = "zero", # "zero", "sigma_min"
|
||||
):
|
||||
|
||||
if solver_type not in ["bh1", "bh2"]:
|
||||
if solver_type in ["midpoint", "heun", "logrho"]:
|
||||
self.register_to_config(solver_type="bh2")
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"{solver_type} is not implemented for {self.__class__}")
|
||||
|
||||
self.predict_x0 = predict_x0
|
||||
# setable values
|
||||
self.num_inference_steps = None
|
||||
alphas = np.linspace(1, 1 / num_train_timesteps,
|
||||
num_train_timesteps)[::-1].copy()
|
||||
sigmas = 1.0 - alphas
|
||||
sigmas = torch.from_numpy(sigmas).to(dtype=torch.float32)
|
||||
|
||||
if not use_dynamic_shifting:
|
||||
# when use_dynamic_shifting is True, we apply the timestep shifting on the fly based on the image resolution
|
||||
sigmas = shift * sigmas / (1 +
|
||||
(shift - 1) * sigmas) # pyright: ignore
|
||||
|
||||
self.sigmas = sigmas
|
||||
self.timesteps = sigmas * num_train_timesteps
|
||||
|
||||
self.model_outputs = [None] * solver_order
|
||||
self.timestep_list = [None] * solver_order
|
||||
self.lower_order_nums = 0
|
||||
self.disable_corrector = disable_corrector
|
||||
self.solver_p = solver_p
|
||||
self.last_sample = None
|
||||
self._step_index = None
|
||||
self._begin_index = None
|
||||
|
||||
self.sigmas = self.sigmas.to(
|
||||
"cpu") # to avoid too much CPU/GPU communication
|
||||
self.sigma_min = self.sigmas[-1].item()
|
||||
self.sigma_max = self.sigmas[0].item()
|
||||
|
||||
@property
|
||||
def step_index(self):
|
||||
"""
|
||||
The index counter for current timestep. It will increase 1 after each scheduler step.
|
||||
"""
|
||||
return self._step_index
|
||||
|
||||
@property
|
||||
def begin_index(self):
|
||||
"""
|
||||
The index for the first timestep. It should be set from pipeline with `set_begin_index` method.
|
||||
"""
|
||||
return self._begin_index
|
||||
|
||||
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.set_begin_index
|
||||
def set_begin_index(self, begin_index: int = 0):
|
||||
"""
|
||||
Sets the begin index for the scheduler. This function should be run from pipeline before the inference.
|
||||
|
||||
Args:
|
||||
begin_index (`int`):
|
||||
The begin index for the scheduler.
|
||||
"""
|
||||
self._begin_index = begin_index
|
||||
|
||||
# Modified from diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler.set_timesteps
|
||||
def set_timesteps(
|
||||
self,
|
||||
num_inference_steps: Union[int, None] = None,
|
||||
device: Union[str, torch.device] = None,
|
||||
sigmas: Optional[List[float]] = None,
|
||||
mu: Optional[Union[float, None]] = None,
|
||||
shift: Optional[Union[float, None]] = None,
|
||||
):
|
||||
"""
|
||||
Sets the discrete timesteps used for the diffusion chain (to be run before inference).
|
||||
Args:
|
||||
num_inference_steps (`int`):
|
||||
Total number of the spacing of the time steps.
|
||||
device (`str` or `torch.device`, *optional*):
|
||||
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
|
||||
"""
|
||||
|
||||
if self.config.use_dynamic_shifting and mu is None:
|
||||
raise ValueError(
|
||||
" you have to pass a value for `mu` when `use_dynamic_shifting` is set to be `True`"
|
||||
)
|
||||
|
||||
if sigmas is None:
|
||||
sigmas = np.linspace(self.sigma_max, self.sigma_min,
|
||||
num_inference_steps +
|
||||
1).copy()[:-1] # pyright: ignore
|
||||
|
||||
if self.config.use_dynamic_shifting:
|
||||
sigmas = self.time_shift(mu, 1.0, sigmas) # pyright: ignore
|
||||
else:
|
||||
if shift is None:
|
||||
shift = self.config.shift
|
||||
sigmas = shift * sigmas / (1 +
|
||||
(shift - 1) * sigmas) # pyright: ignore
|
||||
|
||||
if self.config.final_sigmas_type == "sigma_min":
|
||||
sigma_last = ((1 - self.alphas_cumprod[0]) /
|
||||
self.alphas_cumprod[0])**0.5
|
||||
elif self.config.final_sigmas_type == "zero":
|
||||
sigma_last = 0
|
||||
else:
|
||||
raise ValueError(
|
||||
f"`final_sigmas_type` must be one of 'zero', or 'sigma_min', but got {self.config.final_sigmas_type}"
|
||||
)
|
||||
|
||||
timesteps = sigmas * self.config.num_train_timesteps
|
||||
sigmas = np.concatenate([sigmas, [sigma_last]
|
||||
]).astype(np.float32) # pyright: ignore
|
||||
|
||||
self.sigmas = torch.from_numpy(sigmas)
|
||||
self.timesteps = torch.from_numpy(timesteps).to(
|
||||
device=device, dtype=torch.int64)
|
||||
|
||||
self.num_inference_steps = len(timesteps)
|
||||
|
||||
self.model_outputs = [
|
||||
None,
|
||||
] * self.config.solver_order
|
||||
self.lower_order_nums = 0
|
||||
self.last_sample = None
|
||||
if self.solver_p:
|
||||
self.solver_p.set_timesteps(self.num_inference_steps, device=device)
|
||||
|
||||
# add an index counter for schedulers that allow duplicated timesteps
|
||||
self._step_index = None
|
||||
self._begin_index = None
|
||||
self.sigmas = self.sigmas.to(
|
||||
"cpu") # to avoid too much CPU/GPU communication
|
||||
|
||||
# Copied from diffusers.schedulers.scheduling_ddpm.DDPMScheduler._threshold_sample
|
||||
def _threshold_sample(self, sample: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
"Dynamic thresholding: At each sampling step we set s to a certain percentile absolute pixel value in xt0 (the
|
||||
prediction of x_0 at timestep t), and if s > 1, then we threshold xt0 to the range [-s, s] and then divide by
|
||||
s. Dynamic thresholding pushes saturated pixels (those near -1 and 1) inwards, thereby actively preventing
|
||||
pixels from saturation at each step. We find that dynamic thresholding results in significantly better
|
||||
photorealism as well as better image-text alignment, especially when using very large guidance weights."
|
||||
|
||||
https://arxiv.org/abs/2205.11487
|
||||
"""
|
||||
dtype = sample.dtype
|
||||
batch_size, channels, *remaining_dims = sample.shape
|
||||
|
||||
if dtype not in (torch.float32, torch.float64):
|
||||
sample = sample.float(
|
||||
) # upcast for quantile calculation, and clamp not implemented for cpu half
|
||||
|
||||
# Flatten sample for doing quantile calculation along each image
|
||||
sample = sample.reshape(batch_size, channels * np.prod(remaining_dims))
|
||||
|
||||
abs_sample = sample.abs() # "a certain percentile absolute pixel value"
|
||||
|
||||
s = torch.quantile(
|
||||
abs_sample, self.config.dynamic_thresholding_ratio, dim=1)
|
||||
s = torch.clamp(
|
||||
s, min=1, max=self.config.sample_max_value
|
||||
) # When clamped to min=1, equivalent to standard clipping to [-1, 1]
|
||||
s = s.unsqueeze(
|
||||
1) # (batch_size, 1) because clamp will broadcast along dim=0
|
||||
sample = torch.clamp(
|
||||
sample, -s, s
|
||||
) / s # "we threshold xt0 to the range [-s, s] and then divide by s"
|
||||
|
||||
sample = sample.reshape(batch_size, channels, *remaining_dims)
|
||||
sample = sample.to(dtype)
|
||||
|
||||
return sample
|
||||
|
||||
# Copied from diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler._sigma_to_t
|
||||
def _sigma_to_t(self, sigma):
|
||||
return sigma * self.config.num_train_timesteps
|
||||
|
||||
def _sigma_to_alpha_sigma_t(self, sigma):
|
||||
return 1 - sigma, sigma
|
||||
|
||||
# Copied from diffusers.schedulers.scheduling_flow_match_euler_discrete.set_timesteps
|
||||
def time_shift(self, mu: float, sigma: float, t: torch.Tensor):
|
||||
return math.exp(mu) / (math.exp(mu) + (1 / t - 1)**sigma)
|
||||
|
||||
def convert_model_output(
|
||||
self,
|
||||
model_output: torch.Tensor,
|
||||
*args,
|
||||
sample: torch.Tensor = None,
|
||||
**kwargs,
|
||||
) -> torch.Tensor:
|
||||
r"""
|
||||
Convert the model output to the corresponding type the UniPC algorithm needs.
|
||||
|
||||
Args:
|
||||
model_output (`torch.Tensor`):
|
||||
The direct output from the learned diffusion model.
|
||||
timestep (`int`):
|
||||
The current discrete timestep in the diffusion chain.
|
||||
sample (`torch.Tensor`):
|
||||
A current instance of a sample created by the diffusion process.
|
||||
|
||||
Returns:
|
||||
`torch.Tensor`:
|
||||
The converted model output.
|
||||
"""
|
||||
timestep = args[0] if len(args) > 0 else kwargs.pop("timestep", None)
|
||||
if sample is None:
|
||||
if len(args) > 1:
|
||||
sample = args[1]
|
||||
else:
|
||||
raise ValueError(
|
||||
"missing `sample` as a required keyward argument")
|
||||
|
||||
sigma = self.sigmas[self.step_index]
|
||||
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma)
|
||||
|
||||
if self.predict_x0:
|
||||
if self.config.prediction_type == "flow_prediction":
|
||||
sigma_t = self.sigmas[self.step_index]
|
||||
x0_pred = sample - sigma_t * model_output
|
||||
else:
|
||||
raise ValueError(
|
||||
f"prediction_type given as {self.config.prediction_type} must be one of `epsilon`, `sample`,"
|
||||
" `v_prediction` or `flow_prediction` for the UniPCMultistepScheduler."
|
||||
)
|
||||
|
||||
if self.config.thresholding:
|
||||
x0_pred = self._threshold_sample(x0_pred)
|
||||
|
||||
return x0_pred
|
||||
else:
|
||||
if self.config.prediction_type == "flow_prediction":
|
||||
sigma_t = self.sigmas[self.step_index]
|
||||
epsilon = sample - (1 - sigma_t) * model_output
|
||||
else:
|
||||
raise ValueError(
|
||||
f"prediction_type given as {self.config.prediction_type} must be one of `epsilon`, `sample`,"
|
||||
" `v_prediction` or `flow_prediction` for the UniPCMultistepScheduler."
|
||||
)
|
||||
|
||||
if self.config.thresholding:
|
||||
sigma_t = self.sigmas[self.step_index]
|
||||
x0_pred = sample - sigma_t * model_output
|
||||
x0_pred = self._threshold_sample(x0_pred)
|
||||
epsilon = model_output + x0_pred
|
||||
|
||||
return epsilon
|
||||
|
||||
def multistep_uni_p_bh_update(
|
||||
self,
|
||||
model_output: torch.Tensor,
|
||||
*args,
|
||||
sample: torch.Tensor = None,
|
||||
order: int = None, # pyright: ignore
|
||||
**kwargs,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
One step for the UniP (B(h) version). Alternatively, `self.solver_p` is used if is specified.
|
||||
|
||||
Args:
|
||||
model_output (`torch.Tensor`):
|
||||
The direct output from the learned diffusion model at the current timestep.
|
||||
prev_timestep (`int`):
|
||||
The previous discrete timestep in the diffusion chain.
|
||||
sample (`torch.Tensor`):
|
||||
A current instance of a sample created by the diffusion process.
|
||||
order (`int`):
|
||||
The order of UniP at this timestep (corresponds to the *p* in UniPC-p).
|
||||
|
||||
Returns:
|
||||
`torch.Tensor`:
|
||||
The sample tensor at the previous timestep.
|
||||
"""
|
||||
prev_timestep = args[0] if len(args) > 0 else kwargs.pop(
|
||||
"prev_timestep", None)
|
||||
if sample is None:
|
||||
if len(args) > 1:
|
||||
sample = args[1]
|
||||
else:
|
||||
raise ValueError(
|
||||
" missing `sample` as a required keyward argument")
|
||||
if order is None:
|
||||
if len(args) > 2:
|
||||
order = args[2]
|
||||
else:
|
||||
raise ValueError(
|
||||
" missing `order` as a required keyward argument")
|
||||
model_output_list = self.model_outputs
|
||||
|
||||
s0 = self.timestep_list[-1]
|
||||
m0 = model_output_list[-1]
|
||||
x = sample
|
||||
|
||||
if self.solver_p:
|
||||
x_t = self.solver_p.step(model_output, s0, x).prev_sample
|
||||
return x_t
|
||||
|
||||
sigma_t, sigma_s0 = self.sigmas[self.step_index + 1], self.sigmas[
|
||||
self.step_index] # pyright: ignore
|
||||
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma_t)
|
||||
alpha_s0, sigma_s0 = self._sigma_to_alpha_sigma_t(sigma_s0)
|
||||
|
||||
lambda_t = torch.log(alpha_t) - torch.log(sigma_t)
|
||||
lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0)
|
||||
|
||||
h = lambda_t - lambda_s0
|
||||
device = sample.device
|
||||
|
||||
rks = []
|
||||
D1s = []
|
||||
for i in range(1, order):
|
||||
si = self.step_index - i # pyright: ignore
|
||||
mi = model_output_list[-(i + 1)]
|
||||
alpha_si, sigma_si = self._sigma_to_alpha_sigma_t(self.sigmas[si])
|
||||
lambda_si = torch.log(alpha_si) - torch.log(sigma_si)
|
||||
rk = (lambda_si - lambda_s0) / h
|
||||
rks.append(rk)
|
||||
D1s.append((mi - m0) / rk) # pyright: ignore
|
||||
|
||||
rks.append(1.0)
|
||||
rks = torch.tensor(rks, device=device)
|
||||
|
||||
R = []
|
||||
b = []
|
||||
|
||||
hh = -h if self.predict_x0 else h
|
||||
h_phi_1 = torch.expm1(hh) # h\phi_1(h) = e^h - 1
|
||||
h_phi_k = h_phi_1 / hh - 1
|
||||
|
||||
factorial_i = 1
|
||||
|
||||
if self.config.solver_type == "bh1":
|
||||
B_h = hh
|
||||
elif self.config.solver_type == "bh2":
|
||||
B_h = torch.expm1(hh)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
for i in range(1, order + 1):
|
||||
R.append(torch.pow(rks, i - 1))
|
||||
b.append(h_phi_k * factorial_i / B_h)
|
||||
factorial_i *= i + 1
|
||||
h_phi_k = h_phi_k / hh - 1 / factorial_i
|
||||
|
||||
R = torch.stack(R)
|
||||
b = torch.tensor(b, device=device)
|
||||
|
||||
if len(D1s) > 0:
|
||||
D1s = torch.stack(D1s, dim=1) # (B, K)
|
||||
# for order 2, we use a simplified version
|
||||
if order == 2:
|
||||
rhos_p = torch.tensor([0.5], dtype=x.dtype, device=device)
|
||||
else:
|
||||
rhos_p = torch.linalg.solve(R[:-1, :-1],
|
||||
b[:-1]).to(device).to(x.dtype)
|
||||
else:
|
||||
D1s = None
|
||||
|
||||
if self.predict_x0:
|
||||
x_t_ = sigma_t / sigma_s0 * x - alpha_t * h_phi_1 * m0
|
||||
if D1s is not None:
|
||||
pred_res = torch.einsum("k,bkc...->bc...", rhos_p,
|
||||
D1s) # pyright: ignore
|
||||
else:
|
||||
pred_res = 0
|
||||
x_t = x_t_ - alpha_t * B_h * pred_res
|
||||
else:
|
||||
x_t_ = alpha_t / alpha_s0 * x - sigma_t * h_phi_1 * m0
|
||||
if D1s is not None:
|
||||
pred_res = torch.einsum("k,bkc...->bc...", rhos_p,
|
||||
D1s) # pyright: ignore
|
||||
else:
|
||||
pred_res = 0
|
||||
x_t = x_t_ - sigma_t * B_h * pred_res
|
||||
|
||||
x_t = x_t.to(x.dtype)
|
||||
return x_t
|
||||
|
||||
def multistep_uni_c_bh_update(
|
||||
self,
|
||||
this_model_output: torch.Tensor,
|
||||
*args,
|
||||
last_sample: torch.Tensor = None,
|
||||
this_sample: torch.Tensor = None,
|
||||
order: int = None, # pyright: ignore
|
||||
**kwargs,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
One step for the UniC (B(h) version).
|
||||
|
||||
Args:
|
||||
this_model_output (`torch.Tensor`):
|
||||
The model outputs at `x_t`.
|
||||
this_timestep (`int`):
|
||||
The current timestep `t`.
|
||||
last_sample (`torch.Tensor`):
|
||||
The generated sample before the last predictor `x_{t-1}`.
|
||||
this_sample (`torch.Tensor`):
|
||||
The generated sample after the last predictor `x_{t}`.
|
||||
order (`int`):
|
||||
The `p` of UniC-p at this step. The effective order of accuracy should be `order + 1`.
|
||||
|
||||
Returns:
|
||||
`torch.Tensor`:
|
||||
The corrected sample tensor at the current timestep.
|
||||
"""
|
||||
this_timestep = args[0] if len(args) > 0 else kwargs.pop(
|
||||
"this_timestep", None)
|
||||
if last_sample is None:
|
||||
if len(args) > 1:
|
||||
last_sample = args[1]
|
||||
else:
|
||||
raise ValueError(
|
||||
" missing`last_sample` as a required keyward argument")
|
||||
if this_sample is None:
|
||||
if len(args) > 2:
|
||||
this_sample = args[2]
|
||||
else:
|
||||
raise ValueError(
|
||||
" missing`this_sample` as a required keyward argument")
|
||||
if order is None:
|
||||
if len(args) > 3:
|
||||
order = args[3]
|
||||
else:
|
||||
raise ValueError(
|
||||
" missing`order` as a required keyward argument")
|
||||
|
||||
model_output_list = self.model_outputs
|
||||
|
||||
m0 = model_output_list[-1]
|
||||
x = last_sample
|
||||
x_t = this_sample
|
||||
model_t = this_model_output
|
||||
|
||||
sigma_t, sigma_s0 = self.sigmas[self.step_index], self.sigmas[
|
||||
self.step_index - 1] # pyright: ignore
|
||||
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma_t)
|
||||
alpha_s0, sigma_s0 = self._sigma_to_alpha_sigma_t(sigma_s0)
|
||||
|
||||
lambda_t = torch.log(alpha_t) - torch.log(sigma_t)
|
||||
lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0)
|
||||
|
||||
h = lambda_t - lambda_s0
|
||||
device = this_sample.device
|
||||
|
||||
rks = []
|
||||
D1s = []
|
||||
for i in range(1, order):
|
||||
si = self.step_index - (i + 1) # pyright: ignore
|
||||
mi = model_output_list[-(i + 1)]
|
||||
alpha_si, sigma_si = self._sigma_to_alpha_sigma_t(self.sigmas[si])
|
||||
lambda_si = torch.log(alpha_si) - torch.log(sigma_si)
|
||||
rk = (lambda_si - lambda_s0) / h
|
||||
rks.append(rk)
|
||||
D1s.append((mi - m0) / rk) # pyright: ignore
|
||||
|
||||
rks.append(1.0)
|
||||
rks = torch.tensor(rks, device=device)
|
||||
|
||||
R = []
|
||||
b = []
|
||||
|
||||
hh = -h if self.predict_x0 else h
|
||||
h_phi_1 = torch.expm1(hh) # h\phi_1(h) = e^h - 1
|
||||
h_phi_k = h_phi_1 / hh - 1
|
||||
|
||||
factorial_i = 1
|
||||
|
||||
if self.config.solver_type == "bh1":
|
||||
B_h = hh
|
||||
elif self.config.solver_type == "bh2":
|
||||
B_h = torch.expm1(hh)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
for i in range(1, order + 1):
|
||||
R.append(torch.pow(rks, i - 1))
|
||||
b.append(h_phi_k * factorial_i / B_h)
|
||||
factorial_i *= i + 1
|
||||
h_phi_k = h_phi_k / hh - 1 / factorial_i
|
||||
|
||||
R = torch.stack(R)
|
||||
b = torch.tensor(b, device=device)
|
||||
|
||||
if len(D1s) > 0:
|
||||
D1s = torch.stack(D1s, dim=1)
|
||||
else:
|
||||
D1s = None
|
||||
|
||||
# for order 1, we use a simplified version
|
||||
if order == 1:
|
||||
rhos_c = torch.tensor([0.5], dtype=x.dtype, device=device)
|
||||
else:
|
||||
rhos_c = torch.linalg.solve(R, b).to(device).to(x.dtype)
|
||||
|
||||
if self.predict_x0:
|
||||
x_t_ = sigma_t / sigma_s0 * x - alpha_t * h_phi_1 * m0
|
||||
if D1s is not None:
|
||||
corr_res = torch.einsum("k,bkc...->bc...", rhos_c[:-1], D1s)
|
||||
else:
|
||||
corr_res = 0
|
||||
D1_t = model_t - m0
|
||||
x_t = x_t_ - alpha_t * B_h * (corr_res + rhos_c[-1] * D1_t)
|
||||
else:
|
||||
x_t_ = alpha_t / alpha_s0 * x - sigma_t * h_phi_1 * m0
|
||||
if D1s is not None:
|
||||
corr_res = torch.einsum("k,bkc...->bc...", rhos_c[:-1], D1s)
|
||||
else:
|
||||
corr_res = 0
|
||||
D1_t = model_t - m0
|
||||
x_t = x_t_ - sigma_t * B_h * (corr_res + rhos_c[-1] * D1_t)
|
||||
x_t = x_t.to(x.dtype)
|
||||
return x_t
|
||||
|
||||
def index_for_timestep(self, timestep, schedule_timesteps=None):
|
||||
if schedule_timesteps is None:
|
||||
schedule_timesteps = self.timesteps
|
||||
|
||||
indices = (schedule_timesteps == timestep).nonzero()
|
||||
|
||||
# The sigma index that is taken for the **very** first `step`
|
||||
# is always the second index (or the last index if there is only 1)
|
||||
# This way we can ensure we don't accidentally skip a sigma in
|
||||
# case we start in the middle of the denoising schedule (e.g. for image-to-image)
|
||||
pos = 1 if len(indices) > 1 else 0
|
||||
|
||||
return indices[pos].item()
|
||||
|
||||
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler._init_step_index
|
||||
def _init_step_index(self, timestep):
|
||||
"""
|
||||
Initialize the step_index counter for the scheduler.
|
||||
"""
|
||||
|
||||
if self.begin_index is None:
|
||||
if isinstance(timestep, torch.Tensor):
|
||||
timestep = timestep.to(self.timesteps.device)
|
||||
self._step_index = self.index_for_timestep(timestep)
|
||||
else:
|
||||
self._step_index = self._begin_index
|
||||
|
||||
def step(self,
|
||||
model_output: torch.Tensor,
|
||||
timestep: Union[int, torch.Tensor],
|
||||
sample: torch.Tensor,
|
||||
return_dict: bool = True,
|
||||
generator=None) -> Union[SchedulerOutput, Tuple]:
|
||||
"""
|
||||
Predict the sample from the previous timestep by reversing the SDE. This function propagates the sample with
|
||||
the multistep UniPC.
|
||||
|
||||
Args:
|
||||
model_output (`torch.Tensor`):
|
||||
The direct output from learned diffusion model.
|
||||
timestep (`int`):
|
||||
The current discrete timestep in the diffusion chain.
|
||||
sample (`torch.Tensor`):
|
||||
A current instance of a sample created by the diffusion process.
|
||||
return_dict (`bool`):
|
||||
Whether or not to return a [`~schedulers.scheduling_utils.SchedulerOutput`] or `tuple`.
|
||||
|
||||
Returns:
|
||||
[`~schedulers.scheduling_utils.SchedulerOutput`] or `tuple`:
|
||||
If return_dict is `True`, [`~schedulers.scheduling_utils.SchedulerOutput`] is returned, otherwise a
|
||||
tuple is returned where the first element is the sample tensor.
|
||||
|
||||
"""
|
||||
if self.num_inference_steps is None:
|
||||
raise ValueError(
|
||||
"Number of inference steps is 'None', you need to run 'set_timesteps' after creating the scheduler"
|
||||
)
|
||||
|
||||
if self.step_index is None:
|
||||
self._init_step_index(timestep)
|
||||
|
||||
use_corrector = (
|
||||
self.step_index > 0 and
|
||||
self.step_index - 1 not in self.disable_corrector and
|
||||
self.last_sample is not None # pyright: ignore
|
||||
)
|
||||
|
||||
model_output_convert = self.convert_model_output(
|
||||
model_output, sample=sample)
|
||||
if use_corrector:
|
||||
sample = self.multistep_uni_c_bh_update(
|
||||
this_model_output=model_output_convert,
|
||||
last_sample=self.last_sample,
|
||||
this_sample=sample,
|
||||
order=self.this_order,
|
||||
)
|
||||
|
||||
for i in range(self.config.solver_order - 1):
|
||||
self.model_outputs[i] = self.model_outputs[i + 1]
|
||||
self.timestep_list[i] = self.timestep_list[i + 1]
|
||||
|
||||
self.model_outputs[-1] = model_output_convert
|
||||
self.timestep_list[-1] = timestep # pyright: ignore
|
||||
|
||||
if self.config.lower_order_final:
|
||||
this_order = min(self.config.solver_order,
|
||||
len(self.timesteps) -
|
||||
self.step_index) # pyright: ignore
|
||||
else:
|
||||
this_order = self.config.solver_order
|
||||
|
||||
self.this_order = min(this_order,
|
||||
self.lower_order_nums + 1) # warmup for multistep
|
||||
assert self.this_order > 0
|
||||
|
||||
self.last_sample = sample
|
||||
prev_sample = self.multistep_uni_p_bh_update(
|
||||
model_output=model_output, # pass the original non-converted model output, in case solver-p is used
|
||||
sample=sample,
|
||||
order=self.this_order,
|
||||
)
|
||||
|
||||
if self.lower_order_nums < self.config.solver_order:
|
||||
self.lower_order_nums += 1
|
||||
|
||||
# upon completion increase step index by one
|
||||
self._step_index += 1 # pyright: ignore
|
||||
|
||||
if not return_dict:
|
||||
return (prev_sample,)
|
||||
|
||||
return SchedulerOutput(prev_sample=prev_sample)
|
||||
|
||||
def scale_model_input(self, sample: torch.Tensor, *args,
|
||||
**kwargs) -> torch.Tensor:
|
||||
"""
|
||||
Ensures interchangeability with schedulers that need to scale the denoising model input depending on the
|
||||
current timestep.
|
||||
|
||||
Args:
|
||||
sample (`torch.Tensor`):
|
||||
The input sample.
|
||||
|
||||
Returns:
|
||||
`torch.Tensor`:
|
||||
A scaled input sample.
|
||||
"""
|
||||
return sample
|
||||
|
||||
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.add_noise
|
||||
def add_noise(
|
||||
self,
|
||||
original_samples: torch.Tensor,
|
||||
noise: torch.Tensor,
|
||||
timesteps: torch.IntTensor,
|
||||
) -> torch.Tensor:
|
||||
# Make sure sigmas and timesteps have the same device and dtype as original_samples
|
||||
sigmas = self.sigmas.to(
|
||||
device=original_samples.device, dtype=original_samples.dtype)
|
||||
if original_samples.device.type == "mps" and torch.is_floating_point(
|
||||
timesteps):
|
||||
# mps does not support float64
|
||||
schedule_timesteps = self.timesteps.to(
|
||||
original_samples.device, dtype=torch.float32)
|
||||
timesteps = timesteps.to(
|
||||
original_samples.device, dtype=torch.float32)
|
||||
else:
|
||||
schedule_timesteps = self.timesteps.to(original_samples.device)
|
||||
timesteps = timesteps.to(original_samples.device)
|
||||
|
||||
# begin_index is None when the scheduler is used for training or pipeline does not implement set_begin_index
|
||||
if self.begin_index is None:
|
||||
step_indices = [
|
||||
self.index_for_timestep(t, schedule_timesteps)
|
||||
for t in timesteps
|
||||
]
|
||||
elif self.step_index is not None:
|
||||
# add_noise is called after first denoising step (for inpainting)
|
||||
step_indices = [self.step_index] * timesteps.shape[0]
|
||||
else:
|
||||
# add noise is called before first denoising step to create initial latent(img2img)
|
||||
step_indices = [self.begin_index] * timesteps.shape[0]
|
||||
|
||||
sigma = sigmas[step_indices].flatten()
|
||||
while len(sigma.shape) < len(original_samples.shape):
|
||||
sigma = sigma.unsqueeze(-1)
|
||||
|
||||
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma)
|
||||
noisy_samples = alpha_t * original_samples + sigma_t * noise
|
||||
return noisy_samples
|
||||
|
||||
def __len__(self):
|
||||
return self.config.num_train_timesteps
|
||||
@@ -31,6 +31,56 @@ def load_llama(diffusers_load_config=None):
|
||||
return text_encoder_4, tokenizer_4
|
||||
|
||||
|
||||
def load_hidream_o1(checkpoint_info, diffusers_load_config=None):
|
||||
if diffusers_load_config is None:
|
||||
diffusers_load_config = {}
|
||||
repo_id = sd_models.path_to_repo(checkpoint_info)
|
||||
sd_models.hf_auth_check(checkpoint_info)
|
||||
|
||||
from pipelines.hidream.hidream_o1 import HiDreamO1Pipeline, HiDreamO1ImagePipeline
|
||||
from pipelines.hidream.qwen3_vl_transformers import HiDreamO1Qwen3VLTransformer
|
||||
from pipelines.hidream.scheduler_flashfloweuler import FlashFlowMatchEulerDiscreteScheduler
|
||||
|
||||
load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Model', device_map=True, allow_quant=False)
|
||||
log.debug(f'Load model: type=HiDreamO1 repo="{repo_id}" offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype} args={load_args}')
|
||||
|
||||
o1_load_config = diffusers_load_config.copy()
|
||||
o1_load_config['trust_remote_code'] = True
|
||||
|
||||
transformer = HiDreamO1Qwen3VLTransformer.from_pretrained(
|
||||
repo_id,
|
||||
cache_dir=shared.opts.hfcache_dir,
|
||||
trust_remote_code=True,
|
||||
**load_args,
|
||||
**quant_args,
|
||||
)
|
||||
if shared.opts.diffusers_offload_mode != 'none' and transformer is not None:
|
||||
sd_models.move_model(transformer, devices.cpu)
|
||||
|
||||
processor = transformers.AutoProcessor.from_pretrained(
|
||||
repo_id,
|
||||
cache_dir=shared.opts.hfcache_dir,
|
||||
trust_remote_code=True,
|
||||
)
|
||||
pipe = HiDreamO1Pipeline(
|
||||
transformer=transformer,
|
||||
processor=processor,
|
||||
tokenizer=processor.tokenizer,
|
||||
scheduler=FlashFlowMatchEulerDiscreteScheduler(num_train_timesteps=1000, shift=3.0, use_dynamic_shifting=False),
|
||||
)
|
||||
pipe.task_args = {
|
||||
'output_type': 'pil',
|
||||
}
|
||||
|
||||
del processor
|
||||
del transformer
|
||||
diffusers.pipelines.auto_pipeline.AUTO_TEXT2IMAGE_PIPELINES_MAPPING["hidream-o1"] = HiDreamO1Pipeline
|
||||
diffusers.pipelines.auto_pipeline.AUTO_IMAGE2IMAGE_PIPELINES_MAPPING["hidream-o1"] = HiDreamO1ImagePipeline
|
||||
|
||||
devices.torch_gc()
|
||||
return pipe
|
||||
|
||||
|
||||
def load_hidream(checkpoint_info, diffusers_load_config=None):
|
||||
if diffusers_load_config is None:
|
||||
diffusers_load_config = {}
|
||||
@@ -52,7 +102,7 @@ def load_hidream(checkpoint_info, diffusers_load_config=None):
|
||||
if 'I1' in repo_id:
|
||||
cls = diffusers.HiDreamImagePipeline
|
||||
elif 'E1' in repo_id:
|
||||
from pipelines.hidream.pipeline_hidream_image_editing import HiDreamImageEditingPipeline
|
||||
from pipelines.hidream.hidream_e1 import HiDreamImageEditingPipeline
|
||||
cls = HiDreamImageEditingPipeline
|
||||
diffusers.pipelines.auto_pipeline.AUTO_TEXT2IMAGE_PIPELINES_MAPPING["hidream-e1"] = diffusers.HiDreamImagePipeline
|
||||
diffusers.pipelines.auto_pipeline.AUTO_IMAGE2IMAGE_PIPELINES_MAPPING["hidream-e1"] = HiDreamImageEditingPipeline
|
||||
|
||||
@@ -30,6 +30,7 @@ exclude = [
|
||||
"pipelines/meissonic",
|
||||
"pipelines/omnigen2",
|
||||
"pipelines/hdm",
|
||||
"pipelines/hidream",
|
||||
"pipelines/segmoe",
|
||||
"pipelines/xomni",
|
||||
"pipelines/chrono",
|
||||
|
||||
Reference in New Issue
Block a user