freesolo-flash-dev 0.2.32__tar.gz → 0.2.34__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 (275) hide show
  1. freesolo_flash_dev-0.2.34/.github/workflows/notify-tests-repo.yml +35 -0
  2. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/PKG-INFO +1 -1
  3. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/catalog.py +44 -14
  4. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/__init__.py +17 -8
  5. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/commands.py +57 -20
  6. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/envpush.py +17 -2
  7. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/render.py +2 -2
  8. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/training_doc.py +10 -9
  9. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/client/http.py +11 -8
  10. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cost/facts.py +2 -0
  11. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/envs/adapter.py +3 -2
  12. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/runner/__init__.py +66 -4
  13. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/runner/deploy.py +29 -4
  14. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/runner/lifecycle.py +37 -10
  15. freesolo_flash_dev-0.2.34/flash/serve/pricing.py +94 -0
  16. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/_runtime.py +2 -2
  17. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/billing.py +26 -6
  18. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/billing_retry.py +33 -34
  19. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/envs.py +24 -3
  20. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/reconcile.py +10 -6
  21. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/routes/serving.py +17 -2
  22. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/pyproject.toml +2 -2
  23. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_billing_retry.py +61 -41
  24. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_catalog_consistency.py +28 -15
  25. freesolo_flash_dev-0.2.34/tests/test_charge_pricing.py +139 -0
  26. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_commands.py +35 -14
  27. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_client.py +31 -22
  28. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_publish.py +50 -0
  29. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_push.py +10 -0
  30. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_flash_worker.py +1 -1
  31. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_jobs.py +17 -4
  32. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_orchestrator_flash.py +3 -2
  33. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_serve.py +7 -7
  34. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_server_api.py +41 -0
  35. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_server_billing.py +50 -5
  36. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_serving_contract.py +36 -3
  37. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_spec_and_validation.py +6 -6
  38. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_verifiers.py +69 -2
  39. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_vl_warmstart_recombine.py +10 -10
  40. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/uv.lock +1 -1
  41. freesolo_flash_dev-0.2.32/docs/cli-theme/README.md +0 -32
  42. freesolo_flash_dev-0.2.32/docs/cli-theme/before-after-dark.png +0 -0
  43. freesolo_flash_dev-0.2.32/docs/cli-theme/before-after-light.png +0 -0
  44. freesolo_flash_dev-0.2.32/docs/cli-theme/gallery-dark.png +0 -0
  45. freesolo_flash_dev-0.2.32/docs/cli-theme/gallery-light.png +0 -0
  46. freesolo_flash_dev-0.2.32/docs/cli-theme/help-dark.png +0 -0
  47. freesolo_flash_dev-0.2.32/docs/cli-theme/help-light.png +0 -0
  48. freesolo_flash_dev-0.2.32/flash/serve/pricing.py +0 -58
  49. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.dockerignore +0 -0
  50. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.env.example +0 -0
  51. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/auto-rebake.yml +0 -0
  52. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/bake-kernel-cache.yml +0 -0
  53. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/ci.yml +0 -0
  54. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/main-source-guard.yml +0 -0
  55. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/publish-dev.yml +0 -0
  56. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/publish-image.yml +0 -0
  57. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/publish.yml +0 -0
  58. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/version-parity.yml +0 -0
  59. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/worker-image.yml +0 -0
  60. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.gitignore +0 -0
  61. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/Dockerfile +0 -0
  62. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/Dockerfile.worker +0 -0
  63. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/LICENSE +0 -0
  64. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/README.md +0 -0
  65. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/build/kernel_cache/.gitignore +0 -0
  66. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/build/kernel_cache/.keep +0 -0
  67. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/Dockerfile.kernelcache +0 -0
  68. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/Dockerfile.kernelcache.relayer +0 -0
  69. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/bake_kernel_cache.py +0 -0
  70. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/bake_pod_entry.py +0 -0
  71. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/kernel_fingerprint.py +0 -0
  72. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/make_rp_handler.py +0 -0
  73. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/cli-style/README.md +0 -0
  74. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/cli-style/generate.py +0 -0
  75. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/cli-style/index.html +0 -0
  76. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/cli-style/preview.png +0 -0
  77. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/cli-style/themed-errors.png +0 -0
  78. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/kernel-cache.md +0 -0
  79. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/__init__.py +0 -0
  80. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/_channel.py +0 -0
  81. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/_fileio.py +0 -0
  82. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/_logging.py +0 -0
  83. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/_update_check.py +0 -0
  84. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/__main__.py +0 -0
  85. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/client/__init__.py +0 -0
  86. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/client/config.py +0 -0
  87. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/client/runtime_secrets.py +0 -0
  88. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/client/specs.py +0 -0
  89. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cost/__init__.py +0 -0
  90. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cost/analytical.py +0 -0
  91. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cost/spec.py +0 -0
  92. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cost/types.py +0 -0
  93. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/__init__.py +0 -0
  94. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/accounting.py +0 -0
  95. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/chalk_kernels.py +0 -0
  96. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/multiturn_rollout.py +0 -0
  97. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/recipe.py +0 -0
  98. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/vram.py +0 -0
  99. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/__init__.py +0 -0
  100. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/__main__.py +0 -0
  101. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/_pkg.py +0 -0
  102. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/adapter.py +0 -0
  103. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/decoding.py +0 -0
  104. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/finalize.py +0 -0
  105. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/gpu_setup.py +0 -0
  106. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/grpo.py +0 -0
  107. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/heartbeat.py +0 -0
  108. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/hf.py +0 -0
  109. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/kernel_warmup.py +0 -0
  110. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/lora.py +0 -0
  111. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/packing.py +0 -0
  112. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/__init__.py +0 -0
  113. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/attn.py +0 -0
  114. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/diagnostics.py +0 -0
  115. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/lifecycle.py +0 -0
  116. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/liger.py +0 -0
  117. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/loraplus.py +0 -0
  118. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/memory.py +0 -0
  119. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/rl.py +0 -0
  120. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/sft.py +0 -0
  121. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/wandb_log.py +0 -0
  122. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/envs/__init__.py +0 -0
  123. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/envs/base.py +0 -0
  124. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/envs/pull.py +0 -0
  125. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/envs/registry.py +0 -0
  126. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/__init__.py +0 -0
  127. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/_auth.py +0 -0
  128. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/_http.py +0 -0
  129. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/_instance.py +0 -0
  130. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/_instance_bootstrap.py +0 -0
  131. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/_poll.py +0 -0
  132. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/allocator.py +0 -0
  133. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/base.py +0 -0
  134. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/__init__.py +0 -0
  135. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/api.py +0 -0
  136. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/auth.py +0 -0
  137. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/gpus.py +0 -0
  138. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  139. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  140. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/preflight.py +0 -0
  141. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/pricing.py +0 -0
  142. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/train.py +0 -0
  143. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/preflight.py +0 -0
  144. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/realized.py +0 -0
  145. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/__init__.py +0 -0
  146. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/api.py +0 -0
  147. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/auth.py +0 -0
  148. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/cost.py +0 -0
  149. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/gpus.py +0 -0
  150. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/jobs.py +0 -0
  151. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/keys.py +0 -0
  152. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/preflight.py +0 -0
  153. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/preload.py +0 -0
  154. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/pricing.py +0 -0
  155. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/slots.py +0 -0
  156. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/train/__init__.py +0 -0
  157. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/train/deps.py +0 -0
  158. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/train/endpoints.py +0 -0
  159. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/py.typed +0 -0
  160. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/runner/checkpoints.py +0 -0
  161. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/schema/__init__.py +0 -0
  162. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/schema/fields.py +0 -0
  163. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/serve/__init__.py +0 -0
  164. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/serve/deploy.py +0 -0
  165. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/serve/export.py +0 -0
  166. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/__init__.py +0 -0
  167. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/__main__.py +0 -0
  168. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/_deps.py +0 -0
  169. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/_internal_client.py +0 -0
  170. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/_locks.py +0 -0
  171. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/app.py +0 -0
  172. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/auth.py +0 -0
  173. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/checkpoints.py +0 -0
  174. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/db.py +0 -0
  175. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/environment_registry.py +0 -0
  176. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/routes/__init__.py +0 -0
  177. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/routes/envs.py +0 -0
  178. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/routes/meta.py +0 -0
  179. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/routes/runs.py +0 -0
  180. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/run_registry.py +0 -0
  181. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/spec.py +0 -0
  182. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/infisical-entrypoint.sh +0 -0
  183. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/scripts/build_dev_dist.py +0 -0
  184. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/__init__.py +0 -0
  185. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/_helpers/__init__.py +0 -0
  186. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/_helpers/runner.py +0 -0
  187. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/_helpers/specs.py +0 -0
  188. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/conftest.py +0 -0
  189. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/fixtures/math_eval.jsonl +0 -0
  190. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/fixtures/math_train.jsonl +0 -0
  191. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/live/__init__.py +0 -0
  192. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/live/conftest.py +0 -0
  193. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/live/test_lambda_live.py +0 -0
  194. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/live/test_runpod_live.py +0 -0
  195. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_agent_flash_cli_contract.py +0 -0
  196. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_algorithms.py +0 -0
  197. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_allocator.py +0 -0
  198. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_b200_rollout_opt.py +0 -0
  199. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_backend_jobspec_contract.py +0 -0
  200. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cancel_remote.py +0 -0
  201. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_chalk_kernels.py +0 -0
  202. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_checkpoints.py +0 -0
  203. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_errors.py +0 -0
  204. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_estimate.py +0 -0
  205. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_help.py +0 -0
  206. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_managed.py +0 -0
  207. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_render_theme.py +0 -0
  208. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_client_server_integration.py +0 -0
  209. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_config_overrides.py +0 -0
  210. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_analytical.py +0 -0
  211. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_equation.py +0 -0
  212. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_estimate.py +0 -0
  213. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_hardware.py +0 -0
  214. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_models.py +0 -0
  215. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_rewards.py +0 -0
  216. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_dev_channel.py +0 -0
  217. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_disk_gb.py +0 -0
  218. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_endpoint_name.py +0 -0
  219. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_delete.py +0 -0
  220. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_no_install.py +0 -0
  221. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_pull.py +0 -0
  222. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_rate_limit_resolve.py +0 -0
  223. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_envs_coverage.py +0 -0
  224. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_export.py +0 -0
  225. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_flash_mvp.py +0 -0
  226. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_flashinfer_cache_dirs.py +0 -0
  227. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_github_urlopen_retry.py +0 -0
  228. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_gpus.py +0 -0
  229. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_grpo_mask_aware.py +0 -0
  230. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_grpo_params.py +0 -0
  231. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_grpo_sleep_gate.py +0 -0
  232. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_idle_endpoint_reaper.py +0 -0
  233. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_internal_client.py +0 -0
  234. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_kernel_cache.py +0 -0
  235. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_kernel_fingerprint.py +0 -0
  236. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_kv_util.py +0 -0
  237. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_lambda_runner.py +0 -0
  238. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_logging.py +0 -0
  239. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_login_perms.py +0 -0
  240. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_managed_hf_repo.py +0 -0
  241. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_metrics_schema_agent_contract.py +0 -0
  242. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_mig_guard.py +0 -0
  243. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_multiturn_rollout.py +0 -0
  244. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_oom_escalate_gpu.py +0 -0
  245. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_open_model_policy.py +0 -0
  246. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_packing.py +0 -0
  247. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_poll_helpers.py +0 -0
  248. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_preflight.py +0 -0
  249. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_provider_routing.py +0 -0
  250. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_provider_teardown_robustness.py +0 -0
  251. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_providers_symmetry.py +0 -0
  252. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_realized_cost.py +0 -0
  253. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_resolve_params_b.py +0 -0
  254. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_resume_on_retry.py +0 -0
  255. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_runmgmt.py +0 -0
  256. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_runpod_api_delete.py +0 -0
  257. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_runpod_key_fingerprint.py +0 -0
  258. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_runpod_key_waterfall.py +0 -0
  259. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_runpod_slots.py +0 -0
  260. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_server_db.py +0 -0
  261. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_sft_gc_off.py +0 -0
  262. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_thinking_config.py +0 -0
  263. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_update_check.py +0 -0
  264. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_version.py +0 -0
  265. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  266. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_vl_weight_sync.py +0 -0
  267. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_wandb_naming.py +0 -0
  268. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_warmstart_cross_repo.py +0 -0
  269. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_weight_cache.py +0 -0
  270. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_dryrun.py +0 -0
  271. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_hardexit.py +0 -0
  272. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_image.py +0 -0
  273. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_init_heartbeat.py +0 -0
  274. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_stack.py +0 -0
  275. {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_thinking.py +0 -0
@@ -0,0 +1,35 @@
1
+ name: Notify tests repo
2
+
3
+ # Flash's cross-repo integration seams (flash<->backend auth, allocator, CLI flow)
4
+ # live in the freesolo-co/tests repo, which carries flash as a pinned submodule.
5
+ # On every push to dev/main, dispatch to that repo so its CI bumps the flash
6
+ # submodule pin and runs the affected suites against the new sha. Mirrors the
7
+ # freesolo-side notify-tests-repo.yml; the tests repo listens for `flash-merged`.
8
+
9
+ on:
10
+ push:
11
+ branches: [dev, main]
12
+ workflow_dispatch:
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ notify:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Dispatch to freesolo-co/tests
22
+ env:
23
+ PAT: ${{ secrets.FREESOLO_GITHUB_PAT }}
24
+ SHA: ${{ github.sha }}
25
+ REF: ${{ github.ref_name }}
26
+ run: |
27
+ if [ -z "$PAT" ]; then
28
+ echo "No FREESOLO_GITHUB_PAT (likely a fork) — skipping dispatch."
29
+ exit 0
30
+ fi
31
+ curl -fsS -X POST \
32
+ -H "Authorization: Bearer ${PAT}" \
33
+ -H "Accept: application/vnd.github+json" \
34
+ https://api.github.com/repos/freesolo-co/tests/dispatches \
35
+ -d "{\"event_type\":\"flash-merged\",\"client_payload\":{\"sha\":\"${SHA}\",\"ref\":\"${REF}\"}}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 0.2.32
3
+ Version: 0.2.34
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
@@ -38,6 +38,7 @@ class ServingCapacity:
38
38
  serve_model_id: str = ""
39
39
  max_num_seqs: int = 0
40
40
  max_num_batched_tokens: int = 0
41
+ tensor_parallel_size: int = 0
41
42
  gpu_memory_utilization: float = 0.0
42
43
 
43
44
 
@@ -104,6 +105,7 @@ class ModelInfo:
104
105
  "serve_model_id",
105
106
  "max_num_seqs",
106
107
  "max_num_batched_tokens",
108
+ "tensor_parallel_size",
107
109
  "gpu_memory_utilization",
108
110
  ):
