freesolo-flash-dev 1.0.2__tar.gz → 1.0.4__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 (349) hide show
  1. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.github/workflows/bake-kernel-cache.yml +5 -6
  2. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/Dockerfile.worker +1 -1
  3. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/PKG-INFO +4 -4
  4. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/catalog.py +54 -8
  5. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cli/__init__.py +5 -1
  6. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cli/commands.py +31 -20
  7. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cli/envpush.py +192 -54
  8. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cli/render.py +80 -14
  9. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cli/training_doc.py +6 -4
  10. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/client/http.py +24 -10
  11. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/multiturn_rollout.py +130 -22
  12. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/recipe.py +7 -0
  13. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/__init__.py +6 -0
  14. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/grpo.py +8 -2
  15. freesolo_flash_dev-1.0.4/flash/engine/worker/grpo_multimodal.py +274 -0
  16. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/hf.py +342 -119
  17. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/opd.py +510 -81
  18. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/opd_vllm.py +43 -7
  19. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/packing.py +42 -11
  20. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/__init__.py +4 -0
  21. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/memory.py +49 -0
  22. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/rl.py +114 -17
  23. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/sft.py +82 -18
  24. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/teacher.py +304 -1
  25. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/envs/adapter.py +3 -0
  26. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/envs/loader.py +1 -0
  27. freesolo_flash_dev-1.0.4/flash/multimodal.py +1004 -0
  28. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/_instance_poll.py +29 -17
  29. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/_worker.py +6 -2
  30. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/runner/__init__.py +157 -13
  31. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/runner/deploy.py +418 -111
  32. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/runner/lifecycle.py +193 -21
  33. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/schema/__init__.py +35 -0
  34. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/serve/pricing.py +15 -6
  35. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/_runtime.py +191 -47
  36. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/routes/meta.py +8 -1
  37. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/routes/serving.py +106 -37
  38. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/pyproject.toml +11 -8
  39. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_allocator.py +10 -0
  40. freesolo_flash_dev-1.0.4/tests/test_async_uploads.py +448 -0
  41. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_catalog_consistency.py +22 -2
  42. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_checkpoints.py +15 -14
  43. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cli_commands.py +90 -9
  44. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cli_commands_coverage.py +3 -0
  45. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cli_render_coverage.py +8 -0
  46. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cli_render_theme.py +108 -0
  47. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_client.py +58 -6
  48. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cost_models.py +2 -2
  49. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_disk_gb.py +5 -9
  50. freesolo_flash_dev-1.0.4/tests/test_env_push.py +700 -0
  51. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_flash_mvp.py +5 -1
  52. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_gpus.py +2 -0
  53. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_grpo_params.py +60 -0
  54. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_health_liveness.py +6 -1
  55. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_jobs.py +492 -44
  56. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_kernel_fingerprint.py +30 -3
  57. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_lora_rank_preflight.py +15 -5
  58. freesolo_flash_dev-1.0.4/tests/test_multimodal_input_grads.py +75 -0
  59. freesolo_flash_dev-1.0.4/tests/test_multimodal_training.py +969 -0
  60. freesolo_flash_dev-1.0.4/tests/test_multiturn_image_rollout.py +291 -0
  61. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_multiturn_rollout.py +1 -1
  62. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_multiturn_rollout_coverage.py +1 -1
  63. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_multiturn_rollout_request_policy.py +4 -4
  64. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_opd.py +923 -48
  65. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_opd_vllm.py +46 -1
  66. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_packing.py +66 -0
  67. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_provider_routing.py +17 -2
  68. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_resolve_params_b.py +1 -0
  69. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_resume_on_retry.py +65 -24
  70. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_runmgmt.py +422 -24
  71. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_serve.py +28 -0
  72. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_server_api.py +159 -5
  73. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_serving_context_preflight.py +1 -0
  74. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_serving_contract.py +3 -2
  75. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_sft_gc_off.py +7 -1
  76. freesolo_flash_dev-1.0.4/tests/test_single_turn_multimodal_grpo.py +368 -0
  77. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_spec_and_validation.py +49 -0
  78. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_train_context_preflight.py +22 -0
  79. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_training_controls.py +2 -0
  80. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_verifiers.py +22 -2
  81. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_weight_cache.py +14 -5
  82. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_worker_image.py +17 -14
  83. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/uv.lock +4 -4
  84. freesolo_flash_dev-1.0.2/tests/test_env_push.py +0 -353
  85. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.claude/skills/verify/SKILL.md +0 -0
  86. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.dockerignore +0 -0
  87. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.env.example +0 -0
  88. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.github/workflows/auto-rebake.yml +0 -0
  89. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.github/workflows/ci.yml +0 -0
  90. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.github/workflows/main-source-guard.yml +0 -0
  91. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.github/workflows/notify-tests-repo.yml +0 -0
  92. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.github/workflows/publish-dev.yml +0 -0
  93. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.github/workflows/publish-image.yml +0 -0
  94. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.github/workflows/publish.yml +0 -0
  95. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.github/workflows/version-parity.yml +0 -0
  96. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.github/workflows/worker-image.yml +0 -0
  97. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/.gitignore +0 -0
  98. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/Dockerfile +0 -0
  99. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/LICENSE +0 -0
  100. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/README.md +0 -0
  101. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/build/kernel_cache/.gitignore +0 -0
  102. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/build/kernel_cache/.keep +0 -0
  103. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/docker/Dockerfile.kernelcache +0 -0
  104. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/docker/Dockerfile.kernelcache.relayer +0 -0
  105. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/docker/bake_kernel_cache.py +0 -0
  106. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/docker/bake_pod_entry.py +0 -0
  107. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/docker/kernel_fingerprint.py +0 -0
  108. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/docker/make_rp_handler.py +0 -0
  109. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/__init__.py +0 -0
  110. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/_channel.py +0 -0
  111. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/_fileio.py +0 -0
  112. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/_logging.py +0 -0
  113. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/_update_check.py +0 -0
  114. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/adapter_artifacts.py +0 -0
  115. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cli/__main__.py +0 -0
  116. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cli/_tty.py +0 -0
  117. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cli/env_setup.py +0 -0
  118. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/client/__init__.py +0 -0
  119. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/client/config.py +0 -0
  120. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/client/runtime_secrets.py +0 -0
  121. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/client/specs.py +0 -0
  122. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cost/__init__.py +0 -0
  123. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cost/analytical.py +0 -0
  124. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cost/facts.py +0 -0
  125. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cost/spec.py +0 -0
  126. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/cost/types.py +0 -0
  127. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/diagnostics.py +0 -0
  128. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/__init__.py +0 -0
  129. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/accounting.py +0 -0
  130. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/chalk_kernels.py +0 -0
  131. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/steps.py +0 -0
  132. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/structured_outputs.py +0 -0
  133. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/vram.py +0 -0
  134. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/__main__.py +0 -0
  135. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/_pkg.py +0 -0
  136. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/adapter.py +0 -0
  137. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/decoding.py +0 -0
  138. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/finalize.py +0 -0
  139. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/gpu_setup.py +0 -0
  140. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/heartbeat.py +0 -0
  141. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/kernel_warmup.py +0 -0
  142. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/lora.py +0 -0
  143. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/opd_gkd.py +0 -0
  144. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/attn.py +0 -0
  145. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/diagnostics.py +0 -0
  146. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/lifecycle.py +0 -0
  147. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/liger.py +0 -0
  148. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/loraplus.py +0 -0
  149. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/rng.py +0 -0
  150. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/tokenizer_align.py +0 -0
  151. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker/wandb_log.py +0 -0
  152. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/engine/worker_entrypoint.py +0 -0
  153. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/envs/__init__.py +0 -0
  154. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/envs/archive.py +0 -0
  155. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/envs/archive_policy.py +0 -0
  156. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/envs/base.py +0 -0
  157. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/envs/pull.py +0 -0
  158. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/envs/registry.py +0 -0
  159. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/lora_rank.py +0 -0
  160. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/opd_retry_contract.py +0 -0
  161. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/__init__.py +0 -0
  162. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/_auth.py +0 -0
  163. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/_deadline.py +0 -0
  164. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/_hf_artifacts.py +0 -0
  165. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/_hf_retry.py +0 -0
  166. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/_http.py +0 -0
  167. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/_instance.py +0 -0
  168. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/_instance_bootstrap.py +0 -0
  169. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/_instance_provider.py +0 -0
  170. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/_poll.py +0 -0
  171. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/allocator.py +0 -0
  172. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/base.py +0 -0
  173. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/__init__.py +0 -0
  174. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/api.py +0 -0
  175. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/auth.py +0 -0
  176. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/gpus.py +0 -0
  177. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  178. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  179. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/preflight.py +0 -0
  180. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/pricing.py +0 -0
  181. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/preflight.py +0 -0
  182. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/realized.py +0 -0
  183. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/__init__.py +0 -0
  184. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/api.py +0 -0
  185. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/auth.py +0 -0
  186. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/cost.py +0 -0
  187. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/gpus.py +0 -0
  188. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/jobs.py +0 -0
  189. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/keys.py +0 -0
  190. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/preflight.py +0 -0
  191. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/preload.py +0 -0
  192. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/pricing.py +0 -0
  193. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/slots.py +0 -0
  194. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/train/__init__.py +0 -0
  195. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/train/endpoints.py +0 -0
  196. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/vast/__init__.py +0 -0
  197. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/vast/api.py +0 -0
  198. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/vast/auth.py +0 -0
  199. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/vast/jobs/__init__.py +0 -0
  200. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/vast/jobs/builders.py +0 -0
  201. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/vast/preflight.py +0 -0
  202. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/providers/vast/pricing.py +0 -0
  203. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/py.typed +0 -0
  204. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/runner/checkpoints.py +0 -0
  205. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/runner/verified_revisions.py +0 -0
  206. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/schema/fields.py +0 -0
  207. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/serve/__init__.py +0 -0
  208. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/serve/deploy.py +0 -0
  209. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/serve/export.py +0 -0
  210. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/serve/urls.py +0 -0
  211. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/__init__.py +0 -0
  212. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/__main__.py +0 -0
  213. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/_deps.py +0 -0
  214. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/_internal_client.py +0 -0
  215. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/_locks.py +0 -0
  216. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/app.py +0 -0
  217. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/auth.py +0 -0
  218. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/billing.py +0 -0
  219. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/billing_retry.py +0 -0
  220. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/checkpoints.py +0 -0
  221. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/db.py +0 -0
  222. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/environment_registry.py +0 -0
  223. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/envs.py +0 -0
  224. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/reconcile.py +0 -0
  225. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/repo_cleanup.py +0 -0
  226. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/routes/__init__.py +0 -0
  227. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/routes/envs.py +0 -0
  228. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/routes/runs.py +0 -0
  229. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/server/run_registry.py +0 -0
  230. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/flash/spec.py +0 -0
  231. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/infisical-entrypoint.sh +0 -0
  232. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/scripts/build_dev_dist.py +0 -0
  233. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/__init__.py +0 -0
  234. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/_helpers/__init__.py +0 -0
  235. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/_helpers/runner.py +0 -0
  236. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/_helpers/specs.py +0 -0
  237. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/_helpers/vast.py +0 -0
  238. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/conftest.py +0 -0
  239. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/fixtures/math_eval.jsonl +0 -0
  240. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/fixtures/math_train.jsonl +0 -0
  241. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/live/__init__.py +0 -0
  242. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/live/conftest.py +0 -0
  243. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/live/test_lambda_live.py +0 -0
  244. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/live/test_runpod_live.py +0 -0
  245. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/live/test_vast_live.py +0 -0
  246. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_accounting_coverage.py +0 -0
  247. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_agent_flash_cli_contract.py +0 -0
  248. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_algorithms.py +0 -0
  249. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_b200_rollout_opt.py +0 -0
  250. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_backend_jobspec_contract.py +0 -0
  251. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_base_model_provenance.py +0 -0
  252. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_billing_retry.py +0 -0
  253. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cancel_remote.py +0 -0
  254. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_chalk_kernels.py +0 -0
  255. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_charge_pricing.py +0 -0
  256. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cli_errors.py +0 -0
  257. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cli_estimate.py +0 -0
  258. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cli_help.py +0 -0
  259. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cli_main_coverage.py +0 -0
  260. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cli_managed.py +0 -0
  261. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_client_server_integration.py +0 -0
  262. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_config_overrides.py +0 -0
  263. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_controlled_experiment_repairs.py +0 -0
  264. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cost_analytical.py +0 -0
  265. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cost_equation.py +0 -0
  266. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cost_estimate.py +0 -0
  267. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cost_hardware.py +0 -0
  268. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_cost_rewards.py +0 -0
  269. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_dev_channel.py +0 -0
  270. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_endpoint_name.py +0 -0
  271. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_env_cache_evict.py +0 -0
  272. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_env_delete.py +0 -0
  273. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_env_download.py +0 -0
  274. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_env_download_api.py +0 -0
  275. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_env_no_install.py +0 -0
  276. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_env_publish.py +0 -0
  277. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_env_pull.py +0 -0
  278. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_env_pull_coverage.py +0 -0
  279. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_env_pull_loader_coverage.py +0 -0
  280. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_env_pull_managed_control_plane.py +0 -0
  281. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_env_rate_limit_resolve.py +0 -0
  282. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_envs_coverage.py +0 -0
  283. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_export.py +0 -0
  284. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_flash_worker.py +0 -0
  285. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_flashinfer_cache_dirs.py +0 -0
  286. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_github_urlopen_retry.py +0 -0
  287. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_grpo_mask_aware.py +0 -0
  288. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_grpo_sleep_gate.py +0 -0
  289. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_hf_retry.py +0 -0
  290. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_idle_endpoint_reaper.py +0 -0
  291. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_instance_bootstrap_coverage.py +0 -0
  292. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_internal_client.py +0 -0
  293. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_kernel_cache.py +0 -0
  294. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_kv_util.py +0 -0
  295. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_lambda_api_coverage.py +0 -0
  296. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_lambda_runner.py +0 -0
  297. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_logging.py +0 -0
  298. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_login_perms.py +0 -0
  299. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_lora_rank_coverage.py +0 -0
  300. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_managed_hf_repo.py +0 -0
  301. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_metrics_schema_agent_contract.py +0 -0
  302. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_mig_guard.py +0 -0
  303. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_multiturn_rollout_records.py +0 -0
  304. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_oom_escalate_gpu.py +0 -0
  305. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_opd_full_state_resume.py +0 -0
  306. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_opd_resume_safety.py +0 -0
  307. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_open_model_policy.py +0 -0
  308. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_orchestrator_flash.py +0 -0
  309. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_packing_coverage.py +0 -0
  310. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_poll_helpers.py +0 -0
  311. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_preflight.py +0 -0
  312. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_provider_preflight_coverage.py +0 -0
  313. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_provider_teardown_robustness.py +0 -0
  314. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_providers_symmetry.py +0 -0
  315. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_realized_cost.py +0 -0
  316. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_repo_cleanup.py +0 -0
  317. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_runpod_api_delete.py +0 -0
  318. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_runpod_key_fingerprint.py +0 -0
  319. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_runpod_key_waterfall.py +0 -0
  320. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_runpod_slots.py +0 -0
  321. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_server_app_coverage.py +0 -0
  322. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_server_billing.py +0 -0
  323. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_server_checkpoints_coverage.py +0 -0
  324. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_server_db.py +0 -0
  325. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_server_envs_coverage.py +0 -0
  326. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_server_locks_coverage.py +0 -0
  327. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_serving_pricing_coverage.py +0 -0
  328. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_serving_schema_coverage.py +0 -0
  329. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_sft_example_selection.py +0 -0
  330. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_sft_max_context.py +0 -0
  331. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_structured_outputs.py +0 -0
  332. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_thinking_config.py +0 -0
  333. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_update_check.py +0 -0
  334. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_vast_api.py +0 -0
  335. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_vast_api_coverage.py +0 -0
  336. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_vast_offers.py +0 -0
  337. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_vast_provider_coverage.py +0 -0
  338. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_vast_runner.py +0 -0
  339. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_verified_revisions.py +0 -0
  340. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_version.py +0 -0
  341. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  342. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_wandb_log_coverage.py +0 -0
  343. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_wandb_naming.py +0 -0
  344. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_warmstart_cross_repo.py +0 -0
  345. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_worker_dryrun.py +0 -0
  346. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_worker_hardexit.py +0 -0
  347. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_worker_init_heartbeat.py +0 -0
  348. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_worker_stack.py +0 -0
  349. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.4}/tests/test_worker_thinking.py +0 -0
