freesolo-flash-dev 0.2.58__tar.gz → 1.0.1__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 (341) hide show
  1. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/PKG-INFO +1 -1
  2. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/catalog.py +25 -6
  3. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/commands.py +17 -13
  4. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/client/http.py +2 -2
  5. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cost/analytical.py +58 -12
  6. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/opd_vllm.py +8 -1
  7. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/loader.py +1 -1
  8. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/runner/__init__.py +2 -2
  9. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/schema/__init__.py +9 -1
  10. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/serve/deploy.py +50 -11
  11. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/serve/export.py +147 -4
  12. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/_deps.py +9 -13
  13. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/repo_cleanup.py +23 -0
  14. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/routes/runs.py +18 -3
  15. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/pyproject.toml +2 -2
  16. freesolo_flash_dev-1.0.1/tests/test_accounting_coverage.py +76 -0
  17. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_catalog_consistency.py +30 -8
  18. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_commands.py +21 -1
  19. freesolo_flash_dev-1.0.1/tests/test_cli_commands_coverage.py +213 -0
  20. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_estimate.py +24 -0
  21. freesolo_flash_dev-1.0.1/tests/test_cli_main_coverage.py +19 -0
  22. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_client.py +2 -2
  23. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_cache_evict.py +10 -2
  24. freesolo_flash_dev-1.0.1/tests/test_env_pull_coverage.py +175 -0
  25. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_export.py +363 -0
  26. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_jobs.py +7 -3
  27. freesolo_flash_dev-1.0.1/tests/test_lora_rank_coverage.py +163 -0
  28. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_opd.py +45 -0
  29. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_opd_vllm.py +67 -0
  30. freesolo_flash_dev-1.0.1/tests/test_packing_coverage.py +149 -0
  31. freesolo_flash_dev-1.0.1/tests/test_provider_preflight_coverage.py +61 -0
  32. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_serve.py +118 -3
  33. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_server_api.py +120 -7
  34. freesolo_flash_dev-1.0.1/tests/test_server_app_coverage.py +192 -0
  35. freesolo_flash_dev-1.0.1/tests/test_server_checkpoints_coverage.py +77 -0
  36. freesolo_flash_dev-1.0.1/tests/test_server_locks_coverage.py +55 -0
  37. freesolo_flash_dev-1.0.1/tests/test_serving_context_preflight.py +39 -0
  38. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_serving_contract.py +131 -6
  39. freesolo_flash_dev-1.0.1/tests/test_serving_pricing_coverage.py +21 -0
  40. freesolo_flash_dev-1.0.1/tests/test_serving_schema_coverage.py +203 -0
  41. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_train_context_preflight.py +6 -6
  42. freesolo_flash_dev-1.0.1/tests/test_vast_provider_coverage.py +131 -0
  43. freesolo_flash_dev-1.0.1/tests/test_wandb_log_coverage.py +109 -0
  44. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_warmstart_cross_repo.py +2 -2
  45. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/uv.lock +1 -1
  46. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.claude/skills/verify/SKILL.md +0 -0
  47. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.dockerignore +0 -0
  48. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.env.example +0 -0
  49. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/auto-rebake.yml +0 -0
  50. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/bake-kernel-cache.yml +0 -0
  51. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/ci.yml +0 -0
  52. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/main-source-guard.yml +0 -0
  53. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/notify-tests-repo.yml +0 -0
  54. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/publish-dev.yml +0 -0
  55. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/publish-image.yml +0 -0
  56. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/publish.yml +0 -0
  57. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/version-parity.yml +0 -0
  58. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/worker-image.yml +0 -0
  59. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.gitignore +0 -0
  60. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/Dockerfile +0 -0
  61. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/Dockerfile.worker +0 -0
  62. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/LICENSE +0 -0
  63. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/README.md +0 -0
  64. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/build/kernel_cache/.gitignore +0 -0
  65. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/build/kernel_cache/.keep +0 -0
  66. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/Dockerfile.kernelcache +0 -0
  67. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/Dockerfile.kernelcache.relayer +0 -0
  68. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/bake_kernel_cache.py +0 -0
  69. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/bake_pod_entry.py +0 -0
  70. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/kernel_fingerprint.py +0 -0
  71. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/make_rp_handler.py +0 -0
  72. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/__init__.py +0 -0
  73. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/_channel.py +0 -0
  74. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/_fileio.py +0 -0
  75. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/_logging.py +0 -0
  76. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/_update_check.py +0 -0
  77. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/adapter_artifacts.py +0 -0
  78. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/__init__.py +0 -0
  79. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/__main__.py +0 -0
  80. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/_tty.py +0 -0
  81. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/env_setup.py +0 -0
  82. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/envpush.py +0 -0
  83. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/render.py +0 -0
  84. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/training_doc.py +0 -0
  85. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/client/__init__.py +0 -0
  86. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/client/config.py +0 -0
  87. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/client/runtime_secrets.py +0 -0
  88. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/client/specs.py +0 -0
  89. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cost/__init__.py +0 -0
  90. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cost/facts.py +0 -0
  91. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cost/spec.py +0 -0
  92. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cost/types.py +0 -0
  93. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/diagnostics.py +0 -0
  94. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/__init__.py +0 -0
  95. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/accounting.py +0 -0
  96. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/chalk_kernels.py +0 -0
  97. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/multiturn_rollout.py +0 -0
  98. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/recipe.py +0 -0
  99. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/steps.py +0 -0
  100. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/structured_outputs.py +0 -0
  101. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/vram.py +0 -0
  102. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/__init__.py +0 -0
  103. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/__main__.py +0 -0
  104. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/_pkg.py +0 -0
  105. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/adapter.py +0 -0
  106. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/decoding.py +0 -0
  107. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/finalize.py +0 -0
  108. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/gpu_setup.py +0 -0
  109. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/grpo.py +0 -0
  110. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/heartbeat.py +0 -0
  111. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/hf.py +0 -0
  112. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/kernel_warmup.py +0 -0
  113. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/lora.py +0 -0
  114. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/opd.py +0 -0
  115. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/opd_gkd.py +0 -0
  116. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/packing.py +0 -0
  117. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/__init__.py +0 -0
  118. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/attn.py +0 -0
  119. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/diagnostics.py +0 -0
  120. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/lifecycle.py +0 -0
  121. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/liger.py +0 -0
  122. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/loraplus.py +0 -0
  123. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/memory.py +0 -0
  124. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/rl.py +0 -0
  125. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/rng.py +0 -0
  126. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/sft.py +0 -0
  127. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/teacher.py +0 -0
  128. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/tokenizer_align.py +0 -0
  129. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/wandb_log.py +0 -0
  130. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker_entrypoint.py +0 -0
  131. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/__init__.py +0 -0
  132. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/adapter.py +0 -0
  133. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/archive.py +0 -0
  134. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/archive_policy.py +0 -0
  135. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/base.py +0 -0
  136. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/pull.py +0 -0
  137. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/registry.py +0 -0
  138. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/lora_rank.py +0 -0
  139. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/opd_retry_contract.py +0 -0
  140. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/__init__.py +0 -0
  141. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_auth.py +0 -0
  142. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_deadline.py +0 -0
  143. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_hf_artifacts.py +0 -0
  144. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_hf_retry.py +0 -0
  145. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_http.py +0 -0
  146. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_instance.py +0 -0
  147. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_instance_bootstrap.py +0 -0
  148. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_instance_poll.py +0 -0
  149. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_instance_provider.py +0 -0
  150. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_poll.py +0 -0
  151. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_worker.py +0 -0
  152. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/allocator.py +0 -0
  153. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/base.py +0 -0
  154. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/__init__.py +0 -0
  155. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/api.py +0 -0
  156. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/auth.py +0 -0
  157. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/gpus.py +0 -0
  158. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  159. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  160. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/preflight.py +0 -0
  161. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/pricing.py +0 -0
  162. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/preflight.py +0 -0
  163. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/realized.py +0 -0
  164. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/__init__.py +0 -0
  165. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/api.py +0 -0
  166. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/auth.py +0 -0
  167. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/cost.py +0 -0
  168. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/gpus.py +0 -0
  169. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/jobs.py +0 -0
  170. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/keys.py +0 -0
  171. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/preflight.py +0 -0
  172. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/preload.py +0 -0
  173. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/pricing.py +0 -0
  174. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/slots.py +0 -0
  175. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/train/__init__.py +0 -0
  176. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/train/endpoints.py +0 -0
  177. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/__init__.py +0 -0
  178. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/api.py +0 -0
  179. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/auth.py +0 -0
  180. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/jobs/__init__.py +0 -0
  181. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/jobs/builders.py +0 -0
  182. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/preflight.py +0 -0
  183. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/pricing.py +0 -0
  184. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/py.typed +0 -0
  185. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/runner/checkpoints.py +0 -0
  186. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/runner/deploy.py +0 -0
  187. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/runner/lifecycle.py +0 -0
  188. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/runner/verified_revisions.py +0 -0
  189. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/schema/fields.py +0 -0
  190. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/serve/__init__.py +0 -0
  191. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/serve/pricing.py +0 -0
  192. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/serve/urls.py +0 -0
  193. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/__init__.py +0 -0
  194. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/__main__.py +0 -0
  195. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/_internal_client.py +0 -0
  196. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/_locks.py +0 -0
  197. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/_runtime.py +0 -0
  198. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/app.py +0 -0
  199. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/auth.py +0 -0
  200. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/billing.py +0 -0
  201. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/billing_retry.py +0 -0
  202. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/checkpoints.py +0 -0
  203. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/db.py +0 -0
  204. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/environment_registry.py +0 -0
  205. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/envs.py +0 -0
  206. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/reconcile.py +0 -0
  207. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/routes/__init__.py +0 -0
  208. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/routes/envs.py +0 -0
  209. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/routes/meta.py +0 -0
  210. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/routes/serving.py +0 -0
  211. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/run_registry.py +0 -0
  212. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/spec.py +0 -0
  213. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/infisical-entrypoint.sh +0 -0
  214. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/scripts/build_dev_dist.py +0 -0
  215. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/__init__.py +0 -0
  216. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/_helpers/__init__.py +0 -0
  217. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/_helpers/runner.py +0 -0
  218. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/_helpers/specs.py +0 -0
  219. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/_helpers/vast.py +0 -0
  220. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/conftest.py +0 -0
  221. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/fixtures/math_eval.jsonl +0 -0
  222. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/fixtures/math_train.jsonl +0 -0
  223. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/live/__init__.py +0 -0
  224. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/live/conftest.py +0 -0
  225. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/live/test_lambda_live.py +0 -0
  226. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/live/test_runpod_live.py +0 -0
  227. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/live/test_vast_live.py +0 -0
  228. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_agent_flash_cli_contract.py +0 -0
  229. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_algorithms.py +0 -0
  230. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_allocator.py +0 -0
  231. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_b200_rollout_opt.py +0 -0
  232. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_backend_jobspec_contract.py +0 -0
  233. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_base_model_provenance.py +0 -0
  234. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_billing_retry.py +0 -0
  235. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cancel_remote.py +0 -0
  236. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_chalk_kernels.py +0 -0
  237. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_charge_pricing.py +0 -0
  238. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_checkpoints.py +0 -0
  239. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_errors.py +0 -0
  240. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_help.py +0 -0
  241. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_managed.py +0 -0
  242. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_render_coverage.py +0 -0
  243. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_render_theme.py +0 -0
  244. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_client_server_integration.py +0 -0
  245. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_config_overrides.py +0 -0
  246. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_controlled_experiment_repairs.py +0 -0
  247. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_analytical.py +0 -0
  248. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_equation.py +0 -0
  249. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_estimate.py +0 -0
  250. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_hardware.py +0 -0
  251. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_models.py +0 -0
  252. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_rewards.py +0 -0
  253. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_dev_channel.py +0 -0
  254. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_disk_gb.py +0 -0
  255. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_endpoint_name.py +0 -0
  256. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_delete.py +0 -0
  257. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_download.py +0 -0
  258. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_download_api.py +0 -0
  259. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_no_install.py +0 -0
  260. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_publish.py +0 -0
  261. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_pull.py +0 -0
  262. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_pull_loader_coverage.py +0 -0
  263. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_pull_managed_control_plane.py +0 -0
  264. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_push.py +0 -0
  265. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_rate_limit_resolve.py +0 -0
  266. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_envs_coverage.py +0 -0
  267. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_flash_mvp.py +0 -0
  268. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_flash_worker.py +0 -0
  269. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_flashinfer_cache_dirs.py +0 -0
  270. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_github_urlopen_retry.py +0 -0
  271. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_gpus.py +0 -0
  272. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_grpo_mask_aware.py +0 -0
  273. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_grpo_params.py +0 -0
  274. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_grpo_sleep_gate.py +0 -0
  275. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_health_liveness.py +0 -0
  276. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_hf_retry.py +0 -0
  277. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_idle_endpoint_reaper.py +0 -0
  278. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_instance_bootstrap_coverage.py +0 -0
  279. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_internal_client.py +0 -0
  280. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_kernel_cache.py +0 -0
  281. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_kernel_fingerprint.py +0 -0
  282. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_kv_util.py +0 -0
  283. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_lambda_api_coverage.py +0 -0
  284. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_lambda_runner.py +0 -0
  285. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_logging.py +0 -0
  286. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_login_perms.py +0 -0
  287. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_lora_rank_preflight.py +0 -0
  288. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_managed_hf_repo.py +0 -0
  289. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_metrics_schema_agent_contract.py +0 -0
  290. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_mig_guard.py +0 -0
  291. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_multiturn_rollout.py +0 -0
  292. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_multiturn_rollout_coverage.py +0 -0
  293. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_multiturn_rollout_records.py +0 -0
  294. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_multiturn_rollout_request_policy.py +0 -0
  295. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_oom_escalate_gpu.py +0 -0
  296. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_opd_full_state_resume.py +0 -0
  297. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_opd_resume_safety.py +0 -0
  298. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_open_model_policy.py +0 -0
  299. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_orchestrator_flash.py +0 -0
  300. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_packing.py +0 -0
  301. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_poll_helpers.py +0 -0
  302. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_preflight.py +0 -0
  303. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_provider_routing.py +0 -0
  304. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_provider_teardown_robustness.py +0 -0
  305. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_providers_symmetry.py +0 -0
  306. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_realized_cost.py +0 -0
  307. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_repo_cleanup.py +0 -0
  308. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_resolve_params_b.py +0 -0
  309. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_resume_on_retry.py +0 -0
  310. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_runmgmt.py +0 -0
  311. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_runpod_api_delete.py +0 -0
  312. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_runpod_key_fingerprint.py +0 -0
  313. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_runpod_key_waterfall.py +0 -0
  314. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_runpod_slots.py +0 -0
  315. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_server_billing.py +0 -0
  316. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_server_db.py +0 -0
  317. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_server_envs_coverage.py +0 -0
  318. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_sft_example_selection.py +0 -0
  319. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_sft_gc_off.py +0 -0
  320. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_sft_max_context.py +0 -0
  321. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_spec_and_validation.py +0 -0
  322. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_structured_outputs.py +0 -0
  323. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_thinking_config.py +0 -0
  324. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_training_controls.py +0 -0
  325. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_update_check.py +0 -0
  326. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_vast_api.py +0 -0
  327. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_vast_api_coverage.py +0 -0
  328. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_vast_offers.py +0 -0
  329. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_vast_runner.py +0 -0
  330. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_verified_revisions.py +0 -0
  331. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_verifiers.py +0 -0
  332. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_version.py +0 -0
  333. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  334. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_wandb_naming.py +0 -0
  335. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_weight_cache.py +0 -0
  336. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_dryrun.py +0 -0
  337. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_hardexit.py +0 -0
  338. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_image.py +0 -0
  339. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_init_heartbeat.py +0 -0
  340. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_stack.py +0 -0
  341. {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_thinking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 0.2.58
3
+ Version: 1.0.1
4
4
  Summary: Flash — managed LoRA post-training (SFT/GRPO/OPD) 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
@@ -111,6 +111,8 @@ class ModelInfo:
111
111
  # multimodal-nested config, so the curated values are what actually engage the gate.
112
112
  num_layers: int = 0
113
113
  hidden_size: int = 0
114
+ # vllm hybrid-mamba cache block size in tokens. 0 means the model has no catalogued mamba floor.
115
+ mamba_block_size: int = 0
114
116
 
115
117
  @property
116
118
  def is_moe(self) -> bool:
@@ -124,6 +126,8 @@ class ModelInfo:
124
126
 
125
127
  def to_dict(self) -> dict[str, Any]:
126
128
  data = asdict(self)
129
+ if not data["mamba_block_size"]:
130
+ del data["mamba_block_size"]
127
131
  serving = data["serving"]
128
132
  if serving is None:
129
133
  del data["serving"]
@@ -170,7 +174,7 @@ MODELS: dict[str, ModelInfo] = {
170
174
  serve_model_id=SERVING_FP8_MODEL_REPOS["openbmb/MiniCPM5-1B"],
171
175
  max_loras=16,
172
176
  max_lora_rank=128,
173
- max_model_len=8192,
177
+ max_model_len=32768,
174
178
  ),
175
179
  thinking="hybrid",
176
180
  notes="On-device class SLM (131k ctx); standard Llama architecture.",
@@ -189,7 +193,7 @@ MODELS: dict[str, ModelInfo] = {
189
193
  serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-0.8B"],
190
194
  max_loras=16,
191
195
  max_lora_rank=128,
192
- max_model_len=8192,
196
+ max_model_len=32768,
193
197
  ),
194
198
  thinking="hybrid",
195
199
  notes="Smallest Qwen3.5; cheap smoke/dev runs with the modern arch.",
@@ -208,7 +212,7 @@ MODELS: dict[str, ModelInfo] = {
208
212
  serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-2B"],
209
213
  max_loras=16,
210
214
  max_lora_rank=128,
211
- max_model_len=8192,
215
+ max_model_len=32768,
212
216
  ),
213
217
  thinking="hybrid",
214
218
  ),