109
111
  if serving.get(key) in ("", 0, 0.0, None):
@@ -113,6 +115,15 @@ class ModelInfo:
113
115
 
114
116
  DEFAULT_MODEL = "Qwen/Qwen3.5-4B"
115
117
 
118
+ SERVING_FP8_MODEL_REPOS: dict[str, str] = {
119
+ "openbmb/MiniCPM5-1B": "Freesolo-Co/MiniCPM5-1B-FP8",
120
+ "Qwen/Qwen3.5-0.8B": "Freesolo-Co/Qwen3.5-0.8B-FP8",
121
+ "Qwen/Qwen3.5-2B": "Freesolo-Co/Qwen3.5-2B-FP8",
122
+ "Qwen/Qwen3.5-4B": "Freesolo-Co/Qwen3.5-4B-FP8",
123
+ "Qwen/Qwen3.5-9B": "Freesolo-Co/Qwen3.5-9B-FP8",
124
+ "Qwen/Qwen3.6-35B-A3B": "Qwen/Qwen3.6-35B-A3B-FP8",
125
+ }
126
+
116
127
  MODELS: dict[str, ModelInfo] = {
117
128
  "openbmb/MiniCPM5-1B": ModelInfo(
118
129
  id="openbmb/MiniCPM5-1B",
@@ -123,7 +134,13 @@ MODELS: dict[str, ModelInfo] = {
123
134
  algos=("sft", "grpo"),
124
135
  min_vram_gb=12,
125
136
  recommended_gpu="RTX 4090",
126
- serving=ServingCapacity(gpu="L4", max_loras=16, max_lora_rank=64, max_model_len=32768),
137
+ serving=ServingCapacity(
138
+ gpu="L4",
139
+ serve_model_id=SERVING_FP8_MODEL_REPOS["openbmb/MiniCPM5-1B"],
140
+ max_loras=16,
141
+ max_lora_rank=128,
142
+ max_model_len=32768,
143
+ ),
127
144
  thinking="hybrid",
128
145
  notes="On-device class SLM (131k ctx); standard Llama architecture.",
129
146
  ),
@@ -136,7 +153,13 @@ MODELS: dict[str, ModelInfo] = {
136
153
  algos=("sft", "grpo"),
137
154
  min_vram_gb=12,
138
155
  recommended_gpu="RTX 4090",
139
- serving=ServingCapacity(gpu="L4", max_loras=16, max_lora_rank=64, max_model_len=32768),
156
+ serving=ServingCapacity(
157
+ gpu="L4",
158
+ serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-0.8B"],
159
+ max_loras=16,
160
+ max_lora_rank=128,
161
+ max_model_len=32768,
162
+ ),
140
163
  thinking="hybrid",
141
164
  notes="Smallest Qwen3.5; cheap smoke/dev runs with the modern arch.",
142
165
  ),
@@ -149,7 +172,13 @@ MODELS: dict[str, ModelInfo] = {
149
172
  algos=("sft", "grpo"),
150
173
  min_vram_gb=16,
151
174
  recommended_gpu="RTX 4090",
152
- serving=ServingCapacity(gpu="L4", max_loras=16, max_lora_rank=64, max_model_len=32768),
175
+ serving=ServingCapacity(
176
+ gpu="L4",
177
+ serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-2B"],
178
+ max_loras=16,
179
+ max_lora_rank=128,
180
+ max_model_len=32768,
181
+ ),
153
182
  thinking="hybrid",
154
183
  ),
155
184
  "Qwen/Qwen3.5-4B": ModelInfo(
@@ -162,10 +191,10 @@ MODELS: dict[str, ModelInfo] = {
162
191
  min_vram_gb=32,
163
192
  recommended_gpu="RTX 5090",
164
193
  serving=ServingCapacity(
165
- gpu="L4",
166
- serve_model_id="lovedheart/Qwen3.5-4B-FP8",
194
+ gpu="L40S",
195
+ serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-4B"],
167
196
  max_loras=64,
168
- max_lora_rank=32,
197
+ max_lora_rank=64,
169
198
  max_model_len=8192,
170
199
  max_num_seqs=8,
171
200
  gpu_memory_utilization=0.98,
@@ -187,10 +216,10 @@ MODELS: dict[str, ModelInfo] = {
187
216
  quant="bf16",
188
217
  recommended_gpu="A100 PCIe",
189
218
  serving=ServingCapacity(
190
- gpu="L4",
191
- serve_model_id="lovedheart/Qwen3.5-9B-FP8",
219
+ gpu="L40S",
220
+ serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-9B"],
192
221
  max_loras=44,
193
- max_lora_rank=32,
222
+ max_lora_rank=64,
194
223
  max_model_len=8192,
195
224
  max_num_seqs=8,
196
225
  gpu_memory_utilization=0.98,
@@ -225,13 +254,14 @@ MODELS: dict[str, ModelInfo] = {
225
254
  quant="bf16",
226
255
  recommended_gpu="H200",
227
256
  serving=ServingCapacity(
228
- gpu="A100-80GB",
229
- serve_model_id="Qwen/Qwen3.6-35B-A3B-FP8",
257
+ gpu="A100-80GB:2",
258
+ serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.6-35B-A3B"],
230
259
  max_loras=12,
231
- max_lora_rank=32,
260
+ max_lora_rank=64,
232
261
  max_model_len=8192,
233
262
  max_num_seqs=8,
234
263
  max_num_batched_tokens=4096,
264
+ tensor_parallel_size=2,
235
265
  gpu_memory_utilization=0.98,
236
266
  ),
237
267
  thinking="hybrid",
@@ -262,8 +292,8 @@ def get_model(model_id: str) -> ModelInfo:
262
292
  def serving_lora_rank_cap(model: str | ModelInfo | None) -> int | None:
263
293
  """Return the model's serving LoRA rank cap, or None when Flash has no local cap.
264
294
 
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
295
+ Serving capacity is model-specific: small serving models currently allow rank 128, while larger
296
+ serving paths currently allow rank 64. Unknown/open-policy models intentionally return None instead
267
297
  of inheriting a global fallback.
268
298
  """
269
299
  if isinstance(model, ModelInfo):
@@ -35,6 +35,7 @@ from flash.cli.commands import ( # noqa: F401
35
35
  cmd_env_setup,
36
36
  cmd_export,
37
37
  cmd_gpus,
38
+ cmd_log,
38
39
  cmd_login,
39
40
  cmd_models,
40
41
  cmd_runs,
@@ -83,7 +84,8 @@ _HELP_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
83
84
  "training",
84
85
  [
85
86
  ("train", "submit a managed run from a TOML config"),
86
- ("status", "show a run's status, logs, or live follow"),
87
+ ("status", "show a run's current status"),
88
+ ("log", "print or follow a run's logs"),
87
89
  ("runs", "list runs with their state and cost"),
88
90
  ("checkpoints", "list a run's deployable RL checkpoints"),
89
91
  ("cancel", "cancel a running job"),
@@ -309,21 +311,28 @@ def _build_parser() -> argparse.ArgumentParser:
309
311
  )
310
312
  train.set_defaults(func=cmd_train)
311
313
 
312
- status = sub.add_parser("status", help="show a run's status, logs, or follow logs")
314
+ status = sub.add_parser("status", help="show a run's current status")
313
315
  status.add_argument("run_id")
314
316
  status.add_argument(
315
- "--logs",
317
+ "-f",
318
+ "--follow",
316
319
  action="store_true",
317
- help="print current logs before status the orchestrator log plus the train-subprocess "
318
- "stdout + traceback (console_/error_<phase>.txt) fetched from the run's HF artifact repo",
320
+ help="poll status until the run ends without replaying logs",
319
321
  )
320
- status.add_argument(
322
+ status.set_defaults(func=cmd_status)
323
+
324
+ log = sub.add_parser(
325
+ "log",
326
+ help="print a run's full logs, including worker console/error artifacts",
327
+ )
328
+ log.add_argument("run_id")
329
+ log.add_argument(
321
330
  "-f",
322
331
  "--follow",
323
332
  action="store_true",
324
- help="stream logs until the run ends, then print final status",
333
+ help="stream logs until the run reaches a terminal state",
325
334
  )
326
- status.set_defaults(func=cmd_status)
335
+ log.set_defaults(func=cmd_log)
327
336
 
328
337
  runs = sub.add_parser("runs", help="list runs and their state/cost")
329
338
  runs.set_defaults(func=cmd_runs)
@@ -401,7 +401,7 @@ def cmd_train(args) -> int:
401
401
  else:
402
402
  print(
403
403
  f"run {run_id} submitted; following logs "
404
- f"(Ctrl-C detaches, `flash status {run_id} --follow` resumes)",
404
+ f"(Ctrl-C detaches, `flash log {run_id} --follow` resumes)",
405
405
  file=sys.stderr,
406
406
  )
407
407
  return _follow_run(client, run_id)
@@ -437,28 +437,65 @@ def _follow_run(client: ApiClient, run_id: str) -> int:
437
437
  return 0 if state in _OK_STATES else 1
438
438
 
439
439
 
440
+ def _follow_status(client: ApiClient, run_id: str, interval: float = 2.0) -> int:
441
+ """Poll run status until terminal, without replaying worker logs."""
442
+ last_rendered: str | None = None
443
+ while True:
444
+ status = client.get_run(run_id)
445
+ rendered = render.run_status(status) if render.styled() else json.dumps(status, indent=2)
446
+ if rendered != last_rendered:
447
+ print(rendered)
448
+ last_rendered = rendered
449
+ state = str(status.get("state") or "")
450
+ if state in _CLI_DONE_STATES:
451
+ return 0 if state in _OK_STATES else 1
452
+ time.sleep(interval)
453
+
454
+
455
+ def _print_log_text(text: str) -> bool:
456
+ if not text:
457
+ return False
458
+ print(text, end="")
459
+ if not text.endswith("\n"):
460
+ print()
461
+ return True
462
+
463
+
464
+ def _print_log_snapshot(client: ApiClient, run_id: str) -> bool:
465
+ """Print a finite snapshot from the log endpoint without following new bytes."""
466
+ page = client.get_logs(run_id, offset=0)
467
+ return _print_log_text(str(page.get("logs") or ""))
468
+
469
+
470
+ def _print_worker_output(client: ApiClient, run_id: str, *, printed_any: bool = False) -> bool:
471
+ for name, text in (client.get_worker_output(run_id) or {}).items():
472
+ if not text:
473
+ continue
474
+ sep = "\n" if printed_any else ""
475
+ if render.styled():
476
+ print(f"{sep}{render.log_section(name)}")
477
+ else:
478
+ print(f"{sep}----- {name} -----")
479
+ print(text, end="" if text.endswith("\n") else "\n")
480
+ printed_any = True
481
+ return printed_any
482
+
483
+
484
+ def cmd_log(args) -> int:
485
+ client = client_from_config()
486
+ if getattr(args, "follow", False):
487
+ state = _poll_logs(client, args.run_id, interval=2.0)
488
+ _print_worker_output(client, args.run_id, printed_any=True)
489
+ return 0 if state in _OK_STATES else 1
490
+ printed_any = _print_log_snapshot(client, args.run_id)
491
+ _print_worker_output(client, args.run_id, printed_any=printed_any)
492
+ return 0
493
+
494
+
440
495
  def cmd_status(args) -> int:
441
496
  client = client_from_config()
442
497
  if getattr(args, "follow", False):
443
- return _follow_run(client, args.run_id)
444
- if getattr(args, "logs", False):
445
- logs = client.get_logs(args.run_id).get("logs", "")
446
- printed_any = False
447
- if logs:
448
- print(logs, end="")
449
- if not logs.endswith("\n"):
450
- print()
451
- printed_any = True
452
- for name, text in (client.get_worker_output(args.run_id) or {}).items():
453
- if not text:
454
- continue
455
- sep = "\n" if printed_any else ""
456
- if render.styled():
457
- print(f"{sep}{render.log_section(name)}")
458
- else:
459
- print(f"{sep}----- {name} -----")
460
- print(text, end="" if text.endswith("\n") else "\n")
461
- printed_any = True
498
+ return _follow_status(client, args.run_id)
462
499
  status = client.get_run(args.run_id)
463
500
  if render.styled():
464
501
  print(render.run_status(status))
@@ -190,8 +190,23 @@ _ENV_PUSH_SIDECAR_SUFFIXES = frozenset(
190
190
  def _normalize_env_name(raw: str) -> str | None:
191
191
  import re
192
192
 
193
- name = re.sub(r"[^a-z0-9]+", "-", raw.lower()).strip("-")
194
- return name or None
193
+ def normalize_segment(value: str) -> str | None:
194
+ segment = re.sub(r"[^a-z0-9._-]+", "-", value.lower()).strip("-")
195
+ if segment in {"", ".", ".."} or not re.search(r"[a-z0-9]", segment):
196
+ return None
197
+ return segment
198
+
199
+ text = str(raw or "").strip()
200
+ if "/" not in text:
201
+ return normalize_segment(text)
202
+ parts = [part.strip() for part in text.split("/")]
203
+ if len(parts) != 2 or not all(parts):
204
+ return None
205
+ namespace = normalize_segment(parts[0])
206
+ name = normalize_segment(parts[1])
207
+ if not namespace or not name:
208
+ return None
209
+ return f"{namespace}/{name}"
195
210
 
196
211
 
197
212
  def _with_syspath_bootstrap(env_source: str) -> str:
@@ -381,7 +381,7 @@ def version(value: str) -> str:
381
381
  def submitted(run_id: str) -> str:
382
382
  """The `flash train` hand-off note (printed to stderr before logs start streaming)."""
383
383
  head = ok(f"run {_paint(run_id, _ACCENT2)} submitted")
384
- hint = _dim(f"following logs — Ctrl-C detaches; resume with `flash status {run_id} --follow`")
384
+ hint = _dim(f"following logs — Ctrl-C detaches; resume with `flash log {run_id} --follow`")
385
385
  return _safe(f"{head}\n{hint}")
386
386
 
387
387
 
@@ -659,7 +659,7 @@ def chat_label() -> str:
659
659
 
660
660
 
661
661
  def log_section(name: str) -> str:
662
- """A themed divider above a passthrough worker-log section in `flash status --logs` — the same
662
+ """A themed divider above a passthrough worker-log section in `flash log` — the same
663
663
  idiom as chat_label sitting above a raw chat reply (the log body stays raw). The machine path
664
664
  keeps the plain ``----- name -----`` divider that scripts and tests match on."""
665
665
  rule = _paint(_glyph("─", "-") * 3, _FAINT)
@@ -127,8 +127,9 @@ flash train configs/rl.toml --background # submit and return immediately
127
127
 
128
128
  ```bash
129
129
  flash status <run-id> # state + accrued cost
130
- flash status <run-id> --logs # reward/loss trend + worker console/error logs + any traceback
131
- flash status <run-id> --follow # stream a live run to completion
130
+ flash log <run-id> # reward/loss trend + worker console/error logs + any traceback
131
+ flash log <run-id> --follow # stream a live run to completion
132
+ flash status <run-id> # current run state, cost, and deployment info
132
133
  flash runs # all your runs and their state/cost
133
134
  flash cancel <run-id> # stop a run
134
135
  ```
@@ -205,8 +206,8 @@ spending another GPU run:
205
206
  | All-zero or flat GRPO reward | `reward_mean` stays near 0 and outputs do not improve | Make the reward dense: give partial credit for parse/format/execution/correctness tiers, and log a separate clean `success` metric. Do not keep rerunning an all-zero reward. |
206
207
  | Reward rises but behavior is worse | Short, templated, malformed, or reward-hacked outputs score well | Deploy the adapter and probe real examples. Add hard validity gates before judge calls, penalize degenerate shortcuts, and judge the outcome rather than the surface string. |
207
208
  | Output is truncated | Correct-looking answers cut off mid-response or JSON is incomplete | Increase `max_tokens` for GRPO rollouts or `max_length` for SFT only after seeing truncation. Oversizing them by default just burns memory/cost. |
208
- | Infrastructure, CUDA, OOM, vLLM, or kernel failure | Run errors before useful metrics, often during setup/model load | Treat this as infrastructure pressure, not proof the model is too large. Read `flash status <run-id> --logs`, reduce footprint (`max_length`, `max_tokens`, `group_size`) if needed, and let Flash retry/allocate another fitting GPU class. |
209
- | Run looks stuck after disconnecting | Terminal stopped streaming but the job may still be alive | Ctrl-C detaches. Use `flash status <run-id> --follow` to reattach, `--logs` for the console/error tail, or `flash cancel <run-id>` if you intentionally want to stop it. |
209
+ | Infrastructure, CUDA, OOM, vLLM, or kernel failure | Run errors before useful metrics, often during setup/model load | Treat this as infrastructure pressure, not proof the model is too large. Read `flash log <run-id>`, reduce footprint (`max_length`, `max_tokens`, `group_size`) if needed, and let Flash retry/allocate another fitting GPU class. |
210
+ | Run looks stuck after disconnecting | Terminal stopped streaming but the job may still be alive | Ctrl-C detaches. Use `flash log <run-id> --follow` to reattach, `flash log <run-id>` for the console/error output, or `flash cancel <run-id>` if you intentionally want to stop it. |
210
211
  | Final checkpoint regresses | Last step is worse than an earlier checkpoint | Run `flash checkpoints <run-id>`, deploy a specific step with `flash deploy <run-id> --step N`, and compare with held-out probes before exporting or relying on the final adapter. |
211
212
  | Export fails before upload | CLI says no HuggingFace token | Pass `flash export --api-key hf_...`, or set `HF_TOKEN` in your shell, `.env`, or `.env.local`. Exports are private unless you pass `--public`. |
212
213
  | SFT loss improves but quality does not | Train loss falls while held-out behavior stalls or degrades | Keep a held-out split outside training. Deploy and score that split; if quality drops, reduce epochs or improve data instead of adding more passes. |
@@ -222,15 +223,15 @@ spending another GPU run:
222
223
  - **Read the model's outputs, not just the metrics.** A rising reward can come from
223
224
  reward-hacking or a degenerate output the reward still credits — metrics alone never
224
225
  establish that the model got better. Flash does not expose training-time rollouts
225
- through the CLI (`--logs` gives you the metric trend and the worker's console/error
226
+ through the CLI (`flash log` gives you the metric trend and the worker's console/error
226
227
  logs, not the sampled generations), so to read real outputs **deploy the adapter and
227
228
  probe it**: `flash deploy <run-id>` then `flash chat <run-id> -m "..."` on at least a
228
229
  few real inputs, including ones it should get wrong.
229
230
 
230
231
  ```bash
231
232
  flash status <run-id> # state + accrued cost
232
- flash status <run-id> --logs # metric trend + worker console/error logs (+ traceback)
233
- flash status <run-id> --follow # stream a live run until completion
233
+ flash log <run-id> # metric trend + worker console/error logs (+ traceback)
234
+ flash log <run-id> --follow # stream a live run until completion
234
235
  flash deploy <run-id> # serve the adapter, then `flash chat` it to read real outputs
235
236
  ```
236
237
 
@@ -472,8 +473,8 @@ flash train configs/rl.toml --dry-run # validate the config locally (no GPU, no
472
473
  flash train configs/rl.toml --cost # pre-flight USD estimate, then exit
473
474
  flash train configs/rl.toml # submit and follow logs (Ctrl-C detaches; --background to skip following)
474
475
  flash status <run-id> # state + accrued cost
475
- flash status <run-id> --logs # reward/loss trend + worker console/error logs
476
- flash status <run-id> --follow # stream a live run to completion
476
+ flash log <run-id> # reward/loss trend + worker console/error logs
477
+ flash log <run-id> --follow # stream a live run to completion
477
478
  flash runs # list your runs and their state/cost
478
479
  flash cancel <run-id> # stop a live run
479
480
  flash checkpoints <run-id> # list deployable RL checkpoints
@@ -110,6 +110,14 @@ def _validate_checkpoint_step(step: int | float) -> int:
110
110
  return int(step)
111
111
 
112
112
 
113
+ def _validate_chat_messages(messages: list[dict]) -> None:
114
+ if not isinstance(messages, list):
115
+ raise ClientError("chat messages must be a list")
116
+ for index, message in enumerate(messages):
117
+ if not isinstance(message, dict):
118
+ raise ClientError(f"chat messages[{index}] must be an object")
119
+
120
+
113
121
  class ApiClient:
114
122
  def __init__(
115
123
  self,
@@ -225,14 +233,7 @@ class ApiClient:
225
233
  return self._request("GET", f"/v1/runs/{run_id}/logs?offset={int(offset)}")
226
234
 
227
235
  def get_worker_output(self, run_id: str) -> dict[str, str]:
228
- # Tolerate servers that predate /worker: FastAPI returns bare 404 "Not Found" for unknown
229
- # paths; real run-not-found 404s carry "unknown run_id: ..." so we can distinguish them.
230
- try:
231
- return self._request("GET", f"/v1/runs/{run_id}/worker").get("worker", {})
232
- except ApiError as exc:
233
- if exc.status == 404 and str(exc).strip().lower() == "not found":
234
- return {}
235
- raise
236
+ return self._request("GET", f"/v1/runs/{run_id}/worker").get("worker", {})
236
237
 
237
238
  def cancel_run(self, run_id: str) -> dict:
238
239
  return self._request("POST", f"/v1/runs/{run_id}/cancel")
@@ -289,6 +290,7 @@ class ApiClient:
289
290
  temperature: float = 0.0,
290
291
  max_tokens: int = 512,
291
292
  ) -> dict:
293
+ _validate_chat_messages(messages)
292
294
  return self._request(
293
295
  "POST",
294
296
  f"/v1/runs/{run_id}/chat",
@@ -303,6 +305,7 @@ class ApiClient:
303
305
  temperature: float = 0.0,
304
306
  max_tokens: int = 512,
305
307
  ) -> Iterator[str]:
308
+ _validate_chat_messages(messages)
306
309
  headers = {"Content-Type": "application/json"}
307
310
  if self.api_key:
308
311
  headers["Authorization"] = f"Bearer {self.api_key}"
@@ -7,6 +7,8 @@ from flash.catalog import MODELS
7
7
  from flash.providers.base import GPU_INFO, GpuClass, providers_for
8
8
 
9
9
  GPU_COMPUTE_TFLOPS: dict[str, float] = {
10
+ # A10: 125 TFLOPS dense bf16 tensor (NVIDIA spec); Lambda-only 24 GB class, else defaults to 100.
11
+ "A10": 125.0,
10
12
  "RTX 4090": 165.0,
11
13
  "RTX 5090": 210.0,
12
14
  "A100 PCIe": 312.0,
@@ -1032,10 +1032,11 @@ class FreesoloEnvironment(BaseEnvironment):
1032
1032
  episode_turns: int | None = int(self._env.max_episode_turns(task))
1033
1033
  except Exception:
1034
1034
  episode_turns = None
1035
+ messages = [dict(message) for message in prompt]
1035
1036
  return {
1036
1037
  "task": task,
1037
- "prompt": [dict(message) for message in prompt],
1038
- "messages": [dict(message) for message in prompt],
1038
+ "prompt": prompt,
1039
+ "messages": messages,
1039
1040
  "turns": [],
1040
1041
  "done": False,
1041
1042
  "response_text": "",
@@ -59,6 +59,66 @@ def _gpu_rate(gpu_type: str) -> float:
59
59
  return 0.80
60
60
 
61
61
 
62
+ def charge_usd_for_spec(spec, *, steps: int | None = None, fallback: float = 0.0) -> float:
63
+ """The customer charge for a run: the flash.cost estimate (training-only steps x sec/step x $/hr).
64
+
65
+ This is the price the run was QUOTED at submit. ``steps=None`` prices the spec's planned steps
66
+ (a completed run is charged exactly its quote); pass the actual steps that ran to re-price a
67
+ CANCELLED run at how far it got. Returns ``fallback`` if the spec can't be priced (so a charge is
68
+ never blocked by a pricing failure)."""
69
+ try:
70
+ from flash.cost.analytical import estimate_cost
71
+ from flash.cost.spec import estimate_for_spec, runconfig_from_spec
72
+
73
+ if steps is None:
74
+ return float(estimate_for_spec(spec).total_usd)
75
+ n = max(0, int(steps))
76
+ if n == 0:
77
+ return 0.0 # cancelled before any training step -> nothing to charge
78
+ from dataclasses import replace
79
+
80
+ cfg = runconfig_from_spec(spec)
81
+ planned = int(cfg.steps or 0)
82
+ if planned > 0:
83
+ n = min(n, planned)
84
+ # SFT is priced from train_tokens (not steps), so lowering steps ALONE wouldn't prorate a
85
+ # cancel -- estimate_cost would still charge the full-run token estimate. Scale the token
86
+ # count to the fraction of steps that ran so a mid-training SFT cancel is charged its share,
87
+ # mirroring GRPO's steps-based proration.
88
+ if not cfg.is_grpo and cfg.train_tokens and planned > 0:
89
+ scaled_tokens = max(1, int(cfg.train_tokens * n / planned))
90
+ cfg = replace(cfg, steps=n, train_tokens=scaled_tokens)
91
+ else:
92
+ cfg = replace(cfg, steps=n)
93
+ return float(estimate_cost(cfg).total_usd)
94
+ except Exception:
95
+ return float(fallback)
96
+
97
+
98
+ # Heartbeat stages that mean the worker has entered training (GPU work underway). The per-step
99
+ # `step` field is 1-indexed and only appears once a step COMPLETES, so the expensive first step (a
100
+ # GRPO rollout can be ~17 min) streams one of these stages with NO step yet -- still real GPU time.
101
+ _TRAINING_STAGES = frozenset({"rl_step", "sft_step"})
102
+
103
+
104
+ def actual_steps_run(status: RunStatus) -> int:
105
+ """How many optimizer steps to bill a (cancelled) run for.
106
+
107
+ The worker streams a per-step heartbeat whose ``step`` field is the last COMPLETED optimizer step
108
+ (1-indexed; the last one we persisted is the furthest it reached). Cancelled after N steps -> N.
109
+ The first step reports no ``step`` until it completes, so a cancel mid-first-step would look like
110
+ 0 steps despite real GPU time -- we floor to 1 whenever a training-stage heartbeat is present.
111
+ Returns 0 only when no training heartbeat was seen (cancelled during cold-start/setup) -> $0."""
112
+ hb = status.last_heartbeat if isinstance(status.last_heartbeat, dict) else {}
113
+ step = hb.get("step")
114
+ if isinstance(step, (int, float)) and step > 0:
115
+ return int(step)
116
+ # Training started (rl_step/sft_step) but no completed step yet -> mid-first-step -> bill 1.
117
+ if hb.get("stage") in _TRAINING_STAGES:
118
+ return 1
119
+ return 0
120
+
121
+
62
122
  @dataclass
63
123
  class RunStatus:
64
124
  run_id: str
@@ -74,9 +134,9 @@ class RunStatus:
74
134
  remote: dict | None = None
75
135
  # Realized provider cost (COGS), pulled from the provider's billing API after the run
76
136
  # finishes by the reconciliation job (flash/server/reconcile.py) and reported to the
77
- # freesolo backend for estimator accuracy. Distinct from ``cost_usd`` (the wall x $/hr
78
- # PROJECTION); ``reconciled_at`` marks that the realized pull has happened so it isn't
79
- # re-pulled. Both stay None for un-reconciled / pre-instrumentation runs.
137
+ # freesolo backend for estimator accuracy. Distinct from ``cost_usd`` (the flash.cost ESTIMATE
138
+ # we charge the customer); ``reconciled_at`` marks that the realized pull has happened so it
139
+ # isn't re-pulled. Both stay None for un-reconciled / pre-instrumentation runs.
80
140
  realized_cost_usd: float | None = None
81
141
  reconciled_at: float | None = None
82
142
  # Stamped ONCE on first terminal transition; survives later updated_at bumps from deploy/reconcile.
@@ -234,7 +294,9 @@ _WEIGHT_CACHE_PEAK_FACTOR = 2.0
234
294
  def _fits_weight_cache(info: ModelInfo) -> bool:
235
295
  """Whether the model's peak download footprint fits the shared weight-cache volume."""
236
296
  if not info.params_b:
237
- return True # unknown size -> keep the (attach) default; curated catalog models always set it
297
+ return (
298
+ True # unknown size -> keep the (attach) default; curated catalog models always set it
299
+ )
238
300
  download_gb = info.params_b * 2.0 # bf16: 2 bytes/param (mirrors cost.facts.download_weight_gb)
239
301
  return _WEIGHT_CACHE_PEAK_FACTOR * download_gb <= WEIGHT_CACHE_VOLUME_GB
240
302