freesolo-flash-dev 0.2.30__tar.gz → 0.2.32__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 (277) hide show
  1. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.github/workflows/bake-kernel-cache.yml +9 -2
  2. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/Dockerfile.worker +1 -1
  3. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/PKG-INFO +25 -21
  4. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/README.md +19 -17
  5. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docker/bake_kernel_cache.py +60 -3
  6. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docker/bake_pod_entry.py +27 -0
  7. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/catalog.py +79 -1
  8. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/cli/__init__.py +5 -3
  9. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/cli/commands.py +13 -10
  10. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/cli/render.py +26 -15
  11. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/cli/training_doc.py +63 -15
  12. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/cost/__init__.py +2 -1
  13. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/cost/analytical.py +30 -12
  14. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/cost/facts.py +6 -2
  15. freesolo_flash_dev-0.2.32/flash/cost/spec.py +248 -0
  16. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/cost/types.py +28 -5
  17. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/accounting.py +6 -2
  18. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/__init__.py +9 -2
  19. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/adapter.py +22 -1
  20. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/decoding.py +35 -1
  21. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/lora.py +115 -9
  22. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/rl.py +13 -2
  23. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/envs/adapter.py +255 -7
  24. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/envs/registry.py +3 -1
  25. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/_instance.py +3 -0
  26. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/_instance_bootstrap.py +119 -13
  27. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/base.py +31 -19
  28. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/lambdalabs/__init__.py +2 -0
  29. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/lambdalabs/jobs/__init__.py +12 -3
  30. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/lambdalabs/jobs/builders.py +2 -2
  31. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/lambdalabs/pricing.py +4 -5
  32. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/__init__.py +2 -0
  33. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/jobs.py +3 -0
  34. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/pricing.py +3 -2
  35. freesolo_flash_dev-0.2.32/flash/providers/runpod/train/__init__.py +165 -0
  36. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/train/deps.py +3 -2
  37. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/train/endpoints.py +122 -13
  38. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/runner/__init__.py +53 -6
  39. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/runner/deploy.py +37 -14
  40. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/runner/lifecycle.py +26 -40
  41. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/schema/__init__.py +10 -2
  42. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/serve/deploy.py +86 -72
  43. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/serve/export.py +56 -6
  44. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/_locks.py +1 -13
  45. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/_runtime.py +26 -54
  46. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/app.py +1 -15
  47. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/billing.py +13 -0
  48. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/environment_registry.py +9 -4
  49. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/routes/envs.py +11 -3
  50. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/routes/runs.py +37 -0
  51. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/routes/serving.py +33 -32
  52. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/pyproject.toml +8 -6
  53. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/conftest.py +0 -11
  54. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_allocator.py +5 -5
  55. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cancel_remote.py +2 -32
  56. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_catalog_consistency.py +73 -0
  57. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cli_commands.py +26 -8
  58. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cli_estimate.py +130 -13
  59. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cli_render_theme.py +13 -15
  60. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_client.py +11 -0
  61. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_config_overrides.py +5 -6
  62. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cost_analytical.py +27 -11
  63. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cost_equation.py +6 -4
  64. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cost_estimate.py +6 -1
  65. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cost_hardware.py +16 -6
  66. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_disk_gb.py +1 -2
  67. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_env_publish.py +91 -5
  68. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_env_pull.py +253 -0
  69. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_export.py +102 -1
  70. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_flash_mvp.py +1 -1
  71. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_flash_worker.py +88 -9
  72. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_gpus.py +22 -18
  73. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_grpo_params.py +9 -7
  74. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_grpo_sleep_gate.py +3 -3
  75. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_jobs.py +108 -54
  76. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_lambda_runner.py +89 -4
  77. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_managed_hf_repo.py +31 -10
  78. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_mig_guard.py +3 -3
  79. freesolo_flash_dev-0.2.32/tests/test_orchestrator_flash.py +550 -0
  80. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_packing.py +1 -0
  81. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_provider_routing.py +34 -34
  82. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_provider_teardown_robustness.py +4 -4
  83. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_providers_symmetry.py +6 -6
  84. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_resume_on_retry.py +4 -4
  85. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_runmgmt.py +29 -0
  86. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_serve.py +127 -77
  87. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_server_api.py +265 -21
  88. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_server_billing.py +49 -0
  89. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_serving_contract.py +5 -0
  90. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_spec_and_validation.py +14 -0
  91. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_verifiers.py +47 -1
  92. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_vl_warmstart_recombine.py +196 -16
  93. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_weight_cache.py +1 -1
  94. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_worker_init_heartbeat.py +7 -0
  95. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_worker_stack.py +1 -1
  96. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_worker_thinking.py +20 -5
  97. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/uv.lock +12 -8
  98. freesolo_flash_dev-0.2.30/flash/cost/spec.py +0 -110
  99. freesolo_flash_dev-0.2.30/flash/providers/runpod/train/__init__.py +0 -62
  100. freesolo_flash_dev-0.2.30/flash/server/repo_cleanup.py +0 -296
  101. freesolo_flash_dev-0.2.30/tests/test_orchestrator_flash.py +0 -232
  102. freesolo_flash_dev-0.2.30/tests/test_repo_cleanup.py +0 -513
  103. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.dockerignore +0 -0
  104. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.env.example +0 -0
  105. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.github/workflows/auto-rebake.yml +0 -0
  106. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.github/workflows/ci.yml +0 -0
  107. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.github/workflows/main-source-guard.yml +0 -0
  108. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.github/workflows/publish-dev.yml +0 -0
  109. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.github/workflows/publish-image.yml +0 -0
  110. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.github/workflows/publish.yml +0 -0
  111. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.github/workflows/version-parity.yml +0 -0
  112. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.github/workflows/worker-image.yml +0 -0
  113. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/.gitignore +0 -0
  114. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/Dockerfile +0 -0
  115. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/LICENSE +0 -0
  116. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/build/kernel_cache/.gitignore +0 -0
  117. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/build/kernel_cache/.keep +0 -0
  118. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docker/Dockerfile.kernelcache +0 -0
  119. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docker/Dockerfile.kernelcache.relayer +0 -0
  120. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docker/kernel_fingerprint.py +0 -0
  121. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docker/make_rp_handler.py +0 -0
  122. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-style/README.md +0 -0
  123. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-style/generate.py +0 -0
  124. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-style/index.html +0 -0
  125. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-style/preview.png +0 -0
  126. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-style/themed-errors.png +0 -0
  127. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-theme/README.md +0 -0
  128. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-theme/before-after-dark.png +0 -0
  129. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-theme/before-after-light.png +0 -0
  130. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-theme/gallery-dark.png +0 -0
  131. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-theme/gallery-light.png +0 -0
  132. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-theme/help-dark.png +0 -0
  133. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/cli-theme/help-light.png +0 -0
  134. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/docs/kernel-cache.md +0 -0
  135. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/__init__.py +0 -0
  136. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/_channel.py +0 -0
  137. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/_fileio.py +0 -0
  138. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/_logging.py +0 -0
  139. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/_update_check.py +0 -0
  140. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/cli/__main__.py +0 -0
  141. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/cli/envpush.py +0 -0
  142. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/client/__init__.py +0 -0
  143. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/client/config.py +0 -0
  144. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/client/http.py +0 -0
  145. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/client/runtime_secrets.py +0 -0
  146. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/client/specs.py +0 -0
  147. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/__init__.py +0 -0
  148. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/chalk_kernels.py +0 -0
  149. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/multiturn_rollout.py +0 -0
  150. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/recipe.py +0 -0
  151. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/vram.py +0 -0
  152. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/__main__.py +0 -0
  153. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/_pkg.py +0 -0
  154. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/finalize.py +0 -0
  155. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/gpu_setup.py +0 -0
  156. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/grpo.py +0 -0
  157. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/heartbeat.py +0 -0
  158. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/hf.py +0 -0
  159. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/kernel_warmup.py +0 -0
  160. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/packing.py +0 -0
  161. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/perf/__init__.py +0 -0
  162. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/perf/attn.py +0 -0
  163. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/perf/diagnostics.py +0 -0
  164. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/perf/lifecycle.py +0 -0
  165. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/perf/liger.py +0 -0
  166. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/perf/loraplus.py +0 -0
  167. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/perf/memory.py +0 -0
  168. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/sft.py +0 -0
  169. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/engine/worker/wandb_log.py +0 -0
  170. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/envs/__init__.py +0 -0
  171. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/envs/base.py +0 -0
  172. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/envs/pull.py +0 -0
  173. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/__init__.py +0 -0
  174. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/_auth.py +0 -0
  175. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/_http.py +0 -0
  176. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/_poll.py +0 -0
  177. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/allocator.py +0 -0
  178. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/lambdalabs/api.py +0 -0
  179. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/lambdalabs/auth.py +0 -0
  180. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/lambdalabs/gpus.py +0 -0
  181. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/lambdalabs/preflight.py +0 -0
  182. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/lambdalabs/train.py +0 -0
  183. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/preflight.py +0 -0
  184. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/realized.py +0 -0
  185. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/api.py +0 -0
  186. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/auth.py +0 -0
  187. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/cost.py +0 -0
  188. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/gpus.py +0 -0
  189. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/keys.py +0 -0
  190. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/preflight.py +0 -0
  191. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/preload.py +0 -0
  192. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/providers/runpod/slots.py +0 -0
  193. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/py.typed +0 -0
  194. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/runner/checkpoints.py +0 -0
  195. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/schema/fields.py +0 -0
  196. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/serve/__init__.py +0 -0
  197. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/serve/pricing.py +0 -0
  198. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/__init__.py +0 -0
  199. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/__main__.py +0 -0
  200. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/_deps.py +0 -0
  201. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/_internal_client.py +0 -0
  202. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/auth.py +0 -0
  203. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/billing_retry.py +0 -0
  204. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/checkpoints.py +0 -0
  205. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/db.py +0 -0
  206. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/envs.py +0 -0
  207. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/reconcile.py +0 -0
  208. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/routes/__init__.py +0 -0
  209. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/routes/meta.py +0 -0
  210. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/server/run_registry.py +0 -0
  211. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/flash/spec.py +0 -0
  212. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/infisical-entrypoint.sh +0 -0
  213. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/scripts/build_dev_dist.py +0 -0
  214. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/__init__.py +0 -0
  215. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/_helpers/__init__.py +0 -0
  216. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/_helpers/runner.py +0 -0
  217. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/_helpers/specs.py +0 -0
  218. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/fixtures/math_eval.jsonl +0 -0
  219. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/fixtures/math_train.jsonl +0 -0
  220. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/live/__init__.py +0 -0
  221. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/live/conftest.py +0 -0
  222. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/live/test_lambda_live.py +0 -0
  223. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/live/test_runpod_live.py +0 -0
  224. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_agent_flash_cli_contract.py +0 -0
  225. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_algorithms.py +0 -0
  226. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_b200_rollout_opt.py +0 -0
  227. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_backend_jobspec_contract.py +0 -0
  228. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_billing_retry.py +0 -0
  229. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_chalk_kernels.py +0 -0
  230. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_checkpoints.py +0 -0
  231. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cli_errors.py +0 -0
  232. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cli_help.py +0 -0
  233. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cli_managed.py +0 -0
  234. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_client_server_integration.py +0 -0
  235. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cost_models.py +0 -0
  236. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_cost_rewards.py +0 -0
  237. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_dev_channel.py +0 -0
  238. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_endpoint_name.py +0 -0
  239. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_env_delete.py +0 -0
  240. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_env_no_install.py +0 -0
  241. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_env_push.py +0 -0
  242. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_env_rate_limit_resolve.py +0 -0
  243. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_envs_coverage.py +0 -0
  244. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_flashinfer_cache_dirs.py +0 -0
  245. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_github_urlopen_retry.py +0 -0
  246. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_grpo_mask_aware.py +0 -0
  247. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_idle_endpoint_reaper.py +0 -0
  248. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_internal_client.py +0 -0
  249. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_kernel_cache.py +0 -0
  250. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_kernel_fingerprint.py +0 -0
  251. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_kv_util.py +0 -0
  252. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_logging.py +0 -0
  253. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_login_perms.py +0 -0
  254. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_metrics_schema_agent_contract.py +0 -0
  255. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_multiturn_rollout.py +0 -0
  256. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_oom_escalate_gpu.py +0 -0
  257. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_open_model_policy.py +0 -0
  258. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_poll_helpers.py +0 -0
  259. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_preflight.py +0 -0
  260. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_realized_cost.py +0 -0
  261. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_resolve_params_b.py +0 -0
  262. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_runpod_api_delete.py +0 -0
  263. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_runpod_key_fingerprint.py +0 -0
  264. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_runpod_key_waterfall.py +0 -0
  265. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_runpod_slots.py +0 -0
  266. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_server_db.py +0 -0
  267. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_sft_gc_off.py +0 -0
  268. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_thinking_config.py +0 -0
  269. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_update_check.py +0 -0
  270. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_version.py +0 -0
  271. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  272. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_vl_weight_sync.py +0 -0
  273. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_wandb_naming.py +0 -0
  274. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_warmstart_cross_repo.py +0 -0
  275. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_worker_dryrun.py +0 -0
  276. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_worker_hardexit.py +0 -0
  277. {freesolo_flash_dev-0.2.30 → freesolo_flash_dev-0.2.32}/tests/test_worker_image.py +0 -0