@@ -22,7 +22,7 @@ on:
22
22
  inputs:
23
23
  sms:
24
24
  description: "comma-separated sm list to bake; default is all validated arches"
25
- default: "sm80,sm86,sm89,sm90,sm120"
25
+ default: "sm80,sm86,sm89,sm90,sm120,sm100"
26
26
  required: false
27
27
  # auto-rebake.yml calls this for the EXPENSIVE path (the cache is invalid and must be re-warmed on a
28
28
  # real GPU). It passes the arch subset, the fingerprints to stamp, and a digest-pinned base so the
@@ -105,10 +105,9 @@ jobs:
105
105
  gpu_type_id: "NVIDIA RTX PRO 6000 Blackwell Server Edition",
106
106
  allowed_cuda: "13.0",
107
107
  }
108
- # Datacenter Blackwell (sm100, distinct from the sm120 RTX Pro 6000). B200 has 180 GB and
109
- # good secure-cloud capacity. CUDA-13 host like the other Blackwell parts. NOT in the default
110
- # `sms` list yet (B200 is unvalidated) -- bake it explicitly via `sms=sm100` until a smoke
111
- # passes, then add it to the default.
108
+ # datacenter blackwell (sm100, distinct from the sm120 rtx pro 6000). b200 has 180 gb,
109
+ # good secure-cloud capacity, and is validated. promote its image only after a successful
110
+ # real-b200 bake, then include it in default and automatic rebakes.
112
111
  - {
113
112
  sm: sm100,
114
113
  arch: "10.0",
@@ -120,7 +119,7 @@ jobs:
120
119
  - name: Should bake ${{ matrix.sm }}?
121
120
  id: gate
122
121
  run: |
123
- req="${{ inputs.sms || 'sm80,sm86,sm89,sm90,sm120' }}"
122
+ req="${{ inputs.sms || 'sm80,sm86,sm89,sm90,sm120,sm100' }}"
124
123
  req="${req// /}" # tolerate spaces, e.g. "sm80, sm86"
125
124
  case ",$req," in
126
125
  *,${{ matrix.sm }},*) echo "run=true" >> "$GITHUB_OUTPUT" ;;
