inline-core 1.2.63__tar.gz → 1.2.64__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.64}/PKG-INFO +2 -1
- {inline_core-1.2.63 → inline_core-1.2.64}/pyproject.toml +11 -4
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/device/detect.py +49 -1
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/server/__main__.py +20 -7
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/handlers.py +2 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/store.py +40 -1
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/training.py +36 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/training/arch.py +16 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/training/cache.py +11 -2
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/training/caption.py +19 -2
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/training/dataset.py +15 -5
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/training/h3.py +102 -27
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/training/models.py +60 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/training/trainer.py +21 -4
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_device_detect.py +69 -1
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_minimaxh3_training.py +93 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_store.py +32 -0
- inline_core-1.2.64/tests/test_studio_training.py +175 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_training_models.py +65 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_webui_install.py +100 -1
- {inline_core-1.2.63 → inline_core-1.2.64}/uv.lock +47 -85
- {inline_core-1.2.63 → inline_core-1.2.64}/webui.bat +53 -5
- {inline_core-1.2.63 → inline_core-1.2.64}/webui.sh +81 -7
- inline_core-1.2.63/tests/test_studio_training.py +0 -81
- {inline_core-1.2.63 → inline_core-1.2.64}/.gitignore +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/.python-version +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/CLAUDE.md +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/README.md +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/main.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/scripts/flux2_train_matrix.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/scripts/minimax_h3_lora_check.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/scripts/minimax_h3_train_matrix.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/scripts/reference.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/components/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/components/conditioning.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/components/interfaces.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/config.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/device/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/device/auto.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/device/memory.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/device/policy.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/device/types.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/errors.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/api.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/constraints.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/fetch.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/handlers.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/importer.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/install.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/loader.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/manifest.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/models.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/paths.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/scanner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/state.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/extensions/tools.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/ffmpeg.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/graph/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/graph/cache.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/graph/descriptor.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/graph/executor.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/graph/loader_runners.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/graph/primitives.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/graph/registry.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/graph/runners.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/graph/schema.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/graph/topo.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/graph/validate.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/media.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/catalog.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/checkpoint.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/controlspace.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/flux2/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/flux2/controlnet.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/flux2/embeds.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/flux2/provider.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/flux2/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/flux2/runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/flux2/variants.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/keymap.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/krea2/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/krea2/convert.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/krea2/depth_control.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/krea2/img2img.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/krea2/provider.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/krea2/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/krea2/runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/loaders.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/lora.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/adaln.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/keys.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/load.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/pipeline.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/provider.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vae_keys.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3_audio.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/before_denoise.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/before_encoder.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/decoders.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/denoise.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/encoders.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/modular_blocks_minimax_h3.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/modular_pipeline.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/packing.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/packing_ref2va.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/scheduling_minimax_h3.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/transformer_minimax_h3.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/offload.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/pipeline_runtime.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/prepared.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/preprocess/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/preprocess/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/preprocess/runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/references.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/sampling.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/video_params.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/zimage/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/zimage/primitives.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/zimage/provider.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/zimage/requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/models/zimage/runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/parallel/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/parallel/config.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/parallel/group.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/parallel/launch.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/parallel/protocol.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/parallel/registry.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/parallel/worker.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/runtime/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/runtime/context.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/runtime/file_store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/runtime/progress.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/runtime/run.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/runtime/store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/runtime/video_encode.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/sampling/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/sampling/batch.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/server/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/server/app.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/server/assets.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/server/bootstrap.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/server/frontend.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/server/manager.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/server/rpc.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/server/run_store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/server/serialize.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/assets.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/config.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/fal.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/frames.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/generation.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/graph_build.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/image_meta.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/models.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/moodboard.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/peaks.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/recipe.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/schema.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/system_stats.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/timeline/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/timeline/compose.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/timeline/ffmpeg.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/timeline/render.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/timeline/resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/studio/training_store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/takes.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/training/__init__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/training/__main__.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/src/inline_core/training/protocol.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/conftest.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/helpers.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_cache.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_catalog.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_checkpoint.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_config.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_executor.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_extension_api.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_extension_install.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_extension_manifest.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_extension_resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_extension_scanner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_extension_spine.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_extension_state.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_file_store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_flux2_controlnet.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_flux2_folder.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_flux2_resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_flux2_runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_flux2_training.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_flux2_variants.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_frontend_serving.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_hidden_nodes.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_keymap.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_krea2_convert.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_krea2_depth_control.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_krea2_requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_krea2_runner.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_loader_runners.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_loaders.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_lora.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_lora_download.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_memory_policy.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_minimaxh3_adaln.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_minimaxh3_keys.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_minimaxh3_load.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_minimaxh3_nodes.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_model_requirements.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_offload_prepared.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_output_kind_contract.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_parallel_group.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_pipeline_cache.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_primitives.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_recipe.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_references.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_rpc_bridge.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_run_store.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_sampling.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_schema.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_server.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_staged_residency.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_assets.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_fal.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_frames.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_generation.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_graph_build.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_models.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_moodboard.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_multi_reference.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_node_size.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_peaks.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_rpc.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_schema.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_studio_timeline.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_take_bytes.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_topo.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_training_arch.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_training_dataset.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_training_resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_validate.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_video_encode.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_video_params.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_xfuser_sampler.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_zimage_primitives.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/tests/test_zimage_resolve.py +0 -0
- {inline_core-1.2.63 → inline_core-1.2.64}/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.64
|
|
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.64"
|
|
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"
|
|
@@ -76,10 +76,58 @@ def cpu_only_torch_warning() -> str | None:
|
|
|
76
76
|
"This install has a CPU-ONLY build of PyTorch, but an NVIDIA GPU was detected. "
|
|
77
77
|
"Generation will run on the CPU and be roughly 100x slower. Reinstall torch from the CUDA "
|
|
78
78
|
"index, e.g. `pip install --force-reinstall --extra-index-url "
|
|
79
|
-
"https://download.pytorch.org/whl/
|
|
79
|
+
"https://download.pytorch.org/whl/cu126 torch` (or re-run `webui.sh --install`)."
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
def unsupported_arch_warning() -> str | None:
|
|
84
|
+
"""A warning when the installed torch has no kernels for the GPU it is about to run on.
|
|
85
|
+
|
|
86
|
+
An RTX 50-series card (sm_120) under a wheel built for sm_50..sm_90 is the sharpest case: torch
|
|
87
|
+
reports a CUDA build and a visible device, so every check in ``cpu_only_torch_warning`` passes,
|
|
88
|
+
and the only clue the user gets is PyTorch's own late UserWarning followed by kernels that
|
|
89
|
+
cannot launch. Returns None on any uncertainty, same as the rest of this module.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
import torch
|
|
93
|
+
|
|
94
|
+
if getattr(torch.version, "hip", None):
|
|
95
|
+
return None # the sm_ vocabulary is NVIDIA's; HIP reports gfx arches through it
|
|
96
|
+
get_arch_list = getattr(torch.cuda, "get_arch_list", None)
|
|
97
|
+
get_capability = getattr(torch.cuda, "get_device_capability", None)
|
|
98
|
+
device_count = getattr(torch.cuda, "device_count", None)
|
|
99
|
+
if get_arch_list is None or get_capability is None or device_count is None:
|
|
100
|
+
return None
|
|
101
|
+
if device_count() < 1:
|
|
102
|
+
return None
|
|
103
|
+
arches = [str(arch) for arch in get_arch_list() if str(arch).startswith("sm_")]
|
|
104
|
+
if not arches:
|
|
105
|
+
return None # a CPU-only build; cpu_only_torch_warning owns that case
|
|
106
|
+
major, minor = get_capability(0)
|
|
107
|
+
target = f"sm_{major}{minor}"
|
|
108
|
+
# startswith, because a wheel lists tuned variants like sm_90a for the same architecture.
|
|
109
|
+
if any(arch.startswith(target) for arch in arches):
|
|
110
|
+
return None
|
|
111
|
+
name = _device_name(torch) or "The detected NVIDIA GPU"
|
|
112
|
+
return (
|
|
113
|
+
f"{name} is compute capability {target}, but this install's PyTorch only has kernels "
|
|
114
|
+
f"for {' '.join(arches)}. Generation will fail or fall back to the CPU. Re-run "
|
|
115
|
+
"`webui.sh --install` (Windows: `.\\webui.bat --install`) to pick the wheel index that "
|
|
116
|
+
"matches the card, or force one with `--torch-index cu130` - `cu128` if the driver "
|
|
117
|
+
"predates CUDA 13."
|
|
118
|
+
)
|
|
119
|
+
except Exception: # noqa: BLE001 - a diagnostic must never break startup
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _device_name(torch: object) -> str | None:
|
|
124
|
+
try:
|
|
125
|
+
get_name = getattr(getattr(torch, "cuda", None), "get_device_name", None)
|
|
126
|
+
return str(get_name(0)) if get_name is not None else None
|
|
127
|
+
except Exception: # noqa: BLE001
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
|
|
83
131
|
def _nvidia_gpu_present() -> bool:
|
|
84
132
|
"""Best-effort check for NVIDIA hardware that does NOT rely on torch's CUDA support - that is
|
|
85
133
|
the whole point, since we are called precisely when torch cannot see the GPU.
|
|
@@ -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,21 @@ def build(
|
|
|
28
29
|
*,
|
|
29
30
|
flip: bool = False,
|
|
30
31
|
dropout: float = 0.0,
|
|
32
|
+
clip_frames: int = 1,
|
|
33
|
+
on_status: Callable[[str], None] | None = None,
|
|
31
34
|
) -> tuple[list[dict[str, Any]], dict[str, Any] | None, float]:
|
|
32
|
-
"""Return ``(items, unconditional, shift)``, all as CPU tensors, with the encoders freed.
|
|
35
|
+
"""Return ``(items, unconditional, shift)``, all as CPU tensors, with the encoders freed.
|
|
36
|
+
|
|
37
|
+
``on_status`` reports phase progress to the caller, which forwards it over the JSON protocol.
|
|
38
|
+
Precaching a large dataset takes minutes, and a logger call would be dropped here: the trainer
|
|
39
|
+
subprocess configures no logging handler, so anything below WARNING goes nowhere.
|
|
40
|
+
"""
|
|
33
41
|
if arch == archs.MINIMAX_H3:
|
|
34
42
|
from . import h3
|
|
35
43
|
|
|
36
44
|
items, unconditional = h3.precache(
|
|
37
|
-
dataset_dir, models_dir, device, dtype, resolution, flip, dropout > 0
|
|
45
|
+
dataset_dir, models_dir, device, dtype, resolution, flip, dropout > 0, clip_frames,
|
|
46
|
+
on_status=on_status,
|
|
38
47
|
)
|
|
39
48
|
return items, unconditional, _H3_SHIFT
|
|
40
49
|
|
|
@@ -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
|
|
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
|
|
15
15
|
import gc
|
|
16
16
|
import logging
|
|
17
|
+
from collections.abc import Callable
|
|
17
18
|
from pathlib import Path
|
|
18
19
|
from typing import Any
|
|
19
20
|
|
|
@@ -29,6 +30,10 @@ AUDIO_LATENT_CHANNELS = 32
|
|
|
29
30
|
#: caption dropout and an image whose ``.txt`` is missing.
|
|
30
31
|
_EMPTY_CAPTION = " "
|
|
31
32
|
|
|
33
|
+
#: H3's fixed frame rate, and the shortest clip its video VAE encodes (the first ``17n + 5``).
|
|
34
|
+
_H3_FPS = 24
|
|
35
|
+
_MIN_CLIP_FRAMES = 22
|
|
36
|
+
|
|
32
37
|
|
|
33
38
|
def precache(
|
|
34
39
|
dataset_dir: str,
|
|
@@ -38,38 +43,55 @@ def precache(
|
|
|
38
43
|
resolution: int,
|
|
39
44
|
flip: bool,
|
|
40
45
|
want_unconditional: bool,
|
|
46
|
+
clip_frames: int = 1,
|
|
47
|
+
on_status: Callable[[str], None] | None = None,
|
|
41
48
|
) -> tuple[list[dict[str, Any]], dict[str, Any] | None]:
|
|
42
49
|
"""Every image as a latent and every caption as conditioning, as CPU tensors."""
|
|
43
50
|
from . import dataset as ds
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
say = on_status or (lambda _text: None)
|
|
53
|
+
pairs = ds._pairs(Path(dataset_dir), ds._IMAGE_SUFFIXES + ds._VIDEO_SUFFIXES)
|
|
46
54
|
if not pairs:
|
|
47
55
|
raise RuntimeError("The exported dataset is empty.")
|
|
48
56
|
|
|
49
57
|
root = Path(models_dir)
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
# Only the clips that survived encoding carry captions, or every caption after the first skip
|
|
59
|
+
# would be paired with the wrong latent.
|
|
60
|
+
latents, kept = _encode_pixels(root, pairs, device, resolution, flip, clip_frames, say)
|
|
61
|
+
if not kept:
|
|
62
|
+
raise RuntimeError(
|
|
63
|
+
f"None of the {len(pairs)} dataset items could be encoded. For clips, each must be at "
|
|
64
|
+
f"least {_MIN_CLIP_FRAMES} frames at {_H3_FPS}fps "
|
|
65
|
+
f"({_MIN_CLIP_FRAMES / _H3_FPS:.2f}s)."
|
|
66
|
+
)
|
|
67
|
+
captions = [caption for _img, caption in kept for _ in ((False, True) if flip else (False,))]
|
|
52
68
|
if want_unconditional:
|
|
53
69
|
captions.append("")
|
|
54
|
-
embeds = _encode_captions(root, captions, device, dtype)
|
|
70
|
+
embeds = _encode_captions(root, captions, device, dtype, say)
|
|
71
|
+
say(f"cached {len(latents)} latents and {len(embeds)} captions")
|
|
55
72
|
|
|
56
73
|
items = [
|
|
57
74
|
{"latent": latent, **_conditioning(embed, tags, latent)}
|
|
58
75
|
for latent, (embed, tags) in zip(latents, embeds, strict=False)
|
|
59
76
|
]
|
|
60
|
-
unconditional = None
|
|
61
77
|
if want_unconditional:
|
|
78
|
+
# Dropout swaps a different text length in, which moves every row after it, so the whole
|
|
79
|
+
# layout travels with the embedding. It also depends on the latent grid, and a dataset
|
|
80
|
+
# mixing stills with clips has more than one, so each item carries its own rather than
|
|
81
|
+
# sharing the first item's and mis-sizing every clip.
|
|
62
82
|
embed, tags = embeds[-1]
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return items,
|
|
83
|
+
for item in items:
|
|
84
|
+
item["uncond"] = _conditioning(embed, tags, item["latent"])
|
|
85
|
+
# The per-item copies are what dropout uses; the loop keeps the global slot for the image archs.
|
|
86
|
+
return items, None
|
|
67
87
|
|
|
68
88
|
|
|
69
89
|
def _encode_pixels(
|
|
70
|
-
root: Path, pairs: list[tuple[Path, str]], device: str, resolution: int, flip: bool
|
|
71
|
-
|
|
72
|
-
|
|
90
|
+
root: Path, pairs: list[tuple[Path, str]], device: str, resolution: int, flip: bool,
|
|
91
|
+
clip_frames: int = 1, say: Callable[[str], None] = lambda _text: None,
|
|
92
|
+
) -> tuple[list[Any], list[tuple[Path, str]]]:
|
|
93
|
+
"""Pass one: the video VAE, then dropped. Returns the latents and the pairs they came from."""
|
|
94
|
+
import numpy
|
|
73
95
|
import torch
|
|
74
96
|
from PIL import Image
|
|
75
97
|
|
|
@@ -84,45 +106,98 @@ def _encode_pixels(
|
|
|
84
106
|
from . import dataset as ds
|
|
85
107
|
|
|
86
108
|
out: list[Any] = []
|
|
109
|
+
kept: list[tuple[Path, str]] = []
|
|
110
|
+
skipped: list[str] = []
|
|
111
|
+
total = len(pairs)
|
|
112
|
+
say(f"encoding {total} items at {resolution}px through the video VAE")
|
|
87
113
|
try:
|
|
88
|
-
for
|
|
114
|
+
for index, (path, _caption) in enumerate(pairs, start=1):
|
|
115
|
+
clip = ds.is_video(path)
|
|
116
|
+
try:
|
|
117
|
+
frames = _clip_frames(path, clip_frames) if clip else [Image.open(path)]
|
|
118
|
+
except ShortClipError as exc:
|
|
119
|
+
skipped.append(path.name)
|
|
120
|
+
say(f"skipped {exc}")
|
|
121
|
+
continue
|
|
122
|
+
# A long precache is otherwise silent for many minutes, so report often enough that it
|
|
123
|
+
# reads as progress rather than a hang.
|
|
124
|
+
if index == 1 or index % 5 == 0 or index == total:
|
|
125
|
+
say(f"caching latents {index}/{total}")
|
|
89
126
|
for mirrored in (False, True) if flip else (False,):
|
|
90
|
-
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
raw = torch.from_numpy(
|
|
94
|
-
pixels = raw.permute(
|
|
127
|
+
stack = [_as_array(ds._square(f, resolution, mirrored)) for f in frames]
|
|
128
|
+
# ImageNet statistics, not the [-1, 1] the image archs use, and always 5D:
|
|
129
|
+
# (1, 3, F, H, W).
|
|
130
|
+
raw = torch.from_numpy(numpy.stack(stack)).to(device)
|
|
131
|
+
pixels = raw.permute(3, 0, 1, 2)[None]
|
|
95
132
|
pixels = (pixels.to(torch.float32).div(255.0) - pixel_mean) / pixel_std
|
|
96
133
|
with torch.no_grad():
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
|
|
134
|
+
# A single frame takes the spatial encoder; a 17n+5 clip takes the temporal
|
|
135
|
+
# chunking. Mirrors the split the vendored reference encoder makes.
|
|
136
|
+
moments = vae._encode(pixels) if clip else vae._encode_clip(pixels)
|
|
137
|
+
latent = _sample(moments)
|
|
100
138
|
out.append(((latent.cpu() - mean) / std)[0])
|
|
139
|
+
kept.append((path, _caption))
|
|
101
140
|
finally:
|
|
102
141
|
del vae
|
|
103
142
|
_reclaim()
|
|
104
|
-
|
|
105
|
-
|
|
143
|
+
if skipped:
|
|
144
|
+
say(f"skipped {len(skipped)} of {total} items as too short: {', '.join(skipped)}")
|
|
145
|
+
say(f"cached {len(out)} latents from {len(kept)} items, video VAE released")
|
|
146
|
+
return out, kept
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ShortClipError(RuntimeError):
|
|
150
|
+
"""A clip below H3's frame floor. Skipped, never fatal: one bad file in a large dataset must
|
|
151
|
+
not throw away a precache that takes many minutes."""
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _clip_frames(path: Path, clip_frames: int) -> list[Any]:
|
|
155
|
+
"""A clip as PIL frames on H3's 24fps, 17n+5 grid, taken from the start.
|
|
156
|
+
|
|
157
|
+
Trimmed rather than sampled: a fixed window keeps the precache to one encode per clip, and
|
|
158
|
+
re-encoding a different window every step would defeat caching the latents at all.
|
|
159
|
+
"""
|
|
160
|
+
from PIL import Image
|
|
161
|
+
|
|
162
|
+
from ..models.minimaxh3.vendor.packing_ref2va import (
|
|
163
|
+
decode_reference_video,
|
|
164
|
+
resample_reference_frames,
|
|
165
|
+
trim_reference_num_frames,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
decoded, fps, _audio = decode_reference_video(str(path))
|
|
169
|
+
frames = resample_reference_frames(decoded, fps)
|
|
170
|
+
keep = trim_reference_num_frames(min(frames.shape[0], clip_frames))
|
|
171
|
+
if keep > frames.shape[0]:
|
|
172
|
+
raise ShortClipError(
|
|
173
|
+
f"{path.name} is {frames.shape[0]} frames once resampled to {_H3_FPS}fps, below H3's "
|
|
174
|
+
f"{keep}-frame minimum ({keep / _H3_FPS:.2f}s). Skipped."
|
|
175
|
+
)
|
|
176
|
+
return [Image.fromarray(frame) for frame in frames[:keep]]
|
|
106
177
|
|
|
107
178
|
|
|
108
179
|
def _encode_captions(
|
|
109
|
-
root: Path, captions: list[str], device: str, dtype: Any
|
|
180
|
+
root: Path, captions: list[str], device: str, dtype: Any,
|
|
181
|
+
say: Callable[[str], None] = lambda _text: None,
|
|
110
182
|
) -> list[tuple[Any, Any]]:
|
|
111
183
|
"""Pass two: the 4-bit conditioner, then dropped."""
|
|
112
184
|
import torch
|
|
113
185
|
|
|
114
186
|
from ..models.minimaxh3.vendor.encoders import MiniMaxH3TextEncoderStep
|
|
115
187
|
|
|
188
|
+
say("loading the 4-bit text conditioner (20.5GB)")
|
|
116
189
|
pipeline = _load_conditioner(root, device, dtype)
|
|
117
190
|
# Encode wherever it landed. It spills to host RAM on a card too small for 20.5GB, and the
|
|
118
191
|
# vendored step builds its input ids on the device it is handed, so CUDA ids against a
|
|
119
192
|
# CPU-resident encoder fail in `index_select`.
|
|
120
193
|
where = next(pipeline.text_encoder.parameters()).device
|
|
121
194
|
if where.type != torch.device(device).type:
|
|
122
|
-
|
|
195
|
+
say(f"conditioner spilled to {where}, encoding captions there (slower)")
|
|
123
196
|
out: list[tuple[Any, Any]] = []
|
|
124
197
|
try:
|
|
125
|
-
for caption in captions:
|
|
198
|
+
for index, caption in enumerate(captions, start=1):
|
|
199
|
+
if index == 1 or index % 10 == 0 or index == len(captions):
|
|
200
|
+
say(f"encoding captions {index}/{len(captions)}")
|
|
126
201
|
caption = caption or _EMPTY_CAPTION
|
|
127
202
|
with torch.no_grad():
|
|
128
203
|
# The staticmethod rather than the block, so nothing needs a PipelineState. `dtype`
|
|
@@ -134,7 +209,7 @@ def _encode_captions(
|
|
|
134
209
|
out.append((embeds[0].cpu(), tags.cpu()))
|
|
135
210
|
finally:
|
|
136
211
|
_drop_conditioner(pipeline)
|
|
137
|
-
|
|
212
|
+
say(f"cached {len(out)} captions, conditioner released")
|
|
138
213
|
return out
|
|
139
214
|
|
|
140
215
|
|