| from __future__ import annotations |
|
|
| import math |
| import os |
|
|
| import torch |
| from accelerate import init_empty_weights |
| from transformers import AutoTokenizer |
| from transformers.masking_utils import create_causal_mask |
| from tqdm import tqdm |
|
|
| from mmgp import offload |
| from shared.utils import files_locator as fl |
|
|
| from models.flux.modules.autoencoder_flux2 import AutoencoderKLFlux2, AutoEncoderParamsFlux2 |
| from .constants import IMAGE_POSITION_OFFSET, LLM_TOKEN_INDICATOR, OUTPUT_IMAGE_INDICATOR, QWEN3_VL_ACTIVATION_LAYERS, SEQUENCE_PADDING_INDICATOR |
| from .latent_norm import get_latent_norm |
| from .modeling_ideogram4 import Ideogram4Config, Ideogram4Transformer, get_linear_split_map |
| from .qwen3_vl_configuration import Qwen3VLConfig, register_qwen3_vl_config |
| from .qwen3_vl_transformers import Qwen3VLTextModel |
| from .sampler_configs import PRESETS |
| from .scheduler import get_schedule_for_resolution, make_step_intervals |
|
|
| _DEFAULT_PRESET = "V4_DEFAULT_20" |
| _TRANSFORMER_WRAPPER_PREFIX = "model.diffusion_model." |
| _SAMPLE_SOLVERS = {"euler", "res_2m", "res_2s"} |
|
|
|
|
| def _res_phi(order: int, neg_h: float) -> float: |
| if order == 1: |
| return 1.0 + neg_h * (0.5 + neg_h * (1.0 / 6.0 + neg_h / 24.0)) if abs(neg_h) < 1e-4 else math.expm1(neg_h) / neg_h |
| if order == 2: |
| return 0.5 + neg_h * (1.0 / 6.0 + neg_h * (1.0 / 24.0 + neg_h / 120.0)) if abs(neg_h) < 1e-3 else (math.expm1(neg_h) - neg_h) / (neg_h * neg_h) |
| raise ValueError(f"Unsupported RES phi order {order}") |
|
|
|
|
| def _res_2s_coefficients(h: float, c2: float = 0.5) -> tuple[float, float, float]: |
| f1 = _res_phi(1, -h) |
| f2 = _res_phi(2, -h) |
| a21 = c2 * _res_phi(1, -h * c2) |
| b2 = f2 / c2 |
| return a21, f1 - b2, b2 |
|
|
|
|
| def _res_2m_coefficients(h: float, h_prev: float) -> tuple[float, float]: |
| c2 = -h_prev / h |
| f1 = _res_phi(1, -h) |
| f2 = _res_phi(2, -h) |
| b2 = f2 / c2 |
| return f1 - b2, b2 |
|
|
|
|
| def _phase_label(step_idx: int, guide_phases: int, phase_switch_step: int, phase_switch_step2: int) -> str: |
| if guide_phases <= 1: |
| return "" |
| phase_no = 3 if guide_phases >= 3 and step_idx >= phase_switch_step2 else 2 if guide_phases >= 2 and step_idx >= phase_switch_step else 1 |
| return f"Phase {phase_no}/{guide_phases}" |
|
|
|
|
| def _phase_steps_description(num_steps: int, guide_phases: int, phase_switch_step: int, phase_switch_step2: int) -> str: |
| if guide_phases <= 1: |
| return "" |
| phase_switch_step = min(max(int(phase_switch_step), 0), num_steps) |
| phase_switch_step2 = min(max(int(phase_switch_step2), phase_switch_step), num_steps) |
| description = "Denoising Steps:" |
| description += " Phase 1 = None" if phase_switch_step == 0 else f" Phase 1 = 1:{phase_switch_step}" |
| if guide_phases >= 2: |
| description += ", Phase 2 = None" if phase_switch_step == phase_switch_step2 else f", Phase 2 = {phase_switch_step + 1}:{phase_switch_step2}" |
| if guide_phases >= 3 and phase_switch_step2 < num_steps: |
| description += f", Phase 3 = {phase_switch_step2 + 1}:{num_steps}" |
| return description |
|
|
|
|
| def _time_snr_shift(shift: float, t: float) -> float: |
| if shift == 1.0: |
| return t |
| return shift * t / (1.0 + (shift - 1.0) * t) |
|
|
|
|
| def _flow_model_timestep(t: float, shift: float) -> float: |
| return 1.0 - _time_snr_shift(shift, 1.0 - t) |
|
|
|
|
| def _custom_float(custom_settings: dict, key: str, default: float) -> float: |
| value = custom_settings.get(key, default) |
| return float(value) |
|
|
|
|
| def _apply_ideogram_lora_branches(conditional_transformer, unconditional_transformer, loras_slists, num_steps: int, phase_switch_step: int, phase_switch_step2: int) -> None: |
| if loras_slists is None: |
| return |
| from shared.utils.loras_mutipliers import update_loras_slists |
|
|
| update_loras_slists(conditional_transformer, loras_slists.get("cond", loras_slists), num_steps, phase_switch_step=phase_switch_step, phase_switch_step2=phase_switch_step2) |
| if unconditional_transformer is not None: |
| update_loras_slists(unconditional_transformer, loras_slists.get("uncond", loras_slists), num_steps, phase_switch_step=phase_switch_step, phase_switch_step2=phase_switch_step2) |
|
|
|
|
| def _strip_transformer_wrapper(state_dict, quantization_map=None, tied_weights_map=None): |
| if not any(key.startswith(_TRANSFORMER_WRAPPER_PREFIX) for key in state_dict): |
| return state_dict, quantization_map, tied_weights_map |
|
|
| def strip_mapping(mapping): |
| if mapping is None: |
| return None |
| prefix_len = len(_TRANSFORMER_WRAPPER_PREFIX) |
| return {key[prefix_len:]: value for key, value in mapping.items() if key.startswith(_TRANSFORMER_WRAPPER_PREFIX)} |
|
|
| return strip_mapping(state_dict), strip_mapping(quantization_map), strip_mapping(tied_weights_map) |
|
|
|
|
| def _load_transformer(filename: str, dtype: torch.dtype) -> Ideogram4Transformer: |
| config = Ideogram4Config() |
| split_map = get_linear_split_map(config.emb_dim) |
| with init_empty_weights(include_buffers=True): |
| transformer = Ideogram4Transformer(config) |
| transformer.rotary_emb.reset_inv_freq() |
| offload.load_model_data(transformer, filename, writable_tensors=False, default_dtype=dtype, fused_split_map=split_map, preprocess_sd=_strip_transformer_wrapper) |
| transformer.split_linear_modules_map = split_map |
| transformer.eval().requires_grad_(False) |
| return transformer |
|
|
|
|
| class Ideogram4TextEncoder(torch.nn.Module): |
| def __init__(self, config: Qwen3VLConfig) -> None: |
| super().__init__() |
| self.language_model = Qwen3VLTextModel(config.text_config) |
|
|
|
|
| def _load_text_encoder(filename: str, config_path: str, dtype: torch.dtype) -> Ideogram4TextEncoder: |
| register_qwen3_vl_config() |
| config = Qwen3VLConfig.from_json_file(config_path) |
| with init_empty_weights(include_buffers=True): |
| text_encoder = Ideogram4TextEncoder(config) |
| text_encoder.language_model.rotary_emb.reset_inv_freq() |
| offload.load_model_data(text_encoder.language_model, filename, modelPrefix="language_model", writable_tensors=False, default_dtype=dtype) |
| text_encoder.eval().requires_grad_(False) |
| return text_encoder |
|
|
|
|
| def _load_autoencoder(filename: str, dtype: torch.dtype) -> AutoencoderKLFlux2: |
| with init_empty_weights(include_buffers=True): |
| autoencoder = AutoencoderKLFlux2(AutoEncoderParamsFlux2()) |
| offload.load_model_data(autoencoder, filename, writable_tensors=False, default_dtype=dtype) |
| autoencoder.eval().requires_grad_(False) |
| return autoencoder |
|
|
|
|
| class Ideogram4WanPipeline: |
| def __init__(self, conditional_transformer, unconditional_transformer, text_encoder, text_tokenizer, autoencoder, dtype=torch.bfloat16) -> None: |
| self.conditional_transformer = conditional_transformer |
| self.unconditional_transformer = unconditional_transformer |
| self.text_encoder = text_encoder |
| self.text_tokenizer = text_tokenizer |
| self.autoencoder = autoencoder |
| self.dtype = dtype |
| self.patch_size = 2 |
| self.ae_scale_factor = 8 |
| self.max_text_tokens = 2048 |
| self._interrupt = False |
| shift, scale = get_latent_norm() |
| self.latent_shift = shift |
| self.latent_scale = scale |
|
|
| @property |
| def device(self) -> torch.device: |
| return next(self.conditional_transformer.parameters()).device |
|
|
| @property |
| def runtime_device(self) -> torch.device: |
| return torch.device("cuda" if torch.cuda.is_available() else self.device) |
|
|
| def _tokenize(self, prompt: str) -> tuple[torch.Tensor, int]: |
| messages = [{"role": "user", "content": [{"type": "text", "text": prompt}]}] |
| text = self.text_tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False) |
| encoded = self.text_tokenizer(text, return_tensors="pt", add_special_tokens=False) |
| token_ids = encoded["input_ids"][0] |
| num_text_tokens = int(token_ids.shape[0]) |
| if num_text_tokens > self.max_text_tokens: |
| raise ValueError(f"prompt has {num_text_tokens} tokens, exceeds max_text_tokens={self.max_text_tokens}") |
| return token_ids, num_text_tokens |
|
|
| def _build_inputs(self, prompts: list[str], height: int, width: int) -> dict[str, torch.Tensor | int]: |
| tokenized = [self._tokenize(p) for p in prompts] |
| batch_size = len(prompts) |
| patch = self.patch_size * self.ae_scale_factor |
| if height % patch != 0 or width % patch != 0: |
| raise ValueError(f"height/width must be divisible by {patch}") |
| grid_h = height // patch |
| grid_w = width // patch |
| num_image_tokens = grid_h * grid_w |
| max_text_tokens = max(num_text for _, num_text in tokenized) |
| total_seq_len = max_text_tokens + num_image_tokens |
|
|
| h_idx = torch.arange(grid_h).view(-1, 1).expand(grid_h, grid_w).reshape(-1) |
| w_idx = torch.arange(grid_w).view(1, -1).expand(grid_h, grid_w).reshape(-1) |
| t_idx = torch.zeros_like(h_idx) |
| image_pos = torch.stack([t_idx, h_idx, w_idx], dim=1) + IMAGE_POSITION_OFFSET |
|
|
| token_ids = torch.zeros(batch_size, max_text_tokens, dtype=torch.long) |
| text_position_ids = torch.zeros(batch_size, max_text_tokens, 3, dtype=torch.long) |
| position_ids = torch.zeros(batch_size, total_seq_len, 3, dtype=torch.long) |
| segment_ids = torch.full((batch_size, total_seq_len), SEQUENCE_PADDING_INDICATOR, dtype=torch.long) |
| indicator = torch.zeros(batch_size, total_seq_len, dtype=torch.long) |
|
|
| for batch_idx, (tokens, num_text) in enumerate(tokenized): |
| pad_len = max_text_tokens - num_text |
| total_unpadded = num_text + num_image_tokens |
| offset = pad_len |
| token_ids[batch_idx, offset:offset + num_text] = tokens |
| text_pos = torch.arange(num_text) |
| text_pos_3d = torch.stack([text_pos, text_pos, text_pos], dim=1) |
| text_position_ids[batch_idx, offset:offset + num_text] = text_pos_3d |
| position_ids[batch_idx, offset:offset + num_text] = text_pos_3d |
| position_ids[batch_idx, offset + num_text:] = image_pos |
| indicator[batch_idx, offset:offset + num_text] = LLM_TOKEN_INDICATOR |
| indicator[batch_idx, offset + num_text:] = OUTPUT_IMAGE_INDICATOR |
| segment_ids[batch_idx, offset:offset + total_unpadded] = 1 |
|
|
| device = self.runtime_device |
| return { |
| "token_ids": token_ids.to(device), |
| "text_position_ids": text_position_ids.to(device), |
| "position_ids": position_ids.to(device), |
| "segment_ids": segment_ids.to(device), |
| "indicator": indicator.to(device), |
| "num_image_tokens": num_image_tokens, |
| "grid_h": grid_h, |
| "grid_w": grid_w, |
| "max_text_tokens": max_text_tokens, |
| } |
|
|
| def _encode_text(self, token_ids: torch.Tensor, text_position_ids: torch.Tensor, indicator: torch.Tensor) -> torch.Tensor | None: |
| attention_mask = (indicator == LLM_TOKEN_INDICATOR).to(torch.long) |
| pos_2d = text_position_ids[..., 0].contiguous() |
| language_model = self.text_encoder.language_model |
| language_model._interrupt = self._interrupt |
| with torch.inference_mode(): |
| inputs_embeds = language_model.embed_tokens(token_ids) |
| position_ids = pos_2d[None, ...].expand(4, pos_2d.shape[0], -1) |
| text_position_ids = position_ids[0] |
| mrope_position_ids = position_ids[1:] |
| cache_position = torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device) |
| causal_mask = create_causal_mask( |
| config=language_model.config, |
| input_embeds=inputs_embeds, |
| attention_mask=attention_mask, |
| cache_position=cache_position, |
| past_key_values=None, |
| position_ids=text_position_ids, |
| ) |
| position_embeddings = language_model.rotary_emb(inputs_embeds, mrope_position_ids) |
| tap_layers = set(QWEN3_VL_ACTIVATION_LAYERS) |
| captured = {} |
| hidden_states = inputs_embeds |
| for layer_idx, decoder_layer in enumerate(language_model.layers): |
| hidden_states = decoder_layer( |
| hidden_states, |
| attention_mask=causal_mask, |
| position_ids=text_position_ids, |
| past_key_values=None, |
| cache_position=cache_position, |
| position_embeddings=position_embeddings, |
| ) |
| if layer_idx in tap_layers: |
| captured[layer_idx] = hidden_states.clone() |
| if self._interrupt: |
| return None |
| del hidden_states, inputs_embeds, position_embeddings, causal_mask, position_ids, mrope_position_ids, cache_position, text_position_ids |
| first = captured[QWEN3_VL_ACTIVATION_LAYERS[0]] |
| batch_size, seq_len, hidden_size = first.shape |
| stacked = first.new_empty(batch_size, seq_len, hidden_size, len(QWEN3_VL_ACTIVATION_LAYERS)) |
| for capture_idx, layer_idx in enumerate(QWEN3_VL_ACTIVATION_LAYERS): |
| hidden = captured.pop(layer_idx) |
| stacked[..., capture_idx].copy_(hidden) |
| del hidden |
| stacked = stacked.reshape(batch_size, seq_len, hidden_size * len(QWEN3_VL_ACTIVATION_LAYERS)) |
| stacked.mul_(attention_mask.to(device=stacked.device, dtype=stacked.dtype).unsqueeze(-1)) |
| return stacked |
|
|
| def _decode_image(self, z: torch.Tensor, grid_h: int, grid_w: int) -> torch.Tensor: |
| z = self._unpack_vae_latents(z, grid_h, grid_w) |
| vae_dtype = next(self.autoencoder.decoder.parameters()).dtype |
| return self.autoencoder.decoder(z.to(vae_dtype)).float().clamp(-1.0, 1.0) |
|
|
| def _decode(self, z: torch.Tensor, grid_h: int, grid_w: int) -> torch.Tensor: |
| return self._decode_image(z, grid_h, grid_w).cpu().transpose(0, 1) |
|
|
| def _unpack_vae_latents(self, z: torch.Tensor, grid_h: int, grid_w: int) -> torch.Tensor: |
| z = self._normalize_packed_latents(z) |
| batch_size = z.shape[0] |
| patch = self.patch_size |
| ae_channels = z.shape[-1] // (patch * patch) |
| z = z.view(batch_size, grid_h, grid_w, patch, patch, ae_channels) |
| z = z.permute(0, 5, 1, 3, 2, 4).contiguous() |
| return z.view(batch_size, ae_channels, grid_h * patch, grid_w * patch) |
|
|
| def _normalize_packed_latents(self, z: torch.Tensor) -> torch.Tensor: |
| latent_shift = self.latent_shift.to(z.device, z.dtype) |
| latent_scale = self.latent_scale.to(z.device, z.dtype) |
| return z * latent_scale + latent_shift |
|
|
| def _pack_vae_upsampler_lq_latent(self, z: torch.Tensor, grid_h: int, grid_w: int) -> torch.Tensor: |
| z = self._normalize_packed_latents(z) |
| batch_size = z.shape[0] |
| patch = self.patch_size |
| ae_channels = z.shape[-1] // (patch * patch) |
| z = z.view(batch_size, grid_h, grid_w, patch, patch, ae_channels).permute(0, 5, 3, 4, 1, 2).contiguous() |
| z = z.view(batch_size, ae_channels * patch * patch, grid_h, grid_w) |
| vae_mean = self.autoencoder.bn.running_mean.view(1, -1, 1, 1).to(device=z.device, dtype=z.dtype) |
| vae_scale = torch.sqrt(self.autoencoder.bn.running_var.view(1, -1, 1, 1) + self.autoencoder.bn_eps).to(device=z.device, dtype=z.dtype) |
| return z.sub(vae_mean).div(vae_scale) |
|
|
| @torch.inference_mode() |
| def __call__( |
| self, |
| prompts: str | list[str], |
| *, |
| height: int = 1024, |
| width: int = 1024, |
| num_steps: int = 20, |
| guidance_scale: float = 7.0, |
| guidance2_scale: float = 3.0, |
| guidance3_scale: float = 3.0, |
| guidance_schedule=None, |
| mu: float = 0.0, |
| std: float = 1.75, |
| sample_solver: str = "euler", |
| flow_shift: float = 1.0, |
| guide_phases: int = 1, |
| switch_threshold: int = 0, |
| switch2_threshold: int = 0, |
| loras_slists=None, |
| seed: int | None = None, |
| callback=None, |
| vae_upsampler=None, |
| set_progress_status=None, |
| set_header_text=None, |
| ) -> torch.Tensor | None: |
| if isinstance(prompts, str): |
| prompts = [prompts] |
| sample_solver = (sample_solver or "euler").lower() |
| if sample_solver not in _SAMPLE_SOLVERS: |
| raise ValueError(f"Unsupported Ideogram 4 sampler '{sample_solver}'.") |
| device = self.runtime_device |
| schedule = get_schedule_for_resolution((height, width), known_mean=mu, std=std) |
| step_intervals = make_step_intervals(num_steps).to(device) |
| time_points = schedule(step_intervals).to(device) |
| sigma_points = 1.0 - time_points |
| phase_switch_step = num_steps |
| phase_switch_step2 = num_steps |
| if guidance_schedule is not None: |
| gw_per_step = torch.as_tensor(guidance_schedule, dtype=torch.float32, device=device) |
| else: |
| gw_per_step = torch.full((num_steps,), float(guidance_scale), dtype=torch.float32, device=device) |
| if int(guide_phases) >= 2 and int(switch_threshold) > 0: |
| switch_sigma = float(switch_threshold) / 1000.0 |
| phase_switch_step = int((sigma_points[1:] > switch_sigma).sum().item()) |
| override_mask = sigma_points[1:] <= switch_sigma |
| gw_per_step = torch.where(override_mask, torch.full_like(gw_per_step, float(guidance2_scale)), gw_per_step) |
| if int(guide_phases) >= 3 and int(switch2_threshold) > 0: |
| switch2_sigma = float(switch2_threshold) / 1000.0 |
| phase_switch_step2 = int((sigma_points[1:] > switch2_sigma).sum().item()) |
| override2_mask = sigma_points[1:] <= switch2_sigma |
| gw_per_step = torch.where(override2_mask, torch.full_like(gw_per_step, float(guidance3_scale)), gw_per_step) |
|
|
| _apply_ideogram_lora_branches(self.conditional_transformer, self.unconditional_transformer, loras_slists, num_steps, phase_switch_step, phase_switch_step2) |
| phase_description = _phase_steps_description(num_steps, int(guide_phases), phase_switch_step, phase_switch_step2) |
| if len(phase_description) > 0 and callable(set_header_text): |
| set_header_text(phase_description) |
|
|
| inputs = self._build_inputs(prompts, height=height, width=width) |
| if self._interrupt: |
| return None |
| batch_size = len(prompts) |
| num_image_tokens = inputs["num_image_tokens"] |
| grid_h = inputs["grid_h"] |
| grid_w = inputs["grid_w"] |
| max_text_tokens = inputs["max_text_tokens"] |
| latent_dim = self.conditional_transformer.config.in_channels |
|
|
| llm_features = self._encode_text( |
| inputs["token_ids"], |
| inputs["text_position_ids"], |
| inputs["indicator"][:, :max_text_tokens], |
| ) |
| if llm_features is None or self._interrupt: |
| return None |
|
|
| if self.unconditional_transformer is not None: |
| neg_position_ids = inputs["position_ids"][:, max_text_tokens:] |
| neg_segment_ids = inputs["segment_ids"][:, max_text_tokens:] |
| neg_indicator = inputs["indicator"][:, max_text_tokens:] |
| neg_llm_features = llm_features.new_empty(batch_size, 0, llm_features.shape[-1]) |
|
|
| generator = torch.Generator(device=device) |
| if seed is not None and seed >= 0: |
| generator.manual_seed(int(seed)) |
| z = torch.randn(batch_size, num_image_tokens, latent_dim, dtype=torch.float32, device=device, generator=generator) |
| pos_z = torch.empty(batch_size, max_text_tokens + num_image_tokens, latent_dim, dtype=torch.float32, device=device) |
| pos_z[:, :max_text_tokens].zero_() |
|
|
| if callback is not None: |
| callback(-1, None, True, override_num_inference_steps=num_steps, denoising_extra=_phase_label(0, int(guide_phases), phase_switch_step, phase_switch_step2)) |
|
|
| def predict_velocity(current_z: torch.Tensor, t_val: float, guidance: torch.Tensor) -> torch.Tensor | None: |
| model_t_val = _flow_model_timestep(t_val, float(flow_shift)) |
| t = torch.full((batch_size,), model_t_val, dtype=torch.float32, device=device) |
| pos_z[:, max_text_tokens:].copy_(current_z) |
| pos_out = self.conditional_transformer( |
| llm_features=llm_features, |
| x=pos_z, |
| t=t, |
| position_ids=inputs["position_ids"], |
| segment_ids=inputs["segment_ids"], |
| indicator=inputs["indicator"], |
| ) |
| if pos_out is None: |
| return None |
| pos_v = pos_out[:, max_text_tokens:] |
| if self.unconditional_transformer is None: |
| return pos_v |
|
|
| neg_v = self.unconditional_transformer( |
| llm_features=neg_llm_features, |
| x=current_z, |
| t=t, |
| position_ids=neg_position_ids, |
| segment_ids=neg_segment_ids, |
| indicator=neg_indicator, |
| ) |
| if neg_v is None: |
| return None |
| return guidance * pos_v + (1.0 - guidance) * neg_v |
|
|
| prev_denoised = None |
| prev_sigma = None |
| for step_idx, i in enumerate(tqdm(range(num_steps - 1, -1, -1), total=num_steps, desc="Denoising")): |
| if self._interrupt: |
| return None |
| t_val = float(time_points[i + 1].item()) |
| s_val = float(time_points[i].item()) |
| sigma = float(sigma_points[i + 1].item()) |
| sigma_down = float(sigma_points[i].item()) |
| guidance = gw_per_step[i] |
| denoising_extra = _phase_label(step_idx, int(guide_phases), phase_switch_step, phase_switch_step2) |
| v = predict_velocity(z, t_val, guidance) |
| if v is None: |
| return None |
|
|
| h = -math.log(sigma_down / sigma) if sigma_down > 0.0 else 0.0 |
| denoised = z + v * sigma |
| if sample_solver == "res_2m" and prev_denoised is not None and sigma_down > 0.0 and h < 1.0: |
| b1, b2 = _res_2m_coefficients(h, -math.log(sigma / prev_sigma)) |
| z = z + h * (b1 * (denoised - z) + b2 * (prev_denoised - z)) |
| elif sample_solver in {"res_2s", "res_2m"} and sigma_down > 0.0 and (sample_solver == "res_2s" or sigma >= 0.1): |
| a21, b1, b2 = _res_2s_coefficients(h) |
| sub_sigma = sigma * math.exp(-0.5 * h) |
| sub_z = z + h * a21 * (denoised - z) |
| sub_v = predict_velocity(sub_z, 1.0 - sub_sigma, guidance) |
| if sub_v is None: |
| return None |
| sub_denoised = sub_z + sub_v * sub_sigma |
| z = z + h * (b1 * (denoised - z) + b2 * (sub_denoised - z)) |
| else: |
| z = z + v * (s_val - t_val) |
| prev_denoised = denoised |
| prev_sigma = sigma |
| if callback is not None: |
| preview = self._unpack_vae_latents(z[:1], grid_h, grid_w)[0].unsqueeze(1) |
| callback(step_idx, preview, False, denoising_extra=denoising_extra) |
|
|
| if self._interrupt: |
| return None |
| if vae_upsampler is None: |
| return self._decode(z, grid_h=grid_h, grid_w=grid_w) |
|
|
| def _vae_upsampler_progress(_phase, current_step=None, total_steps=None): |
| if callable(set_progress_status): |
| progress_label = getattr(vae_upsampler, "progress_label", "VAE Spatial Upsampling") |
| if current_step is None or total_steps is None: |
| set_progress_status(f"{progress_label} in progress") |
| else: |
| total_steps = int(total_steps) |
| step_no = min(int(current_step) + 1, total_steps) |
| set_progress_status(f"{progress_label} in progress ({step_no}/{total_steps})") |
|
|
| _vae_upsampler_progress(None) |
| lq_image_ref = [self._decode_image(z, grid_h=grid_h, grid_w=grid_w)] |
| lq_latent_ref = [self._pack_vae_upsampler_lq_latent(z, grid_h, grid_w)] |
| image = vae_upsampler.decode_inputs( |
| lq_image_ref, |
| lq_latent_ref, |
| prompt=prompts, |
| seed=seed, |
| abort_callback=lambda: self._interrupt, |
| progress_callback=_vae_upsampler_progress, |
| ) |
| if image is None: |
| return None |
| return image.cpu().transpose(0, 1) |
|
|
|
|
| class model_factory: |
| def __init__( |
| self, |
| checkpoint_dir, |
| model_filename=None, |
| model_type=None, |
| model_def=None, |
| base_model_type=None, |
| text_encoder_filename=None, |
| dtype=torch.bfloat16, |
| VAE_dtype=torch.float32, |
| save_quantized=False, |
| **kwargs, |
| ): |
| model_def = model_def or {} |
| conditional_only = bool(model_def.get("conditional_transformer_only", False)) |
| min_transformers = 1 if conditional_only else 2 |
| if not isinstance(model_filename, (list, tuple)) or len(model_filename) < min_transformers: |
| raise ValueError("Ideogram 4 requires a conditional transformer file." if conditional_only else "Ideogram 4 requires conditional and unconditional transformer files.") |
| if text_encoder_filename is None: |
| raise ValueError("Ideogram 4 requires a Qwen3-VL text encoder file.") |
|
|
| self.model_type = model_type |
| self.base_model_type = base_model_type |
| self.model_def = model_def |
| dtype = torch.bfloat16 |
| self.dtype = dtype |
|
|
| text_encoder_folder = model_def.get("text_encoder_folder", "Qwen3-VL-8B-Instruct") |
| tokenizer_path = os.path.dirname(fl.locate_file(os.path.join(text_encoder_folder, "tokenizer_config.json"))) |
| text_config_path = fl.locate_file(os.path.join(text_encoder_folder, "config.json")) |
| vae_filename = fl.locate_file("flux2_vae.safetensors") |
|
|
| self.conditional_transformer = _load_transformer(model_filename[0], dtype) |
| self.unconditional_transformer = None if conditional_only else _load_transformer(model_filename[1], dtype) |
| self.transformer = self.conditional_transformer |
| self.model = self.conditional_transformer |
| if self.unconditional_transformer is not None: |
| self.transformer2 = self.unconditional_transformer |
| self.model2 = self.unconditional_transformer |
| if save_quantized: |
| from wgp import save_quantized_model |
| save_quantized_model(self.conditional_transformer, model_type, model_filename[0], dtype, None) |
| if self.unconditional_transformer is not None: |
| save_quantized_model(self.unconditional_transformer, model_type, model_filename[1], dtype, None, submodel_no=2) |
| self.text_encoder = _load_text_encoder(text_encoder_filename, text_config_path, dtype) |
| self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_path, extra_special_tokens={}) |
| self.autoencoder = _load_autoencoder(vae_filename, VAE_dtype) |
| self.pipeline = Ideogram4WanPipeline( |
| self.conditional_transformer, |
| self.unconditional_transformer, |
| self.text_encoder, |
| self.tokenizer, |
| self.autoencoder, |
| dtype=VAE_dtype, |
| ) |
|
|
| def generate( |
| self, |
| seed=None, |
| input_prompt="", |
| sample_solver="euler", |
| width=1024, |
| height=1024, |
| sampling_steps=20, |
| guide_scale=7.0, |
| guide2_scale=3.0, |
| guide3_scale=3.0, |
| shift=1.0, |
| switch_threshold=0, |
| switch2_threshold=0, |
| guide_phases=1, |
| batch_size=1, |
| model_mode=None, |
| custom_settings=None, |
| loras_slists=None, |
| vae_upsampler=None, |
| set_progress_status=None, |
| set_header_text=None, |
| callback=None, |
| **kwargs, |
| ): |
| preset = PRESETS.get(model_mode) |
| custom_settings = custom_settings if isinstance(custom_settings, dict) else {} |
| num_steps = int(preset.num_steps if preset is not None else sampling_steps) |
| mu = _custom_float(custom_settings, "ideogram_mu", preset.mu if preset is not None else 0.0) |
| std = _custom_float(custom_settings, "ideogram_std", preset.std if preset is not None else 1.75) |
| guidance_schedule = preset.guidance_schedule if preset is not None and len(custom_settings) == 0 else None |
|
|
| prompts = [input_prompt] * int(batch_size) |
| return self.pipeline( |
| prompts, |
| height=height, |
| width=width, |
| num_steps=num_steps, |
| guidance_scale=guide_scale, |
| guidance2_scale=guide2_scale, |
| guidance3_scale=guide3_scale, |
| guidance_schedule=guidance_schedule, |
| mu=mu, |
| std=std, |
| sample_solver=sample_solver, |
| flow_shift=float(shift), |
| guide_phases=guide_phases, |
| switch_threshold=switch_threshold, |
| switch2_threshold=switch2_threshold, |
| loras_slists=loras_slists, |
| seed=seed, |
| callback=callback, |
| vae_upsampler=vae_upsampler, |
| set_progress_status=set_progress_status, |
| set_header_text=set_header_text, |
| ) |
|
|
| @property |
| def _interrupt(self): |
| return getattr(self.pipeline, "_interrupt", False) |
|
|
| @_interrupt.setter |
| def _interrupt(self, value): |
| if hasattr(self, "pipeline"): |
| self.pipeline._interrupt = bool(value) |
| self.conditional_transformer._interrupt = bool(value) |
| if self.unconditional_transformer is not None: |
| self.unconditional_transformer._interrupt = bool(value) |
| if hasattr(self.text_encoder, "language_model"): |
| self.text_encoder.language_model._interrupt = bool(value) |
|
|