freesolo-flash-dev 0.2.55__tar.gz → 0.2.56__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.
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.env.example +0 -5
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.github/workflows/bake-kernel-cache.yml +2 -2
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/Dockerfile +6 -11
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/PKG-INFO +1 -1
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cli/commands.py +26 -417
- freesolo_flash_dev-0.2.56/flash/cli/env_setup.py +423 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cli/envpush.py +1 -6
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cli/render.py +11 -10
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cli/training_doc.py +68 -31
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/client/http.py +11 -2
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cost/analytical.py +4 -1
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cost/facts.py +11 -16
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cost/spec.py +10 -8
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cost/types.py +5 -4
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/chalk_kernels.py +0 -17
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/multiturn_rollout.py +16 -0
- freesolo_flash_dev-0.2.56/flash/engine/recipe.py +185 -0
- freesolo_flash_dev-0.2.56/flash/engine/structured_outputs.py +80 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/vram.py +6 -6
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/__init__.py +0 -14
- freesolo_flash_dev-0.2.56/flash/engine/worker/adapter.py +186 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/gpu_setup.py +8 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/heartbeat.py +14 -12
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/hf.py +8 -42
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/lora.py +8 -279
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/opd.py +145 -68
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/opd_vllm.py +81 -5
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/packing.py +2 -2
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/perf/lifecycle.py +0 -26
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/rl.py +170 -143
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/sft.py +4 -8
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/envs/adapter.py +0 -1
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/envs/loader.py +46 -27
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/envs/pull.py +17 -63
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/lora_rank.py +36 -86
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/_worker.py +4 -11
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/allocator.py +1 -2
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/jobs.py +0 -1
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/preload.py +0 -6
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/train/endpoints.py +0 -1
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/vast/api.py +0 -4
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/runner/__init__.py +56 -12
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/runner/lifecycle.py +5 -5
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/schema/__init__.py +25 -0
- freesolo_flash_dev-0.2.56/flash/schema/fields.py +374 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/serve/deploy.py +7 -15
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/_runtime.py +45 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/app.py +19 -1
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/auth.py +14 -9
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/envs.py +5 -10
- freesolo_flash_dev-0.2.56/flash/server/repo_cleanup.py +408 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/routes/meta.py +2 -10
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/spec.py +9 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/pyproject.toml +2 -2
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/conftest.py +8 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_agent_flash_cli_contract.py +20 -5
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_b200_rollout_opt.py +32 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_chalk_kernels.py +0 -13
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_checkpoints.py +5 -80
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cli_errors.py +11 -4
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cli_estimate.py +30 -0
- freesolo_flash_dev-0.2.56/tests/test_cli_render_coverage.py +379 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_client.py +10 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_client_server_integration.py +18 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_env_publish.py +1 -1
- freesolo_flash_dev-0.2.56/tests/test_env_pull_loader_coverage.py +240 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_flash_mvp.py +20 -15
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_flash_worker.py +0 -1
- freesolo_flash_dev-0.2.56/tests/test_instance_bootstrap_coverage.py +453 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_jobs.py +40 -1
- freesolo_flash_dev-0.2.56/tests/test_lambda_api_coverage.py +456 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_lambda_runner.py +7 -9
- freesolo_flash_dev-0.2.56/tests/test_lora_rank_preflight.py +142 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_multiturn_rollout.py +56 -5
- freesolo_flash_dev-0.2.56/tests/test_multiturn_rollout_coverage.py +551 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_opd.py +270 -115
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_opd_vllm.py +283 -0
- freesolo_flash_dev-0.2.56/tests/test_repo_cleanup.py +550 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_serve.py +7 -7
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_server_api.py +40 -0
- freesolo_flash_dev-0.2.56/tests/test_server_envs_coverage.py +436 -0
- freesolo_flash_dev-0.2.56/tests/test_structured_outputs.py +227 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_vast_api.py +3 -3
- freesolo_flash_dev-0.2.56/tests/test_vast_api_coverage.py +258 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_warmstart_cross_repo.py +38 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_weight_cache.py +0 -1
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_worker_image.py +9 -12
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_worker_stack.py +0 -1
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/uv.lock +1 -1
- freesolo_flash_dev-0.2.55/flash/engine/recipe.py +0 -95
- freesolo_flash_dev-0.2.55/flash/engine/worker/adapter.py +0 -304
- freesolo_flash_dev-0.2.55/flash/schema/fields.py +0 -226
- freesolo_flash_dev-0.2.55/tests/test_lora_rank_preflight.py +0 -138
- freesolo_flash_dev-0.2.55/tests/test_vl_warmstart_recombine.py +0 -578
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/README.md +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/catalog.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cli/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/steps.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/opd_gkd.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/teacher.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/tokenizer_align.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/runpod/train/deps.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_charge_pricing.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cli_commands.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_sft_example_selection.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_spec_and_validation.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_train_context_preflight.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/tests/test_worker_thinking.py +0 -0
|
@@ -5,11 +5,6 @@
|
|
|
5
5
|
# GPU substrate. RunPod is the default; Vast is opt-in (only required when set).
|
|
6
6
|
RUNPOD_API_KEY=
|
|
7
7
|
VAST_API_KEY=
|
|
8
|
-
# Use the per-arch baked worker images (cu128-<sm>) to skip the ~10-15 min cold-start JIT. Requires
|
|
9
|
-
# the per-SM images published first (.github/workflows/bake-kernel-cache.yml), and a manual re-bake
|
|
10
|
-
# after any worker-deps change. The control-plane Dockerfile sets this to 1 by default; uncomment to
|
|
11
|
-
# enable for a bare `flash-server` deploy.
|
|
12
|
-
# FLASH_WORKER_IMAGE_PER_SM=1
|
|
13
8
|
# HuggingFace token with write access to each run's [train] hf_repo (code upload +
|
|
14
9
|
# streamed checkpoints/adapters land in that per-run dataset repo). The artifact repo
|
|
15
10
|
# is per-run (set in the run TOML's [train] hf_repo), not an operator-wide env var.
|
{freesolo_flash_dev-0.2.55 → freesolo_flash_dev-0.2.56}/.github/workflows/bake-kernel-cache.yml
RENAMED
|
@@ -4,8 +4,8 @@ name: flash worker kernel-cache (per-arch)
|
|
|
4
4
|
# first-use JIT on a cold worker (the regression #194 reintroduced). Each matrix leg:
|
|
5
5
|
# 1. offloads the warmup to a RunPod GPU of that arch (GH runners have none) -> build/kernel_cache,
|
|
6
6
|
# 2. docker build --build-arg BUILD_KERNEL_CACHE=true -> ghcr.io/freesolo-co/flash-worker:cu128-<sm>,
|
|
7
|
-
# 3. pushes it.
|
|
8
|
-
#
|
|
7
|
+
# 3. pushes it. The allocator then selects the cu128-<sm> tag per GPU class for baked arches
|
|
8
|
+
# (see flash/providers/runpod/train/deps.py).
|
|
9
9
|
#
|
|
10
10
|
# Depends on the base ghcr.io/freesolo-co/flash-worker:cu128 image existing (worker-image.yml) — the
|
|
11
11
|
# warmup runs INSIDE it so the cache matches the image's pinned torch/triton/fla/liger toolchain.
|
|
@@ -24,17 +24,12 @@ RUN pip install --no-cache-dir ".[server]"
|
|
|
24
24
|
VOLUME /root/.flash
|
|
25
25
|
EXPOSE 8080
|
|
26
26
|
|
|
27
|
-
#
|
|
28
|
-
# ~10-15 min first-use JIT;
|
|
29
|
-
# SMs (sm80/86/89/90/120) are published
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# NOTE: this ENV is the default for BARE (non-Infisical) `flash-server` deploys. Under the Infisical
|
|
34
|
-
# entrypoint below, `infisical run` overrides the container env, so for the Infisical-managed deploy
|
|
35
|
-
# set FLASH_WORKER_IMAGE_PER_SM in the vault (path /flash) or add it to INFISICAL_KEEP -- otherwise
|
|
36
|
-
# this default may not reach the server.
|
|
37
|
-
ENV FLASH_WORKER_IMAGE_PER_SM=1
|
|
27
|
+
# The allocator uses the per-arch baked worker images (ghcr.io/.../flash-worker:cu128-<sm>) so cold
|
|
28
|
+
# workers skip the ~10-15 min first-use JIT; each GPU class maps to its matching -smXX tag. All
|
|
29
|
+
# validated SMs (sm80/86/89/90/120) are published; unbaked arches fall back to the base :cu128 image.
|
|
30
|
+
# Rebakes are MANUAL -- after a Dockerfile.worker/deps change rebuilds :cu128, re-run
|
|
31
|
+
# bake-kernel-cache.yml so the -smXX tags don't ship stale deps (the worker-image build posts a
|
|
32
|
+
# reminder).
|
|
38
33
|
|
|
39
34
|
# secret injection wrapper: no-op passthrough unless INFISICAL_CLIENT_ID is set, else
|
|
40
35
|
# `infisical login` (universal-auth) then `infisical run --path /flash` before the server.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.56
|
|
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
|
|
@@ -23,12 +23,21 @@ from flash.client.config import load_credentials
|
|
|
23
23
|
from flash.client.runtime_secrets import runtime_secrets_from_local_env
|
|
24
24
|
from flash.client.specs import spec_payload
|
|
25
25
|
from flash.cost.spec import runconfig_from_spec
|
|
26
|
-
from flash.runner import TERMINAL_STATES
|
|
26
|
+
from flash.runner import TERMINAL_STATES
|
|
27
27
|
from flash.schema import ConfigError, spec_from_file
|
|
28
28
|
|
|
29
29
|
from . import render
|
|
30
30
|
from ._tty import TtyStatusLine
|
|
31
|
-
from .
|
|
31
|
+
from .env_setup import ( # noqa: F401
|
|
32
|
+
_REASONING_OPTIONS,
|
|
33
|
+
_STARTER_DATASET_JSONL,
|
|
34
|
+
_STARTER_DATASET_MULTITURN_JSONL,
|
|
35
|
+
_STARTER_ENV_MULTITURN_PY,
|
|
36
|
+
_STARTER_ENV_PY,
|
|
37
|
+
_TURN_OPTIONS,
|
|
38
|
+
_setup_interactive,
|
|
39
|
+
cmd_env_setup,
|
|
40
|
+
)
|
|
32
41
|
|
|
33
42
|
logger = get_logger("flash.cli")
|
|
34
43
|
|
|
@@ -98,7 +107,7 @@ def cmd_login(args) -> int:
|
|
|
98
107
|
print(render.login_failed(str(exc)), file=sys.stderr)
|
|
99
108
|
return 1
|
|
100
109
|
api_url = args.api_url or load_credentials()[0]
|
|
101
|
-
|
|
110
|
+
save_credentials(api_key, api_url=api_url)
|
|
102
111
|
if args.api_key and env_api_key and env_api_key != args.api_key:
|
|
103
112
|
msg = (
|
|
104
113
|
"FREESOLO_API_KEY is set and will override this saved login for future "
|
|
@@ -129,414 +138,6 @@ def cmd_whoami(args) -> int:
|
|
|
129
138
|
return 0
|
|
130
139
|
|
|
131
140
|
|
|
132
|
-
_STARTER_ENV_PY = '''\
|
|
133
|
-
"""Starter Freesolo environment.
|
|
134
|
-
|
|
135
|
-
Edit dataset/train.jsonl and the reward code, then upload with
|
|
136
|
-
`flash env push --name my-env .`.
|
|
137
|
-
|
|
138
|
-
A managed run should use the returned [environment] id from
|
|
139
|
-
`flash env push --name my-env .`.
|
|
140
|
-
|
|
141
|
-
This starter keeps a tiny smoke-test dataset in dataset/train.jsonl. Replace it
|
|
142
|
-
with your real training rows before a real run.
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
from __future__ import annotations
|
|
146
|
-
|
|
147
|
-
import json
|
|
148
|
-
from pathlib import Path
|
|
149
|
-
|
|
150
|
-
from freesolo.datasets.types import TaskExample
|
|
151
|
-
from freesolo.environments import EnvironmentSingleTurn, RewardResult
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
DEFAULT_DATASET_PATH = Path(__file__).parent / "dataset" / "train.jsonl"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def load_jsonl(path: str | Path):
|
|
158
|
-
rows = []
|
|
159
|
-
with Path(path).open() as f:
|
|
160
|
-
for line in f:
|
|
161
|
-
line = line.strip()
|
|
162
|
-
if line:
|
|
163
|
-
rows.append(json.loads(line))
|
|
164
|
-
return rows
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def exact_match_reward(example: TaskExample, response_text: str) -> RewardResult:
|
|
168
|
-
expected = str(example.output or "").strip()
|
|
169
|
-
score = 1.0 if expected and expected in response_text else 0.0
|
|
170
|
-
return RewardResult(score=score, threshold=1.0)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
class StarterEnv(EnvironmentSingleTurn):
|
|
174
|
-
dataset = load_jsonl(DEFAULT_DATASET_PATH)
|
|
175
|
-
|
|
176
|
-
def build_prompt_messages(self, example: TaskExample, prompt_text: str):
|
|
177
|
-
return [{"role": "user", "content": example.input}]
|
|
178
|
-
|
|
179
|
-
def score_response(self, example: TaskExample, response_text: str) -> RewardResult:
|
|
180
|
-
return exact_match_reward(example, response_text)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def load_environment(dataset_path: str | None = None, **kwargs) -> StarterEnv:
|
|
184
|
-
env = StarterEnv()
|
|
185
|
-
if dataset_path:
|
|
186
|
-
env.dataset = load_jsonl(dataset_path)
|
|
187
|
-
return env
|
|
188
|
-
'''
|
|
189
|
-
|
|
190
|
-
_STARTER_DATASET_JSONL = """\
|
|
191
|
-
{"input":"What is 2 + 2?","output":"4"}
|
|
192
|
-
{"input":"What is 3 + 5?","output":"8"}
|
|
193
|
-
"""
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
_STARTER_ENV_MULTITURN_PY = '''\
|
|
197
|
-
"""Starter Freesolo multi-turn environment.
|
|
198
|
-
|
|
199
|
-
A multi-turn environment runs a bounded episode: the model produces an assistant
|
|
200
|
-
action, `step_episode` advances the world (optionally appending an observation
|
|
201
|
-
message), and the loop repeats until `done` or `max_episode_turns`. The finished
|
|
202
|
-
transcript is graded by `score_episode`.
|
|
203
|
-
|
|
204
|
-
Edit dataset/train.jsonl and the episode logic, then upload with
|
|
205
|
-
`flash env push --name my-env .`.
|
|
206
|
-
|
|
207
|
-
A managed run should use the returned [environment] id from
|
|
208
|
-
`flash env push --name my-env .`.
|
|
209
|
-
|
|
210
|
-
This starter implements a tiny "guess the secret number" game so you can see the
|
|
211
|
-
episode hooks wired end-to-end. Replace it with your real task before a real run.
|
|
212
|
-
|
|
213
|
-
All three algorithms train off this file:
|
|
214
|
-
- GRPO (configs/rl.toml) rolls out full episodes and optimizes `score_episode`.
|
|
215
|
-
- SFT (configs/sft.toml) learns the gold trajectory. Provide it per row as
|
|
216
|
-
`output = {"messages": [...]}` (a full assistant/tool trajectory) or a scalar
|
|
217
|
-
`output` for a single gold assistant turn.
|
|
218
|
-
- OPD (configs/opd.toml) rolls out each episode and distils EVERY assistant turn against
|
|
219
|
-
the Fireworks GLM teacher, conditioned on the transcript so far — the multi-turn
|
|
220
|
-
on-policy-distillation objective. The teacher key is platform-managed (nothing to set).
|
|
221
|
-
"""
|
|
222
|
-
|
|
223
|
-
from __future__ import annotations
|
|
224
|
-
|
|
225
|
-
import json
|
|
226
|
-
from pathlib import Path
|
|
227
|
-
|
|
228
|
-
from freesolo.datasets.types import TaskExample
|
|
229
|
-
from freesolo.environments import (
|
|
230
|
-
EnvironmentEpisode,
|
|
231
|
-
EnvironmentMultiTurn,
|
|
232
|
-
EnvironmentStepResult,
|
|
233
|
-
RewardResult,
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
DEFAULT_DATASET_PATH = Path(__file__).parent / "dataset" / "train.jsonl"
|
|
238
|
-
|
|
239
|
-
# How many assistant guesses the model gets before the episode is forced terminal.
|
|
240
|
-
MAX_TURNS = 5
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
def load_jsonl(path: str | Path):
|
|
244
|
-
rows = []
|
|
245
|
-
with Path(path).open() as f:
|
|
246
|
-
for line in f:
|
|
247
|
-
line = line.strip()
|
|
248
|
-
if line:
|
|
249
|
-
rows.append(json.loads(line))
|
|
250
|
-
return rows
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def _secret(example: TaskExample) -> int:
|
|
254
|
-
return int(str(example.output).strip())
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
class StarterMultiTurnEnv(EnvironmentMultiTurn):
|
|
258
|
-
dataset = load_jsonl(DEFAULT_DATASET_PATH)
|
|
259
|
-
|
|
260
|
-
def start_episode(self, example: TaskExample, prompt_text: str):
|
|
261
|
-
# The opening prompt shown to the model. `example.input` describes the range.
|
|
262
|
-
return [
|
|
263
|
-
{
|
|
264
|
-
"role": "user",
|
|
265
|
-
"content": (
|
|
266
|
-
f"{example.input}\\n"
|
|
267
|
-
f"Reply with a single integer per turn. I will say 'higher', "
|
|
268
|
-
f"'lower', or 'correct'. You have {MAX_TURNS} guesses."
|
|
269
|
-
),
|
|
270
|
-
}
|
|
271
|
-
]
|
|
272
|
-
|
|
273
|
-
def max_episode_turns(self, example: TaskExample) -> int:
|
|
274
|
-
return MAX_TURNS
|
|
275
|
-
|
|
276
|
-
def step_episode(
|
|
277
|
-
self,
|
|
278
|
-
example: TaskExample,
|
|
279
|
-
messages: list,
|
|
280
|
-
assistant_response: str,
|
|
281
|
-
) -> EnvironmentStepResult:
|
|
282
|
-
# Advance the world after one assistant action. Return done=True to end the
|
|
283
|
-
# episode, or append an observation message and keep going.
|
|
284
|
-
try:
|
|
285
|
-
guess = int(assistant_response.strip().split()[0])
|
|
286
|
-
except (ValueError, IndexError):
|
|
287
|
-
return EnvironmentStepResult(
|
|
288
|
-
done=False,
|
|
289
|
-
messages=[{"role": "user", "content": "Please reply with a single integer."}],
|
|
290
|
-
)
|
|
291
|
-
secret = _secret(example)
|
|
292
|
-
if guess == secret:
|
|
293
|
-
return EnvironmentStepResult(done=True, final_response_text=str(guess))
|
|
294
|
-
hint = "higher" if guess < secret else "lower"
|
|
295
|
-
return EnvironmentStepResult(
|
|
296
|
-
done=False,
|
|
297
|
-
messages=[{"role": "user", "content": hint}],
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
def score_episode(
|
|
301
|
-
self,
|
|
302
|
-
example: TaskExample,
|
|
303
|
-
episode: EnvironmentEpisode,
|
|
304
|
-
) -> RewardResult:
|
|
305
|
-
# Grade the finished transcript. Reward a correct final guess; give partial
|
|
306
|
-
# credit for getting close so GRPO has a usable gradient.
|
|
307
|
-
secret = _secret(example)
|
|
308
|
-
try:
|
|
309
|
-
final = int(str(episode.response_text).strip())
|
|
310
|
-
except ValueError:
|
|
311
|
-
return RewardResult(score=0.0, threshold=1.0)
|
|
312
|
-
if final == secret:
|
|
313
|
-
return RewardResult(score=1.0, threshold=1.0, success=True)
|
|
314
|
-
# Closeness in [0, 1): further guesses score lower.
|
|
315
|
-
closeness = max(0.0, 1.0 - abs(final - secret) / 100.0)
|
|
316
|
-
return RewardResult(score=closeness * 0.5, threshold=1.0, success=False)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
def load_environment(dataset_path: str | None = None, **kwargs) -> StarterMultiTurnEnv:
|
|
320
|
-
env = StarterMultiTurnEnv()
|
|
321
|
-
if dataset_path:
|
|
322
|
-
env.dataset = load_jsonl(dataset_path)
|
|
323
|
-
return env
|
|
324
|
-
'''
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
# Multi-turn rows: `input` sets up the episode, `output` is the secret number.
|
|
328
|
-
# The scalar `output` also serves as the gold single-turn SFT target; swap in
|
|
329
|
-
# `{"messages": [...]}` to teach a full gold trajectory.
|
|
330
|
-
_STARTER_DATASET_MULTITURN_JSONL = """\
|
|
331
|
-
{"input":"I picked a secret whole number between 1 and 100.","output":"42"}
|
|
332
|
-
{"input":"I picked a secret whole number between 1 and 100.","output":"73"}
|
|
333
|
-
"""
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
_TURN_OPTIONS = [
|
|
337
|
-
("single", "single-turn", "one prompt -> one response"),
|
|
338
|
-
("multi", "multi-turn", "bounded episode: step_episode / score_episode"),
|
|
339
|
-
]
|
|
340
|
-
_REASONING_OPTIONS = [
|
|
341
|
-
("off", "no reasoning", "the model answers directly"),
|
|
342
|
-
("on", "reasoning", "thinking = true; spends more tokens per answer"),
|
|
343
|
-
]
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
def _setup_interactive(args) -> bool:
|
|
347
|
-
"""Whether to ask the setup questions interactively.
|
|
348
|
-
|
|
349
|
-
Prompt only on a real terminal a human can answer from. Fall back to defaults (no prompt) when
|
|
350
|
-
--yes is passed, under CI, or when stdin is closed/redirected, so automation never blocks on an
|
|
351
|
-
unanswered prompt. A pseudo-TTY in CI can report isatty()=True, so CI is checked explicitly; a
|
|
352
|
-
closed fd 0 can make sys.stdin None, so that is guarded before .isatty()."""
|
|
353
|
-
if getattr(args, "yes", False):
|
|
354
|
-
return False
|
|
355
|
-
if os.environ.get("CI", "").strip().lower() not in ("", "0", "false", "no"):
|
|
356
|
-
return False
|
|
357
|
-
stdin = sys.stdin
|
|
358
|
-
if stdin is None or not stdin.isatty():
|
|
359
|
-
return False
|
|
360
|
-
return render.styled()
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def cmd_env_setup(args) -> int:
|
|
364
|
-
starter_env = Path("environment.py")
|
|
365
|
-
dataset = Path("dataset/train.jsonl")
|
|
366
|
-
# An existing environment.py is the authoritative signal for which turn mode this
|
|
367
|
-
# scaffold already uses (the dataset is plain JSONL with no reliable mode marker).
|
|
368
|
-
# Anchor to it so a re-run never leaves a single-turn env beside a multi-turn
|
|
369
|
-
# dataset (or vice versa); the flag/answer only decides the mode when starting fresh.
|
|
370
|
-
existing_multi: bool | None = None
|
|
371
|
-
anchor = "environment.py"
|
|
372
|
-
if starter_env.exists():
|
|
373
|
-
existing_multi = "EnvironmentMultiTurn" in starter_env.read_text(encoding="utf-8")
|
|
374
|
-
elif dataset.exists():
|
|
375
|
-
# No env.py to anchor on, but the starter multi-turn dataset carries a
|
|
376
|
-
# distinctive prompt; use it so we don't drop a single-turn env beside it.
|
|
377
|
-
existing_multi = "secret whole number" in dataset.read_text(encoding="utf-8")
|
|
378
|
-
anchor = "dataset/train.jsonl"
|
|
379
|
-
|
|
380
|
-
# Resolve the turn mode. An existing scaffold wins (warn if a flag disagrees); otherwise an
|
|
381
|
-
# explicit --single-turn/--multi-turn flag wins; otherwise ask on a terminal, else single-turn.
|
|
382
|
-
flag_mode = getattr(args, "turn_mode", None)
|
|
383
|
-
if existing_multi is not None:
|
|
384
|
-
if flag_mode is not None and (flag_mode == "multi") != existing_multi:
|
|
385
|
-
have = "multi-turn" if existing_multi else "single-turn"
|
|
386
|
-
want = "multi-turn" if flag_mode == "multi" else "single-turn"
|
|
387
|
-
msg = (
|
|
388
|
-
f"existing {anchor} is {have}; keeping it and ignoring --{want}. "
|
|
389
|
-
f"Delete environment.py and dataset/train.jsonl first to re-scaffold as {want}."
|
|
390
|
-
)
|
|
391
|
-
print(render.warn(msg) if render.styled() else f"warning: {msg}", file=sys.stderr)
|
|
392
|
-
multi_turn = existing_multi
|
|
393
|
-
elif flag_mode is not None:
|
|
394
|
-
multi_turn = flag_mode == "multi"
|
|
395
|
-
elif _setup_interactive(args):
|
|
396
|
-
multi_turn = (
|
|
397
|
-
render.select("How does the model interact with your task?", _TURN_OPTIONS) == "multi"
|
|
398
|
-
)
|
|
399
|
-
else:
|
|
400
|
-
multi_turn = False
|
|
401
|
-
|
|
402
|
-
# Resolve reasoning. Like the turn mode, an existing config is authoritative: configs are only
|
|
403
|
-
# written when absent, so applying a reasoning flag to an already-scaffolded project would
|
|
404
|
-
# silently no-op (or write one config with reasoning and leave the other without). Anchor to the
|
|
405
|
-
# existing config's `thinking` state and warn if a flag disagrees; otherwise the flag wins;
|
|
406
|
-
# otherwise ask on a terminal; else off.
|
|
407
|
-
rl = Path("configs/rl.toml")
|
|
408
|
-
sft = Path("configs/sft.toml")
|
|
409
|
-
existing_reasoning: bool | None = None
|
|
410
|
-
for cfg in (rl, sft):
|
|
411
|
-
if cfg.exists():
|
|
412
|
-
existing_reasoning = "thinking = true" in cfg.read_text(encoding="utf-8")
|
|
413
|
-
break
|
|
414
|
-
flag_reason = getattr(args, "reasoning", None)
|
|
415
|
-
if existing_reasoning is not None:
|
|
416
|
-
if flag_reason is not None and flag_reason != existing_reasoning:
|
|
417
|
-
have = "reasoning" if existing_reasoning else "no reasoning"
|
|
418
|
-
want = "reasoning" if flag_reason else "no-reasoning"
|
|
419
|
-
msg = (
|
|
420
|
-
f"existing configs are {have}; keeping them and ignoring --{want}. "
|
|
421
|
-
f"Delete configs/rl.toml and configs/sft.toml first to re-scaffold with --{want}."
|
|
422
|
-
)
|
|
423
|
-
print(render.warn(msg) if render.styled() else f"warning: {msg}", file=sys.stderr)
|
|
424
|
-
reasoning = existing_reasoning
|
|
425
|
-
elif flag_reason is not None:
|
|
426
|
-
reasoning = flag_reason
|
|
427
|
-
elif _setup_interactive(args):
|
|
428
|
-
reasoning = render.select("Train with reasoning (thinking)?", _REASONING_OPTIONS) == "on"
|
|
429
|
-
else:
|
|
430
|
-
reasoning = False
|
|
431
|
-
|
|
432
|
-
env_py = _STARTER_ENV_MULTITURN_PY if multi_turn else _STARTER_ENV_PY
|
|
433
|
-
dataset_jsonl = _STARTER_DATASET_MULTITURN_JSONL if multi_turn else _STARTER_DATASET_JSONL
|
|
434
|
-
Path("configs").mkdir(exist_ok=True)
|
|
435
|
-
Path("dataset").mkdir(exist_ok=True)
|
|
436
|
-
if not dataset.exists():
|
|
437
|
-
dataset.write_text(dataset_jsonl)
|
|
438
|
-
if not starter_env.exists():
|
|
439
|
-
starter_env.write_text(env_py)
|
|
440
|
-
env_comment = (
|
|
441
|
-
"# Environment: upload this project folder with\n"
|
|
442
|
-
"# `flash env push --name my-env .`, then paste the returned id below.\n"
|
|
443
|
-
"# If the environment reads secrets with os.environ, list only the env var names here.\n"
|
|
444
|
-
"# Values are read from your shell or .env at submit time and are not stored in the spec.\n"
|
|
445
|
-
"[environment]\n"
|
|
446
|
-
'id = ""\n\n'
|
|
447
|
-
'# secrets = ["SERPAPI_API_KEY"]\n\n'
|
|
448
|
-
)
|
|
449
|
-
# `thinking = true` opts the run into reasoning mode. Reasoning shares the generation budget with
|
|
450
|
-
# the answer, so GRPO also gets a raised completion budget. These strings are empty when reasoning
|
|
451
|
-
# is off, keeping the default scaffold byte-for-byte identical.
|
|
452
|
-
thinking_line = "thinking = true\n" if reasoning else ""
|
|
453
|
-
rl_reasoning_train = (
|
|
454
|
-
"max_completion_tokens = 2048 # reasoning shares this budget with the answer; raised so it isn't truncated\n"
|
|
455
|
-
if reasoning
|
|
456
|
-
else ""
|
|
457
|
-
)
|
|
458
|
-
sft_reasoning_note = (
|
|
459
|
-
"# reasoning is on (thinking = true): each gold `output` must contain a <think>...</think>\n"
|
|
460
|
-
"# block; validate locally with freesolo.datasets.warn_missing_think_tags before a real run.\n"
|
|
461
|
-
if reasoning
|
|
462
|
-
else ""
|
|
463
|
-
)
|
|
464
|
-
if not rl.exists():
|
|
465
|
-
rl.write_text(
|
|
466
|
-
'model = "Qwen/Qwen3.5-4B"\n'
|
|
467
|
-
'algorithm = "grpo"\n'
|
|
468
|
-
f"{thinking_line}"
|
|
469
|
-
"\n"
|
|
470
|
-
f"{env_comment}"
|
|
471
|
-
"[train]\n"
|
|
472
|
-
"epochs = 1\n"
|
|
473
|
-
"max_examples = 2 # rows to train on; the starter dataset has 2 (raise as your dataset grows)\n"
|
|
474
|
-
f"{rl_reasoning_train}"
|
|
475
|
-
"lora_rank = 32\n"
|
|
476
|
-
"# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
|
|
477
|
-
"# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
|
|
478
|
-
)
|
|
479
|
-
if not sft.exists():
|
|
480
|
-
sft.write_text(
|
|
481
|
-
'model = "Qwen/Qwen3.5-4B"\n'
|
|
482
|
-
'algorithm = "sft"\n'
|
|
483
|
-
f"{thinking_line}"
|
|
484
|
-
"\n"
|
|
485
|
-
f"{env_comment}"
|
|
486
|
-
"[train]\n"
|
|
487
|
-
"epochs = 1\n"
|
|
488
|
-
"max_examples = 2 # rows to train on; the starter dataset has 2 (raise as your dataset grows)\n"
|
|
489
|
-
"lora_rank = 32\n"
|
|
490
|
-
f"{sft_reasoning_note}"
|
|
491
|
-
"# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
|
|
492
|
-
"# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
|
|
493
|
-
)
|
|
494
|
-
opd = Path("configs/opd.toml")
|
|
495
|
-
if not opd.exists():
|
|
496
|
-
# opd (on-policy distillation) works in BOTH modes: single-turn distils one sampled completion
|
|
497
|
-
# per prompt; multi-turn rolls out each episode and distils every assistant turn against the
|
|
498
|
-
# transcript so far. The scaffold differs only by a one-line note pointing at the mode.
|
|
499
|
-
opd_multiturn_note = (
|
|
500
|
-
"# NOTE: opd rolls out each episode and distils EVERY assistant turn (conditioned on the\n"
|
|
501
|
-
"# transcript so far) against the Fireworks GLM teacher — the multi-turn distillation path.\n\n"
|
|
502
|
-
if multi_turn
|
|
503
|
-
else ""
|
|
504
|
-
)
|
|
505
|
-
opd.write_text(
|
|
506
|
-
f"{opd_multiturn_note}"
|
|
507
|
-
'model = "Qwen/Qwen3.5-4B"\n'
|
|
508
|
-
'algorithm = "opd" # on-policy distillation from the managed GLM 5.2 teacher\n\n'
|
|
509
|
-
"# Environment: upload this project folder with\n"
|
|
510
|
-
"# `flash env push --name my-env .`, then paste the returned id below.\n"
|
|
511
|
-
"# The GLM 5.2 teacher and key are platform-managed — nothing to set up or export.\n"
|
|
512
|
-
"[environment]\n"
|
|
513
|
-
'id = ""\n\n'
|
|
514
|
-
"[train]\n"
|
|
515
|
-
"epochs = 1\n"
|
|
516
|
-
"max_examples = 2 # rows to train on; the starter dataset has 2 (raise as your dataset grows)\n"
|
|
517
|
-
"lora_rank = 32\n"
|
|
518
|
-
"# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
|
|
519
|
-
"# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
|
|
520
|
-
)
|
|
521
|
-
training = Path("TRAINING.md")
|
|
522
|
-
if not training.exists():
|
|
523
|
-
# Explicit UTF-8: TRAINING_MD has non-ASCII chars that raise UnicodeEncodeError under a non-UTF-8 locale.
|
|
524
|
-
training.write_text(TRAINING_MD, encoding="utf-8")
|
|
525
|
-
scaffolded = [
|
|
526
|
-
"environment.py",
|
|
527
|
-
"dataset/train.jsonl",
|
|
528
|
-
"configs/sft.toml",
|
|
529
|
-
"configs/rl.toml",
|
|
530
|
-
"configs/opd.toml",
|
|
531
|
-
"TRAINING.md",
|
|
532
|
-
]
|
|
533
|
-
if render.styled():
|
|
534
|
-
print(render.env_setup(scaffolded))
|
|
535
|
-
return 0
|
|
536
|
-
print(f"ensured {', '.join(scaffolded)}")
|
|
537
|
-
return 0
|
|
538
|
-
|
|
539
|
-
|
|
540
141
|
def cmd_models(args) -> int:
|
|
541
142
|
rows = public_model_rows()
|
|
542
143
|
if render.styled():
|
|
@@ -631,20 +232,29 @@ def cmd_train(args) -> int:
|
|
|
631
232
|
return _cmd_train_cost(args)
|
|
632
233
|
spec = spec_from_file(
|
|
633
234
|
args.config,
|
|
634
|
-
run_id=
|
|
235
|
+
run_id=None,
|
|
635
236
|
overrides=args.overrides,
|
|
636
237
|
extra_configs=args.extra_configs,
|
|
637
238
|
)
|
|
239
|
+
client = client_from_config()
|
|
638
240
|
if args.dry_run:
|
|
639
|
-
|
|
241
|
+
# Dry-run is a faithful server-side preview: the control plane runs the SAME config
|
|
242
|
+
# validation and warm-start/serving preflights it would at real submit (serving rank/context
|
|
243
|
+
# caps, the continued warm-start rank match, cost quote) and records a state=dry_run run, but
|
|
244
|
+
# allocates no GPU and charges nothing. Local runtime secrets aren't sent — the server
|
|
245
|
+
# relaxes the required-[environment].secrets check for a preview, so `--dry-run` works before
|
|
246
|
+
# prod secrets are wired up. A rejection surfaces as the server's `error: ...` (exit 1),
|
|
247
|
+
# exactly as a real submit would.
|
|
248
|
+
status = client.create_run(spec_payload(spec), dry_run=True)
|
|
640
249
|
if render.styled():
|
|
641
250
|
print(
|
|
642
|
-
render.object_panel(
|
|
251
|
+
render.object_panel(
|
|
252
|
+
"train", status, "dry run — validated by the server, not submitted"
|
|
253
|
+
)
|
|
643
254
|
)
|
|
644
255
|
else:
|
|
645
|
-
print(json.dumps(
|
|
256
|
+
print(json.dumps(status, indent=2))
|
|
646
257
|
return 0
|
|
647
|
-
client = client_from_config()
|
|
648
258
|
status = client.create_run(
|
|
649
259
|
spec_payload(spec),
|
|
650
260
|
runtime_secrets=runtime_secrets_from_local_env(args.config, keys=spec.environment.secrets),
|
|
@@ -931,7 +541,6 @@ def cmd_deploy(args) -> int:
|
|
|
931
541
|
print(
|
|
932
542
|
render.arrow(url_note) if render.styled() else f"note: {url_note}", file=sys.stderr
|
|
933
543
|
)
|
|
934
|
-
if dep.get("state") != "dry_run":
|
|
935
544
|
state = dep.get("state", "deploying")
|
|
936
545
|
if state == "failed":
|
|
937
546
|
detail = str(dep.get("error") or dep.get("detail") or "unknown error")
|