InvokeAI 6.9.0rc3__py3-none-any.whl → 6.10.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 (104) hide show
  1. invokeai/app/api/dependencies.py +2 -0
  2. invokeai/app/api/routers/model_manager.py +91 -2
  3. invokeai/app/api/routers/workflows.py +9 -0
  4. invokeai/app/invocations/fields.py +19 -0
  5. invokeai/app/invocations/flux_denoise.py +15 -1
  6. invokeai/app/invocations/image_to_latents.py +23 -5
  7. invokeai/app/invocations/latents_to_image.py +2 -25
  8. invokeai/app/invocations/metadata.py +9 -1
  9. invokeai/app/invocations/metadata_linked.py +47 -0
  10. invokeai/app/invocations/model.py +8 -0
  11. invokeai/app/invocations/pbr_maps.py +59 -0
  12. invokeai/app/invocations/primitives.py +12 -0
  13. invokeai/app/invocations/prompt_template.py +57 -0
  14. invokeai/app/invocations/z_image_control.py +112 -0
  15. invokeai/app/invocations/z_image_denoise.py +770 -0
  16. invokeai/app/invocations/z_image_image_to_latents.py +102 -0
  17. invokeai/app/invocations/z_image_latents_to_image.py +103 -0
  18. invokeai/app/invocations/z_image_lora_loader.py +153 -0
  19. invokeai/app/invocations/z_image_model_loader.py +135 -0
  20. invokeai/app/invocations/z_image_text_encoder.py +197 -0
  21. invokeai/app/services/config/config_default.py +3 -1
  22. invokeai/app/services/model_install/model_install_common.py +14 -1
  23. invokeai/app/services/model_install/model_install_default.py +119 -19
  24. invokeai/app/services/model_manager/model_manager_default.py +7 -0
  25. invokeai/app/services/model_records/model_records_base.py +12 -0
  26. invokeai/app/services/model_records/model_records_sql.py +17 -0
  27. invokeai/app/services/shared/graph.py +132 -77
  28. invokeai/app/services/workflow_records/workflow_records_base.py +8 -0
  29. invokeai/app/services/workflow_records/workflow_records_sqlite.py +42 -0
  30. invokeai/app/util/step_callback.py +3 -0
  31. invokeai/backend/flux/denoise.py +196 -11
  32. invokeai/backend/flux/schedulers.py +62 -0
  33. invokeai/backend/image_util/pbr_maps/architecture/block.py +367 -0
  34. invokeai/backend/image_util/pbr_maps/architecture/pbr_rrdb_net.py +70 -0
  35. invokeai/backend/image_util/pbr_maps/pbr_maps.py +141 -0
  36. invokeai/backend/image_util/pbr_maps/utils/image_ops.py +93 -0
  37. invokeai/backend/model_manager/configs/controlnet.py +47 -1
  38. invokeai/backend/model_manager/configs/factory.py +26 -1
  39. invokeai/backend/model_manager/configs/lora.py +79 -1
  40. invokeai/backend/model_manager/configs/main.py +113 -0
  41. invokeai/backend/model_manager/configs/qwen3_encoder.py +156 -0
  42. invokeai/backend/model_manager/load/model_cache/model_cache.py +104 -2
  43. invokeai/backend/model_manager/load/model_cache/torch_module_autocast/custom_modules/custom_diffusers_rms_norm.py +40 -0
  44. invokeai/backend/model_manager/load/model_cache/torch_module_autocast/custom_modules/custom_layer_norm.py +25 -0
  45. invokeai/backend/model_manager/load/model_cache/torch_module_autocast/torch_module_autocast.py +11 -2
  46. invokeai/backend/model_manager/load/model_loaders/cogview4.py +2 -1
  47. invokeai/backend/model_manager/load/model_loaders/flux.py +13 -6
  48. invokeai/backend/model_manager/load/model_loaders/generic_diffusers.py +4 -2
  49. invokeai/backend/model_manager/load/model_loaders/lora.py +11 -0
  50. invokeai/backend/model_manager/load/model_loaders/onnx.py +1 -0
  51. invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py +2 -1
  52. invokeai/backend/model_manager/load/model_loaders/z_image.py +969 -0
  53. invokeai/backend/model_manager/load/model_util.py +6 -1
  54. invokeai/backend/model_manager/metadata/metadata_base.py +12 -5
  55. invokeai/backend/model_manager/model_on_disk.py +3 -0
  56. invokeai/backend/model_manager/starter_models.py +79 -0
  57. invokeai/backend/model_manager/taxonomy.py +5 -0
  58. invokeai/backend/model_manager/util/select_hf_files.py +23 -8
  59. invokeai/backend/patches/layer_patcher.py +34 -16
  60. invokeai/backend/patches/layers/lora_layer_base.py +2 -1
  61. invokeai/backend/patches/lora_conversions/flux_aitoolkit_lora_conversion_utils.py +17 -2
  62. invokeai/backend/patches/lora_conversions/flux_xlabs_lora_conversion_utils.py +92 -0
  63. invokeai/backend/patches/lora_conversions/formats.py +5 -0
  64. invokeai/backend/patches/lora_conversions/z_image_lora_constants.py +8 -0
  65. invokeai/backend/patches/lora_conversions/z_image_lora_conversion_utils.py +189 -0
  66. invokeai/backend/quantization/gguf/ggml_tensor.py +38 -4
  67. invokeai/backend/quantization/gguf/loaders.py +47 -12
  68. invokeai/backend/stable_diffusion/diffusion/conditioning_data.py +13 -0
  69. invokeai/backend/util/devices.py +25 -0
  70. invokeai/backend/util/hotfixes.py +2 -2
  71. invokeai/backend/z_image/__init__.py +16 -0
  72. invokeai/backend/z_image/extensions/__init__.py +1 -0
  73. invokeai/backend/z_image/extensions/regional_prompting_extension.py +205 -0
  74. invokeai/backend/z_image/text_conditioning.py +74 -0
  75. invokeai/backend/z_image/z_image_control_adapter.py +238 -0
  76. invokeai/backend/z_image/z_image_control_transformer.py +643 -0
  77. invokeai/backend/z_image/z_image_controlnet_extension.py +531 -0
  78. invokeai/backend/z_image/z_image_patchify_utils.py +135 -0
  79. invokeai/backend/z_image/z_image_transformer_patch.py +234 -0
  80. invokeai/frontend/web/dist/assets/App-BBELGD-n.js +161 -0
  81. invokeai/frontend/web/dist/assets/{browser-ponyfill-CN1j0ARZ.js → browser-ponyfill-4xPFTMT3.js} +1 -1
  82. invokeai/frontend/web/dist/assets/index-vCDSQboA.js +530 -0
  83. invokeai/frontend/web/dist/index.html +1 -1
  84. invokeai/frontend/web/dist/locales/de.json +24 -6
  85. invokeai/frontend/web/dist/locales/en-GB.json +1 -0
  86. invokeai/frontend/web/dist/locales/en.json +78 -3
  87. invokeai/frontend/web/dist/locales/es.json +0 -5
  88. invokeai/frontend/web/dist/locales/fr.json +0 -6
  89. invokeai/frontend/web/dist/locales/it.json +17 -64
  90. invokeai/frontend/web/dist/locales/ja.json +379 -44
  91. invokeai/frontend/web/dist/locales/ru.json +0 -6
  92. invokeai/frontend/web/dist/locales/vi.json +7 -54
  93. invokeai/frontend/web/dist/locales/zh-CN.json +0 -6
  94. invokeai/version/invokeai_version.py +1 -1
  95. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/METADATA +4 -4
  96. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/RECORD +102 -71
  97. invokeai/frontend/web/dist/assets/App-Cn9UyjoV.js +0 -161
  98. invokeai/frontend/web/dist/assets/index-BDrf9CL-.js +0 -530
  99. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/WHEEL +0 -0
  100. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/entry_points.txt +0 -0
  101. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/licenses/LICENSE +0 -0
  102. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/licenses/LICENSE-SD1+SD2.txt +0 -0
  103. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/licenses/LICENSE-SDXL.txt +0 -0
  104. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/top_level.txt +0 -0
