inline-core 1.2.64__tar.gz → 1.2.66__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.64 → inline_core-1.2.66}/CLAUDE.md +16 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/PKG-INFO +3 -3
- {inline_core-1.2.64 → inline_core-1.2.66}/pyproject.toml +13 -5
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/device/detect.py +37 -4
- inline_core-1.2.66/src/inline_core/device/probe.py +76 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/load.py +8 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/training/cache.py +2 -1
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/training/h3.py +15 -5
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/training/models.py +9 -6
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/training/trainer.py +21 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_device_detect.py +129 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_minimaxh3_training.py +35 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_training_models.py +29 -4
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_webui_install.py +125 -5
- {inline_core-1.2.64 → inline_core-1.2.66}/webui.bat +154 -24
- {inline_core-1.2.64 → inline_core-1.2.66}/webui.sh +162 -27
- {inline_core-1.2.64 → inline_core-1.2.66}/.gitignore +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/.python-version +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/README.md +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/main.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/scripts/flux2_train_matrix.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/scripts/minimax_h3_lora_check.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/scripts/minimax_h3_train_matrix.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/scripts/reference.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/components/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/components/conditioning.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/components/interfaces.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/config.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/device/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/device/auto.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/device/memory.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/device/policy.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/device/types.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/errors.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/api.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/constraints.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/fetch.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/handlers.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/importer.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/install.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/loader.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/manifest.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/models.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/paths.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/resolve.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/scanner.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/state.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/extensions/tools.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/ffmpeg.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/graph/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/graph/cache.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/graph/descriptor.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/graph/executor.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/graph/loader_runners.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/graph/primitives.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/graph/registry.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/graph/runners.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/graph/schema.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/graph/topo.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/graph/validate.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/media.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/catalog.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/checkpoint.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/controlspace.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/flux2/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/flux2/controlnet.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/flux2/embeds.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/flux2/provider.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/flux2/requirements.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/flux2/runner.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/flux2/variants.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/keymap.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/krea2/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/krea2/convert.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/krea2/depth_control.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/krea2/img2img.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/krea2/provider.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/krea2/requirements.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/krea2/runner.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/loaders.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/lora.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/adaln.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/keys.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/pipeline.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/provider.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/requirements.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/runner.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vae_keys.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3_audio.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/before_denoise.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/before_encoder.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/decoders.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/denoise.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/encoders.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/modular_blocks_minimax_h3.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/modular_pipeline.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/packing.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/packing_ref2va.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/scheduling_minimax_h3.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/minimaxh3/vendor/transformer_minimax_h3.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/offload.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/pipeline_runtime.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/prepared.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/preprocess/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/preprocess/requirements.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/preprocess/runner.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/references.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/requirements.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/sampling.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/video_params.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/zimage/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/zimage/primitives.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/zimage/provider.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/zimage/requirements.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/models/zimage/runner.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/parallel/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/parallel/config.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/parallel/group.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/parallel/launch.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/parallel/protocol.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/parallel/registry.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/parallel/worker.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/runtime/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/runtime/context.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/runtime/file_store.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/runtime/progress.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/runtime/run.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/runtime/store.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/runtime/video_encode.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/sampling/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/sampling/batch.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/server/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/server/__main__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/server/app.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/server/assets.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/server/bootstrap.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/server/frontend.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/server/manager.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/server/rpc.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/server/run_store.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/server/serialize.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/assets.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/config.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/fal.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/frames.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/generation.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/graph_build.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/handlers.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/image_meta.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/models.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/moodboard.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/peaks.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/recipe.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/schema.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/store.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/system_stats.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/timeline/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/timeline/compose.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/timeline/ffmpeg.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/timeline/render.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/timeline/resolve.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/training.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/studio/training_store.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/takes.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/training/__init__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/training/__main__.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/training/arch.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/training/caption.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/training/dataset.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/src/inline_core/training/protocol.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/conftest.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/helpers.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_cache.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_catalog.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_checkpoint.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_config.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_executor.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_extension_api.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_extension_install.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_extension_manifest.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_extension_resolve.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_extension_scanner.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_extension_spine.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_extension_state.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_file_store.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_flux2_controlnet.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_flux2_folder.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_flux2_resolve.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_flux2_runner.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_flux2_training.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_flux2_variants.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_frontend_serving.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_hidden_nodes.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_keymap.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_krea2_convert.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_krea2_depth_control.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_krea2_requirements.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_krea2_runner.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_loader_runners.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_loaders.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_lora.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_lora_download.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_memory_policy.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_minimaxh3_adaln.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_minimaxh3_keys.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_minimaxh3_load.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_minimaxh3_nodes.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_model_requirements.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_offload_prepared.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_output_kind_contract.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_parallel_group.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_pipeline_cache.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_primitives.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_recipe.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_references.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_rpc_bridge.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_run_store.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_sampling.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_schema.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_server.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_staged_residency.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_assets.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_fal.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_frames.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_generation.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_graph_build.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_models.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_moodboard.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_multi_reference.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_node_size.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_peaks.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_rpc.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_schema.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_store.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_timeline.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_studio_training.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_take_bytes.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_topo.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_training_arch.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_training_dataset.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_training_resolve.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_validate.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_video_encode.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_video_params.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_xfuser_sampler.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_zimage_primitives.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_zimage_resolve.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/tests/test_zimage_runner.py +0 -0
- {inline_core-1.2.64 → inline_core-1.2.66}/uv.lock +0 -0
|
@@ -126,6 +126,8 @@ between nodes and are never takes.
|
|
|
126
126
|
every stop/start**, taking the models and leaving dangling symlinks in `models/`. This has cost two
|
|
127
127
|
full re-downloads of MiniMax H3 at ~130 GB each. Weights go on the persistent root volume, or on an
|
|
128
128
|
attached volume that survives a restart. Scratch is fine for logs and temporary output only.
|
|
129
|
+
- **`huggingface_hub>=0.32`** - below it `hf_xet` is only an extra, and files over ~50GB (every
|
|
130
|
+
H3 transformer) refuse to download at all.
|
|
129
131
|
- **Models root** - `INLINE_MODELS_DIR`, else `./models`. **Bring your own weights; nothing is
|
|
130
132
|
downloaded.** ComfyUI-style category subfolders (`diffusion_models/`, `vae/`, `text_encoders/`,
|
|
131
133
|
`loras/`, `controlnet/`, `checkpoints/`, `clip_vision/`, `upscale_models/`, `embeddings/`). The
|
|
@@ -233,6 +235,20 @@ real codec that moves tensors lives with the model runner.
|
|
|
233
235
|
vision-language encoder loaded in place of a text one, an unnormalized latent, and a control context
|
|
234
236
|
cast to a quantized weight's `uint8` storage dtype. A unit test cannot see a plausible-but-wrong
|
|
235
237
|
image. Render something and look at it.
|
|
238
|
+
- **Launchers are twins.** `webui.sh` and `webui.bat` change together; only the `launcher` CI job
|
|
239
|
+
can prove the `.bat`, since it cannot run on a dev box.
|
|
240
|
+
- **Match CUDA arches by within-major compatibility, never exactly.** An `sm_8x` cubin runs on any
|
|
241
|
+
`sm_8y` where `y >= x`, so `sm_86` covers Ada's `sm_89`.
|
|
242
|
+
- **Pass the widest `uv` flag that works.** `--no-sources-package` needs uv 0.10+; `--no-sources`
|
|
243
|
+
works back to 0.4 and means the same while torch is the only `[tool.uv.sources]` entry.
|
|
244
|
+
- **An explicit user setting beats a heuristic.** Test `on`/`off` before any auto rule, or the
|
|
245
|
+
control is silently dead for whichever arch the rule excludes.
|
|
246
|
+
- **Report a phase before the slow work, not after.** Progress emitted only on completion makes a
|
|
247
|
+
slow step look like a hung previous phase.
|
|
248
|
+
- **In a `.bat`, `call` anything that might be a `.bat`.** `nvidia-smi` is sometimes a shim, and
|
|
249
|
+
without `call` it takes over the script and never returns.
|
|
250
|
+
- **Log allocated AND reserved VRAM.** `nvidia-smi` shows only reserved, so allocator cache and a
|
|
251
|
+
leaked reference look identical from outside.
|
|
236
252
|
- **Tests (pytest).** Cover the logic that matters: graph validate/topo/executor/cache, the catalog
|
|
237
253
|
scan, the run store + server contract, the device/memory policy, the parallel group + xfuser seam,
|
|
238
254
|
and each model runner (import-guarded, no GPU needed). See `tests/`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: inline-core
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.66
|
|
4
4
|
Summary: The generation engine behind Inline Studio.
|
|
5
5
|
License-Expression: GPL-3.0-or-later
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -14,7 +14,7 @@ Requires-Dist: controlnet-aux>=0.0.7; extra == 'all'
|
|
|
14
14
|
Requires-Dist: diffusers==0.39.0; extra == 'all'
|
|
15
15
|
Requires-Dist: einops>=0.7; extra == 'all'
|
|
16
16
|
Requires-Dist: fastapi>=0.110; extra == 'all'
|
|
17
|
-
Requires-Dist: huggingface-hub>=0.
|
|
17
|
+
Requires-Dist: huggingface-hub>=0.32; extra == 'all'
|
|
18
18
|
Requires-Dist: imageio-ffmpeg>=0.4; extra == 'all'
|
|
19
19
|
Requires-Dist: nvidia-ml-py>=12; extra == 'all'
|
|
20
20
|
Requires-Dist: onnxruntime>=1.17; extra == 'all'
|
|
@@ -41,7 +41,7 @@ Requires-Dist: accelerate>=0.30; extra == 'runtime'
|
|
|
41
41
|
Requires-Dist: av>=12; extra == 'runtime'
|
|
42
42
|
Requires-Dist: controlnet-aux>=0.0.7; extra == 'runtime'
|
|
43
43
|
Requires-Dist: diffusers==0.39.0; extra == 'runtime'
|
|
44
|
-
Requires-Dist: huggingface-hub>=0.
|
|
44
|
+
Requires-Dist: huggingface-hub>=0.32; extra == 'runtime'
|
|
45
45
|
Requires-Dist: onnxruntime>=1.17; extra == 'runtime'
|
|
46
46
|
Requires-Dist: safetensors>=0.4; extra == 'runtime'
|
|
47
47
|
Requires-Dist: scipy>=1.11; extra == 'runtime'
|
|
@@ -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.66"
|
|
5
5
|
description = "The generation engine behind Inline Studio."
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "GPL-3.0-or-later"
|
|
@@ -29,7 +29,10 @@ runtime = [
|
|
|
29
29
|
# Backs the Beta sigma schedule (diffusers gates use_beta_sigmas on scipy).
|
|
30
30
|
"scipy>=1.11",
|
|
31
31
|
# We call snapshot_download directly for the model popup, so pin it rather than rely on transit.
|
|
32
|
-
|
|
32
|
+
# 0.32 is where hf_xet became a hard dependency rather than an extra. Below it, Hugging Face
|
|
33
|
+
# refuses the largest files outright ("too large to be downloaded using the regular download
|
|
34
|
+
# method"), which is every H3 transformer at 66GB.
|
|
35
|
+
"huggingface_hub>=0.32",
|
|
33
36
|
# ControlNet preprocessors (the Apply ControlNet node): OpenPose/DWPose, MiDaS/Zoe depth, canny,
|
|
34
37
|
# HED, lineart, MLSD, scribble, normal. DWPose runs its detector on ONNX Runtime.
|
|
35
38
|
"controlnet-aux>=0.0.7",
|
|
@@ -85,7 +88,10 @@ all = [
|
|
|
85
88
|
# Clip decode for MiniMax H3 LoRA training, and H3's reference node.
|
|
86
89
|
"av>=12",
|
|
87
90
|
"scipy>=1.11",
|
|
88
|
-
|
|
91
|
+
# 0.32 is where hf_xet became a hard dependency rather than an extra. Below it, Hugging Face
|
|
92
|
+
# refuses the largest files outright ("too large to be downloaded using the regular download
|
|
93
|
+
# method"), which is every H3 transformer at 66GB.
|
|
94
|
+
"huggingface_hub>=0.32",
|
|
89
95
|
"controlnet-aux>=0.0.7",
|
|
90
96
|
"onnxruntime>=1.17",
|
|
91
97
|
# server
|
|
@@ -111,8 +117,10 @@ name = "pytorch-cu126"
|
|
|
111
117
|
url = "https://download.pytorch.org/whl/cu126"
|
|
112
118
|
explicit = true
|
|
113
119
|
|
|
114
|
-
# Launcher installs pass `--no-sources
|
|
115
|
-
#
|
|
120
|
+
# Launcher installs pass `--no-sources` so their detected index wins over this pin. That flag is
|
|
121
|
+
# broad rather than per-package because the per-package form is too new for the uv versions people
|
|
122
|
+
# have. torch being the ONLY entry here is what makes that equivalent: adding another would silently
|
|
123
|
+
# start ignoring it on launcher installs too.
|
|
116
124
|
[tool.uv.sources]
|
|
117
125
|
torch = [{ index = "pytorch-cu126", marker = "sys_platform == 'win32'" }]
|
|
118
126
|
|
|
@@ -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
|
|
|
@@ -80,6 +83,37 @@ def cpu_only_torch_warning() -> str | None:
|
|
|
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
|
+
|
|
83
117
|
def unsupported_arch_warning() -> str | None:
|
|
84
118
|
"""A warning when the installed torch has no kernels for the GPU it is about to run on.
|
|
85
119
|
|
|
@@ -105,16 +139,15 @@ def unsupported_arch_warning() -> str | None:
|
|
|
105
139
|
return None # a CPU-only build; cpu_only_torch_warning owns that case
|
|
106
140
|
major, minor = get_capability(0)
|
|
107
141
|
target = f"sm_{major}{minor}"
|
|
108
|
-
|
|
109
|
-
if any(arch.startswith(target) for arch in arches):
|
|
142
|
+
if arch_list_covers(arches, major, minor):
|
|
110
143
|
return None
|
|
111
144
|
name = _device_name(torch) or "The detected NVIDIA GPU"
|
|
112
145
|
return (
|
|
113
146
|
f"{name} is compute capability {target}, but this install's PyTorch only has kernels "
|
|
114
147
|
f"for {' '.join(arches)}. Generation will fail or fall back to the CPU. Re-run "
|
|
115
148
|
"`webui.sh --install` (Windows: `.\\webui.bat --install`) to pick the wheel index that "
|
|
116
|
-
"matches the card, or force one with `--torch-index cu130
|
|
117
|
-
"predates
|
|
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."
|
|
118
151
|
)
|
|
119
152
|
except Exception: # noqa: BLE001 - a diagnostic must never break startup
|
|
120
153
|
return None
|
|
@@ -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
|
|
@@ -30,6 +30,7 @@ def build(
|
|
|
30
30
|
flip: bool = False,
|
|
31
31
|
dropout: float = 0.0,
|
|
32
32
|
clip_frames: int = 1,
|
|
33
|
+
clip_window: str = "start",
|
|
33
34
|
on_status: Callable[[str], None] | None = None,
|
|
34
35
|
) -> tuple[list[dict[str, Any]], dict[str, Any] | None, float]:
|
|
35
36
|
"""Return ``(items, unconditional, shift)``, all as CPU tensors, with the encoders freed.
|
|
@@ -43,7 +44,7 @@ def build(
|
|
|
43
44
|
|
|
44
45
|
items, unconditional = h3.precache(
|
|
45
46
|
dataset_dir, models_dir, device, dtype, resolution, flip, dropout > 0, clip_frames,
|
|
46
|
-
on_status=on_status,
|
|
47
|
+
clip_window=clip_window, on_status=on_status,
|
|
47
48
|
)
|
|
48
49
|
return items, unconditional, _H3_SHIFT
|
|
49
50
|
|
|
@@ -44,6 +44,7 @@ def precache(
|
|
|
44
44
|
flip: bool,
|
|
45
45
|
want_unconditional: bool,
|
|
46
46
|
clip_frames: int = 1,
|
|
47
|
+
clip_window: str = "start",
|
|
47
48
|
on_status: Callable[[str], None] | None = None,
|
|
48
49
|
) -> tuple[list[dict[str, Any]], dict[str, Any] | None]:
|
|
49
50
|
"""Every image as a latent and every caption as conditioning, as CPU tensors."""
|
|
@@ -57,7 +58,9 @@ def precache(
|
|
|
57
58
|
root = Path(models_dir)
|
|
58
59
|
# Only the clips that survived encoding carry captions, or every caption after the first skip
|
|
59
60
|
# would be paired with the wrong latent.
|
|
60
|
-
latents, kept = _encode_pixels(
|
|
61
|
+
latents, kept = _encode_pixels(
|
|
62
|
+
root, pairs, device, resolution, flip, clip_frames, say, clip_window
|
|
63
|
+
)
|
|
61
64
|
if not kept:
|
|
62
65
|
raise RuntimeError(
|
|
63
66
|
f"None of the {len(pairs)} dataset items could be encoded. For clips, each must be at "
|
|
@@ -89,6 +92,7 @@ def precache(
|
|
|
89
92
|
def _encode_pixels(
|
|
90
93
|
root: Path, pairs: list[tuple[Path, str]], device: str, resolution: int, flip: bool,
|
|
91
94
|
clip_frames: int = 1, say: Callable[[str], None] = lambda _text: None,
|
|
95
|
+
clip_window: str = "start",
|
|
92
96
|
) -> tuple[list[Any], list[tuple[Path, str]]]:
|
|
93
97
|
"""Pass one: the video VAE, then dropped. Returns the latents and the pairs they came from."""
|
|
94
98
|
import numpy
|
|
@@ -114,7 +118,9 @@ def _encode_pixels(
|
|
|
114
118
|
for index, (path, _caption) in enumerate(pairs, start=1):
|
|
115
119
|
clip = ds.is_video(path)
|
|
116
120
|
try:
|
|
117
|
-
frames =
|
|
121
|
+
frames = (
|
|
122
|
+
_clip_frames(path, clip_frames, clip_window) if clip else [Image.open(path)]
|
|
123
|
+
)
|
|
118
124
|
except ShortClipError as exc:
|
|
119
125
|
skipped.append(path.name)
|
|
120
126
|
say(f"skipped {exc}")
|
|
@@ -151,11 +157,14 @@ class ShortClipError(RuntimeError):
|
|
|
151
157
|
not throw away a precache that takes many minutes."""
|
|
152
158
|
|
|
153
159
|
|
|
154
|
-
def _clip_frames(path: Path, clip_frames: int) -> list[Any]:
|
|
155
|
-
"""A clip as PIL frames on H3's 24fps, 17n+5 grid
|
|
160
|
+
def _clip_frames(path: Path, clip_frames: int, window: str = "start") -> list[Any]:
|
|
161
|
+
"""A clip as PIL frames on H3's 24fps, 17n+5 grid.
|
|
156
162
|
|
|
157
163
|
Trimmed rather than sampled: a fixed window keeps the precache to one encode per clip, and
|
|
158
164
|
re-encoding a different window every step would defeat caching the latents at all.
|
|
165
|
+
|
|
166
|
+
The grid only snaps **down**, so some of every clip is always dropped. ``window="end"`` takes
|
|
167
|
+
the tail instead of the head, for footage whose action is at the finish.
|
|
159
168
|
"""
|
|
160
169
|
from PIL import Image
|
|
161
170
|
|
|
@@ -173,7 +182,8 @@ def _clip_frames(path: Path, clip_frames: int) -> list[Any]:
|
|
|
173
182
|
f"{path.name} is {frames.shape[0]} frames once resampled to {_H3_FPS}fps, below H3's "
|
|
174
183
|
f"{keep}-frame minimum ({keep / _H3_FPS:.2f}s). Skipped."
|
|
175
184
|
)
|
|
176
|
-
|
|
185
|
+
kept = frames[-keep:] if window == "end" else frames[:keep]
|
|
186
|
+
return [Image.fromarray(frame) for frame in kept]
|
|
177
187
|
|
|
178
188
|
|
|
179
189
|
def _encode_captions(
|
|
@@ -325,20 +325,23 @@ def resolve_offload(
|
|
|
325
325
|
) -> bool:
|
|
326
326
|
"""Whether to stream saved activations to host RAM this run.
|
|
327
327
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
328
|
+
``auto`` was written for a full-precision base: keep the 26GB Krea 2 base resident and put the
|
|
329
|
+
~21GB of 1024 activations elsewhere, rather than dropping the frozen base to NF4. Under a
|
|
330
|
+
quantized base ``auto`` stays off, because there the base is the whole story and offload would
|
|
331
|
+
buy PCIe traffic for nothing.
|
|
332
|
+
|
|
333
|
+
``on``/``off`` are tested before the quant rule, or the control is dead for MiniMax H3 (always
|
|
334
|
+
4-bit), whose base is small and whose clip activations are what overflow the card."""
|
|
332
335
|
from ..device.policy import Quantization
|
|
333
336
|
|
|
334
337
|
if pref == "off":
|
|
335
338
|
return False
|
|
336
|
-
if quant is not Quantization.NONE:
|
|
337
|
-
return False # a quantized base already fits; offload would only slow it down
|
|
338
339
|
if pref == "on":
|
|
339
340
|
return True
|
|
340
341
|
if pref not in ("auto", ""):
|
|
341
342
|
raise RuntimeError(f"Unknown offload preference {pref!r}.")
|
|
343
|
+
if quant is not Quantization.NONE:
|
|
344
|
+
return False # auto only: a quantized base already fits, so do not pay for offload
|
|
342
345
|
|
|
343
346
|
import torch
|
|
344
347
|
|
|
@@ -119,6 +119,20 @@ def _peak_vram_gb() -> float | None:
|
|
|
119
119
|
return round(torch.cuda.max_memory_allocated() / 1e9, 2)
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
def _vram_note(label: str) -> str:
|
|
123
|
+
"""Both numbers: nvidia-smi shows only reserved, so allocator cache and a leaked reference look
|
|
124
|
+
identical from outside."""
|
|
125
|
+
import torch
|
|
126
|
+
|
|
127
|
+
if not torch.cuda.is_available():
|
|
128
|
+
return label
|
|
129
|
+
gb = 1e9
|
|
130
|
+
return (
|
|
131
|
+
f"{label}: allocated {torch.cuda.memory_allocated() / gb:.1f}GB, "
|
|
132
|
+
f"reserved {torch.cuda.memory_reserved() / gb:.1f}GB"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
122
136
|
def _activation_offload(enabled: bool) -> Any:
|
|
123
137
|
"""A context that streams saved activations to host RAM (pinned) for the forward, pulling them
|
|
124
138
|
back on backward. Keeps a full-precision base resident on a card that could not otherwise hold
|
|
@@ -184,6 +198,7 @@ def train(manifest: dict[str, Any]) -> str | None:
|
|
|
184
198
|
manifest["datasetDir"], manifest["modelsDir"], arch.key, str(device), dtype, resolution,
|
|
185
199
|
flip=bool(hp.get("flipAugment")), dropout=dropout,
|
|
186
200
|
clip_frames=archs.clip_frames(arch, hp.get("clipSeconds")),
|
|
201
|
+
clip_window=str(hp.get("clipWindow") or "start"),
|
|
187
202
|
on_status=lambda text: protocol.progress(0, steps, status=text),
|
|
188
203
|
)
|
|
189
204
|
|
|
@@ -204,9 +219,11 @@ def train(manifest: dict[str, Any]) -> str | None:
|
|
|
204
219
|
)
|
|
205
220
|
plan = quant.value + (" + cpu offload" if offload else "")
|
|
206
221
|
protocol.progress(0, steps, status=f"loading model ({plan})")
|
|
222
|
+
print(_vram_note("VRAM after caching, before the base loads"), flush=True)
|
|
207
223
|
transformer = models.load_transformer(
|
|
208
224
|
manifest["modelsDir"], arch.key, manifest["baseMode"], str(device), dtype, quant
|
|
209
225
|
)
|
|
226
|
+
print(_vram_note("VRAM after the base loaded"), flush=True)
|
|
210
227
|
transformer.requires_grad_(False)
|
|
211
228
|
# PEFT picks its bitsandbytes-aware LoRA layer off this one attribute. Without it, and because
|
|
212
229
|
# bnb's Linear4bit subclasses nn.Linear, the generic dispatcher matches instead: grads still
|
|
@@ -236,6 +253,10 @@ def train(manifest: dict[str, Any]) -> str | None:
|
|
|
236
253
|
signal.signal(signal.SIGTERM, stop)
|
|
237
254
|
|
|
238
255
|
transformer.train()
|
|
256
|
+
# Before the first step, not after: emitting only on completion makes a slow step one look like
|
|
257
|
+
# the loader is still running.
|
|
258
|
+
print(_vram_note("VRAM entering the training loop"), flush=True)
|
|
259
|
+
protocol.progress(start, steps, status="training")
|
|
239
260
|
for step in range(start, steps):
|
|
240
261
|
if stop.flagged:
|
|
241
262
|
break
|
|
@@ -192,3 +192,132 @@ def test_silent_without_torch(monkeypatch) -> None:
|
|
|
192
192
|
monkeypatch.setattr(builtins, "__import__", _no_torch)
|
|
193
193
|
monkeypatch.delitem(__import__("sys").modules, "torch", raising=False)
|
|
194
194
|
assert detect.cpu_only_torch_warning() is None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# --- CUDA within-major binary compatibility -----------------------------------------------------
|
|
198
|
+
#
|
|
199
|
+
# A cubin built for sm_8x runs on any sm_8y where y >= x, so sm_86 covers an sm_89 Ada card. Exact
|
|
200
|
+
# per-minor matching told every RTX 40-series owner their install was broken.
|
|
201
|
+
|
|
202
|
+
_CU130_ARCHES = ["sm_75", "sm_80", "sm_86", "sm_90", "sm_100", "sm_120"]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_arch_parse_reads_the_minor_as_the_last_digit() -> None:
|
|
206
|
+
"""sm_120 is (12, 0), not (1, 20). Left-to-right puts the bug on the Blackwell parts."""
|
|
207
|
+
assert detect._parse_arch("sm_120") == (12, 0)
|
|
208
|
+
assert detect._parse_arch("sm_100") == (10, 0)
|
|
209
|
+
assert detect._parse_arch("sm_90a") == (9, 0) # tuned variant
|
|
210
|
+
assert detect._parse_arch("compute_90") is None
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_ada_is_covered_by_ampere_kernels(monkeypatch) -> None:
|
|
214
|
+
"""The RTX 4080 false positive: sm_89 against a wheel whose newest 8.x is sm_86."""
|
|
215
|
+
monkeypatch.setitem(
|
|
216
|
+
__import__("sys").modules,
|
|
217
|
+
"torch",
|
|
218
|
+
_fake_arch_torch(capability=(8, 9), arches=_CU124_ARCHES),
|
|
219
|
+
)
|
|
220
|
+
assert detect.unsupported_arch_warning() is None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_compatibility_runs_upward_only(monkeypatch) -> None:
|
|
224
|
+
"""An sm_86 cubin does NOT run on an sm_80 A100, so that must still warn."""
|
|
225
|
+
monkeypatch.setitem(
|
|
226
|
+
__import__("sys").modules,
|
|
227
|
+
"torch",
|
|
228
|
+
_fake_arch_torch(capability=(8, 0), arches=["sm_86"]),
|
|
229
|
+
)
|
|
230
|
+
assert detect.unsupported_arch_warning() is not None
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_a_dropped_architecture_still_warns(monkeypatch) -> None:
|
|
234
|
+
"""cu130 dropped Volta; sm_70 has no same-major kernel at or below it."""
|
|
235
|
+
monkeypatch.setitem(
|
|
236
|
+
__import__("sys").modules,
|
|
237
|
+
"torch",
|
|
238
|
+
_fake_arch_torch(capability=(7, 0), arches=_CU130_ARCHES),
|
|
239
|
+
)
|
|
240
|
+
assert detect.unsupported_arch_warning() is not None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def test_blackwell_majors_do_not_cover_each_other() -> None:
|
|
244
|
+
"""sm_100 and sm_120 are both Blackwell but different majors, so neither covers the other."""
|
|
245
|
+
assert detect.arch_list_covers(["sm_100"], 12, 0) is False
|
|
246
|
+
assert detect.arch_list_covers(["sm_120"], 10, 0) is False
|
|
247
|
+
assert detect.arch_list_covers(_CU130_ARCHES, 10, 0) is True
|
|
248
|
+
assert detect.arch_list_covers(_CU130_ARCHES, 12, 0) is True
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# --- the install-time probe --------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _fake_probe_torch(
|
|
255
|
+
*, version: str, arches: list[str], capability: tuple[int, int] | None = (12, 0),
|
|
256
|
+
hip: str | None = None, devices: int = 1,
|
|
257
|
+
) -> types.SimpleNamespace:
|
|
258
|
+
return types.SimpleNamespace(
|
|
259
|
+
__version__=version,
|
|
260
|
+
version=types.SimpleNamespace(cuda=None if "+cpu" in version else "12.8", hip=hip),
|
|
261
|
+
cuda=types.SimpleNamespace(
|
|
262
|
+
device_count=lambda: devices,
|
|
263
|
+
get_arch_list=lambda: arches,
|
|
264
|
+
get_device_capability=lambda i=0: capability,
|
|
265
|
+
),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _probe_with(monkeypatch, torch_stub) -> dict:
|
|
270
|
+
from inline_core.device import probe as probe_mod
|
|
271
|
+
|
|
272
|
+
monkeypatch.setitem(__import__("sys").modules, "torch", torch_stub)
|
|
273
|
+
return probe_mod.probe()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_probe_reports_an_uncovered_wheel_as_replaceable(monkeypatch) -> None:
|
|
277
|
+
"""The 5060 Ti case: a cu126 wheel on sm_120, and the +cuXXX tag makes it safe to replace."""
|
|
278
|
+
got = _probe_with(
|
|
279
|
+
monkeypatch, _fake_probe_torch(version="2.9.0+cu126", arches=_CU124_ARCHES)
|
|
280
|
+
)
|
|
281
|
+
assert got["status"] == "uncovered"
|
|
282
|
+
assert got["replaceable"] is True
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_probe_reports_ada_as_covered(monkeypatch) -> None:
|
|
286
|
+
got = _probe_with(
|
|
287
|
+
monkeypatch,
|
|
288
|
+
_fake_probe_torch(version="2.13.0+cu130", arches=_CU130_ARCHES, capability=(8, 9)),
|
|
289
|
+
)
|
|
290
|
+
assert got["status"] == "covered"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def test_probe_never_offers_to_replace_a_rocm_build(monkeypatch) -> None:
|
|
294
|
+
"""The safety gate. A ROCm build fails the sm_ rule too, and reinstalling over someone's
|
|
295
|
+
deliberate choice is worse than the wrong wheel."""
|
|
296
|
+
got = _probe_with(
|
|
297
|
+
monkeypatch,
|
|
298
|
+
_fake_probe_torch(version="2.9.0+rocm6.2", arches=[], hip="6.2.0"),
|
|
299
|
+
)
|
|
300
|
+
assert got["status"] == "rocm"
|
|
301
|
+
assert got["replaceable"] is False
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def test_probe_never_offers_to_replace_an_untagged_build(monkeypatch) -> None:
|
|
305
|
+
"""A nightly or hand-built wheel carries no +cpu/+cuXXX tag, so leave it alone."""
|
|
306
|
+
got = _probe_with(
|
|
307
|
+
monkeypatch, _fake_probe_torch(version="2.14.0.dev20260101", arches=_CU124_ARCHES)
|
|
308
|
+
)
|
|
309
|
+
assert got["status"] == "uncovered"
|
|
310
|
+
assert got["replaceable"] is False
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def test_probe_survives_a_broken_torch(monkeypatch) -> None:
|
|
314
|
+
"""Never covered on uncertainty: a torch that raises must read as unknown."""
|
|
315
|
+
class Boom:
|
|
316
|
+
__version__ = "2.13.0+cu130"
|
|
317
|
+
|
|
318
|
+
def __getattr__(self, _name: str):
|
|
319
|
+
raise RuntimeError("broken install")
|
|
320
|
+
|
|
321
|
+
got = _probe_with(monkeypatch, Boom())
|
|
322
|
+
assert got["status"] == "unknown"
|
|
323
|
+
assert got["status"] != "covered"
|
|
@@ -452,3 +452,38 @@ def test_long_enough_clip_encodes_on_the_frame_grid(tmp_path) -> None:
|
|
|
452
452
|
frames = h3._clip_frames(clip, clip_frames=24)
|
|
453
453
|
# Snapped down onto H3's 17n+5 grid rather than taking all 40.
|
|
454
454
|
assert len(frames) == 22
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def test_clip_window_takes_the_tail_when_asked(tmp_path) -> None:
|
|
458
|
+
"""The 17n+5 grid only snaps down, so part of every clip is dropped. A user whose action sits in
|
|
459
|
+
the last half second needs the tail rather than the head."""
|
|
460
|
+
from inline_core.training import h3
|
|
461
|
+
|
|
462
|
+
clip = _write_clip(tmp_path / "ramp.mp4", frames=40)
|
|
463
|
+
head = h3._clip_frames(clip, clip_frames=24, window="start")
|
|
464
|
+
tail = h3._clip_frames(clip, clip_frames=24, window="end")
|
|
465
|
+
|
|
466
|
+
assert len(head) == len(tail) == 22
|
|
467
|
+
# The fixture ramps brightness per frame, so the two windows cannot be the same footage.
|
|
468
|
+
assert head[0].getpixel((0, 0)) != tail[0].getpixel((0, 0))
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def test_clip_window_defaults_to_the_start(tmp_path) -> None:
|
|
472
|
+
from inline_core.training import h3
|
|
473
|
+
|
|
474
|
+
clip = _write_clip(tmp_path / "ramp2.mp4", frames=40)
|
|
475
|
+
assert h3._clip_frames(clip, clip_frames=24)[0].getpixel((0, 0)) == (
|
|
476
|
+
h3._clip_frames(clip, clip_frames=24, window="start")[0].getpixel((0, 0))
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def test_the_loop_reports_training_before_the_first_step(monkeypatch) -> None:
|
|
481
|
+
"""Emitted only on completion, a slow step one reads as a hung loader."""
|
|
482
|
+
import inspect
|
|
483
|
+
|
|
484
|
+
from inline_core.training import trainer
|
|
485
|
+
|
|
486
|
+
source = inspect.getsource(trainer.train)
|
|
487
|
+
enters_loop = source.index("for step in range(start, steps):")
|
|
488
|
+
announces = source.index('status="training"')
|
|
489
|
+
assert announces < enters_loop, "the training status must be sent before the loop, not after"
|
|
@@ -97,8 +97,8 @@ def test_auto_quantization_accounts_for_resolution(monkeypatch, tmp_path) -> Non
|
|
|
97
97
|
|
|
98
98
|
def test_offload_fits_a_bf16_base_that_would_not_otherwise(monkeypatch, tmp_path) -> None:
|
|
99
99
|
"""bf16 1024 on a 45GB card: base (26GB) + activations (~21GB) overflow, so auto-offload turns
|
|
100
|
-
on to keep the base full precision rather than dropping it to NF4.
|
|
101
|
-
|
|
100
|
+
on to keep the base full precision rather than dropping it to NF4. Under a quantized base AUTO
|
|
101
|
+
stays off, but an explicit on/off is the user's answer and wins."""
|
|
102
102
|
root = tmp_path / "models"
|
|
103
103
|
(root / "diffusion_models").mkdir(parents=True)
|
|
104
104
|
(root / "diffusion_models" / "krea2_raw_bf16.safetensors").write_bytes(b"")
|
|
@@ -120,8 +120,10 @@ def test_offload_fits_a_bf16_base_that_would_not_otherwise(monkeypatch, tmp_path
|
|
|
120
120
|
assert off("auto", Quantization.NONE, str(root), archs.KREA2, "raw", 512) is False
|
|
121
121
|
assert off("on", Quantization.NONE, str(root), archs.KREA2, "raw", 512) is True
|
|
122
122
|
assert off("off", Quantization.NONE, str(root), archs.KREA2, "raw", 1024) is False
|
|
123
|
-
#
|
|
124
|
-
assert off("
|
|
123
|
+
# Auto stays off under a quantized base; an explicit choice wins.
|
|
124
|
+
assert off("auto", Quantization.NF4, str(root), archs.KREA2, "raw", 1024) is False
|
|
125
|
+
assert off("on", Quantization.NF4, str(root), archs.KREA2, "raw", 1024) is True
|
|
126
|
+
assert off("off", Quantization.NF4, str(root), archs.KREA2, "raw", 1024) is False
|
|
125
127
|
|
|
126
128
|
|
|
127
129
|
def test_zimage_has_no_four_bit_path_and_says_so(tmp_path) -> None:
|
|
@@ -198,3 +200,26 @@ def test_fails_open_when_the_machine_cannot_be_read(monkeypatch) -> None:
|
|
|
198
200
|
_fake_env(monkeypatch, mode=0, ram_gib=30, size_gib=62)
|
|
199
201
|
monkeypatch.setattr(models, "_base_size", lambda *a: 0)
|
|
200
202
|
models.check_base_mappable("/m", "minimax-h3", "raw")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_an_explicit_offload_choice_beats_the_quant_heuristic(monkeypatch, tmp_path) -> None:
|
|
206
|
+
"""Dead for H3, always 4-bit, whose clip activations are what overflow the card."""
|
|
207
|
+
from inline_core.device.policy import Quantization
|
|
208
|
+
|
|
209
|
+
root = tmp_path / "models"
|
|
210
|
+
(root / "diffusion_models").mkdir(parents=True)
|
|
211
|
+
monkeypatch.setattr(models, "_base_size", lambda *a: 12 * 1024**3)
|
|
212
|
+
off = models.resolve_offload
|
|
213
|
+
|
|
214
|
+
assert off("on", Quantization.NF4, str(root), archs.MINIMAX_H3, "raw", 512) is True
|
|
215
|
+
assert off("off", Quantization.NF4, str(root), archs.MINIMAX_H3, "raw", 512) is False
|
|
216
|
+
assert off("auto", Quantization.NF4, str(root), archs.MINIMAX_H3, "raw", 512) is False
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_an_unknown_offload_preference_still_raises(monkeypatch, tmp_path) -> None:
|
|
220
|
+
"""The reorder must not let a typo fall through to the auto path and silently mean 'off'."""
|
|
221
|
+
from inline_core.device.policy import Quantization
|
|
222
|
+
|
|
223
|
+
monkeypatch.setattr(models, "_base_size", lambda *a: 12 * 1024**3)
|
|
224
|
+
with pytest.raises(RuntimeError):
|
|
225
|
+
models.resolve_offload("yes", Quantization.NF4, str(tmp_path), archs.MINIMAX_H3, "raw", 512)
|