inline-core 1.2.5__tar.gz → 1.2.6__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.5 → inline_core-1.2.6}/CLAUDE.md +33 -4
- {inline_core-1.2.5 → inline_core-1.2.6}/PKG-INFO +24 -7
- {inline_core-1.2.5 → inline_core-1.2.6}/README.md +12 -6
- {inline_core-1.2.5 → inline_core-1.2.6}/pyproject.toml +24 -3
- inline_core-1.2.6/src/inline_core/__init__.py +14 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/device/memory.py +16 -4
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/device/policy.py +5 -1
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/graph/cache.py +35 -2
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/graph/executor.py +16 -4
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/graph/schema.py +5 -1
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/catalog.py +21 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/checkpoint.py +5 -0
- inline_core-1.2.6/src/inline_core/models/controlspace.py +26 -0
- inline_core-1.2.6/src/inline_core/models/flux2/__init__.py +1 -0
- inline_core-1.2.6/src/inline_core/models/flux2/controlnet.py +234 -0
- inline_core-1.2.6/src/inline_core/models/flux2/embeds.py +165 -0
- inline_core-1.2.6/src/inline_core/models/flux2/provider.py +84 -0
- inline_core-1.2.6/src/inline_core/models/flux2/requirements.py +414 -0
- inline_core-1.2.6/src/inline_core/models/flux2/runner.py +677 -0
- inline_core-1.2.6/src/inline_core/models/flux2/variants.py +334 -0
- inline_core-1.2.6/src/inline_core/models/krea2/depth_control.py +149 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/krea2/img2img.py +2 -2
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/krea2/provider.py +11 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/krea2/requirements.py +75 -1
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/krea2/runner.py +65 -6
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/loaders.py +574 -7
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/pipeline_runtime.py +60 -19
- inline_core-1.2.6/src/inline_core/models/preprocess/__init__.py +5 -0
- inline_core-1.2.6/src/inline_core/models/preprocess/requirements.py +59 -0
- inline_core-1.2.6/src/inline_core/models/preprocess/runner.py +174 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/requirements.py +47 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/zimage/provider.py +12 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/zimage/requirements.py +90 -4
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/zimage/runner.py +72 -8
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/server/app.py +30 -4
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/server/bootstrap.py +25 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/server/serialize.py +28 -4
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/fal.py +43 -5
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/frames.py +44 -18
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/generation.py +38 -8
- inline_core-1.2.6/src/inline_core/studio/graph_build.py +326 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/handlers.py +48 -9
- inline_core-1.2.6/src/inline_core/studio/image_meta.py +36 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/models.py +6 -2
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/moodboard.py +52 -3
- inline_core-1.2.6/src/inline_core/studio/recipe.py +109 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/schema.py +8 -2
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/training.py +10 -2
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/training/arch.py +107 -4
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/training/caption.py +39 -6
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/training/dataset.py +54 -3
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/training/models.py +132 -2
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/training/trainer.py +28 -3
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/helpers.py +1 -0
- inline_core-1.2.6/tests/test_cache.py +116 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_executor.py +26 -1
- inline_core-1.2.6/tests/test_flux2_controlnet.py +159 -0
- inline_core-1.2.6/tests/test_flux2_folder.py +179 -0
- inline_core-1.2.6/tests/test_flux2_resolve.py +176 -0
- inline_core-1.2.6/tests/test_flux2_runner.py +126 -0
- inline_core-1.2.6/tests/test_flux2_training.py +199 -0
- inline_core-1.2.6/tests/test_flux2_variants.py +129 -0
- inline_core-1.2.6/tests/test_krea2_depth_control.py +104 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_krea2_requirements.py +35 -2
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_krea2_runner.py +4 -3
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_loaders.py +35 -0
- inline_core-1.2.6/tests/test_lora_download.py +75 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_memory_policy.py +39 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_model_requirements.py +25 -0
- inline_core-1.2.6/tests/test_pipeline_cache.py +82 -0
- inline_core-1.2.6/tests/test_recipe.py +100 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_fal.py +46 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_generation.py +41 -3
- inline_core-1.2.6/tests/test_studio_graph_build.py +111 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_models.py +20 -0
- inline_core-1.2.6/tests/test_studio_multi_reference.py +163 -0
- inline_core-1.2.6/tests/test_studio_node_size.py +58 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_rpc.py +13 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_schema.py +19 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_training_models.py +29 -0
- inline_core-1.2.6/tests/test_webui_install.py +154 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_zimage_resolve.py +6 -2
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_zimage_runner.py +109 -13
- {inline_core-1.2.5 → inline_core-1.2.6}/uv.lock +258 -1
- inline_core-1.2.6/webui.bat +319 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/webui.sh +96 -18
- inline_core-1.2.5/src/inline_core/__init__.py +0 -7
- inline_core-1.2.5/src/inline_core/studio/graph_build.py +0 -141
- inline_core-1.2.5/tests/test_cache.py +0 -48
- {inline_core-1.2.5 → inline_core-1.2.6}/.gitignore +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/.python-version +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/main.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/scripts/reference.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/components/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/components/conditioning.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/components/interfaces.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/config.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/device/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/device/auto.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/device/detect.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/device/types.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/errors.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/api.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/constraints.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/fetch.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/handlers.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/importer.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/install.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/loader.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/manifest.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/models.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/paths.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/resolve.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/scanner.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/state.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/extensions/tools.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/ffmpeg.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/graph/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/graph/descriptor.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/graph/loader_runners.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/graph/primitives.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/graph/registry.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/graph/runners.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/graph/topo.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/graph/validate.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/media.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/krea2/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/krea2/convert.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/lora.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/sampling.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/zimage/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/models/zimage/primitives.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/parallel/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/parallel/config.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/parallel/group.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/parallel/launch.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/parallel/protocol.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/parallel/registry.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/parallel/worker.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/runtime/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/runtime/context.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/runtime/file_store.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/runtime/progress.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/runtime/run.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/runtime/store.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/sampling/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/sampling/batch.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/server/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/server/__main__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/server/assets.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/server/frontend.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/server/manager.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/server/rpc.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/server/run_store.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/assets.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/config.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/peaks.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/store.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/system_stats.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/timeline/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/timeline/compose.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/timeline/ffmpeg.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/timeline/render.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/timeline/resolve.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/studio/training_store.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/takes.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/training/__init__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/training/__main__.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/src/inline_core/training/protocol.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_catalog.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_checkpoint.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_config.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_device_detect.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_extension_api.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_extension_install.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_extension_manifest.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_extension_resolve.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_extension_scanner.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_extension_spine.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_extension_state.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_file_store.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_frontend_serving.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_hidden_nodes.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_krea2_convert.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_loader_runners.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_lora.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_parallel_group.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_primitives.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_rpc_bridge.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_run_store.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_sampling.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_schema.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_server.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_assets.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_frames.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_moodboard.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_peaks.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_store.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_timeline.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_studio_training.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_take_bytes.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_topo.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_training_arch.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_training_dataset.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_training_resolve.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_validate.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_xfuser_sampler.py +0 -0
- {inline_core-1.2.5 → inline_core-1.2.6}/tests/test_zimage_primitives.py +0 -0
|
@@ -95,7 +95,16 @@ HTTP/WS → server/app.py → RunManager → Executor → Registry (desc
|
|
|
95
95
|
the ComfyUI-equivalent decomposed graph and the intended long-term surface. **Descriptor-only
|
|
96
96
|
today - their runners land in C2.** A graph built from them validates and type-checks but raises
|
|
97
97
|
`No runner registered` at execution.
|
|
98
|
-
- **Model runners** (`models/<name>/`) - high-level single generation nodes. **`
|
|
98
|
+
- **Model runners** (`models/<name>/`) - high-level single generation nodes. **`black-forest-labs/flux-2`
|
|
99
|
+
(`models/flux2/`) is the reference for a _family_:** one node covers klein 4B/9B, their base builds,
|
|
100
|
+
the KV variant and dev, because the picked checkpoint is identified from its own tensor shapes
|
|
101
|
+
(`flux2/variants.py`) and everything else follows. Adding a checkpoint is a row in that table.
|
|
102
|
+
Two rules it establishes: **identify a checkpoint by content, never by filename** (`diffusion_models/`
|
|
103
|
+
is shared across architectures, and Qwen3-4B and Qwen3-VL-4B have byte-identical embedding shapes),
|
|
104
|
+
and **derive geometry from the checkpoint** rather than bundling a config per variant, so a future
|
|
105
|
+
build loads with no code change. A **diffusers folder** is a valid checkpoint too, which is the only
|
|
106
|
+
way a 32B model reaches a 24 GB card: its NF4 shards stream through `from_pretrained`, where
|
|
107
|
+
`from_single_file` cannot quantize at all. **`alibaba/z-image-turbo`
|
|
99
108
|
(`models/zimage/`) is the one runnable generation path today** (prompt + optional image → one take,
|
|
100
109
|
backed by diffusers' `ZImagePipeline`/`ZImageImg2ImgPipeline`). This is the "Z-Image pipeline that
|
|
101
110
|
already works" - the primitives will reach parity in C2. It loads from a **single diffusion
|
|
@@ -143,6 +152,18 @@ between nodes and are never takes.
|
|
|
143
152
|
(Turing/Volta - compute capability < 8.0, e.g. a T4): same footprint, but it uses the fp16 tensor
|
|
144
153
|
cores instead of bf16's slow path. The **VAE stays upcast** (bf16, or fp32 when the denoiser is
|
|
145
154
|
fp16) because fp16 VAE decode can overflow to black/NaN images (`_compute_dtype` in `device/`).
|
|
155
|
+
- **Quantization rungs** - the fit ladder is full-precision resident → int8 resident → **NF4 resident**
|
|
156
|
+
→ sequential offload → wont-fit. NF4 is what makes a 32B model viable on a 24 GB card. Note int8
|
|
157
|
+
forces bf16 (torchao's weight-only int8 silently no-ops under fp16) while NF4 does not, so a Turing
|
|
158
|
+
card keeps its fp16 tensor cores under NF4.
|
|
159
|
+
- **Prequantized checkpoints must not be re-quantized.** A checkpoint that ships already quantized
|
|
160
|
+
(`flux2/variants.is_prequantized`) has an on-disk size that already _is_ its resident size, so the
|
|
161
|
+
ladder's assumption that quantization halves it does not hold, and handing diffusers a second,
|
|
162
|
+
different quantization config is a hard error. Pass `Quantization.NONE` for those.
|
|
163
|
+
- **Staged loading** - when the text encoder and the transformer cannot be co-resident (dev is a
|
|
164
|
+
15 GB encoder beside an 18 GB transformer), the prompt is encoded first and the encoder freed before
|
|
165
|
+
the transformer loads. The decision has to be made _before_ the load: by the time an OOM fires there
|
|
166
|
+
is nothing left to free.
|
|
146
167
|
- **Multi-GPU** - `INLINE_PARALLEL` (e.g. `pipefusion=2`, `pipefusion=2,ulysses=2`); degrees multiply
|
|
147
168
|
to the world size, which must equal the GPU count.
|
|
148
169
|
|
|
@@ -202,6 +223,11 @@ real codec that moves tensors lives with the model runner.
|
|
|
202
223
|
Don't scatter it.
|
|
203
224
|
- **Bring-your-own models.** Nothing is downloaded by the engine. The catalog scans; the user places
|
|
204
225
|
files. A model picker is a `SELECT` param with `options_from="<category>"`.
|
|
226
|
+
- **Verify image models by rendering.** The FLUX.2 work shipped five bugs past a green test suite,
|
|
227
|
+
and every one produced a _wrong image rather than an error_: a mis-keyed checkpoint, a
|
|
228
|
+
vision-language encoder loaded in place of a text one, an unnormalized latent, and a control context
|
|
229
|
+
cast to a quantized weight's `uint8` storage dtype. A unit test cannot see a plausible-but-wrong
|
|
230
|
+
image. Render something and look at it.
|
|
205
231
|
- **Tests (pytest).** Cover the logic that matters: graph validate/topo/executor/cache, the catalog
|
|
206
232
|
scan, the run store + server contract, the device/memory policy, the parallel group + xfuser seam,
|
|
207
233
|
and each model runner (import-guarded, no GPU needed). See `tests/`.
|
|
@@ -211,14 +237,17 @@ real codec that moves tensors lives with the model runner.
|
|
|
211
237
|
|
|
212
238
|
```
|
|
213
239
|
uv venv # create ./.venv
|
|
214
|
-
|
|
215
|
-
uv pip install -e ".[
|
|
216
|
-
uv pip install -e ".[runtime
|
|
240
|
+
# --python pins the target: without it uv installs into an activated venv/conda env, not ./.venv
|
|
241
|
+
uv pip install --python .venv/bin/python -e ".[server,dev]" # engine + server + test tooling
|
|
242
|
+
uv pip install --python .venv/bin/python -e ".[runtime]" # + torch, diffusers, transformers
|
|
243
|
+
uv pip install --python .venv/bin/python -e ".[runtime,parallel]" # + xfuser, multi-GPU denoise
|
|
217
244
|
|
|
218
245
|
./webui.sh # run (loopback:8848); friendly flags → INLINE_* env
|
|
219
246
|
./webui.sh --listen --port 9000 # bind all interfaces
|
|
220
247
|
./webui.sh --lowvram # tight-VRAM profile
|
|
221
248
|
./webui.sh --install --extra runtime # set up ./.venv with the model runtime, then exit
|
|
249
|
+
# (reuses an existing ./.venv; --recreate rebuilds it, and
|
|
250
|
+
# an activated foreign env is reported, never modified)
|
|
222
251
|
python -m inline_core.server # run the server directly (INLINE_HOST / INLINE_PORT)
|
|
223
252
|
|
|
224
253
|
ruff check . # lint (zero warnings)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: inline-core
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.6
|
|
4
4
|
Summary: The generation engine behind Inline Studio.
|
|
5
5
|
License-Expression: GPL-3.0-or-later
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -8,12 +8,21 @@ Requires-Dist: numpy>=1.26
|
|
|
8
8
|
Requires-Dist: psutil>=5.9
|
|
9
9
|
Provides-Extra: all
|
|
10
10
|
Requires-Dist: accelerate>=0.30; extra == 'all'
|
|
11
|
+
Requires-Dist: bitsandbytes>=0.43; (platform_system != 'Darwin') and extra == 'all'
|
|
12
|
+
Requires-Dist: controlnet-aux>=0.0.7; extra == 'all'
|
|
11
13
|
Requires-Dist: diffusers>=0.39; extra == 'all'
|
|
14
|
+
Requires-Dist: einops>=0.7; extra == 'all'
|
|
12
15
|
Requires-Dist: fastapi>=0.110; extra == 'all'
|
|
13
16
|
Requires-Dist: huggingface-hub>=0.23; extra == 'all'
|
|
14
17
|
Requires-Dist: imageio-ffmpeg>=0.4; extra == 'all'
|
|
18
|
+
Requires-Dist: nvidia-ml-py>=12; extra == 'all'
|
|
19
|
+
Requires-Dist: onnxruntime>=1.17; extra == 'all'
|
|
20
|
+
Requires-Dist: peft>=0.11; extra == 'all'
|
|
21
|
+
Requires-Dist: pillow>=10; extra == 'all'
|
|
22
|
+
Requires-Dist: psutil>=5.9; extra == 'all'
|
|
15
23
|
Requires-Dist: safetensors>=0.4; extra == 'all'
|
|
16
24
|
Requires-Dist: scipy>=1.11; extra == 'all'
|
|
25
|
+
Requires-Dist: timm>=1.0; extra == 'all'
|
|
17
26
|
Requires-Dist: torch>=2.2; extra == 'all'
|
|
18
27
|
Requires-Dist: torchao>=0.14; extra == 'all'
|
|
19
28
|
Requires-Dist: transformers>=4.44; extra == 'all'
|
|
@@ -28,8 +37,10 @@ Requires-Dist: nvidia-ml-py>=12; extra == 'parallel'
|
|
|
28
37
|
Requires-Dist: xfuser>=0.4; extra == 'parallel'
|
|
29
38
|
Provides-Extra: runtime
|
|
30
39
|
Requires-Dist: accelerate>=0.30; extra == 'runtime'
|
|
40
|
+
Requires-Dist: controlnet-aux>=0.0.7; extra == 'runtime'
|
|
31
41
|
Requires-Dist: diffusers>=0.39; extra == 'runtime'
|
|
32
42
|
Requires-Dist: huggingface-hub>=0.23; extra == 'runtime'
|
|
43
|
+
Requires-Dist: onnxruntime>=1.17; extra == 'runtime'
|
|
33
44
|
Requires-Dist: safetensors>=0.4; extra == 'runtime'
|
|
34
45
|
Requires-Dist: scipy>=1.11; extra == 'runtime'
|
|
35
46
|
Requires-Dist: torch>=2.2; extra == 'runtime'
|
|
@@ -90,13 +101,18 @@ a 6 GB laptop, pure CPU, or split across several GPUs, without touching the grap
|
|
|
90
101
|
|
|
91
102
|
Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/).
|
|
92
103
|
|
|
104
|
+
`--python` pins each install to `./.venv`; without it uv targets whatever venv or conda env is
|
|
105
|
+
activated in your shell, and installs land there instead.
|
|
106
|
+
|
|
93
107
|
```
|
|
94
108
|
uv venv
|
|
95
|
-
uv pip install -e ".[server]"
|
|
96
|
-
uv pip install -e ".[runtime]"
|
|
97
|
-
uv pip install -e ".[runtime,parallel]" # + xfuser,
|
|
109
|
+
uv pip install --python .venv/bin/python -e ".[server]" # engine + HTTP/websocket API
|
|
110
|
+
uv pip install --python .venv/bin/python -e ".[runtime]" # + torch, diffusers, transformers
|
|
111
|
+
uv pip install --python .venv/bin/python -e ".[runtime,parallel]" # + xfuser, 2+ GPUs
|
|
98
112
|
```
|
|
99
113
|
|
|
114
|
+
Or let the launcher do it: `./webui.sh --install --extra runtime` (Windows: `.\webui.bat`).
|
|
115
|
+
|
|
100
116
|
## Models
|
|
101
117
|
|
|
102
118
|
Drop files into the models dir (default `./models`, override with `INLINE_MODELS_DIR`), ComfyUI-style,
|
|
@@ -231,17 +247,18 @@ offload machinery, `--vram-budget GB` / `INLINE_VRAM_BUDGET_GB` to cap the assum
|
|
|
231
247
|
|
|
232
248
|
## Run
|
|
233
249
|
|
|
234
|
-
The easy path is `webui.sh
|
|
250
|
+
The easy path is `webui.sh` (macOS/Linux) or `webui.bat` (Windows), which maps friendly flags onto the
|
|
251
|
+
engine's `INLINE_*` env knobs. Windows must use `webui.bat` - `webui.sh` is a bash script:
|
|
235
252
|
|
|
236
253
|
```
|
|
237
|
-
./webui.sh # loopback, port 8848
|
|
254
|
+
./webui.sh # loopback, port 8848 (Windows: .\webui.bat)
|
|
238
255
|
./webui.sh --listen --port 9000 # bind all interfaces on 9000
|
|
239
256
|
./webui.sh --multi-gpu # split one image across GPUs (auto with 2+ GPUs)
|
|
240
257
|
./webui.sh --lowvram # tight-VRAM profile
|
|
241
258
|
./webui.sh --install --extra runtime # set up ./.venv with the model runtime, then exit
|
|
242
259
|
```
|
|
243
260
|
|
|
244
|
-
`./webui.sh --help` lists every flag (networking, multi-GPU, device/memory profile, paths). The same
|
|
261
|
+
`./webui.sh --help` (or `.\webui.bat --help`) lists every flag (networking, multi-GPU, device/memory profile, paths). The same
|
|
245
262
|
flags are available on the Python entrypoint - `python main.py --help` - which is the dev path (it also
|
|
246
263
|
takes `--front-end-root DIR` to serve a local SPA build). Or run the server module directly:
|
|
247
264
|
|
|
@@ -39,13 +39,18 @@ a 6 GB laptop, pure CPU, or split across several GPUs, without touching the grap
|
|
|
39
39
|
|
|
40
40
|
Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/).
|
|
41
41
|
|
|
42
|
+
`--python` pins each install to `./.venv`; without it uv targets whatever venv or conda env is
|
|
43
|
+
activated in your shell, and installs land there instead.
|
|
44
|
+
|
|
42
45
|
```
|
|
43
46
|
uv venv
|
|
44
|
-
uv pip install -e ".[server]"
|
|
45
|
-
uv pip install -e ".[runtime]"
|
|
46
|
-
uv pip install -e ".[runtime,parallel]" # + xfuser,
|
|
47
|
+
uv pip install --python .venv/bin/python -e ".[server]" # engine + HTTP/websocket API
|
|
48
|
+
uv pip install --python .venv/bin/python -e ".[runtime]" # + torch, diffusers, transformers
|
|
49
|
+
uv pip install --python .venv/bin/python -e ".[runtime,parallel]" # + xfuser, 2+ GPUs
|
|
47
50
|
```
|
|
48
51
|
|
|
52
|
+
Or let the launcher do it: `./webui.sh --install --extra runtime` (Windows: `.\webui.bat`).
|
|
53
|
+
|
|
49
54
|
## Models
|
|
50
55
|
|
|
51
56
|
Drop files into the models dir (default `./models`, override with `INLINE_MODELS_DIR`), ComfyUI-style,
|
|
@@ -180,17 +185,18 @@ offload machinery, `--vram-budget GB` / `INLINE_VRAM_BUDGET_GB` to cap the assum
|
|
|
180
185
|
|
|
181
186
|
## Run
|
|
182
187
|
|
|
183
|
-
The easy path is `webui.sh
|
|
188
|
+
The easy path is `webui.sh` (macOS/Linux) or `webui.bat` (Windows), which maps friendly flags onto the
|
|
189
|
+
engine's `INLINE_*` env knobs. Windows must use `webui.bat` - `webui.sh` is a bash script:
|
|
184
190
|
|
|
185
191
|
```
|
|
186
|
-
./webui.sh # loopback, port 8848
|
|
192
|
+
./webui.sh # loopback, port 8848 (Windows: .\webui.bat)
|
|
187
193
|
./webui.sh --listen --port 9000 # bind all interfaces on 9000
|
|
188
194
|
./webui.sh --multi-gpu # split one image across GPUs (auto with 2+ GPUs)
|
|
189
195
|
./webui.sh --lowvram # tight-VRAM profile
|
|
190
196
|
./webui.sh --install --extra runtime # set up ./.venv with the model runtime, then exit
|
|
191
197
|
```
|
|
192
198
|
|
|
193
|
-
`./webui.sh --help` lists every flag (networking, multi-GPU, device/memory profile, paths). The same
|
|
199
|
+
`./webui.sh --help` (or `.\webui.bat --help`) lists every flag (networking, multi-GPU, device/memory profile, paths). The same
|
|
194
200
|
flags are available on the Python entrypoint - `python main.py --help` - which is the dev path (it also
|
|
195
201
|
takes `--front-end-root DIR` to serve a local SPA build). Or run the server module directly:
|
|
196
202
|
|
|
@@ -1,7 +1,7 @@
|
|
|
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.6"
|
|
5
5
|
description = "The generation engine behind Inline Studio."
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "GPL-3.0-or-later"
|
|
@@ -28,6 +28,10 @@ runtime = [
|
|
|
28
28
|
"scipy>=1.11",
|
|
29
29
|
# We call snapshot_download directly for the model popup, so pin it rather than rely on transit.
|
|
30
30
|
"huggingface_hub>=0.23",
|
|
31
|
+
# ControlNet preprocessors (the Apply ControlNet node): OpenPose/DWPose, MiDaS/Zoe depth, canny,
|
|
32
|
+
# HED, lineart, MLSD, scribble, normal. DWPose runs its detector on ONNX Runtime.
|
|
33
|
+
"controlnet-aux>=0.0.7",
|
|
34
|
+
"onnxruntime>=1.17",
|
|
31
35
|
]
|
|
32
36
|
server = [
|
|
33
37
|
"fastapi>=0.110",
|
|
@@ -60,8 +64,12 @@ dev = [
|
|
|
60
64
|
"pyright>=1.1",
|
|
61
65
|
"httpx>=0.27",
|
|
62
66
|
]
|
|
63
|
-
# Everything
|
|
67
|
+
# Everything a local install needs in one extra: runtime + server + training (the LoRA trainer),
|
|
68
|
+
# so `--install --extra all` is a single "install everything" command. Multi-GPU (xfuser, the
|
|
69
|
+
# `parallel` extra) is left out on purpose - it is niche and can fail to build on many systems.
|
|
70
|
+
# Lists concrete packages, never `inline-core[...]` self-refs.
|
|
64
71
|
all = [
|
|
72
|
+
# runtime
|
|
65
73
|
"torch>=2.2",
|
|
66
74
|
"diffusers>=0.39",
|
|
67
75
|
"transformers>=4.44",
|
|
@@ -70,9 +78,20 @@ all = [
|
|
|
70
78
|
"torchao>=0.14",
|
|
71
79
|
"scipy>=1.11",
|
|
72
80
|
"huggingface_hub>=0.23",
|
|
81
|
+
"controlnet-aux>=0.0.7",
|
|
82
|
+
"onnxruntime>=1.17",
|
|
83
|
+
# server
|
|
73
84
|
"fastapi>=0.110",
|
|
74
85
|
"uvicorn[standard]>=0.29",
|
|
75
86
|
"imageio-ffmpeg>=0.4",
|
|
87
|
+
# training (the Trainer tab)
|
|
88
|
+
"peft>=0.11",
|
|
89
|
+
"bitsandbytes>=0.43; platform_system != 'Darwin'",
|
|
90
|
+
"Pillow>=10",
|
|
91
|
+
"psutil>=5.9",
|
|
92
|
+
"nvidia-ml-py>=12",
|
|
93
|
+
"timm>=1.0",
|
|
94
|
+
"einops>=0.7",
|
|
76
95
|
]
|
|
77
96
|
|
|
78
97
|
# PyPI ships CPU-only torch on Windows; pin the CUDA index there (pip: see requirements.txt).
|
|
@@ -108,4 +127,6 @@ typeCheckingMode = "strict"
|
|
|
108
127
|
|
|
109
128
|
[tool.pytest.ini_options]
|
|
110
129
|
testpaths = ["tests"]
|
|
111
|
-
|
|
130
|
+
# "." so `tests` imports as a package: several suites share fixtures via `from tests.x import y`,
|
|
131
|
+
# and without it those modules fail to collect and silently stop running.
|
|
132
|
+
pythonpath = ["src", "."]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Inline Core: the generation engine behind Inline.
|
|
2
|
+
|
|
3
|
+
Takes a typed node graph and returns immutable takes. See PLAN.md for the architecture and
|
|
4
|
+
docs/contract.md for the Storyline API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
#: Resolved from the installed package, so pyproject.toml stays the only place a release is
|
|
11
|
+
#: bumped. An editable install records this at install time; reinstall after bumping.
|
|
12
|
+
__version__ = version("inline-core")
|
|
13
|
+
except PackageNotFoundError: # a source tree that was never installed
|
|
14
|
+
__version__ = "0.0.0"
|
|
@@ -47,6 +47,10 @@ _SMART_RESIDENT_MIN_VRAM_GB = 6.0
|
|
|
47
47
|
# ~half the fp16 weight bytes. Deliberately generous so the estimate errs toward a lighter plan.
|
|
48
48
|
_ACTIVATION_HEADROOM_GB = 2.5
|
|
49
49
|
_INT8_FACTOR = 0.5
|
|
50
|
+
# NF4 (bitsandbytes) stores 4-bit weights plus per-block scales, so ~0.55 bytes per parameter
|
|
51
|
+
# against fp16's 2. The rung exists for the very large checkpoints (FLUX.2 dev and friends) that
|
|
52
|
+
# int8 still cannot fit; it is CUDA-only and, like int8, never combined with CPU offload.
|
|
53
|
+
_NF4_FACTOR = 0.28
|
|
50
54
|
|
|
51
55
|
|
|
52
56
|
def _system_ram_gb() -> float | None:
|
|
@@ -202,9 +206,10 @@ class MemoryPolicy(DevicePolicy):
|
|
|
202
206
|
return None
|
|
203
207
|
cap = max(0.0, budget - _ACTIVATION_HEADROOM_GB)
|
|
204
208
|
big = (fp.diffusion_bytes + fp.text_encoder_bytes) / 1e9
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
209
|
+
# The VAE and a ControlNet are never quantized, so they cost the same under every plan.
|
|
210
|
+
fixed = (fp.vae_bytes + fp.controlnet_bytes) / 1e9
|
|
211
|
+
full = big + fixed
|
|
212
|
+
int8 = big * _INT8_FACTOR + fixed
|
|
208
213
|
forced = _env_profile() is not None # explicit --profile pins the profile; fit picks quant
|
|
209
214
|
|
|
210
215
|
def prof(auto: Profile) -> Profile:
|
|
@@ -221,7 +226,14 @@ class MemoryPolicy(DevicePolicy):
|
|
|
221
226
|
int8, budget, True,
|
|
222
227
|
"Weights are int8-quantized to fit this GPU's VRAM.",
|
|
223
228
|
)
|
|
224
|
-
|
|
229
|
+
nf4 = big * _NF4_FACTOR + fixed
|
|
230
|
+
if nf4 <= cap:
|
|
231
|
+
return FitEstimate(
|
|
232
|
+
"nf4", Quantization.NF4, OffloadMode.NONE, prof(Profile.LOWVRAM),
|
|
233
|
+
nf4, budget, True,
|
|
234
|
+
"Weights are 4-bit (NF4) quantized to fit this GPU's VRAM.",
|
|
235
|
+
)
|
|
236
|
+
# Even 4-bit won't fit resident -> CPU-offload streaming. Only viable if the (unquantized)
|
|
225
237
|
# model fits in system RAM, since sequential offload holds the off-GPU weights there.
|
|
226
238
|
ram = self._ram_gb
|
|
227
239
|
if ram is not None and full > ram:
|
|
@@ -87,10 +87,14 @@ class ModelFootprint:
|
|
|
87
87
|
diffusion_bytes: int = 0
|
|
88
88
|
text_encoder_bytes: int = 0
|
|
89
89
|
vae_bytes: int = 0
|
|
90
|
+
#: A ControlNet loaded alongside the denoiser. Never quantized, so it counts full in every plan.
|
|
91
|
+
controlnet_bytes: int = 0
|
|
90
92
|
|
|
91
93
|
@property
|
|
92
94
|
def total_bytes(self) -> int:
|
|
93
|
-
return
|
|
95
|
+
return (
|
|
96
|
+
self.diffusion_bytes + self.text_encoder_bytes + self.vae_bytes + self.controlnet_bytes
|
|
97
|
+
)
|
|
94
98
|
|
|
95
99
|
|
|
96
100
|
@dataclass(frozen=True)
|
|
@@ -12,7 +12,7 @@ from typing import Any
|
|
|
12
12
|
|
|
13
13
|
from ..takes import Take
|
|
14
14
|
from .registry import Registry
|
|
15
|
-
from .schema import Graph, Node
|
|
15
|
+
from .schema import Graph, Node, PortKind
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class NodeCache(ABC):
|
|
@@ -40,8 +40,15 @@ def _canonical_params(node: Node, registry: Registry) -> dict[str, Any]:
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def is_cache_eligible(node: Node, registry: Registry) -> bool:
|
|
43
|
-
"""False when any seed param resolves to a negative (random) value.
|
|
43
|
+
"""False when a control map is wired, or any seed param resolves to a negative (random) value.
|
|
44
|
+
|
|
45
|
+
A node driven by a control map re-runs every time: the user iterates on the pose/depth and
|
|
46
|
+
expects each run to apply the current control, so a cached take would read as "control not
|
|
47
|
+
taking effect" (even a re-render at the same seed must re-apply it)."""
|
|
44
48
|
descriptor = registry.get(node.type)
|
|
49
|
+
for port in descriptor.inputs:
|
|
50
|
+
if port.kind is PortKind.CONTROL and node.inputs.get(port.id):
|
|
51
|
+
return False
|
|
45
52
|
defaults = descriptor.defaults()
|
|
46
53
|
for key in descriptor.seed_keys():
|
|
47
54
|
value = node.params.get(key, defaults.get(key))
|
|
@@ -81,3 +88,29 @@ def node_cache_key(
|
|
|
81
88
|
digest = hashlib.sha256(json.dumps(payload, sort_keys=True, default=str).encode()).hexdigest()
|
|
82
89
|
memo[node_id] = digest
|
|
83
90
|
return digest
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def asset_content_hashes(graph: Graph) -> dict[str, str]:
|
|
94
|
+
"""The byte hash of each file-backed source node's asset, keyed by node id. Feeds
|
|
95
|
+
``node_cache_key`` so the cache invalidates when a file's *content* changes even though its path
|
|
96
|
+
did not (a re-rendered control map, an in-place-replaced input image). Only ``ref="path"`` refs
|
|
97
|
+
are hashable; a missing file is skipped - its path still keys the node through its params."""
|
|
98
|
+
import os
|
|
99
|
+
|
|
100
|
+
hashes: dict[str, str] = {}
|
|
101
|
+
for node in graph.nodes:
|
|
102
|
+
asset = node.params.get("asset")
|
|
103
|
+
if not isinstance(asset, dict) or asset.get("ref") != "path":
|
|
104
|
+
continue
|
|
105
|
+
path = asset.get("path")
|
|
106
|
+
if isinstance(path, str) and os.path.isfile(path):
|
|
107
|
+
hashes[node.id] = _file_hash(path)
|
|
108
|
+
return hashes
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _file_hash(path: str) -> str:
|
|
112
|
+
digest = hashlib.sha256()
|
|
113
|
+
with open(path, "rb") as handle:
|
|
114
|
+
for chunk in iter(lambda: handle.read(1 << 20), b""):
|
|
115
|
+
digest.update(chunk)
|
|
116
|
+
return digest.hexdigest()
|
|
@@ -6,6 +6,7 @@ It orchestrates cheap work inline. A model node's runner submits the denoise to
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import logging
|
|
9
10
|
from dataclasses import replace
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
@@ -13,12 +14,14 @@ from ..errors import CancelledError, GraphValidationError, InlineCoreError
|
|
|
13
14
|
from ..runtime.context import ExecutionContext
|
|
14
15
|
from ..runtime.progress import CancelledEvent, ErrorEvent, NodeDoneEvent, RunDoneEvent
|
|
15
16
|
from ..runtime.run import NodeRuntimeState, RunState, RunStatus, StateTrackingEmitter
|
|
16
|
-
from .cache import NodeCache, is_cache_eligible, node_cache_key
|
|
17
|
+
from .cache import NodeCache, asset_content_hashes, is_cache_eligible, node_cache_key
|
|
17
18
|
from .registry import Registry
|
|
18
19
|
from .schema import Graph, Node
|
|
19
20
|
from .topo import topo_sort, upstream_closure
|
|
20
21
|
from .validate import validate
|
|
21
22
|
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
class Executor:
|
|
24
27
|
def __init__(self, registry: Registry, cache: NodeCache) -> None:
|
|
@@ -33,10 +36,13 @@ class Executor:
|
|
|
33
36
|
order = self._plan(graph, target, state)
|
|
34
37
|
state.status = RunStatus.RUNNING
|
|
35
38
|
outputs: dict[str, dict[str, Any]] = {}
|
|
39
|
+
# Hash the input files once so the node cache is content-addressed: a re-rendered
|
|
40
|
+
# control map (or any replaced input) invalidates even when its path is unchanged.
|
|
41
|
+
asset_hashes = asset_content_hashes(graph)
|
|
36
42
|
for node_id in order:
|
|
37
43
|
if ctx.cancel.cancelled:
|
|
38
44
|
raise CancelledError("Run cancelled.")
|
|
39
|
-
self._run_node(graph, node_id, outputs, run_ctx)
|
|
45
|
+
self._run_node(graph, node_id, outputs, run_ctx, asset_hashes)
|
|
40
46
|
emitter.emit(RunDoneEvent(run_id=ctx.run_id))
|
|
41
47
|
except CancelledError:
|
|
42
48
|
emitter.emit(CancelledEvent(run_id=ctx.run_id))
|
|
@@ -44,6 +50,12 @@ class Executor:
|
|
|
44
50
|
emitter.emit(ErrorEvent(run_id=ctx.run_id, message=str(error), node_id=error.node_id))
|
|
45
51
|
except InlineCoreError as error:
|
|
46
52
|
emitter.emit(ErrorEvent(run_id=ctx.run_id, message=str(error)))
|
|
53
|
+
except Exception as error: # noqa: BLE001
|
|
54
|
+
# A runner that raises a non-InlineCoreError (e.g. a diffusers/HF load error) must still
|
|
55
|
+
# terminate the run: otherwise it escapes to the worker thread, the terminal event is
|
|
56
|
+
# never sent, and the run wedges in "queued" while the UI hangs on "loading model".
|
|
57
|
+
logger.exception("Run %s failed with an unhandled error", ctx.run_id)
|
|
58
|
+
emitter.emit(ErrorEvent(run_id=ctx.run_id, message=str(error) or type(error).__name__))
|
|
47
59
|
|
|
48
60
|
def _plan(self, graph: Graph, target: str, state: RunState) -> list[str]:
|
|
49
61
|
validate(graph, target, self._registry)
|
|
@@ -59,6 +71,7 @@ class Executor:
|
|
|
59
71
|
node_id: str,
|
|
60
72
|
outputs: dict[str, dict[str, Any]],
|
|
61
73
|
ctx: ExecutionContext,
|
|
74
|
+
asset_hashes: dict[str, str],
|
|
62
75
|
) -> None:
|
|
63
76
|
node = graph.node(node_id)
|
|
64
77
|
runner = self._registry.runner(node.type)
|
|
@@ -66,8 +79,7 @@ class Executor:
|
|
|
66
79
|
|
|
67
80
|
key: str | None = None
|
|
68
81
|
if runner.produces_takes and is_cache_eligible(node, self._registry):
|
|
69
|
-
|
|
70
|
-
key = node_cache_key(graph, node_id, self._registry, asset_hashes={})
|
|
82
|
+
key = node_cache_key(graph, node_id, self._registry, asset_hashes=asset_hashes)
|
|
71
83
|
cached = self._cache.get(key)
|
|
72
84
|
if cached is not None:
|
|
73
85
|
ctx.emitter.emit(
|
|
@@ -26,6 +26,7 @@ class PortKind(str, Enum):
|
|
|
26
26
|
LORA = "lora"
|
|
27
27
|
CONDITIONING = "conditioning"
|
|
28
28
|
LATENT = "latent"
|
|
29
|
+
CONTROL = "control"
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
def port_satisfies(source: PortKind, target: PortKind) -> bool:
|
|
@@ -33,7 +34,10 @@ def port_satisfies(source: PortKind, target: PortKind) -> bool:
|
|
|
33
34
|
if source == target:
|
|
34
35
|
return True
|
|
35
36
|
# a single image satisfies a list input (a one-element list)
|
|
36
|
-
|
|
37
|
+
if source is PortKind.IMAGE and target is PortKind.IMAGE_LIST:
|
|
38
|
+
return True
|
|
39
|
+
# a control input accepts any image output (the control map is just an image)
|
|
40
|
+
return source is PortKind.IMAGE and target is PortKind.CONTROL
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
@dataclass(frozen=True)
|
|
@@ -15,6 +15,8 @@ import hashlib
|
|
|
15
15
|
import json
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
|
+
from ..config import models_dir
|
|
19
|
+
|
|
18
20
|
# Category subfolders scanned under the models root. These are the keys a param's `options_from`
|
|
19
21
|
# may reference (see graph/primitives.py); ensure_dirs() creates them so drop-in is obvious.
|
|
20
22
|
CATEGORIES: tuple[str, ...] = (
|
|
@@ -96,3 +98,22 @@ class ModelCatalog:
|
|
|
96
98
|
# A sharded model (config + shards) is one entry, named for its folder.
|
|
97
99
|
names.append(entry.name)
|
|
98
100
|
return sorted(names)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def resolve_picked(category: str, chosen: object) -> Path | None:
|
|
104
|
+
"""A model dropdown's stored value, resolved to a file under ``category``.
|
|
105
|
+
|
|
106
|
+
The select serves bare filenames, but projects written before it did store the full relative
|
|
107
|
+
path, and joining one of those under the category doubles the prefix into a path that cannot
|
|
108
|
+
exist. So: try the value as given under the category, then its bare name there, then the value
|
|
109
|
+
as a path in its own right. Returns None when nothing matches, which callers must not stringify
|
|
110
|
+
(``str(None)`` reaching a loader reads as a corrupt file rather than a stale pick).
|
|
111
|
+
"""
|
|
112
|
+
name = str(chosen or "").strip()
|
|
113
|
+
if not name:
|
|
114
|
+
return None
|
|
115
|
+
root = models_dir() / category
|
|
116
|
+
for candidate in (root / name, root / Path(name).name, Path(name)):
|
|
117
|
+
if candidate.exists():
|
|
118
|
+
return candidate
|
|
119
|
+
return None
|
|
@@ -59,6 +59,11 @@ class CheckpointReader:
|
|
|
59
59
|
def keys(self) -> list[str]:
|
|
60
60
|
return list(self._index)
|
|
61
61
|
|
|
62
|
+
def shapes(self) -> dict[str, list[int]]:
|
|
63
|
+
"""Every tensor's shape, from the header alone - no torch, no tensor read. Lets a caller
|
|
64
|
+
infer a checkpoint's architecture (layer counts, widths) before deciding how to load it."""
|
|
65
|
+
return {key: list(entry.get("shape") or []) for key, entry in self._index.items()}
|
|
66
|
+
|
|
62
67
|
def get_tensor(self, key: str, device: str | None = None) -> Any:
|
|
63
68
|
"""One tensor, read straight from its byte range into a fresh buffer."""
|
|
64
69
|
import torch
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Requirements for the client-side "Control Space" node: just the suggested ControlNet model, so
|
|
2
|
+
the node can offer a one-click download when ``models/controlnet/`` is empty. Torch-free (pure
|
|
3
|
+
filesystem), so it registers even on a runtime-less install - a download only needs the models dir.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from ..config import models_dir
|
|
12
|
+
from .requirements import ModelComponent
|
|
13
|
+
from .zimage.requirements import controlnet_component
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ControlSpaceProvider:
|
|
17
|
+
"""The ControlNet a Control Space render needs downstream - offered as a suggested download."""
|
|
18
|
+
|
|
19
|
+
def components(self, params: dict[str, object] | None = None) -> list[ModelComponent]:
|
|
20
|
+
return [controlnet_component()]
|
|
21
|
+
|
|
22
|
+
def download_target(self, component: ModelComponent) -> Path:
|
|
23
|
+
return models_dir() / component.category
|
|
24
|
+
|
|
25
|
+
def estimate(self, policy: Any) -> dict[str, Any] | None:
|
|
26
|
+
return None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""FLUX.2: one node over the whole checkpoint family (klein 4B/9B, their base builds, KV, dev)."""
|