@@ -55,7 +55,7 @@ RUN pip install --no-cache-dir \
55
55
  "flashinfer-python==0.6.6" \
56
56
  "bitsandbytes>=0.49" \
57
57
  "datasets>=4.7,<6" \
58
- "huggingface_hub>=0.25" \
58
+ "huggingface_hub>=1.2.0" \
59
59
  "accelerate>=1.4" \
60
60
  "wandb>=0.17" \
61
61
  "flash-linear-attention @ git+https://github.com/fla-org/flash-linear-attention.git@f0e213dbd8b5fb90c3c7eca869ac1706d5377139" \
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 1.0.2
3
+ Version: 1.0.4
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
@@ -20,7 +20,7 @@ Requires-Dist: datasets>=2.19; extra == 'dev'
20
20
  Requires-Dist: fastapi; extra == 'dev'
21
21
  Requires-Dist: freesolo>=0.2.54; extra == 'dev'
22
22
  Requires-Dist: httpx>=0.27; extra == 'dev'
23
- Requires-Dist: huggingface-hub>=0.34; extra == 'dev'
23
+ Requires-Dist: huggingface-hub>=1.2.0; extra == 'dev'
24
24
  Requires-Dist: jsonschema>=4.23; extra == 'dev'
25
25
  Requires-Dist: mypy>=1.13.0; extra == 'dev'
