inline-core 1.2.67__tar.gz → 1.2.68__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.67 → inline_core-1.2.68}/PKG-INFO +1 -1
- {inline_core-1.2.67 → inline_core-1.2.68}/pyproject.toml +1 -1
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/config.py +23 -2
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/catalog.py +102 -15
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/pipeline.py +24 -8
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/runner.py +6 -1
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/pipeline_runtime.py +51 -4
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/runtime/progress.py +11 -1
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/runtime/run.py +3 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/server/app.py +74 -7
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/server/manager.py +85 -8
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/server/serialize.py +14 -0
- inline_core-1.2.68/src/inline_core/studio/activity.py +396 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/fal.py +75 -17
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/generation.py +99 -45
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/handlers.py +42 -3
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/schema.py +18 -1
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/store.py +58 -3
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/training.py +149 -9
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/training_store.py +2 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/training/cache.py +46 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/training/dataset.py +8 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/training/h3.py +1 -1
- inline_core-1.2.68/src/inline_core/training/precache_store.py +136 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/training/protocol.py +5 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/training/trainer.py +29 -0
- inline_core-1.2.68/tests/test_catalog.py +146 -0
- inline_core-1.2.68/tests/test_encoder_parking.py +77 -0
- inline_core-1.2.68/tests/test_manager_queue.py +152 -0
- inline_core-1.2.68/tests/test_precache_store.py +113 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_staged_residency.py +92 -0
- inline_core-1.2.68/tests/test_studio_activity.py +270 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_schema.py +25 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_training.py +79 -0
- inline_core-1.2.68/tests/test_training_snapshots.py +68 -0
- inline_core-1.2.67/tests/test_catalog.py +0 -64
- {inline_core-1.2.67 → inline_core-1.2.68}/.gitignore +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/.python-version +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/CLAUDE.md +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/README.md +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/main.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/scripts/flux2_train_matrix.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/scripts/minimax_h3_lora_check.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/scripts/minimax_h3_train_matrix.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/scripts/reference.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/components/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/components/conditioning.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/components/interfaces.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/device/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/device/auto.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/device/detect.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/device/memory.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/device/policy.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/device/probe.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/device/types.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/errors.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/api.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/constraints.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/fetch.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/handlers.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/importer.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/install.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/loader.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/manifest.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/models.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/paths.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/resolve.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/scanner.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/state.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/extensions/tools.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/ffmpeg.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/graph/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/graph/cache.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/graph/descriptor.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/graph/executor.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/graph/loader_runners.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/graph/primitives.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/graph/registry.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/graph/runners.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/graph/schema.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/graph/topo.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/graph/validate.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/media.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/checkpoint.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/controlspace.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/flux2/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/flux2/controlnet.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/flux2/embeds.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/flux2/provider.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/flux2/requirements.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/flux2/runner.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/flux2/variants.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/keymap.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/krea2/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/krea2/convert.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/krea2/depth_control.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/krea2/img2img.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/krea2/provider.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/krea2/requirements.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/krea2/runner.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/loaders.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/lora.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/adaln.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/keys.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/load.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/lora_keys.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/provider.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/requirements.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vae_keys.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3_audio.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/before_denoise.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/before_encoder.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/decoders.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/denoise.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/encoders.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/modular_blocks_minimax_h3.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/modular_pipeline.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/packing.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/packing_ref2va.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/scheduling_minimax_h3.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/minimaxh3/vendor/transformer_minimax_h3.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/offload.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/prepared.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/preprocess/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/preprocess/requirements.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/preprocess/runner.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/references.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/requirements.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/sampling.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/video_params.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/zimage/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/zimage/primitives.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/zimage/provider.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/zimage/requirements.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/models/zimage/runner.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/parallel/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/parallel/config.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/parallel/group.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/parallel/launch.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/parallel/protocol.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/parallel/registry.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/parallel/worker.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/runtime/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/runtime/context.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/runtime/file_store.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/runtime/store.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/runtime/video_encode.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/sampling/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/sampling/batch.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/server/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/server/__main__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/server/assets.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/server/bootstrap.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/server/frontend.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/server/rpc.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/server/run_store.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/assets.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/config.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/frames.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/graph_build.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/image_meta.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/models.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/moodboard.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/peaks.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/recipe.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/system_stats.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/timeline/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/timeline/compose.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/timeline/ffmpeg.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/timeline/render.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/studio/timeline/resolve.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/takes.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/training/__init__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/training/__main__.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/training/arch.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/training/caption.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/src/inline_core/training/models.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/conftest.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/helpers.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_cache.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_checkpoint.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_config.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_device_detect.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_executor.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_extension_api.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_extension_install.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_extension_manifest.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_extension_resolve.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_extension_scanner.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_extension_spine.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_extension_state.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_file_store.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_flux2_controlnet.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_flux2_folder.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_flux2_resolve.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_flux2_runner.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_flux2_training.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_flux2_variants.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_frontend_serving.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_hidden_nodes.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_keymap.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_krea2_convert.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_krea2_depth_control.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_krea2_requirements.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_krea2_runner.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_loader_runners.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_loaders.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_lora.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_lora_download.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_memory_policy.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_minimaxh3_adaln.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_minimaxh3_keys.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_minimaxh3_load.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_minimaxh3_lora_keys.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_minimaxh3_nodes.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_minimaxh3_training.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_model_requirements.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_offload_prepared.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_output_kind_contract.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_parallel_group.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_pipeline_cache.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_primitives.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_recipe.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_references.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_rpc_bridge.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_run_store.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_sampling.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_schema.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_server.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_assets.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_fal.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_frames.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_generation.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_graph_build.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_models.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_moodboard.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_multi_reference.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_node_size.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_peaks.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_rpc.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_store.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_studio_timeline.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_take_bytes.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_topo.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_training_arch.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_training_dataset.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_training_models.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_training_resolve.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_validate.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_video_encode.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_video_params.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_webui_install.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_xfuser_sampler.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_zimage_primitives.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_zimage_resolve.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/tests/test_zimage_runner.py +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/uv.lock +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/webui.bat +0 -0
- {inline_core-1.2.67 → inline_core-1.2.68}/webui.sh +0 -0
|
@@ -7,12 +7,33 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def models_dir() -> Path:
|
|
10
|
-
"""The models root
|
|
11
|
-
`./models`. Users drop their own weight files here; nothing is downloaded."""
|
|
10
|
+
"""The **writable** models root: where downloads and trained LoRAs land. `INLINE_MODELS_DIR`,
|
|
11
|
+
else `./models`. Users drop their own weight files here; nothing is downloaded."""
|
|
12
12
|
env = os.environ.get("INLINE_MODELS_DIR")
|
|
13
13
|
return Path(env).expanduser() if env else Path("models")
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def models_dirs() -> list[Path]:
|
|
17
|
+
"""Every root scanned for installed weights, writable one first.
|
|
18
|
+
|
|
19
|
+
Pointing `INLINE_MODELS_DIR` (or `--models-dir`) somewhere else used to hide `./models`
|
|
20
|
+
entirely, so a user with weights in both places could only ever see one set. The extra roots
|
|
21
|
+
are read-only: only `models_dir()` is ever written to.
|
|
22
|
+
"""
|
|
23
|
+
roots = [models_dir()]
|
|
24
|
+
default = Path("models")
|
|
25
|
+
for extra in (default, *_extra_models_dirs()):
|
|
26
|
+
if extra not in roots and extra.is_dir():
|
|
27
|
+
roots.append(extra)
|
|
28
|
+
return roots
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _extra_models_dirs() -> list[Path]:
|
|
32
|
+
"""Additional read-only roots from `INLINE_EXTRA_MODELS_DIRS` (os.pathsep-separated)."""
|
|
33
|
+
raw = os.environ.get("INLINE_EXTRA_MODELS_DIRS", "")
|
|
34
|
+
return [Path(p).expanduser() for p in raw.split(os.pathsep) if p.strip()]
|
|
35
|
+
|
|
36
|
+
|
|
16
37
|
def data_dir() -> Path:
|
|
17
38
|
"""Engine-owned working data (the run DB, takes). `INLINE_DATA_DIR`, else `./.inline`."""
|
|
18
39
|
env = os.environ.get("INLINE_DATA_DIR")
|
|
@@ -13,9 +13,11 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import hashlib
|
|
15
15
|
import json
|
|
16
|
+
from collections.abc import Sequence
|
|
16
17
|
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
17
19
|
|
|
18
|
-
from ..config import
|
|
20
|
+
from ..config import models_dirs
|
|
19
21
|
|
|
20
22
|
# Category subfolders scanned under the models root. These are the keys a param's `options_from`
|
|
21
23
|
# may reference (see graph/primitives.py); ensure_dirs() creates them so drop-in is obvious.
|
|
@@ -29,6 +31,9 @@ CATEGORIES: tuple[str, ...] = (
|
|
|
29
31
|
"controlnet",
|
|
30
32
|
"upscale_models",
|
|
31
33
|
"embeddings",
|
|
34
|
+
# Written by the preprocess runner rather than the user, but it holds real weights and the
|
|
35
|
+
# panel is meant to show everything on disk.
|
|
36
|
+
"annotators",
|
|
32
37
|
)
|
|
33
38
|
|
|
34
39
|
# Extensions we treat as model weights. A folder counts as a model if it contains one of these.
|
|
@@ -45,34 +50,111 @@ def _folder_has_weight(path: Path) -> bool:
|
|
|
45
50
|
return any(_is_weight(child) for child in path.rglob("*"))
|
|
46
51
|
|
|
47
52
|
|
|
53
|
+
def _file_node(path: Path) -> dict[str, Any]:
|
|
54
|
+
stat = path.stat()
|
|
55
|
+
return {
|
|
56
|
+
"name": path.name,
|
|
57
|
+
"path": str(path),
|
|
58
|
+
"kind": "file",
|
|
59
|
+
"sizeBytes": stat.st_size,
|
|
60
|
+
"mtime": int(stat.st_mtime * 1000),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _dir_node(path: Path, name: str) -> dict[str, Any] | None:
|
|
65
|
+
"""A directory as a tree node, or None when it holds nothing worth showing."""
|
|
66
|
+
if not path.is_dir():
|
|
67
|
+
return None
|
|
68
|
+
children: list[dict[str, Any]] = []
|
|
69
|
+
try:
|
|
70
|
+
entries = sorted(path.iterdir(), key=lambda p: p.name.lower())
|
|
71
|
+
except OSError:
|
|
72
|
+
return None
|
|
73
|
+
for entry in entries:
|
|
74
|
+
# Caches like loras/.cache and text_encoders/.snap are noise in a panel about weights.
|
|
75
|
+
if entry.name.startswith("."):
|
|
76
|
+
continue
|
|
77
|
+
if _is_weight(entry):
|
|
78
|
+
children.append(_file_node(entry))
|
|
79
|
+
elif entry.is_dir():
|
|
80
|
+
child = _dir_node(entry, entry.name)
|
|
81
|
+
if child is not None:
|
|
82
|
+
children.append(child)
|
|
83
|
+
if not children:
|
|
84
|
+
return None
|
|
85
|
+
return {
|
|
86
|
+
"name": name,
|
|
87
|
+
"path": str(path),
|
|
88
|
+
"kind": "dir",
|
|
89
|
+
"children": children,
|
|
90
|
+
"fileCount": sum(1 for c in children if c["kind"] == "file"),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
48
94
|
class ModelCatalog:
|
|
49
|
-
"""Scans the models
|
|
95
|
+
"""Scans the models roots and answers "what's installed" per category.
|
|
50
96
|
|
|
51
97
|
Cheap to construct; nothing touches disk until ``ensure_dirs`` or ``rescan``/``scan``. Results
|
|
52
|
-
are cached so ``list`` and ``fingerprint`` are hits between scans.
|
|
98
|
+
are cached so ``list`` and ``fingerprint`` are hits between scans. Several roots may be scanned
|
|
99
|
+
(see ``config.models_dirs``); only the first is ever written to.
|
|
53
100
|
"""
|
|
54
101
|
|
|
55
|
-
def __init__(self, root: Path) -> None:
|
|
56
|
-
|
|
102
|
+
def __init__(self, root: Path | Sequence[Path]) -> None:
|
|
103
|
+
roots = [Path(root)] if isinstance(root, str | Path) else [Path(r) for r in root]
|
|
104
|
+
self._roots = roots or [Path("models")]
|
|
57
105
|
self._entries: dict[str, list[str]] = {category: [] for category in CATEGORIES}
|
|
58
106
|
|
|
59
107
|
@property
|
|
60
108
|
def root(self) -> Path:
|
|
61
|
-
|
|
109
|
+
"""The writable root. Downloads and trained LoRAs land here."""
|
|
110
|
+
return self._roots[0]
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def roots(self) -> list[Path]:
|
|
114
|
+
return list(self._roots)
|
|
62
115
|
|
|
63
116
|
def ensure_dirs(self) -> None:
|
|
64
|
-
"""Create the root and
|
|
117
|
+
"""Create the writable root and its category subfolders, so drop-in has somewhere to go."""
|
|
65
118
|
for category in CATEGORIES:
|
|
66
|
-
(self.
|
|
119
|
+
(self.root / category).mkdir(parents=True, exist_ok=True)
|
|
67
120
|
|
|
68
121
|
def rescan(self) -> dict[str, list[str]]:
|
|
69
|
-
"""Re-read every category from
|
|
122
|
+
"""Re-read every category from every root, cache the result, and return it."""
|
|
70
123
|
entries: dict[str, list[str]] = {}
|
|
71
124
|
for category in CATEGORIES:
|
|
72
|
-
|
|
125
|
+
names: list[str] = []
|
|
126
|
+
for root in self._roots:
|
|
127
|
+
for name in self._scan_category(root / category):
|
|
128
|
+
if name not in names:
|
|
129
|
+
names.append(name)
|
|
130
|
+
entries[category] = sorted(names)
|
|
73
131
|
self._entries = entries
|
|
74
132
|
return entries
|
|
75
133
|
|
|
134
|
+
def tree(self) -> list[dict[str, Any]]:
|
|
135
|
+
"""Every root as a nested, read-only listing for the Models panel.
|
|
136
|
+
|
|
137
|
+
Separate from ``rescan`` because the select only needs flat names, while the panel wants
|
|
138
|
+
nesting, sizes and mtimes; keeping them apart stops the hot path paying for the detail.
|
|
139
|
+
"""
|
|
140
|
+
out: list[dict[str, Any]] = []
|
|
141
|
+
for index, root in enumerate(self._roots):
|
|
142
|
+
categories = [
|
|
143
|
+
node
|
|
144
|
+
for category in CATEGORIES
|
|
145
|
+
if (node := _dir_node(root / category, category)) is not None
|
|
146
|
+
]
|
|
147
|
+
out.append(
|
|
148
|
+
{
|
|
149
|
+
"path": str(root),
|
|
150
|
+
"label": root.name or str(root),
|
|
151
|
+
"writable": index == 0,
|
|
152
|
+
"exists": root.is_dir(),
|
|
153
|
+
"categories": categories,
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
return out
|
|
157
|
+
|
|
76
158
|
# app.py calls scan() in the lifespan; rescan() is the same work exposed for tests/callers that
|
|
77
159
|
# want the mapping back. Keep both so neither call site has to know about the other.
|
|
78
160
|
def scan(self) -> dict[str, list[str]]:
|
|
@@ -92,6 +174,8 @@ class ModelCatalog:
|
|
|
92
174
|
return []
|
|
93
175
|
names: list[str] = []
|
|
94
176
|
for entry in directory.iterdir():
|
|
177
|
+
if entry.name.startswith("."):
|
|
178
|
+
continue
|
|
95
179
|
if _is_weight(entry):
|
|
96
180
|
names.append(entry.name)
|
|
97
181
|
elif entry.is_dir() and _folder_has_weight(entry):
|
|
@@ -112,8 +196,11 @@ def resolve_picked(category: str, chosen: object) -> Path | None:
|
|
|
112
196
|
name = str(chosen or "").strip()
|
|
113
197
|
if not name:
|
|
114
198
|
return None
|
|
115
|
-
root
|
|
116
|
-
for
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
199
|
+
# Every root, writable one first, so a pick resolves wherever the user actually keeps it.
|
|
200
|
+
for models_root in models_dirs():
|
|
201
|
+
root = models_root / category
|
|
202
|
+
for candidate in (root / name, root / Path(name).name):
|
|
203
|
+
if candidate.exists():
|
|
204
|
+
return candidate
|
|
205
|
+
bare = Path(name)
|
|
206
|
+
return bare if bare.exists() else None
|
|
@@ -452,7 +452,7 @@ def _denoiser_name(blocks: Any) -> str:
|
|
|
452
452
|
raise ComponentError("This MiniMax H3 blockset declares no denoiser component.")
|
|
453
453
|
|
|
454
454
|
|
|
455
|
-
def render_staged(pipe: Any, device: Any, **call: Any) -> Any:
|
|
455
|
+
def render_staged(pipe: Any, device: Any, cancel_check: Any = None, **call: Any) -> Any:
|
|
456
456
|
"""Encode the prompt, get the conditioner off the card, then denoise with the card to itself.
|
|
457
457
|
|
|
458
458
|
H3's conditioner is a 32B model that rivals its denoiser, and holding both means the denoiser
|
|
@@ -463,21 +463,40 @@ def render_staged(pipe: Any, device: Any, **call: Any) -> Any:
|
|
|
463
463
|
Falls back to the single call when the pipeline was not built staged, so a caller never has to
|
|
464
464
|
ask which kind it holds.
|
|
465
465
|
"""
|
|
466
|
+
def check() -> None:
|
|
467
|
+
if cancel_check is not None:
|
|
468
|
+
cancel_check()
|
|
469
|
+
|
|
466
470
|
phases = getattr(pipe, "_inline_phases", None)
|
|
467
471
|
if phases is None:
|
|
472
|
+
check()
|
|
468
473
|
return pipe(**call)
|
|
469
474
|
head, tail = phases
|
|
470
475
|
device = str(device)
|
|
471
476
|
|
|
472
|
-
# Every kwarg goes to BOTH halves, not just the first. `set_timesteps` lives in the tail, so a
|
|
473
|
-
# head-only handoff left it reading the descriptor default: a render that asked for 8 steps
|
|
474
|
-
# silently took 49. The halves ignore what their own blocks do not declare, so this is safe.
|
|
475
|
-
state = head(**call)
|
|
476
477
|
# Parked, not released: the next render needs it again, and 19.5 GB across the bus is seconds
|
|
477
478
|
# against the minutes of streaming it buys back.
|
|
478
479
|
denoiser = getattr(pipe, getattr(pipe, "_inline_denoiser", "transformer"))
|
|
479
480
|
resident = getattr(pipe, "_inline_resident_denoiser", True)
|
|
480
481
|
|
|
482
|
+
# Each half claims the card *before* it runs rather than the previous one restoring the layout
|
|
483
|
+
# on its way out. Same two transfers per render in the steady state, but a cancelled denoise no
|
|
484
|
+
# longer pays ~40 GB of restore traffic before the exception surfaces, which read as the cancel
|
|
485
|
+
# being ignored for half a minute. A `.to()` onto the device a module already sits on is free.
|
|
486
|
+
check()
|
|
487
|
+
if resident:
|
|
488
|
+
denoiser.to("cpu")
|
|
489
|
+
pipe.text_encoder.to(device)
|
|
490
|
+
rt.free_vram()
|
|
491
|
+
|
|
492
|
+
# Every kwarg goes to BOTH halves, not just the first. `set_timesteps` lives in the tail, so a
|
|
493
|
+
# head-only handoff left it reading the descriptor default: a render that asked for 8 steps
|
|
494
|
+
# silently took 49. The halves ignore what their own blocks do not declare, so this is safe.
|
|
495
|
+
state = head(**call)
|
|
496
|
+
# The conditioner is a 32B model, so the encode alone runs for a while with no step hook to
|
|
497
|
+
# cancel from; without this a cancel there waits for the whole denoise as well.
|
|
498
|
+
check()
|
|
499
|
+
|
|
481
500
|
# Parking the conditioner is worth doing either way: it frees the card for activations even when
|
|
482
501
|
# the denoiser is too big to take it. Moving the denoiser across is only right when it fits.
|
|
483
502
|
pipe.text_encoder.to("cpu")
|
|
@@ -487,9 +506,6 @@ def render_staged(pipe: Any, device: Any, **call: Any) -> Any:
|
|
|
487
506
|
try:
|
|
488
507
|
return tail(state, **call)
|
|
489
508
|
finally:
|
|
490
|
-
if resident:
|
|
491
|
-
denoiser.to("cpu")
|
|
492
|
-
pipe.text_encoder.to(device)
|
|
493
509
|
rt.free_vram()
|
|
494
510
|
|
|
495
511
|
|
|
@@ -341,7 +341,12 @@ class MiniMaxH3Runner(NodeRunner):
|
|
|
341
341
|
rt.attach_step_progress(pipe, on_step)
|
|
342
342
|
started = time.perf_counter()
|
|
343
343
|
try:
|
|
344
|
-
state = render_staged(
|
|
344
|
+
state = render_staged(
|
|
345
|
+
pipe,
|
|
346
|
+
self._policy.placement('denoiser').device,
|
|
347
|
+
cancel_check=lambda: rt.raise_if_cancelled(ctx),
|
|
348
|
+
**call,
|
|
349
|
+
)
|
|
345
350
|
except CancelledError:
|
|
346
351
|
rt.free_vram()
|
|
347
352
|
raise
|
|
@@ -66,6 +66,38 @@ def is_resident(policy: DevicePolicy) -> bool:
|
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
# VRAM the denoise should still have spare, with the encoder kept resident, before parking is worth
|
|
70
|
+
# its two multi-GB copies. Sized for activations at a large resolution, not for the weights.
|
|
71
|
+
_ENCODER_RESIDENT_HEADROOM_MB = 8 * 1024
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def module_bytes(module: Any) -> int:
|
|
75
|
+
"""On-device size of a module's parameters and buffers."""
|
|
76
|
+
try:
|
|
77
|
+
params = sum(p.numel() * p.element_size() for p in module.parameters())
|
|
78
|
+
buffers = sum(b.numel() * b.element_size() for b in module.buffers())
|
|
79
|
+
return params + buffers
|
|
80
|
+
except Exception: # noqa: BLE001 - a size estimate must never break a run
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def should_park_encoder(policy: DevicePolicy, text_encoder: Any) -> bool:
|
|
85
|
+
"""Whether to evict the text encoder to the CPU for the denoise.
|
|
86
|
+
|
|
87
|
+
Parking buys VRAM at the cost of a GPU->CPU copy now and a CPU->GPU copy on the next run, which
|
|
88
|
+
on a large card is seconds of PCIe traffic for headroom that was never contended. So it is only
|
|
89
|
+
worth it when keeping the encoder would actually crowd the denoise.
|
|
90
|
+
"""
|
|
91
|
+
if policy.profile is Profile.LOWVRAM:
|
|
92
|
+
return True
|
|
93
|
+
free_mb = policy.free_vram_mb()
|
|
94
|
+
if free_mb is None:
|
|
95
|
+
return True # unmeasurable card: keep the conservative behaviour
|
|
96
|
+
# Measured with the encoder already on the card, so this is exactly what the denoise would get
|
|
97
|
+
# if it stayed. Parking would hand back its own size on top.
|
|
98
|
+
return free_mb < _ENCODER_RESIDENT_HEADROOM_MB
|
|
99
|
+
|
|
100
|
+
|
|
69
101
|
def configure_pipeline(pipe: Any, policy: DevicePolicy) -> None:
|
|
70
102
|
"""Place the pipeline and enable the low-VRAM savers the policy asks for."""
|
|
71
103
|
placement = policy.placement("denoiser")
|
|
@@ -340,9 +372,12 @@ def encoded_prompt_kwargs(
|
|
|
340
372
|
text_encoder.to(device)
|
|
341
373
|
with torch.no_grad():
|
|
342
374
|
kwargs = encode(device)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
375
|
+
park = should_park_encoder(policy, text_encoder)
|
|
376
|
+
if park:
|
|
377
|
+
# torchao's .to() round-trip on quantized weights is unreliable, so only the encoder
|
|
378
|
+
# moves.
|
|
379
|
+
text_encoder.to("cpu")
|
|
380
|
+
free_vram()
|
|
346
381
|
except Exception as error: # noqa: BLE001 - an optimization must never break generation
|
|
347
382
|
logger.warning(
|
|
348
383
|
"Text-encoder GPU encode failed (%s); denoising with the encoder resident.", error
|
|
@@ -350,7 +385,17 @@ def encoded_prompt_kwargs(
|
|
|
350
385
|
try_call(text_encoder.to, device)
|
|
351
386
|
return fallback()
|
|
352
387
|
|
|
353
|
-
|
|
388
|
+
if park:
|
|
389
|
+
logger.info(
|
|
390
|
+
"Text encoder parked on the CPU for the denoise | host RAM %.1fGB", host_ram_gb()
|
|
391
|
+
)
|
|
392
|
+
else:
|
|
393
|
+
logger.info(
|
|
394
|
+
"Text encoder kept on %s for the denoise (%.1fGB VRAM free) | host RAM %.1fGB",
|
|
395
|
+
device,
|
|
396
|
+
(policy.free_vram_mb() or 0) / 1024,
|
|
397
|
+
host_ram_gb(),
|
|
398
|
+
)
|
|
354
399
|
return kwargs
|
|
355
400
|
|
|
356
401
|
|
|
@@ -634,6 +679,7 @@ __all__ = [
|
|
|
634
679
|
"is_resident",
|
|
635
680
|
"load_image",
|
|
636
681
|
"lora_stack",
|
|
682
|
+
"module_bytes",
|
|
637
683
|
"oom_message",
|
|
638
684
|
"path_or_none",
|
|
639
685
|
"peak_vram_gb",
|
|
@@ -642,6 +688,7 @@ __all__ = [
|
|
|
642
688
|
"reset_peak_vram",
|
|
643
689
|
"resolve_seed",
|
|
644
690
|
"shrink_vae_tiles",
|
|
691
|
+
"should_park_encoder",
|
|
645
692
|
"smaller_resolutions",
|
|
646
693
|
"supports_prompt_embeds",
|
|
647
694
|
"text_encoder_detached",
|
|
@@ -43,6 +43,14 @@ class NodeDoneEvent:
|
|
|
43
43
|
takes: list[Take] = field(default_factory=list)
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class RunStartedEvent:
|
|
48
|
+
"""A queued run reached the worker. Without it nothing distinguishes queued from running, so a
|
|
49
|
+
client cannot tell a long model load from a run still waiting behind another."""
|
|
50
|
+
|
|
51
|
+
run_id: str
|
|
52
|
+
|
|
53
|
+
|
|
46
54
|
@dataclass(frozen=True)
|
|
47
55
|
class RunDoneEvent:
|
|
48
56
|
run_id: str
|
|
@@ -60,7 +68,9 @@ class ErrorEvent:
|
|
|
60
68
|
node_id: str | None = None
|
|
61
69
|
|
|
62
70
|
|
|
63
|
-
RunEvent =
|
|
71
|
+
RunEvent = (
|
|
72
|
+
ProgressEvent | NodeDoneEvent | RunStartedEvent | RunDoneEvent | CancelledEvent | ErrorEvent
|
|
73
|
+
)
|
|
64
74
|
|
|
65
75
|
|
|
66
76
|
class ProgressEmitter(ABC):
|
|
@@ -14,6 +14,7 @@ from .progress import (
|
|
|
14
14
|
ProgressEvent,
|
|
15
15
|
RunDoneEvent,
|
|
16
16
|
RunEvent,
|
|
17
|
+
RunStartedEvent,
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
|
|
@@ -89,6 +90,8 @@ def apply_event(state: RunState, event: RunEvent) -> None:
|
|
|
89
90
|
known = {t.id for t in state.takes}
|
|
90
91
|
state.takes.extend(t for t in event.takes if t.id not in known)
|
|
91
92
|
_recompute_fraction(state)
|
|
93
|
+
elif isinstance(event, RunStartedEvent):
|
|
94
|
+
state.status = RunStatus.RUNNING
|
|
92
95
|
elif isinstance(event, RunDoneEvent):
|
|
93
96
|
state.status = RunStatus.DONE
|
|
94
97
|
state.fraction = 1.0
|
|
@@ -19,7 +19,7 @@ from fastapi import FastAPI, Request, Response, WebSocket, WebSocketDisconnect
|
|
|
19
19
|
from fastapi.responses import FileResponse, JSONResponse
|
|
20
20
|
from fastapi.staticfiles import StaticFiles
|
|
21
21
|
|
|
22
|
-
from ..config import
|
|
22
|
+
from ..config import models_dirs
|
|
23
23
|
from ..device.memory import MemoryPolicy
|
|
24
24
|
from ..device.policy import DevicePolicy
|
|
25
25
|
from ..errors import GraphValidationError, UnknownNodeType
|
|
@@ -34,7 +34,7 @@ from .assets import AssetStore
|
|
|
34
34
|
from .manager import RunConflict, RunManager
|
|
35
35
|
from .rpc import EventBroadcaster, RpcRouter
|
|
36
36
|
from .run_store import RunStore
|
|
37
|
-
from .serialize import descriptor_json, event_json, run_json, take_json
|
|
37
|
+
from .serialize import descriptor_json, event_json, run_json, run_summary_json, take_json
|
|
38
38
|
|
|
39
39
|
# GET /v1/runs/<id> (the client's run-status poll) - but not /events or nested paths.
|
|
40
40
|
_RUN_POLL_PATH = re.compile(r"^/v1/runs/[^/]+$")
|
|
@@ -135,17 +135,23 @@ def create_app(
|
|
|
135
135
|
# reports no model requirements, which is what a torch-less install already showed.
|
|
136
136
|
reqs = requirements if requirements is not None else RequirementsRegistry()
|
|
137
137
|
assets = AssetStore(Path(asset_dir or "./.inline-assets"))
|
|
138
|
-
|
|
138
|
+
# An explicit models_root is that caller's whole world; otherwise scan every configured root so
|
|
139
|
+
# a custom --models-dir does not hide ./models.
|
|
140
|
+
catalog = ModelCatalog([Path(models_root)] if models_root else models_dirs())
|
|
139
141
|
takes_root = Path(takes_dir or "./.inline-takes")
|
|
140
142
|
manager = RunManager(registry, cache, policy, store=run_store, takes=FileTakeStore(takes_root))
|
|
141
143
|
rpc = rpc or RpcRouter()
|
|
142
144
|
events = events or EventBroadcaster()
|
|
143
145
|
# Host/GPU telemetry for the Trainer tab; only meaningful with the SPA (studio) backend wired.
|
|
144
146
|
stats = SystemStats(events) if studio_store is not None else None
|
|
147
|
+
# Assigned further down with the studio wiring; the lifespan closure reads it at startup.
|
|
148
|
+
activity_registry: Any = None
|
|
145
149
|
|
|
146
150
|
@asynccontextmanager
|
|
147
151
|
async def lifespan(app: FastAPI): # noqa: ANN202
|
|
148
152
|
manager.bind_loop(asyncio.get_running_loop())
|
|
153
|
+
if activity_registry is not None:
|
|
154
|
+
activity_registry.bind_loop(asyncio.get_running_loop())
|
|
149
155
|
catalog.ensure_dirs()
|
|
150
156
|
catalog.scan()
|
|
151
157
|
if stats is not None:
|
|
@@ -199,9 +205,15 @@ def create_app(
|
|
|
199
205
|
target = body.get("target")
|
|
200
206
|
if not isinstance(target, str):
|
|
201
207
|
return _error("invalid_request", "'target' is required.", 422)
|
|
208
|
+
meta = body.get("meta")
|
|
202
209
|
try:
|
|
203
210
|
graph = parse_graph(body.get("graph"))
|
|
204
|
-
record, created = manager.submit(
|
|
211
|
+
record, created = manager.submit(
|
|
212
|
+
graph,
|
|
213
|
+
target,
|
|
214
|
+
body.get("clientRunId"),
|
|
215
|
+
meta if isinstance(meta, dict) else None,
|
|
216
|
+
)
|
|
205
217
|
except GraphValidationError as error:
|
|
206
218
|
return _error("invalid_graph", str(error), 422, node_id=error.node_id)
|
|
207
219
|
except RunConflict as error:
|
|
@@ -211,6 +223,15 @@ def create_app(
|
|
|
211
223
|
status_code=201 if created else 200,
|
|
212
224
|
)
|
|
213
225
|
|
|
226
|
+
@app.get("/v1/runs")
|
|
227
|
+
async def list_runs() -> Response:
|
|
228
|
+
runs = [
|
|
229
|
+
run_summary_json(r.state, manager.queue_position(r.state.run_id))
|
|
230
|
+
for r in manager.list_runs()
|
|
231
|
+
if not r.done
|
|
232
|
+
]
|
|
233
|
+
return JSONResponse({"runs": runs})
|
|
234
|
+
|
|
214
235
|
@app.get("/v1/runs/{run_id}")
|
|
215
236
|
async def get_run(run_id: str) -> Response:
|
|
216
237
|
record = manager.get(run_id)
|
|
@@ -300,6 +321,7 @@ def create_app(
|
|
|
300
321
|
if studio_store is not None:
|
|
301
322
|
from ..extensions.handlers import register_extension_handlers
|
|
302
323
|
from ..extensions.install import Installer
|
|
324
|
+
from ..studio.activity import ActivityRegistry
|
|
303
325
|
from ..studio.fal import FalGeneration
|
|
304
326
|
from ..studio.generation import CoreGeneration
|
|
305
327
|
from ..studio.handlers import register_studio_handlers
|
|
@@ -318,21 +340,49 @@ def create_app(
|
|
|
318
340
|
def core_status() -> dict[str, Any]:
|
|
319
341
|
return {"running": True, "url": ""}
|
|
320
342
|
|
|
343
|
+
def rescan_models() -> dict[str, Any]:
|
|
344
|
+
"""Re-read the models roots and tell every client the registry moved.
|
|
345
|
+
|
|
346
|
+
The catalog caches its scan, and only a download or a finished training run refreshed
|
|
347
|
+
it - so a weight file dropped in by hand stayed invisible until Core restarted.
|
|
348
|
+
"""
|
|
349
|
+
catalog.rescan()
|
|
350
|
+
version = _version(registry, catalog)
|
|
351
|
+
events.broadcast("events:modelsChanged", {"registryVersion": version})
|
|
352
|
+
return {"registryVersion": version}
|
|
353
|
+
|
|
354
|
+
# Observes the manager, so a run submitted straight to POST /v1/runs is listed and
|
|
355
|
+
# cancellable here too, not just the ones this UI started.
|
|
356
|
+
activity = ActivityRegistry(studio_store, events)
|
|
357
|
+
activity.observe(manager)
|
|
358
|
+
activity.set_canceller("core", manager.cancel)
|
|
359
|
+
core_generation = CoreGeneration(studio_store, manager, events, registry, activity)
|
|
360
|
+
fal_generation = FalGeneration(studio_store, events, activity)
|
|
361
|
+
training_service = Training(
|
|
362
|
+
studio_store, events, on_output=catalog.rescan, activity=activity
|
|
363
|
+
)
|
|
364
|
+
activity.set_canceller("fal", fal_generation.cancel_run)
|
|
365
|
+
activity.set_canceller("training", training_service.cancel)
|
|
366
|
+
activity_registry = activity
|
|
367
|
+
|
|
321
368
|
register_studio_handlers(
|
|
322
369
|
rpc,
|
|
323
370
|
studio_store,
|
|
324
371
|
core_models=core_models,
|
|
325
372
|
core_status=core_status,
|
|
326
|
-
generation=
|
|
327
|
-
fal_generation=
|
|
373
|
+
generation=core_generation,
|
|
374
|
+
fal_generation=fal_generation,
|
|
328
375
|
timeline=Timeline(studio_store, events),
|
|
329
|
-
training=
|
|
376
|
+
training=training_service,
|
|
377
|
+
activity=activity,
|
|
330
378
|
# Explicit model downloads write into models/; rescan so new files bump the registry.
|
|
331
379
|
# The policy lets the requirements popup show a memory fit estimate before a load;
|
|
332
380
|
# the requirements registry says which node types have models at all.
|
|
333
381
|
model_downloads=ModelDownloads(
|
|
334
382
|
events, on_change=catalog.rescan, policy=policy, requirements=reqs
|
|
335
383
|
),
|
|
384
|
+
model_tree=catalog.tree,
|
|
385
|
+
model_rescan=rescan_models,
|
|
336
386
|
)
|
|
337
387
|
|
|
338
388
|
register_extension_handlers(
|
|
@@ -347,6 +397,23 @@ def create_app(
|
|
|
347
397
|
),
|
|
348
398
|
)
|
|
349
399
|
|
|
400
|
+
@app.get("/download/snapshot/{run_id}/{step}")
|
|
401
|
+
async def download_snapshot(run_id: str, step: int) -> Response:
|
|
402
|
+
# A mid-run LoRA lives in the project's working dir, which /media does not surface and
|
|
403
|
+
# no model picker scans, so a download is the only way to get one out of the browser.
|
|
404
|
+
try:
|
|
405
|
+
root = (studio_store.folder() / "training_runs" / run_id / "snapshots").resolve()
|
|
406
|
+
except RuntimeError:
|
|
407
|
+
return Response("No project open", status_code=404)
|
|
408
|
+
target = (root / f"step-{step:06d}.safetensors").resolve()
|
|
409
|
+
if target.parent != root: # no traversal out of the run's own folder
|
|
410
|
+
return Response("Forbidden", status_code=403)
|
|
411
|
+
if not target.is_file():
|
|
412
|
+
return Response("Not found", status_code=404)
|
|
413
|
+
return FileResponse(
|
|
414
|
+
target, filename=target.name, media_type="application/octet-stream"
|
|
415
|
+
)
|
|
416
|
+
|
|
350
417
|
@app.get("/media/{media_path:path}")
|
|
351
418
|
async def media(media_path: str, request: Request) -> Response:
|
|
352
419
|
try:
|