freesolo-flash-dev 0.2.29__tar.gz → 0.2.31__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.29 → freesolo_flash_dev-0.2.31}/.github/workflows/bake-kernel-cache.yml +25 -31
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/Dockerfile.worker +1 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/PKG-INFO +10 -8
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/README.md +4 -4
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docker/bake_kernel_cache.py +60 -3
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docker/bake_pod_entry.py +27 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/catalog.py +79 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/cli/__init__.py +8 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/cli/commands.py +5 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/cli/render.py +3 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/cli/training_doc.py +8 -5
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/cost/__init__.py +2 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/cost/analytical.py +30 -12
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/cost/facts.py +6 -2
- freesolo_flash_dev-0.2.31/flash/cost/spec.py +248 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/cost/types.py +16 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/accounting.py +6 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/__init__.py +9 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/adapter.py +22 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/decoding.py +35 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/lora.py +115 -9
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/rl.py +13 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/envs/adapter.py +215 -7
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/envs/registry.py +3 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/_instance.py +3 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/_instance_bootstrap.py +119 -13
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/base.py +24 -8
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/lambdalabs/__init__.py +2 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/lambdalabs/jobs/__init__.py +12 -3
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/lambdalabs/jobs/builders.py +2 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/lambdalabs/pricing.py +9 -4
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/__init__.py +2 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/jobs.py +3 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/pricing.py +5 -2
- freesolo_flash_dev-0.2.31/flash/providers/runpod/train/__init__.py +154 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/train/deps.py +3 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/train/endpoints.py +111 -9
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/runner/__init__.py +53 -6
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/runner/deploy.py +37 -14
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/runner/lifecycle.py +26 -40
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/schema/__init__.py +10 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/serve/deploy.py +77 -71
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/serve/export.py +54 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/_locks.py +1 -13
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/_runtime.py +26 -54
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/app.py +1 -15
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/auth.py +14 -4
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/billing.py +13 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/environment_registry.py +9 -4
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/envs.py +18 -23
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/routes/envs.py +11 -3
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/routes/meta.py +2 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/routes/runs.py +37 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/routes/serving.py +30 -26
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/pyproject.toml +8 -6
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/conftest.py +0 -11
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_allocator.py +5 -5
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_billing_retry.py +6 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cancel_remote.py +2 -32
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_catalog_consistency.py +73 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cli_commands.py +8 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cli_estimate.py +130 -13
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cli_help.py +31 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_client.py +14 -3
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_client_server_integration.py +2 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_config_overrides.py +5 -6
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cost_analytical.py +27 -11
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cost_equation.py +6 -4
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cost_estimate.py +6 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cost_hardware.py +16 -6
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_disk_gb.py +1 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_env_publish.py +141 -61
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_env_pull.py +229 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_export.py +75 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_flash_mvp.py +1 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_flash_worker.py +77 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_gpus.py +51 -17
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_grpo_params.py +9 -7
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_grpo_sleep_gate.py +3 -3
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_jobs.py +108 -54
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_lambda_runner.py +89 -4
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_managed_hf_repo.py +31 -10
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_mig_guard.py +3 -3
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_orchestrator_flash.py +267 -17
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_packing.py +1 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_provider_routing.py +34 -34
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_provider_teardown_robustness.py +4 -4
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_providers_symmetry.py +6 -6
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_resume_on_retry.py +4 -4
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_runmgmt.py +29 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_serve.py +146 -77
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_server_api.py +295 -37
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_server_billing.py +60 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_server_db.py +3 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_serving_contract.py +3 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_spec_and_validation.py +14 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_verifiers.py +47 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_vl_warmstart_recombine.py +196 -16
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_weight_cache.py +1 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_worker_init_heartbeat.py +7 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_worker_stack.py +1 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_worker_thinking.py +20 -5
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/uv.lock +12 -8
- freesolo_flash_dev-0.2.29/flash/cost/spec.py +0 -110
- freesolo_flash_dev-0.2.29/flash/providers/runpod/train/__init__.py +0 -62
- freesolo_flash_dev-0.2.29/flash/server/repo_cleanup.py +0 -296
- freesolo_flash_dev-0.2.29/tests/test_repo_cleanup.py +0 -513
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.env.example +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-style/README.md +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-style/generate.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-style/index.html +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-style/preview.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-style/themed-errors.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-theme/README.md +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-theme/before-after-dark.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-theme/before-after-light.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-theme/gallery-dark.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-theme/gallery-light.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-theme/help-dark.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/cli-theme/help-light.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/docs/kernel-cache.md +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/client/http.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/hf.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/sft.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/lambdalabs/train.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/flash/spec.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_vl_weight_sync.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/tests/test_worker_image.py +0 -0
{freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.31}/.github/workflows/bake-kernel-cache.yml
RENAMED
|
@@ -11,8 +11,8 @@ name: flash worker kernel-cache (per-arch)
|
|
|
11
11
|
# warmup runs INSIDE it so the cache matches the image's pinned torch/triton/fla/liger toolchain.
|
|
12
12
|
#
|
|
13
13
|
# Secrets: RUNPOD_API_KEY (the warmup GPU pod) + HF_TOKEN (the temp dataset that ferries code + the
|
|
14
|
-
# cache artifact) are
|
|
15
|
-
#
|
|
14
|
+
# cache artifact) are native repo Actions secrets, synced from Infisical (prod:/flash-ci) by the
|
|
15
|
+
# github-ci-flash Secret Sync; the GHCR push uses the default GITHUB_TOKEN. Heavy + paid,
|
|
16
16
|
# but UNGATED: the fingerprint detection in auto-rebake.yml decides WHEN this runs (only on a real
|
|
17
17
|
# kernel-toolchain change), so there is no manual approval step. Invoked by hand (workflow_dispatch)
|
|
18
18
|
# OR automatically by auto-rebake.yml (workflow_call) when the kernel toolchain changed.
|
|
@@ -76,16 +76,23 @@ jobs:
|
|
|
76
76
|
gpu_type_id: "NVIDIA RTX A6000",
|
|
77
77
|
allowed_cuda: "",
|
|
78
78
|
}
|
|
79
|
+
# sm89 (Ada): warm on the L40S -- a DATACENTER Ada card with reliable secure-cloud capacity --
|
|
80
|
+
# instead of the consumer RTX 4090 (scarce/cold on secure cloud, where the bake pod often never
|
|
81
|
+
# finished pulling the ~20GB image before the deadline). The cache is sm-keyed, so an L40S-warmed
|
|
82
|
+
# sm89 cache is valid on every sm89 card (4090/L40/L4). NB: verify the exact RunPod gpuTypeId.
|
|
79
83
|
- {
|
|
80
84
|
sm: sm89,
|
|
81
85
|
arch: "8.9",
|
|
82
|
-
gpu_type_id: "NVIDIA
|
|
86
|
+
gpu_type_id: "NVIDIA L40S",
|
|
83
87
|
allowed_cuda: "",
|
|
84
88
|
}
|
|
89
|
+
# sm90 (Hopper): warm on the H200 (141GB, sm90) -- better secure-cloud capacity than the
|
|
90
|
+
# heavily-contended H100 80GB HBM3 (which cold-pull-stalled the bake). Same sm90, so the cache
|
|
91
|
+
# is valid on every H100/H200. H200 is in flash's validated GPU catalog (providers/base.py).
|
|
85
92
|
- {
|
|
86
93
|
sm: sm90,
|
|
87
94
|
arch: "9.0",
|
|
88
|
-
gpu_type_id: "NVIDIA
|
|
95
|
+
gpu_type_id: "NVIDIA H200",
|
|
89
96
|
allowed_cuda: "",
|
|
90
97
|
}
|
|
91
98
|
# Blackwell needs CUDA-13 hosts to JIT its PTX (matches min_cuda_for in the provider). Bake
|
|
@@ -179,38 +186,25 @@ jobs:
|
|
|
179
186
|
if: steps.gate.outputs.run == 'true'
|
|
180
187
|
run: uv sync --extra server
|
|
181
188
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
# RUNPOD_API_KEY + HF_TOKEN come from Infisical (/flash, prod) -- mirrors infisical-entrypoint.sh;
|
|
189
|
-
# the CLI takes the project UUID directly (the secrets-action wants a slug). `env -u
|
|
190
|
-
# FLASH_CHALK_SPEC` drops any chalk-spec override living in /flash so the bake always warms the
|
|
191
|
-
# repo-default chalk (the value the fingerprint hashed) -- otherwise the image could be warmed with
|
|
192
|
-
# one chalk and labeled with another. BASE_IMAGE is digest-pinned so the warm runs inside the EXACT
|
|
193
|
-
# base the per-arch tag will sit on.
|
|
189
|
+
# RUNPOD_API_KEY (the warmup GPU pod) + HF_TOKEN (the temp dataset that ferries code/cache) are
|
|
190
|
+
# native repo Actions secrets, synced from Infisical (prod:/flash-ci) by the github-ci-flash
|
|
191
|
+
# Secret Sync. No Infisical CLI/login at job time -- Infisical stays the source of truth and
|
|
192
|
+
# rotations propagate via the sync. FLASH_CHALK_SPEC isn't synced, so bake_kernel_cache.py uses
|
|
193
|
+
# the repo-default DEFAULT_CHALK_SPEC (the value the fingerprint hashed) automatically. BASE_IMAGE
|
|
194
|
+
# is digest-pinned so the warm runs inside the EXACT base the per-arch tag will sit on.
|
|
194
195
|
- name: Warm kernels on a RunPod ${{ matrix.sm }} GPU -> build/kernel_cache
|
|
195
196
|
if: steps.gate.outputs.run == 'true'
|
|
196
197
|
env:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_PROJECT_ID }}
|
|
198
|
+
RUNPOD_API_KEY: ${{ secrets.RUNPOD_API_KEY }}
|
|
199
|
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
|
200
200
|
BASE_IMAGE: ${{ steps.base.outputs.ref }}
|
|
201
201
|
run: |
|
|
202
|
-
|
|
203
|
-
--
|
|
204
|
-
--
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
uv run python docker/bake_kernel_cache.py \
|
|
209
|
-
--arch "${{ matrix.arch }}" --sm "${{ matrix.sm }}" \
|
|
210
|
-
--gpu-type-id "${{ matrix.gpu_type_id }}" \
|
|
211
|
-
--allowed-cuda "${{ matrix.allowed_cuda }}" \
|
|
212
|
-
--image "$BASE_IMAGE" \
|
|
213
|
-
--out build/kernel_cache
|
|
202
|
+
uv run python docker/bake_kernel_cache.py \
|
|
203
|
+
--arch "${{ matrix.arch }}" --sm "${{ matrix.sm }}" \
|
|
204
|
+
--gpu-type-id "${{ matrix.gpu_type_id }}" \
|
|
205
|
+
--allowed-cuda "${{ matrix.allowed_cuda }}" \
|
|
206
|
+
--image "$BASE_IMAGE" \
|
|
207
|
+
--out build/kernel_cache
|
|
214
208
|
|
|
215
209
|
- name: Free disk space
|
|
216
210
|
if: steps.gate.outputs.run == 'true'
|
|
@@ -63,7 +63,7 @@ RUN pip install --no-cache-dir \
|
|
|
63
63
|
"tilelang==0.1.11" \
|
|
64
64
|
"apache-tvm-ffi==0.1.11" \
|
|
65
65
|
"hf_transfer" \
|
|
66
|
-
"freesolo>=0.2.
|
|
66
|
+
"freesolo>=0.2.52" \
|
|
67
67
|
"runpod"
|
|
68
68
|
# Gated-DeltaNet (Qwen3.5/3.6) runs on flash-linear-attention's Triton kernels. On consumer cards
|
|
69
69
|
# (Ampere/Ada/Blackwell) the Triton path is correct. On Hopper (sm90) fla's GDN chunk_bwd is
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.31
|
|
4
4
|
Summary: Flash — managed LoRA post-training (SFT/GRPO) for Freesolo environments, driven by the `flash` CLI
|
|
5
5
|
Project-URL: Homepage, https://github.com/freesolo-co/flash
|
|
6
6
|
Project-URL: Repository, https://github.com/freesolo-co/flash
|
|
@@ -18,19 +18,20 @@ Requires-Python: <3.13,>=3.11
|
|
|
18
18
|
Provides-Extra: dev
|
|
19
19
|
Requires-Dist: datasets>=2.19; extra == 'dev'
|
|
20
20
|
Requires-Dist: fastapi; extra == 'dev'
|
|
21
|
-
Requires-Dist: freesolo>=0.2.
|
|
21
|
+
Requires-Dist: freesolo>=0.2.52; extra == 'dev'
|
|
22
22
|
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
23
23
|
Requires-Dist: huggingface-hub>=0.34; extra == 'dev'
|
|
24
24
|
Requires-Dist: mypy>=1.13.0; extra == 'dev'
|
|
25
25
|
Requires-Dist: pytest>=9.0.3; extra == 'dev'
|
|
26
26
|
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
27
27
|
Requires-Dist: runpod-flash; extra == 'dev'
|
|
28
|
+
Requires-Dist: transformers<5.11,>=5.6; extra == 'dev'
|
|
28
29
|
Requires-Dist: uvicorn; extra == 'dev'
|
|
29
30
|
Provides-Extra: gpu
|
|
30
31
|
Requires-Dist: accelerate>=1.4; extra == 'gpu'
|
|
31
32
|
Requires-Dist: bitsandbytes>=0.49; extra == 'gpu'
|
|
32
33
|
Requires-Dist: datasets>=2.19; extra == 'gpu'
|
|
33
|
-
Requires-Dist: freesolo>=0.2.
|
|
34
|
+
Requires-Dist: freesolo>=0.2.52; extra == 'gpu'
|
|
34
35
|
Requires-Dist: huggingface-hub>=0.34; extra == 'gpu'
|
|
35
36
|
Requires-Dist: peft>=0.19; extra == 'gpu'
|
|
36
37
|
Requires-Dist: torch==2.10.0; extra == 'gpu'
|
|
@@ -40,10 +41,11 @@ Requires-Dist: vllm==0.19.1; extra == 'gpu'
|
|
|
40
41
|
Provides-Extra: server
|
|
41
42
|
Requires-Dist: datasets>=2.19; extra == 'server'
|
|
42
43
|
Requires-Dist: fastapi; extra == 'server'
|
|
43
|
-
Requires-Dist: freesolo>=0.2.
|
|
44
|
+
Requires-Dist: freesolo>=0.2.52; extra == 'server'
|
|
44
45
|
Requires-Dist: httpx>=0.27; extra == 'server'
|
|
45
46
|
Requires-Dist: huggingface-hub>=0.34; extra == 'server'
|
|
46
47
|
Requires-Dist: runpod-flash; extra == 'server'
|
|
48
|
+
Requires-Dist: transformers<5.11,>=5.6; extra == 'server'
|
|
47
49
|
Requires-Dist: uvicorn; extra == 'server'
|
|
48
50
|
Description-Content-Type: text/markdown
|
|
49
51
|
|
|
@@ -101,10 +103,10 @@ uv run flash-server # control plane (operator-side, run onc
|
|
|
101
103
|
|
|
102
104
|
The control plane owns provider credentials: `RUNPOD_API_KEY` is always required,
|
|
103
105
|
plus the shared `HF_TOKEN`.
|
|
104
|
-
The artifact repo is platform-managed and
|
|
105
|
-
`Freesolo-Co/flashrun-<
|
|
106
|
-
knob and not an operator-wide env var. Clients authenticate with their freesolo
|
|
107
|
-
(`flash login`).
|
|
106
|
+
The artifact repo is platform-managed and environment-scoped (runs for the same environment share
|
|
107
|
+
one private `Freesolo-Co/flashrun-<environment>-<hash>` repo, written by the operator `HF_TOKEN`);
|
|
108
|
+
it is not a user knob and not an operator-wide env var. Clients authenticate with their freesolo
|
|
109
|
+
API key (`flash login`).
|
|
108
110
|
|
|
109
111
|
## Release channels
|
|
110
112
|
|
|
@@ -52,10 +52,10 @@ uv run flash-server # control plane (operator-side, run onc
|
|
|
52
52
|
|
|
53
53
|
The control plane owns provider credentials: `RUNPOD_API_KEY` is always required,
|
|
54
54
|
plus the shared `HF_TOKEN`.
|
|
55
|
-
The artifact repo is platform-managed and
|
|
56
|
-
`Freesolo-Co/flashrun-<
|
|
57
|
-
knob and not an operator-wide env var. Clients authenticate with their freesolo
|
|
58
|
-
(`flash login`).
|
|
55
|
+
The artifact repo is platform-managed and environment-scoped (runs for the same environment share
|
|
56
|
+
one private `Freesolo-Co/flashrun-<environment>-<hash>` repo, written by the operator `HF_TOKEN`);
|
|
57
|
+
it is not a user knob and not an operator-wide env var. Clients authenticate with their freesolo
|
|
58
|
+
API key (`flash login`).
|
|
59
59
|
|
|
60
60
|
## Release channels
|
|
61
61
|
|
|
@@ -66,15 +66,19 @@ def main() -> int:
|
|
|
66
66
|
ap = argparse.ArgumentParser(description="offload the kernel-cache warmup to a RunPod GPU")
|
|
67
67
|
ap.add_argument("--arch", required=True, help="TORCH_CUDA_ARCH_LIST target, e.g. 9.0")
|
|
68
68
|
ap.add_argument("--sm", required=True, help="sm tag, e.g. sm90 (must match the produced cache)")
|
|
69
|
-
ap.add_argument(
|
|
69
|
+
ap.add_argument(
|
|
70
|
+
"--gpu-type-id", required=True, help="RunPod gpuTypeId, e.g. 'NVIDIA H100 80GB HBM3'"
|
|
71
|
+
)
|
|
70
72
|
ap.add_argument("--image", default="ghcr.io/freesolo-co/flash-worker:cu128")
|
|
71
73
|
ap.add_argument("--out", default="build/kernel_cache")
|
|
72
74
|
# the warm pod only pulls the ~20GB image + writes the cache (no model download), so keep this
|
|
73
75
|
# modest -- an over-large ask shrinks the eligible host pool and trips "machine does not have the
|
|
74
76
|
# resources" on scarce classes (e.g. Blackwell sm120 on secure cloud).
|
|
75
77
|
ap.add_argument("--container-disk-gb", type=int, default=60)
|
|
76
|
-
ap.add_argument("--deadline-min", type=int, default=
|
|
77
|
-
ap.add_argument(
|
|
78
|
+
ap.add_argument("--deadline-min", type=int, default=90)
|
|
79
|
+
ap.add_argument(
|
|
80
|
+
"--run-id", default="", help="unique suffix for the temp repo (default: time+uuid)"
|
|
81
|
+
)
|
|
78
82
|
ap.add_argument(
|
|
79
83
|
"--allowed-cuda",
|
|
80
84
|
default="",
|
|
@@ -189,6 +193,59 @@ def main() -> int:
|
|
|
189
193
|
shutil.move(s, d)
|
|
190
194
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
191
195
|
rc = _verify(args.out, args.sm)
|
|
196
|
+
else:
|
|
197
|
+
# Non-success (timeout / pod_died): the pod-side out/ (STARTED marker, warmup.log) lives in
|
|
198
|
+
# the temp dataset the `finally` below deletes, so pull + print it FIRST. out/STARTED present
|
|
199
|
+
# = the warmup entrypoint ran (a timeout then means it hung / was slow); STARTED absent is
|
|
200
|
+
# AMBIGUOUS -- either the pod ran the wrong entrypoint, or the best-effort STARTED upload
|
|
201
|
+
# failed while the warmup is still running (the pod log disambiguates). warmup.log is only
|
|
202
|
+
# uploaded AFTER the warmup process returns, so it's present only if the warmup
|
|
203
|
+
# finished/was killed, not on a pure mid-run hang.
|
|
204
|
+
log(f"outcome={outcome}: pulling pod-side out/ for diagnostics before cleanup")
|
|
205
|
+
try:
|
|
206
|
+
dbg = os.path.join(args.out, ".dbg")
|
|
207
|
+
snapshot_download(
|
|
208
|
+
repo_id=repo,
|
|
209
|
+
repo_type="dataset",
|
|
210
|
+
allow_patterns=["out/**"],
|
|
211
|
+
local_dir=dbg,
|
|
212
|
+
token=token,
|
|
213
|
+
)
|
|
214
|
+
src = os.path.join(dbg, "out")
|
|
215
|
+
if os.path.isdir(src) and os.listdir(src):
|
|
216
|
+
started = os.path.isfile(os.path.join(src, "STARTED"))
|
|
217
|
+
log(f"warmup entrypoint ran (out/STARTED present): {started}")
|
|
218
|
+
for root, _, files in os.walk(src):
|
|
219
|
+
for f in sorted(files):
|
|
220
|
+
p = os.path.join(root, f)
|
|
221
|
+
log(f" out/{os.path.relpath(p, src)} ({os.path.getsize(p)} b)")
|
|
222
|
+
wl = os.path.join(src, "warmup.log")
|
|
223
|
+
if os.path.isfile(wl):
|
|
224
|
+
log("--- warmup.log tail (last 60 lines) ---")
|
|
225
|
+
with open(wl, errors="replace") as wlf:
|
|
226
|
+
for line in wlf.read().splitlines()[-60:]:
|
|
227
|
+
log(f" | {line}")
|
|
228
|
+
else:
|
|
229
|
+
log(
|
|
230
|
+
"no warmup.log (warmup never returned -> mid-run hang or still running at deadline)"
|
|
231
|
+
)
|
|
232
|
+
else:
|
|
233
|
+
# Ambiguous: out/STARTED is uploaded best-effort BEFORE warmup, and the cache tree
|
|
234
|
+
# only lands AFTER warmup returns. So an empty out/ means EITHER the entrypoint
|
|
235
|
+
# never ran (wrong CMD / docker_args) OR the STARTED upload failed while warmup is
|
|
236
|
+
# still mid-run. The pod retries that upload 3x, so a dropped marker is unlikely and
|
|
237
|
+
# wrong-entrypoint is the probable cause -- but don't assert it. The only proof is
|
|
238
|
+
# the pod's OWN console (its "[bake] uploaded out/STARTED" / WARNING lines), which
|
|
239
|
+
# this helper can't fetch (pod terminated below) -> read it in the RunPod dashboard.
|
|
240
|
+
log(
|
|
241
|
+
"pod produced NO out/ -> warmup entrypoint probably never ran "
|
|
242
|
+
"(wrong CMD / docker_args); the only other cause is a failed out/STARTED "
|
|
243
|
+
"upload mid-run (unlikely, it retries 3x) -- confirm via the pod console "
|
|
244
|
+
"in the RunPod dashboard (this CI log does not capture pod stdout)"
|
|
245
|
+
)
|
|
246
|
+
shutil.rmtree(dbg, ignore_errors=True)
|
|
247
|
+
except Exception as e:
|
|
248
|
+
log(f"diagnostic fetch failed (ignore): {str(e)[:160]}")
|
|
192
249
|
finally:
|
|
193
250
|
if pod_id:
|
|
194
251
|
try:
|
|
@@ -28,6 +28,33 @@ def main() -> int:
|
|
|
28
28
|
arch = os.environ.get("BAKE_ARCH", "")
|
|
29
29
|
api = HfApi(token=token)
|
|
30
30
|
|
|
31
|
+
# Upload a STARTED marker FIRST -- before the code download / chalk install / warmup -- so a CI-side
|
|
32
|
+
# timeout can tell whether this entrypoint even ran. Present on a timeout = the warmup started (so it
|
|
33
|
+
# hung or was slow); absent = the pod ran the wrong entrypoint, OR this upload itself failed while
|
|
34
|
+
# the warmup is still running. Retry a few times so a transient HF blip doesn't drop the marker and
|
|
35
|
+
# make the helper misread a slow run as a wrong entrypoint.
|
|
36
|
+
started_uploaded = False
|
|
37
|
+
for attempt in range(3):
|
|
38
|
+
try:
|
|
39
|
+
api.upload_file(
|
|
40
|
+
path_or_fileobj=io.BytesIO(f"started arch={arch}\n".encode()),
|
|
41
|
+
path_in_repo="out/STARTED",
|
|
42
|
+
repo_id=repo,
|
|
43
|
+
repo_type="dataset",
|
|
44
|
+
)
|
|
45
|
+
started_uploaded = True
|
|
46
|
+
print("[bake] uploaded out/STARTED", flush=True)
|
|
47
|
+
break
|
|
48
|
+
except Exception as e:
|
|
49
|
+
# keep the "out/STARTED" token in the line so it greps the same as the success/WARNING lines
|
|
50
|
+
print(f"[bake] out/STARTED upload attempt {attempt + 1}/3 failed: {e}", flush=True)
|
|
51
|
+
if attempt < 2: # no point sleeping after the last attempt
|
|
52
|
+
time.sleep(3)
|
|
53
|
+
if not started_uploaded:
|
|
54
|
+
# The marker never landed; warn loudly so a later "no out/STARTED" is read as upload-failed,
|
|
55
|
+
# not as a wrong-entrypoint run.
|
|
56
|
+
print("[bake] WARNING: out/STARTED never uploaded; warmup still proceeds", flush=True)
|
|
57
|
+
|
|
31
58
|
# the flash code the helper uploaded (upload_code -> code/flash); run the warmup from it.
|
|
32
59
|
snapshot_download(
|
|
33
60
|
repo_id=repo,
|
|
@@ -29,6 +29,18 @@ DEFAULT_GPU = "RTX 5090"
|
|
|
29
29
|
_DEFAULT_VOCAB_SIZE = 248_320
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class ServingCapacity:
|
|
34
|
+
gpu: str
|
|
35
|
+
max_loras: int
|
|
36
|
+
max_lora_rank: int
|
|
37
|
+
max_model_len: int
|
|
38
|
+
serve_model_id: str = ""
|
|
39
|
+
max_num_seqs: int = 0
|
|
40
|
+
max_num_batched_tokens: int = 0
|
|
41
|
+
gpu_memory_utilization: float = 0.0
|
|
42
|
+
|
|
43
|
+
|
|
32
44
|
@dataclass(frozen=True)
|
|
33
45
|
class ModelInfo:
|
|
34
46
|
id: str
|
|
@@ -61,6 +73,10 @@ class ModelInfo:
|
|
|
61
73
|
notes: str = ""
|
|
62
74
|
# 0 = platform default (64 GB) suffices. Runner raises gpu.disk_gb to at least this.
|
|
63
75
|
min_disk_gb: int = 0
|
|
76
|
+
# Deployment capacity of the external freesolo multi-LoRA serving app. This is separate from
|
|
77
|
+
# Flash's training GPU recommendation above; serving uses Modal/vLLM and sizes hot LoRA buffers
|
|
78
|
+
# by max_loras x max_lora_rank at engine init.
|
|
79
|
+
serving: ServingCapacity | None = None
|
|
64
80
|
# "none" / "hybrid" (Qwen3-style) / "always" (can't disable) / "unknown" (open-model policy)
|
|
65
81
|
thinking: str = "none"
|
|
66
82
|
vocab_size: int = _DEFAULT_VOCAB_SIZE
|
|
@@ -79,7 +95,20 @@ class ModelInfo:
|
|
|
79
95
|
hidden_size: int = 0
|
|
80
96
|
|
|
81
97
|
def to_dict(self) -> dict[str, Any]:
|
|
82
|
-
|
|
98
|
+
data = asdict(self)
|
|
99
|
+
serving = data.get("serving")
|
|
100
|
+
if serving is None:
|
|
101
|
+
data.pop("serving", None)
|
|
102
|
+
else:
|
|
103
|
+
for key in (
|
|
104
|
+
"serve_model_id",
|
|
105
|
+
"max_num_seqs",
|
|
106
|
+
"max_num_batched_tokens",
|
|
107
|
+
"gpu_memory_utilization",
|
|
108
|
+
):
|
|
109
|
+
if serving.get(key) in ("", 0, 0.0, None):
|
|
110
|
+
serving.pop(key, None)
|
|
111
|
+
return data
|
|
83
112
|
|
|
84
113
|
|
|
85
114
|
DEFAULT_MODEL = "Qwen/Qwen3.5-4B"
|
|
@@ -94,6 +123,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
94
123
|
algos=("sft", "grpo"),
|
|
95
124
|
min_vram_gb=12,
|
|
96
125
|
recommended_gpu="RTX 4090",
|
|
126
|
+
serving=ServingCapacity(gpu="L4", max_loras=16, max_lora_rank=64, max_model_len=32768),
|
|
97
127
|
thinking="hybrid",
|
|
98
128
|
notes="On-device class SLM (131k ctx); standard Llama architecture.",
|
|
99
129
|
),
|
|
@@ -106,6 +136,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
106
136
|
algos=("sft", "grpo"),
|
|
107
137
|
min_vram_gb=12,
|
|
108
138
|
recommended_gpu="RTX 4090",
|
|
139
|
+
serving=ServingCapacity(gpu="L4", max_loras=16, max_lora_rank=64, max_model_len=32768),
|
|
109
140
|
thinking="hybrid",
|
|
110
141
|
notes="Smallest Qwen3.5; cheap smoke/dev runs with the modern arch.",
|
|
111
142
|
),
|
|
@@ -118,6 +149,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
118
149
|
algos=("sft", "grpo"),
|
|
119
150
|
min_vram_gb=16,
|
|
120
151
|
recommended_gpu="RTX 4090",
|
|
152
|
+
serving=ServingCapacity(gpu="L4", max_loras=16, max_lora_rank=64, max_model_len=32768),
|
|
121
153
|
thinking="hybrid",
|
|
122
154
|
),
|
|
123
155
|
"Qwen/Qwen3.5-4B": ModelInfo(
|
|
@@ -129,6 +161,15 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
129
161
|
algos=("sft", "grpo"),
|
|
130
162
|
min_vram_gb=32,
|
|
131
163
|
recommended_gpu="RTX 5090",
|
|
164
|
+
serving=ServingCapacity(
|
|
165
|
+
gpu="L4",
|
|
166
|
+
serve_model_id="lovedheart/Qwen3.5-4B-FP8",
|
|
167
|
+
max_loras=64,
|
|
168
|
+
max_lora_rank=32,
|
|
169
|
+
max_model_len=8192,
|
|
170
|
+
max_num_seqs=8,
|
|
171
|
+
gpu_memory_utilization=0.98,
|
|
172
|
+
),
|
|
132
173
|
thinking="hybrid",
|
|
133
174
|
notes="Current-gen 4B. GRPO uses the sleep-mode memory recipe (hybrid arch needs "
|
|
134
175
|
"extra engine state-cache); fused DeltaNet kernels ship in the default stack.",
|
|
@@ -145,6 +186,15 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
145
186
|
grpo_min_vram_gb=80,
|
|
146
187
|
quant="bf16",
|
|
147
188
|
recommended_gpu="A100 PCIe",
|
|
189
|
+
serving=ServingCapacity(
|
|
190
|
+
gpu="L4",
|
|
191
|
+
serve_model_id="lovedheart/Qwen3.5-9B-FP8",
|
|
192
|
+
max_loras=44,
|
|
193
|
+
max_lora_rank=32,
|
|
194
|
+
max_model_len=8192,
|
|
195
|
+
max_num_seqs=8,
|
|
196
|
+
gpu_memory_utilization=0.98,
|
|
197
|
+
),
|
|
148
198
|
thinking="hybrid",
|
|
149
199
|
notes="bf16 LoRA. ~19 GB of weights; SFT fits a 48 GB card, while colocated GRPO "
|
|
150
200
|
"(two bf16 copies + KV + the 248k-vocab fp32 logits) needs an 80 GB-class card "
|
|
@@ -174,6 +224,16 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
174
224
|
sleep_unsupported=True,
|
|
175
225
|
quant="bf16",
|
|
176
226
|
recommended_gpu="H200",
|
|
227
|
+
serving=ServingCapacity(
|
|
228
|
+
gpu="A100-80GB",
|
|
229
|
+
serve_model_id="Qwen/Qwen3.6-35B-A3B-FP8",
|
|
230
|
+
max_loras=12,
|
|
231
|
+
max_lora_rank=32,
|
|
232
|
+
max_model_len=8192,
|
|
233
|
+
max_num_seqs=8,
|
|
234
|
+
max_num_batched_tokens=4096,
|
|
235
|
+
gpu_memory_utilization=0.98,
|
|
236
|
+
),
|
|
177
237
|
thinking="hybrid",
|
|
178
238
|
min_disk_gb=200,
|
|
179
239
|
notes="MoE (35B total / ~3B active), bf16 LoRA. SFT runs on the 141 GB H200 (the ~70 GB "
|
|
@@ -199,6 +259,24 @@ def get_model(model_id: str) -> ModelInfo:
|
|
|
199
259
|
) from exc
|
|
200
260
|
|
|
201
261
|
|
|
262
|
+
def serving_lora_rank_cap(model: str | ModelInfo | None) -> int | None:
|
|
263
|
+
"""Return the model's serving LoRA rank cap, or None when Flash has no local cap.
|
|
264
|
+
|
|
265
|
+
Serving capacity is model-specific: small serving models currently allow rank 64, while larger
|
|
266
|
+
serving paths can cap at rank 32. Unknown/open-policy models intentionally return None instead
|
|
267
|
+
of inheriting a global fallback.
|
|
268
|
+
"""
|
|
269
|
+
if isinstance(model, ModelInfo):
|
|
270
|
+
info = model
|
|
271
|
+
elif isinstance(model, str) and model.strip():
|
|
272
|
+
info = MODELS.get(model.strip())
|
|
273
|
+
else:
|
|
274
|
+
info = None
|
|
275
|
+
if info is None or info.serving is None:
|
|
276
|
+
return None
|
|
277
|
+
return int(info.serving.max_lora_rank)
|
|
278
|
+
|
|
279
|
+
|
|
202
280
|
def vocab_size_for(model_id: str) -> int:
|
|
203
281
|
"""Curated vocab_size for a model, or the safe default for open-model-policy entries."""
|
|
204
282
|
info = MODELS.get(model_id)
|
|
@@ -72,7 +72,11 @@ _HELP_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
|
72
72
|
(
|
|
73
73
|
"environments",
|
|
74
74
|
[
|
|
75
|
-
("env", "
|
|
75
|
+
("env setup", "scaffold a starter Freesolo environment"),
|
|
76
|
+
("env list", "list local environment sources"),
|
|
77
|
+
("env push", "upload a local environment"),
|
|
78
|
+
("env pull", "download a published environment or file"),
|
|
79
|
+
("env delete", "delete a published environment"),
|
|
76
80
|
],
|
|
77
81
|
),
|
|
78
82
|
(
|
|
@@ -160,7 +164,9 @@ class _FlashParser(_ThemedParser):
|
|
|
160
164
|
return super().format_help()
|
|
161
165
|
usage = f"{CLI_NAME} [--debug] [-v] <command> [args]"
|
|
162
166
|
footers = [
|
|
163
|
-
f"new here? run `{CLI_NAME} login`, then `{CLI_NAME}
|
|
167
|
+
f"new here? run `{CLI_NAME} login`, then `{CLI_NAME} env setup`",
|
|
168
|
+
f"train after publishing: `{CLI_NAME} env push --name my-env .`, "
|
|
169
|
+
f"then `{CLI_NAME} train configs/rl.toml`",
|
|
164
170
|
f"any command in depth: `{CLI_NAME} <command> --help`",
|
|
165
171
|
"docs: https://freesolo.co/docs",
|
|
166
172
|
]
|
|
@@ -338,7 +338,11 @@ def cmd_env_list(args) -> int:
|
|
|
338
338
|
|
|
339
339
|
|
|
340
340
|
def _cmd_train_cost(args) -> int:
|
|
341
|
-
"""
|
|
341
|
+
"""`flash train --cost`: print the pre-flight USD cost for the config and exit (no submit).
|
|
342
|
+
|
|
343
|
+
Catalog-only and deterministic; SFT uses the actual local training-token count when the env
|
|
344
|
+
and tokenizer are importable. An uncapped SFT run must be able to count the env's train split,
|
|
345
|
+
otherwise it errors instead of guessing a dataset size."""
|
|
342
346
|
from flash.cost import estimate_cost
|
|
343
347
|
|
|
344
348
|
spec = spec_from_file(
|
|
@@ -592,7 +592,8 @@ def cost_panel(est) -> str:
|
|
|
592
592
|
),
|
|
593
593
|
(
|
|
594
594
|
"setup",
|
|
595
|
-
f"{est.setup_seconds / 60:.1f} min
|
|
595
|
+
f"{est.setup_seconds / 60:.1f} min "
|
|
596
|
+
f"{_dim(f'(cold start: boot + deps + model load{setup_extra}; not billed)')}",
|
|
596
597
|
),
|
|
597
598
|
("per step", f"{est.seconds_per_step:.2f} s"),
|
|
598
599
|
(
|
|
@@ -601,6 +602,7 @@ def cost_panel(est) -> str:
|
|
|
601
602
|
+ (_paint(" [capped at wall-clock limit]", _RED) if est.wall_capped else ""),
|
|
602
603
|
),
|
|
603
604
|
("wall clock", f"{est.wall_clock_hours:.2f} h"),
|
|
605
|
+
("billable", f"{est.billable_hours:.2f} h {_dim('(training only)')}"),
|
|
604
606
|
]
|
|
605
607
|
panel = _kv(pairs)
|
|
606
608
|
total = f" {_paint('TOTAL'.ljust(10), _GRAY, '1')} {_paint(_glyph('·', '-'), _FAINT)} {_paint(f'${est.total_usd:.2f}', _TEAL, '1')}"
|
|
@@ -302,9 +302,12 @@ Pick SFT when you already have good answers and want the model to imitate them.
|
|
|
302
302
|
more.
|
|
303
303
|
- **SFT is a great warm start for GRPO.** SFT first to teach the format and a competent
|
|
304
304
|
baseline, then GRPO to optimize past it. Across that lineage keep the **same base
|
|
305
|
-
model
|
|
306
|
-
|
|
307
|
-
|
|
305
|
+
model**. For text-only continued adapters, keep the same adapter shape. For VL
|
|
306
|
+
warm-starts, Flash trains a fresh GRPO LoRA and rank-stacks it with the SFT LoRA
|
|
307
|
+
for deployment, so `SFT rank + GRPO rank` must stay within the selected model's
|
|
308
|
+
effective serving `max_lora_rank`. That cap comes from the serving/model policy:
|
|
309
|
+
some small serving models allow rank 64, while larger serving paths can cap at rank
|
|
310
|
+
32. Flash preflights the rank-stacked deploy rank against that model-specific cap.
|
|
308
311
|
|
|
309
312
|
```toml
|
|
310
313
|
# configs/rl.toml — warm-start GRPO from the SFT run's adapter
|
|
@@ -314,8 +317,8 @@ algorithm = "grpo"
|
|
|
314
317
|
# paste the full adapter_ref `flash status <sft-run-id>` prints, verbatim
|
|
315
318
|
# (shape: <owner>/<repo>:sft/<run-id> — the owner/repo prefix is required)
|
|
316
319
|
init_from_adapter = "your-org/your-repo:sft/<sft-run-id>"
|
|
317
|
-
lora_rank =
|
|
318
|
-
lora_alpha =
|
|
320
|
+
lora_rank = 16 # for VL warm-starts, SFT rank + GRPO rank must fit the effective serving cap
|
|
321
|
+
lora_alpha = 32
|
|
319
322
|
```
|
|
320
323
|
|
|
321
324
|
SFT is **epoch-driven** (`epochs`); GRPO is **step-driven** (`steps`).
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
"""Flash training-cost estimator: deterministic pre-flight estimate
|
|
1
|
+
"""Flash training-cost estimator: a deterministic, equation-based pre-flight estimate
|
|
2
|
+
(``estimate_cost``) of cost = training-only GPU hours x market $/hr. No output multiplier."""
|
|
2
3
|
|
|
3
4
|
from __future__ import annotations
|
|
4
5
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
"""The analytical cost model: total =
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""The analytical cost model: total = training-only GPU hours x GPU $/hr.
|
|
2
|
+
|
|
3
|
+
Elapsed wall clock still includes cold-start setup + steps x per-step time, but setup/cold-start
|
|
4
|
+
is reported as non-billable. GRPO splits each step into a vLLM rollout + reward grading +
|
|
5
|
+
policy/reference update.
|
|
6
|
+
"""
|
|
4
7
|
|
|
5
8
|
from __future__ import annotations
|
|
6
9
|
|
|
@@ -38,11 +41,11 @@ REWARD_CONCURRENCY = 16.0
|
|
|
38
41
|
|
|
39
42
|
# Cold-start overhead (seconds): container boot + deps + model load (+ vLLM init for GRPO).
|
|
40
43
|
#
|
|
41
|
-
# Calibrated against a real fresh-worker run (0.8B SFT, RTX 3090 @ $0.239/hr) whose
|
|
44
|
+
# Calibrated against a real fresh-worker run (0.8B SFT, RTX 3090 @ $0.239/hr) whose elapsed wall
|
|
42
45
|
# was ~708s for only ~26 priced steps -- i.e. cold start, not training, dominated. A fresh worker
|
|
43
46
|
# spent ~12.5 min in `sft_model_load` alone (download + checkpoint deserialize + GPU placement +
|
|
44
|
-
# framework/CUDA init), so the MODEL-LOAD term -- not boot/deps --
|
|
45
|
-
#
|
|
47
|
+
# framework/CUDA init), so the MODEL-LOAD term -- not boot/deps -- dominates a short job's elapsed
|
|
48
|
+
# time. MODEL_LOAD_BASE_S is the fixed (size-independent) load/init overhead; the download term on
|
|
46
49
|
# top of it scales with checkpoint size, so bigger models pay a longer cold start.
|
|
47
50
|
WORKER_BOOT_S = 120.0 # container pull + start
|
|
48
51
|
DEPS_INSTALL_S = 90.0 # pip/uv resolve + install
|
|
@@ -64,9 +67,9 @@ def _fmt_duration(seconds: float) -> str:
|
|
|
64
67
|
|
|
65
68
|
|
|
66
69
|
def setup_seconds(config: RunConfig) -> float:
|
|
67
|
-
"""Cold-start wall time
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
"""Cold-start wall time before the first optimizer step: container boot + deps + model load
|
|
71
|
+
(a fixed deserialize/placement/init base + a size-scaled download), plus vLLM init for GRPO.
|
|
72
|
+
This elapsed setup time is reported but not included in customer-facing cost."""
|
|
70
73
|
model_load = MODEL_LOAD_BASE_S + download_weight_gb(config.model_id) / DOWNLOAD_RATE_GBPS
|
|
71
74
|
s = WORKER_BOOT_S + DEPS_INSTALL_S + model_load
|
|
72
75
|
if config.is_grpo:
|
|
@@ -96,6 +99,15 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
|
96
99
|
return gen_s + reward_s + update_s
|
|
97
100
|
|
|
98
101
|
|
|
102
|
+
def sft_seconds_for_tokens(config: RunConfig, gpu: str, train_tokens: float) -> float:
|
|
103
|
+
"""SFT steady-state wall time for an actual token count on ``gpu``."""
|
|
104
|
+
n = config.normalized()
|
|
105
|
+
params = active_params_b(n.model_id) * 1e9
|
|
106
|
+
peak = gpu_tflops(gpu) * 1e12
|
|
107
|
+
flops = SFT_FLOPS_PER_TOKEN_PER_PARAM * params * train_tokens
|
|
108
|
+
return flops / (peak * MFU_SFT_TRAIN)
|
|
109
|
+
|
|
110
|
+
|
|
99
111
|
def select_gpu(config: RunConfig) -> tuple[str, int]:
|
|
100
112
|
"""(chosen GPU class, required VRAM GB): the cheapest fitting class for the cost.
|
|
101
113
|
|
|
@@ -128,6 +140,8 @@ def _notes(config: RunConfig, raw_train_s: float, wall_capped: bool, cap_s: floa
|
|
|
128
140
|
+ (f", env {n.environment}" if n.environment else "")
|
|
129
141
|
+ ") + policy+reference update"
|
|
130
142
|
)
|
|
143
|
+
elif n.train_tokens is not None:
|
|
144
|
+
notes.append(f"SFT priced on {n.train_tokens:,} actual train tokens")
|
|
131
145
|
notes.append(f"GPU sized with {vram_headroom() - 1:.0%} VRAM headroom; static GPU $/hr")
|
|
132
146
|
if wall_capped:
|
|
133
147
|
notes.append(
|
|
@@ -141,14 +155,18 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
|
|
|
141
155
|
"""Deterministic pre-flight cost calculation."""
|
|
142
156
|
gpu, need = select_gpu(config)
|
|
143
157
|
hourly = gpu_hourly_usd(gpu, provider=config.provider)
|
|
144
|
-
# Mirror the runner's max(60, max_wall_seconds) floor so
|
|
158
|
+
# Mirror the runner's max(60, max_wall_seconds) floor so elapsed wall time is not undercounted.
|
|
145
159
|
cap_s = max(60.0, float(config.max_wall_seconds)) if config.max_wall_seconds is not None else wall_cap_s
|
|
146
160
|
|
|
147
161
|
setup = setup_seconds(config)
|
|
148
162
|
sps = seconds_per_step(config, gpu)
|
|
149
163
|
raw_train = config.steps * sps
|
|
164
|
+
if not config.is_grpo and config.train_tokens is not None:
|
|
165
|
+
raw_train = sft_seconds_for_tokens(config, gpu, config.train_tokens)
|
|
166
|
+
sps = raw_train / config.steps
|
|
150
167
|
|
|
151
|
-
# The cap is on total wall; setup is billed
|
|
168
|
+
# The cap is on total elapsed wall; setup is reported but not billed, so only training
|
|
169
|
+
# contributes to total_usd.
|
|
152
170
|
wall_capped = (setup + raw_train) > cap_s
|
|
153
171
|
setup = min(setup, cap_s)
|
|
154
172
|
train = max(0.0, cap_s - setup) if wall_capped else raw_train
|
|
@@ -168,6 +186,6 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
|
|
|
168
186
|
train_seconds=train,
|
|
169
187
|
wall_clock_seconds=wall,
|
|
170
188
|
wall_capped=wall_capped,
|
|
171
|
-
total_usd=
|
|
189
|
+
total_usd=train / 3600.0 * hourly,
|
|
172
190
|
notes=_notes(config, raw_train, wall_capped, cap_s),
|
|
173
191
|
)
|