26
26
  Requires-Dist: pytest>=9.0.3; extra == 'dev'
@@ -35,7 +35,7 @@ Requires-Dist: accelerate>=1.4; extra == 'gpu'
35
35
  Requires-Dist: bitsandbytes>=0.49; extra == 'gpu'
36
36
  Requires-Dist: datasets>=2.19; extra == 'gpu'
37
37
  Requires-Dist: freesolo>=0.2.54; extra == 'gpu'
38
- Requires-Dist: huggingface-hub>=0.34; extra == 'gpu'
38
+ Requires-Dist: huggingface-hub>=1.2.0; extra == 'gpu'
39
39
  Requires-Dist: peft>=0.19; extra == 'gpu'
40
40
  Requires-Dist: torch==2.10.0; extra == 'gpu'
41
41
  Requires-Dist: transformers<5.11,>=5.6; extra == 'gpu'
@@ -46,7 +46,7 @@ Requires-Dist: datasets>=2.19; extra == 'server'
46
46
  Requires-Dist: fastapi; extra == 'server'
47
47
  Requires-Dist: freesolo>=0.2.54; extra == 'server'
48
48
  Requires-Dist: httpx>=0.27; extra == 'server'
49
- Requires-Dist: huggingface-hub>=0.34; extra == 'server'
49
+ Requires-Dist: huggingface-hub>=1.2.0; extra == 'server'
50
50
  Requires-Dist: jsonschema>=4.23; extra == 'server'
51
51
  Requires-Dist: referencing>=0.28.4; extra == 'server'
52
52
  Requires-Dist: regex>=2024.11.6; extra == 'server'
@@ -13,6 +13,22 @@ ALGORITHMS = ("sft", "grpo", "opd")
13
13
  # the cost model, VRAM sizing and worker allocator branch on — import ``samples_on_policy`` instead
14
14
  # of hand-rolling the ("grpo", "opd") tuple at each site.
15
15
  _ON_POLICY_ALGORITHMS = frozenset({"grpo", "opd"})
16
+ _IMAGE_TRAINING_MODELS = frozenset(
17
+ {
18
+ "Qwen/Qwen3.5-0.8B",
19
+ "Qwen/Qwen3.5-2B",
20
+ "Qwen/Qwen3.5-4B",
21
+ "Qwen/Qwen3.5-9B",
22
+ "Qwen/Qwen3.6-27B",
23
+ "Qwen/Qwen3.6-35B-A3B",
24
+ }
25
+ )
26
+
27
+
28
+ def supports_image_training(model: str | ModelInfo | None) -> bool:
29
+ """Return whether a curated model supports image-bearing SFT, GRPO, and OPD."""
30
+ model_id = model.id if isinstance(model, ModelInfo) else model
31
+ return bool(model_id and model_id in _IMAGE_TRAINING_MODELS)
16
32
 
17
33
 
18
34
  def samples_on_policy(algorithm: str) -> bool:
@@ -146,10 +162,10 @@ class ModelInfo:
146
162
 
147
163
  DEFAULT_MODEL = "Qwen/Qwen3.5-4B"
148
164
 