@@ -76,16 +76,23 @@ jobs:
76
76
  gpu_type_id: "NVIDIA RTX A6000",
77
77
  allowed_cuda: "",
78
78
  }
79
+ # sm89 (Ada): warm on the L40S -- a DATACENTER Ada card with reliable secure-cloud capacity --
80
+ # instead of the consumer RTX 4090 (scarce/cold on secure cloud, where the bake pod often never
81
+ # finished pulling the ~20GB image before the deadline). The cache is sm-keyed, so an L40S-warmed
82
+ # sm89 cache is valid on every sm89 card (4090/L40/L4). NB: verify the exact RunPod gpuTypeId.
79
83
  - {
80
84
  sm: sm89,
81
85
  arch: "8.9",
82
- gpu_type_id: "NVIDIA GeForce RTX 4090",
86
+ gpu_type_id: "NVIDIA L40S",
83
87
  allowed_cuda: "",
84
88
  }
89
+ # sm90 (Hopper): warm on the H200 (141GB, sm90) -- better secure-cloud capacity than the
90
+ # heavily-contended H100 80GB HBM3 (which cold-pull-stalled the bake). Same sm90, so the cache
91
+ # is valid on every H100/H200. H200 is in flash's validated GPU catalog (providers/base.py).
85
92
  - {
86
93
  sm: sm90,
87
94
  arch: "9.0",
88
- gpu_type_id: "NVIDIA H100 80GB HBM3",
95
+ gpu_type_id: "NVIDIA H200",
89
96
  allowed_cuda: "",
90
97
  }
