imagehub 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- imagehub/__init__.py +48 -0
- imagehub/backends/__init__.py +5 -0
- imagehub/backends/amused.py +345 -0
- imagehub/backends/auraflow.py +302 -0
- imagehub/backends/bagel.py +247 -0
- imagehub/backends/base.py +106 -0
- imagehub/backends/chroma.py +434 -0
- imagehub/backends/cogview3_plus.py +356 -0
- imagehub/backends/cogview4.py +354 -0
- imagehub/backends/cosmos2.py +279 -0
- imagehub/backends/deepfloyd_if.py +290 -0
- imagehub/backends/dreamlite.py +348 -0
- imagehub/backends/emu3.py +260 -0
- imagehub/backends/ernie_image.py +307 -0
- imagehub/backends/flux.py +558 -0
- imagehub/backends/flux2.py +392 -0
- imagehub/backends/flux_kontext.py +68 -0
- imagehub/backends/glm_image.py +456 -0
- imagehub/backends/hart.py +387 -0
- imagehub/backends/hidream.py +428 -0
- imagehub/backends/hidream_o1.py +397 -0
- imagehub/backends/hunyuan_dit.py +455 -0
- imagehub/backends/hunyuanimage.py +400 -0
- imagehub/backends/ideogram4.py +423 -0
- imagehub/backends/infinity.py +315 -0
- imagehub/backends/janus.py +279 -0
- imagehub/backends/joyai_image.py +442 -0
- imagehub/backends/kandinsky21.py +461 -0
- imagehub/backends/kandinsky22.py +404 -0
- imagehub/backends/kandinsky3.py +334 -0
- imagehub/backends/kandinsky5.py +463 -0
- imagehub/backends/kolors.py +355 -0
- imagehub/backends/krea2.py +499 -0
- imagehub/backends/ldm.py +199 -0
- imagehub/backends/llamagen.py +481 -0
- imagehub/backends/longcat_image.py +708 -0
- imagehub/backends/lumina2.py +376 -0
- imagehub/backends/lumina_dimoo.py +307 -0
- imagehub/backends/lumina_next.py +360 -0
- imagehub/backends/meissonic.py +330 -0
- imagehub/backends/nextstep.py +215 -0
- imagehub/backends/nucleus_image.py +253 -0
- imagehub/backends/omnigen.py +331 -0
- imagehub/backends/omnigen_inputs.py +225 -0
- imagehub/backends/ovis_image.py +266 -0
- imagehub/backends/pixart.py +538 -0
- imagehub/backends/pixeldit.py +341 -0
- imagehub/backends/prx.py +383 -0
- imagehub/backends/qwen_image.py +944 -0
- imagehub/backends/sana.py +507 -0
- imagehub/backends/sdxl_refiner.py +175 -0
- imagehub/backends/show_o.py +308 -0
- imagehub/backends/stable_cascade.py +377 -0
- imagehub/backends/stable_diffusion.py +434 -0
- imagehub/backends/stable_diffusion_3.py +411 -0
- imagehub/backends/stable_diffusion_xl.py +557 -0
- imagehub/backends/visualcloze.py +535 -0
- imagehub/backends/wuerstchen.py +395 -0
- imagehub/backends/z_image.py +331 -0
- imagehub/caption_verifier.py +287 -0
- imagehub/catalog.py +4094 -0
- imagehub/checkpoints.py +408 -0
- imagehub/download.py +169 -0
- imagehub/exceptions.py +33 -0
- imagehub/factory.py +60 -0
- imagehub/image.py +183 -0
- imagehub/licensing.py +22 -0
- imagehub/lora.py +651 -0
- imagehub/lora_formats.py +503 -0
- imagehub/models/__init__.py +1 -0
- imagehub/models/auraflow_transformer.py +365 -0
- imagehub/models/autoencoder_dc.py +379 -0
- imagehub/models/autoencoder_ideogram4.py +572 -0
- imagehub/models/autoencoder_kl.py +228 -0
- imagehub/models/autoencoder_kl_flux2.py +221 -0
- imagehub/models/autoencoder_kl_hunyuanimage.py +217 -0
- imagehub/models/autoencoder_kl_qwen_image.py +506 -0
- imagehub/models/autoencoder_kl_qwen_image21.py +648 -0
- imagehub/models/autoencoder_kl_wan.py +252 -0
- imagehub/models/autoencoder_tiny.py +141 -0
- imagehub/models/bagel.py +422 -0
- imagehub/models/bert.py +196 -0
- imagehub/models/chatglm.py +271 -0
- imagehub/models/chroma_transformer.py +485 -0
- imagehub/models/clip_text.py +191 -0
- imagehub/models/clip_vision.py +158 -0
- imagehub/models/cogview3_plus_transformer.py +363 -0
- imagehub/models/cogview4_transformer.py +479 -0
- imagehub/models/cosmos_transformer.py +307 -0
- imagehub/models/dreamlite.py +467 -0
- imagehub/models/emu3.py +153 -0
- imagehub/models/emu3_vq.py +315 -0
- imagehub/models/ernie_image_transformer.py +277 -0
- imagehub/models/flux2_transformer.py +425 -0
- imagehub/models/flux_transformer.py +425 -0
- imagehub/models/fp8.py +120 -0
- imagehub/models/gemma.py +121 -0
- imagehub/models/gemma2.py +231 -0
- imagehub/models/glm.py +239 -0
- imagehub/models/glm_image_ar.py +341 -0
- imagehub/models/glm_image_transformer.py +199 -0
- imagehub/models/hart.py +502 -0
- imagehub/models/hidream_o1.py +208 -0
- imagehub/models/hidream_transformer.py +565 -0
- imagehub/models/hunyuan_dit_transformer.py +539 -0
- imagehub/models/hunyuanimage_transformer.py +514 -0
- imagehub/models/ideogram4_text.py +150 -0
- imagehub/models/ideogram4_transformer.py +332 -0
- imagehub/models/infinity.py +381 -0
- imagehub/models/infinity_vae.py +202 -0
- imagehub/models/janus.py +56 -0
- imagehub/models/janus_vq.py +253 -0
- imagehub/models/joyimage_transformer.py +329 -0
- imagehub/models/kandinsky3_unet.py +455 -0
- imagehub/models/kandinsky5_transformer.py +420 -0
- imagehub/models/krea2_transformer.py +430 -0
- imagehub/models/layers.py +91 -0
- imagehub/models/ldm_bert.py +116 -0
- imagehub/models/llada.py +240 -0
- imagehub/models/llama.py +304 -0
- imagehub/models/llama_text.py +231 -0
- imagehub/models/llamagen.py +310 -0
- imagehub/models/llamagen_vq.py +209 -0
- imagehub/models/longcat_image_transformer.py +160 -0
- imagehub/models/lumina2_transformer.py +422 -0
- imagehub/models/lumina_next_transformer.py +426 -0
- imagehub/models/magvit_v2.py +241 -0
- imagehub/models/meissonic_transformer.py +154 -0
- imagehub/models/mistral3_text.py +312 -0
- imagehub/models/multilingual_clip.py +45 -0
- imagehub/models/nextstep.py +408 -0
- imagehub/models/nucleus_image_transformer.py +346 -0
- imagehub/models/omnigen_transformer.py +380 -0
- imagehub/models/ovis_image_transformer.py +248 -0
- imagehub/models/paella_vq.py +110 -0
- imagehub/models/pixart_transformer.py +526 -0
- imagehub/models/pixeldit.py +472 -0
- imagehub/models/prior_transformer.py +267 -0
- imagehub/models/prx_transformer.py +256 -0
- imagehub/models/qwen2.py +163 -0
- imagehub/models/qwen2_5_vl_text.py +657 -0
- imagehub/models/qwen3.py +264 -0
- imagehub/models/qwen3_vl.py +251 -0
- imagehub/models/qwen3_vl_text.py +255 -0
- imagehub/models/qwen_image21_transformer.py +579 -0
- imagehub/models/qwen_image_transformer.py +352 -0
- imagehub/models/sana_transformer.py +464 -0
- imagehub/models/sd3_transformer.py +517 -0
- imagehub/models/show_o.py +176 -0
- imagehub/models/stable_cascade_unet.py +463 -0
- imagehub/models/t5.py +277 -0
- imagehub/models/t5gemma.py +137 -0
- imagehub/models/umt5.py +64 -0
- imagehub/models/unet_2d.py +978 -0
- imagehub/models/uvit_2d.py +397 -0
- imagehub/models/vq_model.py +331 -0
- imagehub/models/wuerstchen.py +363 -0
- imagehub/models/xlm_roberta.py +221 -0
- imagehub/models/z_image_transformer.py +410 -0
- imagehub/pipelines/__init__.py +4 -0
- imagehub/pipelines/base.py +105 -0
- imagehub/pipelines/text_to_image.py +27 -0
- imagehub/pretrained.py +197 -0
- imagehub/registry.py +181 -0
- imagehub/schedulers/__init__.py +1 -0
- imagehub/schedulers/amused.py +139 -0
- imagehub/schedulers/ddim.py +140 -0
- imagehub/schedulers/ddim_cogvideox.py +157 -0
- imagehub/schedulers/ddpm.py +232 -0
- imagehub/schedulers/ddpm_wuerstchen.py +81 -0
- imagehub/schedulers/deis_multistep.py +219 -0
- imagehub/schedulers/dpmsolver_multistep.py +249 -0
- imagehub/schedulers/edm_dpmsolver.py +180 -0
- imagehub/schedulers/euler.py +195 -0
- imagehub/schedulers/euler_ancestral.py +158 -0
- imagehub/schedulers/flow_match_euler.py +388 -0
- imagehub/schedulers/flow_match_lcm.py +187 -0
- imagehub/schedulers/flow_unipc.py +166 -0
- imagehub/schedulers/ideogram4.py +95 -0
- imagehub/schedulers/lcm.py +191 -0
- imagehub/schedulers/pndm.py +111 -0
- imagehub/schedulers/scm.py +121 -0
- imagehub/schedulers/unclip.py +133 -0
- imagehub/schedulers/unipc.py +193 -0
- imagehub/tokenization.py +258 -0
- imagehub/tokenization_auraflow.py +84 -0
- imagehub/tokenization_bert.py +187 -0
- imagehub/tokenization_byt5.py +90 -0
- imagehub/tokenization_chatglm.py +197 -0
- imagehub/tokenization_codegen.py +145 -0
- imagehub/tokenization_deepseek.py +229 -0
- imagehub/tokenization_emu3.py +103 -0
- imagehub/tokenization_gemma.py +175 -0
- imagehub/tokenization_glm.py +202 -0
- imagehub/tokenization_glm_image.py +65 -0
- imagehub/tokenization_llama3.py +135 -0
- imagehub/tokenization_mistral.py +225 -0
- imagehub/tokenization_omnigen.py +48 -0
- imagehub/tokenization_qwen.py +374 -0
- imagehub/tokenization_t5.py +543 -0
- imagehub/tokenization_xlm_roberta.py +186 -0
- imagehub/training/__init__.py +44 -0
- imagehub/training/arguments.py +211 -0
- imagehub/training/checkpointing.py +32 -0
- imagehub/training/data.py +241 -0
- imagehub/training/distributed.py +96 -0
- imagehub/training/family.py +147 -0
- imagehub/training/flow_matching.py +143 -0
- imagehub/training/flux.py +237 -0
- imagehub/training/flux2.py +292 -0
- imagehub/training/hidream.py +292 -0
- imagehub/training/krea2.py +301 -0
- imagehub/training/lumina2.py +219 -0
- imagehub/training/qwen_image.py +355 -0
- imagehub/training/reporting.py +84 -0
- imagehub/training/sana.py +259 -0
- imagehub/training/schedules.py +38 -0
- imagehub/training/sd1.py +251 -0
- imagehub/training/sd3.py +222 -0
- imagehub/training/sdxl.py +148 -0
- imagehub/training/specs.py +178 -0
- imagehub/training/trainer.py +497 -0
- imagehub/training/z_image.py +224 -0
- imagehub/types.py +40 -0
- imagehub-0.1.0.dist-info/METADATA +284 -0
- imagehub-0.1.0.dist-info/RECORD +272 -0
- imagehub-0.1.0.dist-info/WHEEL +4 -0
- imagehub-0.1.0.dist-info/licenses/LICENSE +202 -0
- imagehub-0.1.0.dist-info/licenses/NOTICE +91 -0
- imagehub-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +73 -0
- imagehub-0.1.0.dist-info/licenses/licenses/BK-SDM-CreativeML-OpenRAIL-M.txt +83 -0
- imagehub-0.1.0.dist-info/licenses/licenses/Diffusers-Apache-2.0.txt +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/ETRI-KOALA-Dual-License.txt +206 -0
- imagehub-0.1.0.dist-info/licenses/licenses/HART-MIT.txt +21 -0
- imagehub-0.1.0.dist-info/licenses/licenses/HiDream-O1-MIT.txt +21 -0
- imagehub-0.1.0.dist-info/licenses/licenses/Ideogram4-Apache-2.0.txt +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/Infinity-MIT.txt +21 -0
- imagehub-0.1.0.dist-info/licenses/licenses/LlamaGen-MIT.txt +21 -0
- imagehub-0.1.0.dist-info/licenses/licenses/Lumina-DiMOO-Apache-2.0.txt +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/Meissonic-Apache-2.0.txt +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/OpenAI-CLIP-MIT.txt +22 -0
- imagehub-0.1.0.dist-info/licenses/licenses/PixelDiT-NSCLv1.txt +34 -0
- imagehub-0.1.0.dist-info/licenses/licenses/SDXS-Apache-2.0.txt +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/Segmind-SSD-1B-Apache-2.0.txt +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/Segmind-distill-sd-CreativeML-OpenRAIL-M.txt +163 -0
- imagehub-0.1.0.dist-info/licenses/licenses/Show-o-Apache-2.0.txt +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/Transformers-Apache-2.0.txt +203 -0
- imagehub-0.1.0.dist-info/licenses/licenses/bagel/LICENSE +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/bagel/SOURCE.json +7 -0
- imagehub-0.1.0.dist-info/licenses/licenses/emu3/LICENSE +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/emu3/SOURCE.json +7 -0
- imagehub-0.1.0.dist-info/licenses/licenses/hart/LICENSE +21 -0
- imagehub-0.1.0.dist-info/licenses/licenses/hart/SOURCE.json +7 -0
- imagehub-0.1.0.dist-info/licenses/licenses/hidream-o1/LICENSE +21 -0
- imagehub-0.1.0.dist-info/licenses/licenses/hidream-o1/SOURCE.json +7 -0
- imagehub-0.1.0.dist-info/licenses/licenses/ideogram4/LICENSE +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/ideogram4/SOURCE.json +7 -0
- imagehub-0.1.0.dist-info/licenses/licenses/infinity/LICENSE +21 -0
- imagehub-0.1.0.dist-info/licenses/licenses/infinity/SOURCE.json +7 -0
- imagehub-0.1.0.dist-info/licenses/licenses/llamagen/LICENSE +21 -0
- imagehub-0.1.0.dist-info/licenses/licenses/llamagen/SOURCE.json +7 -0
- imagehub-0.1.0.dist-info/licenses/licenses/lumina-dimoo/LICENSE +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/lumina-dimoo/SOURCE.json +7 -0
- imagehub-0.1.0.dist-info/licenses/licenses/meissonic/LICENSE +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/meissonic/SOURCE.json +7 -0
- imagehub-0.1.0.dist-info/licenses/licenses/nextstep/LICENSE +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/nextstep/SOURCE.json +7 -0
- imagehub-0.1.0.dist-info/licenses/licenses/pixeldit/LICENSE +34 -0
- imagehub-0.1.0.dist-info/licenses/licenses/pixeldit/NOTICE +72 -0
- imagehub-0.1.0.dist-info/licenses/licenses/pixeldit/SOURCE.json +7 -0
- imagehub-0.1.0.dist-info/licenses/licenses/show-o/LICENSE +201 -0
- imagehub-0.1.0.dist-info/licenses/licenses/show-o/SOURCE.json +7 -0
imagehub/__init__.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""imagehub: open-weight image generation architectures in native PyTorch."""
|
|
2
|
+
|
|
3
|
+
from imagehub.backends.base import Backend, TextToImageBackend
|
|
4
|
+
from imagehub.catalog import ModelSpec, available_models, model_info
|
|
5
|
+
from imagehub.exceptions import (
|
|
6
|
+
BackendBusyError,
|
|
7
|
+
BackendContractError,
|
|
8
|
+
CheckpointError,
|
|
9
|
+
DownloadError,
|
|
10
|
+
ImageHubError,
|
|
11
|
+
ImageHubLicenseWarning,
|
|
12
|
+
ImagehubLicenseWarning,
|
|
13
|
+
MissingDependencyError,
|
|
14
|
+
)
|
|
15
|
+
from imagehub.factory import pipeline
|
|
16
|
+
from imagehub.image import Image
|
|
17
|
+
from imagehub.pipelines import Pipeline, TextToImagePipeline
|
|
18
|
+
from imagehub.registry import BackendRegistry, available_backends, backend_registry
|
|
19
|
+
from imagehub.types import ImageResult, T2IRequest, Task
|
|
20
|
+
|
|
21
|
+
__version__ = "0.1.0"
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"Backend",
|
|
25
|
+
"BackendBusyError",
|
|
26
|
+
"BackendContractError",
|
|
27
|
+
"BackendRegistry",
|
|
28
|
+
"CheckpointError",
|
|
29
|
+
"DownloadError",
|
|
30
|
+
"Image",
|
|
31
|
+
"ImageHubError",
|
|
32
|
+
"ImageHubLicenseWarning",
|
|
33
|
+
"ImagehubLicenseWarning",
|
|
34
|
+
"ImageResult",
|
|
35
|
+
"ModelSpec",
|
|
36
|
+
"MissingDependencyError",
|
|
37
|
+
"Pipeline",
|
|
38
|
+
"T2IRequest",
|
|
39
|
+
"Task",
|
|
40
|
+
"TextToImageBackend",
|
|
41
|
+
"TextToImagePipeline",
|
|
42
|
+
"__version__",
|
|
43
|
+
"available_backends",
|
|
44
|
+
"available_models",
|
|
45
|
+
"backend_registry",
|
|
46
|
+
"model_info",
|
|
47
|
+
"pipeline",
|
|
48
|
+
]
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""aMUSEd text-to-image backend over a local diffusers snapshot (masked-token generation)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import os
|
|
7
|
+
import secrets
|
|
8
|
+
import warnings
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import torch
|
|
14
|
+
from torch import Tensor
|
|
15
|
+
|
|
16
|
+
from imagehub.backends.base import TextToImageBackend
|
|
17
|
+
from imagehub.backends.stable_diffusion import resolve_device
|
|
18
|
+
from imagehub.catalog import MODEL_CATALOG
|
|
19
|
+
from imagehub.checkpoints import read_json, read_snapshot
|
|
20
|
+
from imagehub.exceptions import CheckpointError
|
|
21
|
+
from imagehub.image import Image
|
|
22
|
+
from imagehub.models.clip_text import CLIPTextModelWithProjection
|
|
23
|
+
from imagehub.models.uvit_2d import UVit2DModel
|
|
24
|
+
from imagehub.models.vq_model import VQModel
|
|
25
|
+
from imagehub.schedulers.amused import AmusedScheduler
|
|
26
|
+
from imagehub.tokenization import CLIPTokenizer
|
|
27
|
+
from imagehub.types import ImageResult, T2IRequest
|
|
28
|
+
|
|
29
|
+
COMPONENTS = ("tokenizer", "text_encoder", "transformer", "vqvae", "scheduler")
|
|
30
|
+
OPTION_DEFAULTS: dict[str, Any] = {
|
|
31
|
+
"steps": 12,
|
|
32
|
+
"guidance": 10.0,
|
|
33
|
+
"width": None,
|
|
34
|
+
"height": None,
|
|
35
|
+
"seed": None,
|
|
36
|
+
"negative_prompt": "",
|
|
37
|
+
"aesthetic_score": 6,
|
|
38
|
+
"crop_coord": (0, 0),
|
|
39
|
+
"temperature": (2, 0),
|
|
40
|
+
}
|
|
41
|
+
_DTYPES = (torch.float32, torch.float16, torch.bfloat16)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _hint(root: Path, model_id: str | None) -> str:
|
|
45
|
+
model = model_id or "amused-512"
|
|
46
|
+
return f"Download a snapshot with: python -m imagehub.pretrained {model} {root}"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class AmusedComponents:
|
|
51
|
+
tokenizer: CLIPTokenizer
|
|
52
|
+
text_encoder: CLIPTextModelWithProjection
|
|
53
|
+
transformer: UVit2DModel
|
|
54
|
+
vqvae: VQModel
|
|
55
|
+
scheduler_config: dict[str, Any]
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def load(
|
|
59
|
+
cls,
|
|
60
|
+
directory: str | os.PathLike[str],
|
|
61
|
+
*,
|
|
62
|
+
device: torch.device,
|
|
63
|
+
dtype: torch.dtype,
|
|
64
|
+
model_id: str | None = None,
|
|
65
|
+
) -> AmusedComponents:
|
|
66
|
+
root = Path(directory)
|
|
67
|
+
missing = [name for name in COMPONENTS if not (root / name).is_dir()]
|
|
68
|
+
if missing:
|
|
69
|
+
raise CheckpointError(
|
|
70
|
+
f"{root} is not an aMUSEd snapshot (missing {', '.join(missing)}). "
|
|
71
|
+
+ _hint(root, model_id)
|
|
72
|
+
)
|
|
73
|
+
try:
|
|
74
|
+
class_name = read_json(root / "model_index.json").get("_class_name")
|
|
75
|
+
if class_name != "AmusedPipeline":
|
|
76
|
+
raise CheckpointError(f"Unsupported aMUSEd pipeline class {class_name!r}")
|
|
77
|
+
return cls(
|
|
78
|
+
tokenizer=CLIPTokenizer(root / "tokenizer"),
|
|
79
|
+
text_encoder=CLIPTextModelWithProjection.from_pretrained(
|
|
80
|
+
root / "text_encoder", device=device, dtype=dtype
|
|
81
|
+
),
|
|
82
|
+
transformer=UVit2DModel.from_pretrained(
|
|
83
|
+
root / "transformer", device=device, dtype=dtype
|
|
84
|
+
),
|
|
85
|
+
vqvae=VQModel.from_pretrained(root / "vqvae", device=device, dtype=dtype),
|
|
86
|
+
scheduler_config=read_json(root / "scheduler" / "scheduler_config.json"),
|
|
87
|
+
)
|
|
88
|
+
except FileNotFoundError as error:
|
|
89
|
+
raise CheckpointError(
|
|
90
|
+
f"{error.filename or error} is missing. " + _hint(root, model_id)
|
|
91
|
+
) from error
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def vae_scale_factor(vqvae: VQModel) -> int:
|
|
95
|
+
return 2 ** (len(vqvae.config["block_out_channels"]) - 1)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _embed(components: AmusedComponents, text: str, device: torch.device) -> tuple[Tensor, Tensor]:
|
|
99
|
+
"""Pooled projected embedding and the penultimate hidden state for one text."""
|
|
100
|
+
ids = torch.tensor([components.tokenizer(text)], device=device)
|
|
101
|
+
text_embeds, _, hidden_states = components.text_encoder(ids, output_hidden_states=True)
|
|
102
|
+
return text_embeds, hidden_states[-2]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@torch.inference_mode()
|
|
106
|
+
def encode_prompt(
|
|
107
|
+
components: AmusedComponents,
|
|
108
|
+
prompt: str,
|
|
109
|
+
negative_prompt: str,
|
|
110
|
+
device: torch.device,
|
|
111
|
+
do_classifier_free_guidance: bool,
|
|
112
|
+
) -> tuple[Tensor, Tensor]:
|
|
113
|
+
"""Pooled embeddings and hidden states, with the negative prompt first when guiding."""
|
|
114
|
+
pooled, hidden = _embed(components, prompt, device)
|
|
115
|
+
if not do_classifier_free_guidance:
|
|
116
|
+
return pooled, hidden
|
|
117
|
+
negative_pooled, negative_hidden = _embed(components, negative_prompt, device)
|
|
118
|
+
return torch.cat([negative_pooled, pooled]), torch.cat([negative_hidden, hidden])
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@torch.inference_mode()
|
|
122
|
+
def denoise(
|
|
123
|
+
components: AmusedComponents,
|
|
124
|
+
*,
|
|
125
|
+
prompt: str,
|
|
126
|
+
negative_prompt: str,
|
|
127
|
+
steps: int,
|
|
128
|
+
guidance: float,
|
|
129
|
+
height: int,
|
|
130
|
+
width: int,
|
|
131
|
+
aesthetic_score: float = 6,
|
|
132
|
+
crop_coord: tuple[int, int] = (0, 0),
|
|
133
|
+
temperature: float | tuple[float, float] | list[float] = (2, 0),
|
|
134
|
+
generator: torch.Generator | None = None,
|
|
135
|
+
device: torch.device | None = None,
|
|
136
|
+
) -> Tensor:
|
|
137
|
+
"""Run the masked-token loop from fully masked tokens and return ids (1, h/16, w/16)."""
|
|
138
|
+
transformer = components.transformer
|
|
139
|
+
device = device or transformer.embed.embeddings.weight.device
|
|
140
|
+
use_cfg = guidance > 1.0
|
|
141
|
+
pooled, hidden = encode_prompt(components, prompt, negative_prompt, device, use_cfg)
|
|
142
|
+
scale = vae_scale_factor(components.vqvae)
|
|
143
|
+
# The micro-conditioning lists the width before the height, as the reference does.
|
|
144
|
+
micro_conds = torch.tensor(
|
|
145
|
+
[width, height, crop_coord[0], crop_coord[1], aesthetic_score],
|
|
146
|
+
device=device,
|
|
147
|
+
dtype=hidden.dtype,
|
|
148
|
+
)
|
|
149
|
+
micro_conds = micro_conds.unsqueeze(0).expand(2 if use_cfg else 1, -1)
|
|
150
|
+
scheduler = AmusedScheduler(components.scheduler_config)
|
|
151
|
+
latents = torch.full(
|
|
152
|
+
(1, height // scale, width // scale),
|
|
153
|
+
scheduler.mask_token_id,
|
|
154
|
+
dtype=torch.long,
|
|
155
|
+
device=device,
|
|
156
|
+
)
|
|
157
|
+
scheduler.set_timesteps(steps, temperature, device)
|
|
158
|
+
assert scheduler.timesteps is not None
|
|
159
|
+
for timestep in scheduler.timesteps:
|
|
160
|
+
model_input = torch.cat([latents] * 2) if use_cfg else latents
|
|
161
|
+
logits = transformer(model_input, hidden, pooled, micro_conds)
|
|
162
|
+
if use_cfg:
|
|
163
|
+
uncond_logits, cond_logits = logits.chunk(2)
|
|
164
|
+
logits = uncond_logits + guidance * (cond_logits - uncond_logits)
|
|
165
|
+
latents = scheduler.step(logits, timestep, latents, generator=generator).prev_sample
|
|
166
|
+
return latents
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@torch.inference_mode()
|
|
170
|
+
def decode_tokens(vqvae: VQModel, tokens: Tensor) -> Tensor:
|
|
171
|
+
"""Decode token ids to uint8 images of shape (batch, height, width, 3) on CPU."""
|
|
172
|
+
batch, height, width = tokens.shape
|
|
173
|
+
upcast = vqvae.post_quant_conv.weight.dtype == torch.float16 and vqvae.config["force_upcast"]
|
|
174
|
+
if upcast:
|
|
175
|
+
vqvae.float()
|
|
176
|
+
try:
|
|
177
|
+
image = vqvae.decode(
|
|
178
|
+
tokens,
|
|
179
|
+
force_not_quantize=True,
|
|
180
|
+
shape=(batch, height, width, vqvae.config["latent_channels"]),
|
|
181
|
+
).clip(0, 1)
|
|
182
|
+
finally:
|
|
183
|
+
if upcast:
|
|
184
|
+
vqvae.half()
|
|
185
|
+
image = image.permute(0, 2, 3, 1).float().cpu()
|
|
186
|
+
return (image * 255).round().to(torch.uint8)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _positive_int(options: dict[str, Any], name: str) -> None:
|
|
190
|
+
if type(options[name]) is not int or options[name] <= 0:
|
|
191
|
+
raise ValueError(f"{name} must be a positive int")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _finite_number(value: Any, name: str, *, minimum: float | None = None) -> None:
|
|
195
|
+
if (
|
|
196
|
+
isinstance(value, bool)
|
|
197
|
+
or not isinstance(value, int | float)
|
|
198
|
+
or not math.isfinite(value)
|
|
199
|
+
or (minimum is not None and value < minimum)
|
|
200
|
+
):
|
|
201
|
+
bound = "" if minimum is None else f" >= {minimum:g}"
|
|
202
|
+
raise ValueError(f"{name} must be a finite number{bound}")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class AmusedBackend(TextToImageBackend):
|
|
206
|
+
def __init__(
|
|
207
|
+
self,
|
|
208
|
+
model: str | os.PathLike[str],
|
|
209
|
+
*,
|
|
210
|
+
device: str | torch.device = "cpu",
|
|
211
|
+
dtype: torch.dtype | None = None,
|
|
212
|
+
model_id: str | None = None,
|
|
213
|
+
) -> None:
|
|
214
|
+
super().__init__()
|
|
215
|
+
self.source = Path(model)
|
|
216
|
+
self.model_id = model_id
|
|
217
|
+
self.device = resolve_device(device)
|
|
218
|
+
self.dtype = dtype or torch.float32
|
|
219
|
+
if self.dtype not in _DTYPES:
|
|
220
|
+
raise ValueError("dtype must be torch.float32, torch.float16 or torch.bfloat16")
|
|
221
|
+
self.components: AmusedComponents | None = None
|
|
222
|
+
self.snapshot: dict[str, Any] | None = None
|
|
223
|
+
defaults = (
|
|
224
|
+
dict(MODEL_CATALOG[model_id].config.get("defaults", {}))
|
|
225
|
+
if model_id is not None and model_id in MODEL_CATALOG
|
|
226
|
+
else {}
|
|
227
|
+
)
|
|
228
|
+
self.option_defaults = OPTION_DEFAULTS | defaults
|
|
229
|
+
|
|
230
|
+
def validate_options(self, inputs: T2IRequest, options: dict[str, Any]) -> dict[str, Any]:
|
|
231
|
+
merged = dict(inputs.parameters) | options
|
|
232
|
+
unknown = sorted(set(merged) - set(OPTION_DEFAULTS))
|
|
233
|
+
if unknown:
|
|
234
|
+
raise TypeError(f"Unknown aMUSEd options: {', '.join(unknown)}")
|
|
235
|
+
o = self.option_defaults | merged
|
|
236
|
+
if o["negative_prompt"] is None:
|
|
237
|
+
o["negative_prompt"] = ""
|
|
238
|
+
if not isinstance(o["negative_prompt"], str):
|
|
239
|
+
raise TypeError("negative_prompt must be a string or None")
|
|
240
|
+
_positive_int(o, "steps")
|
|
241
|
+
if o["steps"] > 1000:
|
|
242
|
+
raise ValueError("steps must be at most 1000")
|
|
243
|
+
_finite_number(o["guidance"], "guidance", minimum=0)
|
|
244
|
+
_finite_number(o["aesthetic_score"], "aesthetic_score")
|
|
245
|
+
crop = o["crop_coord"]
|
|
246
|
+
if (
|
|
247
|
+
not isinstance(crop, tuple | list)
|
|
248
|
+
or len(crop) != 2
|
|
249
|
+
or any(type(value) is not int for value in crop)
|
|
250
|
+
):
|
|
251
|
+
raise ValueError("crop_coord must be a pair of ints")
|
|
252
|
+
o["crop_coord"] = (int(crop[0]), int(crop[1]))
|
|
253
|
+
temperature = o["temperature"]
|
|
254
|
+
if isinstance(temperature, tuple | list):
|
|
255
|
+
if len(temperature) != 2:
|
|
256
|
+
raise ValueError("temperature must be a number or a (start, end) pair")
|
|
257
|
+
for value in temperature:
|
|
258
|
+
_finite_number(value, "temperature", minimum=0)
|
|
259
|
+
o["temperature"] = (temperature[0], temperature[1])
|
|
260
|
+
else:
|
|
261
|
+
_finite_number(temperature, "temperature", minimum=0)
|
|
262
|
+
seed = o["seed"]
|
|
263
|
+
if seed is not None and (type(seed) is not int or not 0 <= seed < 2**64):
|
|
264
|
+
raise ValueError("seed must be an int in [0, 2**64)")
|
|
265
|
+
try:
|
|
266
|
+
transformer_config = read_json(self.source / "transformer" / "config.json")
|
|
267
|
+
except FileNotFoundError:
|
|
268
|
+
transformer_config = {}
|
|
269
|
+
try:
|
|
270
|
+
blocks = read_json(self.source / "vqvae" / "config.json").get(
|
|
271
|
+
"block_out_channels", [1, 1, 1, 1, 1]
|
|
272
|
+
)
|
|
273
|
+
except FileNotFoundError:
|
|
274
|
+
blocks = [1, 1, 1, 1, 1]
|
|
275
|
+
scale = 2 ** (len(blocks) - 1)
|
|
276
|
+
# The 512 model halves the token grid inside the network, so it needs even grids.
|
|
277
|
+
multiple = scale * (2 if transformer_config.get("downsample") else 1)
|
|
278
|
+
size = int(transformer_config.get("sample_size", 64)) * scale
|
|
279
|
+
for name in ("width", "height"):
|
|
280
|
+
if o[name] is None:
|
|
281
|
+
o[name] = size
|
|
282
|
+
_positive_int(o, name)
|
|
283
|
+
if o[name] % multiple:
|
|
284
|
+
raise ValueError(f"{name} must be a multiple of {multiple}")
|
|
285
|
+
return o
|
|
286
|
+
|
|
287
|
+
def _load(self) -> None:
|
|
288
|
+
self.components = AmusedComponents.load(
|
|
289
|
+
self.source, device=self.device, dtype=self.dtype, model_id=self.model_id
|
|
290
|
+
)
|
|
291
|
+
self.snapshot = read_snapshot(self.source)
|
|
292
|
+
recorded = (self.snapshot or {}).get("model_id")
|
|
293
|
+
if recorded is not None and self.model_id is not None and recorded != self.model_id:
|
|
294
|
+
warnings.warn(
|
|
295
|
+
f"{self.source} is a snapshot of {recorded!r} but {self.model_id!r} was selected; "
|
|
296
|
+
"both share the aMUSEd architecture, so it was loaded",
|
|
297
|
+
UserWarning,
|
|
298
|
+
stacklevel=2,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def _unload(self) -> None:
|
|
302
|
+
self.components = None
|
|
303
|
+
self.snapshot = None
|
|
304
|
+
|
|
305
|
+
def _infer(self, inputs: T2IRequest, **o: Any) -> ImageResult:
|
|
306
|
+
assert self.components is not None
|
|
307
|
+
seed = o["seed"]
|
|
308
|
+
if seed is None:
|
|
309
|
+
seed = secrets.randbits(63)
|
|
310
|
+
generator = torch.Generator("cpu").manual_seed(seed)
|
|
311
|
+
tokens = denoise(
|
|
312
|
+
self.components,
|
|
313
|
+
prompt=inputs.prompt,
|
|
314
|
+
negative_prompt=o["negative_prompt"],
|
|
315
|
+
steps=o["steps"],
|
|
316
|
+
guidance=float(o["guidance"]),
|
|
317
|
+
height=o["height"],
|
|
318
|
+
width=o["width"],
|
|
319
|
+
aesthetic_score=o["aesthetic_score"],
|
|
320
|
+
crop_coord=o["crop_coord"],
|
|
321
|
+
temperature=o["temperature"],
|
|
322
|
+
generator=generator,
|
|
323
|
+
device=self.device,
|
|
324
|
+
)
|
|
325
|
+
pixels = decode_tokens(self.components.vqvae, tokens)
|
|
326
|
+
snapshot = self.snapshot or {}
|
|
327
|
+
metadata = {
|
|
328
|
+
"model_id": snapshot.get("model_id"),
|
|
329
|
+
"revision": snapshot.get("revision"),
|
|
330
|
+
"seed": seed,
|
|
331
|
+
"steps": o["steps"],
|
|
332
|
+
"guidance": float(o["guidance"]),
|
|
333
|
+
"width": o["width"],
|
|
334
|
+
"height": o["height"],
|
|
335
|
+
"negative_prompt": o["negative_prompt"],
|
|
336
|
+
"aesthetic_score": o["aesthetic_score"],
|
|
337
|
+
"crop_coord": list(o["crop_coord"]),
|
|
338
|
+
"temperature": list(o["temperature"])
|
|
339
|
+
if isinstance(o["temperature"], tuple)
|
|
340
|
+
else o["temperature"],
|
|
341
|
+
"scheduler": "amused",
|
|
342
|
+
"device": str(self.device),
|
|
343
|
+
"dtype": str(self.dtype).removeprefix("torch."),
|
|
344
|
+
}
|
|
345
|
+
return ImageResult(tuple(Image(pixel) for pixel in pixels), metadata)
|