inline-core 1.2.3__tar.gz → 1.2.6__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 (213) hide show
  1. {inline_core-1.2.3 → inline_core-1.2.6}/CLAUDE.md +33 -4
  2. {inline_core-1.2.3 → inline_core-1.2.6}/PKG-INFO +60 -19
  3. {inline_core-1.2.3 → inline_core-1.2.6}/README.md +37 -16
  4. {inline_core-1.2.3 → inline_core-1.2.6}/pyproject.toml +42 -6
  5. inline_core-1.2.6/src/inline_core/__init__.py +14 -0
  6. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/device/memory.py +16 -4
  7. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/device/policy.py +5 -1
  8. inline_core-1.2.6/src/inline_core/ffmpeg.py +30 -0
  9. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/graph/cache.py +35 -2
  10. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/graph/executor.py +16 -4
  11. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/graph/loader_runners.py +32 -1
  12. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/graph/primitives.py +16 -1
  13. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/graph/schema.py +6 -1
  14. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/models/catalog.py +21 -0
  15. inline_core-1.2.6/src/inline_core/models/checkpoint.py +92 -0
  16. inline_core-1.2.6/src/inline_core/models/controlspace.py +26 -0
  17. inline_core-1.2.6/src/inline_core/models/flux2/__init__.py +1 -0
  18. inline_core-1.2.6/src/inline_core/models/flux2/controlnet.py +234 -0
  19. inline_core-1.2.6/src/inline_core/models/flux2/embeds.py +165 -0
  20. inline_core-1.2.6/src/inline_core/models/flux2/provider.py +84 -0
  21. inline_core-1.2.6/src/inline_core/models/flux2/requirements.py +414 -0
  22. inline_core-1.2.6/src/inline_core/models/flux2/runner.py +677 -0
  23. inline_core-1.2.6/src/inline_core/models/flux2/variants.py +334 -0
  24. inline_core-1.2.6/src/inline_core/models/krea2/__init__.py +9 -0
  25. inline_core-1.2.6/src/inline_core/models/krea2/convert.py +138 -0
  26. inline_core-1.2.6/src/inline_core/models/krea2/depth_control.py +149 -0
  27. inline_core-1.2.6/src/inline_core/models/krea2/img2img.py +89 -0
  28. inline_core-1.2.6/src/inline_core/models/krea2/provider.py +68 -0
  29. inline_core-1.2.6/src/inline_core/models/krea2/requirements.py +254 -0
  30. inline_core-1.2.6/src/inline_core/models/krea2/runner.py +466 -0
  31. inline_core-1.2.6/src/inline_core/models/loaders.py +1385 -0
  32. inline_core-1.2.6/src/inline_core/models/lora.py +190 -0
  33. inline_core-1.2.6/src/inline_core/models/pipeline_runtime.py +563 -0
  34. inline_core-1.2.6/src/inline_core/models/preprocess/__init__.py +5 -0
  35. inline_core-1.2.6/src/inline_core/models/preprocess/requirements.py +59 -0
  36. inline_core-1.2.6/src/inline_core/models/preprocess/runner.py +174 -0
  37. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/models/requirements.py +47 -0
  38. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/models/zimage/provider.py +12 -0
  39. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/models/zimage/requirements.py +90 -4
  40. inline_core-1.2.6/src/inline_core/models/zimage/runner.py +529 -0
  41. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/server/app.py +39 -4
  42. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/server/bootstrap.py +35 -0
  43. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/server/rpc.py +5 -0
  44. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/server/serialize.py +28 -4
  45. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/fal.py +43 -5
  46. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/frames.py +44 -18
  47. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/generation.py +38 -8
  48. inline_core-1.2.6/src/inline_core/studio/graph_build.py +326 -0
  49. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/handlers.py +82 -8
  50. inline_core-1.2.6/src/inline_core/studio/image_meta.py +36 -0
  51. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/models.py +6 -2
  52. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/moodboard.py +157 -25
  53. inline_core-1.2.6/src/inline_core/studio/recipe.py +109 -0
  54. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/schema.py +66 -4
  55. inline_core-1.2.6/src/inline_core/studio/system_stats.py +92 -0
  56. inline_core-1.2.6/src/inline_core/studio/training.py +485 -0
  57. inline_core-1.2.6/src/inline_core/studio/training_store.py +274 -0
  58. inline_core-1.2.6/src/inline_core/training/__init__.py +10 -0
  59. inline_core-1.2.6/src/inline_core/training/__main__.py +74 -0
  60. inline_core-1.2.6/src/inline_core/training/arch.py +287 -0
  61. inline_core-1.2.6/src/inline_core/training/caption.py +160 -0
  62. inline_core-1.2.6/src/inline_core/training/dataset.py +209 -0
  63. inline_core-1.2.6/src/inline_core/training/models.py +418 -0
  64. inline_core-1.2.6/src/inline_core/training/protocol.py +56 -0
  65. inline_core-1.2.6/src/inline_core/training/trainer.py +261 -0
  66. {inline_core-1.2.3 → inline_core-1.2.6}/tests/helpers.py +1 -0
  67. inline_core-1.2.6/tests/test_cache.py +116 -0
  68. inline_core-1.2.6/tests/test_checkpoint.py +79 -0
  69. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_executor.py +26 -1
  70. inline_core-1.2.6/tests/test_flux2_controlnet.py +159 -0
  71. inline_core-1.2.6/tests/test_flux2_folder.py +179 -0
  72. inline_core-1.2.6/tests/test_flux2_resolve.py +176 -0
  73. inline_core-1.2.6/tests/test_flux2_runner.py +126 -0
  74. inline_core-1.2.6/tests/test_flux2_training.py +199 -0
  75. inline_core-1.2.6/tests/test_flux2_variants.py +129 -0
  76. inline_core-1.2.6/tests/test_krea2_convert.py +157 -0
  77. inline_core-1.2.6/tests/test_krea2_depth_control.py +104 -0
  78. inline_core-1.2.6/tests/test_krea2_requirements.py +148 -0
  79. inline_core-1.2.6/tests/test_krea2_runner.py +164 -0
  80. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_loaders.py +50 -3
  81. inline_core-1.2.6/tests/test_lora.py +218 -0
  82. inline_core-1.2.6/tests/test_lora_download.py +75 -0
  83. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_memory_policy.py +39 -0
  84. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_model_requirements.py +25 -0
  85. inline_core-1.2.6/tests/test_pipeline_cache.py +82 -0
  86. inline_core-1.2.6/tests/test_recipe.py +100 -0
  87. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_studio_fal.py +46 -0
  88. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_studio_generation.py +41 -3
  89. inline_core-1.2.6/tests/test_studio_graph_build.py +111 -0
  90. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_studio_models.py +20 -0
  91. inline_core-1.2.6/tests/test_studio_multi_reference.py +163 -0
  92. inline_core-1.2.6/tests/test_studio_node_size.py +58 -0
  93. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_studio_rpc.py +16 -0
  94. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_studio_schema.py +19 -0
  95. inline_core-1.2.6/tests/test_studio_training.py +81 -0
  96. inline_core-1.2.6/tests/test_training_arch.py +113 -0
  97. inline_core-1.2.6/tests/test_training_dataset.py +103 -0
  98. inline_core-1.2.6/tests/test_training_models.py +135 -0
  99. inline_core-1.2.6/tests/test_training_resolve.py +90 -0
  100. inline_core-1.2.6/tests/test_webui_install.py +154 -0
  101. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_zimage_resolve.py +6 -2
  102. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_zimage_runner.py +128 -29
  103. {inline_core-1.2.3 → inline_core-1.2.6}/uv.lock +387 -4
  104. inline_core-1.2.6/webui.bat +319 -0
  105. {inline_core-1.2.3 → inline_core-1.2.6}/webui.sh +97 -19
  106. inline_core-1.2.3/src/inline_core/__init__.py +0 -7
  107. inline_core-1.2.3/src/inline_core/models/loaders.py +0 -471
  108. inline_core-1.2.3/src/inline_core/models/zimage/runner.py +0 -945
  109. inline_core-1.2.3/src/inline_core/studio/graph_build.py +0 -141
  110. inline_core-1.2.3/tests/test_cache.py +0 -48
  111. {inline_core-1.2.3 → inline_core-1.2.6}/.gitignore +0 -0
  112. {inline_core-1.2.3 → inline_core-1.2.6}/.python-version +0 -0
  113. {inline_core-1.2.3 → inline_core-1.2.6}/main.py +0 -0
  114. {inline_core-1.2.3 → inline_core-1.2.6}/scripts/reference.py +0 -0
  115. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/components/__init__.py +0 -0
  116. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/components/conditioning.py +0 -0
  117. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/components/interfaces.py +0 -0
  118. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/config.py +0 -0
  119. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/device/__init__.py +0 -0
  120. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/device/auto.py +0 -0
  121. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/device/detect.py +0 -0
  122. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/device/types.py +0 -0
  123. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/errors.py +0 -0
  124. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/__init__.py +0 -0
  125. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/api.py +0 -0
  126. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/constraints.py +0 -0
  127. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/fetch.py +0 -0
  128. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/handlers.py +0 -0
  129. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/importer.py +0 -0
  130. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/install.py +0 -0
  131. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/loader.py +0 -0
  132. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/manifest.py +0 -0
  133. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/models.py +0 -0
  134. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/paths.py +0 -0
  135. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/resolve.py +0 -0
  136. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/scanner.py +0 -0
  137. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/state.py +0 -0
  138. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/extensions/tools.py +0 -0
  139. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/graph/__init__.py +0 -0
  140. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/graph/descriptor.py +0 -0
  141. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/graph/registry.py +0 -0
  142. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/graph/runners.py +0 -0
  143. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/graph/topo.py +0 -0
  144. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/graph/validate.py +0 -0
  145. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/media.py +0 -0
  146. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/models/__init__.py +0 -0
  147. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/models/sampling.py +0 -0
  148. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/models/zimage/__init__.py +0 -0
  149. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/models/zimage/primitives.py +0 -0
  150. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/parallel/__init__.py +0 -0
  151. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/parallel/config.py +0 -0
  152. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/parallel/group.py +0 -0
  153. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/parallel/launch.py +0 -0
  154. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/parallel/protocol.py +0 -0
  155. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/parallel/registry.py +0 -0
  156. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/parallel/worker.py +0 -0
  157. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/runtime/__init__.py +0 -0
  158. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/runtime/context.py +0 -0
  159. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/runtime/file_store.py +0 -0
  160. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/runtime/progress.py +0 -0
  161. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/runtime/run.py +0 -0
  162. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/runtime/store.py +0 -0
  163. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/sampling/__init__.py +0 -0
  164. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/sampling/batch.py +0 -0
  165. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/server/__init__.py +0 -0
  166. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/server/__main__.py +0 -0
  167. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/server/assets.py +0 -0
  168. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/server/frontend.py +0 -0
  169. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/server/manager.py +0 -0
  170. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/server/run_store.py +0 -0
  171. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/__init__.py +0 -0
  172. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/assets.py +0 -0
  173. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/config.py +0 -0
  174. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/peaks.py +0 -0
  175. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/store.py +0 -0
  176. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/timeline/__init__.py +0 -0
  177. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/timeline/compose.py +0 -0
  178. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/timeline/ffmpeg.py +0 -0
  179. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/timeline/render.py +0 -0
  180. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/studio/timeline/resolve.py +0 -0
  181. {inline_core-1.2.3 → inline_core-1.2.6}/src/inline_core/takes.py +0 -0
  182. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_catalog.py +0 -0
  183. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_config.py +0 -0
  184. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_device_detect.py +0 -0
  185. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_extension_api.py +0 -0
  186. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_extension_install.py +0 -0
  187. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_extension_manifest.py +0 -0
  188. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_extension_resolve.py +0 -0
  189. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_extension_scanner.py +0 -0
  190. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_extension_spine.py +0 -0
  191. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_extension_state.py +0 -0
  192. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_file_store.py +0 -0
  193. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_frontend_serving.py +0 -0
  194. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_hidden_nodes.py +0 -0
  195. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_loader_runners.py +0 -0
  196. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_parallel_group.py +0 -0
  197. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_primitives.py +0 -0
  198. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_rpc_bridge.py +0 -0
  199. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_run_store.py +0 -0
  200. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_sampling.py +0 -0
  201. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_schema.py +0 -0
  202. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_server.py +0 -0
  203. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_studio_assets.py +0 -0
  204. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_studio_frames.py +0 -0
  205. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_studio_moodboard.py +0 -0
  206. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_studio_peaks.py +0 -0
  207. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_studio_store.py +0 -0
  208. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_studio_timeline.py +0 -0
  209. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_take_bytes.py +0 -0
  210. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_topo.py +0 -0
  211. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_validate.py +0 -0
  212. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_xfuser_sampler.py +0 -0
  213. {inline_core-1.2.3 → inline_core-1.2.6}/tests/test_zimage_primitives.py +0 -0
