inline-core 1.2.62__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.
Files changed (256) hide show
  1. {inline_core-1.2.62 → inline_core-1.2.64}/PKG-INFO +2 -1
  2. {inline_core-1.2.62 → inline_core-1.2.64}/pyproject.toml +11 -4
  3. {inline_core-1.2.62 → inline_core-1.2.64}/scripts/minimax_h3_train_matrix.py +7 -4
  4. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/device/detect.py +49 -1
  5. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/load.py +5 -8
  6. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/server/__main__.py +20 -7
  7. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/handlers.py +2 -0
  8. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/store.py +40 -1
  9. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/training.py +36 -0
  10. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/training/arch.py +26 -28
  11. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/training/cache.py +11 -2
  12. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/training/caption.py +19 -2
  13. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/training/dataset.py +15 -5
  14. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/training/h3.py +170 -54
  15. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/training/models.py +67 -0
  16. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/training/trainer.py +24 -8
  17. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_device_detect.py +69 -1
  18. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_minimaxh3_training.py +148 -0
  19. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_store.py +32 -0
  20. inline_core-1.2.64/tests/test_studio_training.py +175 -0
  21. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_training_models.py +65 -0
  22. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_webui_install.py +100 -1
  23. {inline_core-1.2.62 → inline_core-1.2.64}/uv.lock +47 -85
  24. {inline_core-1.2.62 → inline_core-1.2.64}/webui.bat +53 -5
  25. {inline_core-1.2.62 → inline_core-1.2.64}/webui.sh +81 -7
  26. inline_core-1.2.62/tests/test_studio_training.py +0 -81
  27. {inline_core-1.2.62 → inline_core-1.2.64}/.gitignore +0 -0
  28. {inline_core-1.2.62 → inline_core-1.2.64}/.python-version +0 -0
  29. {inline_core-1.2.62 → inline_core-1.2.64}/CLAUDE.md +0 -0
  30. {inline_core-1.2.62 → inline_core-1.2.64}/README.md +0 -0
  31. {inline_core-1.2.62 → inline_core-1.2.64}/main.py +0 -0
  32. {inline_core-1.2.62 → inline_core-1.2.64}/scripts/flux2_train_matrix.py +0 -0
  33. {inline_core-1.2.62 → inline_core-1.2.64}/scripts/minimax_h3_lora_check.py +0 -0
  34. {inline_core-1.2.62 → inline_core-1.2.64}/scripts/reference.py +0 -0
  35. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/__init__.py +0 -0
  36. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/components/__init__.py +0 -0
  37. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/components/conditioning.py +0 -0
  38. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/components/interfaces.py +0 -0
  39. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/config.py +0 -0
  40. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/device/__init__.py +0 -0
  41. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/device/auto.py +0 -0
  42. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/device/memory.py +0 -0
  43. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/device/policy.py +0 -0
  44. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/device/types.py +0 -0
  45. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/errors.py +0 -0
  46. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/__init__.py +0 -0
  47. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/api.py +0 -0
  48. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/constraints.py +0 -0
  49. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/fetch.py +0 -0
  50. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/handlers.py +0 -0
  51. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/importer.py +0 -0
  52. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/install.py +0 -0
  53. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/loader.py +0 -0
  54. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/manifest.py +0 -0
  55. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/models.py +0 -0
  56. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/paths.py +0 -0
  57. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/resolve.py +0 -0
  58. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/scanner.py +0 -0
  59. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/state.py +0 -0
  60. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/extensions/tools.py +0 -0
  61. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/ffmpeg.py +0 -0
  62. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/graph/__init__.py +0 -0
  63. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/graph/cache.py +0 -0
  64. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/graph/descriptor.py +0 -0
  65. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/graph/executor.py +0 -0
  66. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/graph/loader_runners.py +0 -0
  67. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/graph/primitives.py +0 -0
  68. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/graph/registry.py +0 -0
  69. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/graph/runners.py +0 -0
  70. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/graph/schema.py +0 -0
  71. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/graph/topo.py +0 -0
  72. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/graph/validate.py +0 -0
  73. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/media.py +0 -0
  74. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/__init__.py +0 -0
  75. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/catalog.py +0 -0
  76. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/checkpoint.py +0 -0
  77. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/controlspace.py +0 -0
  78. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/flux2/__init__.py +0 -0
  79. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/flux2/controlnet.py +0 -0
  80. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/flux2/embeds.py +0 -0
  81. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/flux2/provider.py +0 -0
  82. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/flux2/requirements.py +0 -0
  83. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/flux2/runner.py +0 -0
  84. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/flux2/variants.py +0 -0
  85. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/keymap.py +0 -0
  86. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/krea2/__init__.py +0 -0
  87. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/krea2/convert.py +0 -0
  88. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/krea2/depth_control.py +0 -0
  89. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/krea2/img2img.py +0 -0
  90. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/krea2/provider.py +0 -0
  91. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/krea2/requirements.py +0 -0
  92. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/krea2/runner.py +0 -0
  93. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/loaders.py +0 -0
  94. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/lora.py +0 -0
  95. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/__init__.py +0 -0
  96. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/adaln.py +0 -0
  97. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/keys.py +0 -0
  98. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/pipeline.py +0 -0
  99. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/provider.py +0 -0
  100. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/requirements.py +0 -0
  101. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/runner.py +0 -0
  102. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vae_keys.py +0 -0
  103. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/__init__.py +0 -0
  104. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3.py +0 -0
  105. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3_audio.py +0 -0
  106. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/before_denoise.py +0 -0
  107. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/before_encoder.py +0 -0
  108. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/decoders.py +0 -0
  109. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/denoise.py +0 -0
  110. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/encoders.py +0 -0
  111. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/modular_blocks_minimax_h3.py +0 -0
  112. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/modular_pipeline.py +0 -0
  113. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/packing.py +0 -0
  114. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/packing_ref2va.py +0 -0
  115. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/scheduling_minimax_h3.py +0 -0
  116. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/minimaxh3/vendor/transformer_minimax_h3.py +0 -0
  117. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/offload.py +0 -0
  118. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/pipeline_runtime.py +0 -0
  119. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/prepared.py +0 -0
  120. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/preprocess/__init__.py +0 -0
  121. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/preprocess/requirements.py +0 -0
  122. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/preprocess/runner.py +0 -0
  123. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/references.py +0 -0
  124. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/requirements.py +0 -0
  125. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/sampling.py +0 -0
  126. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/video_params.py +0 -0
  127. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/zimage/__init__.py +0 -0
  128. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/zimage/primitives.py +0 -0
  129. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/zimage/provider.py +0 -0
  130. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/zimage/requirements.py +0 -0
  131. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/models/zimage/runner.py +0 -0
  132. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/parallel/__init__.py +0 -0
  133. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/parallel/config.py +0 -0
  134. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/parallel/group.py +0 -0
  135. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/parallel/launch.py +0 -0
  136. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/parallel/protocol.py +0 -0
  137. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/parallel/registry.py +0 -0
  138. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/parallel/worker.py +0 -0
  139. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/runtime/__init__.py +0 -0
  140. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/runtime/context.py +0 -0
  141. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/runtime/file_store.py +0 -0
  142. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/runtime/progress.py +0 -0
  143. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/runtime/run.py +0 -0
  144. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/runtime/store.py +0 -0
  145. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/runtime/video_encode.py +0 -0
  146. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/sampling/__init__.py +0 -0
  147. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/sampling/batch.py +0 -0
  148. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/server/__init__.py +0 -0
  149. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/server/app.py +0 -0
  150. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/server/assets.py +0 -0
  151. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/server/bootstrap.py +0 -0
  152. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/server/frontend.py +0 -0
  153. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/server/manager.py +0 -0
  154. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/server/rpc.py +0 -0
  155. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/server/run_store.py +0 -0
  156. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/server/serialize.py +0 -0
  157. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/__init__.py +0 -0
  158. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/assets.py +0 -0
  159. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/config.py +0 -0
  160. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/fal.py +0 -0
  161. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/frames.py +0 -0
  162. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/generation.py +0 -0
  163. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/graph_build.py +0 -0
  164. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/image_meta.py +0 -0
  165. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/models.py +0 -0
  166. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/moodboard.py +0 -0
  167. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/peaks.py +0 -0
  168. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/recipe.py +0 -0
  169. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/schema.py +0 -0
  170. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/system_stats.py +0 -0
  171. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/timeline/__init__.py +0 -0
  172. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/timeline/compose.py +0 -0
  173. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/timeline/ffmpeg.py +0 -0
  174. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/timeline/render.py +0 -0
  175. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/timeline/resolve.py +0 -0
  176. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/studio/training_store.py +0 -0
  177. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/takes.py +0 -0
  178. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/training/__init__.py +0 -0
  179. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/training/__main__.py +0 -0
  180. {inline_core-1.2.62 → inline_core-1.2.64}/src/inline_core/training/protocol.py +0 -0
  181. {inline_core-1.2.62 → inline_core-1.2.64}/tests/conftest.py +0 -0
  182. {inline_core-1.2.62 → inline_core-1.2.64}/tests/helpers.py +0 -0
  183. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_cache.py +0 -0
  184. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_catalog.py +0 -0
  185. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_checkpoint.py +0 -0
  186. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_config.py +0 -0
  187. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_executor.py +0 -0
  188. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_extension_api.py +0 -0
  189. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_extension_install.py +0 -0
  190. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_extension_manifest.py +0 -0
  191. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_extension_resolve.py +0 -0
  192. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_extension_scanner.py +0 -0
  193. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_extension_spine.py +0 -0
  194. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_extension_state.py +0 -0
  195. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_file_store.py +0 -0
  196. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_flux2_controlnet.py +0 -0
  197. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_flux2_folder.py +0 -0
  198. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_flux2_resolve.py +0 -0
  199. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_flux2_runner.py +0 -0
  200. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_flux2_training.py +0 -0
  201. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_flux2_variants.py +0 -0
  202. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_frontend_serving.py +0 -0
  203. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_hidden_nodes.py +0 -0
  204. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_keymap.py +0 -0
  205. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_krea2_convert.py +0 -0
  206. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_krea2_depth_control.py +0 -0
  207. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_krea2_requirements.py +0 -0
  208. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_krea2_runner.py +0 -0
  209. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_loader_runners.py +0 -0
  210. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_loaders.py +0 -0
  211. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_lora.py +0 -0
  212. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_lora_download.py +0 -0
  213. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_memory_policy.py +0 -0
  214. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_minimaxh3_adaln.py +0 -0
  215. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_minimaxh3_keys.py +0 -0
  216. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_minimaxh3_load.py +0 -0
  217. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_minimaxh3_nodes.py +0 -0
  218. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_model_requirements.py +0 -0
  219. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_offload_prepared.py +0 -0
  220. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_output_kind_contract.py +0 -0
  221. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_parallel_group.py +0 -0
  222. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_pipeline_cache.py +0 -0
  223. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_primitives.py +0 -0
  224. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_recipe.py +0 -0
  225. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_references.py +0 -0
  226. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_rpc_bridge.py +0 -0
  227. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_run_store.py +0 -0
  228. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_sampling.py +0 -0
  229. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_schema.py +0 -0
  230. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_server.py +0 -0
  231. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_staged_residency.py +0 -0
  232. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_assets.py +0 -0
  233. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_fal.py +0 -0
  234. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_frames.py +0 -0
  235. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_generation.py +0 -0
  236. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_graph_build.py +0 -0
  237. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_models.py +0 -0
  238. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_moodboard.py +0 -0
  239. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_multi_reference.py +0 -0
  240. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_node_size.py +0 -0
  241. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_peaks.py +0 -0
  242. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_rpc.py +0 -0
  243. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_schema.py +0 -0
  244. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_studio_timeline.py +0 -0
  245. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_take_bytes.py +0 -0
  246. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_topo.py +0 -0
  247. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_training_arch.py +0 -0
  248. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_training_dataset.py +0 -0
  249. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_training_resolve.py +0 -0
  250. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_validate.py +0 -0
  251. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_video_encode.py +0 -0
  252. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_video_params.py +0 -0
  253. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_xfuser_sampler.py +0 -0
  254. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_zimage_primitives.py +0 -0
  255. {inline_core-1.2.62 → inline_core-1.2.64}/tests/test_zimage_resolve.py +0 -0
  256. {inline_core-1.2.62 → 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.62
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.62"
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-cu124"
106
- url = "https://download.pytorch.org/whl/cu124"
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-cu124", marker = "sys_platform == 'win32'" }]
117
+ torch = [{ index = "pytorch-cu126", marker = "sys_platform == 'win32'" }]
111
118
 
112
119
  [project.scripts]
113
120
  inline-studio = "inline_core.server.__main__:main"
@@ -34,7 +34,9 @@ _STEPS = 12
34
34
  _RANK = 16
35
35
 
36
36
 
37
- def _manifest(work: Path, dataset: Path, models: Path, resolution: int) -> Path:
37
+ def _manifest(
38
+ work: Path, dataset: Path, models: Path, resolution: int, steps: int = _STEPS
39
+ ) -> Path:
38
40
  """The same manifest shape `studio/training.py::_prepare` writes."""
39
41
  checkpoints = work / "checkpoints"
40
42
  checkpoints.mkdir(parents=True, exist_ok=True)
@@ -62,8 +64,8 @@ def _manifest(work: Path, dataset: Path, models: Path, resolution: int) -> Path:
62
64
  "alpha": _RANK,
63
65
  "learningRate": 1e-4,
64
66
  "batchSize": 1,
65
- "steps": _STEPS,
66
- "saveEvery": _STEPS,
67
+ "steps": steps,
68
+ "saveEvery": steps,
67
69
  "resolution": resolution,
68
70
  },