@@ -49,6 +49,7 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
49
49
  FLUXConditioningInfo,
50
50
  SD3ConditioningInfo,
51
51
  SDXLConditioningInfo,
52
+ ZImageConditioningInfo,
52
53
  )
53
54
  from invokeai.backend.util.logging import InvokeAILogger
54
55
  from invokeai.version.invokeai_version import __version__
@@ -129,6 +130,7 @@ class ApiDependencies:
129
130
  FLUXConditioningInfo,
130
131
  SD3ConditioningInfo,
131
132
  CogView4ConditioningInfo,
133
+ ZImageConditioningInfo,
132
134
  ],
133
135
  ephemeral=True,
134
136
  ),
@@ -28,7 +28,7 @@ from invokeai.app.services.model_records import (
28
28
  UnknownModelException,
29
29
  )
30
30
  from invokeai.app.util.suppress_output import SuppressOutput
31
- from invokeai.backend.model_manager.configs.factory import AnyModelConfig
31
+ from invokeai.backend.model_manager.configs.factory import AnyModelConfig, ModelConfigFactory
32
32
  from invokeai.backend.model_manager.configs.main import (
33
33
  Main_Checkpoint_SD1_Config,
34
34
  Main_Checkpoint_SD2_Config,
@@ -38,6 +38,7 @@ from invokeai.backend.model_manager.configs.main import (
38
38
  from invokeai.backend.model_manager.load.model_cache.cache_stats import CacheStats
39
39
  from invokeai.backend.model_manager.metadata.fetch.huggingface import HuggingFaceMetadataFetch
40
40
  from invokeai.backend.model_manager.metadata.metadata_base import ModelMetadataWithFiles, UnknownMetadataException
41
+ from invokeai.backend.model_manager.model_on_disk import ModelOnDisk
41
42
  from invokeai.backend.model_manager.search import ModelSearch
42
43
  from invokeai.backend.model_manager.starter_models import (
43
44
  STARTER_BUNDLES,
@@ -191,6 +192,40 @@ async def get_model_record(
191
192
  raise HTTPException(status_code=404, detail=str(e))
192
193
 
193
194
 
195
+ @model_manager_router.post(
196
+ "/i/{key}/reidentify",
197
+ operation_id="reidentify_model",
198
+ responses={
199
+ 200: {
200
+ "description": "The model configuration was retrieved successfully",
201
+ "content": {"application/json": {"example": example_model_config}},
202
+ },
203
+ 400: {"description": "Bad request"},
204
+ 404: {"description": "The model could not be found"},
205
+ },
206
+ )
207
+ async def reidentify_model(
208
+ key: Annotated[str, Path(description="Key of the model to reidentify.")],
209
+ ) -> AnyModelConfig:
210
+ """Attempt to reidentify a model by re-probing its weights file."""
211
+ try:
212
+ config = ApiDependencies.invoker.services.model_manager.store.get_model(key)
213
+ models_path = ApiDependencies.invoker.services.configuration.models_path
214
+ if pathlib.Path(config.path).is_relative_to(models_path):
215
+ model_path = pathlib.Path(config.path)
216
+ else:
217
+ model_path = models_path / config.path
218
+ mod = ModelOnDisk(model_path)
219
+ result = ModelConfigFactory.from_model_on_disk(mod)
220
+ if result.config is None:
221
+ raise InvalidModelException("Unable to identify model format")
222
+ result.config.key = config.key # retain the same key
223
+ new_config = ApiDependencies.invoker.services.model_manager.store.replace_model(config.key, result.config)
224
+ return new_config
225
+ except UnknownModelException as e:
226
+ raise HTTPException(status_code=404, detail=str(e))
227
+
228
+
194
229
  class FoundModel(BaseModel):
195
230
  path: str = Field(description="Path to the model")
196
231
  is_installed: bool = Field(description="Whether or not the model is already installed")
@@ -238,9 +273,10 @@ async def scan_for_models(
238
273
  found_model = FoundModel(path=path, is_installed=is_installed)
239
274
  scan_results.append(found_model)
240
275
  except Exception as e:
276
+ error_type = type(e).__name__
241
277
  raise HTTPException(
242
278
  status_code=500,
243
- detail=f"An error occurred while searching the directory: {e}",
279
+ detail=f"An error occurred while searching the directory: {error_type}",
244
280
  )
245
281
  return scan_results
246
282
 
@@ -411,6 +447,59 @@ async def delete_model(
411
447
  raise HTTPException(status_code=404, detail=str(e))
412
448
 
413
449
 
450
+ class BulkDeleteModelsRequest(BaseModel):
451
+ """Request body for bulk model deletion."""
452
+
453
+ keys: List[str] = Field(description="List of model keys to delete")
454
+
455
+
456
+ class BulkDeleteModelsResponse(BaseModel):
457
+ """Response body for bulk model deletion."""
458
+
459
+ deleted: List[str] = Field(description="List of successfully deleted model keys")
460
+ failed: List[dict] = Field(description="List of failed deletions with error messages")
461
+
462
+
463
+ @model_manager_router.post(
464
+ "/i/bulk_delete",
465
+ operation_id="bulk_delete_models",
466
+ responses={
467
+ 200: {"description": "Models deleted (possibly with some failures)"},
468
+ },
469
+ status_code=200,
470
+ )
471
+ async def bulk_delete_models(
472
+ request: BulkDeleteModelsRequest = Body(description="List of model keys to delete"),
473
+ ) -> BulkDeleteModelsResponse:
474
+ """
475
+ Delete multiple model records from database.
476
+
477
+ The configuration records will be removed. The corresponding weights files will be
478
+ deleted as well if they reside within the InvokeAI "models" directory.
479
+ Returns a list of successfully deleted keys and failed deletions with error messages.
480
+ """
481
+ logger = ApiDependencies.invoker.services.logger
482
+ installer = ApiDependencies.invoker.services.model_manager.install
483
+
484
+ deleted = []
485
+ failed = []
486
+
487
+ for key in request.keys:
488
+ try:
489
+ installer.delete(key)
490
+ deleted.append(key)
491
+ logger.info(f"Deleted model: {key}")
492
+ except UnknownModelException as e:
493
+ logger.error(f"Failed to delete model {key}: {str(e)}")
494
+ failed.append({"key": key, "error": str(e)})
495
+ except Exception as e:
496
+ logger.error(f"Failed to delete model {key}: {str(e)}")
497
+ failed.append({"key": key, "error": str(e)})
498
+
499
+ logger.info(f"Bulk delete completed: {len(deleted)} deleted, {len(failed)} failed")
500
+ return BulkDeleteModelsResponse(deleted=deleted, failed=failed)
501
+
502
+
414
503
  @model_manager_router.delete(
415
504
  "/i/{key}/image",
416
505
  operation_id="delete_model_image",
@@ -223,6 +223,15 @@ async def get_workflow_thumbnail(
223
223
  raise HTTPException(status_code=404)
224
224
 
225
225
 
226
+ @workflows_router.get("/tags", operation_id="get_all_tags")
227
+ async def get_all_tags(
228
+ categories: Optional[list[WorkflowCategory]] = Query(default=None, description="The categories to include"),
229
+ ) -> list[str]:
230
+ """Gets all unique tags from workflows"""
231
+
232
+ return ApiDependencies.invoker.services.workflow_records.get_all_tags(categories=categories)
233
+
234
+
226
235
  @workflows_router.get("/counts_by_tag", operation_id="get_counts_by_tag")
227
236
  async def get_counts_by_tag(
228
237
  tags: list[str] = Query(description="The tags to get counts for"),
@@ -154,6 +154,7 @@ class FieldDescriptions:
154
154
  clip = "CLIP (tokenizer, text encoder, LoRAs) and skipped layer count"
155
155
  t5_encoder = "T5 tokenizer and text encoder"
156
156
  glm_encoder = "GLM (THUDM) tokenizer and text encoder"
157
+ qwen3_encoder = "Qwen3 tokenizer and text encoder"
157
158
  clip_embed_model = "CLIP Embed loader"
158
159
  clip_g_model = "CLIP-G Embed loader"
159
160
  unet = "UNet (scheduler, LoRAs)"
@@ -169,6 +170,7 @@ class FieldDescriptions:
169
170
  flux_model = "Flux model (Transformer) to load"
170
171
  sd3_model = "SD3 model (MMDiTX) to load"
171
172
  cogview4_model = "CogView4 model (Transformer) to load"
173
+ z_image_model = "Z-Image model (Transformer) to load"
172
174
  sdxl_main_model = "SDXL Main model (UNet, VAE, CLIP1, CLIP2) to load"
173
175
  sdxl_refiner_model = "SDXL Refiner Main Modde (UNet, VAE, CLIP2) to load"
174
176
  onnx_main_model = "ONNX Main model (UNet, VAE, CLIP) to load"
@@ -241,6 +243,12 @@ class BoardField(BaseModel):
241
243
  board_id: str = Field(description="The id of the board")
242
244
 
243
245
 
246
+ class StylePresetField(BaseModel):
247
+ """A style preset primitive field"""
248
+
249
+ style_preset_id: str = Field(description="The id of the style preset")
250
+
251
+
244
252
  class DenoiseMaskField(BaseModel):
245
253
  """An inpaint mask field"""
246
254
 
@@ -321,6 +329,17 @@ class CogView4ConditioningField(BaseModel):
321
329
  conditioning_name: str = Field(description="The name of conditioning tensor")
322
330
 
323
331
 
332
+ class ZImageConditioningField(BaseModel):
333
+ """A Z-Image conditioning tensor primitive value"""
334
+
335
+ conditioning_name: str = Field(description="The name of conditioning tensor")
336
+ mask: Optional[TensorField] = Field(
337
+ default=None,
338
+ description="The mask associated with this conditioning tensor for regional prompting. "
339
+ "Excluded regions should be set to False, included regions should be set to True.",
340
+ )
341
+
342
+
324
343
  class ConditioningField(BaseModel):
325
344
  """A conditioning tensor primitive value"""
326
345
 
@@ -47,6 +47,7 @@ from invokeai.backend.flux.sampling_utils import (
47
47
  pack,
48
48
  unpack,
49
49
  )
50
+ from invokeai.backend.flux.schedulers import FLUX_SCHEDULER_LABELS, FLUX_SCHEDULER_MAP, FLUX_SCHEDULER_NAME_VALUES
50
51
  from invokeai.backend.flux.text_conditioning import FluxReduxConditioning, FluxTextConditioning
51
52
  from invokeai.backend.model_manager.taxonomy import BaseModelType, FluxVariantType, ModelFormat, ModelType
52
53
  from invokeai.backend.patches.layer_patcher import LayerPatcher
@@ -63,7 +64,7 @@ from invokeai.backend.util.devices import TorchDevice
63
64
  title="FLUX Denoise",
64
65
  tags=["image", "flux"],
65
66
  category="image",
66
- version="4.1.0",
67
+ version="4.2.0",
67
68
  )
68
69
  class FluxDenoiseInvocation(BaseInvocation):
69
70
  """Run denoising process with a FLUX transformer model."""
@@ -132,6 +133,12 @@ class FluxDenoiseInvocation(BaseInvocation):
132
133
  num_steps: int = InputField(
133
134
  default=4, description="Number of diffusion steps. Recommended values are schnell: 4, dev: 50."
134
135
  )
136
+ scheduler: FLUX_SCHEDULER_NAME_VALUES = InputField(
137
+ default="euler",
138
+ description="Scheduler (sampler) for the denoising process. 'euler' is fast and standard. "
139
+ "'heun' is 2nd-order (better quality, 2x slower). 'lcm' is optimized for few steps.",
140
+ ui_choice_labels=FLUX_SCHEDULER_LABELS,
141
+ )
135
142
  guidance: float = InputField(
136
143
  default=4.0,
137
144
  description="The guidance strength. Higher values adhere more strictly to the prompt, and will produce less diverse images. FLUX dev only, ignored for schnell.",
@@ -242,6 +249,12 @@ class FluxDenoiseInvocation(BaseInvocation):
242
249
  shift=not is_schnell,
243
250
  )
244
251
 
252
+ # Create scheduler if not using default euler
253
+ scheduler = None
254
+ if self.scheduler in FLUX_SCHEDULER_MAP:
255
+ scheduler_class = FLUX_SCHEDULER_MAP[self.scheduler]
256
+ scheduler = scheduler_class(num_train_timesteps=1000)
257
+
245
258
  # Clip the timesteps schedule based on denoising_start and denoising_end.
246
259
  timesteps = clip_timestep_schedule_fractional(timesteps, self.denoising_start, self.denoising_end)
247
260
 
@@ -426,6 +439,7 @@ class FluxDenoiseInvocation(BaseInvocation):
426
439
  img_cond=img_cond,
427
440
  img_cond_seq=img_cond_seq,
428
441
  img_cond_seq_ids=img_cond_seq_ids,
442
+ scheduler=scheduler,
429
443
  )
430
444
 
431
445
  x = unpack(x.float(), self.height, self.width)
@@ -1,5 +1,6 @@
1
1
  from contextlib import nullcontext
2
2
  from functools import singledispatchmethod
3
+ from typing import Literal
3
4
 
4
5
  import einops
5
6
  import torch
@@ -20,7 +21,7 @@ from invokeai.app.invocations.fields import (
20
21
  Input,
21
22
  InputField,
22
23
  )
23
- from invokeai.app.invocations.model import VAEField
24
+ from invokeai.app.invocations.model import BaseModelType, VAEField
24
25
  from invokeai.app.invocations.primitives import LatentsOutput
25
26
  from invokeai.app.services.shared.invocation_context import InvocationContext
26
27
  from invokeai.backend.model_manager.load.load_base import LoadedModel
@@ -29,13 +30,21 @@ from invokeai.backend.stable_diffusion.vae_tiling import patch_vae_tiling_params
29
30
  from invokeai.backend.util.devices import TorchDevice
30
31
  from invokeai.backend.util.vae_working_memory import estimate_vae_working_memory_sd15_sdxl
31
32
 
33
+ """
34
+ SDXL VAE color compensation values determined experimentally to reduce color drift.
35
+ If more reliable values are found in the future (e.g. individual color channels), they can be updated.
36
+ SD1.5, TAESD, TAESDXL VAEs distort in less predictable ways, so no compensation is offered at this time.
37
+ """
38
+ COMPENSATION_OPTIONS = Literal["None", "SDXL"]
39
+ COLOR_COMPENSATION_MAP = {"None": [1, 0], "SDXL": [1.015, -0.002]}
40
+
32
41
 
33
42
  @invocation(
34
43
  "i2l",
35
44
  title="Image to Latents - SD1.5, SDXL",
36
45
  tags=["latents", "image", "vae", "i2l"],
37
46
  category="latents",
38
- version="1.1.1",
47
+ version="1.2.0",
39
48
  )
40
49
  class ImageToLatentsInvocation(BaseInvocation):
41
50
  """Encodes an image into latents."""
@@ -52,6 +61,10 @@ class ImageToLatentsInvocation(BaseInvocation):
52
61
  # offer a way to directly set None values.
53
62
  tile_size: int = InputField(default=0, multiple_of=8, description=FieldDescriptions.vae_tile_size)
54
63
  fp32: bool = InputField(default=False, description=FieldDescriptions.fp32)
64
+ color_compensation: COMPENSATION_OPTIONS = InputField(
65
+ default="None",
66
+ description="Apply VAE scaling compensation when encoding images (reduces color drift).",
67
+ )
55
68
 
56
69
  @classmethod
57
70
  def vae_encode(
@@ -62,7 +75,7 @@ class ImageToLatentsInvocation(BaseInvocation):
62
75
  image_tensor: torch.Tensor,
63
76
  tile_size: int = 0,
64
77
  ) -> torch.Tensor:
65
- assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny))
78
+ assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny)), "VAE must be of type SD-1.5 or SDXL"
66
79
  estimated_working_memory = estimate_vae_working_memory_sd15_sdxl(
67
80
  operation="encode",
68
81
  image_tensor=image_tensor,
@@ -71,7 +84,7 @@ class ImageToLatentsInvocation(BaseInvocation):
71
84
  fp32=upcast,
72
85
  )
73
86
  with vae_info.model_on_device(working_mem_bytes=estimated_working_memory) as (_, vae):
74
- assert isinstance(vae, (AutoencoderKL, AutoencoderTiny))
87
+ assert isinstance(vae, (AutoencoderKL, AutoencoderTiny)), "VAE must be of type SD-1.5 or SDXL"
75
88
  orig_dtype = vae.dtype
76
89
  if upcast:
77
90
  vae.to(dtype=torch.float32)
@@ -127,9 +140,14 @@ class ImageToLatentsInvocation(BaseInvocation):
127
140
  image = context.images.get_pil(self.image.image_name)
128
141
 
129
142
  vae_info = context.models.load(self.vae.vae)
130
- assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny))
143
+ assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny)), "VAE must be of type SD-1.5 or SDXL"
131
144
 