91
98
  # Blackwell needs CUDA-13 hosts to JIT its PTX (matches min_cuda_for in the provider). Bake
@@ -63,7 +63,7 @@ RUN pip install --no-cache-dir \
63
63
  "tilelang==0.1.11" \
64
64
  "apache-tvm-ffi==0.1.11" \
65
65
  "hf_transfer" \
66
- "freesolo>=0.2.49" \
66
+ "freesolo>=0.2.52" \
67
67
  "runpod"
68
68
  # Gated-DeltaNet (Qwen3.5/3.6) runs on flash-linear-attention's Triton kernels. On consumer cards
69
69
  # (Ampere/Ada/Blackwell) the Triton path is correct. On Hopper (sm90) fla's GDN chunk_bwd is
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 0.2.30
3
+ Version: 0.2.32
4
4
  Summary: Flash — managed LoRA post-training (SFT/GRPO) for Freesolo environments, driven by the `flash` CLI
5
5
  Project-URL: Homepage, https://github.com/freesolo-co/flash
6
6
  Project-URL: Repository, https://github.com/freesolo-co/flash
@@ -18,19 +18,20 @@ Requires-Python: <3.13,>=3.11
18
18
  Provides-Extra: dev
19
19
  Requires-Dist: datasets>=2.19; extra == 'dev'
20
20
  Requires-Dist: fastapi; extra == 'dev'
21
- Requires-Dist: freesolo>=0.2.49; extra == 'dev'
21
+ Requires-Dist: freesolo>=0.2.52; extra == 'dev'
22
22
  Requires-Dist: httpx>=0.27; extra == 'dev'
23
23
  Requires-Dist: huggingface-hub>=0.34; extra == 'dev'
24
24
  Requires-Dist: mypy>=1.13.0; extra == 'dev'
25
25
  Requires-Dist: pytest>=9.0.3; extra == 'dev'
26
26
  Requires-Dist: ruff>=0.6; extra == 'dev'
27
27
  Requires-Dist: runpod-flash; extra == 'dev'
28
+ Requires-Dist: transformers<5.11,>=5.6; extra == 'dev'
28
29
  Requires-Dist: uvicorn; extra == 'dev'
29
30
  Provides-Extra: gpu
30
31
  Requires-Dist: accelerate>=1.4; extra == 'gpu'
31
32
  Requires-Dist: bitsandbytes>=0.49; extra == 'gpu'
32
33
  Requires-Dist: datasets>=2.19; extra == 'gpu'
33
- Requires-Dist: freesolo>=0.2.49; extra == 'gpu'
34
+ Requires-Dist: freesolo>=0.2.52; extra == 'gpu'
34
35
  Requires-Dist: huggingface-hub>=0.34; extra == 'gpu'
35
36
  Requires-Dist: peft>=0.19; extra == 'gpu'
36
37
  Requires-Dist: torch==2.10.0; extra == 'gpu'
@@ -40,24 +41,26 @@ Requires-Dist: vllm==0.19.1; extra == 'gpu'
40
41
  Provides-Extra: server
41
42
  Requires-Dist: datasets>=2.19; extra == 'server'
42
43
  Requires-Dist: fastapi; extra == 'server'
43
- Requires-Dist: freesolo>=0.2.49; extra == 'server'
44
+ Requires-Dist: freesolo>=0.2.52; extra == 'server'
44
45
  Requires-Dist: httpx>=0.27; extra == 'server'
45
46
  Requires-Dist: huggingface-hub>=0.34; extra == 'server'
46
47
  Requires-Dist: runpod-flash; extra == 'server'
48
+ Requires-Dist: transformers<5.11,>=5.6; extra == 'server'
47
49
  Requires-Dist: uvicorn; extra == 'server'
