inline-core 1.2.52__tar.gz → 1.2.61__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.52 → inline_core-1.2.61}/CLAUDE.md +85 -4
- {inline_core-1.2.52 → inline_core-1.2.61}/PKG-INFO +16 -6
- {inline_core-1.2.52 → inline_core-1.2.61}/README.md +8 -3
- {inline_core-1.2.52 → inline_core-1.2.61}/pyproject.toml +22 -4
- inline_core-1.2.61/scripts/flux2_train_matrix.py +204 -0
- inline_core-1.2.61/src/inline_core/__init__.py +14 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/device/memory.py +16 -4
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/device/policy.py +5 -1
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/graph/cache.py +35 -2
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/graph/executor.py +16 -4
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/graph/registry.py +4 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/graph/schema.py +5 -1
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/catalog.py +21 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/checkpoint.py +5 -0
- inline_core-1.2.61/src/inline_core/models/controlspace.py +26 -0
- inline_core-1.2.61/src/inline_core/models/flux2/__init__.py +1 -0
- inline_core-1.2.61/src/inline_core/models/flux2/controlnet.py +234 -0
- inline_core-1.2.61/src/inline_core/models/flux2/embeds.py +165 -0
- inline_core-1.2.61/src/inline_core/models/flux2/provider.py +84 -0
- inline_core-1.2.61/src/inline_core/models/flux2/requirements.py +414 -0
- inline_core-1.2.61/src/inline_core/models/flux2/runner.py +677 -0
- inline_core-1.2.61/src/inline_core/models/flux2/variants.py +334 -0
- inline_core-1.2.61/src/inline_core/models/keymap.py +304 -0
- inline_core-1.2.61/src/inline_core/models/krea2/depth_control.py +149 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/krea2/img2img.py +2 -2
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/krea2/provider.py +11 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/krea2/requirements.py +75 -1
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/krea2/runner.py +65 -6
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/loaders.py +574 -7
- inline_core-1.2.61/src/inline_core/models/minimaxh3/__init__.py +7 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/adaln.py +138 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/keys.py +134 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/load.py +286 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/pipeline.py +757 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/provider.py +102 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/requirements.py +249 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/runner.py +406 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vae_keys.py +125 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/__init__.py +46 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3.py +922 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3_audio.py +679 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/before_denoise.py +425 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/before_encoder.py +408 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/decoders.py +199 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/denoise.py +325 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/encoders.py +639 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/modular_blocks_minimax_h3.py +345 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/modular_pipeline.py +115 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/packing.py +538 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/packing_ref2va.py +841 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/scheduling_minimax_h3.py +288 -0
- inline_core-1.2.61/src/inline_core/models/minimaxh3/vendor/transformer_minimax_h3.py +644 -0
- inline_core-1.2.61/src/inline_core/models/offload.py +281 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/pipeline_runtime.py +149 -20
- inline_core-1.2.61/src/inline_core/models/prepared.py +151 -0
- inline_core-1.2.61/src/inline_core/models/preprocess/__init__.py +5 -0
- inline_core-1.2.61/src/inline_core/models/preprocess/requirements.py +59 -0
- inline_core-1.2.61/src/inline_core/models/preprocess/runner.py +174 -0
- inline_core-1.2.61/src/inline_core/models/references.py +125 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/requirements.py +57 -2
- inline_core-1.2.61/src/inline_core/models/video_params.py +144 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/zimage/provider.py +12 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/zimage/requirements.py +90 -4
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/zimage/runner.py +72 -8
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/runtime/file_store.py +36 -6
- inline_core-1.2.61/src/inline_core/runtime/store.py +45 -0
- inline_core-1.2.61/src/inline_core/runtime/video_encode.py +204 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/server/app.py +6 -4
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/server/bootstrap.py +35 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/server/serialize.py +28 -4
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/fal.py +43 -5
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/frames.py +44 -18
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/generation.py +63 -9
- inline_core-1.2.61/src/inline_core/studio/graph_build.py +326 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/handlers.py +43 -6
- inline_core-1.2.61/src/inline_core/studio/image_meta.py +36 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/models.py +38 -4
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/moodboard.py +52 -3
- inline_core-1.2.61/src/inline_core/studio/recipe.py +109 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/schema.py +8 -2
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/training/arch.py +107 -4
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/training/dataset.py +54 -3
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/training/models.py +101 -2
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/helpers.py +1 -0
- inline_core-1.2.61/tests/test_cache.py +116 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_executor.py +26 -1
- inline_core-1.2.61/tests/test_flux2_controlnet.py +159 -0
- inline_core-1.2.61/tests/test_flux2_folder.py +179 -0
- inline_core-1.2.61/tests/test_flux2_resolve.py +176 -0
- inline_core-1.2.61/tests/test_flux2_runner.py +126 -0
- inline_core-1.2.61/tests/test_flux2_training.py +199 -0
- inline_core-1.2.61/tests/test_flux2_variants.py +129 -0
- inline_core-1.2.61/tests/test_keymap.py +268 -0
- inline_core-1.2.61/tests/test_krea2_depth_control.py +104 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_krea2_requirements.py +35 -2
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_krea2_runner.py +4 -3
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_loaders.py +35 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_memory_policy.py +39 -0
- inline_core-1.2.61/tests/test_minimaxh3_adaln.py +159 -0
- inline_core-1.2.61/tests/test_minimaxh3_keys.py +138 -0
- inline_core-1.2.61/tests/test_minimaxh3_load.py +257 -0
- inline_core-1.2.61/tests/test_minimaxh3_nodes.py +335 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_model_requirements.py +25 -0
- inline_core-1.2.61/tests/test_offload_prepared.py +233 -0
- inline_core-1.2.61/tests/test_output_kind_contract.py +67 -0
- inline_core-1.2.61/tests/test_pipeline_cache.py +82 -0
- inline_core-1.2.61/tests/test_recipe.py +100 -0
- inline_core-1.2.61/tests/test_references.py +84 -0
- inline_core-1.2.61/tests/test_staged_residency.py +101 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_fal.py +46 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_generation.py +126 -3
- inline_core-1.2.61/tests/test_studio_graph_build.py +111 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_models.py +20 -0
- inline_core-1.2.61/tests/test_studio_multi_reference.py +163 -0
- inline_core-1.2.61/tests/test_studio_node_size.py +58 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_rpc.py +17 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_schema.py +19 -0
- inline_core-1.2.61/tests/test_video_encode.py +158 -0
- inline_core-1.2.61/tests/test_video_params.py +114 -0
- inline_core-1.2.61/tests/test_webui_install.py +154 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_zimage_resolve.py +6 -2
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_zimage_runner.py +109 -13
- {inline_core-1.2.52 → inline_core-1.2.61}/uv.lock +260 -3
- {inline_core-1.2.52 → inline_core-1.2.61}/webui.bat +110 -16
- {inline_core-1.2.52 → inline_core-1.2.61}/webui.sh +96 -18
- inline_core-1.2.52/src/inline_core/__init__.py +0 -7
- inline_core-1.2.52/src/inline_core/runtime/store.py +0 -18
- inline_core-1.2.52/src/inline_core/studio/graph_build.py +0 -141
- inline_core-1.2.52/tests/test_cache.py +0 -48
- {inline_core-1.2.52 → inline_core-1.2.61}/.gitignore +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/.python-version +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/main.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/scripts/reference.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/components/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/components/conditioning.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/components/interfaces.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/config.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/device/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/device/auto.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/device/detect.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/device/types.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/errors.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/api.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/constraints.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/fetch.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/handlers.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/importer.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/install.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/loader.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/manifest.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/models.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/paths.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/resolve.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/scanner.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/state.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/extensions/tools.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/ffmpeg.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/graph/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/graph/descriptor.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/graph/loader_runners.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/graph/primitives.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/graph/runners.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/graph/topo.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/graph/validate.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/media.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/krea2/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/krea2/convert.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/lora.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/sampling.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/zimage/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/models/zimage/primitives.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/parallel/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/parallel/config.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/parallel/group.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/parallel/launch.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/parallel/protocol.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/parallel/registry.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/parallel/worker.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/runtime/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/runtime/context.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/runtime/progress.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/runtime/run.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/sampling/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/sampling/batch.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/server/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/server/__main__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/server/assets.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/server/frontend.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/server/manager.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/server/rpc.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/server/run_store.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/assets.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/config.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/peaks.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/store.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/system_stats.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/timeline/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/timeline/compose.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/timeline/ffmpeg.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/timeline/render.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/timeline/resolve.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/training.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/studio/training_store.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/takes.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/training/__init__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/training/__main__.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/training/caption.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/training/protocol.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/src/inline_core/training/trainer.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_catalog.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_checkpoint.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_config.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_device_detect.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_extension_api.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_extension_install.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_extension_manifest.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_extension_resolve.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_extension_scanner.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_extension_spine.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_extension_state.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_file_store.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_frontend_serving.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_hidden_nodes.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_krea2_convert.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_loader_runners.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_lora.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_lora_download.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_parallel_group.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_primitives.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_rpc_bridge.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_run_store.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_sampling.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_schema.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_server.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_assets.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_frames.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_moodboard.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_peaks.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_store.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_timeline.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_studio_training.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_take_bytes.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_topo.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_training_arch.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_training_dataset.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_training_models.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_training_resolve.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_validate.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/tests/test_xfuser_sampler.py +0 -0
- {inline_core-1.2.52 → inline_core-1.2.61}/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
|
|
@@ -112,6 +121,11 @@ between nodes and are never takes.
|
|
|
112
121
|
|
|
113
122
|
### Storage & configuration (all env, see `config.py`)
|
|
114
123
|
|
|
124
|
+
- **Never put weights on an instance's ephemeral/scratch disk.** On cloud GPU boxes (`/opt/dlami/nvme`,
|
|
125
|
+
`/mnt/resource`, and anything else labelled ephemeral or instance-store) the volume is **wiped on
|
|
126
|
+
every stop/start**, taking the models and leaving dangling symlinks in `models/`. This has cost two
|
|
127
|
+
full re-downloads of MiniMax H3 at ~130 GB each. Weights go on the persistent root volume, or on an
|
|
128
|
+
attached volume that survives a restart. Scratch is fine for logs and temporary output only.
|
|
115
129
|
- **Models root** - `INLINE_MODELS_DIR`, else `./models`. **Bring your own weights; nothing is
|
|
116
130
|
downloaded.** ComfyUI-style category subfolders (`diffusion_models/`, `vae/`, `text_encoders/`,
|
|
117
131
|
`loras/`, `controlnet/`, `checkpoints/`, `clip_vision/`, `upscale_models/`, `embeddings/`). The
|
|
@@ -143,6 +157,18 @@ between nodes and are never takes.
|
|
|
143
157
|
(Turing/Volta - compute capability < 8.0, e.g. a T4): same footprint, but it uses the fp16 tensor
|
|
144
158
|
cores instead of bf16's slow path. The **VAE stays upcast** (bf16, or fp32 when the denoiser is
|
|
145
159
|
fp16) because fp16 VAE decode can overflow to black/NaN images (`_compute_dtype` in `device/`).
|
|
160
|
+
- **Quantization rungs** - the fit ladder is full-precision resident → int8 resident → **NF4 resident**
|
|
161
|
+
→ sequential offload → wont-fit. NF4 is what makes a 32B model viable on a 24 GB card. Note int8
|
|
162
|
+
forces bf16 (torchao's weight-only int8 silently no-ops under fp16) while NF4 does not, so a Turing
|
|
163
|
+
card keeps its fp16 tensor cores under NF4.
|
|
164
|
+
- **Prequantized checkpoints must not be re-quantized.** A checkpoint that ships already quantized
|
|
165
|
+
(`flux2/variants.is_prequantized`) has an on-disk size that already _is_ its resident size, so the
|
|
166
|
+
ladder's assumption that quantization halves it does not hold, and handing diffusers a second,
|
|
167
|
+
different quantization config is a hard error. Pass `Quantization.NONE` for those.
|
|
168
|
+
- **Staged loading** - when the text encoder and the transformer cannot be co-resident (dev is a
|
|
169
|
+
15 GB encoder beside an 18 GB transformer), the prompt is encoded first and the encoder freed before
|
|
170
|
+
the transformer loads. The decision has to be made _before_ the load: by the time an OOM fires there
|
|
171
|
+
is nothing left to free.
|
|
146
172
|
- **Multi-GPU** - `INLINE_PARALLEL` (e.g. `pipefusion=2`, `pipefusion=2,ulysses=2`); degrees multiply
|
|
147
173
|
to the world size, which must equal the GPU count.
|
|
148
174
|
|
|
@@ -202,6 +228,11 @@ real codec that moves tensors lives with the model runner.
|
|
|
202
228
|
Don't scatter it.
|
|
203
229
|
- **Bring-your-own models.** Nothing is downloaded by the engine. The catalog scans; the user places
|
|
204
230
|
files. A model picker is a `SELECT` param with `options_from="<category>"`.
|
|
231
|
+
- **Verify image models by rendering.** The FLUX.2 work shipped five bugs past a green test suite,
|
|
232
|
+
and every one produced a _wrong image rather than an error_: a mis-keyed checkpoint, a
|
|
233
|
+
vision-language encoder loaded in place of a text one, an unnormalized latent, and a control context
|
|
234
|
+
cast to a quantized weight's `uint8` storage dtype. A unit test cannot see a plausible-but-wrong
|
|
235
|
+
image. Render something and look at it.
|
|
205
236
|
- **Tests (pytest).** Cover the logic that matters: graph validate/topo/executor/cache, the catalog
|
|
206
237
|
scan, the run store + server contract, the device/memory policy, the parallel group + xfuser seam,
|
|
207
238
|
and each model runner (import-guarded, no GPU needed). See `tests/`.
|
|
@@ -211,14 +242,17 @@ real codec that moves tensors lives with the model runner.
|
|
|
211
242
|
|
|
212
243
|
```
|
|
213
244
|
uv venv # create ./.venv
|
|
214
|
-
|
|
215
|
-
uv pip install -e ".[
|
|
216
|
-
uv pip install -e ".[runtime
|
|
245
|
+
# --python pins the target: without it uv installs into an activated venv/conda env, not ./.venv
|
|
246
|
+
uv pip install --python .venv/bin/python -e ".[server,dev]" # engine + server + test tooling
|
|
247
|
+
uv pip install --python .venv/bin/python -e ".[runtime]" # + torch, diffusers, transformers
|
|
248
|
+
uv pip install --python .venv/bin/python -e ".[runtime,parallel]" # + xfuser, multi-GPU denoise
|
|
217
249
|
|
|
218
250
|
./webui.sh # run (loopback:8848); friendly flags → INLINE_* env
|
|
219
251
|
./webui.sh --listen --port 9000 # bind all interfaces
|
|
220
252
|
./webui.sh --lowvram # tight-VRAM profile
|
|
221
253
|
./webui.sh --install --extra runtime # set up ./.venv with the model runtime, then exit
|
|
254
|
+
# (reuses an existing ./.venv; --recreate rebuilds it, and
|
|
255
|
+
# an activated foreign env is reported, never modified)
|
|
222
256
|
python -m inline_core.server # run the server directly (INLINE_HOST / INLINE_PORT)
|
|
223
257
|
|
|
224
258
|
ruff check . # lint (zero warnings)
|
|
@@ -227,6 +261,53 @@ uv run pytest -q # tests (no GPU; model code is import-
|
|
|
227
261
|
|
|
228
262
|
## Where to add things
|
|
229
263
|
|
|
264
|
+
- **New video model** → do **not** rediscover the plumbing; it exists as shared seams, and
|
|
265
|
+
`models/minimaxh3/` is the reference caller:
|
|
266
|
+
- `runtime/video_encode.py` + `TakeStore.save_video` / `save_audio` - frames plus a waveform to one
|
|
267
|
+
playable MP4. A model that generates video and its soundtrack jointly returns **both** takes;
|
|
268
|
+
only the one matching the descriptor's `output_kind` claims the node's canvas slot (see
|
|
269
|
+
`studio/generation._save_take`, and `tests/test_output_kind_contract.py` which keeps the
|
|
270
|
+
declarations honest).
|
|
271
|
+
- `models/video_params.py` - `VideoGrid` + `video_param_fields(...)`, the way
|
|
272
|
+
`sampling_param_fields(...)` already works. A duration snaps **up** onto the model's frame grid
|
|
273
|
+
and is then clamped into the model's window, which is what both reference implementations do:
|
|
274
|
+
asking H3 for 10 seconds gives 10.125, not 9.417. **fps is a model constant, never a param**,
|
|
275
|
+
or it desyncs from the grid.
|
|
276
|
+
- `models/references.py` - wired image/video/audio ports as one ordered, numbered list. Wiring
|
|
277
|
+
order is what the prompt addresses, so it is meaning, not decoration.
|
|
278
|
+
- `models/keymap.py` - load a checkpoint written for another implementation. Declare a key plan
|
|
279
|
+
(rename / split / swap halves / drop / assert-equal) and apply it while weights stream in. The
|
|
280
|
+
transforms it performs are the ones that fail **silently**, so a plan declares its expected row
|
|
281
|
+
layout and the detector measures the real one. It needs the **whole tensor**: one head's worth of
|
|
282
|
+
rows cannot tell the layouts apart, and it raises rather than guessing.
|
|
283
|
+
- `models/prepared.py` - cache a quantised model once. Everything that changes the bytes goes in
|
|
284
|
+
the hash, including model-specific flags, or switching a flag serves a stale artifact.
|
|
285
|
+
- `models/offload.py` - the device policy's plan to a concrete torchao + group-offload recipe,
|
|
286
|
+
plus **split residency**: group offload holds the whole model in host RAM, so a model bigger
|
|
287
|
+
than the RAM available has nowhere to sit and the kernel swaps it. `blocks_to_place` sizes the
|
|
288
|
+
overflow and those leading blocks go on the accelerator instead, placed as they land rather
|
|
289
|
+
than after the load. It moves the minimum, because every block left resident is VRAM the
|
|
290
|
+
render wanted for activations.
|
|
291
|
+
- **A group-offloaded model's host footprint is reclaimable at load and unreclaimable one step
|
|
292
|
+
later, so never size a split from free memory during the load.** Streaming from a safetensors
|
|
293
|
+
mmap leaves the CPU-side weights as clean file-backed pages the kernel can drop and re-read for
|
|
294
|
+
free. The first denoising step ends that: group offload returns each block with
|
|
295
|
+
`module.to("cpu")`, which allocates fresh anonymous memory and drops the file-backed storage.
|
|
296
|
+
A planner reading `available` mid-load is reading a number that is about to stop being true,
|
|
297
|
+
and the failure mode is not an exception. It is the machine resetting with the page cache
|
|
298
|
+
converted out from under it, no OOM message and no shutdown sequence. Budget the full
|
|
299
|
+
post-conversion footprint, and count what other components will claim from the same RAM
|
|
300
|
+
afterwards (a leaf-offloaded VAE lands there too).
|
|
301
|
+
- **Ordering, when a load both transforms and quantises:** structural transform first,
|
|
302
|
+
quantisation last, and a prequantized source takes no structural transform at all. The three
|
|
303
|
+
clauses and why they are not negotiable are in `models/offload.py`'s docstring.
|
|
304
|
+
- **Vendoring unreleased upstream code** → `models/<name>/vendor/`, **verbatim, import rewrites
|
|
305
|
+
only**. Put a provenance header in its `__init__.py` naming the repo, PR, branch, commit sha and
|
|
306
|
+
date. `models/*/vendor/` is already excluded from ruff and pyright by a glob, because editing it to
|
|
307
|
+
satisfy our linters destroys the one property that makes a re-sync reviewable. Never patch
|
|
308
|
+
installed diffusers: construct components directly and pass them in, so nothing resolves a class by
|
|
309
|
+
name through a registry we do not own. Pin, don't floor, any dependency whose experimental surface
|
|
310
|
+
the vendored code imports from.
|
|
230
311
|
- **New model runner** → a subpackage `models/<name>/` with `runner.py` (a `NodeDescriptor` + a
|
|
231
312
|
`NodeRunner` + `register_<name>(registry, store, policy)`) and an `__init__.py` re-exporting it;
|
|
232
313
|
add a `try/except ImportError` block in `server/bootstrap.py`; add an optional-deps extra in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: inline-core
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.61
|
|
4
4
|
Summary: The generation engine behind Inline Studio.
|
|
5
5
|
License-Expression: GPL-3.0-or-later
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -9,12 +9,14 @@ Requires-Dist: psutil>=5.9
|
|
|
9
9
|
Provides-Extra: all
|
|
10
10
|
Requires-Dist: accelerate>=0.30; extra == 'all'
|
|
11
11
|
Requires-Dist: bitsandbytes>=0.43; (platform_system != 'Darwin') and extra == 'all'
|
|
12
|
-
Requires-Dist:
|
|
12
|
+
Requires-Dist: controlnet-aux>=0.0.7; extra == 'all'
|
|
13
|
+
Requires-Dist: diffusers==0.39.0; extra == 'all'
|
|
13
14
|
Requires-Dist: einops>=0.7; extra == 'all'
|
|
14
15
|
Requires-Dist: fastapi>=0.110; extra == 'all'
|
|
15
16
|
Requires-Dist: huggingface-hub>=0.23; extra == 'all'
|
|
16
17
|
Requires-Dist: imageio-ffmpeg>=0.4; extra == 'all'
|
|
17
18
|
Requires-Dist: nvidia-ml-py>=12; extra == 'all'
|
|
19
|
+
Requires-Dist: onnxruntime>=1.17; extra == 'all'
|
|
18
20
|
Requires-Dist: peft>=0.11; extra == 'all'
|
|
19
21
|
Requires-Dist: pillow>=10; extra == 'all'
|
|
20
22
|
Requires-Dist: psutil>=5.9; extra == 'all'
|
|
@@ -35,8 +37,11 @@ Requires-Dist: nvidia-ml-py>=12; extra == 'parallel'
|
|
|
35
37
|
Requires-Dist: xfuser>=0.4; extra == 'parallel'
|
|
36
38
|
Provides-Extra: runtime
|
|
37
39
|
Requires-Dist: accelerate>=0.30; extra == 'runtime'
|
|
38
|
-
Requires-Dist:
|
|
40
|
+
Requires-Dist: av>=12; extra == 'runtime'
|
|
41
|
+
Requires-Dist: controlnet-aux>=0.0.7; extra == 'runtime'
|
|
42
|
+
Requires-Dist: diffusers==0.39.0; extra == 'runtime'
|
|
39
43
|
Requires-Dist: huggingface-hub>=0.23; extra == 'runtime'
|
|
44
|
+
Requires-Dist: onnxruntime>=1.17; extra == 'runtime'
|
|
40
45
|
Requires-Dist: safetensors>=0.4; extra == 'runtime'
|
|
41
46
|
Requires-Dist: scipy>=1.11; extra == 'runtime'
|
|
42
47
|
Requires-Dist: torch>=2.2; extra == 'runtime'
|
|
@@ -97,13 +102,18 @@ a 6 GB laptop, pure CPU, or split across several GPUs, without touching the grap
|
|
|
97
102
|
|
|
98
103
|
Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/).
|
|
99
104
|
|
|
105
|
+
`--python` pins each install to `./.venv`; without it uv targets whatever venv or conda env is
|
|
106
|
+
activated in your shell, and installs land there instead.
|
|
107
|
+
|
|
100
108
|
```
|
|
101
109
|
uv venv
|
|
102
|
-
uv pip install -e ".[server]"
|
|
103
|
-
uv pip install -e ".[runtime]"
|
|
104
|
-
uv pip install -e ".[runtime,parallel]" # + xfuser,
|
|
110
|
+
uv pip install --python .venv/bin/python -e ".[server]" # engine + HTTP/websocket API
|
|
111
|
+
uv pip install --python .venv/bin/python -e ".[runtime]" # + torch, diffusers, transformers
|
|
112
|
+
uv pip install --python .venv/bin/python -e ".[runtime,parallel]" # + xfuser, 2+ GPUs
|
|
105
113
|
```
|
|
106
114
|
|
|
115
|
+
Or let the launcher do it: `./webui.sh --install --extra runtime` (Windows: `.\webui.bat`).
|
|
116
|
+
|
|
107
117
|
## Models
|
|
108
118
|
|
|
109
119
|
Drop files into the models dir (default `./models`, override with `INLINE_MODELS_DIR`), ComfyUI-style,
|
|
@@ -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,
|
|
@@ -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.61"
|
|
5
5
|
description = "The generation engine behind Inline Studio."
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "GPL-3.0-or-later"
|
|
@@ -18,7 +18,9 @@ runtime = [
|
|
|
18
18
|
# A bare `torch` is CPU-only on Windows; see [tool.uv] below and requirements.txt.
|
|
19
19
|
"torch>=2.2",
|
|
20
20
|
# Krea 2 needs diffusers >= 0.39 (Krea2Pipeline); Z-Image needs >= 0.36 (ZImagePipeline).
|
|
21
|
-
|
|
21
|
+
# Pinned, not floored: MiniMax H3 is vendored from an unmerged PR and imports six symbols
|
|
22
|
+
# from the experimental Modular Diffusers surface, which a minor release may rename.
|
|
23
|
+
"diffusers==0.39.0",
|
|
22
24
|
"transformers>=4.44",
|
|
23
25
|
"accelerate>=0.30",
|
|
24
26
|
"safetensors>=0.4",
|
|
@@ -28,6 +30,14 @@ runtime = [
|
|
|
28
30
|
"scipy>=1.11",
|
|
29
31
|
# We call snapshot_download directly for the model popup, so pin it rather than rely on transit.
|
|
30
32
|
"huggingface_hub>=0.23",
|
|
33
|
+
# ControlNet preprocessors (the Apply ControlNet node): OpenPose/DWPose, MiDaS/Zoe depth, canny,
|
|
34
|
+
# HED, lineart, MLSD, scribble, normal. DWPose runs its detector on ONNX Runtime.
|
|
35
|
+
"controlnet-aux>=0.0.7",
|
|
36
|
+
"onnxruntime>=1.17",
|
|
37
|
+
# MiniMax H3's reference node decodes a wired video or audio clip when it builds the reference.
|
|
38
|
+
# The blocks raise a plain ImportError without it, so the node would advertise two ports it
|
|
39
|
+
# cannot read. Video output goes out through ffmpeg, not this.
|
|
40
|
+
"av>=12",
|
|
31
41
|
]
|
|
32
42
|
server = [
|
|
33
43
|
"fastapi>=0.110",
|
|
@@ -67,13 +77,15 @@ dev = [
|
|
|
67
77
|
all = [
|
|
68
78
|
# runtime
|
|
69
79
|
"torch>=2.2",
|
|
70
|
-
"diffusers
|
|
80
|
+
"diffusers==0.39.0",
|
|
71
81
|
"transformers>=4.44",
|
|
72
82
|
"accelerate>=0.30",
|
|
73
83
|
"safetensors>=0.4",
|
|
74
84
|
"torchao>=0.14",
|
|
75
85
|
"scipy>=1.11",
|
|
76
86
|
"huggingface_hub>=0.23",
|
|
87
|
+
"controlnet-aux>=0.0.7",
|
|
88
|
+
"onnxruntime>=1.17",
|
|
77
89
|
# server
|
|
78
90
|
"fastapi>=0.110",
|
|
79
91
|
"uvicorn[standard]>=0.29",
|
|
@@ -110,15 +122,21 @@ packages = ["src/inline_core"]
|
|
|
110
122
|
[tool.ruff]
|
|
111
123
|
line-length = 100
|
|
112
124
|
target-version = "py311"
|
|
125
|
+
# Vendored upstream code (see models/minimaxh3/vendor/__init__.py). Editing it to satisfy our
|
|
126
|
+
# linters would destroy the one property that makes a re-sync reviewable: it is verbatim.
|
|
127
|
+
extend-exclude = ["src/inline_core/models/*/vendor"]
|
|
113
128
|
|
|
114
129
|
[tool.ruff.lint]
|
|
115
130
|
select = ["E", "F", "I", "UP", "B"]
|
|
116
131
|
|
|
117
132
|
[tool.pyright]
|
|
118
133
|
include = ["src", "tests"]
|
|
134
|
+
exclude = ["**/models/*/vendor"]
|
|
119
135
|
pythonVersion = "3.11"
|
|
120
136
|
typeCheckingMode = "strict"
|
|
121
137
|
|
|
122
138
|
[tool.pytest.ini_options]
|
|
123
139
|
testpaths = ["tests"]
|
|
124
|
-
|
|
140
|
+
# "." so `tests` imports as a package: several suites share fixtures via `from tests.x import y`,
|
|
141
|
+
# and without it those modules fail to collect and silently stop running.
|
|
142
|
+
pythonpath = ["src", "."]
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""VRAM + step-time sweep for FLUX.2 LoRA training: the cells behind the README benchmark table.
|
|
2
|
+
|
|
3
|
+
cd core && PYTHONPATH=src .venv/bin/python scripts/flux2_train_matrix.py --dataset <dir>
|
|
4
|
+
|
|
5
|
+
One cell = one real run of `python -m inline_core.training`, the same entry point the Trainer tab
|
|
6
|
+
spawns, so a number here is a number a user would see. Anything else (importing `train` in-process,
|
|
7
|
+
or a hand-rolled loop) would measure a different program.
|
|
8
|
+
|
|
9
|
+
Held fixed at the settings the existing Z-Image and Krea 2 rows used: 12 steps, rank 16, batch 1,
|
|
10
|
+
gradient checkpointing on. What varies is resolution and base precision. Peak VRAM is the trainer's
|
|
11
|
+
own `torch.cuda.max_memory_allocated` reading off the last progress line; an OOM is recorded as a
|
|
12
|
+
cell rather than aborting the sweep, because "does not fit" is a result the table needs.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
import time
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
_REPO = Path(__file__).resolve().parent.parent.parent
|
|
26
|
+
_CORE = _REPO / "core"
|
|
27
|
+
_DEFAULT_OUT = _REPO / "outputs" / "flux2-train-matrix"
|
|
28
|
+
|
|
29
|
+
# (resolution, baseQuant). `none` is the bf16 base; `nf4` is the 4-bit (QLoRA) base.
|
|
30
|
+
CELLS: tuple[tuple[int, str], ...] = (
|
|
31
|
+
(512, "none"),
|
|
32
|
+
(512, "nf4"),
|
|
33
|
+
(1024, "none"),
|
|
34
|
+
(1024, "nf4"),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
_STEPS = 12
|
|
38
|
+
_RANK = 16
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _manifest(work: Path, dataset: Path, models: Path, resolution: int, quant: str) -> Path:
|
|
42
|
+
"""The same manifest shape `studio/training.py::_prepare` writes."""
|
|
43
|
+
checkpoints = work / "checkpoints"
|
|
44
|
+
checkpoints.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
manifest = {
|
|
46
|
+
"runId": work.name,
|
|
47
|
+
"workingDir": str(work),
|
|
48
|
+
"datasetDir": str(dataset),
|
|
49
|
+
"checkpointDir": str(checkpoints),
|
|
50
|
+
"outputPath": str(work / "lora.safetensors"),
|
|
51
|
+
"resumeFrom": None,
|
|
52
|
+
"modelsDir": str(models),
|
|
53
|
+
"arch": "flux2",
|
|
54
|
+
# FLUX.2 offers one base mode: the undistilled klein base. `raw` is that mode's key.
|
|
55
|
+
"baseMode": "raw",
|
|
56
|
+
"triggerWord": "",
|
|
57
|
+
"hyperparams": {
|
|
58
|
+
"arch": "flux2",
|
|
59
|
+
"baseMode": "raw",
|
|
60
|
+
"baseQuant": quant,
|
|
61
|
+
# Explicit, not `auto`: the sweep is measuring what each precision costs, and auto would
|
|
62
|
+
# silently swap a bf16 cell for NF4 the moment it predicted a bad fit.
|
|
63
|
+
"offload": "off",
|
|
64
|
+
"loraScope": "full",
|
|
65
|
+
"captionDropout": 0.0,
|
|
66
|
+
"flipAugment": False,
|
|
67
|
+
"rank": _RANK,
|
|
68
|
+
"alpha": _RANK,
|
|
69
|
+
"learningRate": 1e-4,
|
|
70
|
+
"batchSize": 1,
|
|
71
|
+
"steps": _STEPS,
|
|
72
|
+
"saveEvery": _STEPS,
|
|
73
|
+
"resolution": resolution,
|
|
74
|
+
},
|
|
75
|
+
"gpuIds": [],
|
|
76
|
+
}
|
|
77
|
+
path = work / "manifest.json"
|
|
78
|
+
path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
|
79
|
+
return path
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _run_cell(python: str, manifest: Path, log: Path) -> dict[str, object]:
|
|
83
|
+
"""Drain the JSON-line protocol, keeping the last VRAM reading and the wall time from the first
|
|
84
|
+
training step onward - loading and latent precache are not what the table reports."""
|
|
85
|
+
env = {**os.environ, "PYTHONPATH": str(_CORE / "src")}
|
|
86
|
+
proc = subprocess.Popen(
|
|
87
|
+
[python, "-m", "inline_core.training", str(manifest)],
|
|
88
|
+
cwd=str(_CORE),
|
|
89
|
+
env=env,
|
|
90
|
+
stdout=subprocess.PIPE,
|
|
91
|
+
stderr=subprocess.STDOUT,
|
|
92
|
+
text=True,
|
|
93
|
+
bufsize=1,
|
|
94
|
+
)
|
|
95
|
+
vram: float | None = None
|
|
96
|
+
error: str | None = None
|
|
97
|
+
first_step_at: float | None = None
|
|
98
|
+
last_step_at: float | None = None
|
|
99
|
+
steps_seen = 0
|
|
100
|
+
started = time.perf_counter()
|
|
101
|
+
lines: list[str] = []
|
|
102
|
+
assert proc.stdout is not None
|
|
103
|
+
for line in proc.stdout:
|
|
104
|
+
lines.append(line)
|
|
105
|
+
line = line.strip()
|
|
106
|
+
if not line.startswith("{"):
|
|
107
|
+
continue
|
|
108
|
+
try:
|
|
109
|
+
message = json.loads(line)
|
|
110
|
+
except json.JSONDecodeError:
|
|
111
|
+
continue
|
|
112
|
+
kind = message.get("type")
|
|
113
|
+
if kind == "progress":
|
|
114
|
+
if message.get("vram") is not None:
|
|
115
|
+
vram = float(message["vram"])
|
|
116
|
+
if message.get("step"):
|
|
117
|
+
steps_seen = int(message["step"])
|
|
118
|
+
now = time.perf_counter()
|
|
119
|
+
if first_step_at is None:
|
|
120
|
+
first_step_at = now
|
|
121
|
+
last_step_at = now
|
|
122
|
+
elif kind == "error":
|
|
123
|
+
error = str(message.get("message") or "")
|
|
124
|
+
proc.wait()
|
|
125
|
+
log.write_text("".join(lines), encoding="utf-8")
|
|
126
|
+
|
|
127
|
+
oom = bool(error) and ("out of gpu memory" in error.lower() or "out of memory" in error.lower())
|
|
128
|
+
# Step 1 pays for the first graph build, so time the interval after it and scale by the gap.
|
|
129
|
+
per_step: float | None = None
|
|
130
|
+
if first_step_at is not None and last_step_at is not None and steps_seen > 1:
|
|
131
|
+
per_step = (last_step_at - first_step_at) / (steps_seen - 1)
|
|
132
|
+
return {
|
|
133
|
+
"peak_vram_gb": vram,
|
|
134
|
+
"seconds_per_step": round(per_step, 2) if per_step else None,
|
|
135
|
+
"seconds_12_steps": round(per_step * _STEPS, 1) if per_step else None,
|
|
136
|
+
"total_seconds": round(time.perf_counter() - started, 1),
|
|
137
|
+
"steps_completed": steps_seen,
|
|
138
|
+
"status": "oom" if oom else ("ok" if proc.returncode == 0 else "failed"),
|
|
139
|
+
"error": error,
|
|
140
|
+
"log": log.name,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def main() -> int:
|
|
145
|
+
parser = argparse.ArgumentParser()
|
|
146
|
+
parser.add_argument("--dataset", type=Path, required=True, help="dir of NNNN.jpg + NNNN.txt")
|
|
147
|
+
parser.add_argument("--out", type=Path, default=_DEFAULT_OUT)
|
|
148
|
+
parser.add_argument("--models", type=Path, default=_CORE / "models")
|
|
149
|
+
parser.add_argument("--python", default=str(_CORE / ".venv" / "bin" / "python"))
|
|
150
|
+
parser.add_argument("--gpu", default="", help="label for the results file, e.g. 'L40S (46GB)'")
|
|
151
|
+
parser.add_argument("--only", default="", help="substring of a cell id, to redo one row")
|
|
152
|
+
args = parser.parse_args()
|
|
153
|
+
|
|
154
|
+
if not args.dataset.is_dir():
|
|
155
|
+
raise SystemExit(f"dataset not found: {args.dataset}")
|
|
156
|
+
|
|
157
|
+
args.out.mkdir(parents=True, exist_ok=True)
|
|
158
|
+
results_path = args.out / "results.json"
|
|
159
|
+
results: dict[str, dict[str, object]] = {}
|
|
160
|
+
if results_path.exists():
|
|
161
|
+
results = json.loads(results_path.read_text()).get("cells", {})
|
|
162
|
+
|
|
163
|
+
label = args.gpu
|
|
164
|
+
if not label:
|
|
165
|
+
try:
|
|
166
|
+
name = subprocess.run(
|
|
167
|
+
["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader"],
|
|
168
|
+
capture_output=True,
|
|
169
|
+
text=True,
|
|
170
|
+
check=True,
|
|
171
|
+
).stdout.strip()
|
|
172
|
+
label = name.splitlines()[0]
|
|
173
|
+
except Exception: # noqa: BLE001 - the label is cosmetic
|
|
174
|
+
label = "unknown GPU"
|
|
175
|
+
|
|
176
|
+
def write() -> None:
|
|
177
|
+
results_path.write_text(
|
|
178
|
+
json.dumps(
|
|
179
|
+
{"gpu": label, "steps": _STEPS, "rank": _RANK, "cells": results},
|
|
180
|
+
indent=2,
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
for resolution, quant in CELLS:
|
|
185
|
+
cell_id = f"{resolution}-{quant}"
|
|
186
|
+
if args.only and args.only not in cell_id:
|
|
187
|
+
continue
|
|
188
|
+
work = args.out / cell_id
|
|
189
|
+
work.mkdir(parents=True, exist_ok=True)
|
|
190
|
+
manifest = _manifest(work, args.dataset, args.models, resolution, quant)
|
|
191
|
+
print(f"--- {cell_id}: {resolution}px, base {quant} ---", flush=True)
|
|
192
|
+
result = _run_cell(args.python, manifest, args.out / f"{cell_id}.log")
|
|
193
|
+
result.update({"resolution": resolution, "base_quant": quant})
|
|
194
|
+
results[cell_id] = result
|
|
195
|
+
print(json.dumps(result, indent=2), flush=True)
|
|
196
|
+
write()
|
|
197
|
+
|
|
198
|
+
write()
|
|
199
|
+
print(f"\n{results_path}")
|
|
200
|
+
return 0
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if __name__ == "__main__":
|
|
204
|
+
raise SystemExit(main())
|
|
@@ -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()
|