inline-core 1.2.3__tar.gz → 1.2.5__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.3 → inline_core-1.2.5}/PKG-INFO +37 -13
- {inline_core-1.2.3 → inline_core-1.2.5}/README.md +25 -10
- {inline_core-1.2.3 → inline_core-1.2.5}/pyproject.toml +19 -4
- inline_core-1.2.5/src/inline_core/ffmpeg.py +30 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/graph/loader_runners.py +32 -1
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/graph/primitives.py +16 -1
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/graph/schema.py +1 -0
- inline_core-1.2.5/src/inline_core/models/checkpoint.py +87 -0
- inline_core-1.2.5/src/inline_core/models/krea2/__init__.py +9 -0
- inline_core-1.2.5/src/inline_core/models/krea2/convert.py +138 -0
- inline_core-1.2.5/src/inline_core/models/krea2/img2img.py +89 -0
- inline_core-1.2.5/src/inline_core/models/krea2/provider.py +57 -0
- inline_core-1.2.5/src/inline_core/models/krea2/requirements.py +180 -0
- inline_core-1.2.5/src/inline_core/models/krea2/runner.py +407 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/models/loaders.py +381 -34
- inline_core-1.2.5/src/inline_core/models/lora.py +190 -0
- inline_core-1.2.5/src/inline_core/models/pipeline_runtime.py +522 -0
- inline_core-1.2.5/src/inline_core/models/zimage/runner.py +465 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/server/app.py +9 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/server/bootstrap.py +10 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/server/rpc.py +5 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/handlers.py +37 -2
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/moodboard.py +106 -23
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/schema.py +59 -3
- inline_core-1.2.5/src/inline_core/studio/system_stats.py +92 -0
- inline_core-1.2.5/src/inline_core/studio/training.py +477 -0
- inline_core-1.2.5/src/inline_core/studio/training_store.py +274 -0
- inline_core-1.2.5/src/inline_core/training/__init__.py +10 -0
- inline_core-1.2.5/src/inline_core/training/__main__.py +74 -0
- inline_core-1.2.5/src/inline_core/training/arch.py +184 -0
- inline_core-1.2.5/src/inline_core/training/caption.py +127 -0
- inline_core-1.2.5/src/inline_core/training/dataset.py +158 -0
- inline_core-1.2.5/src/inline_core/training/models.py +288 -0
- inline_core-1.2.5/src/inline_core/training/protocol.py +56 -0
- inline_core-1.2.5/src/inline_core/training/trainer.py +236 -0
- inline_core-1.2.5/tests/test_checkpoint.py +79 -0
- inline_core-1.2.5/tests/test_krea2_convert.py +157 -0
- inline_core-1.2.5/tests/test_krea2_requirements.py +115 -0
- inline_core-1.2.5/tests/test_krea2_runner.py +163 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_loaders.py +15 -3
- inline_core-1.2.5/tests/test_lora.py +218 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_studio_rpc.py +3 -0
- inline_core-1.2.5/tests/test_studio_training.py +81 -0
- inline_core-1.2.5/tests/test_training_arch.py +113 -0
- inline_core-1.2.5/tests/test_training_dataset.py +103 -0
- inline_core-1.2.5/tests/test_training_models.py +106 -0
- inline_core-1.2.5/tests/test_training_resolve.py +90 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_zimage_runner.py +27 -24
- {inline_core-1.2.3 → inline_core-1.2.5}/uv.lock +130 -4
- {inline_core-1.2.3 → inline_core-1.2.5}/webui.sh +1 -1
- inline_core-1.2.3/src/inline_core/models/zimage/runner.py +0 -945
- {inline_core-1.2.3 → inline_core-1.2.5}/.gitignore +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/.python-version +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/CLAUDE.md +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/main.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/scripts/reference.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/components/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/components/conditioning.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/components/interfaces.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/config.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/device/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/device/auto.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/device/detect.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/device/memory.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/device/policy.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/device/types.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/errors.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/api.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/constraints.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/fetch.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/handlers.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/importer.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/install.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/loader.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/manifest.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/models.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/paths.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/resolve.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/scanner.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/state.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/extensions/tools.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/graph/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/graph/cache.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/graph/descriptor.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/graph/executor.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/graph/registry.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/graph/runners.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/graph/topo.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/graph/validate.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/media.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/models/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/models/catalog.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/models/requirements.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/models/sampling.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/models/zimage/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/models/zimage/primitives.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/models/zimage/provider.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/models/zimage/requirements.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/parallel/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/parallel/config.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/parallel/group.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/parallel/launch.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/parallel/protocol.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/parallel/registry.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/parallel/worker.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/runtime/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/runtime/context.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/runtime/file_store.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/runtime/progress.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/runtime/run.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/runtime/store.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/sampling/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/sampling/batch.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/server/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/server/__main__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/server/assets.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/server/frontend.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/server/manager.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/server/run_store.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/server/serialize.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/assets.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/config.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/fal.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/frames.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/generation.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/graph_build.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/models.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/peaks.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/store.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/timeline/__init__.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/timeline/compose.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/timeline/ffmpeg.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/timeline/render.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/studio/timeline/resolve.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/src/inline_core/takes.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/helpers.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_cache.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_catalog.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_config.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_device_detect.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_executor.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_extension_api.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_extension_install.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_extension_manifest.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_extension_resolve.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_extension_scanner.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_extension_spine.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_extension_state.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_file_store.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_frontend_serving.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_hidden_nodes.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_loader_runners.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_memory_policy.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_model_requirements.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_parallel_group.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_primitives.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_rpc_bridge.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_run_store.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_sampling.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_schema.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_server.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_studio_assets.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_studio_fal.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_studio_frames.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_studio_generation.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_studio_models.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_studio_moodboard.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_studio_peaks.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_studio_schema.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_studio_store.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_studio_timeline.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_take_bytes.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_topo.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_validate.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_xfuser_sampler.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_zimage_primitives.py +0 -0
- {inline_core-1.2.3 → inline_core-1.2.5}/tests/test_zimage_resolve.py +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.5
|
|
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'
|
|
@@ -38,6 +39,14 @@ Provides-Extra: server
|
|
|
38
39
|
Requires-Dist: fastapi>=0.110; extra == 'server'
|
|
39
40
|
Requires-Dist: imageio-ffmpeg>=0.4; extra == 'server'
|
|
40
41
|
Requires-Dist: uvicorn[standard]>=0.29; extra == 'server'
|
|
42
|
+
Provides-Extra: training
|
|
43
|
+
Requires-Dist: bitsandbytes>=0.43; (platform_system != 'Darwin') and extra == 'training'
|
|
44
|
+
Requires-Dist: einops>=0.7; extra == 'training'
|
|
45
|
+
Requires-Dist: nvidia-ml-py>=12; extra == 'training'
|
|
46
|
+
Requires-Dist: peft>=0.11; extra == 'training'
|
|
47
|
+
Requires-Dist: pillow>=10; extra == 'training'
|
|
48
|
+
Requires-Dist: psutil>=5.9; extra == 'training'
|
|
49
|
+
Requires-Dist: timm>=1.0; extra == 'training'
|
|
41
50
|
Description-Content-Type: text/markdown
|
|
42
51
|
|
|
43
52
|
# Inline Core
|
|
@@ -47,8 +56,9 @@ The generation engine behind Inline. It takes a typed node graph (JSON) and retu
|
|
|
47
56
|
low-VRAM laptops up to multi-GPU machines that split a single image's sampling across GPUs (via
|
|
48
57
|
xDiT). It is Inline Studio's built-in render backend.
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
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.
|
|
52
62
|
|
|
53
63
|
> Status: early, and running end to end against a stub engine. In place and tested: the graph engine,
|
|
54
64
|
> the typed `/v1` HTTP + websocket API (durable runs, streamed progress, coalescing), the model-dir
|
|
@@ -94,9 +104,13 @@ by category:
|
|
|
94
104
|
|
|
95
105
|
```
|
|
96
106
|
models/
|
|
97
|
-
diffusion_models/ z_image_turbo_bf16.safetensors
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
100
114
|
loras/ controlnet/ checkpoints/ ...
|
|
101
115
|
```
|
|
102
116
|
|
|
@@ -115,8 +129,17 @@ paths:
|
|
|
115
129
|
`models/`** (never the hidden HF cache) with visible progress.
|
|
116
130
|
|
|
117
131
|
Override the diffusion source with `INLINE_ZIMAGE_MODEL` (a file or a diffusers dir), and the supporting
|
|
118
|
-
components with `INLINE_ZIMAGE_VAE` / `INLINE_ZIMAGE_TEXT_ENCODER
|
|
119
|
-
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.
|
|
120
143
|
|
|
121
144
|
## Nodes
|
|
122
145
|
|
|
@@ -124,8 +147,9 @@ start; a node's model pickers list what is present.
|
|
|
124
147
|
renders any node generically - adding a node type needs no UI release.
|
|
125
148
|
|
|
126
149
|
**High-level model nodes are what the user sees.** Generation is one-click: you drop a single
|
|
127
|
-
**Z-Image Turbo** node, wire a Prompt into it, and hit Run. The
|
|
128
|
-
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.
|
|
129
153
|
|
|
130
154
|
Underneath, a **low-level primitive vocabulary** exists (`load/diffusion-model`, `load/vae`,
|
|
131
155
|
`load/text-encoder`, `encode/text`, `latent/empty`, `sample`, `vae/decode`, `vae/encode`) - the
|
|
@@ -214,7 +238,7 @@ The easy path is `webui.sh`, which maps friendly flags onto the engine's `INLINE
|
|
|
214
238
|
./webui.sh --listen --port 9000 # bind all interfaces on 9000
|
|
215
239
|
./webui.sh --multi-gpu # split one image across GPUs (auto with 2+ GPUs)
|
|
216
240
|
./webui.sh --lowvram # tight-VRAM profile
|
|
217
|
-
./webui.sh --install --extra
|
|
241
|
+
./webui.sh --install --extra runtime # set up ./.venv with the model runtime, then exit
|
|
218
242
|
```
|
|
219
243
|
|
|
220
244
|
`./webui.sh --help` lists every flag (networking, multi-GPU, device/memory profile, paths). The same
|
|
@@ -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
|
|
@@ -172,7 +187,7 @@ The easy path is `webui.sh`, which maps friendly flags onto the engine's `INLINE
|
|
|
172
187
|
./webui.sh --listen --port 9000 # bind all interfaces on 9000
|
|
173
188
|
./webui.sh --multi-gpu # split one image across GPUs (auto with 2+ GPUs)
|
|
174
189
|
./webui.sh --lowvram # tight-VRAM profile
|
|
175
|
-
./webui.sh --install --extra
|
|
190
|
+
./webui.sh --install --extra runtime # set up ./.venv with the model runtime, then exit
|
|
176
191
|
```
|
|
177
192
|
|
|
178
193
|
`./webui.sh --help` lists every flag (networking, multi-GPU, device/memory profile, paths). The same
|
|
@@ -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.5"
|
|
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",
|
|
@@ -39,6 +40,20 @@ parallel = [
|
|
|
39
40
|
"xfuser>=0.4",
|
|
40
41
|
"nvidia-ml-py>=12",
|
|
41
42
|
]
|
|
43
|
+
# LoRA training (the Trainer tab): PEFT adapter training + local auto-caption + host/GPU telemetry.
|
|
44
|
+
# Install alongside `runtime`: `.[runtime,training]`. Reuses runtime's torch/diffusers/accelerate.
|
|
45
|
+
training = [
|
|
46
|
+
"peft>=0.11",
|
|
47
|
+
# 8-bit Adam keeps optimizer state small; no macOS wheels, so skip it there (AdamW fallback).
|
|
48
|
+
"bitsandbytes>=0.43; platform_system != 'Darwin'",
|
|
49
|
+
"Pillow>=10",
|
|
50
|
+
# Telemetry (CPU/RAM + per-GPU NVML) for the Trainer tab.
|
|
51
|
+
"psutil>=5.9",
|
|
52
|
+
"nvidia-ml-py>=12",
|
|
53
|
+
# Florence-2 auto-captioner is loaded via trust_remote_code and needs these.
|
|
54
|
+
"timm>=1.0",
|
|
55
|
+
"einops>=0.7",
|
|
56
|
+
]
|
|
42
57
|
dev = [
|
|
43
58
|
"pytest>=8",
|
|
44
59
|
"ruff>=0.6",
|
|
@@ -48,7 +63,7 @@ dev = [
|
|
|
48
63
|
# Everything needed to run locally. Lists concrete packages, never `inline-core[...]` self-refs.
|
|
49
64
|
all = [
|
|
50
65
|
"torch>=2.2",
|
|
51
|
-
"diffusers>=0.
|
|
66
|
+
"diffusers>=0.39",
|
|
52
67
|
"transformers>=4.44",
|
|
53
68
|
"accelerate>=0.30",
|
|
54
69
|
"safetensors>=0.4",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Locate the ffmpeg/ffprobe binaries. Prefers a bundled ``imageio-ffmpeg``, else PATH.
|
|
2
|
+
|
|
3
|
+
Lives at the top level rather than under ``studio/`` because both the timeline (studio) and the
|
|
4
|
+
take store (runtime) need it, and runtime must not import studio.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import shutil
|
|
10
|
+
from functools import lru_cache
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@lru_cache(maxsize=1)
|
|
14
|
+
def ffmpeg_exe() -> str | None:
|
|
15
|
+
try:
|
|
16
|
+
import imageio_ffmpeg
|
|
17
|
+
|
|
18
|
+
return imageio_ffmpeg.get_ffmpeg_exe()
|
|
19
|
+
except Exception: # noqa: BLE001
|
|
20
|
+
return shutil.which("ffmpeg")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@lru_cache(maxsize=1)
|
|
24
|
+
def ffprobe_exe() -> str | None:
|
|
25
|
+
"""PATH only - imageio bundles ffmpeg alone, so probing degrades gracefully when absent."""
|
|
26
|
+
return shutil.which("ffprobe")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def ffmpeg_available() -> bool:
|
|
30
|
+
return ffmpeg_exe() is not None
|
|
@@ -20,7 +20,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
20
20
|
|
|
21
21
|
from ..config import models_dir
|
|
22
22
|
from ..errors import ComponentError
|
|
23
|
-
from .primitives import LOAD_DIFFUSION_MODEL, LOAD_TEXT_ENCODER, LOAD_VAE
|
|
23
|
+
from .primitives import LOAD_DIFFUSION_MODEL, LOAD_LORA, LOAD_TEXT_ENCODER, LOAD_VAE
|
|
24
24
|
from .runners import NodeResult, NodeRunner
|
|
25
25
|
from .schema import Node
|
|
26
26
|
|
|
@@ -44,6 +44,16 @@ class ComponentRef:
|
|
|
44
44
|
file: str
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class LoraRef:
|
|
49
|
+
"""One LoRA in a stack: its absolute file path and blend strength. A ``load/lora`` node emits a
|
|
50
|
+
tuple of these (its own ref appended to any upstream stack); the model runner fuses them into
|
|
51
|
+
the diffusion transformer in order. Frozen + hashable so it can key the loader cache."""
|
|
52
|
+
|
|
53
|
+
file: str
|
|
54
|
+
strength: float
|
|
55
|
+
|
|
56
|
+
|
|
47
57
|
def _resolve_file(category: str, chosen: str) -> Path:
|
|
48
58
|
"""The single weight file a Load node points at: the explicit dropdown pick, else the first
|
|
49
59
|
weight file in ``models/<category>/`` (mirrors the model node's "auto"). Raises if none."""
|
|
@@ -81,6 +91,26 @@ class LoadComponentRunner(NodeRunner):
|
|
|
81
91
|
return NodeResult(outputs={self._output: ref})
|
|
82
92
|
|
|
83
93
|
|
|
94
|
+
class LoadLoraRunner(NodeRunner):
|
|
95
|
+
"""Resolve this node's ``file``/``strength`` into a ``LoraRef`` and append it to any upstream
|
|
96
|
+
stack on the ``lora`` input - so chaining ``load/lora`` nodes stacks them in wiring order. The
|
|
97
|
+
stack rides its own ``lora`` edge into the model runner, which fuses it into the transformer."""
|
|
98
|
+
|
|
99
|
+
produces_takes = False
|
|
100
|
+
|
|
101
|
+
def run(self, node: Node, inputs: dict[str, list[Any]], ctx: ExecutionContext) -> NodeResult:
|
|
102
|
+
upstream = _first(inputs.get("lora")) or ()
|
|
103
|
+
file = _resolve_file("loras", str(node.params.get("file", "")))
|
|
104
|
+
strength = float(node.params.get("strength", 1.0))
|
|
105
|
+
stack = (*upstream, LoraRef(file=str(file), strength=strength))
|
|
106
|
+
return NodeResult(outputs={"lora": stack})
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _first(values: list[Any] | None) -> Any:
|
|
110
|
+
"""The first wired value on a port, or None (an optional input may be absent/unconnected)."""
|
|
111
|
+
return values[0] if values else None
|
|
112
|
+
|
|
113
|
+
|
|
84
114
|
def register_loaders(registry: Registry) -> None:
|
|
85
115
|
"""Register the ``load/*`` nodes **visible** (unhidden) with their runners, so they appear in
|
|
86
116
|
the add-node menu and can feed a model node's component inputs. Torch-free - always on."""
|
|
@@ -98,3 +128,4 @@ def register_loaders(registry: Registry) -> None:
|
|
|
98
128
|
kind="text_encoder", category="text_encoders", output_port="text_encoder"
|
|
99
129
|
),
|
|
100
130
|
)
|
|
131
|
+
registry.register(replace(LOAD_LORA, hidden=False), LoadLoraRunner())
|
|
@@ -47,6 +47,20 @@ LOAD_TEXT_ENCODER = NodeDescriptor(
|
|
|
47
47
|
outputs=(Port("text_encoder", "Text encoder", PortKind.TEXT_ENCODER),),
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
+
LOAD_LORA = NodeDescriptor(
|
|
51
|
+
type="load/lora",
|
|
52
|
+
title="Load LoRA",
|
|
53
|
+
category="Loaders",
|
|
54
|
+
icon="box",
|
|
55
|
+
# Optional upstream lora: chain load/lora -> load/lora to stack several in order.
|
|
56
|
+
inputs=(Port("lora", "LoRA", PortKind.LORA, required=False),),
|
|
57
|
+
params=(
|
|
58
|
+
ParamField("file", "LoRA", Widget.SELECT, "", options_from="loras"),
|
|
59
|
+
ParamField("strength", "Strength", Widget.NUMBER, 1.0, min=-2.0, max=2.0, step=0.05),
|
|
60
|
+
),
|
|
61
|
+
outputs=(Port("lora", "LoRA", PortKind.LORA),),
|
|
62
|
+
)
|
|
63
|
+
|
|
50
64
|
ENCODE_TEXT = NodeDescriptor(
|
|
51
65
|
type="encode/text",
|
|
52
66
|
title="Encode Text",
|
|
@@ -122,6 +136,7 @@ PRIMITIVES: tuple[NodeDescriptor, ...] = (
|
|
|
122
136
|
LOAD_DIFFUSION_MODEL,
|
|
123
137
|
LOAD_VAE,
|
|
124
138
|
LOAD_TEXT_ENCODER,
|
|
139
|
+
LOAD_LORA,
|
|
125
140
|
ENCODE_TEXT,
|
|
126
141
|
EMPTY_LATENT,
|
|
127
142
|
SAMPLE,
|
|
@@ -132,7 +147,7 @@ PRIMITIVES: tuple[NodeDescriptor, ...] = (
|
|
|
132
147
|
|
|
133
148
|
#: The loader primitives now have runners and are offered in the add-node menu - registered
|
|
134
149
|
#: (unhidden) with their runners by ``graph/loader_runners.py``, so they are skipped here.
|
|
135
|
-
_HAS_RUNNER = {LOAD_DIFFUSION_MODEL.type, LOAD_VAE.type, LOAD_TEXT_ENCODER.type}
|
|
150
|
+
_HAS_RUNNER = {LOAD_DIFFUSION_MODEL.type, LOAD_VAE.type, LOAD_TEXT_ENCODER.type, LOAD_LORA.type}
|
|
136
151
|
|
|
137
152
|
|
|
138
153
|
def register_primitives(registry: Registry) -> None:
|
|
@@ -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
|