@@ -95,7 +95,16 @@ HTTP/WS → server/app.py → RunManager → Executor → Registry (desc
95
95
  the ComfyUI-equivalent decomposed graph and the intended long-term surface. **Descriptor-only
96
96
  today - their runners land in C2.** A graph built from them validates and type-checks but raises
97
97
  `No runner registered` at execution.
98
- - **Model runners** (`models/<name>/`) - high-level single generation nodes. **`alibaba/z-image-turbo`
98
+ - **Model runners** (`models/<name>/`) - high-level single generation nodes. **`black-forest-labs/flux-2`
99
+ (`models/flux2/`) is the reference for a _family_:** one node covers klein 4B/9B, their base builds,
100
+ the KV variant and dev, because the picked checkpoint is identified from its own tensor shapes
101
+ (`flux2/variants.py`) and everything else follows. Adding a checkpoint is a row in that table.
102
+ Two rules it establishes: **identify a checkpoint by content, never by filename** (`diffusion_models/`
103
+ is shared across architectures, and Qwen3-4B and Qwen3-VL-4B have byte-identical embedding shapes),
104
+ and **derive geometry from the checkpoint** rather than bundling a config per variant, so a future
105
+ build loads with no code change. A **diffusers folder** is a valid checkpoint too, which is the only
106
+ way a 32B model reaches a 24 GB card: its NF4 shards stream through `from_pretrained`, where
107
+ `from_single_file` cannot quantize at all. **`alibaba/z-image-turbo`
99
108
  (`models/zimage/`) is the one runnable generation path today** (prompt + optional image → one take,
100
109
  backed by diffusers' `ZImagePipeline`/`ZImageImg2ImgPipeline`). This is the "Z-Image pipeline that
101
110
  already works" - the primitives will reach parity in C2. It loads from a **single diffusion
@@ -143,6 +152,18 @@ between nodes and are never takes.
143
152
  (Turing/Volta - compute capability < 8.0, e.g. a T4): same footprint, but it uses the fp16 tensor
144
153
  cores instead of bf16's slow path. The **VAE stays upcast** (bf16, or fp32 when the denoiser is
145
154
  fp16) because fp16 VAE decode can overflow to black/NaN images (`_compute_dtype` in `device/`).
155
+ - **Quantization rungs** - the fit ladder is full-precision resident → int8 resident → **NF4 resident**
156
+ → sequential offload → wont-fit. NF4 is what makes a 32B model viable on a 24 GB card. Note int8
157
+ forces bf16 (torchao's weight-only int8 silently no-ops under fp16) while NF4 does not, so a Turing
158
+ card keeps its fp16 tensor cores under NF4.
159
+ - **Prequantized checkpoints must not be re-quantized.** A checkpoint that ships already quantized
160
+ (`flux2/variants.is_prequantized`) has an on-disk size that already _is_ its resident size, so the
161
+ ladder's assumption that quantization halves it does not hold, and handing diffusers a second,
162
+ different quantization config is a hard error. Pass `Quantization.NONE` for those.
163
+ - **Staged loading** - when the text encoder and the transformer cannot be co-resident (dev is a
164
+ 15 GB encoder beside an 18 GB transformer), the prompt is encoded first and the encoder freed before
165
+ the transformer loads. The decision has to be made _before_ the load: by the time an OOM fires there
166
+ is nothing left to free.
146
167
  - **Multi-GPU** - `INLINE_PARALLEL` (e.g. `pipefusion=2`, `pipefusion=2,ulysses=2`); degrees multiply
147
168
  to the world size, which must equal the GPU count.
148
169
 
@@ -202,6 +223,11 @@ real codec that moves tensors lives with the model runner.
202
223
  Don't scatter it.
203
224
  - **Bring-your-own models.** Nothing is downloaded by the engine. The catalog scans; the user places
204
225
  files. A model picker is a `SELECT` param with `options_from="<category>"`.
226
+ - **Verify image models by rendering.** The FLUX.2 work shipped five bugs past a green test suite,
227
+ and every one produced a _wrong image rather than an error_: a mis-keyed checkpoint, a
228
+ vision-language encoder loaded in place of a text one, an unnormalized latent, and a control context
229
+ cast to a quantized weight's `uint8` storage dtype. A unit test cannot see a plausible-but-wrong
230
+ image. Render something and look at it.
205
231
  - **Tests (pytest).** Cover the logic that matters: graph validate/topo/executor/cache, the catalog
206
232
  scan, the run store + server contract, the device/memory policy, the parallel group + xfuser seam,
207
233
  and each model runner (import-guarded, no GPU needed). See `tests/`.
@@ -211,14 +237,17 @@ real codec that moves tensors lives with the model runner.
211
237
 
212
238
  ```
213
239
  uv venv # create ./.venv
214
- uv pip install -e ".[server,dev]" # engine + server + test tooling
215
- uv pip install -e ".[runtime]" # + torch, diffusers, transformers (real generation)
216
- uv pip install -e ".[runtime,parallel]" # + xfuser, for multi-GPU denoise (2+ GPUs)
240
+ # --python pins the target: without it uv installs into an activated venv/conda env, not ./.venv
241
+ uv pip install --python .venv/bin/python -e ".[server,dev]" # engine + server + test tooling
242
+ uv pip install --python .venv/bin/python -e ".[runtime]" # + torch, diffusers, transformers
243
+ uv pip install --python .venv/bin/python -e ".[runtime,parallel]" # + xfuser, multi-GPU denoise
217
244
 
218
245
  ./webui.sh # run (loopback:8848); friendly flags → INLINE_* env
219
246
  ./webui.sh --listen --port 9000 # bind all interfaces
220
247
  ./webui.sh --lowvram # tight-VRAM profile
221
248
  ./webui.sh --install --extra runtime # set up ./.venv with the model runtime, then exit
249
+ # (reuses an existing ./.venv; --recreate rebuilds it, and
250
+ # an activated foreign env is reported, never modified)
222
251
  python -m inline_core.server # run the server directly (INLINE_HOST / INLINE_PORT)
223
252
 
224
253
  ruff check . # lint (zero warnings)
@@ -1,18 +1,28 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inline-core
3
- Version: 1.2.3
3
+ Version: 1.2.6
4
4
  Summary: The generation engine behind Inline Studio.
5
+ License-Expression: GPL-3.0-or-later
5
6
  Requires-Python: >=3.11
6
7
  Requires-Dist: numpy>=1.26
7
8
  Requires-Dist: psutil>=5.9
8
9
  Provides-Extra: all
9
10
  Requires-Dist: accelerate>=0.30; extra == 'all'
10
- Requires-Dist: diffusers>=0.36; extra == 'all'
11
+ Requires-Dist: bitsandbytes>=0.43; (platform_system != 'Darwin') and extra == 'all'
12
+ Requires-Dist: controlnet-aux>=0.0.7; extra == 'all'
13
+ Requires-Dist: diffusers>=0.39; extra == 'all'
14
+ Requires-Dist: einops>=0.7; extra == 'all'
11
15
  Requires-Dist: fastapi>=0.110; extra == 'all'
12
16
  Requires-Dist: huggingface-hub>=0.23; extra == 'all'
13
17
  Requires-Dist: imageio-ffmpeg>=0.4; extra == 'all'
18
+ Requires-Dist: nvidia-ml-py>=12; extra == 'all'
19
+ Requires-Dist: onnxruntime>=1.17; extra == 'all'
20
+ Requires-Dist: peft>=0.11; extra == 'all'
21
+ Requires-Dist: pillow>=10; extra == 'all'
22
+ Requires-Dist: psutil>=5.9; extra == 'all'
14
23
  Requires-Dist: safetensors>=0.4; extra == 'all'
15
24
  Requires-Dist: scipy>=1.11; extra == 'all'
25
+ Requires-Dist: timm>=1.0; extra == 'all'
16
26
  Requires-Dist: torch>=2.2; extra == 'all'
17
27
  Requires-Dist: torchao>=0.14; extra == 'all'
18
28
  Requires-Dist: transformers>=4.44; extra == 'all'
@@ -27,8 +37,10 @@ Requires-Dist: nvidia-ml-py>=12; extra == 'parallel'
27
37
  Requires-Dist: xfuser>=0.4; extra == 'parallel'
28
38
  Provides-Extra: runtime
29
39
  Requires-Dist: accelerate>=0.30; extra == 'runtime'
30
- Requires-Dist: diffusers>=0.36; extra == 'runtime'
40
+ Requires-Dist: controlnet-aux>=0.0.7; extra == 'runtime'
41
+ Requires-Dist: diffusers>=0.39; extra == 'runtime'
31
42
  Requires-Dist: huggingface-hub>=0.23; extra == 'runtime'
43
+ Requires-Dist: onnxruntime>=1.17; extra == 'runtime'
32
44
  Requires-Dist: safetensors>=0.4; extra == 'runtime'
33
45
  Requires-Dist: scipy>=1.11; extra == 'runtime'
34
46
  Requires-Dist: torch>=2.2; extra == 'runtime'
@@ -38,6 +50,14 @@ Provides-Extra: server
38
50
  Requires-Dist: fastapi>=0.110; extra == 'server'
39
51
  Requires-Dist: imageio-ffmpeg>=0.4; extra == 'server'
40
52
  Requires-Dist: uvicorn[standard]>=0.29; extra == 'server'
53
+ Provides-Extra: training
54
+ Requires-Dist: bitsandbytes>=0.43; (platform_system != 'Darwin') and extra == 'training'
55
+ Requires-Dist: einops>=0.7; extra == 'training'
56
+ Requires-Dist: nvidia-ml-py>=12; extra == 'training'
57
+ Requires-Dist: peft>=0.11; extra == 'training'
58
+ Requires-Dist: pillow>=10; extra == 'training'
59
+ Requires-Dist: psutil>=5.9; extra == 'training'
60
+ Requires-Dist: timm>=1.0; extra == 'training'
41
61
  Description-Content-Type: text/markdown
42
62
 
43
63
  # Inline Core
@@ -47,8 +67,9 @@ The generation engine behind Inline. It takes a typed node graph (JSON) and retu
47
67
  low-VRAM laptops up to multi-GPU machines that split a single image's sampling across GPUs (via
48
68
  xDiT). It is Inline Studio's built-in render backend.
49
69
 
50
- First model: Z-Image (Alibaba Tongyi), a 6B rectified-flow diffusion transformer (and a model xDiT
51
- already supports, so the multi-GPU split works on it from the start).
70
+ Models today: **Z-Image** (Alibaba Tongyi), a 6B rectified-flow diffusion transformer and the model
71
+ xDiT already supports, so the multi-GPU split works on it from the start; and **Krea 2** (Krea AI),
72
+ a 12.9B single-stream MMDiT released as an undistilled RAW base plus an 8-step distilled Turbo.
52
73
 
53
74
  > Status: early, and running end to end against a stub engine. In place and tested: the graph engine,
54
75
  > the typed `/v1` HTTP + websocket API (durable runs, streamed progress, coalescing), the model-dir
@@ -80,13 +101,18 @@ a 6 GB laptop, pure CPU, or split across several GPUs, without touching the grap
80
101
 
81
102
  Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/).
82
103
 
104
+ `--python` pins each install to `./.venv`; without it uv targets whatever venv or conda env is
105
+ activated in your shell, and installs land there instead.
106
+
83
107
  ```
84
108
  uv venv
85
- uv pip install -e ".[server]" # engine + HTTP/websocket API
86
- uv pip install -e ".[runtime]" # + torch, diffusers, transformers (for real generation)
87
- uv pip install -e ".[runtime,parallel]" # + xfuser, to split one image across GPUs (2+ GPUs)
109
+ uv pip install --python .venv/bin/python -e ".[server]" # engine + HTTP/websocket API
110
+ uv pip install --python .venv/bin/python -e ".[runtime]" # + torch, diffusers, transformers
111
+ uv pip install --python .venv/bin/python -e ".[runtime,parallel]" # + xfuser, 2+ GPUs
88
112
  ```
89
113
 
114
+ Or let the launcher do it: `./webui.sh --install --extra runtime` (Windows: `.\webui.bat`).
115
+
90
116
  ## Models
91
117
 
92
118
  Drop files into the models dir (default `./models`, override with `INLINE_MODELS_DIR`), ComfyUI-style,
@@ -94,9 +120,13 @@ by category:
94
120
 
95
121
  ```
96
122
  models/
97
- diffusion_models/ z_image_turbo_bf16.safetensors <- the one file you need for Z-Image
98
- vae/ ae.safetensors <- optional (see below)
99
- text_encoders/ qwen/ (an HF-format folder: config + tokenizer + weights) <- optional
123
+ diffusion_models/ z_image_turbo_bf16.safetensors <- Z-Image
124
+ krea2_turbo_bf16.safetensors <- Krea 2 Turbo (generate)
125
+ krea2_raw_bf16.safetensors <- Krea 2 RAW (fine-tune)
126
+ vae/ ae.safetensors <- Z-Image
127
+ qwen_image_vae_diffusers.safetensors <- Krea 2
128
+ text_encoders/ qwen_3_4b.safetensors <- Z-Image
129
+ qwen3vl_4b_bf16.safetensors <- Krea 2
100
130
  loras/ controlnet/ checkpoints/ ...
101
131
  ```
102
132
 
@@ -115,8 +145,17 @@ paths:
115
145
  `models/`** (never the hidden HF cache) with visible progress.
116
146
 
117
147
  Override the diffusion source with `INLINE_ZIMAGE_MODEL` (a file or a diffusers dir), and the supporting
118
- components with `INLINE_ZIMAGE_VAE` / `INLINE_ZIMAGE_TEXT_ENCODER`. The engine scans the models dir on
119
- start; a node's model pickers list what is present.
148
+ components with `INLINE_ZIMAGE_VAE` / `INLINE_ZIMAGE_TEXT_ENCODER`; Krea 2 has the same three under
149
+ `INLINE_KREA2_*`. The engine scans the models dir on start; a node's model pickers list what is present.
150
+
151
+ **Krea 2 loads from the same ComfyUI single files**, with two constraints worth knowing. Only the
152
+ **bf16** builds in [`Comfy-Org/Krea-2`](https://huggingface.co/Comfy-Org/Krea-2) are loadable: the
153
+ fp8/int8/nvfp4 variants carry ComfyUI-specific scale tensors, and the loader refuses them by name
154
+ rather than failing mid-load. And the **VAE must be the diffusers-format** Qwen-Image file (the
155
+ node's popup fetches it from `Qwen/Qwen-Image`), because ComfyUI's copy of the same weights uses a
156
+ module layout diffusers has no converter for. `Krea2Transformer2DModel` has no `from_single_file`, so
157
+ the transformer checkpoint is renamed into diffusers naming on load (`models/krea2/convert.py`) and
158
+ streamed tensor by tensor onto the GPU.
120
159
 
121
160
  ## Nodes
122
161
 
@@ -124,8 +163,9 @@ start; a node's model pickers list what is present.
124
163
  renders any node generically - adding a node type needs no UI release.
125
164
 
126
165
  **High-level model nodes are what the user sees.** Generation is one-click: you drop a single
127
- **Z-Image Turbo** node, wire a Prompt into it, and hit Run. The node hooks up the diffusion model, VAE,
128
- and text-encoder behind the scenes - no loader/sampler wiring.
166
+ **Z-Image Turbo**, **Krea 2 Turbo** or **Krea 2 RAW** node, wire a Prompt into it, and hit Run. The
167
+ node hooks up the diffusion model, VAE, and text-encoder behind the scenes - no loader/sampler
168
+ wiring.
129
169
 
130
170
  Underneath, a **low-level primitive vocabulary** exists (`load/diffusion-model`, `load/vae`,
131
171
  `load/text-encoder`, `encode/text`, `latent/empty`, `sample`, `vae/decode`, `vae/encode`) - the
@@ -207,17 +247,18 @@ offload machinery, `--vram-budget GB` / `INLINE_VRAM_BUDGET_GB` to cap the assum
207
247
 
208
248
  ## Run
209
249
 
210
- The easy path is `webui.sh`, which maps friendly flags onto the engine's `INLINE_*` env knobs:
250
+ The easy path is `webui.sh` (macOS/Linux) or `webui.bat` (Windows), which maps friendly flags onto the
251
+ engine's `INLINE_*` env knobs. Windows must use `webui.bat` - `webui.sh` is a bash script:
211
252
 
212
253
  ```
213
- ./webui.sh # loopback, port 8848
254
+ ./webui.sh # loopback, port 8848 (Windows: .\webui.bat)
214
255
  ./webui.sh --listen --port 9000 # bind all interfaces on 9000
215
256
  ./webui.sh --multi-gpu # split one image across GPUs (auto with 2+ GPUs)
216
257
  ./webui.sh --lowvram # tight-VRAM profile
217
- ./webui.sh --install --extra zimage # set up ./.venv with the Z-Image runtime, then exit
258
+ ./webui.sh --install --extra runtime # set up ./.venv with the model runtime, then exit
218
259
  ```
219
260
 
220
- `./webui.sh --help` lists every flag (networking, multi-GPU, device/memory profile, paths). The same
261
+ `./webui.sh --help` (or `.\webui.bat --help`) lists every flag (networking, multi-GPU, device/memory profile, paths). The same
221
262
  flags are available on the Python entrypoint - `python main.py --help` - which is the dev path (it also
222
263
  takes `--front-end-root DIR` to serve a local SPA build). Or run the server module directly:
223
264
 
@@ -5,8 +5,9 @@ The generation engine behind Inline. It takes a typed node graph (JSON) and retu
5
5
  low-VRAM laptops up to multi-GPU machines that split a single image's sampling across GPUs (via
6
6
  xDiT). It is Inline Studio's built-in render backend.
7
7
 
8
- First model: Z-Image (Alibaba Tongyi), a 6B rectified-flow diffusion transformer (and a model xDiT
9
- already supports, so the multi-GPU split works on it from the start).
8
+ Models today: **Z-Image** (Alibaba Tongyi), a 6B rectified-flow diffusion transformer and the model
9
+ xDiT already supports, so the multi-GPU split works on it from the start; and **Krea 2** (Krea AI),
10
+ a 12.9B single-stream MMDiT released as an undistilled RAW base plus an 8-step distilled Turbo.
10
11
 
11
12
  > Status: early, and running end to end against a stub engine. In place and tested: the graph engine,
12
13
  > the typed `/v1` HTTP + websocket API (durable runs, streamed progress, coalescing), the model-dir
@@ -38,13 +39,18 @@ a 6 GB laptop, pure CPU, or split across several GPUs, without touching the grap
38
39
 
39
40
  Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/).
40
41
 
42
+ `--python` pins each install to `./.venv`; without it uv targets whatever venv or conda env is
43
+ activated in your shell, and installs land there instead.
44
+
41
45
  ```
42
46
  uv venv
43
- uv pip install -e ".[server]" # engine + HTTP/websocket API
44
- uv pip install -e ".[runtime]" # + torch, diffusers, transformers (for real generation)
45
- uv pip install -e ".[runtime,parallel]" # + xfuser, to split one image across GPUs (2+ GPUs)
47
+ uv pip install --python .venv/bin/python -e ".[server]" # engine + HTTP/websocket API
48
+ uv pip install --python .venv/bin/python -e ".[runtime]" # + torch, diffusers, transformers
49
+ uv pip install --python .venv/bin/python -e ".[runtime,parallel]" # + xfuser, 2+ GPUs
46
50
  ```
47
51
 
52
+ Or let the launcher do it: `./webui.sh --install --extra runtime` (Windows: `.\webui.bat`).
53
+
48
54
  ## Models
49
55
 
50
56
  Drop files into the models dir (default `./models`, override with `INLINE_MODELS_DIR`), ComfyUI-style,
@@ -52,9 +58,13 @@ by category:
52
58
 
53
59
  ```
54
60
  models/
55
- diffusion_models/ z_image_turbo_bf16.safetensors <- the one file you need for Z-Image
56
- vae/ ae.safetensors <- optional (see below)
57
- text_encoders/ qwen/ (an HF-format folder: config + tokenizer + weights) <- optional
61
+ diffusion_models/ z_image_turbo_bf16.safetensors <- Z-Image
62
+ krea2_turbo_bf16.safetensors <- Krea 2 Turbo (generate)
63
+ krea2_raw_bf16.safetensors <- Krea 2 RAW (fine-tune)
64
+ vae/ ae.safetensors <- Z-Image
65
+ qwen_image_vae_diffusers.safetensors <- Krea 2
66
+ text_encoders/ qwen_3_4b.safetensors <- Z-Image
67
+ qwen3vl_4b_bf16.safetensors <- Krea 2
58
68
  loras/ controlnet/ checkpoints/ ...
59
69
  ```
60
70
 
@@ -73,8 +83,17 @@ paths:
73
83
  `models/`** (never the hidden HF cache) with visible progress.
74
84
 
75
85
  Override the diffusion source with `INLINE_ZIMAGE_MODEL` (a file or a diffusers dir), and the supporting
76
- components with `INLINE_ZIMAGE_VAE` / `INLINE_ZIMAGE_TEXT_ENCODER`. The engine scans the models dir on
77
- start; a node's model pickers list what is present.
86
+ components with `INLINE_ZIMAGE_VAE` / `INLINE_ZIMAGE_TEXT_ENCODER`; Krea 2 has the same three under
87
+ `INLINE_KREA2_*`. The engine scans the models dir on start; a node's model pickers list what is present.
88
+
89
+ **Krea 2 loads from the same ComfyUI single files**, with two constraints worth knowing. Only the
90
+ **bf16** builds in [`Comfy-Org/Krea-2`](https://huggingface.co/Comfy-Org/Krea-2) are loadable: the
91
+ fp8/int8/nvfp4 variants carry ComfyUI-specific scale tensors, and the loader refuses them by name
92
+ rather than failing mid-load. And the **VAE must be the diffusers-format** Qwen-Image file (the
93
+ node's popup fetches it from `Qwen/Qwen-Image`), because ComfyUI's copy of the same weights uses a
94
+ module layout diffusers has no converter for. `Krea2Transformer2DModel` has no `from_single_file`, so
95
+ the transformer checkpoint is renamed into diffusers naming on load (`models/krea2/convert.py`) and
96
+ streamed tensor by tensor onto the GPU.
78
97
 
79
98
  ## Nodes
80
99
 
@@ -82,8 +101,9 @@ start; a node's model pickers list what is present.
82
101
  renders any node generically - adding a node type needs no UI release.
83
102
 
84
103
  **High-level model nodes are what the user sees.** Generation is one-click: you drop a single
85
- **Z-Image Turbo** node, wire a Prompt into it, and hit Run. The node hooks up the diffusion model, VAE,
86
- and text-encoder behind the scenes - no loader/sampler wiring.
104
+ **Z-Image Turbo**, **Krea 2 Turbo** or **Krea 2 RAW** node, wire a Prompt into it, and hit Run. The
105
+ node hooks up the diffusion model, VAE, and text-encoder behind the scenes - no loader/sampler
106
+ wiring.
87
107
 
88
108
  Underneath, a **low-level primitive vocabulary** exists (`load/diffusion-model`, `load/vae`,
89
109
  `load/text-encoder`, `encode/text`, `latent/empty`, `sample`, `vae/decode`, `vae/encode`) - the
@@ -165,17 +185,18 @@ offload machinery, `--vram-budget GB` / `INLINE_VRAM_BUDGET_GB` to cap the assum
165
185
 
166
186
  ## Run
167
187
 
168
- The easy path is `webui.sh`, which maps friendly flags onto the engine's `INLINE_*` env knobs:
188
+ The easy path is `webui.sh` (macOS/Linux) or `webui.bat` (Windows), which maps friendly flags onto the
189
+ engine's `INLINE_*` env knobs. Windows must use `webui.bat` - `webui.sh` is a bash script:
169
190
 
170
191
  ```
171
- ./webui.sh # loopback, port 8848
192
+ ./webui.sh # loopback, port 8848 (Windows: .\webui.bat)
172
193
  ./webui.sh --listen --port 9000 # bind all interfaces on 9000
173
194
  ./webui.sh --multi-gpu # split one image across GPUs (auto with 2+ GPUs)
174
195
  ./webui.sh --lowvram # tight-VRAM profile
175
- ./webui.sh --install --extra zimage # set up ./.venv with the Z-Image runtime, then exit
196
+ ./webui.sh --install --extra runtime # set up ./.venv with the model runtime, then exit
176
197
  ```
177
198
 
178
- `./webui.sh --help` lists every flag (networking, multi-GPU, device/memory profile, paths). The same
199
+ `./webui.sh --help` (or `.\webui.bat --help`) lists every flag (networking, multi-GPU, device/memory profile, paths). The same
179
200
  flags are available on the Python entrypoint - `python main.py --help` - which is the dev path (it also
180
201
  takes `--front-end-root DIR` to serve a local SPA build). Or run the server module directly:
181
202
 
@@ -1,9 +1,10 @@
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.3"
4
+ version = "1.2.6"
5
5
  description = "The generation engine behind Inline Studio."
6
6
  readme = "README.md"
7
+ license = "GPL-3.0-or-later"
7
8
  requires-python = ">=3.11"
8
9
  # Deliberately torch-free so the engine boots and serves source nodes with no ML stack installed.
9
10
  dependencies = [
@@ -16,8 +17,8 @@ dependencies = [
16
17
  runtime = [
17
18
  # A bare `torch` is CPU-only on Windows; see [tool.uv] below and requirements.txt.
18
19
  "torch>=2.2",
19
- # Z-Image needs diffusers >= 0.36 (ZImagePipeline).
20
- "diffusers>=0.36",
20
+ # Krea 2 needs diffusers >= 0.39 (Krea2Pipeline); Z-Image needs >= 0.36 (ZImagePipeline).
21
+ "diffusers>=0.39",
21
22
  "transformers>=4.44",
22
23
  "accelerate>=0.30",
23
24
  "safetensors>=0.4",
@@ -27,6 +28,10 @@ runtime = [
27
28
  "scipy>=1.11",
28
29
  # We call snapshot_download directly for the model popup, so pin it rather than rely on transit.
29
30
  "huggingface_hub>=0.23",
31
+ # ControlNet preprocessors (the Apply ControlNet node): OpenPose/DWPose, MiDaS/Zoe depth, canny,
32
+ # HED, lineart, MLSD, scribble, normal. DWPose runs its detector on ONNX Runtime.
33
+ "controlnet-aux>=0.0.7",
34
+ "onnxruntime>=1.17",
30
35
  ]
31
36
  server = [
32
37
  "fastapi>=0.110",
@@ -39,25 +44,54 @@ parallel = [
39
44
  "xfuser>=0.4",
40
45
  "nvidia-ml-py>=12",
41
46
  ]
47
+ # LoRA training (the Trainer tab): PEFT adapter training + local auto-caption + host/GPU telemetry.
48
+ # Install alongside `runtime`: `.[runtime,training]`. Reuses runtime's torch/diffusers/accelerate.
49
+ training = [
50
+ "peft>=0.11",
51
+ # 8-bit Adam keeps optimizer state small; no macOS wheels, so skip it there (AdamW fallback).
52
+ "bitsandbytes>=0.43; platform_system != 'Darwin'",
53
+ "Pillow>=10",
54
+ # Telemetry (CPU/RAM + per-GPU NVML) for the Trainer tab.
55
+ "psutil>=5.9",
56
+ "nvidia-ml-py>=12",
57
+ # Florence-2 auto-captioner is loaded via trust_remote_code and needs these.
58
+ "timm>=1.0",
59
+ "einops>=0.7",
60
+ ]
42
61
  dev = [
43
62
  "pytest>=8",
44
63
  "ruff>=0.6",
45
64
  "pyright>=1.1",
46
65
  "httpx>=0.27",
47
66
  ]
48
- # Everything needed to run locally. Lists concrete packages, never `inline-core[...]` self-refs.
67
+ # Everything a local install needs in one extra: runtime + server + training (the LoRA trainer),
68
+ # so `--install --extra all` is a single "install everything" command. Multi-GPU (xfuser, the
69
+ # `parallel` extra) is left out on purpose - it is niche and can fail to build on many systems.
70
+ # Lists concrete packages, never `inline-core[...]` self-refs.
49
71
  all = [
72
+ # runtime
50
73
  "torch>=2.2",
51
- "diffusers>=0.36",
74
+ "diffusers>=0.39",
52
75
  "transformers>=4.44",
53
76
  "accelerate>=0.30",
54
77
  "safetensors>=0.4",
55
78
  "torchao>=0.14",
56
79
  "scipy>=1.11",
57
80
  "huggingface_hub>=0.23",
81
+ "controlnet-aux>=0.0.7",
82
+ "onnxruntime>=1.17",
83
+ # server
58
84
  "fastapi>=0.110",
59
85
  "uvicorn[standard]>=0.29",
60
86
  "imageio-ffmpeg>=0.4",
87
+ # training (the Trainer tab)
88
+ "peft>=0.11",
89
+ "bitsandbytes>=0.43; platform_system != 'Darwin'",
90
+ "Pillow>=10",
91
+ "psutil>=5.9",
92
+ "nvidia-ml-py>=12",
93
+ "timm>=1.0",
94
+ "einops>=0.7",
61
95
  ]
62
96
 
63
97
  # PyPI ships CPU-only torch on Windows; pin the CUDA index there (pip: see requirements.txt).
@@ -93,4 +127,6 @@ typeCheckingMode = "strict"
93
127
 
94
128
  [tool.pytest.ini_options]
95
129
  testpaths = ["tests"]
96
- pythonpath = ["src"]
130
+ # "." so `tests` imports as a package: several suites share fixtures via `from tests.x import y`,
131
+ # and without it those modules fail to collect and silently stop running.
132
+ pythonpath = ["src", "."]
@@ -0,0 +1,14 @@
1
+ """Inline Core: the generation engine behind Inline.
2
+
3
+ Takes a typed node graph and returns immutable takes. See PLAN.md for the architecture and
4
+ docs/contract.md for the Storyline API.
5
+ """
6
+
7
+ from importlib.metadata import PackageNotFoundError, version
8
+
9
+ try:
10
+ #: Resolved from the installed package, so pyproject.toml stays the only place a release is
11
+ #: bumped. An editable install records this at install time; reinstall after bumping.
12
+ __version__ = version("inline-core")
13
+ except PackageNotFoundError: # a source tree that was never installed
14
+ __version__ = "0.0.0"
@@ -47,6 +47,10 @@ _SMART_RESIDENT_MIN_VRAM_GB = 6.0
47
47
  # ~half the fp16 weight bytes. Deliberately generous so the estimate errs toward a lighter plan.
48
48
  _ACTIVATION_HEADROOM_GB = 2.5
49
49
  _INT8_FACTOR = 0.5
50
+ # NF4 (bitsandbytes) stores 4-bit weights plus per-block scales, so ~0.55 bytes per parameter
51
+ # against fp16's 2. The rung exists for the very large checkpoints (FLUX.2 dev and friends) that
52
+ # int8 still cannot fit; it is CUDA-only and, like int8, never combined with CPU offload.
53
+ _NF4_FACTOR = 0.28
50
54
 
51
55
 
52
56
  def _system_ram_gb() -> float | None:
@@ -202,9 +206,10 @@ class MemoryPolicy(DevicePolicy):
202
206
  return None
203
207
  cap = max(0.0, budget - _ACTIVATION_HEADROOM_GB)
204
208
  big = (fp.diffusion_bytes + fp.text_encoder_bytes) / 1e9
205
- vae = fp.vae_bytes / 1e9
206
- full = big + vae
207
- int8 = big * _INT8_FACTOR + vae
209
+ # The VAE and a ControlNet are never quantized, so they cost the same under every plan.
210
+ fixed = (fp.vae_bytes + fp.controlnet_bytes) / 1e9
211
+ full = big + fixed
212
+ int8 = big * _INT8_FACTOR + fixed
208
213
  forced = _env_profile() is not None # explicit --profile pins the profile; fit picks quant
209
214
 
210
215
  def prof(auto: Profile) -> Profile:
@@ -221,7 +226,14 @@ class MemoryPolicy(DevicePolicy):
221
226
  int8, budget, True,
222
227
  "Weights are int8-quantized to fit this GPU's VRAM.",
223
228
  )
224
- # int8 still won't fit resident -> CPU-offload streaming. Only viable if the (unquantized)
229
+ nf4 = big * _NF4_FACTOR + fixed
230
+ if nf4 <= cap:
231
+ return FitEstimate(
232
+ "nf4", Quantization.NF4, OffloadMode.NONE, prof(Profile.LOWVRAM),
233
+ nf4, budget, True,
234
+ "Weights are 4-bit (NF4) quantized to fit this GPU's VRAM.",
235
+ )
236
+ # Even 4-bit won't fit resident -> CPU-offload streaming. Only viable if the (unquantized)
225
237
  # model fits in system RAM, since sequential offload holds the off-GPU weights there.
226
238
  ram = self._ram_gb
227
239
  if ram is not None and full > ram:
@@ -87,10 +87,14 @@ class ModelFootprint:
87
87
  diffusion_bytes: int = 0
88
88
  text_encoder_bytes: int = 0
89
89
  vae_bytes: int = 0
90
+ #: A ControlNet loaded alongside the denoiser. Never quantized, so it counts full in every plan.
91
+ controlnet_bytes: int = 0
90
92
 
91
93
  @property
92
94
  def total_bytes(self) -> int:
93
- return self.diffusion_bytes + self.text_encoder_bytes + self.vae_bytes
95
+ return (
96
+ self.diffusion_bytes + self.text_encoder_bytes + self.vae_bytes + self.controlnet_bytes
97
+ )
94
98
 
95
99
 
96
100
  @dataclass(frozen=True)
@@ -0,0 +1,30 @@
1
+ """Locate the ffmpeg/ffprobe binaries. Prefers a bundled ``imageio-ffmpeg``, else PATH.
2
+
3
+ Lives at the top level rather than under ``studio/`` because both the timeline (studio) and the
4
+ take store (runtime) need it, and runtime must not import studio.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import shutil
10
+ from functools import lru_cache
11
+
12
+
13
+ @lru_cache(maxsize=1)
14
+ def ffmpeg_exe() -> str | None:
15
+ try:
16
+ import imageio_ffmpeg
17
+
18
+ return imageio_ffmpeg.get_ffmpeg_exe()
19
+ except Exception: # noqa: BLE001
20
+ return shutil.which("ffmpeg")
21
+
22
+
23
+ @lru_cache(maxsize=1)
24
+ def ffprobe_exe() -> str | None:
25
+ """PATH only - imageio bundles ffmpeg alone, so probing degrades gracefully when absent."""
26
+ return shutil.which("ffprobe")
27
+
28
+
29
+ def ffmpeg_available() -> bool:
30
+ return ffmpeg_exe() is not None
@@ -12,7 +12,7 @@ from typing import Any
12
12
 
13
13
  from ..takes import Take
14
14
  from .registry import Registry
15
- from .schema import Graph, Node
15
+ from .schema import Graph, Node, PortKind
16
16
 
17
17
 
18
18
  class NodeCache(ABC):
@@ -40,8 +40,15 @@ def _canonical_params(node: Node, registry: Registry) -> dict[str, Any]:
40
40
 
41
41
 
42
42
  def is_cache_eligible(node: Node, registry: Registry) -> bool:
43
- """False when any seed param resolves to a negative (random) value."""
43
+ """False when a control map is wired, or any seed param resolves to a negative (random) value.
44
+
45
+ A node driven by a control map re-runs every time: the user iterates on the pose/depth and
46
+ expects each run to apply the current control, so a cached take would read as "control not
47
+ taking effect" (even a re-render at the same seed must re-apply it)."""
44
48
  descriptor = registry.get(node.type)
49
+ for port in descriptor.inputs:
50
+ if port.kind is PortKind.CONTROL and node.inputs.get(port.id):
51
+ return False
45
52
  defaults = descriptor.defaults()
46
53
  for key in descriptor.seed_keys():
47
54
  value = node.params.get(key, defaults.get(key))
@@ -81,3 +88,29 @@ def node_cache_key(
81
88
  digest = hashlib.sha256(json.dumps(payload, sort_keys=True, default=str).encode()).hexdigest()
82
89
  memo[node_id] = digest
83
90
  return digest
91
+
92
+
93
+ def asset_content_hashes(graph: Graph) -> dict[str, str]:
94
+ """The byte hash of each file-backed source node's asset, keyed by node id. Feeds
95
+ ``node_cache_key`` so the cache invalidates when a file's *content* changes even though its path
96
+ did not (a re-rendered control map, an in-place-replaced input image). Only ``ref="path"`` refs
97
+ are hashable; a missing file is skipped - its path still keys the node through its params."""
98
+ import os
99
+
100
+ hashes: dict[str, str] = {}
101
+ for node in graph.nodes:
102
+ asset = node.params.get("asset")
103
+ if not isinstance(asset, dict) or asset.get("ref") != "path":
104
+ continue
105
+ path = asset.get("path")
106
+ if isinstance(path, str) and os.path.isfile(path):
107
+ hashes[node.id] = _file_hash(path)
108
+ return hashes
109
+
110
+
111
+ def _file_hash(path: str) -> str:
112
+ digest = hashlib.sha256()
113
+ with open(path, "rb") as handle:
114
+ for chunk in iter(lambda: handle.read(1 << 20), b""):
115
+ digest.update(chunk)
116
+ return digest.hexdigest()