48
50
  Description-Content-Type: text/markdown
49
51
 
50
52
  # Flash
51
53
 
52
- Managed LoRA post-training service: SFT and GRPO on managed RunPod Flash GPUs.
53
- The allocator picks the cheapest validated RunPod GPU class that fits the run.
54
+ Managed LoRA post-training service: SFT and GRPO on managed Flash GPUs.
55
+ The allocator picks the cheapest validated managed GPU class that fits the run.
54
56
 
55
57
  ## Scope
56
58
 
57
59
  - `flash train <cfg.toml>` / control-plane `POST /runs` — submit a training job;
58
60
  one dedicated GPU per run, supervised server-side (stall watchdog, bounded
59
61
  auto-retry resuming from the last streamed checkpoint, endpoint GC).
60
- - `flash deploy`, `flash chat` — serving for trained adapters.
62
+ - `flash checkpoints`, `flash deploy`, `flash chat` — serving for trained adapters,
63
+ including deployable intermediate RL checkpoints.
61
64
  - **Freesolo SDK environments.** Every run names a Freesolo environment id.
62
65
  Scaffold `environment.py` plus `datasets/train.jsonl`, upload `.` or another
63
66
  folder with `flash env push --name <name> <folder>`, then reference the
@@ -68,21 +71,22 @@ The allocator picks the cheapest validated RunPod GPU class that fits the run.
68
71
  ## Layout
69
72
 
70
73
  - `flash/catalog.py` — curated model catalog (Qwen3 dense supported tier;
71
- Qwen3.5/3.6 experimental tier) + `model_policy = "allow"` VRAM-fit check + each
72
- model's `thinking` capability (opt-in reasoning mode `thinking = true`)
74
+ Qwen3.5/3.6 experimental tier), VRAM-fit sizing, and each model's `thinking`
75
+ capability (opt-in reasoning mode `thinking = true`)
73
76
  - `flash/schema.py`, `flash/spec.py` — TOML → `JobSpec`
74
77
  - `flash/runner.py` — server-side run supervisor (durable job handle,
75
78
  retries, cost guard, endpoint GC)