69
71
  "gpuIds": [],
@@ -152,6 +154,7 @@ def main() -> int:
152
154
  parser.add_argument("--python", default=str(_CORE / ".venv" / "bin" / "python"))
153
155
  parser.add_argument("--gpu", default="", help="label for the results file, e.g. 'L40S (46GB)'")
154
156
  parser.add_argument("--only", default="", help="one resolution, to redo a single row")
157
+ parser.add_argument("--steps", type=int, default=_STEPS, help="override for a smoke run")
155
158
  args = parser.parse_args()
156
159
 
157
160
  if not args.dataset.is_dir():
@@ -186,7 +189,7 @@ def main() -> int:
186
189
  continue
187
190
  work = args.out / cell_id
188
191
  work.mkdir(parents=True, exist_ok=True)
189
- manifest = _manifest(work, args.dataset, args.models, resolution)
192
+ manifest = _manifest(work, args.dataset, args.models, resolution, args.steps)
190
193
  print(f"--- {cell_id}px, 4-bit base ---", flush=True)
191
194
  result = _run_cell(args.python, manifest, args.out / f"{cell_id}.log")
192
195
  result.update({"resolution": resolution, "base_quant": "nf4"})
@@ -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/cu124 torch` (or re-run `webui.sh --install`)."
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.
@@ -165,9 +165,8 @@ def load_transformer(
165
165
  def _fusing_shrink(plan: Any, fused: set[str], inner: Any) -> Any:
166
166
  """Fuse a block's share of the LoRA stack the moment it lands, then hand off to ``shrink``.
167
167
 
168
- Order matters both ways: after the stream the block's weights exist, and before ``shrink`` they
169
- are still unquantised, which is the only window in which a full-precision delta can be added
170
- in place.
168
+ The only window that works: after the stream the weights exist, before ``shrink`` they are
169
+ still unquantised, and a full-precision delta cannot be added to a quantised weight.
171
170
  """
172
171
 
173
172
  def shrink(model: Any, prefix: str) -> None:
@@ -195,11 +194,9 @@ def _fuse_subtree(module: Any, plan: Any, prefix: str) -> set[str]:
195
194
  def _finish_fuse(model: Any, plan: Any, fused: set[str]) -> None:
196
195
  """Fuse what the block callback never saw, then prove nothing was missed.
197
196
 
198
- The callback only fires for ``transformer_blocks.N``, so ``context_embedder``, the token
199
- refiner and the output heads would silently keep their base weights - and silently is the whole
200
- problem. ``plan_loras`` only raises when a key matches *no* module in the model, and these
201
- modules do exist, so a partial fuse validates clean and then degrades the output without ever
202
- erroring. The residual pass covers them and the check makes any remaining gap loud.
197
+ The callback fires only for ``transformer_blocks.N``, so ``context_embedder`` and the token
198
+ refiner would keep their base weights. ``plan_loras`` raises only when a key matches *no*
199
+ module, and these exist, so a partial fuse validates clean and then degrades output silently.
203
200
  """
204
201
  residual = {name: deltas for name, deltas in plan.items() if name not in fused}
205
202
  if residual:
@@ -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
- registered, extensions = register_models(
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
- print(f"Studio data: {studio_config.data_dir()} | workspace: {studio_config.workspace_dir()}")
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
 
@@ -76,21 +76,12 @@ _FLUX2_TARGETS = [
76
76
  ]
77
77
 
78
78
 
79
- #: MiniMax H3: the attention and SwiGLU feed-forward Linears of all 50 blocks, plus the text
80
- #: projection. Confirmed against MiniMaxH3Transformer3DModel.named_modules(): a block's Linears are
81
- #: exactly to_q/to_k/to_v/to_out.0, ff.net.0.proj, ff.net.2 and adaln_proj.linear.
79
+ #: MiniMax H3: every Linear in a block except ``adaln_proj``, plus the text projection.
82
80
  #:
83
- #: ``adaln_proj.linear`` is deliberately absent. It is replaced at load by the rank-8 factorisation
84
- #: (``minimaxh3/adaln.py``) that takes the checkpoint from 66GB to 40GB, so the module a LoRA would
85
- #: attach to carries the whole modulation signal through eight columns - the same reason
86
- #: ``minimaxh3/pipeline.py`` refuses to quantize it.
87
- #:
88
- #: The fp32-pinned modules are absent for a different reason: H3 ships a mixed-precision checkpoint
89
- #: where the patch projections, the timestep MLP and the two output heads stay float32
90
- #: (``_keep_in_fp32_modules``), and adapting those fights the precision split, not the model.
91
- #:
92
- #: PEFT matches by name suffix, so this also adapts the two token-refiner blocks. That is wanted,
93
- #: and it is why the loader must fuse outside the block stack too - see ``minimaxh3/load.py``.
81
+ #: ``adaln_proj`` is out because the load factorises it to rank 8, so a LoRA would attach to eight
82
+ #: columns carrying the whole modulation signal. The fp32-pinned heads are out because adapting them
83
+ #: fights the checkpoint's precision split. PEFT matches by suffix, so this also reaches the two
84
+ #: token-refiner blocks, which is why the loader has to fuse outside the block stack.
94
85
  _MINIMAX_H3_TARGETS = [
95
86
  "to_q",
96
87
  "to_k",
@@ -270,13 +261,8 @@ _H3_PATCH = (1, 2, 2)
270
261
  def _h3_forward(transformer: Any, noisy: Any, timestep: Any, item: dict[str, Any]) -> Any:
271
262
  """One prediction from MiniMaxH3Transformer3DModel, mirroring the vendored denoise block.
272
263
 
273
- H3 runs one packed 1-D sequence holding text, audio and video rows rather than separate streams,
274
- and the caller owns that layout. The precache builds it once per image (it only depends on the
275
- caption length and the latent grid) and caches the tensors, so a step just patchifies, assigns
276
- every row this step's timestep, and selects the video rows back out.
277
-
278
- A still is one latent frame with no audio rows at all, which the model accepts: the audio head
279
- runs over an empty index and returns empty.
264
+ H3 packs text, audio and video into one 1-D sequence and the caller owns that layout, so the
265
+ precache builds it per image and a step only patchifies and selects the video rows back out.
280
266
  """
281
267
  from ..models.minimaxh3.vendor.packing import patchify_video_latents, unpatchify_video_tokens
282
268
 
@@ -336,14 +322,10 @@ ARCHS: dict[str, TrainingArch] = {
336
322
  MINIMAX_H3: TrainingArch(
337
323
  key=MINIMAX_H3,
338
324
  target_modules=_MINIMAX_H3_TARGETS,
339
- # H3's shift is the same expression Z-Image uses, at the scheduler's video shift of 12.0
340
- # (MiniMaxH3Scheduler builds its grid as shift * s / (1 + (shift - 1) * s)), so the
341
- # generic sigma applies unchanged.
325
+ # Same shift expression as Z-Image, at the scheduler's video shift of 12.0.
342
326
  sigma=_zimage_sigma,
343
- # Z-Image's convention exactly, and worth a test rather than a comment because Krea 2 and
344
- # FLUX.2 use the opposite one: MiniMaxH3Scheduler.scale_noise is
345
- # x_t = t * clean + (1 - t) * noise at t = 1 - sigma, and its step reconstructs
346
- # x0 = x_t + sigma * v, so the velocity the model predicts is clean - noise.
327
+ # Z-Image's convention, opposite to Krea 2 and FLUX.2, and pinned against the vendored
328
+ # scheduler in test_minimaxh3_training.py rather than restated here.
347
329
  timestep=lambda sigma: 1.0 - sigma,
348
330
  target=lambda clean, noise: clean - noise,
349
331
  forward=_h3_forward,
@@ -351,6 +333,22 @@ ARCHS: dict[str, TrainingArch] = {
351
333
  }
352
334
 
353
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
+
354
352
  def get(key: str | None) -> TrainingArch:
355
353
  """The arch to train. Defaults to Z-Image so a run predating Krea 2 still resumes."""
356
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 = Image.open(path).convert("RGB")
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
- def _pairs(dataset_dir: Path) -> list[tuple[Path, str]]:
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 img in sorted(dataset_dir.iterdir()):
24
- if img.suffix.lower() not in _IMAGE_SUFFIXES:
33
+ for media in sorted(dataset_dir.iterdir()):
34
+ if media.suffix.lower() not in suffixes:
25
35
  continue
26
- caption_file = img.with_suffix(".txt")
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((img, caption))
38
+ out.append((media, caption))
29
39
  return out
30
40
 
31
41