132
145
  image_tensor = image_resized_to_grid_as_tensor(image.convert("RGB"))
146
+
147
+ if self.color_compensation != "None" and vae_info.config.base == BaseModelType.StableDiffusionXL:
148
+ scale, bias = COLOR_COMPENSATION_MAP[self.color_compensation]
149
+ image_tensor = image_tensor * scale + bias
150
+
133
151
  if image_tensor.dim() == 3:
134
152
  image_tensor = einops.rearrange(image_tensor, "c h w -> 1 c h w")
135
153
 
@@ -2,12 +2,6 @@ from contextlib import nullcontext
2
2
 
3
3
  import torch
4
4
  from diffusers.image_processor import VaeImageProcessor
5
- from diffusers.models.attention_processor import (
6
- AttnProcessor2_0,
7
- LoRAAttnProcessor2_0,
8
- LoRAXFormersAttnProcessor,
9
- XFormersAttnProcessor,
10
- )
11
5
  from diffusers.models.autoencoders.autoencoder_kl import AutoencoderKL
12
6
  from diffusers.models.autoencoders.autoencoder_tiny import AutoencoderTiny
13
7
 
@@ -77,26 +71,9 @@ class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
77
71
  assert isinstance(vae, (AutoencoderKL, AutoencoderTiny))
