add hidream-o1

Co-authored-by: Copilot <copilot@github.com>
Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2026-05-10 21:45:19 +02:00
parent 0d49cf0f7e
commit 7ee740e744
26 changed files with 4374 additions and 32 deletions
+4
View File
@@ -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.
+4
View File
@@ -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.
+102
View File
@@ -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
+6
View File
@@ -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
-1
View File
@@ -4,7 +4,6 @@
### Assigned
- Check Outpaint, @vladmandic
- Chat-based interface, @vladmandic
- Control tab verify overrides handling, @vladmandic
- Reimplement `llama` remover for Kanvas, @vladmandic
+10
View File
@@ -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",
+9
View File
@@ -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.",
+1 -1
View File
@@ -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?
+2
View File
@@ -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:
+17 -6
View File
@@ -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:
+2
View File
@@ -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
+6 -3
View File
@@ -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.
+4
View File
@@ -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():
+8
View File
@@ -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
View File
@@ -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
+458
View File
@@ -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,
)
+369
View File
@@ -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
+778
View File
@@ -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
+51 -1
View File
@@ -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
+1
View File
@@ -30,6 +30,7 @@ exclude = [
"pipelines/meissonic",
"pipelines/omnigen2",
"pipelines/hdm",
"pipelines/hidream",
"pipelines/segmoe",
"pipelines/xomni",
"pipelines/chrono",