inline-core 1.2.41__tar.gz → 1.2.51__tar.gz
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.
- {inline_core-1.2.41 → inline_core-1.2.51}/PKG-INFO +33 -16
- {inline_core-1.2.41 → inline_core-1.2.51}/README.md +29 -13
- {inline_core-1.2.41 → inline_core-1.2.51}/pyproject.toml +5 -4
- inline_core-1.2.51/src/inline_core/models/checkpoint.py +87 -0
- inline_core-1.2.51/src/inline_core/models/krea2/__init__.py +9 -0
- inline_core-1.2.51/src/inline_core/models/krea2/convert.py +138 -0
- inline_core-1.2.51/src/inline_core/models/krea2/img2img.py +89 -0
- inline_core-1.2.51/src/inline_core/models/krea2/provider.py +57 -0
- inline_core-1.2.51/src/inline_core/models/krea2/requirements.py +180 -0
- inline_core-1.2.51/src/inline_core/models/krea2/runner.py +407 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/models/loaders.py +334 -22
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/models/lora.py +78 -28
- inline_core-1.2.51/src/inline_core/models/pipeline_runtime.py +522 -0
- inline_core-1.2.51/src/inline_core/models/zimage/runner.py +465 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/server/app.py +24 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/server/bootstrap.py +10 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/handlers.py +6 -2
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/training.py +30 -3
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/training_store.py +6 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/training/__main__.py +8 -1
- inline_core-1.2.51/src/inline_core/training/arch.py +184 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/training/caption.py +39 -6
- inline_core-1.2.51/src/inline_core/training/dataset.py +158 -0
- inline_core-1.2.51/src/inline_core/training/models.py +319 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/training/protocol.py +8 -1
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/training/trainer.py +81 -56
- inline_core-1.2.51/tests/test_checkpoint.py +79 -0
- inline_core-1.2.51/tests/test_krea2_convert.py +157 -0
- inline_core-1.2.51/tests/test_krea2_requirements.py +115 -0
- inline_core-1.2.51/tests/test_krea2_runner.py +163 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_loaders.py +15 -3
- inline_core-1.2.51/tests/test_lora_download.py +75 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_rpc.py +2 -0
- inline_core-1.2.51/tests/test_training_arch.py +113 -0
- inline_core-1.2.51/tests/test_training_dataset.py +103 -0
- inline_core-1.2.51/tests/test_training_models.py +135 -0
- inline_core-1.2.51/tests/test_training_resolve.py +90 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_zimage_runner.py +17 -16
- {inline_core-1.2.41 → inline_core-1.2.51}/uv.lock +54 -54
- inline_core-1.2.51/webui.bat +225 -0
- inline_core-1.2.41/src/inline_core/models/zimage/runner.py +0 -985
- inline_core-1.2.41/src/inline_core/training/dataset.py +0 -100
- inline_core-1.2.41/src/inline_core/training/models.py +0 -125
- {inline_core-1.2.41 → inline_core-1.2.51}/.gitignore +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/.python-version +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/CLAUDE.md +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/main.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/scripts/reference.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/components/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/components/conditioning.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/components/interfaces.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/config.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/device/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/device/auto.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/device/detect.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/device/memory.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/device/policy.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/device/types.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/errors.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/api.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/constraints.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/fetch.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/handlers.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/importer.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/install.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/loader.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/manifest.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/models.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/paths.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/resolve.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/scanner.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/state.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/extensions/tools.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/ffmpeg.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/graph/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/graph/cache.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/graph/descriptor.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/graph/executor.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/graph/loader_runners.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/graph/primitives.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/graph/registry.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/graph/runners.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/graph/schema.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/graph/topo.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/graph/validate.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/media.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/models/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/models/catalog.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/models/requirements.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/models/sampling.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/models/zimage/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/models/zimage/primitives.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/models/zimage/provider.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/models/zimage/requirements.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/parallel/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/parallel/config.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/parallel/group.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/parallel/launch.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/parallel/protocol.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/parallel/registry.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/parallel/worker.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/runtime/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/runtime/context.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/runtime/file_store.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/runtime/progress.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/runtime/run.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/runtime/store.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/sampling/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/sampling/batch.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/server/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/server/__main__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/server/assets.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/server/frontend.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/server/manager.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/server/rpc.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/server/run_store.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/server/serialize.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/assets.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/config.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/fal.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/frames.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/generation.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/graph_build.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/models.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/moodboard.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/peaks.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/schema.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/store.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/system_stats.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/timeline/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/timeline/compose.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/timeline/ffmpeg.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/timeline/render.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/studio/timeline/resolve.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/takes.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/src/inline_core/training/__init__.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/helpers.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_cache.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_catalog.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_config.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_device_detect.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_executor.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_extension_api.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_extension_install.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_extension_manifest.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_extension_resolve.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_extension_scanner.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_extension_spine.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_extension_state.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_file_store.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_frontend_serving.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_hidden_nodes.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_loader_runners.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_lora.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_memory_policy.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_model_requirements.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_parallel_group.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_primitives.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_rpc_bridge.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_run_store.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_sampling.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_schema.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_server.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_assets.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_fal.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_frames.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_generation.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_models.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_moodboard.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_peaks.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_schema.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_store.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_timeline.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_studio_training.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_take_bytes.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_topo.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_validate.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_xfuser_sampler.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_zimage_primitives.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/tests/test_zimage_resolve.py +0 -0
- {inline_core-1.2.41 → inline_core-1.2.51}/webui.sh +0 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: inline-core
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.51
|
|
4
4
|
Summary: The generation engine behind Inline Studio.
|
|
5
|
+
License-Expression: GPL-3.0-or-later
|
|
5
6
|
Requires-Python: >=3.11
|
|
6
7
|
Requires-Dist: numpy>=1.26
|
|
7
8
|
Requires-Dist: psutil>=5.9
|
|
8
9
|
Provides-Extra: all
|
|
9
10
|
Requires-Dist: accelerate>=0.30; extra == 'all'
|
|
10
|
-
Requires-Dist: diffusers>=0.
|
|
11
|
+
Requires-Dist: diffusers>=0.39; extra == 'all'
|
|
11
12
|
Requires-Dist: fastapi>=0.110; extra == 'all'
|
|
12
13
|
Requires-Dist: huggingface-hub>=0.23; extra == 'all'
|
|
13
14
|
Requires-Dist: imageio-ffmpeg>=0.4; extra == 'all'
|
|
@@ -27,7 +28,7 @@ Requires-Dist: nvidia-ml-py>=12; extra == 'parallel'
|
|
|
27
28
|
Requires-Dist: xfuser>=0.4; extra == 'parallel'
|
|
28
29
|
Provides-Extra: runtime
|
|
29
30
|
Requires-Dist: accelerate>=0.30; extra == 'runtime'
|
|
30
|
-
Requires-Dist: diffusers>=0.
|
|
31
|
+
Requires-Dist: diffusers>=0.39; extra == 'runtime'
|
|
31
32
|
Requires-Dist: huggingface-hub>=0.23; extra == 'runtime'
|
|
32
33
|
Requires-Dist: safetensors>=0.4; extra == 'runtime'
|
|
33
34
|
Requires-Dist: scipy>=1.11; extra == 'runtime'
|
|
@@ -55,8 +56,9 @@ The generation engine behind Inline. It takes a typed node graph (JSON) and retu
|
|
|
55
56
|
low-VRAM laptops up to multi-GPU machines that split a single image's sampling across GPUs (via
|
|
56
57
|
xDiT). It is Inline Studio's built-in render backend.
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
already supports, so the multi-GPU split works on it from the start)
|
|
59
|
+
Models today: **Z-Image** (Alibaba Tongyi), a 6B rectified-flow diffusion transformer and the model
|
|
60
|
+
xDiT already supports, so the multi-GPU split works on it from the start; and **Krea 2** (Krea AI),
|
|
61
|
+
a 12.9B single-stream MMDiT released as an undistilled RAW base plus an 8-step distilled Turbo.
|
|
60
62
|
|
|
61
63
|
> Status: early, and running end to end against a stub engine. In place and tested: the graph engine,
|
|
62
64
|
> the typed `/v1` HTTP + websocket API (durable runs, streamed progress, coalescing), the model-dir
|
|
@@ -102,9 +104,13 @@ by category:
|
|
|
102
104
|
|
|
103
105
|
```
|
|
104
106
|
models/
|
|
105
|
-
diffusion_models/ z_image_turbo_bf16.safetensors
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
diffusion_models/ z_image_turbo_bf16.safetensors <- Z-Image
|
|
108
|
+
krea2_turbo_bf16.safetensors <- Krea 2 Turbo (generate)
|
|
109
|
+
krea2_raw_bf16.safetensors <- Krea 2 RAW (fine-tune)
|
|
110
|
+
vae/ ae.safetensors <- Z-Image
|
|
111
|
+
qwen_image_vae_diffusers.safetensors <- Krea 2
|
|
112
|
+
text_encoders/ qwen_3_4b.safetensors <- Z-Image
|
|
113
|
+
qwen3vl_4b_bf16.safetensors <- Krea 2
|
|
108
114
|
loras/ controlnet/ checkpoints/ ...
|
|
109
115
|
```
|
|
110
116
|
|
|
@@ -123,8 +129,17 @@ paths:
|
|
|
123
129
|
`models/`** (never the hidden HF cache) with visible progress.
|
|
124
130
|
|
|
125
131
|
Override the diffusion source with `INLINE_ZIMAGE_MODEL` (a file or a diffusers dir), and the supporting
|
|
126
|
-
components with `INLINE_ZIMAGE_VAE` / `INLINE_ZIMAGE_TEXT_ENCODER
|
|
127
|
-
start; a node's model pickers list what is present.
|
|
132
|
+
components with `INLINE_ZIMAGE_VAE` / `INLINE_ZIMAGE_TEXT_ENCODER`; Krea 2 has the same three under
|
|
133
|
+
`INLINE_KREA2_*`. The engine scans the models dir on start; a node's model pickers list what is present.
|
|
134
|
+
|
|
135
|
+
**Krea 2 loads from the same ComfyUI single files**, with two constraints worth knowing. Only the
|
|
136
|
+
**bf16** builds in [`Comfy-Org/Krea-2`](https://huggingface.co/Comfy-Org/Krea-2) are loadable: the
|
|
137
|
+
fp8/int8/nvfp4 variants carry ComfyUI-specific scale tensors, and the loader refuses them by name
|
|
138
|
+
rather than failing mid-load. And the **VAE must be the diffusers-format** Qwen-Image file (the
|
|
139
|
+
node's popup fetches it from `Qwen/Qwen-Image`), because ComfyUI's copy of the same weights uses a
|
|
140
|
+
module layout diffusers has no converter for. `Krea2Transformer2DModel` has no `from_single_file`, so
|
|
141
|
+
the transformer checkpoint is renamed into diffusers naming on load (`models/krea2/convert.py`) and
|
|
142
|
+
streamed tensor by tensor onto the GPU.
|
|
128
143
|
|
|
129
144
|
## Nodes
|
|
130
145
|
|
|
@@ -132,8 +147,9 @@ start; a node's model pickers list what is present.
|
|
|
132
147
|
renders any node generically - adding a node type needs no UI release.
|
|
133
148
|
|
|
134
149
|
**High-level model nodes are what the user sees.** Generation is one-click: you drop a single
|
|
135
|
-
**Z-Image Turbo** node, wire a Prompt into it, and hit Run. The
|
|
136
|
-
and text-encoder behind the scenes - no loader/sampler
|
|
150
|
+
**Z-Image Turbo**, **Krea 2 Turbo** or **Krea 2 RAW** node, wire a Prompt into it, and hit Run. The
|
|
151
|
+
node hooks up the diffusion model, VAE, and text-encoder behind the scenes - no loader/sampler
|
|
152
|
+
wiring.
|
|
137
153
|
|
|
138
154
|
Underneath, a **low-level primitive vocabulary** exists (`load/diffusion-model`, `load/vae`,
|
|
139
155
|
`load/text-encoder`, `encode/text`, `latent/empty`, `sample`, `vae/decode`, `vae/encode`) - the
|
|
@@ -215,17 +231,18 @@ offload machinery, `--vram-budget GB` / `INLINE_VRAM_BUDGET_GB` to cap the assum
|
|
|
215
231
|
|
|
216
232
|
## Run
|
|
217
233
|
|
|
218
|
-
The easy path is `webui.sh
|
|
234
|
+
The easy path is `webui.sh` (macOS/Linux) or `webui.bat` (Windows), which maps friendly flags onto the
|
|
235
|
+
engine's `INLINE_*` env knobs. Windows must use `webui.bat` - `webui.sh` is a bash script:
|
|
219
236
|
|
|
220
237
|
```
|
|
221
|
-
./webui.sh # loopback, port 8848
|
|
238
|
+
./webui.sh # loopback, port 8848 (Windows: .\webui.bat)
|
|
222
239
|
./webui.sh --listen --port 9000 # bind all interfaces on 9000
|
|
223
240
|
./webui.sh --multi-gpu # split one image across GPUs (auto with 2+ GPUs)
|
|
224
241
|
./webui.sh --lowvram # tight-VRAM profile
|
|
225
|
-
./webui.sh --install --extra
|
|
242
|
+
./webui.sh --install --extra runtime # set up ./.venv with the model runtime, then exit
|
|
226
243
|
```
|
|
227
244
|
|
|
228
|
-
`./webui.sh --help` lists every flag (networking, multi-GPU, device/memory profile, paths). The same
|
|
245
|
+
`./webui.sh --help` (or `.\webui.bat --help`) lists every flag (networking, multi-GPU, device/memory profile, paths). The same
|
|
229
246
|
flags are available on the Python entrypoint - `python main.py --help` - which is the dev path (it also
|
|
230
247
|
takes `--front-end-root DIR` to serve a local SPA build). Or run the server module directly:
|
|
231
248
|
|
|
@@ -5,8 +5,9 @@ The generation engine behind Inline. It takes a typed node graph (JSON) and retu
|
|
|
5
5
|
low-VRAM laptops up to multi-GPU machines that split a single image's sampling across GPUs (via
|
|
6
6
|
xDiT). It is Inline Studio's built-in render backend.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
already supports, so the multi-GPU split works on it from the start)
|
|
8
|
+
Models today: **Z-Image** (Alibaba Tongyi), a 6B rectified-flow diffusion transformer and the model
|
|
9
|
+
xDiT already supports, so the multi-GPU split works on it from the start; and **Krea 2** (Krea AI),
|
|
10
|
+
a 12.9B single-stream MMDiT released as an undistilled RAW base plus an 8-step distilled Turbo.
|
|
10
11
|
|
|
11
12
|
> Status: early, and running end to end against a stub engine. In place and tested: the graph engine,
|
|
12
13
|
> the typed `/v1` HTTP + websocket API (durable runs, streamed progress, coalescing), the model-dir
|
|
@@ -52,9 +53,13 @@ by category:
|
|
|
52
53
|
|
|
53
54
|
```
|
|
54
55
|
models/
|
|
55
|
-
diffusion_models/ z_image_turbo_bf16.safetensors
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
diffusion_models/ z_image_turbo_bf16.safetensors <- Z-Image
|
|
57
|
+
krea2_turbo_bf16.safetensors <- Krea 2 Turbo (generate)
|
|
58
|
+
krea2_raw_bf16.safetensors <- Krea 2 RAW (fine-tune)
|
|
59
|
+
vae/ ae.safetensors <- Z-Image
|
|
60
|
+
qwen_image_vae_diffusers.safetensors <- Krea 2
|
|
61
|
+
text_encoders/ qwen_3_4b.safetensors <- Z-Image
|
|
62
|
+
qwen3vl_4b_bf16.safetensors <- Krea 2
|
|
58
63
|
loras/ controlnet/ checkpoints/ ...
|
|
59
64
|
```
|
|
60
65
|
|
|
@@ -73,8 +78,17 @@ paths:
|
|
|
73
78
|
`models/`** (never the hidden HF cache) with visible progress.
|
|
74
79
|
|
|
75
80
|
Override the diffusion source with `INLINE_ZIMAGE_MODEL` (a file or a diffusers dir), and the supporting
|
|
76
|
-
components with `INLINE_ZIMAGE_VAE` / `INLINE_ZIMAGE_TEXT_ENCODER
|
|
77
|
-
start; a node's model pickers list what is present.
|
|
81
|
+
components with `INLINE_ZIMAGE_VAE` / `INLINE_ZIMAGE_TEXT_ENCODER`; Krea 2 has the same three under
|
|
82
|
+
`INLINE_KREA2_*`. The engine scans the models dir on start; a node's model pickers list what is present.
|
|
83
|
+
|
|
84
|
+
**Krea 2 loads from the same ComfyUI single files**, with two constraints worth knowing. Only the
|
|
85
|
+
**bf16** builds in [`Comfy-Org/Krea-2`](https://huggingface.co/Comfy-Org/Krea-2) are loadable: the
|
|
86
|
+
fp8/int8/nvfp4 variants carry ComfyUI-specific scale tensors, and the loader refuses them by name
|
|
87
|
+
rather than failing mid-load. And the **VAE must be the diffusers-format** Qwen-Image file (the
|
|
88
|
+
node's popup fetches it from `Qwen/Qwen-Image`), because ComfyUI's copy of the same weights uses a
|
|
89
|
+
module layout diffusers has no converter for. `Krea2Transformer2DModel` has no `from_single_file`, so
|
|
90
|
+
the transformer checkpoint is renamed into diffusers naming on load (`models/krea2/convert.py`) and
|
|
91
|
+
streamed tensor by tensor onto the GPU.
|
|
78
92
|
|
|
79
93
|
## Nodes
|
|
80
94
|
|
|
@@ -82,8 +96,9 @@ start; a node's model pickers list what is present.
|
|
|
82
96
|
renders any node generically - adding a node type needs no UI release.
|
|
83
97
|
|
|
84
98
|
**High-level model nodes are what the user sees.** Generation is one-click: you drop a single
|
|
85
|
-
**Z-Image Turbo** node, wire a Prompt into it, and hit Run. The
|
|
86
|
-
and text-encoder behind the scenes - no loader/sampler
|
|
99
|
+
**Z-Image Turbo**, **Krea 2 Turbo** or **Krea 2 RAW** node, wire a Prompt into it, and hit Run. The
|
|
100
|
+
node hooks up the diffusion model, VAE, and text-encoder behind the scenes - no loader/sampler
|
|
101
|
+
wiring.
|
|
87
102
|
|
|
88
103
|
Underneath, a **low-level primitive vocabulary** exists (`load/diffusion-model`, `load/vae`,
|
|
89
104
|
`load/text-encoder`, `encode/text`, `latent/empty`, `sample`, `vae/decode`, `vae/encode`) - the
|
|
@@ -165,17 +180,18 @@ offload machinery, `--vram-budget GB` / `INLINE_VRAM_BUDGET_GB` to cap the assum
|
|
|
165
180
|
|
|
166
181
|
## Run
|
|
167
182
|
|
|
168
|
-
The easy path is `webui.sh
|
|
183
|
+
The easy path is `webui.sh` (macOS/Linux) or `webui.bat` (Windows), which maps friendly flags onto the
|
|
184
|
+
engine's `INLINE_*` env knobs. Windows must use `webui.bat` - `webui.sh` is a bash script:
|
|
169
185
|
|
|
170
186
|
```
|
|
171
|
-
./webui.sh # loopback, port 8848
|
|
187
|
+
./webui.sh # loopback, port 8848 (Windows: .\webui.bat)
|
|
172
188
|
./webui.sh --listen --port 9000 # bind all interfaces on 9000
|
|
173
189
|
./webui.sh --multi-gpu # split one image across GPUs (auto with 2+ GPUs)
|
|
174
190
|
./webui.sh --lowvram # tight-VRAM profile
|
|
175
|
-
./webui.sh --install --extra
|
|
191
|
+
./webui.sh --install --extra runtime # set up ./.venv with the model runtime, then exit
|
|
176
192
|
```
|
|
177
193
|
|
|
178
|
-
`./webui.sh --help` lists every flag (networking, multi-GPU, device/memory profile, paths). The same
|
|
194
|
+
`./webui.sh --help` (or `.\webui.bat --help`) lists every flag (networking, multi-GPU, device/memory profile, paths). The same
|
|
179
195
|
flags are available on the Python entrypoint - `python main.py --help` - which is the dev path (it also
|
|
180
196
|
takes `--front-end-root DIR` to serve a local SPA build). Or run the server module directly:
|
|
181
197
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
# PyPI name; the import package is `inline_core` (src/inline_core).
|
|
3
3
|
name = "inline-core"
|
|
4
|
-
version = "1.2.
|
|
4
|
+
version = "1.2.51"
|
|
5
5
|
description = "The generation engine behind Inline Studio."
|
|
6
6
|
readme = "README.md"
|
|
7
|
+
license = "GPL-3.0-or-later"
|
|
7
8
|
requires-python = ">=3.11"
|
|
8
9
|
# Deliberately torch-free so the engine boots and serves source nodes with no ML stack installed.
|
|
9
10
|
dependencies = [
|
|
@@ -16,8 +17,8 @@ dependencies = [
|
|
|
16
17
|
runtime = [
|
|
17
18
|
# A bare `torch` is CPU-only on Windows; see [tool.uv] below and requirements.txt.
|
|
18
19
|
"torch>=2.2",
|
|
19
|
-
# Z-Image needs
|
|
20
|
-
"diffusers>=0.
|
|
20
|
+
# Krea 2 needs diffusers >= 0.39 (Krea2Pipeline); Z-Image needs >= 0.36 (ZImagePipeline).
|
|
21
|
+
"diffusers>=0.39",
|
|
21
22
|
"transformers>=4.44",
|
|
22
23
|
"accelerate>=0.30",
|
|
23
24
|
"safetensors>=0.4",
|
|
@@ -62,7 +63,7 @@ dev = [
|
|
|
62
63
|
# Everything needed to run locally. Lists concrete packages, never `inline-core[...]` self-refs.
|
|
63
64
|
all = [
|
|
64
65
|
"torch>=2.2",
|
|
65
|
-
"diffusers>=0.
|
|
66
|
+
"diffusers>=0.39",
|
|
66
67
|
"transformers>=4.44",
|
|
67
68
|
"accelerate>=0.30",
|
|
68
69
|
"safetensors>=0.4",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Read a safetensors checkpoint tensor by tensor, without mapping the whole file.
|
|
2
|
+
|
|
3
|
+
``safetensors.safe_open`` maps the entire file at once, and Linux refuses a mapping larger than
|
|
4
|
+
physical RAM when there is no swap (the default heuristic overcommit mode). A 26GB Krea 2 checkpoint
|
|
5
|
+
is therefore unreadable on a 16GB machine - it fails with ``Cannot allocate memory`` before any GPU
|
|
6
|
+
work, no matter how small the model would be once quantized.
|
|
7
|
+
|
|
8
|
+
Reading each tensor's byte range instead keeps peak host RAM at one tensor and works for any file
|
|
9
|
+
size. Only used for the big single-file checkpoints; small files (LoRAs, VAEs) still go through
|
|
10
|
+
safetensors directly.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import struct
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from ..errors import ComponentError
|
|
21
|
+
|
|
22
|
+
#: safetensors dtype names -> torch dtypes, resolved lazily so importing this module stays cheap.
|
|
23
|
+
_DTYPE_NAMES = {
|
|
24
|
+
"F64": "float64",
|
|
25
|
+
"F32": "float32",
|
|
26
|
+
"F16": "float16",
|
|
27
|
+
"BF16": "bfloat16",
|
|
28
|
+
"I64": "int64",
|
|
29
|
+
"I32": "int32",
|
|
30
|
+
"I16": "int16",
|
|
31
|
+
"I8": "int8",
|
|
32
|
+
"U8": "uint8",
|
|
33
|
+
"BOOL": "bool",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CheckpointReader:
|
|
38
|
+
"""Random access to one safetensors file, one tensor at a time."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, path: str | Path) -> None:
|
|
41
|
+
self._path = Path(path)
|
|
42
|
+
try:
|
|
43
|
+
size = self._path.stat().st_size
|
|
44
|
+
with self._path.open("rb") as handle:
|
|
45
|
+
(header_len,) = struct.unpack("<Q", handle.read(8))
|
|
46
|
+
# Garbage bytes decode to an enormous length; reject it rather than trying to
|
|
47
|
+
# allocate it, which would raise MemoryError instead of a usable message.
|
|
48
|
+
if not 0 < header_len <= size - 8:
|
|
49
|
+
raise ValueError("header length is not plausible for this file")
|
|
50
|
+
header = json.loads(handle.read(header_len))
|
|
51
|
+
if not isinstance(header, dict):
|
|
52
|
+
raise ValueError("header is not a JSON object")
|
|
53
|
+
except (OSError, ValueError, struct.error) as error:
|
|
54
|
+
raise ComponentError(f"Could not read checkpoint {self._path.name}: {error}") from error
|
|
55
|
+
self._start = 8 + header_len
|
|
56
|
+
self._index: dict[str, Any] = {k: v for k, v in header.items() if k != "__metadata__"}
|
|
57
|
+
self.metadata: dict[str, Any] = header.get("__metadata__") or {}
|
|
58
|
+
|
|
59
|
+
def keys(self) -> list[str]:
|
|
60
|
+
return list(self._index)
|
|
61
|
+
|
|
62
|
+
def get_tensor(self, key: str, device: str | None = None) -> Any:
|
|
63
|
+
"""One tensor, read straight from its byte range into a fresh buffer."""
|
|
64
|
+
import torch
|
|
65
|
+
|
|
66
|
+
entry = self._index[key]
|
|
67
|
+
dtype_name = _DTYPE_NAMES.get(entry["dtype"])
|
|
68
|
+
if dtype_name is None:
|
|
69
|
+
raise ComponentError(
|
|
70
|
+
f"Checkpoint {self._path.name} uses the unsupported dtype {entry['dtype']!r} "
|
|
71
|
+
f"for {key!r}."
|
|
72
|
+
)
|
|
73
|
+
start, end = entry["data_offsets"]
|
|
74
|
+
buffer = bytearray(end - start)
|
|
75
|
+
with self._path.open("rb") as handle:
|
|
76
|
+
handle.seek(self._start + start)
|
|
77
|
+
if handle.readinto(buffer) != len(buffer):
|
|
78
|
+
raise ComponentError(f"Checkpoint {self._path.name} is truncated at {key!r}.")
|
|
79
|
+
tensor = torch.frombuffer(buffer, dtype=getattr(torch, dtype_name))
|
|
80
|
+
tensor = tensor.reshape(entry["shape"]) if entry["shape"] else tensor.reshape(())
|
|
81
|
+
return tensor.to(device) if device else tensor
|
|
82
|
+
|
|
83
|
+
def __enter__(self) -> CheckpointReader:
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
def __exit__(self, *_exc: object) -> None:
|
|
87
|
+
return None
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""The Krea 2 (Krea AI) runtime: a diffusers-backed text-to-image / img2img runner.
|
|
2
|
+
|
|
3
|
+
Optional subpackage. `server.bootstrap` imports `register_krea2` best-effort, so a core install
|
|
4
|
+
without the ``runtime`` extra (torch + diffusers) still boots and serves the source nodes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .runner import register_krea2
|
|
8
|
+
|
|
9
|
+
__all__ = ["register_krea2"]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Rename a Krea 2 checkpoint from the reference (ComfyUI) layout to the diffusers one.
|
|
2
|
+
|
|
3
|
+
``Krea2Transformer2DModel`` has no ``from_single_file``, so the single ``.safetensors`` under
|
|
4
|
+
``diffusion_models/`` cannot be loaded directly. The two layouts describe the same 430 tensors under
|
|
5
|
+
different names, so a pure rename plus one reshape is the whole conversion.
|
|
6
|
+
|
|
7
|
+
The same rename maps a LoRA's module path (``module_alias``), which matters because the two Krea 2
|
|
8
|
+
LoRA conventions disagree: the official Comfy-Org style LoRAs already use diffusers names, while
|
|
9
|
+
ostris' training adapter uses reference ones.
|
|
10
|
+
|
|
11
|
+
Torch-free apart from the tensors handed in, so it is cheap to import and easy to test.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
from collections.abc import Mapping
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from ...errors import ComponentError
|
|
21
|
+
|
|
22
|
+
#: Root-anchored renames, longest-first so ``txtmlp.0.scale`` wins over the ``txtmlp.1.`` prefix.
|
|
23
|
+
_ROOT: tuple[tuple[str, str], ...] = (
|
|
24
|
+
("txtmlp.0.scale", "txt_in.norm.weight"),
|
|
25
|
+
("txtmlp.1.", "txt_in.linear_1."),
|
|
26
|
+
("txtmlp.3.", "txt_in.linear_2."),
|
|
27
|
+
("tmlp.0.", "time_embed.linear_1."),
|
|
28
|
+
("tmlp.2.", "time_embed.linear_2."),
|
|
29
|
+
("tproj.1.", "time_mod_proj."),
|
|
30
|
+
("first.", "img_in."),
|
|
31
|
+
("last.linear.", "final_layer.linear."),
|
|
32
|
+
("last.norm.scale", "final_layer.norm.weight"),
|
|
33
|
+
("last.modulation.lin", "final_layer.scale_shift_table"),
|
|
34
|
+
("txtfusion.", "text_fusion."),
|
|
35
|
+
("blocks.", "transformer_blocks."),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
#: Renames applied anywhere in the path - they name parts of a block, which appears at three depths
|
|
39
|
+
#: (the main stack and the two text-fusion stacks).
|
|
40
|
+
_INNER: tuple[tuple[str, str], ...] = (
|
|
41
|
+
(".attn.qknorm.qnorm.scale", ".attn.norm_q.weight"),
|
|
42
|
+
(".attn.qknorm.knorm.scale", ".attn.norm_k.weight"),
|
|
43
|
+
(".attn.wq.", ".attn.to_q."),
|
|
44
|
+
(".attn.wk.", ".attn.to_k."),
|
|
45
|
+
(".attn.wv.", ".attn.to_v."),
|
|
46
|
+
(".attn.wo.", ".attn.to_out.0."),
|
|
47
|
+
(".attn.gate.", ".attn.to_gate."),
|
|
48
|
+
(".mlp.up.", ".ff.up."),
|
|
49
|
+
(".mlp.gate.", ".ff.gate."),
|
|
50
|
+
(".mlp.down.", ".ff.down."),
|
|
51
|
+
(".prenorm.scale", ".norm1.weight"),
|
|
52
|
+
(".postnorm.scale", ".norm2.weight"),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
#: Suffixes ComfyUI's quantized builds add on top of the 430 reference tensors.
|
|
56
|
+
_QUANT_MARKERS = (".weight_scale", ".weight_scale_2", ".comfy_quant")
|
|
57
|
+
|
|
58
|
+
_MOD_LIN = re.compile(r"\.mod\.lin$")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def convert_key(key: str) -> str:
|
|
62
|
+
"""One reference key in the diffusers naming. An already-diffusers key passes through."""
|
|
63
|
+
for old, new in _ROOT:
|
|
64
|
+
if key.startswith(old):
|
|
65
|
+
key = new + key[len(old) :]
|
|
66
|
+
break
|
|
67
|
+
for old, new in _INNER:
|
|
68
|
+
key = key.replace(old, new)
|
|
69
|
+
return _MOD_LIN.sub(".scale_shift_table", key)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def module_alias(stem: str) -> str | None:
|
|
73
|
+
"""A LoRA module path in diffusers naming, or None when the rename leaves it unchanged.
|
|
74
|
+
|
|
75
|
+
``lora.py`` has already stripped the checkpoint prefix, so ``stem`` looks like
|
|
76
|
+
``blocks.0.attn.wq``. The trailing ``.weight`` makes the suffix-anchored rules above fire."""
|
|
77
|
+
converted = convert_key(f"{stem}.weight").removesuffix(".weight")
|
|
78
|
+
return converted if converted != stem else None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def is_quantized_checkpoint(keys: Any) -> bool:
|
|
82
|
+
"""Whether this is a ComfyUI fp8/int8/nvfp4 build rather than the bf16 one."""
|
|
83
|
+
return any(str(k).endswith(_QUANT_MARKERS) for k in keys)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def check_loadable(keys: Any, expected: set[str]) -> None:
|
|
87
|
+
"""Refuse a checkpoint that would only partially load, before any tensor is read.
|
|
88
|
+
|
|
89
|
+
Key-only so the streaming loader can validate up front; a partial load would otherwise leave
|
|
90
|
+
random-initialised layers and produce quietly wrong images instead of an error."""
|
|
91
|
+
keys = list(keys)
|
|
92
|
+
if is_quantized_checkpoint(keys):
|
|
93
|
+
raise ComponentError(
|
|
94
|
+
"This is a ComfyUI quantized Krea 2 build (fp8 / int8 / nvfp4), which only ComfyUI can "
|
|
95
|
+
"read. Use krea2_raw_bf16.safetensors or krea2_turbo_bf16.safetensors - smart memory "
|
|
96
|
+
"quantizes it for your GPU on load."
|
|
97
|
+
)
|
|
98
|
+
converted = {convert_key(k): k for k in keys}
|
|
99
|
+
unknown = sorted(original for name, original in converted.items() if name not in expected)
|
|
100
|
+
if unknown:
|
|
101
|
+
raise ComponentError(
|
|
102
|
+
f"Krea 2 checkpoint has {len(unknown)} unrecognised tensors (e.g. "
|
|
103
|
+
f"{', '.join(unknown[:3])}). It is probably a different model or a quantized build."
|
|
104
|
+
)
|
|
105
|
+
missing = sorted(expected - set(converted))
|
|
106
|
+
if missing:
|
|
107
|
+
raise ComponentError(
|
|
108
|
+
f"Krea 2 checkpoint is missing {len(missing)} tensors (e.g. "
|
|
109
|
+
f"{', '.join(missing[:3])}). The file looks truncated or is not a full checkpoint."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def convert_state_dict(
|
|
114
|
+
state: Mapping[str, Any], shapes: Mapping[str, tuple[int, ...]]
|
|
115
|
+
) -> dict[str, Any]:
|
|
116
|
+
"""Rename ``state`` into ``shapes``' naming, reshaping the one entry whose layout differs
|
|
117
|
+
(``blocks.N.mod.lin`` is flat where ``scale_shift_table`` is 2-D)."""
|
|
118
|
+
check_loadable(state.keys(), set(shapes))
|
|
119
|
+
converted: dict[str, Any] = {}
|
|
120
|
+
for key, tensor in state.items():
|
|
121
|
+
name = convert_key(key)
|
|
122
|
+
want = tuple(shapes[name])
|
|
123
|
+
if tuple(tensor.shape) != want:
|
|
124
|
+
if tensor.numel() != _numel(want):
|
|
125
|
+
raise ComponentError(
|
|
126
|
+
f"Krea 2 checkpoint tensor {key!r} is {tuple(tensor.shape)}, but the model "
|
|
127
|
+
f"expects {want}. This file is not a Krea 2 transformer."
|
|
128
|
+
)
|
|
129
|
+
tensor = tensor.reshape(want)
|
|
130
|
+
converted[name] = tensor
|
|
131
|
+
return converted
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _numel(shape: tuple[int, ...]) -> int:
|
|
135
|
+
total = 1
|
|
136
|
+
for dim in shape:
|
|
137
|
+
total *= dim
|
|
138
|
+
return total
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Krea 2 img2img: start the schedule partway, from an encoded input image.
|
|
2
|
+
|
|
3
|
+
diffusers ships no ``Krea2Img2ImgPipeline``, but ``Krea2Pipeline.__call__`` already accepts
|
|
4
|
+
pre-noised ``latents`` and an explicit ``sigmas`` schedule, so img2img needs no fork of the denoise
|
|
5
|
+
loop - only the right latents and the right tail of the schedule.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import torch
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def img2img_kwargs(
|
|
16
|
+
pipe: Any,
|
|
17
|
+
*,
|
|
18
|
+
image: Any,
|
|
19
|
+
strength: float,
|
|
20
|
+
steps: int,
|
|
21
|
+
width: int,
|
|
22
|
+
height: int,
|
|
23
|
+
generator: Any,
|
|
24
|
+
device: str,
|
|
25
|
+
) -> dict[str, Any]:
|
|
26
|
+
"""Start the schedule partway, from the input image, using the pipeline's own ``latents`` and
|
|
27
|
+
``sigmas`` inputs - diffusers ships no ``Krea2Img2ImgPipeline`` and forking its denoise loop
|
|
28
|
+
would be a maintenance liability.
|
|
29
|
+
|
|
30
|
+
The sigma tail is handed over **unshifted**: ``set_timesteps`` applies the same monotone
|
|
31
|
+
exponential shift the pipeline would, so a shifted subset and a subset of the shifted schedule
|
|
32
|
+
are the same thing. The latents are noised at the *shifted* start sigma so the noise level the
|
|
33
|
+
model sees matches the timestep it is given."""
|
|
34
|
+
import numpy as np
|
|
35
|
+
from diffusers.pipelines.krea2.pipeline_krea2 import calculate_shift
|
|
36
|
+
|
|
37
|
+
raw = np.linspace(1.0, 1 / steps, steps)
|
|
38
|
+
start = min(steps - 1, max(0, int(round((1.0 - strength) * steps))))
|
|
39
|
+
|
|
40
|
+
vae_scale = pipe.vae_scale_factor * pipe.patch_size
|
|
41
|
+
grid_h, grid_w = height // vae_scale, width // vae_scale
|
|
42
|
+
if pipe.config.is_distilled:
|
|
43
|
+
mu = 1.15
|
|
44
|
+
else:
|
|
45
|
+
config = pipe.scheduler.config
|
|
46
|
+
mu = calculate_shift(
|
|
47
|
+
grid_h * grid_w,
|
|
48
|
+
config.get("base_image_seq_len", 256),
|
|
49
|
+
config.get("max_image_seq_len", 6400),
|
|
50
|
+
config.get("base_shift", 0.5),
|
|
51
|
+
config.get("max_shift", 1.15),
|
|
52
|
+
)
|
|
53
|
+
schedule = pipe.scheduler.__class__.from_config(pipe.scheduler.config)
|
|
54
|
+
schedule.set_timesteps(sigmas=raw, mu=mu, device=device)
|
|
55
|
+
sigma = schedule.sigmas[start].to(device)
|
|
56
|
+
|
|
57
|
+
latents = _encode_image(pipe, image, width, height, device, generator)
|
|
58
|
+
noise = torch.randn(
|
|
59
|
+
latents.shape, generator=generator, device=latents.device, dtype=latents.dtype
|
|
60
|
+
)
|
|
61
|
+
return {"latents": (1.0 - sigma) * latents + sigma * noise, "sigmas": raw[start:].tolist()}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _encode_image(
|
|
65
|
+
pipe: Any, image: Any, width: int, height: int, device: str, generator: Any
|
|
66
|
+
) -> Any:
|
|
67
|
+
"""The input image as packed, normalized Krea 2 latents. The Qwen-Image VAE is a video codec, so
|
|
68
|
+
the pixels carry a length-1 temporal axis. The VAE sample is drawn from the run's seeded
|
|
69
|
+
generator, not the global RNG, so the same seed reproduces the same img2img result."""
|
|
70
|
+
import numpy as np
|
|
71
|
+
|
|
72
|
+
resized = image.convert("RGB").resize((width, height))
|
|
73
|
+
array = np.asarray(resized, dtype="float32") / 127.5 - 1.0
|
|
74
|
+
dtype = pipe.vae.dtype
|
|
75
|
+
pixels = torch.from_numpy(array).permute(2, 0, 1)[None, :, None].to(device, dtype)
|
|
76
|
+
|
|
77
|
+
with torch.no_grad():
|
|
78
|
+
latents = pipe.vae.encode(pixels).latent_dist.sample(generator=generator)
|
|
79
|
+
mean, std = _latent_stats(pipe.vae, latents)
|
|
80
|
+
latents = ((latents - mean) / std).squeeze(2) # drop the temporal axis
|
|
81
|
+
batch, channels, latent_h, latent_w = latents.shape
|
|
82
|
+
return pipe._pack_latents(latents, batch, channels, latent_h, latent_w)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _latent_stats(vae: Any, latents: Any) -> tuple[Any, Any]:
|
|
86
|
+
shape = (1, vae.config.z_dim, 1, 1, 1)
|
|
87
|
+
mean = torch.tensor(vae.config.latents_mean, device=latents.device, dtype=latents.dtype)
|
|
88
|
+
std = torch.tensor(vae.config.latents_std, device=latents.device, dtype=latents.dtype)
|
|
89
|
+
return mean.view(shape), std.view(shape)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Krea 2's answer to "what do I need on disk" - one provider per node variant."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ...config import models_dir
|
|
9
|
+
from ..requirements import ModelComponent
|
|
10
|
+
from .requirements import (
|
|
11
|
+
footprint_bytes,
|
|
12
|
+
krea2_requirements,
|
|
13
|
+
resolve_diffusion,
|
|
14
|
+
resolve_text_encoder,
|
|
15
|
+
resolve_vae,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Krea2Provider:
|
|
20
|
+
"""Requirements + fit estimate for one Krea 2 node (``turbo`` or ``raw``)."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, variant: str) -> None:
|
|
23
|
+
self._variant = variant
|
|
24
|
+
|
|
25
|
+
def components(self, params: dict[str, object] | None = None) -> list[ModelComponent]:
|
|
26
|
+
return krea2_requirements(self._variant, params)
|
|
27
|
+
|
|
28
|
+
def download_target(self, component: ModelComponent) -> Path:
|
|
29
|
+
return models_dir() / component.category
|
|
30
|
+
|
|
31
|
+
def estimate(self, policy: Any) -> dict[str, Any] | None:
|
|
32
|
+
"""Whether the model will fit this machine, so the popup can warn before a 26GB load.
|
|
33
|
+
``None`` whenever it can't be sized - a wrong estimate is worse than none."""
|
|
34
|
+
if policy is None:
|
|
35
|
+
return None
|
|
36
|
+
try:
|
|
37
|
+
from ...device.policy import ModelFootprint
|
|
38
|
+
except ImportError:
|
|
39
|
+
return None
|
|
40
|
+
footprint = ModelFootprint(
|
|
41
|
+
**footprint_bytes(
|
|
42
|
+
resolve_diffusion(self._variant), resolve_vae(None), resolve_text_encoder(None)
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
fit = policy.estimate_fit(footprint) # pure - never mutates the shared policy
|
|
46
|
+
if fit is None:
|
|
47
|
+
return None
|
|
48
|
+
soft = not fit.fits or fit.plan in ("int8", "offload")
|
|
49
|
+
return {
|
|
50
|
+
"plan": fit.plan,
|
|
51
|
+
"fits": fit.fits,
|
|
52
|
+
"requiredVramMb": int(fit.required_vram_gb * 1024),
|
|
53
|
+
"totalVramMb": int(fit.total_vram_gb * 1024) if fit.total_vram_gb else None,
|
|
54
|
+
"freeVramMb": policy.free_vram_mb(),
|
|
55
|
+
"freeRamMb": policy.free_ram_mb(),
|
|
56
|
+
"warning": fit.note if soft else None,
|
|
57
|
+
}
|