149
- # The pre-quantized FP8 checkpoint each base model's serving engine LOADS (mirrors serving's
150
- # ``src.prequant_config``). Every dense model now serves a Freesolo-OWNED FP8_DYNAMIC checkpoint (no
151
- # community-repo dependence); the 35B VL MoE serves the OFFICIAL Qwen FP8 (it preserves the full
152
- # vision-language model). Informational for the catalog mirror — deploy gating reads only max_lora_rank.
165
+ # the pre-quantized fp8 checkpoint each base model's serving engine loads (mirrors serving's
166
+ # ``src.prequant_config``). every dense model serves a freesolo-owned fp8 checkpoint; the qwen3.6
167
+ # 35b-a3b moe serves the official qwen fp8 variant. informational for the catalog mirror; deploy
168
+ # gating reads only max_lora_rank.
153
169
  SERVING_FP8_MODEL_REPOS: dict[str, str] = {
154
170
  "openbmb/MiniCPM5-1B": "Freesolo-Co/MiniCPM5-1B-FP8",
155
171
  "Qwen/Qwen3.5-0.8B": "Freesolo-Co/Qwen3.5-0.8B-FP8",
@@ -157,6 +173,7 @@ SERVING_FP8_MODEL_REPOS: dict[str, str] = {
157
173
  "Qwen/Qwen3.5-4B": "Freesolo-Co/Qwen3.5-4B-FP8",
158
174
  "Qwen/Qwen3.5-9B": "Freesolo-Co/Qwen3.5-9B-FP8",
159
175
  "Qwen/Qwen3.6-35B-A3B": "Qwen/Qwen3.6-35B-A3B-FP8",
176
+ "Qwen/Qwen3.6-27B": "Freesolo-Co/Qwen3.6-27B-FP8",
160
177
  }
161
178
 
162
179
  MODELS: dict[str, ModelInfo] = {
@@ -182,7 +199,7 @@ MODELS: dict[str, ModelInfo] = {
182
199
  "Qwen/Qwen3.5-0.8B": ModelInfo(
183
200
  id="Qwen/Qwen3.5-0.8B",
184
201
  display_name="Qwen3.5 0.8B",
185
- params="0.9B (text-only fine-tune)",
202
+ params="0.9B",
186
203
  params_b=0.9,
187
204
  vocab_size=248_320,
188
205
  algos=ALGORITHMS,
@@ -201,7 +218,7 @@ MODELS: dict[str, ModelInfo] = {
201
218
  "Qwen/Qwen3.5-2B": ModelInfo(
202
219
  id="Qwen/Qwen3.5-2B",
203
220
  display_name="Qwen3.5 2B",
204
- params="2.3B (text-only fine-tune)",
221
+ params="2.3B",
205
222
  params_b=2.3,
206
223
  vocab_size=248_320,
207
224
  algos=ALGORITHMS,
@@ -219,7 +236,7 @@ MODELS: dict[str, ModelInfo] = {
219
236
  "Qwen/Qwen3.5-4B": ModelInfo(
220
237
  id="Qwen/Qwen3.5-4B",
221
238
  display_name="Qwen3.5 4B",
222
- params="4.7B (text-only fine-tune)",
239
+ params="4.7B",
223
240
  params_b=4.7,
224
241
  vocab_size=248_320,
225
242
  algos=ALGORITHMS,
@@ -241,7 +258,7 @@ MODELS: dict[str, ModelInfo] = {
241
258
  "Qwen/Qwen3.5-9B": ModelInfo(
242
259
  id="Qwen/Qwen3.5-9B",
243
260
  display_name="Qwen3.5 9B",
244
- params="9.7B (text-only fine-tune)",
261
+ params="9.7B",
245
262
  params_b=9.7,
246
263
  vocab_size=248_320,
247
264
  algos=ALGORITHMS,
@@ -264,6 +281,35 @@ MODELS: dict[str, ModelInfo] = {
264
281
  "(two bf16 copies + KV + the 248k-vocab fp32 logits) needs an 80 GB-class card "
265
282
  "(grpo_min_vram_gb floor).",
266
283
  ),
284
+ "Qwen/Qwen3.6-27B": ModelInfo(
285
+ id="Qwen/Qwen3.6-27B",
286
+ display_name="Qwen3.6 27B",
287
+ params="27B dense (multimodal VL, hybrid GDN)",
288
+ params_b=27.0,
289
+ num_layers=64,
290
+ hidden_size=5120,
291
+ vocab_size=248_320,
292
+ algos=("sft", "grpo", "opd"),
293
+ min_vram_gb=80,
294
+ sft_min_vram_gb=80,
295
+ quant="bf16",
296
+ recommended_gpu="A100 PCIe",
297
+ min_disk_gb=160,
298
+ serving=ServingCapacity(
299
+ gpu="H100",
300
+ serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.6-27B"],
301
+ max_loras=16,
302
+ max_lora_rank=64,
303
+ max_model_len=32768,
304
+ max_num_seqs=8,
305
+ gpu_memory_utilization=0.98,
306
+ ),
307
+ thinking="hybrid",
308
+ notes="Dense 27B multimodal VL checkpoint with image-capable bf16 LoRA training. SFT fits "
309
+ "the 80GB A100 (~54GB weights); colocated GRPO needs the B200 (trainer + vLLM rollout = two "
310
+ "~54GB copies). Serves the owned VL-preserving FP8 on an H100 tier (dense, so no MoE expert "
311
+ "LoRA-buffer multiplier).",
312
+ ),
267
313
  "Qwen/Qwen3.6-35B-A3B": ModelInfo(
268
314
  id="Qwen/Qwen3.6-35B-A3B",
269
315
  display_name="Qwen3.6 35B-A3B (MoE)",
@@ -424,7 +424,11 @@ def _build_parser() -> argparse.ArgumentParser:
424
424
  deployments.set_defaults(func=cmd_deployments)
425
425
 
426
426
  chat = sub.add_parser("chat", help="chat with a deployed adapter")
427
- chat.add_argument("run_id")
427
+ chat.add_argument(
428
+ "run_id",
429
+ help="a bare RUN_ID (its current deployment), RUN_ID/step-N (a specific deployed "
430
+ "checkpoint), or a full immutable adapter revision",
431
+ )
428
432
  chat.add_argument("-m", "--message", required=True)
429
433
  chat.add_argument(
430
434
  "--system",
@@ -160,8 +160,9 @@ def cmd_gpus(args) -> int:
160
160
  (info for info in GPU_INFO.values() if info.enum_member), key=lambda g: g.hourly_usd
161
161
  )
162
162
  tip = (
163
- "Tip: GPU class selection is fully automatic the submit-time allocator always picks the\n"
164
- "cheapest validated managed class that fits the model, so you don't pin a GPU type."
163
+ "Tip: GPU allocation is automatic by default.\n"
164
+ "The allocator picks the cheapest validated class that fits. Pin a specific class by "
165
+ 'adding exact_type = "<CLASS>" to the [gpu] section.'
165
166
  )
166
167
  if render.styled():
167
168
  rows = [(info.name, info.vram_gb, runpod_rates.get(info.name)) for info in infos]
@@ -313,9 +314,8 @@ def cmd_train(args) -> int:
313
314
  runtime_secrets_from_local_env(args.config, keys=spec.environment.secrets) or None
314
315
  )
315
316
  if args.dry_run:
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.
317
+ # dry-run runs submit-time server preflights without importing user code, allocating a gpu,
318
+ # or charging anything. a rejection surfaces as the server's error with exit status 1.
319
319
  try:
320
320
  status = client.create_run(
321
321
  payload,
@@ -330,6 +330,14 @@ def cmd_train(args) -> int:
330
330
  raise ApiError(exc.status, detail) from exc
331
331
  compatibility = status.pop("train_schema_compatibility", None)
332
332
  _print_train_schema_compatibility(compatibility)
333
+ print(
334
+ "dry-run validated: config/schema, model+algorithm compatibility, lora rank, "
335
+ "runtime-secret presence, warm-start source, serving context cap, and cost. it did NOT "
336
+ "import or run your environment.py; dataset loading, start_episode/episode shapes, "
337
+ "reward/scorer, worker imports, model load, and gpu/training are first exercised on the "
338
+ "worker after cold-start.",
339
+ file=sys.stderr,
340
+ )
333
341
  if render.styled():
334
342
  print(
335
343
  render.object_panel(
@@ -376,18 +384,26 @@ def _log_follow_progress(status: dict | None, fallback_state: str) -> tuple[str,
376
384
  parts = [state]
377
385
  heartbeat = status.get("last_heartbeat") if isinstance(status, dict) else None
378
386
  if isinstance(heartbeat, dict):
387
+ heartbeat_age_seconds = render._heartbeat_age_seconds(heartbeat.get("ts"))
379
388
  stage = heartbeat.get("stage")
380
389
  if stage:
381
390
  parts.append(f"stage={stage}")
391
+ if state == "running":
392
+ warmup = render.warmup_message(
393
+ stage,
394
+ heartbeat_age_seconds,
395
+ render.heartbeat_is_current_attempt(status, heartbeat),
396
+ )
397
+ if warmup:
398
+ parts.append(warmup)
382
399
  step = heartbeat.get("step")
383
400
  if step is not None:
384
401
  parts.append(f"step={step}")
385
402
  # live heartbeat age so a long quiet phase reads as "alive, throttled" not "frozen".
386
403
  # minute granularity: the non-TTY follow path prints a line whenever this string changes,
387
404
  # so a seconds-precision age would emit one line per poll.
388
- ts = heartbeat.get("ts")
389
- if isinstance(ts, (int, float)) and ts > 0:
390
- mins = int(max(0.0, time.time() - ts) // 60)
405
+ if heartbeat_age_seconds is not None:
406
+ mins = int(heartbeat_age_seconds // 60)
391
407
  parts.append(f"hb={mins}m" if mins else "hb=<1m")
392
408
  realized = status.get("realized_cost_usd")
393
409
  if realized is not None:
@@ -700,7 +716,7 @@ def cmd_deployments(args) -> int:
700
716
  return 0
701
717
  print(
702
718
  f"{'RUN ID':<30} {'STEP':<6} {'REVISION':<40} {'STATE':<14} "
703
- f"{'VERIFIED AT':<18} {'OPENAI MODEL':<30} DETAIL"
719
+ f"{'VERIFIED AT':<20} {'OPENAI MODEL':<30} DETAIL"
704
720
  )
705
721
  for row in rows:
706
722
  deployment = row.get("deployment") or {}
@@ -708,14 +724,16 @@ def cmd_deployments(args) -> int:
708
724
  step = deployment.get("checkpoint_step")
709
725
  step_text = "final" if step is None else str(step)
710
726
  verified_at = deployment.get("verified_at")
711
- verified_text = "-" if verified_at is None else str(verified_at)
727
+ verified_text = (
728
+ "-" if verified_at is None else (render._humanize_ts(verified_at) or str(verified_at))
729
+ )
712
730
  revision = str(deployment.get("adapter_revision") or "-")
713
731
  state = str(deployment.get("state") or "-")
714
732
  openai_model = str(deployment.get("openai_model") or run_id)
715
733
  detail = str(deployment.get("error") or deployment.get("detail") or "")[:160]
716
734
  print(
717
735
  f"{run_id:<30} {step_text:<6} {revision:<40} {state:<14} "
718
- f"{verified_text:<18} {openai_model:<30} {detail}"
736
+ f"{verified_text:<20} {openai_model:<30} {detail}"
719
737
  )
720
738
  return 0
721
739
 
@@ -728,18 +746,11 @@ def cmd_chat(args) -> int:
728
746
  if revision is None and parsed is None:
729
747
  print(
730
748
  f"invalid chat target {args.run_id!r} "
731
- "(expected a bare <run_id> or full immutable adapter revision)",
732
- file=sys.stderr,
733
- )
734
- return 1
735
- if revision is None and parsed[1] is not None:
736
- print(
737
- "RUN_ID/step-N is not a valid chat target because it would route through the mutable "
738
- "run alias; use the full immutable adapter revision returned by `flash deployments`",
749
+ "(expected a bare <run_id>, <run_id>/step-N, or full immutable adapter revision)",
739
750
  file=sys.stderr,
740
751
  )
741
752
  return 1
742
- chat_target = args.run_id if revision is not None else parsed[0]
753
+ chat_target = args.run_id
743
754
  client = client_from_config()
744
755
  messages = [{"role": "user", "content": args.message}]
745
756
  system = getattr(args, "system", None)
@@ -11,6 +11,7 @@ import os
11
11
  import re
12
12
  import sys
13
13
  import tempfile
14
+ from collections.abc import Iterator
14
15
  from pathlib import Path
15
16
  from typing import TYPE_CHECKING, Literal, TypeAlias
16
17
 
@@ -174,6 +175,10 @@ def cmd_env_delete(args) -> int:
174
175
 
175
176
 
176
177
  _ENV_ENTRYPOINT = "environment.py"
178
+ _ENV_SYSPATH_BOOTSTRAP = (
179
+ "import os as _flash_os, sys as _flash_sys\n"
180
+ "_flash_sys.path.insert(0, _flash_os.path.dirname(__file__))\n"
181
+ )
177
182
  _ENV_PUSH_IGNORED_NAMES = frozenset(
178
183
  {
179
184
  ".prime",
@@ -183,26 +188,47 @@ _ENV_PUSH_IGNORED_NAMES = frozenset(
183
188
  ".venv",
184
189
  ".mypy_cache",
185
190
  ".pytest_cache",
186
- "pyproject.toml",
187
- "source",
191
+ ".ruff_cache",
192
+ ".idea",
193
+ ".vscode",
194
+ ".ds_store",
195
+ ".git-worktrees",
196
+ ".env",
197
+ ".netrc",
198
+ ".aws",
199
+ ".ssh",
200
+ "venv",
201
+ "node_modules",
202
+ "credentials",
203
+ "secrets",
188
204
  }
189
205
  )
190
- # ``.md`` is included so the ``TRAINING.md`` playbook `flash env setup` scaffolds (and any
191
- # user-authored README/NOTES) travels with the env into the hub and back out through
192
- # ``flash env pull`` — a published env should carry its own training guidance, not just code+data.
193
- _ENV_PUSH_SIDECAR_SUFFIXES = frozenset(
194
- {
195
- ".csv",
196
- ".json",
197
- ".jsonl",
198
- ".md",
199
- ".parquet",
200
- ".tsv",
201
- ".txt",
202
- ".yaml",
203
- ".yml",
204
- }
206
+ _ENV_PUSH_ROOT_IGNORED_NAMES = frozenset({"pyproject.toml", "source"})
207
+ _ENV_PUSH_SECRET_PATTERNS = (
208
+ ".env.*",
209
+ "*.env",
210
+ "*.pem",
211
+ "*.key",
212
+ "*.pfx",
213
+ "*.p12",
214
+ "credentials*",
215
+ "id_rsa*",
216
+ "id_dsa*",
217
+ "id_ecdsa*",
218
+ "id_ed25519*",
205
219
  )
220
+ # secret patterns match secret data FILES only (see _ignore_env_push_path): they never prune a
221
+ # directory, so a package tree like credentials_store/ still ships, and python source is exempt,
222
+ # so helper modules like credentials.py or id_ed25519_loader.py travel instead of being silently
223
+ # dropped and breaking the worker with ModuleNotFoundError. that keeps the prefixes broad enough
224
+ # to catch backup variants (credentials_backup, id_rsa_backup) without eating code or packages.
225
+ _ENV_PUSH_CODE_SUFFIXES = frozenset({".py", ".pyi"})
226
+ _ENV_PUSH_MAX_TOTAL_BYTES = 256 * 1024 * 1024
227
+
228
+ try:
229
+ from flash.envs.archive_policy import ARCHIVE_MEMBER_LIMIT as _ENV_PUSH_MAX_FILES
230
+ except ImportError:
231
+ _ENV_PUSH_MAX_FILES = 5000
206
232
 
207
233
 
208
234
  def _normalize_env_name(raw: str) -> str | None:
@@ -227,16 +253,12 @@ def _normalize_env_name(raw: str) -> str | None:
227
253
 
228
254
  def _with_syspath_bootstrap(env_source: str) -> str:
229
255
  """Prepend a sys.path bootstrap so a published env can resolve shipped sibling helpers."""
230
- bootstrap = (
231
- "import os as _flash_os, sys as _flash_sys\n"
232
- "_flash_sys.path.insert(0, _flash_os.path.dirname(__file__))\n"
233
- )
234
256
  import ast
235
257
 
236
258
  try:
237
259
  tree = ast.parse(env_source)
238
260
  except SyntaxError:
239
- return bootstrap + env_source
261
+ return _ENV_SYSPATH_BOOTSTRAP + env_source
240
262
  insert_after = 0
241
263
  body = tree.body
242
264
  i = 0
@@ -252,7 +274,16 @@ def _with_syspath_bootstrap(env_source: str) -> str:
252
274
  insert_after = body[i].end_lineno or insert_after
253
275
  i += 1
254
276
  lines = env_source.splitlines(keepends=True)
255
- return "".join(lines[:insert_after]) + bootstrap + "".join(lines[insert_after:])
277
+ return (
278
+ "".join(lines[:insert_after])
279
+ + _ENV_SYSPATH_BOOTSTRAP
280
+ + "".join(lines[insert_after:])
281
+ )
282
+
283
+
284
+ def _raise_walk_error(error: OSError) -> None:
285
+ """fail env packaging loudly instead of silently skipping an unreadable directory."""
286
+ raise error
256
287
 
257
288
 
258
289
  def _tar_b64(directory: Path) -> str:
@@ -266,44 +297,130 @@ def _tar_b64(directory: Path) -> str:
266
297
  with tarfile.open(fileobj=buf, mode="w:gz") as tar:
267
298
  for root, dirs, files in os.walk(directory):
268
299
  root_path = Path(root)
269
- dirs[:] = sorted(d for d in dirs if d not in _ENV_PUSH_IGNORED_NAMES)
270
- for name in dirs:
271
- child = root_path / name
272
- tar.add(child, arcname=str(child.relative_to(directory)), recursive=False)
300
+ dirs[:] = sorted(d for d in dirs if d.lower() not in _ENV_PUSH_IGNORED_NAMES)
273
301
  for name in sorted(files):
274
302
  child = root_path / name
275
303
  tar.add(child, arcname=str(child.relative_to(directory)), recursive=False)
276
304
  return base64.b64encode(buf.getvalue()).decode()
277
305
 
278
306
 
279
- def _copy_env_sidecars(env_root: Path, dest: Path, *, entrypoint: Path) -> None:
280
- """Copy helper code and data sidecars beside environment.py."""
281
- import shutil
307
+ def _ignore_env_push_path(path: Path, *, env_root: Path, entrypoint: Path) -> bool:
308
+ """Return whether a source path must not be included in an env package."""
309
+ import fnmatch
310
+
311
+ name = path.name
312
+ lowered_name = name.lower()
313
+ if path.is_symlink():
314
+ return True
315
+ if path.is_dir() and (path / "pyvenv.cfg").is_file():
316
+ return True
317
+ if path == entrypoint or (
318
+ path.parent == env_root and lowered_name == _ENV_ENTRYPOINT.lower()
319
+ ):
320
+ return True
321
+ if lowered_name.startswith(".") or lowered_name in _ENV_PUSH_IGNORED_NAMES:
322
+ return True
323
+ if path.parent == env_root and lowered_name in _ENV_PUSH_ROOT_IGNORED_NAMES:
324
+ return True
325
+ if (
326
+ path.is_file()
327
+ and path.suffix.lower() not in _ENV_PUSH_CODE_SUFFIXES
328
+ and any(fnmatch.fnmatchcase(lowered_name, pattern) for pattern in _ENV_PUSH_SECRET_PATTERNS)
329
+ ):
330
+ return True
331
+ return not (path.is_dir() or path.is_file())
332
+
333
+
334
+ def _iter_env_sidecar_files(
335
+ env_root: Path, *, entrypoint: Path, include_full_tree: bool
336
+ ) -> Iterator[tuple[Path, Path]]:
337
+ """Yield publishable sidecar files and their package-relative paths."""
338
+ import os
282
339
 
283
- for child in sorted(env_root.iterdir()):
284
- if (
285
- child == entrypoint
286
- or child.name == _ENV_ENTRYPOINT
287
- or child.name in _ENV_PUSH_IGNORED_NAMES
340
+ roots = [env_root]
341
+ if not include_full_tree:
342
+ # a single-file push carries only the entrypoint, its dataset sidecars, and its own
343
+ # docs. ship user-authored docs so the synthesized stub readme does not stand in for
344
+ # real training guidance the user shipped next to the module.
345
+ for doc_name in ("README.md", "TRAINING.md"):
346
+ doc = env_root / doc_name
347
+ if doc.is_file() and not _ignore_env_push_path(
348
+ doc, env_root=env_root, entrypoint=entrypoint
349
+ ):
350
+ yield doc, doc.relative_to(env_root)
351
+ roots = [
352
+ env_root / name
353
+ for name in ("dataset", "datasets")
354
+ if (env_root / name).is_dir() and not (env_root / name).is_symlink()
355
+ ]
356
+
357
+ for walk_root in roots:
358
+ for root, dirs, files in os.walk(
359
+ walk_root, topdown=True, followlinks=False, onerror=_raise_walk_error
288
360
  ):
289
- continue
290
- if child.name.startswith("."):
291
- continue
292
- target = dest / child.name
293
- if child.is_dir():
294
- if child.name == "dataset":
295
- shutil.copytree(
296
- child,
297
- target,
298
- ignore=shutil.ignore_patterns(*_ENV_PUSH_IGNORED_NAMES),
361
+ root_path = Path(root)
362
+ dirs[:] = sorted(
363
+ name
364
+ for name in dirs
365
+ if not _ignore_env_push_path(
366
+ root_path / name, env_root=env_root, entrypoint=entrypoint
299
367
  )
300
- continue
301
- if not child.is_file():
302
- continue
303
- if (
304
- child.suffix == ".py" and not child.name.startswith("__")
305
- ) or child.suffix.lower() in _ENV_PUSH_SIDECAR_SUFFIXES:
306
- shutil.copy2(child, target)
368
+ )
369
+ for name in sorted(files):
370
+ child = root_path / name
371
+ if _ignore_env_push_path(child, env_root=env_root, entrypoint=entrypoint):
372
+ continue
373
+ yield child, child.relative_to(env_root)
374
+
375
+
376
+ def _check_env_push_limits(
377
+ env_root: Path, *, entrypoint: Path, include_full_tree: bool, env_name: str
378
+ ) -> None:
379
+ """Reject oversized source trees before copying or building an in-memory archive."""
380
+ files = 1
381
+ total_bytes = entrypoint.stat().st_size + len(_ENV_SYSPATH_BOOTSTRAP.encode())
382
+ directories: set[Path] = set()
383
+ has_readme = False
384
+ for child, relative in _iter_env_sidecar_files(
385
+ env_root, entrypoint=entrypoint, include_full_tree=include_full_tree
386
+ ):
387
+ files += 1
388
+ total_bytes += child.stat().st_size
389
+ has_readme = has_readme or relative == Path("README.md")
390
+ # the server counts every file AND directory against the same member cap when it
391
+ # repackages the checkout for download, so count unique parent dirs here too.
392
+ directories.update(parent for parent in relative.parents if parent != Path("."))
393
+ if not has_readme:
394
+ files += 1
395
+ total_bytes += len(f"# {env_name}\n\nFlash Freesolo environment.\n".encode())
396
+
397
+ members = files + len(directories)
398
+ if total_bytes > _ENV_PUSH_MAX_TOTAL_BYTES:
399
+ raise ValueError(
400
+ f"environment package totals {_human_bytes(total_bytes)} "
401
+ f"(limit {_human_bytes(_ENV_PUSH_MAX_TOTAL_BYTES)}); "
402
+ "remove large artifacts or use a smaller dataset"
403
+ )
404
+ if members > _ENV_PUSH_MAX_FILES:
405
+ raise ValueError(
406
+ f"selected environment contains {members:,} files and directories "
407
+ f"(limit {_ENV_PUSH_MAX_FILES:,}); "
408
+ "remove large artifacts or use a smaller dataset"
409
+ )
410
+
411
+
412
+ def _copy_env_sidecars(
413
+ env_root: Path, dest: Path, *, entrypoint: Path, include_full_tree: bool
414
+ ) -> None:
415
+ """Copy publishable sidecar files beside environment.py, creating directories lazily."""
416
+ import shutil
417
+
418
+ for child, relative in _iter_env_sidecar_files(
419
+ env_root, entrypoint=entrypoint, include_full_tree=include_full_tree
420
+ ):
421
+ target = dest / relative
422
+ target.parent.mkdir(parents=True, exist_ok=True)
423
+ shutil.copy2(child, target)
307
424
 
308
425
 
309
426
  def _human_bytes(n: int) -> str:
@@ -385,8 +502,11 @@ def cmd_env_push(args) -> int:
385
502
  src = Path(args.path)
386
503
  if not src.exists():
387
504
  return _err(f"no such path: {src}")
505
+ if src.is_symlink():
506
+ return _err(f"cannot publish {src}: symlinks are not allowed")
388
507
 
389
- if src.is_dir():
508
+ include_full_tree = src.is_dir()
509
+ if include_full_tree:
390
510
  canonical_entrypoint = src / _ENV_ENTRYPOINT
391
511
  if canonical_entrypoint.is_file():
392
512
  entrypoint = canonical_entrypoint
@@ -410,11 +530,29 @@ def cmd_env_push(args) -> int:
410
530
  else:
411
531
  return _err(f"cannot publish {src}: expected a Freesolo .py module or an env directory.")
412
532
 
533
+ if entrypoint.is_symlink():
534
+ return _err(f"cannot publish {entrypoint}: symlinks are not allowed")
535
+
536
+ try:
537
+ _check_env_push_limits(
538
+ env_root,
539
+ entrypoint=entrypoint,
540
+ include_full_tree=include_full_tree,
541
+ env_name=env_name,
542
+ )
543
+ except (OSError, ValueError) as exc:
544
+ return _err(f"cannot publish {src}: {exc}")
545
+
413
546
  with tempfile.TemporaryDirectory(prefix="flash-env-push-") as tmp:
414
547
  pkg = Path(tmp)
415
548
  module_source = entrypoint.read_text()
416
549
  (pkg / _ENV_ENTRYPOINT).write_text(_with_syspath_bootstrap(module_source))
417
- _copy_env_sidecars(env_root, pkg, entrypoint=entrypoint)
550
+ _copy_env_sidecars(
551
+ env_root,
552
+ pkg,
553
+ entrypoint=entrypoint,
554
+ include_full_tree=include_full_tree,
555
+ )
418
556
  # Only synthesize a stub README when the env didn't ship its own (now carried as a
419
557
  # ``.md`` sidecar) — don't clobber a user-authored README with boilerplate.
420
558
  readme = pkg / "README.md"