76
- - `flash/providers/` — RunPod Flash provider code (pricing, gpus, durable
79
+ - `flash/providers/` — managed GPU substrate code (pricing, GPU classes, durable
77
80
  submit/poll, preflight) behind the `base.Provider` protocol, with an
78
- `allocator.py` that picks the cheapest fitting class
81
+ `allocator.py` that picks the cheapest fitting managed GPU class
79
82
  - `flash/engine/` — the on-GPU worker (TRL + colocated vLLM rollouts) and the
80
83
  shared recipe; SFT targets and RL rewards route through the active environment
81
84
  (task-specific grading lives with its example, not in the engine)
82
85
  - `flash/envs/` — environment machinery: registry and the adapter that loads
83
86
  Freesolo SDK environments onto the worker's interface
84
87
  - `flash env setup` — scaffold a starter local Freesolo env, `datasets/train.jsonl`,
85
- and ready-to-run configs to start from
88
+ ready-to-run configs, and a `TRAINING.md` playbook with common failure modes and
89
+ mitigations
86
90
  - `flash/serve/`, `flash/server/` — adapter serving and the FastAPI control
87
91
  plane (run operator-side via the separate `flash-server` command)
88
92
  - `Dockerfile` — the control-plane image (used by the repo docker-compose)
@@ -99,11 +103,11 @@ uv run flash --help
99
103
  uv run flash-server # control plane (operator-side, run once)
100
104
  ```
101
105
 
102
- The control plane owns provider credentials: `RUNPOD_API_KEY` is always required,
103
- plus the shared `HF_TOKEN`.
104
- The artifact repo is platform-managed and per-run (each run gets its own
105
- `Freesolo-Co/flashrun-<run_id>`, written by the operator `HF_TOKEN`); it is not a user
106
- knob and not an operator-wide env var. Clients authenticate with their freesolo API key
106
+ The control plane owns infrastructure credentials plus the shared `HF_TOKEN`.
107
+ The artifact repo is platform-managed and environment-scoped (runs for the same environment share
108
+ one private `Freesolo-Co/flashrun-<environment>-<hash>` repo, written by the operator `HF_TOKEN`);
109
+ Flash uploads code under content-addressed prefixes and only reuses completed snapshots. The repo is
110
+ not a user knob and not an operator-wide env var. Clients authenticate with their freesolo API key
107
111
  (`flash login`).
108
112
 
109
113
  ## Release channels
@@ -173,10 +177,10 @@ Use `choices[0].message.content` for the generated text. The run id is the adapt
173
177
  for serving. If the run is not deployed yet, `/v1/runs/<run_id>/chat` returns `409`
174
178
  with a hint to deploy first.
175
179
 
176
- Operators can also call the Modal serving app directly after the adapter is registered.
177
- The default serving app is `https://clado-ai--freesolo-lora-serving.modal.run`, and
178
- operators can point Flash at another serving app by setting `FREESOLO_SERVING_URL`.
179
- Use that same base URL when calling the app directly; pass the run id as `model`:
180
+ Operators can also call the serving backend directly after the adapter is registered.
181
+ The default serving backend is `https://clado-ai--freesolo-lora-serving.modal.run`, and
182
+ operators can point Flash at another backend by setting `FREESOLO_SERVING_URL`.
183
+ Use that same base URL when calling the backend directly; pass the run id as `model`:
180
184
 
181
185
  ```bash
182
186
  export FREESOLO_SERVING_URL=https://clado-ai--freesolo-lora-serving.modal.run
@@ -1,14 +1,15 @@
1
1
  # Flash
2
2
 
3
- Managed LoRA post-training service: SFT and GRPO on managed RunPod Flash GPUs.
4
- The allocator picks the cheapest validated RunPod GPU class that fits the run.
3
+ Managed LoRA post-training service: SFT and GRPO on managed Flash GPUs.
4
+ The allocator picks the cheapest validated managed GPU class that fits the run.
5
5
 
6
6
  ## Scope
7
7
 
8
8
  - `flash train <cfg.toml>` / control-plane `POST /runs` — submit a training job;
9
9
  one dedicated GPU per run, supervised server-side (stall watchdog, bounded
10
10
  auto-retry resuming from the last streamed checkpoint, endpoint GC).
11
- - `flash deploy`, `flash chat` — serving for trained adapters.
11
+ - `flash checkpoints`, `flash deploy`, `flash chat` — serving for trained adapters,
12
+ including deployable intermediate RL checkpoints.
12
13
  - **Freesolo SDK environments.** Every run names a Freesolo environment id.
13
14
  Scaffold `environment.py` plus `datasets/train.jsonl`, upload `.` or another
14
15
  folder with `flash env push --name <name> <folder>`, then reference the
@@ -19,21 +20,22 @@ The allocator picks the cheapest validated RunPod GPU class that fits the run.
19
20
  ## Layout
20
21
 
21
22
  - `flash/catalog.py` — curated model catalog (Qwen3 dense supported tier;
22
- Qwen3.5/3.6 experimental tier) + `model_policy = "allow"` VRAM-fit check + each
23
- model's `thinking` capability (opt-in reasoning mode `thinking = true`)
23
+ Qwen3.5/3.6 experimental tier), VRAM-fit sizing, and each model's `thinking`
24
+ capability (opt-in reasoning mode `thinking = true`)
24
25
  - `flash/schema.py`, `flash/spec.py` — TOML → `JobSpec`
25
26
  - `flash/runner.py` — server-side run supervisor (durable job handle,
26
27
  retries, cost guard, endpoint GC)
27
- - `flash/providers/` — RunPod Flash provider code (pricing, gpus, durable
28
+ - `flash/providers/` — managed GPU substrate code (pricing, GPU classes, durable
28
29
  submit/poll, preflight) behind the `base.Provider` protocol, with an
29
- `allocator.py` that picks the cheapest fitting class
30
+ `allocator.py` that picks the cheapest fitting managed GPU class
30
31
  - `flash/engine/` — the on-GPU worker (TRL + colocated vLLM rollouts) and the
31
32
  shared recipe; SFT targets and RL rewards route through the active environment
32
33
  (task-specific grading lives with its example, not in the engine)
33
34
  - `flash/envs/` — environment machinery: registry and the adapter that loads
34
35
  Freesolo SDK environments onto the worker's interface
35
36
  - `flash env setup` — scaffold a starter local Freesolo env, `datasets/train.jsonl`,
36
- and ready-to-run configs to start from
37
+ ready-to-run configs, and a `TRAINING.md` playbook with common failure modes and
38
+ mitigations
37
39
  - `flash/serve/`, `flash/server/` — adapter serving and the FastAPI control
38
40
  plane (run operator-side via the separate `flash-server` command)
39
41
  - `Dockerfile` — the control-plane image (used by the repo docker-compose)
@@ -50,11 +52,11 @@ uv run flash --help
50
52
  uv run flash-server # control plane (operator-side, run once)
51
53
  ```
52
54
 
53
- The control plane owns provider credentials: `RUNPOD_API_KEY` is always required,
54
- plus the shared `HF_TOKEN`.
55
- The artifact repo is platform-managed and per-run (each run gets its own
56
- `Freesolo-Co/flashrun-<run_id>`, written by the operator `HF_TOKEN`); it is not a user
57
- knob and not an operator-wide env var. Clients authenticate with their freesolo API key
55
+ The control plane owns infrastructure credentials plus the shared `HF_TOKEN`.
56
+ The artifact repo is platform-managed and environment-scoped (runs for the same environment share
57
+ one private `Freesolo-Co/flashrun-<environment>-<hash>` repo, written by the operator `HF_TOKEN`);
58
+ Flash uploads code under content-addressed prefixes and only reuses completed snapshots. The repo is
59
+ not a user knob and not an operator-wide env var. Clients authenticate with their freesolo API key
58
60
  (`flash login`).
59
61
 
60
62
  ## Release channels
@@ -124,10 +126,10 @@ Use `choices[0].message.content` for the generated text. The run id is the adapt
124
126
  for serving. If the run is not deployed yet, `/v1/runs/<run_id>/chat` returns `409`
125
127
  with a hint to deploy first.
126
128
 
127
- Operators can also call the Modal serving app directly after the adapter is registered.
128
- The default serving app is `https://clado-ai--freesolo-lora-serving.modal.run`, and
129
- operators can point Flash at another serving app by setting `FREESOLO_SERVING_URL`.
130
- Use that same base URL when calling the app directly; pass the run id as `model`:
129
+ Operators can also call the serving backend directly after the adapter is registered.
130
+ The default serving backend is `https://clado-ai--freesolo-lora-serving.modal.run`, and
131
+ operators can point Flash at another backend by setting `FREESOLO_SERVING_URL`.
132
+ Use that same base URL when calling the backend directly; pass the run id as `model`:
131
133
 
132
134
  ```bash
133
135
  export FREESOLO_SERVING_URL=https://clado-ai--freesolo-lora-serving.modal.run
@@ -66,15 +66,19 @@ def main() -> int:
66
66
  ap = argparse.ArgumentParser(description="offload the kernel-cache warmup to a RunPod GPU")
67
67
  ap.add_argument("--arch", required=True, help="TORCH_CUDA_ARCH_LIST target, e.g. 9.0")
68
68
  ap.add_argument("--sm", required=True, help="sm tag, e.g. sm90 (must match the produced cache)")
69
- ap.add_argument("--gpu-type-id", required=True, help="RunPod gpuTypeId, e.g. 'NVIDIA H100 80GB HBM3'")
69
+ ap.add_argument(
70
+ "--gpu-type-id", required=True, help="RunPod gpuTypeId, e.g. 'NVIDIA H100 80GB HBM3'"
71
+ )
70
72
  ap.add_argument("--image", default="ghcr.io/freesolo-co/flash-worker:cu128")
71
73
  ap.add_argument("--out", default="build/kernel_cache")
72
74
  # the warm pod only pulls the ~20GB image + writes the cache (no model download), so keep this
73
75
  # modest -- an over-large ask shrinks the eligible host pool and trips "machine does not have the
74
76
  # resources" on scarce classes (e.g. Blackwell sm120 on secure cloud).
75
77
  ap.add_argument("--container-disk-gb", type=int, default=60)
76
- ap.add_argument("--deadline-min", type=int, default=45)
77
- ap.add_argument("--run-id", default="", help="unique suffix for the temp repo (default: time+uuid)")
78
+ ap.add_argument("--deadline-min", type=int, default=90)
79
+ ap.add_argument(
80
+ "--run-id", default="", help="unique suffix for the temp repo (default: time+uuid)"
81
+ )
78
82
  ap.add_argument(
79
83
  "--allowed-cuda",
80
84
  default="",
@@ -189,6 +193,59 @@ def main() -> int:
189
193
  shutil.move(s, d)
190
194
  shutil.rmtree(tmp, ignore_errors=True)
191
195
  rc = _verify(args.out, args.sm)
196
+ else:
197
+ # Non-success (timeout / pod_died): the pod-side out/ (STARTED marker, warmup.log) lives in
198
+ # the temp dataset the `finally` below deletes, so pull + print it FIRST. out/STARTED present
199
+ # = the warmup entrypoint ran (a timeout then means it hung / was slow); STARTED absent is
200
+ # AMBIGUOUS -- either the pod ran the wrong entrypoint, or the best-effort STARTED upload
201
+ # failed while the warmup is still running (the pod log disambiguates). warmup.log is only
202
+ # uploaded AFTER the warmup process returns, so it's present only if the warmup
203
+ # finished/was killed, not on a pure mid-run hang.
204
+ log(f"outcome={outcome}: pulling pod-side out/ for diagnostics before cleanup")
205
+ try:
206
+ dbg = os.path.join(args.out, ".dbg")
207
+ snapshot_download(
208
+ repo_id=repo,
209
+ repo_type="dataset",
210
+ allow_patterns=["out/**"],
211
+ local_dir=dbg,
212
+ token=token,
213
+ )
214
+ src = os.path.join(dbg, "out")
215
+ if os.path.isdir(src) and os.listdir(src):
216
+ started = os.path.isfile(os.path.join(src, "STARTED"))
217
+ log(f"warmup entrypoint ran (out/STARTED present): {started}")
218
+ for root, _, files in os.walk(src):
219
+ for f in sorted(files):
220
+ p = os.path.join(root, f)
221
+ log(f" out/{os.path.relpath(p, src)} ({os.path.getsize(p)} b)")
222
+ wl = os.path.join(src, "warmup.log")
223
+ if os.path.isfile(wl):
224
+ log("--- warmup.log tail (last 60 lines) ---")
225
+ with open(wl, errors="replace") as wlf:
226
+ for line in wlf.read().splitlines()[-60:]:
227
+ log(f" | {line}")
228
+ else:
229
+ log(
230
+ "no warmup.log (warmup never returned -> mid-run hang or still running at deadline)"
231
+ )
232
+ else:
233
+ # Ambiguous: out/STARTED is uploaded best-effort BEFORE warmup, and the cache tree
234
+ # only lands AFTER warmup returns. So an empty out/ means EITHER the entrypoint
235
+ # never ran (wrong CMD / docker_args) OR the STARTED upload failed while warmup is
236
+ # still mid-run. The pod retries that upload 3x, so a dropped marker is unlikely and
237
+ # wrong-entrypoint is the probable cause -- but don't assert it. The only proof is
238
+ # the pod's OWN console (its "[bake] uploaded out/STARTED" / WARNING lines), which
239
+ # this helper can't fetch (pod terminated below) -> read it in the RunPod dashboard.
240
+ log(
241
+ "pod produced NO out/ -> warmup entrypoint probably never ran "
242
+ "(wrong CMD / docker_args); the only other cause is a failed out/STARTED "
243
+ "upload mid-run (unlikely, it retries 3x) -- confirm via the pod console "
244
+ "in the RunPod dashboard (this CI log does not capture pod stdout)"
245
+ )
246
+ shutil.rmtree(dbg, ignore_errors=True)
247
+ except Exception as e:
248
+ log(f"diagnostic fetch failed (ignore): {str(e)[:160]}")
192
249
  finally:
193
250
  if pod_id:
194
251
  try:
@@ -28,6 +28,33 @@ def main() -> int:
28
28
  arch = os.environ.get("BAKE_ARCH", "")
29
29
  api = HfApi(token=token)
30
30
 
31
+ # Upload a STARTED marker FIRST -- before the code download / chalk install / warmup -- so a CI-side
32
+ # timeout can tell whether this entrypoint even ran. Present on a timeout = the warmup started (so it
33
+ # hung or was slow); absent = the pod ran the wrong entrypoint, OR this upload itself failed while
34
+ # the warmup is still running. Retry a few times so a transient HF blip doesn't drop the marker and
35
+ # make the helper misread a slow run as a wrong entrypoint.
36
+ started_uploaded = False
37
+ for attempt in range(3):
38
+ try:
39
+ api.upload_file(
40
+ path_or_fileobj=io.BytesIO(f"started arch={arch}\n".encode()),
41
+ path_in_repo="out/STARTED",
42
+ repo_id=repo,
43
+ repo_type="dataset",
44
+ )
45
+ started_uploaded = True
46
+ print("[bake] uploaded out/STARTED", flush=True)
47
+ break
48
+ except Exception as e:
49
+ # keep the "out/STARTED" token in the line so it greps the same as the success/WARNING lines
50
+ print(f"[bake] out/STARTED upload attempt {attempt + 1}/3 failed: {e}", flush=True)
51
+ if attempt < 2: # no point sleeping after the last attempt
52
+ time.sleep(3)
53
+ if not started_uploaded:
54
+ # The marker never landed; warn loudly so a later "no out/STARTED" is read as upload-failed,
55
+ # not as a wrong-entrypoint run.
56
+ print("[bake] WARNING: out/STARTED never uploaded; warmup still proceeds", flush=True)
57
+
31
58
  # the flash code the helper uploaded (upload_code -> code/flash); run the warmup from it.
32
59
  snapshot_download(
33
60
  repo_id=repo,
@@ -29,6 +29,18 @@ DEFAULT_GPU = "RTX 5090"
29
29
  _DEFAULT_VOCAB_SIZE = 248_320
30
30
 
31
31
 
32
+ @dataclass(frozen=True)
33
+ class ServingCapacity:
34
+ gpu: str
35
+ max_loras: int
36
+ max_lora_rank: int
37
+ max_model_len: int
38
+ serve_model_id: str = ""
39
+ max_num_seqs: int = 0
40
+ max_num_batched_tokens: int = 0
41
+ gpu_memory_utilization: float = 0.0
42
+
43
+
32
44
  @dataclass(frozen=True)
33
45
  class ModelInfo:
34
46
  id: str
@@ -61,6 +73,10 @@ class ModelInfo:
61
73
  notes: str = ""
62
74
  # 0 = platform default (64 GB) suffices. Runner raises gpu.disk_gb to at least this.
63
75
  min_disk_gb: int = 0
76
+ # Deployment capacity of the external freesolo multi-LoRA serving app. This is separate from
77
+ # Flash's training GPU recommendation above; serving uses Modal/vLLM and sizes hot LoRA buffers
78
+ # by max_loras x max_lora_rank at engine init.
79
+ serving: ServingCapacity | None = None
64
80
  # "none" / "hybrid" (Qwen3-style) / "always" (can't disable) / "unknown" (open-model policy)
65
81
  thinking: str = "none"
66
82
  vocab_size: int = _DEFAULT_VOCAB_SIZE
@@ -79,7 +95,20 @@ class ModelInfo:
79
95
  hidden_size: int = 0
80
96
 
81
97
  def to_dict(self) -> dict[str, Any]:
82
- return asdict(self)
98
+ data = asdict(self)
99
+ serving = data.get("serving")
100
+ if serving is None:
101
+ data.pop("serving", None)
102
+ else:
103
+ for key in (
104
+ "serve_model_id",
105
+ "max_num_seqs",
106
+ "max_num_batched_tokens",
107
+ "gpu_memory_utilization",
108
+ ):
109
+ if serving.get(key) in ("", 0, 0.0, None):
110
+ serving.pop(key, None)
111
+ return data
83
112
 
84
113
 
85
114
  DEFAULT_MODEL = "Qwen/Qwen3.5-4B"
@@ -94,6 +123,7 @@ MODELS: dict[str, ModelInfo] = {
94
123
  algos=("sft", "grpo"),
95
124
  min_vram_gb=12,
96
125
  recommended_gpu="RTX 4090",
126
+ serving=ServingCapacity(gpu="L4", max_loras=16, max_lora_rank=64, max_model_len=32768),
97
127
  thinking="hybrid",
98
128
  notes="On-device class SLM (131k ctx); standard Llama architecture.",
99
129
  ),
@@ -106,6 +136,7 @@ MODELS: dict[str, ModelInfo] = {
106
136
  algos=("sft", "grpo"),
107
137
  min_vram_gb=12,
108
138
  recommended_gpu="RTX 4090",
139
+ serving=ServingCapacity(gpu="L4", max_loras=16, max_lora_rank=64, max_model_len=32768),
109
140
  thinking="hybrid",
110
141
  notes="Smallest Qwen3.5; cheap smoke/dev runs with the modern arch.",
111
142
  ),
@@ -118,6 +149,7 @@ MODELS: dict[str, ModelInfo] = {
118
149
  algos=("sft", "grpo"),
119
150
  min_vram_gb=16,
120
151
  recommended_gpu="RTX 4090",
152
+ serving=ServingCapacity(gpu="L4", max_loras=16, max_lora_rank=64, max_model_len=32768),
121
153
  thinking="hybrid",
122
154
  ),
123
155
  "Qwen/Qwen3.5-4B": ModelInfo(
@@ -129,6 +161,15 @@ MODELS: dict[str, ModelInfo] = {
129
161
  algos=("sft", "grpo"),
130
162
  min_vram_gb=32,
131
163
  recommended_gpu="RTX 5090",
164
+ serving=ServingCapacity(
165
+ gpu="L4",
166
+ serve_model_id="lovedheart/Qwen3.5-4B-FP8",
167
+ max_loras=64,
168
+ max_lora_rank=32,
169
+ max_model_len=8192,
170
+ max_num_seqs=8,
171
+ gpu_memory_utilization=0.98,
172
+ ),
132
173
  thinking="hybrid",
133
174
  notes="Current-gen 4B. GRPO uses the sleep-mode memory recipe (hybrid arch needs "
134
175
  "extra engine state-cache); fused DeltaNet kernels ship in the default stack.",
@@ -145,6 +186,15 @@ MODELS: dict[str, ModelInfo] = {
145
186
  grpo_min_vram_gb=80,
146
187
  quant="bf16",
147
188
  recommended_gpu="A100 PCIe",
189
+ serving=ServingCapacity(
190
+ gpu="L4",
191
+ serve_model_id="lovedheart/Qwen3.5-9B-FP8",
192
+ max_loras=44,
193
+ max_lora_rank=32,
194
+ max_model_len=8192,
195
+ max_num_seqs=8,
196
+ gpu_memory_utilization=0.98,
197
+ ),
148
198
  thinking="hybrid",
149
199
  notes="bf16 LoRA. ~19 GB of weights; SFT fits a 48 GB card, while colocated GRPO "
150
200
  "(two bf16 copies + KV + the 248k-vocab fp32 logits) needs an 80 GB-class card "
@@ -174,6 +224,16 @@ MODELS: dict[str, ModelInfo] = {
174
224
  sleep_unsupported=True,
175
225
  quant="bf16",
176
226
  recommended_gpu="H200",
227
+ serving=ServingCapacity(
228
+ gpu="A100-80GB",
229
+ serve_model_id="Qwen/Qwen3.6-35B-A3B-FP8",
230
+ max_loras=12,
231
+ max_lora_rank=32,
232
+ max_model_len=8192,
233
+ max_num_seqs=8,
234
+ max_num_batched_tokens=4096,
235
+ gpu_memory_utilization=0.98,
236
+ ),
177
237
  thinking="hybrid",
178
238
  min_disk_gb=200,
179
239
  notes="MoE (35B total / ~3B active), bf16 LoRA. SFT runs on the 141 GB H200 (the ~70 GB "
@@ -199,6 +259,24 @@ def get_model(model_id: str) -> ModelInfo:
199
259
  ) from exc
200
260
 
201
261
 
262
+ def serving_lora_rank_cap(model: str | ModelInfo | None) -> int | None:
263
+ """Return the model's serving LoRA rank cap, or None when Flash has no local cap.
264
+
265
+ Serving capacity is model-specific: small serving models currently allow rank 64, while larger
266
+ serving paths can cap at rank 32. Unknown/open-policy models intentionally return None instead
267
+ of inheriting a global fallback.
268
+ """
269
+ if isinstance(model, ModelInfo):
270
+ info = model
271
+ elif isinstance(model, str) and model.strip():
272
+ info = MODELS.get(model.strip())
273
+ else:
274
+ info = None
275
+ if info is None or info.serving is None:
276
+ return None
277
+ return int(info.serving.max_lora_rank)
278
+
279
+
202
280
  def vocab_size_for(model_id: str) -> int:
203
281
  """Curated vocab_size for a model, or the safe default for open-model-policy entries."""
204
282
  info = MODELS.get(model_id)
@@ -66,7 +66,7 @@ _HELP_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
66
66
  "catalog",
67
67
  [
68
68
  ("models", "list supported base models"),
69
- ("gpus", "list managed GPU classes with live $/hr"),
69
+ ("gpus", "list managed GPU classes with estimated $/hr"),
70
70
  ],
71
71
  ),
72
72
  (
@@ -226,7 +226,7 @@ def _build_parser() -> argparse.ArgumentParser:
226
226
  models = sub.add_parser("models", help="list supported base models")
227
227
  models.set_defaults(func=cmd_models)
228
228
 
229
- gpus = sub.add_parser("gpus", help="list managed GPU classes with live $/hr")
229
+ gpus = sub.add_parser("gpus", help="list managed GPU classes with estimated $/hr")
230
230
  gpus.set_defaults(func=cmd_gpus)
231
231
 
232
232
  env = sub.add_parser("env", help="manage Freesolo environments")
@@ -268,7 +268,9 @@ def _build_parser() -> argparse.ArgumentParser:
268
268
  env_pull.set_defaults(func=cmd_env_pull)
269
269
 
270
270
  env_delete = env_sub.add_parser("delete", help="delete a published Freesolo environment")
271
- env_delete.add_argument("env_id", help="the Freesolo environment id to delete, e.g. you/your-env")
271
+ env_delete.add_argument(
272
+ "env_id", help="the Freesolo environment id to delete, e.g. you/your-env"
273
+ )
272
274
  env_delete.add_argument(
273
275
  "-y",
274
276
  "--yes",
@@ -236,8 +236,8 @@ def cmd_env_setup(args) -> int:
236
236
  "[train]\n"
237
237
  "steps = 150\n"
238
238
  "lora_rank = 32\n"
239
- "# GPU and the HF artifact repo are managed automatically by the platform: the GPU is\n"
240
- "# the cheapest fitting class across providers, and each run gets its own artifact repo.\n"
239
+ "# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
240
+ "# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
241
241
  )
242
242
  sft = Path("configs/sft.toml")
243
243
  if not sft.exists():
@@ -248,8 +248,8 @@ def cmd_env_setup(args) -> int:
248
248
  "[train]\n"
249
249
  "epochs = 1\n"
250
250
  "lora_rank = 32\n"
251
- "# GPU and the HF artifact repo are managed automatically by the platform: the GPU is\n"
252
- "# the cheapest fitting class across providers, and each run gets its own artifact repo.\n"
251
+ "# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
252
+ "# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
253
253
  )
254
254
  training = Path("TRAINING.md")
255
255
  if not training.exists():
@@ -280,7 +280,7 @@ def cmd_models(args) -> int:
280
280
 
281
281
 
282
282
  def cmd_gpus(args) -> int:
283
- """List RunPod GPU classes, VRAM, and $/hr."""
283
+ """List validated managed GPU classes, VRAM, and estimated $/hr."""
284
284
  from flash.providers.base import GPU_INFO
285
285
  from flash.providers.runpod.pricing import static_rates as runpod_static_rates
286
286
 
@@ -290,7 +290,7 @@ def cmd_gpus(args) -> int:
290
290
  )
291
291
  tip = (
292
292
  "Tip: GPU class selection is fully automatic — the submit-time allocator always picks the\n"
293
- "cheapest validated RunPod class that fits the model, so you don't pin a GPU type."
293
+ "cheapest validated managed class that fits the model, so you don't pin a GPU type."
294
294
  )
295
295
  if render.styled():
296
296
  rows = [(info.name, info.vram_gb, runpod_rates.get(info.name)) for info in infos]
@@ -300,7 +300,7 @@ def cmd_gpus(args) -> int:
300
300
  def fmt_rate(v: float | None) -> str:
301
301
  return f"{v:>10.2f}" if v else f"{'-':>10}"
302
302
 
303
- print(f"{'gpu':<16}{'vram':>6}{'runpod$/hr':>11}")
303
+ print(f"{'gpu':<16}{'vram':>6}{'$/hr':>11}")
304
304
  for info in infos:
305
305
  runpod_rate = runpod_rates.get(info.name)
306
306
  print(f"{info.name:<16}{info.vram_gb:>5}G{fmt_rate(runpod_rate):>11}")
@@ -338,7 +338,11 @@ def cmd_env_list(args) -> int:
338
338
 
339
339
 
340
340
  def _cmd_train_cost(args) -> int:
341
- """Print the pre-flight USD cost for the config and exit without submitting."""
341
+ """`flash train --cost`: print the pre-flight USD cost for the config and exit (no submit).
342
+
343
+ Catalog-only and deterministic; SFT uses the actual local training-token count when the env
344
+ and tokenizer are importable. An uncapped SFT run must be able to count the env's train split,
345
+ otherwise it errors instead of guessing a dataset size."""
342
346
  from flash.cost import estimate_cost
343
347
 
344
348
  spec = spec_from_file(
@@ -479,8 +483,7 @@ def cmd_runs(args) -> int:
479
483
  spec = r.get("spec") or {}
480
484
  model = spec.get("model", "")
481
485
  algorithm = str(spec.get("algorithm") or "-").upper()
482
- gpu, provider = render._run_where(spec, r.get("remote") or {})
483
- where = f"{gpu}@{provider}" if provider else gpu
486
+ where = render._run_gpu(spec, r.get("remote") or {})
484
487
  print(
485
488
  f"{r['run_id']:<32} {r['state']:<11} {algorithm:<5} "
486
489
  f"{r.get('cost_usd', 0.0):>8.4f} {where:<22} {model}"