inline-core 1.2.63__tar.gz → 1.2.65__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.63 → inline_core-1.2.65}/PKG-INFO +2 -1
- {inline_core-1.2.63 → inline_core-1.2.65}/pyproject.toml +11 -4
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/device/detect.py +82 -1
- inline_core-1.2.65/src/inline_core/device/probe.py +76 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/load.py +8 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/server/__main__.py +20 -7
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/handlers.py +2 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/store.py +40 -1
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/training.py +36 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/training/arch.py +16 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/training/cache.py +12 -2
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/training/caption.py +19 -2
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/training/dataset.py +15 -5
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/training/h3.py +112 -27
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/training/models.py +60 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/training/trainer.py +22 -4
- inline_core-1.2.65/tests/test_device_detect.py +323 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_minimaxh3_training.py +116 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_store.py +32 -0
- inline_core-1.2.65/tests/test_studio_training.py +175 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_training_models.py +65 -0
- inline_core-1.2.65/tests/test_webui_install.py +338 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/uv.lock +47 -85
- {inline_core-1.2.63 → inline_core-1.2.65}/webui.bat +192 -7
- {inline_core-1.2.63 → inline_core-1.2.65}/webui.sh +229 -7
- inline_core-1.2.63/tests/test_device_detect.py +0 -126
- inline_core-1.2.63/tests/test_studio_training.py +0 -81
- inline_core-1.2.63/tests/test_webui_install.py +0 -154
- {inline_core-1.2.63 → inline_core-1.2.65}/.gitignore +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/.python-version +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/CLAUDE.md +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/README.md +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/main.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/scripts/flux2_train_matrix.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/scripts/minimax_h3_lora_check.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/scripts/minimax_h3_train_matrix.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/scripts/reference.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/components/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/components/conditioning.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/components/interfaces.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/config.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/device/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/device/auto.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/device/memory.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/device/policy.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/device/types.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/errors.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/api.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/constraints.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/fetch.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/handlers.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/importer.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/install.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/loader.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/manifest.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/models.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/paths.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/scanner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/state.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/extensions/tools.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/ffmpeg.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/graph/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/graph/cache.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/graph/descriptor.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/graph/executor.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/graph/loader_runners.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/graph/primitives.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/graph/registry.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/graph/runners.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/graph/schema.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/graph/topo.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/graph/validate.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/media.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/catalog.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/checkpoint.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/controlspace.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/flux2/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/flux2/controlnet.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/flux2/embeds.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/flux2/provider.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/flux2/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/flux2/runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/flux2/variants.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/keymap.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/krea2/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/krea2/convert.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/krea2/depth_control.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/krea2/img2img.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/krea2/provider.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/krea2/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/krea2/runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/loaders.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/lora.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/adaln.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/keys.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/pipeline.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/provider.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vae_keys.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3_audio.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/before_denoise.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/before_encoder.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/decoders.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/denoise.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/encoders.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/modular_blocks_minimax_h3.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/modular_pipeline.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/packing.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/packing_ref2va.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/scheduling_minimax_h3.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/minimaxh3/vendor/transformer_minimax_h3.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/offload.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/pipeline_runtime.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/prepared.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/preprocess/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/preprocess/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/preprocess/runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/references.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/sampling.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/video_params.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/zimage/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/zimage/primitives.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/zimage/provider.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/zimage/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/models/zimage/runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/parallel/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/parallel/config.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/parallel/group.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/parallel/launch.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/parallel/protocol.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/parallel/registry.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/parallel/worker.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/runtime/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/runtime/context.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/runtime/file_store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/runtime/progress.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/runtime/run.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/runtime/store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/runtime/video_encode.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/sampling/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/sampling/batch.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/server/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/server/app.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/server/assets.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/server/bootstrap.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/server/frontend.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/server/manager.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/server/rpc.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/server/run_store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/server/serialize.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/assets.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/config.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/fal.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/frames.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/generation.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/graph_build.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/image_meta.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/models.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/moodboard.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/peaks.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/recipe.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/schema.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/system_stats.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/timeline/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/timeline/compose.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/timeline/ffmpeg.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/timeline/render.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/timeline/resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/studio/training_store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/takes.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/training/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/training/__main__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/src/inline_core/training/protocol.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/conftest.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/helpers.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_cache.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_catalog.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_checkpoint.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_config.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_executor.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_extension_api.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_extension_install.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_extension_manifest.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_extension_resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_extension_scanner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_extension_spine.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_extension_state.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_file_store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_flux2_controlnet.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_flux2_folder.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_flux2_resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_flux2_runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_flux2_training.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_flux2_variants.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_frontend_serving.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_hidden_nodes.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_keymap.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_krea2_convert.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_krea2_depth_control.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_krea2_requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_krea2_runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_loader_runners.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_loaders.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_lora.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_lora_download.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_memory_policy.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_minimaxh3_adaln.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_minimaxh3_keys.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_minimaxh3_load.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_minimaxh3_nodes.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_model_requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_offload_prepared.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_output_kind_contract.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_parallel_group.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_pipeline_cache.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_primitives.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_recipe.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_references.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_rpc_bridge.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_run_store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_sampling.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_schema.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_server.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_staged_residency.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_assets.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_fal.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_frames.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_generation.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_graph_build.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_models.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_moodboard.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_multi_reference.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_node_size.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_peaks.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_rpc.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_schema.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_studio_timeline.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_take_bytes.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_topo.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_training_arch.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_training_dataset.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_training_resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_validate.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_video_encode.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_video_params.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_xfuser_sampler.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_zimage_primitives.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_zimage_resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.65}/tests/test_zimage_runner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: inline-core
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.65
|
|
4
4
|
Summary: The generation engine behind Inline Studio.
|
|
5
5
|
License-Expression: GPL-3.0-or-later
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -8,6 +8,7 @@ Requires-Dist: numpy>=1.26
|
|
|
8
8
|
Requires-Dist: psutil>=5.9
|
|
9
9
|
Provides-Extra: all
|
|
10
10
|
Requires-Dist: accelerate>=0.30; extra == 'all'
|
|
11
|
+
Requires-Dist: av>=12; extra == 'all'
|
|
11
12
|
Requires-Dist: bitsandbytes>=0.43; (platform_system != 'Darwin') and extra == 'all'
|
|
12
13
|
Requires-Dist: controlnet-aux>=0.0.7; extra == 'all'
|
|
13
14
|
Requires-Dist: diffusers==0.39.0; extra == 'all'
|
|
@@ -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.65"
|
|
5
5
|
description = "The generation engine behind Inline Studio."
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "GPL-3.0-or-later"
|
|
@@ -82,6 +82,8 @@ all = [
|
|
|
82
82
|
"accelerate>=0.30",
|
|
83
83
|
"safetensors>=0.4",
|
|
84
84
|
"torchao>=0.14",
|
|
85
|
+
# Clip decode for MiniMax H3 LoRA training, and H3's reference node.
|
|
86
|
+
"av>=12",
|
|
85
87
|
"scipy>=1.11",
|
|
86
88
|
"huggingface_hub>=0.23",
|
|
87
89
|
"controlnet-aux>=0.0.7",
|
|
@@ -101,13 +103,18 @@ all = [
|
|
|
101
103
|
]
|
|
102
104
|
|
|
103
105
|
# PyPI ships CPU-only torch on Windows; pin the CUDA index there (pip: see requirements.txt).
|
|
106
|
+
# cu126 rather than a newer index because it is the last one still built for sm_50..sm_70 (Maxwell
|
|
107
|
+
# through Volta), so it is the safe default for a card we know nothing about. Blackwell (sm_120) has
|
|
108
|
+
# no wheel here at all - webui.sh/.bat detect the compute capability and pass the matching index.
|
|
104
109
|
[[tool.uv.index]]
|
|
105
|
-
name = "pytorch-
|
|
106
|
-
url = "https://download.pytorch.org/whl/
|
|
110
|
+
name = "pytorch-cu126"
|
|
111
|
+
url = "https://download.pytorch.org/whl/cu126"
|
|
107
112
|
explicit = true
|
|
108
113
|
|
|
114
|
+
# Launcher installs pass `--no-sources-package torch` so their detected index wins over this pin;
|
|
115
|
+
# anything else added to this table would still apply to them.
|
|
109
116
|
[tool.uv.sources]
|
|
110
|
-
torch = [{ index = "pytorch-
|
|
117
|
+
torch = [{ index = "pytorch-cu126", marker = "sys_platform == 'win32'" }]
|
|
111
118
|
|
|
112
119
|
[project.scripts]
|
|
113
120
|
inline-studio = "inline_core.server.__main__:main"
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from string import ascii_letters
|
|
7
|
+
|
|
5
8
|
from .types import Device, DeviceKind
|
|
6
9
|
|
|
7
10
|
|
|
@@ -76,10 +79,88 @@ def cpu_only_torch_warning() -> str | None:
|
|
|
76
79
|
"This install has a CPU-ONLY build of PyTorch, but an NVIDIA GPU was detected. "
|
|
77
80
|
"Generation will run on the CPU and be roughly 100x slower. Reinstall torch from the CUDA "
|
|
78
81
|
"index, e.g. `pip install --force-reinstall --extra-index-url "
|
|
79
|
-
"https://download.pytorch.org/whl/
|
|
82
|
+
"https://download.pytorch.org/whl/cu126 torch` (or re-run `webui.sh --install`)."
|
|
80
83
|
)
|
|
81
84
|
|
|
82
85
|
|
|
86
|
+
def _parse_arch(arch: str) -> tuple[int, int] | None:
|
|
87
|
+
"""``sm_120`` to ``(12, 0)``.
|
|
88
|
+
|
|
89
|
+
The minor is the **last digit** and the major is everything before it, so ``sm_100`` is (10, 0).
|
|
90
|
+
Reading left to right instead gives (1, 20) and puts the bug precisely on the Blackwell parts.
|
|
91
|
+
Tuned variants (``sm_90a``) drop their trailing letters.
|
|
92
|
+
"""
|
|
93
|
+
digits = arch[3:].rstrip(ascii_letters)
|
|
94
|
+
if len(digits) < 2 or not digits.isdigit():
|
|
95
|
+
return None
|
|
96
|
+
return int(digits[:-1]), int(digits[-1])
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def arch_list_covers(arches: Iterable[str], major: int, minor: int) -> bool:
|
|
100
|
+
"""Whether a wheel's arch list has kernels that will run on a ``(major, minor)`` device.
|
|
101
|
+
|
|
102
|
+
CUDA binary compatibility runs **upward within one major only**: an ``sm_8x`` cubin executes on
|
|
103
|
+
any ``sm_8y`` where ``y >= x``. So ``sm_86`` covers an sm_89 Ada card, which is why exact
|
|
104
|
+
matching told every RTX 40-series owner their install was broken. Crossing a major never works,
|
|
105
|
+
so ``sm_90`` does not cover sm_120 and the Blackwell warning stays correct.
|
|
106
|
+
|
|
107
|
+
PTX entries (``compute_90``) JIT forward but are not matched here: they do not start with
|
|
108
|
+
``sm_``, so a PTX-carrying wheel still warns. Rare, and warning is the safe way to be wrong.
|
|
109
|
+
"""
|
|
110
|
+
for arch in arches:
|
|
111
|
+
parsed = _parse_arch(arch)
|
|
112
|
+
if parsed is not None and parsed[0] == major and parsed[1] <= minor:
|
|
113
|
+
return True
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def unsupported_arch_warning() -> str | None:
|
|
118
|
+
"""A warning when the installed torch has no kernels for the GPU it is about to run on.
|
|
119
|
+
|
|
120
|
+
An RTX 50-series card (sm_120) under a wheel built for sm_50..sm_90 is the sharpest case: torch
|
|
121
|
+
reports a CUDA build and a visible device, so every check in ``cpu_only_torch_warning`` passes,
|
|
122
|
+
and the only clue the user gets is PyTorch's own late UserWarning followed by kernels that
|
|
123
|
+
cannot launch. Returns None on any uncertainty, same as the rest of this module.
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
import torch
|
|
127
|
+
|
|
128
|
+
if getattr(torch.version, "hip", None):
|
|
129
|
+
return None # the sm_ vocabulary is NVIDIA's; HIP reports gfx arches through it
|
|
130
|
+
get_arch_list = getattr(torch.cuda, "get_arch_list", None)
|
|
131
|
+
get_capability = getattr(torch.cuda, "get_device_capability", None)
|
|
132
|
+
device_count = getattr(torch.cuda, "device_count", None)
|
|
133
|
+
if get_arch_list is None or get_capability is None or device_count is None:
|
|
134
|
+
return None
|
|
135
|
+
if device_count() < 1:
|
|
136
|
+
return None
|
|
137
|
+
arches = [str(arch) for arch in get_arch_list() if str(arch).startswith("sm_")]
|
|
138
|
+
if not arches:
|
|
139
|
+
return None # a CPU-only build; cpu_only_torch_warning owns that case
|
|
140
|
+
major, minor = get_capability(0)
|
|
141
|
+
target = f"sm_{major}{minor}"
|
|
142
|
+
if arch_list_covers(arches, major, minor):
|
|
143
|
+
return None
|
|
144
|
+
name = _device_name(torch) or "The detected NVIDIA GPU"
|
|
145
|
+
return (
|
|
146
|
+
f"{name} is compute capability {target}, but this install's PyTorch only has kernels "
|
|
147
|
+
f"for {' '.join(arches)}. Generation will fail or fall back to the CPU. Re-run "
|
|
148
|
+
"`webui.sh --install` (Windows: `.\\webui.bat --install`) to pick the wheel index that "
|
|
149
|
+
"matches the card, or force one with `--torch-index cu130`. On a Blackwell card whose "
|
|
150
|
+
"driver predates R580, use `cu128`, which still has sm_120 but is frozen at torch 2.11."
|
|
151
|
+
)
|
|
152
|
+
except Exception: # noqa: BLE001 - a diagnostic must never break startup
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _device_name(torch: object) -> str | None:
|
|
157
|
+
try:
|
|
158
|
+
get_name = getattr(getattr(torch, "cuda", None), "get_device_name", None)
|
|
159
|
+
return str(get_name(0)) if get_name is not None else None
|
|
160
|
+
except Exception: # noqa: BLE001
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
|
|
83
164
|
def _nvidia_gpu_present() -> bool:
|
|
84
165
|
"""Best-effort check for NVIDIA hardware that does NOT rely on torch's CUDA support - that is
|
|
85
166
|
the whole point, since we are called precisely when torch cannot see the GPU.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Install-time probe: which torch is installed, and does it have kernels for this card?
|
|
2
|
+
|
|
3
|
+
Run as ``python -m inline_core.device.probe`` by both launchers. They cannot share the
|
|
4
|
+
compatibility rule any other way, and restating it in bash and batch is how the two drift.
|
|
5
|
+
|
|
6
|
+
Always prints one JSON object and always exits 0, so a shell branches on ``status`` rather than
|
|
7
|
+
parsing stderr. A **nonzero exit means the probe itself failed**, which callers must treat as
|
|
8
|
+
unknown and never as covered.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from .detect import arch_list_covers
|
|
19
|
+
|
|
20
|
+
#: A local version tag we recognise as a pytorch.org build, e.g. ``2.13.0+cu130`` or ``2.9.0+cpu``.
|
|
21
|
+
#: Only these are safe to replace automatically: a ROCm build, a nightly or a hand-built wheel will
|
|
22
|
+
#: also fail the arch check, and silently reinstalling over someone's deliberate choice is worse
|
|
23
|
+
#: than the wrong wheel.
|
|
24
|
+
_REPLACEABLE_LOCAL = re.compile(r"\+(?:cpu|cu\d+)$")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def probe() -> dict[str, Any]:
|
|
28
|
+
"""What the launcher needs to decide whether to replace torch. Never raises."""
|
|
29
|
+
out: dict[str, Any] = {
|
|
30
|
+
"status": "unknown",
|
|
31
|
+
"torch": None,
|
|
32
|
+
"cuda": None,
|
|
33
|
+
"archList": [],
|
|
34
|
+
"capability": None,
|
|
35
|
+
"replaceable": False,
|
|
36
|
+
}
|
|
37
|
+
try:
|
|
38
|
+
import torch
|
|
39
|
+
except Exception: # noqa: BLE001 - no torch yet is a normal first install
|
|
40
|
+
out["status"] = "no-torch"
|
|
41
|
+
return out
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
out["torch"] = str(torch.__version__)
|
|
45
|
+
out["cuda"] = getattr(torch.version, "cuda", None)
|
|
46
|
+
out["replaceable"] = bool(_REPLACEABLE_LOCAL.search(out["torch"]))
|
|
47
|
+
# HIP reports gfx arches through the sm_ call, so the rule does not apply. Never
|
|
48
|
+
# auto-replace a ROCm build.
|
|
49
|
+
if getattr(torch.version, "hip", None):
|
|
50
|
+
out["status"] = "rocm"
|
|
51
|
+
out["replaceable"] = False
|
|
52
|
+
return out
|
|
53
|
+
arches = [str(a) for a in torch.cuda.get_arch_list() if str(a).startswith("sm_")]
|
|
54
|
+
out["archList"] = arches
|
|
55
|
+
if not arches:
|
|
56
|
+
out["status"] = "cpu-only"
|
|
57
|
+
return out
|
|
58
|
+
if torch.cuda.device_count() < 1:
|
|
59
|
+
out["status"] = "no-gpu"
|
|
60
|
+
return out
|
|
61
|
+
major, minor = torch.cuda.get_device_capability(0)
|
|
62
|
+
out["capability"] = [major, minor]
|
|
63
|
+
out["status"] = "covered" if arch_list_covers(arches, major, minor) else "uncovered"
|
|
64
|
+
except Exception: # noqa: BLE001 - a broken torch is exactly what we are here to detect
|
|
65
|
+
out["status"] = "unknown"
|
|
66
|
+
return out
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def main() -> int:
|
|
70
|
+
json.dump(probe(), sys.stdout)
|
|
71
|
+
sys.stdout.write("\n")
|
|
72
|
+
return 0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
raise SystemExit(main())
|
|
@@ -157,6 +157,14 @@ def load_transformer(
|
|
|
157
157
|
|
|
158
158
|
if lora_plan:
|
|
159
159
|
_finish_fuse(model, lora_plan, fused)
|
|
160
|
+
# Announced because a fused LoRA is otherwise invisible: it changes the weights and nothing
|
|
161
|
+
# else, so a run with no adapter and a run with one that never arrived look identical in
|
|
162
|
+
# the log, and the only way to tell them apart was to render twice and compare.
|
|
163
|
+
logger.info(
|
|
164
|
+
"MiniMax H3: fused %d LoRA layer(s) from %s",
|
|
165
|
+
len(lora_plan),
|
|
166
|
+
", ".join(f"{Path(ref.file).name}@{ref.strength:g}" for ref in loras),
|
|
167
|
+
)
|
|
160
168
|
_assert_nothing_left_on_meta(model, filled)
|
|
161
169
|
model.eval()
|
|
162
170
|
return model
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import logging as _logging
|
|
5
6
|
import os as _os
|
|
6
7
|
|
|
7
8
|
# Default PyTorch to expandable CUDA segments before torch initializes its allocator (it reads this
|
|
@@ -19,7 +20,7 @@ _os.environ.setdefault("HF_HUB_DISABLE_XET", "1")
|
|
|
19
20
|
import uvicorn
|
|
20
21
|
|
|
21
22
|
from ..config import data_dir, server_host, server_port
|
|
22
|
-
from ..device.detect import cpu_only_torch_warning
|
|
23
|
+
from ..device.detect import cpu_only_torch_warning, unsupported_arch_warning
|
|
23
24
|
from ..device.memory import MemoryPolicy
|
|
24
25
|
from ..extensions.loader import LoadedExtension
|
|
25
26
|
from ..graph.cache import InMemoryCache
|
|
@@ -35,7 +36,20 @@ from .rpc import EventBroadcaster, RpcRouter
|
|
|
35
36
|
from .run_store import SqliteRunStore
|
|
36
37
|
|
|
37
38
|
|
|
39
|
+
def _quiet_dependency_noise() -> None:
|
|
40
|
+
"""Two third-party import-time warnings that are noise here, not signal.
|
|
41
|
+
|
|
42
|
+
diffusers cannot build its torchao ``torch.load`` allowlist because torchao dropped
|
|
43
|
+
``uint4_layout``; we never load torchao-serialized checkpoints, and quantization itself is
|
|
44
|
+
unaffected. torch's pytree then warns about torchao registering Enum subclasses - torchao's to
|
|
45
|
+
fix, not ours. Both loggers have exactly one warning site, so this hides nothing else.
|
|
46
|
+
"""
|
|
47
|
+
for name in ("diffusers.quantizers.torchao.torchao_quantizer", "torch.utils._pytree"):
|
|
48
|
+
_logging.getLogger(name).setLevel(_logging.ERROR)
|
|
49
|
+
|
|
50
|
+
|
|
38
51
|
def main() -> None:
|
|
52
|
+
_quiet_dependency_noise() # before register_models, which is what pulls diffusers/torchao in
|
|
39
53
|
policy = MemoryPolicy()
|
|
40
54
|
registry = build_default_registry()
|
|
41
55
|
data = data_dir()
|
|
@@ -47,20 +61,17 @@ def main() -> None:
|
|
|
47
61
|
rpc = RpcRouter()
|
|
48
62
|
events = EventBroadcaster()
|
|
49
63
|
requirements = RequirementsRegistry()
|
|
50
|
-
|
|
64
|
+
_, extensions = register_models(
|
|
51
65
|
registry, take_store, policy, requirements=requirements, rpc=rpc, events=events
|
|
52
66
|
)
|
|
53
|
-
print(f"Registered models: {registered or 'none (source nodes only)'}")
|
|
54
67
|
if extensions:
|
|
55
68
|
print(f"Extensions: {_extension_summary(extensions)}")
|
|
56
69
|
# A CPU-only torch wheel on a CUDA machine is a silent ~100x slowdown, so say it loudly here
|
|
57
70
|
# rather than letting the user conclude the engine is just slow.
|
|
58
|
-
torch_warning = cpu_only_torch_warning()
|
|
71
|
+
torch_warning = cpu_only_torch_warning() or unsupported_arch_warning()
|
|
59
72
|
if torch_warning:
|
|
60
73
|
print(f"WARNING: {torch_warning}")
|
|
61
74
|
frontend_root = resolve_frontend_root()
|
|
62
|
-
fe = frontend_root or "none (API only); use --front-end-root or install the frontend package"
|
|
63
|
-
print(f"Frontend: {fe}")
|
|
64
75
|
# The Studio app-backend: Core is the sole native backend (projects, frames, moodboard, assets,
|
|
65
76
|
# generation, fal, timeline). Every InlineStudioApi channel is handled here.
|
|
66
77
|
store = StudioStore(
|
|
@@ -68,7 +79,9 @@ def main() -> None:
|
|
|
68
79
|
studio_config.workspace_dir(),
|
|
69
80
|
default_core_url=studio_config.DEFAULT_CORE_URL,
|
|
70
81
|
)
|
|
71
|
-
|
|
82
|
+
# Reopen whatever was open before the restart, so a browser tab left open across it keeps
|
|
83
|
+
# working instead of failing every call with "No project is open."
|
|
84
|
+
store.restore_last_project()
|
|
72
85
|
app = create_app(
|
|
73
86
|
registry=registry,
|
|
74
87
|
cache=InMemoryCache(),
|
|
@@ -88,6 +88,7 @@ def register_studio_handlers(
|
|
|
88
88
|
reg("project:openZip", lambda: None)
|
|
89
89
|
reg("project:listRecent", store.list_recent)
|
|
90
90
|
reg("project:current", store.current_project)
|
|
91
|
+
reg("project:close", store.close_project)
|
|
91
92
|
reg("project:mediaDirs", store.media_dirs)
|
|
92
93
|
reg("project:export", lambda _path: None) # zip export: pending (see plan)
|
|
93
94
|
reg("dialog:pickDirectory", lambda *_: str(cfg.workspace_dir()))
|
|
@@ -264,6 +265,7 @@ def register_studio_handlers(
|
|
|
264
265
|
reg("training:createDataset", lambda inp: training.create_dataset(inp))
|
|
265
266
|
reg("training:listItems", lambda did: training.list_items(did))
|
|
266
267
|
reg("training:addItems", lambda did, aids: training.add_items(did, aids))
|
|
268
|
+
reg("training:addFromPath", lambda did, path: training.add_from_path(did, path))
|
|
267
269
|
reg("training:removeItem", lambda iid: training.remove_item(iid))
|
|
268
270
|
reg("training:setCaption", lambda iid, cap: training.set_caption(iid, cap))
|
|
269
271
|
reg("training:autoCaption",
|
|
@@ -131,6 +131,7 @@ class StudioStore:
|
|
|
131
131
|
project = {"id": pid, "name": name, "path": str(folder), "createdAt": now, "updatedAt": now}
|
|
132
132
|
self._current = project
|
|
133
133
|
self.record_recent(name, str(folder))
|
|
134
|
+
self._remember_last_project(str(folder))
|
|
134
135
|
return project
|
|
135
136
|
|
|
136
137
|
def open_project(self, selected: str) -> dict[str, Any]:
|
|
@@ -143,6 +144,7 @@ class StudioStore:
|
|
|
143
144
|
project = self._load_project_row(folder)
|
|
144
145
|
self._current = project
|
|
145
146
|
self.record_recent(project["name"], str(folder))
|
|
147
|
+
self._remember_last_project(str(folder))
|
|
146
148
|
return project
|
|
147
149
|
|
|
148
150
|
def _load_project_row(self, folder: Path) -> dict[str, Any]:
|
|
@@ -160,7 +162,44 @@ class StudioStore:
|
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
def current_project(self) -> dict[str, Any] | None:
|
|
163
|
-
return self._current
|
|
165
|
+
return self._current or self.restore_last_project()
|
|
166
|
+
|
|
167
|
+
def close_project(self) -> None:
|
|
168
|
+
self.close()
|
|
169
|
+
self._current = None
|
|
170
|
+
self._remember_last_project(None)
|
|
171
|
+
|
|
172
|
+
# --- last opened project ----------------------------------------------------------------------
|
|
173
|
+
# The open project is otherwise only in memory, so restarting Core left a still-open browser tab
|
|
174
|
+
# failing every call with "No project is open." Kept in its own file rather than settings.json,
|
|
175
|
+
# because _save_settings rewrites that from get_settings() and would drop any key it omits.
|
|
176
|
+
|
|
177
|
+
def _last_project_file(self) -> Path:
|
|
178
|
+
return self._app_data / "last_project"
|
|
179
|
+
|
|
180
|
+
def _remember_last_project(self, path: str | None) -> None:
|
|
181
|
+
file = self._last_project_file()
|
|
182
|
+
try:
|
|
183
|
+
if path:
|
|
184
|
+
file.write_text(path, encoding="utf-8")
|
|
185
|
+
elif file.exists():
|
|
186
|
+
file.unlink()
|
|
187
|
+
except OSError:
|
|
188
|
+
pass # never fail an open just because the marker could not be written
|
|
189
|
+
|
|
190
|
+
def restore_last_project(self) -> dict[str, Any] | None:
|
|
191
|
+
"""Reopen the project left open at shutdown. Best-effort: a moved or deleted one is
|
|
192
|
+
forgotten and the launcher shows instead."""
|
|
193
|
+
if self._conn is not None:
|
|
194
|
+
return self._current
|
|
195
|
+
file = self._last_project_file()
|
|
196
|
+
if not file.exists():
|
|
197
|
+
return None
|
|
198
|
+
try:
|
|
199
|
+
return self.open_project(file.read_text(encoding="utf-8").strip())
|
|
200
|
+
except (OSError, ValueError, sqlite3.Error):
|
|
201
|
+
self._remember_last_project(None)
|
|
202
|
+
return None
|
|
164
203
|
|
|
165
204
|
def media_dirs(self) -> dict[str, str]:
|
|
166
205
|
if self._folder is None:
|
|
@@ -71,6 +71,42 @@ class Training:
|
|
|
71
71
|
def add_items(self, dataset_id: str, asset_ids: list[str]) -> list[dict[str, Any]]:
|
|
72
72
|
return ts.add_items(self._conn(), dataset_id, asset_ids)
|
|
73
73
|
|
|
74
|
+
def add_from_path(self, dataset_id: str, path: str) -> list[dict[str, Any]]:
|
|
75
|
+
"""Import a folder of images and clips into the dataset, captions included.
|
|
76
|
+
|
|
77
|
+
The browser cannot hand over a folder, and uploading a clip dataset through it means
|
|
78
|
+
pushing gigabytes over HTTP to a server that can already see the disk. Paths come from the
|
|
79
|
+
client here the same way ``assets:importPaths`` already accepts them.
|
|
80
|
+
"""
|
|
81
|
+
from . import assets as ax
|
|
82
|
+
|
|
83
|
+
folder = Path(path).expanduser()
|
|
84
|
+
if not folder.is_dir():
|
|
85
|
+
raise ValueError(f"Not a folder: {path}")
|
|
86
|
+
conn, project = self._conn(), self._store.folder()
|
|
87
|
+
media = [
|
|
88
|
+
p
|
|
89
|
+
for p in sorted(folder.iterdir())
|
|
90
|
+
if p.is_file() and ax.kind_for_file(str(p)) in ("image", "video")
|
|
91
|
+
]
|
|
92
|
+
if not media:
|
|
93
|
+
raise ValueError(f"No images or clips in {path}")
|
|
94
|
+
|
|
95
|
+
imported = [(p, ax.import_file(conn, project, str(p), None)) for p in media]
|
|
96
|
+
added = ts.add_items(conn, dataset_id, [a["id"] for _p, a in imported if a])
|
|
97
|
+
|
|
98
|
+
# `NNNN.txt` beside `NNNN.png` is the caption, the convention the drag-drop path already
|
|
99
|
+
# follows. Only newly added items are touched, so re-importing cannot clobber an edit.
|
|
100
|
+
by_asset = {item["assetId"]: item for item in added}
|
|
101
|
+
for source, asset in imported:
|
|
102
|
+
item = by_asset.get(asset["id"]) if asset else None
|
|
103
|
+
sidecar = source.with_suffix(".txt")
|
|
104
|
+
if item and sidecar.is_file():
|
|
105
|
+
caption = sidecar.read_text(encoding="utf-8").strip()
|
|
106
|
+
if caption:
|
|
107
|
+
ts.set_caption(conn, item["id"], caption)
|
|
108
|
+
return ts.list_items(conn, dataset_id)
|
|
109
|
+
|
|
74
110
|
def remove_item(self, item_id: str) -> None:
|
|
75
111
|
ts.remove_item(self._conn(), item_id)
|
|
76
112
|
|
|
@@ -333,6 +333,22 @@ ARCHS: dict[str, TrainingArch] = {
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
|
|
336
|
+
def clip_frames(arch: TrainingArch, seconds: Any) -> int:
|
|
337
|
+
"""How many frames of a clip to train on, snapped to the arch's frame grid.
|
|
338
|
+
|
|
339
|
+
1 for an arch with no clip support, which is what a still costs. For H3 the floor is a whole
|
|
340
|
+
17-frame chunk plus the 5-frame head, so a shorter request rounds up to 0.92s rather than being
|
|
341
|
+
refused; the VAE has no way to encode less.
|
|
342
|
+
"""
|
|
343
|
+
if arch.key != MINIMAX_H3:
|
|
344
|
+
return 1
|
|
345
|
+
from ..models.minimaxh3.vendor.packing import MINIMAX_H3_FPS
|
|
346
|
+
from ..models.minimaxh3.vendor.packing_ref2va import trim_reference_num_frames
|
|
347
|
+
|
|
348
|
+
wanted = round(float(seconds) * MINIMAX_H3_FPS) if seconds else 1
|
|
349
|
+
return trim_reference_num_frames(max(1, wanted))
|
|
350
|
+
|
|
351
|
+
|
|
336
352
|
def get(key: str | None) -> TrainingArch:
|
|
337
353
|
"""The arch to train. Defaults to Z-Image so a run predating Krea 2 still resumes."""
|
|
338
354
|
arch = ARCHS.get(key or Z_IMAGE)
|
|
@@ -7,6 +7,7 @@ them (see ``h3.py``). Either way the transformer loads into a card with nothing
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
from collections.abc import Callable
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
12
13
|
from . import arch as archs
|
|
@@ -28,13 +29,22 @@ def build(
|
|
|
28
29
|
*,
|
|
29
30
|
flip: bool = False,
|
|
30
31
|
dropout: float = 0.0,
|
|
32
|
+
clip_frames: int = 1,
|
|
33
|
+
clip_window: str = "start",
|
|
34
|
+
on_status: Callable[[str], None] | None = None,
|
|
31
35
|
) -> tuple[list[dict[str, Any]], dict[str, Any] | None, float]:
|
|
32
|
-
"""Return ``(items, unconditional, shift)``, all as CPU tensors, with the encoders freed.
|
|
36
|
+
"""Return ``(items, unconditional, shift)``, all as CPU tensors, with the encoders freed.
|
|
37
|
+
|
|
38
|
+
``on_status`` reports phase progress to the caller, which forwards it over the JSON protocol.
|
|
39
|
+
Precaching a large dataset takes minutes, and a logger call would be dropped here: the trainer
|
|
40
|
+
subprocess configures no logging handler, so anything below WARNING goes nowhere.
|
|
41
|
+
"""
|
|
33
42
|
if arch == archs.MINIMAX_H3:
|
|
34
43
|
from . import h3
|
|
35
44
|
|
|
36
45
|
items, unconditional = h3.precache(
|
|
37
|
-
dataset_dir, models_dir, device, dtype, resolution, flip, dropout > 0
|
|
46
|
+
dataset_dir, models_dir, device, dtype, resolution, flip, dropout > 0, clip_frames,
|
|
47
|
+
clip_window=clip_window, on_status=on_status,
|
|
38
48
|
)
|
|
39
49
|
return items, unconditional, _H3_SHIFT
|
|
40
50
|
|
|
@@ -105,13 +105,30 @@ def _load_with_fallback(model_id: str) -> tuple[Any, Any, Any]:
|
|
|
105
105
|
raise first from None
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
def _open(path: str) -> Any:
|
|
109
|
+
"""The frame to caption. A clip is captioned from its middle frame, which is more
|
|
110
|
+
representative than the first and stops PIL raising on a container it cannot read."""
|
|
111
|
+
from pathlib import Path
|
|
112
|
+
|
|
113
|
+
from PIL import Image
|
|
114
|
+
|
|
115
|
+
from . import dataset as ds
|
|
116
|
+
|
|
117
|
+
if not ds.is_video(Path(path)):
|
|
118
|
+
return Image.open(path).convert("RGB")
|
|
119
|
+
|
|
120
|
+
from ..models.minimaxh3.vendor.packing_ref2va import decode_reference_video
|
|
121
|
+
|
|
122
|
+
frames, _fps, _audio = decode_reference_video(path)
|
|
123
|
+
return Image.fromarray(frames[len(frames) // 2]).convert("RGB")
|
|
124
|
+
|
|
125
|
+
|
|
108
126
|
def _caption_one(model: Any, processor: Any, device: str, path: str) -> str:
|
|
109
127
|
"""One caption. Handles both shapes: task-token models (Florence-2, which post-processes a
|
|
110
128
|
tagged string) and plain image-captioning models (BLIP), which just decode the output."""
|
|
111
129
|
import torch
|
|
112
|
-
from PIL import Image
|
|
113
130
|
|
|
114
|
-
image =
|
|
131
|
+
image = _open(path)
|
|
115
132
|
task_style = hasattr(processor, "post_process_generation")
|
|
116
133
|
inputs = (
|
|
117
134
|
processor(text=_TASK, images=image, return_tensors="pt")
|
|
@@ -17,15 +17,25 @@ from . import arch as archs
|
|
|
17
17
|
|
|
18
18
|
_IMAGE_SUFFIXES = (".png", ".jpg", ".jpeg", ".webp", ".bmp")
|
|
19
19
|
|
|
20
|
+
#: Only the video archs pass these to ``_pairs``. An image arch handed a clip would reach PIL and
|
|
21
|
+
#: raise, so the default stays images and each caller opts in.
|
|
22
|
+
_VIDEO_SUFFIXES = (".mp4", ".mov", ".webm", ".mkv", ".avi")
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
|
|
25
|
+
def is_video(path: Path) -> bool:
|
|
26
|
+
return path.suffix.lower() in _VIDEO_SUFFIXES
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _pairs(
|
|
30
|
+
dataset_dir: Path, suffixes: tuple[str, ...] = _IMAGE_SUFFIXES
|
|
31
|
+
) -> list[tuple[Path, str]]:
|
|
22
32
|
out: list[tuple[Path, str]] = []
|
|
23
|
-
for
|
|
24
|
-
if
|
|
33
|
+
for media in sorted(dataset_dir.iterdir()):
|
|
34
|
+
if media.suffix.lower() not in suffixes:
|
|
25
35
|
continue
|
|
26
|
-
caption_file =
|
|
36
|
+
caption_file = media.with_suffix(".txt")
|
|
27
37
|
caption = caption_file.read_text(encoding="utf-8").strip() if caption_file.exists() else ""
|
|
28
|
-
out.append((
|
|
38
|
+
out.append((media, caption))
|
|
29
39
|
return out
|
|
30
40
|
|
|
31
41
|
|