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.
Files changed (272) hide show
  1. imagehub/__init__.py +48 -0
  2. imagehub/backends/__init__.py +5 -0
  3. imagehub/backends/amused.py +345 -0
  4. imagehub/backends/auraflow.py +302 -0
  5. imagehub/backends/bagel.py +247 -0
  6. imagehub/backends/base.py +106 -0
  7. imagehub/backends/chroma.py +434 -0
  8. imagehub/backends/cogview3_plus.py +356 -0
  9. imagehub/backends/cogview4.py +354 -0
  10. imagehub/backends/cosmos2.py +279 -0
  11. imagehub/backends/deepfloyd_if.py +290 -0
  12. imagehub/backends/dreamlite.py +348 -0
  13. imagehub/backends/emu3.py +260 -0
  14. imagehub/backends/ernie_image.py +307 -0
  15. imagehub/backends/flux.py +558 -0
  16. imagehub/backends/flux2.py +392 -0
  17. imagehub/backends/flux_kontext.py +68 -0
  18. imagehub/backends/glm_image.py +456 -0
  19. imagehub/backends/hart.py +387 -0
  20. imagehub/backends/hidream.py +428 -0
  21. imagehub/backends/hidream_o1.py +397 -0
  22. imagehub/backends/hunyuan_dit.py +455 -0
  23. imagehub/backends/hunyuanimage.py +400 -0
  24. imagehub/backends/ideogram4.py +423 -0
  25. imagehub/backends/infinity.py +315 -0
  26. imagehub/backends/janus.py +279 -0
  27. imagehub/backends/joyai_image.py +442 -0
  28. imagehub/backends/kandinsky21.py +461 -0
  29. imagehub/backends/kandinsky22.py +404 -0
  30. imagehub/backends/kandinsky3.py +334 -0
  31. imagehub/backends/kandinsky5.py +463 -0
  32. imagehub/backends/kolors.py +355 -0
  33. imagehub/backends/krea2.py +499 -0
  34. imagehub/backends/ldm.py +199 -0
  35. imagehub/backends/llamagen.py +481 -0
  36. imagehub/backends/longcat_image.py +708 -0
  37. imagehub/backends/lumina2.py +376 -0
  38. imagehub/backends/lumina_dimoo.py +307 -0
  39. imagehub/backends/lumina_next.py +360 -0
  40. imagehub/backends/meissonic.py +330 -0
  41. imagehub/backends/nextstep.py +215 -0
  42. imagehub/backends/nucleus_image.py +253 -0
  43. imagehub/backends/omnigen.py +331 -0
  44. imagehub/backends/omnigen_inputs.py +225 -0
  45. imagehub/backends/ovis_image.py +266 -0
  46. imagehub/backends/pixart.py +538 -0
  47. imagehub/backends/pixeldit.py +341 -0
  48. imagehub/backends/prx.py +383 -0
  49. imagehub/backends/qwen_image.py +944 -0
  50. imagehub/backends/sana.py +507 -0
  51. imagehub/backends/sdxl_refiner.py +175 -0
  52. imagehub/backends/show_o.py +308 -0
  53. imagehub/backends/stable_cascade.py +377 -0
  54. imagehub/backends/stable_diffusion.py +434 -0
  55. imagehub/backends/stable_diffusion_3.py +411 -0
  56. imagehub/backends/stable_diffusion_xl.py +557 -0
  57. imagehub/backends/visualcloze.py +535 -0
  58. imagehub/backends/wuerstchen.py +395 -0
  59. imagehub/backends/z_image.py +331 -0
  60. imagehub/caption_verifier.py +287 -0
  61. imagehub/catalog.py +4094 -0
  62. imagehub/checkpoints.py +408 -0
  63. imagehub/download.py +169 -0
  64. imagehub/exceptions.py +33 -0
  65. imagehub/factory.py +60 -0
  66. imagehub/image.py +183 -0
  67. imagehub/licensing.py +22 -0
  68. imagehub/lora.py +651 -0
  69. imagehub/lora_formats.py +503 -0
  70. imagehub/models/__init__.py +1 -0
  71. imagehub/models/auraflow_transformer.py +365 -0
  72. imagehub/models/autoencoder_dc.py +379 -0
  73. imagehub/models/autoencoder_ideogram4.py +572 -0
  74. imagehub/models/autoencoder_kl.py +228 -0
  75. imagehub/models/autoencoder_kl_flux2.py +221 -0
  76. imagehub/models/autoencoder_kl_hunyuanimage.py +217 -0
  77. imagehub/models/autoencoder_kl_qwen_image.py +506 -0
  78. imagehub/models/autoencoder_kl_qwen_image21.py +648 -0
  79. imagehub/models/autoencoder_kl_wan.py +252 -0
  80. imagehub/models/autoencoder_tiny.py +141 -0
  81. imagehub/models/bagel.py +422 -0
  82. imagehub/models/bert.py +196 -0
  83. imagehub/models/chatglm.py +271 -0
  84. imagehub/models/chroma_transformer.py +485 -0
  85. imagehub/models/clip_text.py +191 -0
  86. imagehub/models/clip_vision.py +158 -0
  87. imagehub/models/cogview3_plus_transformer.py +363 -0
  88. imagehub/models/cogview4_transformer.py +479 -0
  89. imagehub/models/cosmos_transformer.py +307 -0
  90. imagehub/models/dreamlite.py +467 -0
  91. imagehub/models/emu3.py +153 -0
  92. imagehub/models/emu3_vq.py +315 -0
  93. imagehub/models/ernie_image_transformer.py +277 -0
  94. imagehub/models/flux2_transformer.py +425 -0
  95. imagehub/models/flux_transformer.py +425 -0
  96. imagehub/models/fp8.py +120 -0
  97. imagehub/models/gemma.py +121 -0
  98. imagehub/models/gemma2.py +231 -0
  99. imagehub/models/glm.py +239 -0
  100. imagehub/models/glm_image_ar.py +341 -0
  101. imagehub/models/glm_image_transformer.py +199 -0
  102. imagehub/models/hart.py +502 -0
  103. imagehub/models/hidream_o1.py +208 -0
  104. imagehub/models/hidream_transformer.py +565 -0
  105. imagehub/models/hunyuan_dit_transformer.py +539 -0
  106. imagehub/models/hunyuanimage_transformer.py +514 -0
  107. imagehub/models/ideogram4_text.py +150 -0
  108. imagehub/models/ideogram4_transformer.py +332 -0
  109. imagehub/models/infinity.py +381 -0
  110. imagehub/models/infinity_vae.py +202 -0
  111. imagehub/models/janus.py +56 -0
  112. imagehub/models/janus_vq.py +253 -0
  113. imagehub/models/joyimage_transformer.py +329 -0
  114. imagehub/models/kandinsky3_unet.py +455 -0
  115. imagehub/models/kandinsky5_transformer.py +420 -0
  116. imagehub/models/krea2_transformer.py +430 -0
  117. imagehub/models/layers.py +91 -0
  118. imagehub/models/ldm_bert.py +116 -0
  119. imagehub/models/llada.py +240 -0
  120. imagehub/models/llama.py +304 -0
  121. imagehub/models/llama_text.py +231 -0
  122. imagehub/models/llamagen.py +310 -0
  123. imagehub/models/llamagen_vq.py +209 -0
  124. imagehub/models/longcat_image_transformer.py +160 -0
  125. imagehub/models/lumina2_transformer.py +422 -0
  126. imagehub/models/lumina_next_transformer.py +426 -0
  127. imagehub/models/magvit_v2.py +241 -0
  128. imagehub/models/meissonic_transformer.py +154 -0
  129. imagehub/models/mistral3_text.py +312 -0
  130. imagehub/models/multilingual_clip.py +45 -0
  131. imagehub/models/nextstep.py +408 -0
  132. imagehub/models/nucleus_image_transformer.py +346 -0
  133. imagehub/models/omnigen_transformer.py +380 -0
  134. imagehub/models/ovis_image_transformer.py +248 -0
  135. imagehub/models/paella_vq.py +110 -0
  136. imagehub/models/pixart_transformer.py +526 -0
  137. imagehub/models/pixeldit.py +472 -0
  138. imagehub/models/prior_transformer.py +267 -0
  139. imagehub/models/prx_transformer.py +256 -0
  140. imagehub/models/qwen2.py +163 -0
  141. imagehub/models/qwen2_5_vl_text.py +657 -0
  142. imagehub/models/qwen3.py +264 -0
  143. imagehub/models/qwen3_vl.py +251 -0
  144. imagehub/models/qwen3_vl_text.py +255 -0
  145. imagehub/models/qwen_image21_transformer.py +579 -0
  146. imagehub/models/qwen_image_transformer.py +352 -0
  147. imagehub/models/sana_transformer.py +464 -0
  148. imagehub/models/sd3_transformer.py +517 -0
  149. imagehub/models/show_o.py +176 -0
  150. imagehub/models/stable_cascade_unet.py +463 -0
  151. imagehub/models/t5.py +277 -0
  152. imagehub/models/t5gemma.py +137 -0
  153. imagehub/models/umt5.py +64 -0
  154. imagehub/models/unet_2d.py +978 -0
  155. imagehub/models/uvit_2d.py +397 -0
  156. imagehub/models/vq_model.py +331 -0
  157. imagehub/models/wuerstchen.py +363 -0
  158. imagehub/models/xlm_roberta.py +221 -0
  159. imagehub/models/z_image_transformer.py +410 -0
  160. imagehub/pipelines/__init__.py +4 -0
  161. imagehub/pipelines/base.py +105 -0
  162. imagehub/pipelines/text_to_image.py +27 -0
  163. imagehub/pretrained.py +197 -0
  164. imagehub/registry.py +181 -0
  165. imagehub/schedulers/__init__.py +1 -0
  166. imagehub/schedulers/amused.py +139 -0
  167. imagehub/schedulers/ddim.py +140 -0
  168. imagehub/schedulers/ddim_cogvideox.py +157 -0
  169. imagehub/schedulers/ddpm.py +232 -0
  170. imagehub/schedulers/ddpm_wuerstchen.py +81 -0
  171. imagehub/schedulers/deis_multistep.py +219 -0
  172. imagehub/schedulers/dpmsolver_multistep.py +249 -0
  173. imagehub/schedulers/edm_dpmsolver.py +180 -0
  174. imagehub/schedulers/euler.py +195 -0
  175. imagehub/schedulers/euler_ancestral.py +158 -0
  176. imagehub/schedulers/flow_match_euler.py +388 -0
  177. imagehub/schedulers/flow_match_lcm.py +187 -0
  178. imagehub/schedulers/flow_unipc.py +166 -0
  179. imagehub/schedulers/ideogram4.py +95 -0
  180. imagehub/schedulers/lcm.py +191 -0
  181. imagehub/schedulers/pndm.py +111 -0
  182. imagehub/schedulers/scm.py +121 -0
  183. imagehub/schedulers/unclip.py +133 -0
  184. imagehub/schedulers/unipc.py +193 -0
  185. imagehub/tokenization.py +258 -0
  186. imagehub/tokenization_auraflow.py +84 -0
  187. imagehub/tokenization_bert.py +187 -0
  188. imagehub/tokenization_byt5.py +90 -0
  189. imagehub/tokenization_chatglm.py +197 -0
  190. imagehub/tokenization_codegen.py +145 -0
  191. imagehub/tokenization_deepseek.py +229 -0
  192. imagehub/tokenization_emu3.py +103 -0
  193. imagehub/tokenization_gemma.py +175 -0
  194. imagehub/tokenization_glm.py +202 -0
  195. imagehub/tokenization_glm_image.py +65 -0
  196. imagehub/tokenization_llama3.py +135 -0
  197. imagehub/tokenization_mistral.py +225 -0
  198. imagehub/tokenization_omnigen.py +48 -0
  199. imagehub/tokenization_qwen.py +374 -0
  200. imagehub/tokenization_t5.py +543 -0
  201. imagehub/tokenization_xlm_roberta.py +186 -0
  202. imagehub/training/__init__.py +44 -0
  203. imagehub/training/arguments.py +211 -0
  204. imagehub/training/checkpointing.py +32 -0
  205. imagehub/training/data.py +241 -0
  206. imagehub/training/distributed.py +96 -0
  207. imagehub/training/family.py +147 -0
  208. imagehub/training/flow_matching.py +143 -0
  209. imagehub/training/flux.py +237 -0
  210. imagehub/training/flux2.py +292 -0
  211. imagehub/training/hidream.py +292 -0
  212. imagehub/training/krea2.py +301 -0
  213. imagehub/training/lumina2.py +219 -0
  214. imagehub/training/qwen_image.py +355 -0
  215. imagehub/training/reporting.py +84 -0
  216. imagehub/training/sana.py +259 -0
  217. imagehub/training/schedules.py +38 -0
  218. imagehub/training/sd1.py +251 -0
  219. imagehub/training/sd3.py +222 -0
  220. imagehub/training/sdxl.py +148 -0
  221. imagehub/training/specs.py +178 -0
  222. imagehub/training/trainer.py +497 -0
  223. imagehub/training/z_image.py +224 -0
  224. imagehub/types.py +40 -0
  225. imagehub-0.1.0.dist-info/METADATA +284 -0
  226. imagehub-0.1.0.dist-info/RECORD +272 -0
  227. imagehub-0.1.0.dist-info/WHEEL +4 -0
  228. imagehub-0.1.0.dist-info/licenses/LICENSE +202 -0
  229. imagehub-0.1.0.dist-info/licenses/NOTICE +91 -0
  230. imagehub-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +73 -0
  231. imagehub-0.1.0.dist-info/licenses/licenses/BK-SDM-CreativeML-OpenRAIL-M.txt +83 -0
  232. imagehub-0.1.0.dist-info/licenses/licenses/Diffusers-Apache-2.0.txt +201 -0
  233. imagehub-0.1.0.dist-info/licenses/licenses/ETRI-KOALA-Dual-License.txt +206 -0
  234. imagehub-0.1.0.dist-info/licenses/licenses/HART-MIT.txt +21 -0
  235. imagehub-0.1.0.dist-info/licenses/licenses/HiDream-O1-MIT.txt +21 -0
  236. imagehub-0.1.0.dist-info/licenses/licenses/Ideogram4-Apache-2.0.txt +201 -0
  237. imagehub-0.1.0.dist-info/licenses/licenses/Infinity-MIT.txt +21 -0
  238. imagehub-0.1.0.dist-info/licenses/licenses/LlamaGen-MIT.txt +21 -0
  239. imagehub-0.1.0.dist-info/licenses/licenses/Lumina-DiMOO-Apache-2.0.txt +201 -0
  240. imagehub-0.1.0.dist-info/licenses/licenses/Meissonic-Apache-2.0.txt +201 -0
  241. imagehub-0.1.0.dist-info/licenses/licenses/OpenAI-CLIP-MIT.txt +22 -0
  242. imagehub-0.1.0.dist-info/licenses/licenses/PixelDiT-NSCLv1.txt +34 -0
  243. imagehub-0.1.0.dist-info/licenses/licenses/SDXS-Apache-2.0.txt +201 -0
  244. imagehub-0.1.0.dist-info/licenses/licenses/Segmind-SSD-1B-Apache-2.0.txt +201 -0
  245. imagehub-0.1.0.dist-info/licenses/licenses/Segmind-distill-sd-CreativeML-OpenRAIL-M.txt +163 -0
  246. imagehub-0.1.0.dist-info/licenses/licenses/Show-o-Apache-2.0.txt +201 -0
  247. imagehub-0.1.0.dist-info/licenses/licenses/Transformers-Apache-2.0.txt +203 -0
  248. imagehub-0.1.0.dist-info/licenses/licenses/bagel/LICENSE +201 -0
  249. imagehub-0.1.0.dist-info/licenses/licenses/bagel/SOURCE.json +7 -0
  250. imagehub-0.1.0.dist-info/licenses/licenses/emu3/LICENSE +201 -0
  251. imagehub-0.1.0.dist-info/licenses/licenses/emu3/SOURCE.json +7 -0
  252. imagehub-0.1.0.dist-info/licenses/licenses/hart/LICENSE +21 -0
  253. imagehub-0.1.0.dist-info/licenses/licenses/hart/SOURCE.json +7 -0
  254. imagehub-0.1.0.dist-info/licenses/licenses/hidream-o1/LICENSE +21 -0
  255. imagehub-0.1.0.dist-info/licenses/licenses/hidream-o1/SOURCE.json +7 -0
  256. imagehub-0.1.0.dist-info/licenses/licenses/ideogram4/LICENSE +201 -0
  257. imagehub-0.1.0.dist-info/licenses/licenses/ideogram4/SOURCE.json +7 -0
  258. imagehub-0.1.0.dist-info/licenses/licenses/infinity/LICENSE +21 -0
  259. imagehub-0.1.0.dist-info/licenses/licenses/infinity/SOURCE.json +7 -0
  260. imagehub-0.1.0.dist-info/licenses/licenses/llamagen/LICENSE +21 -0
  261. imagehub-0.1.0.dist-info/licenses/licenses/llamagen/SOURCE.json +7 -0
  262. imagehub-0.1.0.dist-info/licenses/licenses/lumina-dimoo/LICENSE +201 -0
  263. imagehub-0.1.0.dist-info/licenses/licenses/lumina-dimoo/SOURCE.json +7 -0
  264. imagehub-0.1.0.dist-info/licenses/licenses/meissonic/LICENSE +201 -0
  265. imagehub-0.1.0.dist-info/licenses/licenses/meissonic/SOURCE.json +7 -0
  266. imagehub-0.1.0.dist-info/licenses/licenses/nextstep/LICENSE +201 -0
  267. imagehub-0.1.0.dist-info/licenses/licenses/nextstep/SOURCE.json +7 -0
  268. imagehub-0.1.0.dist-info/licenses/licenses/pixeldit/LICENSE +34 -0
  269. imagehub-0.1.0.dist-info/licenses/licenses/pixeldit/NOTICE +72 -0
  270. imagehub-0.1.0.dist-info/licenses/licenses/pixeldit/SOURCE.json +7 -0
  271. imagehub-0.1.0.dist-info/licenses/licenses/show-o/LICENSE +201 -0
  272. 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,5 @@
1
+ """Model adapters: lazy loading, option validation and inference for one architecture family."""
2
+
3
+ from imagehub.backends.base import Backend, TextToImageBackend
4
+
5
+ __all__ = ["Backend", "TextToImageBackend"]
@@ -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)