@@ -226,7 +230,7 @@ MODELS: dict[str, ModelInfo] = {
226
230
  serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-4B"],
227
231
  max_loras=16,
228
232
  max_lora_rank=64,
229
- max_model_len=8192,
233
+ max_model_len=32768,
230
234
  max_num_seqs=8,
231
235
  gpu_memory_utilization=0.98,
232
236
  ),
@@ -247,11 +251,11 @@ MODELS: dict[str, ModelInfo] = {
247
251
  quant="bf16",
248
252
  recommended_gpu="A100 PCIe",
249
253
  serving=ServingCapacity(
250
- gpu="L4",
254
+ gpu="H100",
251
255
  serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-9B"],
252
256
  max_loras=16,
253
257
  max_lora_rank=64,
254
- max_model_len=8192,
258
+ max_model_len=32768,
255
259
  max_num_seqs=8,
256
260
  gpu_memory_utilization=0.98,
257
261
  ),
@@ -271,6 +275,10 @@ MODELS: dict[str, ModelInfo] = {
271
275
  # layers x 2048 hidden (hybrid GatedDeltaNet + full-attention, 256 experts / 8 active).
272
276
  num_layers=40,
273
277
  hidden_size=2048,
278
+ # vllm 0.19.1 model_executor/models/config.py derives this from the qwen config via
279
+ # mamba_utils.py: the 1,097,728-byte gdn state page needs 1072 fp8 attention tokens
280
+ # after the 16-token backend alignment applied by hybridattentionmambamodelconfig.
281
+ mamba_block_size=1072,
274
282
  vocab_size=248_320,
275
283
  algos=ALGORITHMS,
276
284
  min_vram_gb=141,
@@ -312,6 +320,17 @@ MODELS: dict[str, ModelInfo] = {
312
320
  }
313
321
 
314
322
 
323
+ def opd_mamba_batched_token_floor(
324
+ model_id: str, seq_cap: int, max_num_seqs: int | None
325
+ ) -> int | None:
326
+ """return a mamba floor only when vllm's derived opd scheduler budget is too small."""
327
+ info = MODELS.get(model_id)
328
+ if info is None or info.mamba_block_size <= 0 or max_num_seqs is None:
329
+ return None
330
+ derived_budget = int(max_num_seqs) * int(seq_cap)
331
+ return info.mamba_block_size if derived_budget < info.mamba_block_size else None
332
+
333
+
315
334
  def list_models() -> list[ModelInfo]:
316
335
  return sorted(MODELS.values(), key=lambda m: (m.min_vram_gb, m.id))
317
336
 
@@ -214,6 +214,7 @@ def _cmd_train_cost(args) -> int:
214
214
  Catalog-only and deterministic. SFT cost never imports the environment; it requires a positive
215
215
  [train].max_examples row count instead of guessing or locally counting a dataset."""
216
216
  from flash.cost import estimate_cost
217
+ from flash.lora_rank import preflight_train_context_within_serving
217
218
 
218
219
  spec = spec_from_file(
219
220
  args.config,
@@ -221,6 +222,7 @@ def _cmd_train_cost(args) -> int:
221
222
  overrides=args.overrides,
222
223
  extra_configs=args.extra_configs,
223
224
  )
225
+ preflight_train_context_within_serving(spec)
224
226
  if spec.train.init_from_adapter:
225
227
  # --cost is offline/catalog-only and cannot read the source adapter, so the rank stays at the
226
228
  # local default. Warm starts train and are priced at the SOURCE adapter's authoritative rank
@@ -302,23 +304,24 @@ def cmd_train(args) -> int:
302
304
  )
303
305
  payload = spec_payload(spec, authored_train_keys=authored_train_keys)
304
306
  client = client_from_config()
307
+ client_train_schema = {
308
+ "version": __version__,
309
+ "fields": train_schema_metadata(),
310
+ "authored_keys": sorted(authored_train_keys),
311
+ }
312
+ runtime_secrets = (
313
+ runtime_secrets_from_local_env(args.config, keys=spec.environment.secrets) or None
314
+ )
305
315
  if args.dry_run:
306
- # Dry-run is a faithful server-side preview: the control plane runs the SAME config
307
- # validation and warm-start/serving preflights it would at real submit (serving rank/context
308
- # caps, the continued warm-start rank match, cost quote) and records a state=dry_run run, but
309
- # allocates no GPU and charges nothing. Local runtime secrets aren't sent — the server
310
- # relaxes the required-[environment].secrets check for a preview, so `--dry-run` works before
311
- # prod secrets are wired up. A rejection surfaces as the server's `error: ...` (exit 1),
312
- # exactly as a real submit would.
316
+ # dry-run is a faithful server-side preview: it sends the same declared secrets and runs the
317
+ # same config, warm-start, serving, and cost preflights as a real submit, but allocates no gpu
318
+ # and charges nothing. a rejection surfaces as the server's error with exit status 1.
313
319
  try:
314
320
  status = client.create_run(
315
321
  payload,
322
+ runtime_secrets=runtime_secrets,
316
323
  dry_run=True,
317
- client_train_schema={
318
- "version": __version__,
319
- "fields": train_schema_metadata(),
320
- "authored_keys": sorted(authored_train_keys),
321
- },
324
+ client_train_schema=client_train_schema,
322
325
  )
323
326
  except ApiError as exc:
324
327
  detail = _legacy_train_key_rejection_detail(exc, authored_train_keys)
@@ -338,7 +341,8 @@ def cmd_train(args) -> int:
338
341
  return 0
339
342
  status = client.create_run(
340
343
  payload,
341
- runtime_secrets=runtime_secrets_from_local_env(args.config, keys=spec.environment.secrets),
344
+ runtime_secrets=runtime_secrets,
345
+ client_train_schema=client_train_schema,
342
346
  )
343
347
  run_id = status["run_id"]
344
348
  logger.info(
@@ -343,8 +343,8 @@ class ApiClient:
343
343
  # Server-side preview: runs the same validation/preflights as a real submit and records a
344
344
  # state=dry_run run, but allocates no GPU and charges nothing. Returns that status.
345
345
  body["dry_run"] = True
346
- if client_train_schema is not None:
347
- body["client_train_schema"] = client_train_schema
346
+ if client_train_schema is not None:
347
+ body["client_train_schema"] = client_train_schema
348
348
  return self._request("POST", "/v1/runs", body=body)
349
349
 
350
350
  def list_runs(self) -> list[dict]:
@@ -40,6 +40,22 @@ MFU_TRAIN = 0.35 # GRPO policy/reference update
40
40
  MFU_SFT_TRAIN = 0.25 # SFT fwd/bwd (smaller effective batch, long sequences)
41
41
  MFU_DECODE = 0.12 # batched vLLM rollout (decode is memory-bandwidth-bound)
42
42
 
43
+ # --- MoE (mixture-of-experts) per-step correction ----------------------------------------------
44
+ # For an MoE model (active params << total, e.g. Qwen3.6-35B-A3B: ~3B active / 35B total) the wall
45
+ # time per step is NOT the tiny active-param FLOPs the dense model predicts. Routing, all-expert
46
+ # coordination, and grouped-GEMM under-utilization at small batch make the step scale with TOTAL
47
+ # params. Pricing 35B-A3B on active params under-quoted real RunPod runs by 13-27x (SFT ~1.2s vs
48
+ # ~42s realized/step; GRPO ~1.4s vs ~24s). So an MoE prices on TOTAL params at a reduced effective
49
+ # MFU + a per-step overhead + a one-time compile. DENSE models (active == total) are unaffected --
50
+ # they keep the original active-param path and the MFUs above.
51
+ MFU_SFT_TRAIN_MOE = 0.10 # MoE SFT fwd/bwd priced on total params
52
+ MFU_TRAIN_MOE = 0.09 # MoE GRPO/OPD policy update priced on total params
53
+ MOE_STEP_OVERHEAD_S = 2.0 # routing/dispatch/kernel-launch overhead an MoE pays every step
54
+ # One-time kernel/graph compile amortized into the first training step (fused Triton kernels;
55
+ # + vLLM cudagraph capture for rollout methods). MoE-only; the prior model omitted it entirely.
56
+ COMPILE_MOE_SFT_S = 35.0
57
+ COMPILE_MOE_ROLLOUT_S = 48.0 # GRPO / OPD (adds vLLM cudagraph capture)
58
+
43
59
  # Reward grading is CONCURRENT: a step's completions score in parallel slots, so the reward
44
60
  # wall is ceil(completions / slots) waves x latency, not completions x latency.
45
61
  REWARD_CONCURRENCY = 16.0
@@ -116,13 +132,32 @@ def required_save_overhead_seconds(config: RunConfig) -> float:
116
132
  return len(n.save_at_steps) * per_save
117
133
 
118
134
 
135
+ def _is_moe(model_id: str) -> bool:
136
+ """True when the model routes each token through a subset of experts (active < total params)."""
137
+ return active_params_b(model_id) < total_params_b(model_id)
138
+
139
+
140
+ def compile_seconds(config: RunConfig, gpu: str) -> float:
141
+ """One-time kernel/graph compile folded into the first training step. MoE-only; 0 for dense
142
+ (whose original timing did not model it). Larger for rollout methods (add vLLM cudagraph
143
+ capture)."""
144
+ _ = gpu
145
+ if not _is_moe(config.model_id):
146
+ return 0.0
147
+ return COMPILE_MOE_SFT_S if config.method == "sft" else COMPILE_MOE_ROLLOUT_S
148
+
149
+
119
150
  def seconds_per_step(config: RunConfig, gpu: str) -> float:
120
151
  """Steady-state wall time for one optimizer step on ``gpu``."""
121
152
  n = config.normalized()
122
- # Per-token FLOPs scale with the ACTIVE params (an MoE token routes through only a subset of
123
- # experts); for a dense model this equals the total. Memory/size terms below keep total_params_b.
124
- params = active_params_b(n.model_id) * 1e9
125
153
  peak = gpu_tflops(gpu) * 1e12 # FLOP/s
154
+ # An MoE's per-step wall scales with TOTAL params (routing + all-expert coordination + grouped
155
+ # GEMM under-utilization), not the tiny active-param FLOPs; dense models keep active (== total).
156
+ moe = _is_moe(n.model_id)
157
+ params = (total_params_b(n.model_id) if moe else active_params_b(n.model_id)) * 1e9
158
+ overhead = MOE_STEP_OVERHEAD_S if moe else 0.0
159
+ sft_mfu = MFU_SFT_TRAIN_MOE if moe else MFU_SFT_TRAIN
160
+ update_mfu = MFU_TRAIN_MOE if moe else MFU_TRAIN
126
161
 
127
162
  if n.is_opd:
128
163
  # OPD step = on-policy student rollout (like GRPO) + remote teacher scoring (CONCURRENT
@@ -131,7 +166,7 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
131
166
  # sequence (see _opd_step_shape), not completion-only, or long-prompt opd is underquoted.
132
167
  completions, seq_tokens = _opd_step_shape(n)
133
168
  gen_s = (GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_DECODE)
134
- update_s = (OPD_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_TRAIN)
169
+ update_s = (OPD_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * update_mfu)
135
170
  teacher_lat = teacher_seconds_per_completion()
136
171
  # run_opd's primary path scores a step's completions CONCURRENTLY over Fireworks with a fan-out
137
172
  # cap of the step's OWN completion count (prompts_per_step * group_size, opd.py Phase 2), so
@@ -139,30 +174,33 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
139
174
  # latency, NOT the full serial sum (that describes only the CPU-test fallback that can't
140
175
  # batch-generate). The teacher endpoint's rate limit is the real ceiling on this fan-out.
141
176
  teacher_s = teacher_lat
142
- return gen_s + teacher_s + update_s
177
+ return overhead + gen_s + teacher_s + update_s
143
178
 
144
179
  if not n.is_grpo:
145
180
  flops = SFT_FLOPS_PER_TOKEN_PER_PARAM * params * (n.batch_size * n.seq_len)
146
- return flops / (peak * MFU_SFT_TRAIN)
181
+ return overhead + flops / (peak * sft_mfu)
147
182
 
148
183
  # GRPO step = rollout (G completions/prompt) + concurrent reward grading + policy/ref update.
149
184
  completions = n.batch_size * n.group_size
150
185
  gen_tokens = completions * n.completion_len
151
186
  gen_s = (GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM * params * gen_tokens) / (peak * MFU_DECODE)
152
- update_s = (GRPO_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * gen_tokens) / (peak * MFU_TRAIN)
187
+ update_s = (GRPO_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * gen_tokens) / (peak * update_mfu)
153
188
  latency = reward_seconds_per_completion(n.reward_seconds_per_completion)
154
189
  reward_s = (
155
190
  math.ceil(completions / REWARD_CONCURRENCY) * latency
156
191
  ) # ceil: a partial wave still costs one latency
157
- return gen_s + reward_s + update_s
192
+ return overhead + gen_s + reward_s + update_s
158
193
 
159
194
  def sft_seconds_for_tokens(config: RunConfig, gpu: str, train_tokens: float) -> float:
160
195
  """SFT steady-state wall time for an actual token count on ``gpu``."""
161
196
  n = config.normalized()
162
- params = active_params_b(n.model_id) * 1e9
197
+ # MoE prices on total params at a reduced MFU (see seconds_per_step); dense keeps active.
198
+ moe = _is_moe(n.model_id)
199
+ params = (total_params_b(n.model_id) if moe else active_params_b(n.model_id)) * 1e9
200
+ mfu = MFU_SFT_TRAIN_MOE if moe else MFU_SFT_TRAIN
163
201
  peak = gpu_tflops(gpu) * 1e12
164
202
  flops = SFT_FLOPS_PER_TOKEN_PER_PARAM * params * train_tokens
165
- return flops / (peak * MFU_SFT_TRAIN)
203
+ return flops / (peak * mfu)
166
204
 
167
205
 
168
206
  def select_gpu(config: RunConfig, *, max_wall_seconds: float = 0.0) -> tuple[str, int]:
@@ -221,6 +259,11 @@ def _notes(
221
259
  f"{len(n.save_at_steps)} synchronous required save(s) add "
222
260
  f"~{_fmt_duration(required_save_s)}"
223
261
  )
262
+ if _is_moe(n.model_id):
263
+ notes.append(
264
+ "MoE model: per-step time priced on total params (routing + all-expert coordination), "
265
+ "not just active-param FLOPs, plus a one-time kernel compile"
266
+ )
224
267
  notes.append(f"GPU sized with {vram_headroom() - 1:.0%} VRAM headroom; static GPU $/hr")
225
268
  if wall_capped:
226
269
  notes.append(
@@ -283,9 +326,12 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
283
326
  setup = setup_seconds(config)
284
327
  sps = seconds_per_step(config, gpu)
285
328
  required_save_s = required_save_overhead_seconds(config)
286
- raw_train = config.steps * sps + required_save_s
329
+ # A one-time kernel/graph compile is paid once on the first step (MoE-only; 0 for dense). It is
330
+ # training GPU time, so it belongs in the (billed) train term, not setup.
331
+ compile_s = compile_seconds(config, gpu)
332
+ raw_train = compile_s + config.steps * sps + required_save_s
287
333
  if not config.is_grpo and config.train_tokens is not None:
288
- raw_train = sft_seconds_for_tokens(config, gpu, config.train_tokens) + required_save_s
334
+ raw_train = compile_s + sft_seconds_for_tokens(config, gpu, config.train_tokens) + required_save_s
289
335
  sps = raw_train / config.steps
290
336
 
291
337
  # The cap is on total elapsed wall; setup is reported but not billed, so only training
@@ -127,7 +127,8 @@ def opd_vllm_kwargs(model_id: str, knobs: Any, seq_cap: int) -> dict[str, Any]:
127
127
 
128
128
  fp8_kv = bool(cc >= (8, 9))
129
129
  kwargs["kv_cache_dtype"] = "fp8" if fp8_kv else None
130
- kwargs["max_num_batched_tokens"] = max(8192, int(seq_cap)) if card_gb >= 140 else None
130
+ if card_gb >= 140:
131
+ kwargs["max_num_batched_tokens"] = max(8192, int(seq_cap))
131
132
 
132
133
  if card_gb > 0:
133
134
  try:
@@ -189,6 +190,12 @@ def opd_vllm_kwargs(model_id: str, knobs: Any, seq_cap: int) -> dict[str, Any]:
189
190
  kwargs["rollout_batch_size"] = rollout_concurrency
190
191
  except Exception as exc:
191
192
  print(f"[opd] vLLM memory-util sizing failed; using 0.10: {exc}")
193
+ if card_gb < 140:
194
+ from flash.catalog import opd_mamba_batched_token_floor
195
+
196
+ kwargs["max_num_batched_tokens"] = opd_mamba_batched_token_floor(
197
+ model_id, seq_cap, kwargs["max_num_seqs"]
198
+ )
192
199
  if startup_oom is not None:
193
200
  raise startup_oom
194
201
 
@@ -36,7 +36,7 @@ from flash.envs.archive_policy import (
36
36
  _DEFAULT_GITHUB_REF = "main"
37
37
  _DEFAULT_ENVIRONMENT_PATH = "environment.py"
38
38
  _DEFAULT_MANAGED_ENV_REPO = "freesolo-co/environment-hub"
39
- _CACHE_ROOT = Path(os.environ.get("FLASH_ENV_CACHE_DIR", "/tmp/flash-env-cache"))
39
+ _CACHE_ROOT = Path("/tmp/flash-env-cache")
40
40
  # bound the on-disk env cache so it cannot grow without limit (one subdir per env
41
41
  # content-sha, ~30-80 MB each). evicted LRU by dir mtime, which we bump on cache hit.
42
42
  _CACHE_MAX_ENTRIES = 32
@@ -691,7 +691,7 @@ def _prepare_init_from_adapter(
691
691
  owner_key_id: int | None = None,
692
692
  token: str | None = None,
693
693
  ) -> tuple[JobSpec, JobSpec, dict | None]:
694
- """Preserve the public request while applying source metadata only to the worker spec."""
694
+ """prepare public and worker specs with source-authoritative adapter metadata."""
695
695
  _require_supported_adapter_continuation(spec)
696
696
  ref = spec.train.init_from_adapter
697
697
  if not ref:
@@ -776,7 +776,7 @@ def _prepare_init_from_adapter(
776
776
  )
777
777
  assert metadata is not None
778
778
  identity = adapter_artifact_identity(storage, config, token, revision).to_dict()
779
- public_spec = spec
779
+ public_spec = replace(spec, train=replace(spec.train, lora_alpha=metadata.alpha))
780
780
  worker_spec = replace(
781
781
  worker_spec,
782
782
  train=replace(worker_spec.train, lora_rank=metadata.rank, lora_alpha=metadata.alpha),
@@ -546,7 +546,12 @@ def _validate_grpo(spec: JobSpec) -> None:
546
546
 
547
547
 
548
548
  def _validate_opd(spec: JobSpec) -> None:
549
- """validate that the opd context budget leaves room for a prompt."""
549
+ """validate opd-specific training constraints."""
550
+ if spec.train.kl_penalty_coef == 0.0:
551
+ raise ConfigError(
552
+ "[train] kl_penalty_coef must be > 0 for opd because it scales the gkd "
553
+ "distillation objective; omit it to use the default 1.0 or set a positive value"
554
+ )
550
555
  if spec.train.max_context_tokens:
551
556
  # Mirror run_opd's prompt-budget guard at PARSE time: a context budget that leaves no room
552
557
  # for any prompt after the completion budget is rejected here, BEFORE a paid worker is
@@ -564,6 +569,9 @@ def _validate_opd(spec: JobSpec) -> None:
564
569
  f"invalid budget fails before a GPU worker is provisioned."
565
570
  )
566
571
 
572
+ # short hybrid-mamba contexts are safe because the worker compares vllm's derived scheduler
573
+ # budget with the catalogued block size and supplies an explicit floor only when required.
574
+
567
575
 
568
576
  # Each algorithm's spec-level contract lives in ONE validator, dispatched by name so a new algorithm
569
577
  # adds a function + a map entry rather than another scattered ``if spec.algorithm == ...`` block.
@@ -26,6 +26,7 @@ READBACK_DELAY_SECONDS = 2.0
26
26
  REVISION_READY_BUDGET_SECONDS = 5 * 60.0
27
27
  ACTIVATION_READBACK_ATTEMPTS = 3
28
28
  THINKING_STRUCTURED_OUTPUTS_CAPABILITY = "thinking_structured_outputs_deferred_v1"
29
+ REVISION_PROVENANCE_CAPABILITY = "revision_provenance"
29
30
  _RETRYABLE_SMOKE_503_CODES = frozenset({"adapter_loading", "engine_unavailable"})
30
31
 
31
32
 
@@ -382,7 +383,10 @@ def deploy_adapter(
382
383
  dep.adapter_revision = revision
383
384
  checkpoint = f"{run_id}/step-{checkpoint_step}" if checkpoint_step is not None else run_id
384
385
  so_default = parse_structured_outputs(structured_outputs) if structured_outputs else None
385
- _require_serving_capabilities(thinking_structured_outputs=thinking and so_default is not None)
386
+ advertised = _require_serving_capabilities(
387
+ thinking_structured_outputs=thinking and so_default is not None
388
+ )
389
+ require_provenance = REVISION_PROVENANCE_CAPABILITY in advertised
386
390
 
387
391
  body = {
388
392
  "adapter_id": revision,
@@ -391,7 +395,9 @@ def deploy_adapter(
391
395
  "subfolder": subfolder,
392
396
  "repo_type": "dataset",
393
397
  "checkpoint": checkpoint,
394
- "status": "ready",
398
+ # NOTE: do NOT send a client-chosen "status" -- the serving backend sets the record status
399
+ # itself on registration and rejects an incoming "status" as an extra field (HTTP 422
400
+ # extra_forbidden). Sending it blocked every deploy against the deployed serving backend.
395
401
  "metadata": {
396
402
  "record_type": "revision",
397
403
  "run_id": run_id,
@@ -419,14 +425,18 @@ def deploy_adapter(
419
425
  record = _registered_adapter(revision)
420
426
  except ServingError as read_exc:
421
427
  raise exc from read_exc
422
- if record is None or not _matches_revision_identity(record, body):
428
+ if record is None or not _matches_revision_identity(
429
+ record, body, require_provenance=require_provenance
430
+ ):
423
431
  raise exc
424
432
  logger.warning(
425
433
  "adapter registration response was ambiguous; revision %s exists with matching identity",
426
434
  revision,
427
435
  )
428
436
 
429
- _wait_revision_ready(revision, subfolder, expected_identity=body)
437
+ _wait_revision_ready(
438
+ revision, subfolder, expected_identity=body, require_provenance=require_provenance
439
+ )
430
440
  if before_activate is not None:
431
441
  before_activate(revision, checkpoint)
432
442
  activation = _activate_revision(
@@ -469,9 +479,9 @@ def _registered_adapter(adapter_id: str, *, timeout_s: float | None = None) -> d
469
479
  return record
470
480
 
471
481
 
472
- def _matches_revision_identity(record: dict, expected: dict) -> bool:
473
- metadata = record.get("metadata") if isinstance(record.get("metadata"), dict) else {}
474
- expected_metadata = expected["metadata"]
482
+ def _matches_revision_identity(
483
+ record: dict, expected: dict, *, require_provenance: bool = True
484
+ ) -> bool:
475
485
  scalar_fields = (
476
486
  "adapter_id",
477
487
  "repo_id",
@@ -487,19 +497,35 @@ def _matches_revision_identity(record: dict, expected: dict) -> bool:
487
497
  return False
488
498
  if (record.get("structured_outputs") or None) != (expected.get("structured_outputs") or None):
489
499
  return False
500
+ if not require_provenance:
501
+ # backends without revision_provenance do not echo provenance metadata; the immutable
502
+ # adapter_id already pins the artifact, so this cross-check is best effort here.
503
+ return True
504
+ metadata = record.get("metadata") if isinstance(record.get("metadata"), dict) else {}
505
+ expected_metadata = expected["metadata"]
490
506
  return all(
491
507
  metadata.get(field) == expected_metadata.get(field)
492
508
  for field in ("record_type", "run_id", "checkpoint_step", "hf_revision")
493
509
  )
494
510
 
495
511
 
496
- def _require_serving_capabilities(*, thinking_structured_outputs: bool = False) -> None:
512
+ def _require_serving_capabilities(*, thinking_structured_outputs: bool = False) -> set[str]:
513
+ # SAFETY-CRITICAL capabilities the deploy correctness contract genuinely depends on: immutable
514
+ # revisions (a revision id always maps to one artifact) and an atomic alias compare-and-swap
515
+ # (the alias flip can't race). These are hard-required.
497
516
  required = {
498
517
  "immutable_adapter_revisions",
499
518
  "alias_compare_and_swap",
500
- "revision_provenance",
501
519
  }
520
+ # PREFERRED (not required): `revision_provenance` only lets the serving backend echo back the
521
+ # run/checkpoint/hf_revision metadata, which is used ONLY on the rare 5xx-during-registration
522
+ # recovery path (`_matches_revision_identity`). Hard-requiring it blocked EVERY deploy org-wide
523
+ # whenever the serving build lagged on advertising it, even though the happy path never uses it.
524
+ # So its absence is a logged warning, not a deploy-blocking error.
525
+ preferred = {REVISION_PROVENANCE_CAPABILITY}
502
526
  if thinking_structured_outputs:
527
+ # Genuinely required for this run: thinking + structured outputs needs the serving backend's
528
+ # deferred-constraint support (grammar applied after </think>) or served output is invalid.
503
529
  required.add(THINKING_STRUCTURED_OUTPUTS_CAPABILITY)
504
530
  url = f"{serving_base_url()}/healthz"
505
531
  response = _serving_request("GET", url)
@@ -524,12 +550,24 @@ def _require_serving_capabilities(*, thinking_structured_outputs: bool = False)
524
550
  f"serving_contract_unsupported: serving health check at {url} capabilities must be "
525
551
  "strings"
526
552
  )
527
- missing = sorted(required - set(capabilities))
553
+ advertised = set(capabilities)
554
+ missing = sorted(required - advertised)
528
555
  if missing:
529
556
  raise ServingError(
530
557
  "serving_contract_unsupported: serving is missing required capabilities "
531
558
  + ", ".join(missing)
532
559
  )
560
+ missing_preferred = sorted(preferred - advertised)
561
+ if missing_preferred:
562
+ # Not fatal: the happy-path deploy does not use these; only the ambiguous-registration
563
+ # recovery path degrades (best-effort identity check). Surface it so an operator can ship
564
+ # the serving build that advertises them, without blocking customers meanwhile.
565
+ logger.warning(
566
+ "serving backend does not advertise preferred capabilities %s; deploying anyway "
567
+ "(ambiguous-registration recovery will be best-effort)",
568
+ ", ".join(missing_preferred),
569
+ )
570
+ return advertised
533
571
 
534
572
 
535
573
  def _wait_revision_ready(
@@ -537,6 +575,7 @@ def _wait_revision_ready(
537
575
  subfolder: str,
538
576
  *,
539
577
  expected_identity: dict | None = None,
578
+ require_provenance: bool = True,
540
579
  budget_s: float = REVISION_READY_BUDGET_SECONDS,
541
580
  ) -> dict:
542
581
  deadline = time.monotonic() + max(0.0, float(budget_s))
@@ -566,7 +605,7 @@ def _wait_revision_ready(
566
605
  continue
567
606
  last_read_error = None
568
607
  if expected_identity is not None and not _matches_revision_identity(
569
- record, expected_identity
608
+ record, expected_identity, require_provenance=require_provenance
570
609
  ):
571
610
  raise ServingError(
572
611
  f"adapter revision {revision} resolved to a different immutable identity"