freesolo-flash-dev 0.2.37__tar.gz → 0.2.39__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.37 → freesolo_flash_dev-0.2.39}/Dockerfile.worker +5 -6
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/PKG-INFO +1 -1
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docker/Dockerfile.kernelcache +1 -1
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docker/bake_kernel_cache.py +4 -4
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docker/kernel_fingerprint.py +47 -7
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/catalog.py +27 -14
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/cli/__init__.py +1 -2
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/cli/envpush.py +16 -34
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/cli/render.py +3 -1
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/cost/analytical.py +21 -6
- freesolo_flash_dev-0.2.39/flash/cost/facts.py +161 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/chalk_kernels.py +30 -7
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/vram.py +13 -3
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/kernel_warmup.py +18 -98
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/rl.py +15 -85
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/sft.py +93 -26
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/envs/pull.py +3 -87
- freesolo_flash_dev-0.2.39/flash/providers/__init__.py +63 -0
- freesolo_flash_dev-0.2.39/flash/providers/_hf_artifacts.py +177 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/_instance.py +76 -2
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/_instance_bootstrap.py +38 -3
- freesolo_flash_dev-0.2.39/flash/providers/_instance_poll.py +438 -0
- freesolo_flash_dev-0.2.39/flash/providers/_instance_provider.py +171 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/_poll.py +12 -0
- freesolo_flash_dev-0.2.37/flash/providers/runpod/train/deps.py → freesolo_flash_dev-0.2.39/flash/providers/_worker.py +93 -5
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/allocator.py +28 -39
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/base.py +128 -3
- freesolo_flash_dev-0.2.39/flash/providers/lambdalabs/__init__.py +154 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/lambdalabs/gpus.py +2 -9
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/lambdalabs/jobs/__init__.py +80 -230
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/lambdalabs/jobs/builders.py +31 -31
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/realized.py +7 -2
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/__init__.py +22 -1
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/gpus.py +2 -2
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/jobs.py +34 -85
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/pricing.py +2 -2
- freesolo_flash_dev-0.2.39/flash/providers/runpod/train/__init__.py +29 -0
- freesolo_flash_dev-0.2.39/flash/providers/runpod/train/deps.py +19 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/train/endpoints.py +33 -3
- freesolo_flash_dev-0.2.39/flash/providers/vast/__init__.py +194 -0
- freesolo_flash_dev-0.2.39/flash/providers/vast/api.py +318 -0
- freesolo_flash_dev-0.2.39/flash/providers/vast/auth.py +16 -0
- freesolo_flash_dev-0.2.39/flash/providers/vast/jobs/__init__.py +656 -0
- freesolo_flash_dev-0.2.39/flash/providers/vast/jobs/builders.py +197 -0
- freesolo_flash_dev-0.2.39/flash/providers/vast/preflight.py +17 -0
- freesolo_flash_dev-0.2.39/flash/providers/vast/pricing.py +147 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/runner/__init__.py +16 -2
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/runner/deploy.py +28 -1
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/runner/lifecycle.py +68 -22
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/_runtime.py +153 -8
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/app.py +8 -7
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/pyproject.toml +7 -10
- freesolo_flash_dev-0.2.39/tests/_helpers/vast.py +38 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/conftest.py +6 -5
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/live/conftest.py +6 -5
- freesolo_flash_dev-0.2.39/tests/live/test_vast_live.py +56 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_allocator.py +152 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cancel_remote.py +1 -2
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_catalog_consistency.py +32 -21
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_chalk_kernels.py +34 -13
- freesolo_flash_dev-0.2.39/tests/test_cost_estimate.py +231 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_env_pull.py +99 -411
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_flash_worker.py +104 -11
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_gpus.py +31 -9
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_grpo_params.py +33 -40
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_jobs.py +153 -17
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_kernel_cache.py +38 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_kernel_fingerprint.py +9 -2
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_lambda_runner.py +84 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_lora_rank_preflight.py +11 -6
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_oom_escalate_gpu.py +7 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_orchestrator_flash.py +4 -4
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_providers_symmetry.py +110 -14
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_resume_on_retry.py +120 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_serve.py +9 -6
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_server_api.py +437 -1
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_spec_and_validation.py +9 -6
- freesolo_flash_dev-0.2.39/tests/test_vast_api.py +462 -0
- freesolo_flash_dev-0.2.39/tests/test_vast_offers.py +250 -0
- freesolo_flash_dev-0.2.39/tests/test_vast_runner.py +1471 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_vl_warmstart_recombine.py +20 -14
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_weight_cache.py +1 -1
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/uv.lock +1 -1
- freesolo_flash_dev-0.2.37/flash/cost/facts.py +0 -110
- freesolo_flash_dev-0.2.37/flash/providers/__init__.py +0 -37
- freesolo_flash_dev-0.2.37/flash/providers/lambdalabs/__init__.py +0 -122
- freesolo_flash_dev-0.2.37/flash/providers/lambdalabs/train.py +0 -20
- freesolo_flash_dev-0.2.37/flash/providers/runpod/train/__init__.py +0 -114
- freesolo_flash_dev-0.2.37/tests/test_cost_estimate.py +0 -84
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.env.example +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/README.md +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docs/cli-style/README.md +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docs/cli-style/generate.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docs/cli-style/index.html +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docs/cli-style/preview.png +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docs/cli-style/themed-errors.png +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/docs/kernel-cache.md +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/cli/commands.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/cli/training_doc.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/client/http.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/adapter.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/hf.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/envs/adapter.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/envs/loader.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/lora_rank.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/schema/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/serve/deploy.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/flash/spec.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_charge_pricing.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cli_commands.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_client.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_vl_weight_sync.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-0.2.37 → freesolo_flash_dev-0.2.39}/tests/test_worker_thinking.py +0 -0
|
@@ -42,7 +42,7 @@ ENV TRITON_CACHE_DIR=/opt/flash/kernelcache/triton \
|
|
|
42
42
|
RUN mkdir -p /opt/flash/kernelcache/triton /opt/flash/kernelcache/inductor \
|
|
43
43
|
/opt/flash/kernelcache/flashinfer_cubin /opt/flash/kernelcache/flashinfer
|
|
44
44
|
|
|
45
|
-
# build-essential for Triton/Inductor
|
|
45
|
+
# build-essential for Triton/Inductor/chalk + any source builds; git for pip VCS installs.
|
|
46
46
|
RUN apt-get update && apt-get install -y --no-install-recommends build-essential git curl \
|
|
47
47
|
&& rm -rf /var/lib/apt/lists/*
|
|
48
48
|
|
|
@@ -58,7 +58,6 @@ RUN pip install --no-cache-dir \
|
|
|
58
58
|
"huggingface_hub>=0.25" \
|
|
59
59
|
"accelerate>=1.4" \
|
|
60
60
|
"wandb>=0.17" \
|
|
61
|
-
"liger-kernel>=0.5" \
|
|
62
61
|
"flash-linear-attention @ git+https://github.com/fla-org/flash-linear-attention.git@f0e213dbd8b5fb90c3c7eca869ac1706d5377139" \
|
|
63
62
|
"tilelang==0.1.11" \
|
|
64
63
|
"apache-tvm-ffi==0.1.11" \
|
|
@@ -148,7 +147,7 @@ RUN TORCH_CUDA_ARCH_LIST="8.0 8.6 8.9 9.0 10.0 12.0" CAUSAL_CONV1D_FORCE_BUILD=T
|
|
|
148
147
|
# affect another tenant). The redirect can't be a static image ENV: the volume mounts at a fixed path
|
|
149
148
|
# only at runtime and only when attached, so a baked ENV would break cache-less (no-volume) cold runs.
|
|
150
149
|
#
|
|
151
|
-
# Compiled-kernel cache: OPT-IN bake, JIT fallback. The fused Triton (
|
|
150
|
+
# Compiled-kernel cache: OPT-IN bake, JIT fallback. The fused Triton (fla/chalk),
|
|
152
151
|
# TorchInductor, and Hopper tilelang kernels the trainer builds on first use are content-addressed
|
|
153
152
|
# by GPU ARCH + toolchain version, so they can only be compiled on a real GPU of each catalog arch
|
|
154
153
|
# (8.0/8.6/8.9/9.0/12.0) — which the CI image builder normally lacks (the FA2 *source* path above
|
|
@@ -187,7 +186,7 @@ RUN if [ "${BUILD_KERNEL_CACHE}" = "true" ] && [ -f /opt/flash/kernelcache/mega_
|
|
|
187
186
|
# Sanity: the worker imports must succeed (catches a broken stack at build time, not run time).
|
|
188
187
|
# Import EVERY package the worker runtime uses (derived from a full AST scan of the engine/envs/
|
|
189
188
|
# bootstrap code + the WORKER_DEPS list above): the core stack, the optimizer/accelerator libs
|
|
190
|
-
# (
|
|
189
|
+
# (flash-linear-attention=fla, flash_attn, bitsandbytes, triton), and the HF/env libs. A
|
|
191
190
|
# missing or ABI-broken package fails the build HERE instead of silently shipping (the FA wheel in
|
|
192
191
|
# particular can install yet fail to import against this torch). cuDNN comes from the base image.
|
|
193
192
|
# flash_attn is BEST-EFFORT (the install above tolerates a build failure -> SDPA fallback), so it
|
|
@@ -195,7 +194,7 @@ RUN if [ "${BUILD_KERNEL_CACHE}" = "true" ] && [ -f /opt/flash/kernelcache/mega_
|
|
|
195
194
|
# could never be produced. The rest of the stack is mandatory.
|
|
196
195
|
RUN python -c "import importlib.metadata as M; \
|
|
197
196
|
import torch, transformers, trl, peft, vllm, datasets, huggingface_hub, accelerate, wandb, \
|
|
198
|
-
|
|
197
|
+
fla, bitsandbytes, hf_transfer, triton, freesolo, runpod; \
|
|
199
198
|
import importlib; \
|
|
200
199
|
fa_ok = False; \
|
|
201
200
|
exec('try:\n import flash_attn\n fa_ok = True\nexcept Exception:\n fa_ok = False'); \
|
|
@@ -207,7 +206,7 @@ cudnn = torch.backends.cudnn.version(); assert cudnn, 'cuDNN not compiled into t
|
|
|
207
206
|
ver = lambda n: M.version(n); \
|
|
208
207
|
print('worker stack OK | torch', torch.__version__, '| cudnn', cudnn, '| triton', triton.__version__, '| flash_attn', 'present' if fa_ok else 'ABSENT (SDPA fallback)', '| flash_attn_3', 'present (Hopper FA3)' if fa3_ok else 'ABSENT (SDPA on Hopper)', '| flashinfer', 'present (B200 MoE/attn)' if fi_ok else 'ABSENT (Triton fused-MoE fallback)'); \
|
|
209
208
|
[print(' ', d, ver(d)) for d in ['transformers','trl','peft','vllm','flashinfer-python','datasets','huggingface-hub', \
|
|
210
|
-
'accelerate','wandb','
|
|
209
|
+
'accelerate','wandb','flash-linear-attention','bitsandbytes', \
|
|
211
210
|
'hf-transfer','freesolo','runpod']]"
|
|
212
211
|
|
|
213
212
|
# RunPod Serverless worker entrypoint (Pattern D — Flash custom image). Generate /rp_handler.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.39
|
|
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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Thin per-arch kernel-cache layer.
|
|
2
2
|
#
|
|
3
3
|
# Bakes a warmup-produced mega-cache ON TOP of the EXACT base image the warmup ran inside, so the
|
|
4
|
-
# cache's toolchain (torch/triton/fla/
|
|
4
|
+
# cache's toolchain (torch/triton/fla/chalk) is guaranteed to match the image it ships in. Rebuilding
|
|
5
5
|
# the full Dockerfile.worker from the checkout instead could drift from the published base and bake a
|
|
6
6
|
# cache that silently won't load (cold-JIT fallback) -- this avoids that by construction.
|
|
7
7
|
#
|
|
@@ -100,16 +100,16 @@ def main() -> int:
|
|
|
100
100
|
entry_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bake_pod_entry.py")
|
|
101
101
|
allowed_cuda = [v.strip() for v in args.allowed_cuda.split(",") if v.strip()] or None
|
|
102
102
|
|
|
103
|
-
#
|
|
104
|
-
#
|
|
103
|
+
# The chalk install spec the production worker uses, so the bake warms the same kernels. Keep the
|
|
104
|
+
# default import fail-loud: a stale hidden fallback would silently bake against the wrong chalk SHA.
|
|
105
105
|
chalk_spec = os.environ.get("FLASH_CHALK_SPEC", "").strip()
|
|
106
106
|
if not chalk_spec:
|
|
107
107
|
try:
|
|
108
108
|
from flash.providers.runpod.train.deps import DEFAULT_CHALK_SPEC
|
|
109
109
|
|
|
110
110
|
chalk_spec = DEFAULT_CHALK_SPEC
|
|
111
|
-
except Exception:
|
|
112
|
-
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
raise RuntimeError("could not import DEFAULT_CHALK_SPEC for kernel-cache bake") from exc
|
|
113
113
|
|
|
114
114
|
_upload_flash_code(api, repo, token)
|
|
115
115
|
|
|
@@ -11,7 +11,7 @@ Two fingerprints, because "out of date" has two flavors with very different cost
|
|
|
11
11
|
* fp_cache = hash of the inputs whose kernels live in the baked mega-cache
|
|
12
12
|
(torch.compiler.save_cache_artifacts: Triton/Inductor/torch.compile). Changing one of these
|
|
13
13
|
INVALIDATES the cache, so the per-arch image needs a real GPU re-warm. These are: the base
|
|
14
|
-
FROM image (torch+triton), the fla git pin,
|
|
14
|
+
FROM image (torch+triton), the fla git pin, tilelang, apache-tvm-ffi, the default chalk
|
|
15
15
|
spec, and the warmup script itself (it decides which kernels get compiled).
|
|
16
16
|
|
|
17
17
|
* fp_base = hash of everything else baked into :cu128 that is NOT in the cache (FA2/FA3 wheels,
|
|
@@ -27,7 +27,7 @@ time, which is why every parse below FAILS LOUD rather than hashing a None.
|
|
|
27
27
|
Known limitations (deliberately scoped -- each only ever costs a recoverable cold-JIT, never
|
|
28
28
|
correctness, and the alternatives over-fire the paid GPU bake):
|
|
29
29
|
* fp_cache hashes the Dockerfile dep PINS, not pip-resolved versions. A cache-affecting range
|
|
30
|
-
(
|
|
30
|
+
(the chalk spec) could resolve a newer build on a later worker-image rebuild with
|
|
31
31
|
no text change, leaving fp_cache unmoved. Pin those exactly for airtight coverage. (fp_base DOES
|
|
32
32
|
hash the whole Dockerfile.worker, so arbitrary base edits -- apt/ENV/CMD/cache-dir -- still
|
|
33
33
|
trigger a free re-layer; only a cache-affecting change that isn't a parsed pin slips through.)
|
|
@@ -43,6 +43,7 @@ stdlib only, no flash/torch import, so it runs under a bare python3 in CI (no uv
|
|
|
43
43
|
from __future__ import annotations
|
|
44
44
|
|
|
45
45
|
import argparse
|
|
46
|
+
import ast
|
|
46
47
|
import hashlib
|
|
47
48
|
import json
|
|
48
49
|
import re
|
|
@@ -56,7 +57,7 @@ LABEL_REVISION = "org.opencontainers.image.revision"
|
|
|
56
57
|
|
|
57
58
|
# pip specs in Dockerfile.worker's main stack whose kernels land in the mega-cache. everything else
|
|
58
59
|
# in that block is a base-layer dep (fp_base). fla is matched separately (it carries the git sha).
|
|
59
|
-
_CACHE_PKGS = {"
|
|
60
|
+
_CACHE_PKGS = {"tilelang", "apache-tvm-ffi"}
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
def _search(pattern: str, text: str, what: str, *, flags: int = 0) -> str:
|
|
@@ -71,6 +72,44 @@ def _sha256_file(path: Path) -> str:
|
|
|
71
72
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
72
73
|
|
|
73
74
|
|
|
75
|
+
def _python_string_constant(text: str, name: str, what: str) -> str:
|
|
76
|
+
"""Resolve a simple module-level string constant, including f-strings using prior constants."""
|
|
77
|
+
values: dict[str, str] = {}
|
|
78
|
+
|
|
79
|
+
def _eval(node: ast.AST) -> str | None:
|
|
80
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
81
|
+
return node.value
|
|
82
|
+
if isinstance(node, ast.JoinedStr):
|
|
83
|
+
parts: list[str] = []
|
|
84
|
+
for value in node.values:
|
|
85
|
+
if isinstance(value, ast.Constant) and isinstance(value.value, str):
|
|
86
|
+
parts.append(value.value)
|
|
87
|
+
elif (
|
|
88
|
+
isinstance(value, ast.FormattedValue)
|
|
89
|
+
and isinstance(value.value, ast.Name)
|
|
90
|
+
and value.value.id in values
|
|
91
|
+
):
|
|
92
|
+
parts.append(values[value.value.id])
|
|
93
|
+
else:
|
|
94
|
+
return None
|
|
95
|
+
return "".join(parts)
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
tree = ast.parse(text)
|
|
99
|
+
for node in tree.body:
|
|
100
|
+
if not isinstance(node, ast.Assign):
|
|
101
|
+
continue
|
|
102
|
+
value = _eval(node.value)
|
|
103
|
+
if value is None:
|
|
104
|
+
continue
|
|
105
|
+
for target in node.targets:
|
|
106
|
+
if isinstance(target, ast.Name):
|
|
107
|
+
values[target.id] = value
|
|
108
|
+
if name not in values:
|
|
109
|
+
raise ValueError(f"kernel_fingerprint: could not parse {what}")
|
|
110
|
+
return values[name]
|
|
111
|
+
|
|
112
|
+
|
|
74
113
|
def _pip_stack_specs(dockerfile: str) -> list[str]:
|
|
75
114
|
"""The quoted specs from Dockerfile.worker's first `pip install` block (the pinned worker stack)."""
|
|
76
115
|
specs: list[str] = []
|
|
@@ -104,13 +143,13 @@ def collect_inputs(
|
|
|
104
143
|
|
|
105
144
|
Each value comes from where the IMAGE actually gets it:
|
|
106
145
|
* pins / FROM / causal-conv1d from Dockerfile.worker (what the image is built from),
|
|
107
|
-
* default chalk spec textually from
|
|
146
|
+
* default chalk spec textually from _worker.py (what the bake warms),
|
|
108
147
|
* FA2/FA3 from worker-image.yml's build-args (overridable via fa2_spec/fa3_spec for the
|
|
109
148
|
resolved-build-arg case), and file hashes for the warmup/handler sources.
|
|
110
149
|
base_inputs_partial does NOT yet include fp_cache; compute_fingerprints folds it in.
|
|
111
150
|
"""
|
|
112
151
|
dockerfile = (root / "Dockerfile.worker").read_text()
|
|
113
|
-
|
|
152
|
+
worker_pkg = (root / "flash" / "providers" / "_worker.py").read_text()
|
|
114
153
|
worker_image_yml = (root / ".github" / "workflows" / "worker-image.yml").read_text()
|
|
115
154
|
|
|
116
155
|
specs = _pip_stack_specs(dockerfile)
|
|
@@ -129,12 +168,13 @@ def collect_inputs(
|
|
|
129
168
|
raise ValueError(
|
|
130
169
|
"kernel_fingerprint: fla spec missing or not pinned to a 40-char commit sha"
|
|
131
170
|
)
|
|
132
|
-
chalk =
|
|
171
|
+
chalk = _python_string_constant(
|
|
172
|
+
worker_pkg, "DEFAULT_CHALK_SPEC", "_worker.py DEFAULT_CHALK_SPEC"
|
|
173
|
+
)
|
|
133
174
|
|
|
134
175
|
cache_inputs = {
|
|
135
176
|
"from_image": from_image,
|
|
136
177
|
"fla": fla,
|
|
137
|
-
"liger": _need("liger-kernel"),
|
|
138
178
|
"tilelang": _need("tilelang"),
|
|
139
179
|
"tvm_ffi": _need("apache-tvm-ffi"),
|
|
140
180
|
"chalk": chalk,
|
|
@@ -115,9 +115,16 @@ class ModelInfo:
|
|
|
115
115
|
|
|
116
116
|
DEFAULT_MODEL = "Qwen/Qwen3.5-4B"
|
|
117
117
|
|
|
118
|
+
# The pre-quantized FP8 checkpoint each base model's serving engine LOADS (mirrors serving's
|
|
119
|
+
# ``src.prequant_config``). Every dense model now serves a Freesolo-OWNED FP8_DYNAMIC checkpoint (no
|
|
120
|
+
# community-repo dependence); the 35B VL MoE serves the OFFICIAL Qwen FP8 (it preserves the full
|
|
121
|
+
# vision-language model). Informational for the catalog mirror — deploy gating reads only max_lora_rank.
|
|
118
122
|
SERVING_FP8_MODEL_REPOS: dict[str, str] = {
|
|
119
|
-
"
|
|
120
|
-
"Qwen/Qwen3.5-
|
|
123
|
+
"openbmb/MiniCPM5-1B": "Freesolo-Co/MiniCPM5-1B-FP8",
|
|
124
|
+
"Qwen/Qwen3.5-0.8B": "Freesolo-Co/Qwen3.5-0.8B-FP8",
|
|
125
|
+
"Qwen/Qwen3.5-2B": "Freesolo-Co/Qwen3.5-2B-FP8",
|
|
126
|
+
"Qwen/Qwen3.5-4B": "Freesolo-Co/Qwen3.5-4B-FP8",
|
|
127
|
+
"Qwen/Qwen3.5-9B": "Freesolo-Co/Qwen3.5-9B-FP8",
|
|
121
128
|
"Qwen/Qwen3.6-35B-A3B": "Qwen/Qwen3.6-35B-A3B-FP8",
|
|
122
129
|
}
|
|
123
130
|
|
|
@@ -133,9 +140,10 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
133
140
|
recommended_gpu="RTX 4090",
|
|
134
141
|
serving=ServingCapacity(
|
|
135
142
|
gpu="L4",
|
|
143
|
+
serve_model_id=SERVING_FP8_MODEL_REPOS["openbmb/MiniCPM5-1B"],
|
|
136
144
|
max_loras=16,
|
|
137
|
-
max_lora_rank=
|
|
138
|
-
max_model_len=
|
|
145
|
+
max_lora_rank=128,
|
|
146
|
+
max_model_len=8192,
|
|
139
147
|
),
|
|
140
148
|
thinking="hybrid",
|
|
141
149
|
notes="On-device class SLM (131k ctx); standard Llama architecture.",
|
|
@@ -151,9 +159,10 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
151
159
|
recommended_gpu="RTX 4090",
|
|
152
160
|
serving=ServingCapacity(
|
|
153
161
|
gpu="L4",
|
|
162
|
+
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-0.8B"],
|
|
154
163
|
max_loras=16,
|
|
155
|
-
max_lora_rank=
|
|
156
|
-
max_model_len=
|
|
164
|
+
max_lora_rank=128,
|
|
165
|
+
max_model_len=8192,
|
|
157
166
|
),
|
|
158
167
|
thinking="hybrid",
|
|
159
168
|
notes="Smallest Qwen3.5; cheap smoke/dev runs with the modern arch.",
|
|
@@ -169,9 +178,10 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
169
178
|
recommended_gpu="RTX 4090",
|
|
170
179
|
serving=ServingCapacity(
|
|
171
180
|
gpu="L4",
|
|
181
|
+
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-2B"],
|
|
172
182
|
max_loras=16,
|
|
173
|
-
max_lora_rank=
|
|
174
|
-
max_model_len=
|
|
183
|
+
max_lora_rank=128,
|
|
184
|
+
max_model_len=8192,
|
|
175
185
|
),
|
|
176
186
|
thinking="hybrid",
|
|
177
187
|
),
|
|
@@ -187,8 +197,8 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
187
197
|
serving=ServingCapacity(
|
|
188
198
|
gpu="L4",
|
|
189
199
|
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-4B"],
|
|
190
|
-
max_loras=
|
|
191
|
-
max_lora_rank=
|
|
200
|
+
max_loras=16,
|
|
201
|
+
max_lora_rank=64,
|
|
192
202
|
max_model_len=8192,
|
|
193
203
|
max_num_seqs=8,
|
|
194
204
|
gpu_memory_utilization=0.98,
|
|
@@ -212,8 +222,8 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
212
222
|
serving=ServingCapacity(
|
|
213
223
|
gpu="L4",
|
|
214
224
|
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-9B"],
|
|
215
|
-
max_loras=
|
|
216
|
-
max_lora_rank=
|
|
225
|
+
max_loras=16,
|
|
226
|
+
max_lora_rank=64,
|
|
217
227
|
max_model_len=8192,
|
|
218
228
|
max_num_seqs=8,
|
|
219
229
|
gpu_memory_utilization=0.98,
|
|
@@ -250,8 +260,11 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
250
260
|
serving=ServingCapacity(
|
|
251
261
|
gpu="A100-80GB",
|
|
252
262
|
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.6-35B-A3B"],
|
|
253
|
-
|
|
254
|
-
|
|
263
|
+
# rank-64 at only 6 hot slots: the fused-MoE LoRA buffer scales with
|
|
264
|
+
# max_loras x rank x num_experts, so the A100-80GB ceiling is ~max_loras x rank = 384
|
|
265
|
+
# (6 x 64 fits at 99.3% util; 16 x 64 OOMs on every single/multi GPU). Serving-validated.
|
|
266
|
+
max_loras=6,
|
|
267
|
+
max_lora_rank=64,
|
|
255
268
|
max_model_len=8192,
|
|
256
269
|
max_num_seqs=8,
|
|
257
270
|
max_num_batched_tokens=4096,
|
|
@@ -253,8 +253,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
253
253
|
)
|
|
254
254
|
env_pull.add_argument(
|
|
255
255
|
"env_id",
|
|
256
|
-
help='the Freesolo environment
|
|
257
|
-
'"github:owner/repo@ref:path" ref, or a github.com URL',
|
|
256
|
+
help='the managed Freesolo environment slug "your-name/your-env"',
|
|
258
257
|
)
|
|
259
258
|
env_pull.add_argument(
|
|
260
259
|
"path",
|
|
@@ -65,38 +65,32 @@ def _normalize_managed_hub_id(raw: object) -> tuple[str | None, _ManagedHubIdErr
|
|
|
65
65
|
def cmd_env_pull(args) -> int:
|
|
66
66
|
"""Download a published Freesolo environment (or a single file from it) to local disk.
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
(e.g. ``datasets/train.jsonl``) come back intact instead of empty.
|
|
68
|
+
Environments are addressed by their managed hub slug ``namespace/name`` and pulled as package
|
|
69
|
+
tarballs through the authenticated Flash control plane.
|
|
71
70
|
"""
|
|
72
71
|
from flash.client import ClientError, client_from_config
|
|
73
|
-
from flash.envs.adapter import is_freesolo_environment_id
|
|
74
72
|
from flash.envs.pull import (
|
|
75
|
-
download_environment_file,
|
|
76
73
|
download_environment_file_from_archive,
|
|
77
74
|
ensure_environment_pull_destination_available,
|
|
78
75
|
environment_local_dirname,
|
|
79
|
-
pull_environment_package,
|
|
80
76
|
pull_environment_package_from_archive,
|
|
81
77
|
)
|
|
82
78
|
|
|
83
79
|
env_id = str(args.env_id or "").strip()
|
|
84
|
-
|
|
80
|
+
managed_env_id, managed_error = _normalize_managed_hub_id(env_id)
|
|
81
|
+
if managed_error == "not-canonical":
|
|
82
|
+
return _err(
|
|
83
|
+
f'env id must be lowercase "namespace/name" with no spaces (got {args.env_id!r})'
|
|
84
|
+
)
|
|
85
|
+
if managed_env_id is None:
|
|
85
86
|
print(
|
|
86
|
-
'env id must be a Freesolo
|
|
87
|
-
f
|
|
87
|
+
'env id must be a managed Freesolo hub slug "your-name/your-env" '
|
|
88
|
+
f"(got {args.env_id!r})",
|
|
88
89
|
file=sys.stderr,
|
|
89
90
|
)
|
|
90
91
|
return 1
|
|
92
|
+
env_id = managed_env_id
|
|
91
93
|
try:
|
|
92
|
-
managed_env_id, managed_error = _normalize_managed_hub_id(env_id)
|
|
93
|
-
if managed_error == "not-canonical":
|
|
94
|
-
return _err(
|
|
95
|
-
f'env id must be lowercase "namespace/name" with no spaces (got {args.env_id!r})'
|
|
96
|
-
)
|
|
97
|
-
managed = managed_env_id is not None
|
|
98
|
-
if managed:
|
|
99
|
-
env_id = managed_env_id
|
|
100
94
|
if args.path:
|
|
101
95
|
default_name = Path(args.path.replace("\\", "/")).name
|
|
102
96
|
out = Path(args.output) if args.output else Path(default_name)
|
|
@@ -110,11 +104,8 @@ def cmd_env_pull(args) -> int:
|
|
|
110
104
|
if (out.exists() or out.is_symlink()) and not args.force:
|
|
111
105
|
print(f"refusing to overwrite {out} (pass --force)", file=sys.stderr)
|
|
112
106
|
return 1
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
data = download_environment_file_from_archive(package, args.path)
|
|
116
|
-
else:
|
|
117
|
-
data = download_environment_file(env_id, args.path)
|
|
107
|
+
package = client_from_config().download_env_package(env_id)
|
|
108
|
+
data = download_environment_file_from_archive(package, args.path)
|
|
118
109
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
119
110
|
_atomic_write_bytes(out, data)
|
|
120
111
|
if render.styled():
|
|
@@ -123,12 +114,9 @@ def cmd_env_pull(args) -> int:
|
|
|
123
114
|
print(f"pulled {args.path} from {env_id} -> {out} ({len(data):,} bytes)")
|
|
124
115
|
else:
|
|
125
116
|
out = Path(args.output) if args.output else Path(environment_local_dirname(env_id))
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
pull_environment_package_from_archive(package, out, overwrite=args.force)
|
|
130
|
-
else:
|
|
131
|
-
pull_environment_package(env_id, out, overwrite=args.force)
|
|
117
|
+
ensure_environment_pull_destination_available(out, overwrite=args.force)
|
|
118
|
+
package = client_from_config().download_env_package(env_id)
|
|
119
|
+
pull_environment_package_from_archive(package, out, overwrite=args.force)
|
|
132
120
|
if render.styled():
|
|
133
121
|
print(render.env_pulled(f"{out}/", env_id))
|
|
134
122
|
else:
|
|
@@ -146,12 +134,6 @@ def cmd_env_pull(args) -> int:
|
|
|
146
134
|
return 1
|
|
147
135
|
except (ValueError, FileNotFoundError, RuntimeError, OSError) as exc:
|
|
148
136
|
print(f"env pull failed: {exc}", file=sys.stderr)
|
|
149
|
-
if "GitHub environment request failed" in str(exc) and not os.environ.get("GITHUB_TOKEN"):
|
|
150
|
-
print(
|
|
151
|
-
"hint: private/internal environments need a GitHub token; set GITHUB_TOKEN "
|
|
152
|
-
"(e.g. `export GITHUB_TOKEN=$(gh auth token)`).",
|
|
153
|
-
file=sys.stderr,
|
|
154
|
-
)
|
|
155
137
|
return 1
|
|
156
138
|
|
|
157
139
|
|
|
@@ -359,6 +359,8 @@ def _color_json(obj, depth: int) -> str:
|
|
|
359
359
|
|
|
360
360
|
def _hide_provider_metadata(obj):
|
|
361
361
|
"""Styled CLI details are for humans; keep backend provider names out of that view."""
|
|
362
|
+
from flash.providers import PROVIDER_NAMES
|
|
363
|
+
|
|
362
364
|
if isinstance(obj, dict):
|
|
363
365
|
return {
|
|
364
366
|
k: _hide_provider_metadata(v)
|
|
@@ -367,7 +369,7 @@ def _hide_provider_metadata(obj):
|
|
|
367
369
|
}
|
|
368
370
|
if isinstance(obj, list):
|
|
369
371
|
return [_hide_provider_metadata(v) for v in obj]
|
|
370
|
-
if isinstance(obj, str) and obj.lower() in
|
|
372
|
+
if isinstance(obj, str) and obj.lower() in PROVIDER_NAMES:
|
|
371
373
|
return "managed"
|
|
372
374
|
return obj
|
|
373
375
|
|
|
@@ -98,7 +98,6 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
|
98
98
|
reward_s = math.ceil(completions / REWARD_CONCURRENCY) * latency # ceil: a partial wave still costs one latency
|
|
99
99
|
return gen_s + reward_s + update_s
|
|
100
100
|
|
|
101
|
-
|
|
102
101
|
def sft_seconds_for_tokens(config: RunConfig, gpu: str, train_tokens: float) -> float:
|
|
103
102
|
"""SFT steady-state wall time for an actual token count on ``gpu``."""
|
|
104
103
|
n = config.normalized()
|
|
@@ -108,7 +107,7 @@ def sft_seconds_for_tokens(config: RunConfig, gpu: str, train_tokens: float) ->
|
|
|
108
107
|
return flops / (peak * MFU_SFT_TRAIN)
|
|
109
108
|
|
|
110
109
|
|
|
111
|
-
def select_gpu(config: RunConfig) -> tuple[str, int]:
|
|
110
|
+
def select_gpu(config: RunConfig, *, max_wall_seconds: float = 0.0) -> tuple[str, int]:
|
|
112
111
|
"""(chosen GPU class, required VRAM GB): the cheapest fitting class for the cost.
|
|
113
112
|
|
|
114
113
|
Uses ``pick_gpu``, which (unlike the submit-time allocator) intentionally stays gate-free —
|
|
@@ -122,7 +121,7 @@ def select_gpu(config: RunConfig) -> tuple[str, int]:
|
|
|
122
121
|
train=config.train_knobs(),
|
|
123
122
|
thinking=config.thinking,
|
|
124
123
|
)
|
|
125
|
-
gpu = pick_gpu(need, provider=config.provider)
|
|
124
|
+
gpu = pick_gpu(need, provider=config.provider, max_wall_seconds=max_wall_seconds)
|
|
126
125
|
return gpu, need
|
|
127
126
|
|
|
128
127
|
|
|
@@ -153,10 +152,26 @@ def _notes(config: RunConfig, raw_train_s: float, wall_capped: bool, cap_s: floa
|
|
|
153
152
|
|
|
154
153
|
def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S) -> CostEstimate:
|
|
155
154
|
"""Deterministic pre-flight cost calculation."""
|
|
156
|
-
|
|
157
|
-
hourly = gpu_hourly_usd(gpu, provider=config.provider)
|
|
158
|
-
# Mirror the runner's max(60, max_wall_seconds) floor so elapsed wall time is not undercounted.
|
|
155
|
+
# Billing cap: mirror the runner's max(60, max_wall_seconds) floor so a sub-60s cap isn't underpriced.
|
|
159
156
|
cap_s = max(60.0, float(config.max_wall_seconds)) if config.max_wall_seconds is not None else wall_cap_s
|
|
157
|
+
# Vast market duration filter: price against offers that outlast the run, using the SAME semantics
|
|
158
|
+
# ``usable_offers`` applies at LAUNCH (not the 60s-floored billing cap_s) — a non-positive wall means
|
|
159
|
+
# NO filter, a positive one is floored at 60s by usable_offers itself:
|
|
160
|
+
# None -> the 24h spec default the run runs under (== DEFAULT_WALL_CAP_S);
|
|
161
|
+
# > 0 -> that wall; <= 0 -> 0.0 (no filter, exactly like launch).
|
|
162
|
+
if config.max_wall_seconds is None:
|
|
163
|
+
market_wall_s = wall_cap_s
|
|
164
|
+
elif config.max_wall_seconds > 0:
|
|
165
|
+
market_wall_s = float(config.max_wall_seconds)
|
|
166
|
+
else:
|
|
167
|
+
market_wall_s = 0.0
|
|
168
|
+
gpu, need = select_gpu(config, max_wall_seconds=market_wall_s)
|
|
169
|
+
# Quote the SAME VRAM-floored Vast market pick_gpu selected under (min_vram_gb=need): without the
|
|
170
|
+
# floor the rate lookup searches from the smallest managed class, letting cheap small-card offers
|
|
171
|
+
# crowd a high-VRAM selection off the limited page -> it silently falls back to the static rate.
|
|
172
|
+
hourly = gpu_hourly_usd(
|
|
173
|
+
gpu, provider=config.provider, max_wall_seconds=market_wall_s, min_vram_gb=need
|
|
174
|
+
)
|
|
160
175
|
|
|
161
176
|
setup = setup_seconds(config)
|
|
162
177
|
sps = seconds_per_step(config, gpu)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Static lookup facts for the cost model: GPU price/VRAM/compute + cheapest-fit
|
|
2
|
+
selection, model size/quant, and reward-grader latency. Pure tables + accessors."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from flash.catalog import MODELS
|
|
7
|
+
from flash.providers.base import GPU_INFO, GpuClass, providers_for
|
|
8
|
+
|
|
9
|
+
GPU_COMPUTE_TFLOPS: dict[str, float] = {
|
|
10
|
+
# A10: 125 TFLOPS dense bf16 tensor (NVIDIA spec); Lambda-only 24 GB class, else defaults to 100.
|
|
11
|
+
"A10": 125.0,
|
|
12
|
+
"RTX 4090": 165.0,
|
|
13
|
+
"RTX 5090": 210.0,
|
|
14
|
+
"A100 PCIe": 312.0,
|
|
15
|
+
"A100 SXM": 312.0,
|
|
16
|
+
# A100 SXM 40GB: same SMs/tensor cores as the 80GB A100 SXM, less HBM only.
|
|
17
|
+
# Without this, 33-40 GB Lambda/Vast quotes fall back to _DEFAULT_TFLOPS.
|
|
18
|
+
"A100 SXM 40GB": 312.0,
|
|
19
|
+
"H100": 990.0,
|
|
20
|
+
# H200: same SMs/tensor cores as H100, more HBM only.
|
|
21
|
+
"H200": 990.0,
|
|
22
|
+
"RTX Pro 6000": 250.0,
|
|
23
|
+
# B200: 2.25 PFLOPS bf16 dense (NVIDIA spec); prevents ~10x cost over-estimate vs _DEFAULT_TFLOPS.
|
|
24
|
+
"B200": 2250.0,
|
|
25
|
+
}
|
|
26
|
+
_DEFAULT_TFLOPS = 100.0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def gpu_tflops(name: str) -> float:
|
|
30
|
+
"""Peak bf16 tensor TFLOPS for a managed GPU class."""
|
|
31
|
+
return GPU_COMPUTE_TFLOPS.get(name, _DEFAULT_TFLOPS)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def gpu_hourly_usd(
|
|
35
|
+
name: str, provider: str | None = None, max_wall_seconds: float = 0.0, min_vram_gb: int = 0
|
|
36
|
+
) -> float:
|
|
37
|
+
"""Representative $/hr for a class, on ``provider`` when given.
|
|
38
|
+
|
|
39
|
+
When ``provider`` is ``lambda`` or ``vast`` and the class is offered there, price it through that
|
|
40
|
+
provider's pricing module (live with a static fallback); otherwise use the RunPod static rate.
|
|
41
|
+
|
|
42
|
+
``max_wall_seconds`` (>0) is threaded into the Vast live market so a duration-bound quote prices
|
|
43
|
+
against offers that outlast the run, not a short-lived one filtered out at launch.
|
|
44
|
+
|
|
45
|
+
``min_vram_gb`` (>0) floors the Vast market search at the job's required VRAM — the SAME floor
|
|
46
|
+
``pick_gpu`` selected under — so a high-VRAM class isn't crowded off the price-sorted page and
|
|
47
|
+
misquoted on the static fallback (selection/quote parity).
|
|
48
|
+
"""
|
|
49
|
+
info = GPU_INFO.get(name)
|
|
50
|
+
if info is None:
|
|
51
|
+
raise KeyError(f"unknown GPU class {name!r}")
|
|
52
|
+
p = (provider or "").strip().lower()
|
|
53
|
+
if p == "lambda" and info.lambda_name:
|
|
54
|
+
from flash.providers import get_provider
|
|
55
|
+
|
|
56
|
+
return get_provider("lambda").hourly_rate(name)
|
|
57
|
+
if p == "vast" and info.vast_name:
|
|
58
|
+
# Vast is a live market whose rates differ materially from RunPod's static ones, so price a
|
|
59
|
+
# provider="vast" quote through the Vast pricing module (live + static fallback).
|
|
60
|
+
from flash.providers.vast.pricing import hourly_rate
|
|
61
|
+
|
|
62
|
+
return hourly_rate(name, max_wall_seconds=max_wall_seconds, min_vram_gb=min_vram_gb)
|
|
63
|
+
return info.hourly_usd
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def gpu_vram_gb(name: str) -> int:
|
|
67
|
+
info = GPU_INFO.get(name)
|
|
68
|
+
if info is None:
|
|
69
|
+
raise KeyError(f"unknown GPU class {name!r}")
|
|
70
|
+
return info.vram_gb
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def pick_gpu(
|
|
74
|
+
required_vram_gb: int, *, provider: str | None = None, max_wall_seconds: float = 0.0
|
|
75
|
+
) -> str:
|
|
76
|
+
"""Cheapest GPU class that fits ``required_vram_gb``, ranked by static $/hr.
|
|
77
|
+
|
|
78
|
+
No pin; every fitting class is eligible, validated or not. NOTE this is intentionally
|
|
79
|
+
gate-free: the submit-time allocator restricts to the validated pool, so the
|
|
80
|
+
actually-provisioned class can be pricier than the one priced here. ``provider`` restricts
|
|
81
|
+
candidates to what it can provision. ``max_wall_seconds`` (>0) prices the Vast market against
|
|
82
|
+
offers that outlast the run, so a long-run quote doesn't SELECT a class on the strength of a
|
|
83
|
+
short-lived offer that won't survive to launch.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def _selectable(g: GpuClass) -> bool:
|
|
87
|
+
return provider in (None, "auto") or provider in providers_for(g.name)
|
|
88
|
+
|
|
89
|
+
candidates = [g for g in GPU_INFO.values() if g.vram_gb >= required_vram_gb and _selectable(g)]
|
|
90
|
+
if not candidates:
|
|
91
|
+
raise ValueError(f"no GPU class fits >= {required_vram_gb} GB")
|
|
92
|
+
# Rank by the rate on the REQUESTED provider, not the RunPod nominal. For Vast, fetch the live offer
|
|
93
|
+
# map ONCE (a duration-bound query bypasses the per-call cache, so pricing per candidate would fire N
|
|
94
|
+
# identical market fetches) and, when reachable, restrict to classes that actually have a rentable
|
|
95
|
+
# offer under the wall cap and rank by LIVE price — else a cheaper class with no surviving offer gets
|
|
96
|
+
# selected/quoted on a static rate the launch path would never rent. Static fallback when offline.
|
|
97
|
+
if (provider or "").strip().lower() == "vast":
|
|
98
|
+
from flash.providers.vast.pricing import live_offer_rates
|
|
99
|
+
|
|
100
|
+
live = live_offer_rates(max_wall_seconds=max_wall_seconds, min_vram_gb=required_vram_gb)
|
|
101
|
+
rentable = [g for g in candidates if g.name in live] if live else []
|
|
102
|
+
if rentable:
|
|
103
|
+
candidates = rentable
|
|
104
|
+
|
|
105
|
+
def _rate(g: GpuClass) -> float:
|
|
106
|
+
return live[g.name]
|
|
107
|
+
else:
|
|
108
|
+
|
|
109
|
+
def _rate(g: GpuClass) -> float:
|
|
110
|
+
return g.hourly_usd
|
|
111
|
+
else:
|
|
112
|
+
|
|
113
|
+
def _rate(g: GpuClass) -> float:
|
|
114
|
+
return gpu_hourly_usd(g.name, provider=provider, max_wall_seconds=max_wall_seconds)
|
|
115
|
+
|
|
116
|
+
best = min(candidates, key=lambda g: (_rate(g), g.vram_gb, g.name))
|
|
117
|
+
return best.name
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def total_params_b(model_id: str) -> float:
|
|
121
|
+
"""Total parameter count (billions) for a catalog model."""
|
|
122
|
+
info = MODELS.get(model_id)
|
|
123
|
+
if info is None:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"unknown model {model_id!r}; cost estimation supports catalog models only "
|
|
126
|
+
f"({', '.join(MODELS)})"
|
|
127
|
+
)
|
|
128
|
+
return info.params_b
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def active_params_b(model_id: str) -> float:
|
|
132
|
+
"""Active params per token (billions); falls back to total for dense models. Use for FLOPs, not VRAM."""
|
|
133
|
+
info = MODELS.get(model_id)
|
|
134
|
+
if info is None:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
f"unknown model {model_id!r}; cost estimation supports catalog models only "
|
|
137
|
+
f"({', '.join(MODELS)})"
|
|
138
|
+
)
|
|
139
|
+
return info.active_params_b or info.params_b
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def model_quant(model_id: str) -> str:
|
|
143
|
+
"""Quantization of the catalog entry; defaults to 'bf16'."""
|
|
144
|
+
info = MODELS.get(model_id)
|
|
145
|
+
return (info.quant or "bf16") if info is not None else "bf16"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def download_weight_gb(model_id: str) -> float:
|
|
149
|
+
"""Full bf16 checkpoint size in GB (2 bytes/param)."""
|
|
150
|
+
return total_params_b(model_id) * 2.0
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ~1s mid-range default across grader types (regex ~0.01s to LLM judge ~3s).
|
|
154
|
+
AVG_REWARD_SECONDS_PER_COMPLETION = 1.0
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def reward_seconds_per_completion(override: float | None = None) -> float:
|
|
158
|
+
"""Per-completion reward latency (s): the explicit override, else the single average."""
|
|
159
|
+
if override is not None:
|
|
160
|
+
return max(0.0, override)
|
|
161
|
+
return AVG_REWARD_SECONDS_PER_COMPLETION
|