78
72
  latents = latents.to(TorchDevice.choose_torch_device())
79
73
  if self.fp32:
74
+ # FP32 mode: convert everything to float32 for maximum precision
80
75
  vae.to(dtype=torch.float32)
81
-
82
- use_torch_2_0_or_xformers = hasattr(vae.decoder, "mid_block") and isinstance(
83
- vae.decoder.mid_block.attentions[0].processor,
84
- (
85
- AttnProcessor2_0,
86
- XFormersAttnProcessor,
87
- LoRAXFormersAttnProcessor,
88
- LoRAAttnProcessor2_0,
89
- ),
90
- )
91
- # if xformers or torch_2_0 is used attention block does not need
92
- # to be in float32 which can save lots of memory
93
- if use_torch_2_0_or_xformers:
94
- vae.post_quant_conv.to(latents.dtype)
95
- vae.decoder.conv_in.to(latents.dtype)
96
- vae.decoder.mid_block.to(latents.dtype)
97
- else:
98
- latents = latents.float()
99
-
76
+ latents = latents.float()
100
77
  else:
101
78
  vae.to(dtype=torch.float16)
102
79
  latents = latents.half()
@@ -158,6 +158,10 @@ GENERATION_MODES = Literal[
158
158
  "cogview4_img2img",
159
159
  "cogview4_inpaint",
160
160
  "cogview4_outpaint",
161
+ "z_image_txt2img",
162
+ "z_image_img2img",
163
+ "z_image_inpaint",
164
+ "z_image_outpaint",
161
165
  ]
162
166
 
163
167
 
@@ -166,7 +170,7 @@ GENERATION_MODES = Literal[
166
170
  title="Core Metadata",
167
171
  tags=["metadata"],
168
172
  category="metadata",
169
- version="2.0.0",
173
+ version="2.1.0",
170
174
  classification=Classification.Internal,
171
175
  )
172
176
  class CoreMetadataInvocation(BaseInvocation):
@@ -217,6 +221,10 @@ class CoreMetadataInvocation(BaseInvocation):
217
221
  default=None,
218
222
  description="The VAE used for decoding, if the main model's default was not used",
219
223
  )
224
+ qwen3_encoder: Optional[ModelIdentifierField] = InputField(
225
+ default=None,
226
+ description="The Qwen3 text encoder model used for Z-Image inference",
227
+ )
220
228
 
221
229
  # High resolution fix metadata.
222
230
  hrf_enabled: Optional[bool] = InputField(
@@ -52,6 +52,7 @@ from invokeai.app.invocations.primitives import (
52
52
  )
53
53
  from invokeai.app.invocations.scheduler import SchedulerOutput
54
54
  from invokeai.app.invocations.t2i_adapter import T2IAdapterField, T2IAdapterInvocation
55
+ from invokeai.app.invocations.z_image_denoise import ZImageDenoiseInvocation
55
56
  from invokeai.app.services.shared.invocation_context import InvocationContext
56
57
  from invokeai.backend.model_manager.taxonomy import BaseModelType, ModelType, SubModelType
57
58
  from invokeai.backend.stable_diffusion.schedulers.schedulers import SCHEDULER_NAME_VALUES
@@ -729,6 +730,52 @@ class FluxDenoiseLatentsMetaInvocation(FluxDenoiseInvocation, WithMetadata):
729
730
  return LatentsMetaOutput(**params, metadata=MetadataField.model_validate(md))
730
731
 
731
732
 
733
+ @invocation(
734
+ "z_image_denoise_meta",
735
+ title=f"{ZImageDenoiseInvocation.UIConfig.title} + Metadata",
736
+ tags=["z-image", "latents", "denoise", "txt2img", "t2i", "t2l", "img2img", "i2i", "l2l"],
737
+ category="latents",
738
+ version="1.0.0",
739
+ )
740
+ class ZImageDenoiseMetaInvocation(ZImageDenoiseInvocation, WithMetadata):
741
+ """Run denoising process with a Z-Image transformer model + metadata."""
742
+
743
+ def invoke(self, context: InvocationContext) -> LatentsMetaOutput:
744
+ def _loras_to_json(obj: Union[Any, list[Any]]):
745
+ if not isinstance(obj, list):
746
+ obj = [obj]
747
+
748
+ output: list[dict[str, Any]] = []
749
+ for item in obj:
750
+ output.append(
751
+ LoRAMetadataField(
752
+ model=item.lora,
753
+ weight=item.weight,
754
+ ).model_dump(exclude_none=True, exclude={"id", "type", "is_intermediate", "use_cache"})
755
+ )
756
+ return output
757
+
758
+ obj = super().invoke(context)
759
+
760
+ md: Dict[str, Any] = {} if self.metadata is None else self.metadata.root
761
+ md.update({"width": obj.width})
762
+ md.update({"height": obj.height})
763
+ md.update({"steps": self.steps})
764
+ md.update({"guidance": self.guidance_scale})
765
+ md.update({"denoising_start": self.denoising_start})
766
+ md.update({"denoising_end": self.denoising_end})
767
+ md.update({"scheduler": self.scheduler})
768
+ md.update({"model": self.transformer.transformer})
769
+ md.update({"seed": self.seed})
770
+ if len(self.transformer.loras) > 0:
771
+ md.update({"loras": _loras_to_json(self.transformer.loras)})
772
+
773
+ params = obj.__dict__.copy()
774
+ del params["type"]
775
+
776
+ return LatentsMetaOutput(**params, metadata=MetadataField.model_validate(md))
777
+
778
+
732
779
  @invocation(
733
780
  "metadata_to_vae",
734
781
  title="Metadata To VAE",
@@ -72,6 +72,14 @@ class GlmEncoderField(BaseModel):
72
72
  text_encoder: ModelIdentifierField = Field(description="Info to load text_encoder submodel")
73
73
 
74
74
 
75
+ class Qwen3EncoderField(BaseModel):
76
+ """Field for Qwen3 text encoder used by Z-Image models."""
77
+
78
+ tokenizer: ModelIdentifierField = Field(description="Info to load tokenizer submodel")
79
+ text_encoder: ModelIdentifierField = Field(description="Info to load text_encoder submodel")
80
+ loras: List[LoRAField] = Field(default_factory=list, description="LoRAs to apply on model loading")
81
+
82
+
75
83
  class VAEField(BaseModel):
76
84
  vae: ModelIdentifierField = Field(description="Info to load vae submodel")
77
85
  seamless_axes: List[str] = Field(default_factory=list, description='Axes("x" and "y") to which apply seamless')
@@ -0,0 +1,59 @@
1
+ import pathlib
2
+ from typing import Literal
3
+
4
+ from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output
5
+ from invokeai.app.invocations.fields import ImageField, InputField, OutputField, WithBoard, WithMetadata
6
+ from invokeai.app.services.shared.invocation_context import InvocationContext
7
+ from invokeai.backend.image_util.pbr_maps.architecture.pbr_rrdb_net import PBR_RRDB_Net
8
+ from invokeai.backend.image_util.pbr_maps.pbr_maps import NORMAL_MAP_MODEL, OTHER_MAP_MODEL, PBRMapsGenerator
9
+ from invokeai.backend.util.devices import TorchDevice
10
+
11
+
12
+ @invocation_output("pbr_maps-output")
13
+ class PBRMapsOutput(BaseInvocationOutput):
14
+ normal_map: ImageField = OutputField(default=None, description="The generated normal map")
15
+ roughness_map: ImageField = OutputField(default=None, description="The generated roughness map")
16
+ displacement_map: ImageField = OutputField(default=None, description="The generated displacement map")
17
+
18
+
19
+ @invocation("pbr_maps", title="PBR Maps", tags=["image", "material"], category="image", version="1.0.0")
20
+ class PBRMapsInvocation(BaseInvocation, WithMetadata, WithBoard):
21
+ """Generate Normal, Displacement and Roughness Map from a given image"""
22
+
23
+ image: ImageField = InputField(description="Input image")
24
+ tile_size: int = InputField(default=512, description="Tile size")
25
+ border_mode: Literal["none", "seamless", "mirror", "replicate"] = InputField(
26
+ default="none", description="Border mode to apply to eliminate any artifacts or seams"
27
+ )
28
+
29
+ def invoke(self, context: InvocationContext) -> PBRMapsOutput:
30
+ image_pil = context.images.get_pil(self.image.image_name, mode="RGB")
31
+
32
+ def loader(model_path: pathlib.Path):
33
+ return PBRMapsGenerator.load_model(model_path, TorchDevice.choose_torch_device())
34
+
35
+ torch_device = TorchDevice.choose_torch_device()
36
+
37
+ with (
38
+ context.models.load_remote_model(NORMAL_MAP_MODEL, loader) as normal_map_model,
39
+ context.models.load_remote_model(OTHER_MAP_MODEL, loader) as other_map_model,
40
+ ):
41
+ assert isinstance(normal_map_model, PBR_RRDB_Net)
42
+ assert isinstance(other_map_model, PBR_RRDB_Net)
43
+ pbr_pipeline = PBRMapsGenerator(normal_map_model, other_map_model, torch_device)
44
+ normal_map, roughness_map, displacement_map = pbr_pipeline.generate_maps(
45
+ image_pil, self.tile_size, self.border_mode
46
+ )
47
+
48
+ normal_map = context.images.save(normal_map)
49
+ normal_map_field = ImageField(image_name=normal_map.image_name)
50
+
51
+ roughness_map = context.images.save(roughness_map)
52
+ roughness_map_field = ImageField(image_name=roughness_map.image_name)
53
+
54
+ displacement_map = context.images.save(displacement_map)
55
+ displacement_map_field = ImageField(image_name=displacement_map.image_name)
56
+
57
+ return PBRMapsOutput(
58
+ normal_map=normal_map_field, roughness_map=roughness_map_field, displacement_map=displacement_map_field
59
+ )
@@ -27,6 +27,7 @@ from invokeai.app.invocations.fields import (
27
27
  SD3ConditioningField,
28
28
  TensorField,
29
29
  UIComponent,
30
+ ZImageConditioningField,
30
31
  )
31
32
  from invokeai.app.services.images.images_common import ImageDTO
32
33
  from invokeai.app.services.shared.invocation_context import InvocationContext
@@ -461,6 +462,17 @@ class CogView4ConditioningOutput(BaseInvocationOutput):
461
462
  return cls(conditioning=CogView4ConditioningField(conditioning_name=conditioning_name))
462
463
 
463
464
 
465
+ @invocation_output("z_image_conditioning_output")
466
+ class ZImageConditioningOutput(BaseInvocationOutput):
467
+ """Base class for nodes that output a Z-Image text conditioning tensor."""
468
+
469
+ conditioning: ZImageConditioningField = OutputField(description=FieldDescriptions.cond)
470
+
471
+ @classmethod
472
+ def build(cls, conditioning_name: str) -> "ZImageConditioningOutput":
473
+ return cls(conditioning=ZImageConditioningField(conditioning_name=conditioning_name))
474
+
475
+
464
476
  @invocation_output("conditioning_output")
465
477
  class ConditioningOutput(BaseInvocationOutput):
466
478
  """Base class for nodes that output a single conditioning tensor"""
@@ -0,0 +1,57 @@
1
+ from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output
2
+ from invokeai.app.invocations.fields import InputField, OutputField, StylePresetField, UIComponent
3
+ from invokeai.app.services.shared.invocation_context import InvocationContext
4
+
5
+
6
+ @invocation_output("prompt_template_output")
7
+ class PromptTemplateOutput(BaseInvocationOutput):
8
+ """Output for the Prompt Template node"""
9
+
10
+ positive_prompt: str = OutputField(description="The positive prompt with the template applied")
11
+ negative_prompt: str = OutputField(description="The negative prompt with the template applied")
12
+
13
+
14
+ @invocation(
15
+ "prompt_template",
16
+ title="Prompt Template",
17
+ tags=["prompt", "template", "style", "preset"],
18
+ category="prompt",
19
+ version="1.0.0",
20
+ )
21
+ class PromptTemplateInvocation(BaseInvocation):
22
+ """Applies a Style Preset template to positive and negative prompts.
23
+
24
+ Select a Style Preset and provide positive/negative prompts. The node replaces
25
+ {prompt} placeholders in the template with your input prompts.
26
+ """
27
+
28
+ style_preset: StylePresetField = InputField(
29
+ description="The Style Preset to use as a template",
30
+ )
31
+ positive_prompt: str = InputField(
32
+ default="",
33
+ description="The positive prompt to insert into the template's {prompt} placeholder",
34
+ ui_component=UIComponent.Textarea,
35
+ )
36
+ negative_prompt: str = InputField(
37
+ default="",
38
+ description="The negative prompt to insert into the template's {prompt} placeholder",
39
+ ui_component=UIComponent.Textarea,
40
+ )
41
+
42
+ def invoke(self, context: InvocationContext) -> PromptTemplateOutput:
43
+ # Fetch the style preset from the database
44
+ style_preset = context._services.style_preset_records.get(self.style_preset.style_preset_id)
45
+
46
+ # Get the template prompts
47
+ positive_template = style_preset.preset_data.positive_prompt
48
+ negative_template = style_preset.preset_data.negative_prompt
49
+
50
+ # Replace {prompt} placeholder with the input prompts
51
+ rendered_positive = positive_template.replace("{prompt}", self.positive_prompt)
52
+ rendered_negative = negative_template.replace("{prompt}", self.negative_prompt)
53
+
54
+ return PromptTemplateOutput(
55
+ positive_prompt=rendered_positive,
56
+ negative_prompt=rendered_negative,
57
+ )