freesolo-flash-dev 0.2.32__tar.gz → 0.2.34__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.34/.github/workflows/notify-tests-repo.yml +35 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/PKG-INFO +1 -1
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/catalog.py +44 -14
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/__init__.py +17 -8
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/commands.py +57 -20
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/envpush.py +17 -2
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/render.py +2 -2
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/training_doc.py +10 -9
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/client/http.py +11 -8
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cost/facts.py +2 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/envs/adapter.py +3 -2
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/runner/__init__.py +66 -4
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/runner/deploy.py +29 -4
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/runner/lifecycle.py +37 -10
- freesolo_flash_dev-0.2.34/flash/serve/pricing.py +94 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/_runtime.py +2 -2
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/billing.py +26 -6
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/billing_retry.py +33 -34
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/envs.py +24 -3
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/reconcile.py +10 -6
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/routes/serving.py +17 -2
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/pyproject.toml +2 -2
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_billing_retry.py +61 -41
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_catalog_consistency.py +28 -15
- freesolo_flash_dev-0.2.34/tests/test_charge_pricing.py +139 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_commands.py +35 -14
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_client.py +31 -22
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_publish.py +50 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_push.py +10 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_flash_worker.py +1 -1
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_jobs.py +17 -4
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_orchestrator_flash.py +3 -2
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_serve.py +7 -7
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_server_api.py +41 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_server_billing.py +50 -5
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_serving_contract.py +36 -3
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_spec_and_validation.py +6 -6
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_verifiers.py +69 -2
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_vl_warmstart_recombine.py +10 -10
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/uv.lock +1 -1
- freesolo_flash_dev-0.2.32/docs/cli-theme/README.md +0 -32
- freesolo_flash_dev-0.2.32/docs/cli-theme/before-after-dark.png +0 -0
- freesolo_flash_dev-0.2.32/docs/cli-theme/before-after-light.png +0 -0
- freesolo_flash_dev-0.2.32/docs/cli-theme/gallery-dark.png +0 -0
- freesolo_flash_dev-0.2.32/docs/cli-theme/gallery-light.png +0 -0
- freesolo_flash_dev-0.2.32/docs/cli-theme/help-dark.png +0 -0
- freesolo_flash_dev-0.2.32/docs/cli-theme/help-light.png +0 -0
- freesolo_flash_dev-0.2.32/flash/serve/pricing.py +0 -58
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.env.example +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/README.md +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/cli-style/README.md +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/cli-style/generate.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/cli-style/index.html +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/cli-style/preview.png +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/cli-style/themed-errors.png +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/docs/kernel-cache.md +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cost/analytical.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/adapter.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/hf.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/rl.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/sft.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/lambdalabs/train.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/train/deps.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/schema/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/serve/deploy.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/app.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/flash/spec.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/conftest.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_vl_weight_sync.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-0.2.32 → freesolo_flash_dev-0.2.34}/tests/test_worker_thinking.py +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Notify tests repo
|
|
2
|
+
|
|
3
|
+
# Flash's cross-repo integration seams (flash<->backend auth, allocator, CLI flow)
|
|
4
|
+
# live in the freesolo-co/tests repo, which carries flash as a pinned submodule.
|
|
5
|
+
# On every push to dev/main, dispatch to that repo so its CI bumps the flash
|
|
6
|
+
# submodule pin and runs the affected suites against the new sha. Mirrors the
|
|
7
|
+
# freesolo-side notify-tests-repo.yml; the tests repo listens for `flash-merged`.
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
branches: [dev, main]
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
notify:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- name: Dispatch to freesolo-co/tests
|
|
22
|
+
env:
|
|
23
|
+
PAT: ${{ secrets.FREESOLO_GITHUB_PAT }}
|
|
24
|
+
SHA: ${{ github.sha }}
|
|
25
|
+
REF: ${{ github.ref_name }}
|
|
26
|
+
run: |
|
|
27
|
+
if [ -z "$PAT" ]; then
|
|
28
|
+
echo "No FREESOLO_GITHUB_PAT (likely a fork) — skipping dispatch."
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
curl -fsS -X POST \
|
|
32
|
+
-H "Authorization: Bearer ${PAT}" \
|
|
33
|
+
-H "Accept: application/vnd.github+json" \
|
|
34
|
+
https://api.github.com/repos/freesolo-co/tests/dispatches \
|
|
35
|
+
-d "{\"event_type\":\"flash-merged\",\"client_payload\":{\"sha\":\"${SHA}\",\"ref\":\"${REF}\"}}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.34
|
|
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
|
|
@@ -38,6 +38,7 @@ class ServingCapacity:
|
|
|
38
38
|
serve_model_id: str = ""
|
|
39
39
|
max_num_seqs: int = 0
|
|
40
40
|
max_num_batched_tokens: int = 0
|
|
41
|
+
tensor_parallel_size: int = 0
|
|
41
42
|
gpu_memory_utilization: float = 0.0
|
|
42
43
|
|
|
43
44
|
|
|
@@ -104,6 +105,7 @@ class ModelInfo:
|
|
|
104
105
|
"serve_model_id",
|
|
105
106
|
"max_num_seqs",
|
|
106
107
|
"max_num_batched_tokens",
|
|
108
|
+
"tensor_parallel_size",
|
|
107
109
|
"gpu_memory_utilization",
|
|
108
110
|
):
|
|
109
111
|
if serving.get(key) in ("", 0, 0.0, None):
|
|
@@ -113,6 +115,15 @@ class ModelInfo:
|
|
|
113
115
|
|
|
114
116
|
DEFAULT_MODEL = "Qwen/Qwen3.5-4B"
|
|
115
117
|
|
|
118
|
+
SERVING_FP8_MODEL_REPOS: dict[str, str] = {
|
|
119
|
+
"openbmb/MiniCPM5-1B": "Freesolo-Co/MiniCPM5-1B-FP8",
|
|
120
|
+
"Qwen/Qwen3.5-0.8B": "Freesolo-Co/Qwen3.5-0.8B-FP8",
|
|
121
|
+
"Qwen/Qwen3.5-2B": "Freesolo-Co/Qwen3.5-2B-FP8",
|
|
122
|
+
"Qwen/Qwen3.5-4B": "Freesolo-Co/Qwen3.5-4B-FP8",
|
|
123
|
+
"Qwen/Qwen3.5-9B": "Freesolo-Co/Qwen3.5-9B-FP8",
|
|
124
|
+
"Qwen/Qwen3.6-35B-A3B": "Qwen/Qwen3.6-35B-A3B-FP8",
|
|
125
|
+
}
|
|
126
|
+
|
|
116
127
|
MODELS: dict[str, ModelInfo] = {
|
|
117
128
|
"openbmb/MiniCPM5-1B": ModelInfo(
|
|
118
129
|
id="openbmb/MiniCPM5-1B",
|
|
@@ -123,7 +134,13 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
123
134
|
algos=("sft", "grpo"),
|
|
124
135
|
min_vram_gb=12,
|
|
125
136
|
recommended_gpu="RTX 4090",
|
|
126
|
-
serving=ServingCapacity(
|
|
137
|
+
serving=ServingCapacity(
|
|
138
|
+
gpu="L4",
|
|
139
|
+
serve_model_id=SERVING_FP8_MODEL_REPOS["openbmb/MiniCPM5-1B"],
|
|
140
|
+
max_loras=16,
|
|
141
|
+
max_lora_rank=128,
|
|
142
|
+
max_model_len=32768,
|
|
143
|
+
),
|
|
127
144
|
thinking="hybrid",
|
|
128
145
|
notes="On-device class SLM (131k ctx); standard Llama architecture.",
|
|
129
146
|
),
|
|
@@ -136,7 +153,13 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
136
153
|
algos=("sft", "grpo"),
|
|
137
154
|
min_vram_gb=12,
|
|
138
155
|
recommended_gpu="RTX 4090",
|
|
139
|
-
serving=ServingCapacity(
|
|
156
|
+
serving=ServingCapacity(
|
|
157
|
+
gpu="L4",
|
|
158
|
+
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-0.8B"],
|
|
159
|
+
max_loras=16,
|
|
160
|
+
max_lora_rank=128,
|
|
161
|
+
max_model_len=32768,
|
|
162
|
+
),
|
|
140
163
|
thinking="hybrid",
|
|
141
164
|
notes="Smallest Qwen3.5; cheap smoke/dev runs with the modern arch.",
|
|
142
165
|
),
|
|
@@ -149,7 +172,13 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
149
172
|
algos=("sft", "grpo"),
|
|
150
173
|
min_vram_gb=16,
|
|
151
174
|
recommended_gpu="RTX 4090",
|
|
152
|
-
serving=ServingCapacity(
|
|
175
|
+
serving=ServingCapacity(
|
|
176
|
+
gpu="L4",
|
|
177
|
+
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-2B"],
|
|
178
|
+
max_loras=16,
|
|
179
|
+
max_lora_rank=128,
|
|
180
|
+
max_model_len=32768,
|
|
181
|
+
),
|
|
153
182
|
thinking="hybrid",
|
|
154
183
|
),
|
|
155
184
|
"Qwen/Qwen3.5-4B": ModelInfo(
|
|
@@ -162,10 +191,10 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
162
191
|
min_vram_gb=32,
|
|
163
192
|
recommended_gpu="RTX 5090",
|
|
164
193
|
serving=ServingCapacity(
|
|
165
|
-
gpu="
|
|
166
|
-
serve_model_id="
|
|
194
|
+
gpu="L40S",
|
|
195
|
+
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-4B"],
|
|
167
196
|
max_loras=64,
|
|
168
|
-
max_lora_rank=
|
|
197
|
+
max_lora_rank=64,
|
|
169
198
|
max_model_len=8192,
|
|
170
199
|
max_num_seqs=8,
|
|
171
200
|
gpu_memory_utilization=0.98,
|
|
@@ -187,10 +216,10 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
187
216
|
quant="bf16",
|
|
188
217
|
recommended_gpu="A100 PCIe",
|
|
189
218
|
serving=ServingCapacity(
|
|
190
|
-
gpu="
|
|
191
|
-
serve_model_id="
|
|
219
|
+
gpu="L40S",
|
|
220
|
+
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-9B"],
|
|
192
221
|
max_loras=44,
|
|
193
|
-
max_lora_rank=
|
|
222
|
+
max_lora_rank=64,
|
|
194
223
|
max_model_len=8192,
|
|
195
224
|
max_num_seqs=8,
|
|
196
225
|
gpu_memory_utilization=0.98,
|
|
@@ -225,13 +254,14 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
225
254
|
quant="bf16",
|
|
226
255
|
recommended_gpu="H200",
|
|
227
256
|
serving=ServingCapacity(
|
|
228
|
-
gpu="A100-80GB",
|
|
229
|
-
serve_model_id="Qwen/Qwen3.6-35B-A3B
|
|
257
|
+
gpu="A100-80GB:2",
|
|
258
|
+
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.6-35B-A3B"],
|
|
230
259
|
max_loras=12,
|
|
231
|
-
max_lora_rank=
|
|
260
|
+
max_lora_rank=64,
|
|
232
261
|
max_model_len=8192,
|
|
233
262
|
max_num_seqs=8,
|
|
234
263
|
max_num_batched_tokens=4096,
|
|
264
|
+
tensor_parallel_size=2,
|
|
235
265
|
gpu_memory_utilization=0.98,
|
|
236
266
|
),
|
|
237
267
|
thinking="hybrid",
|
|
@@ -262,8 +292,8 @@ def get_model(model_id: str) -> ModelInfo:
|
|
|
262
292
|
def serving_lora_rank_cap(model: str | ModelInfo | None) -> int | None:
|
|
263
293
|
"""Return the model's serving LoRA rank cap, or None when Flash has no local cap.
|
|
264
294
|
|
|
265
|
-
Serving capacity is model-specific: small serving models currently allow rank
|
|
266
|
-
serving paths
|
|
295
|
+
Serving capacity is model-specific: small serving models currently allow rank 128, while larger
|
|
296
|
+
serving paths currently allow rank 64. Unknown/open-policy models intentionally return None instead
|
|
267
297
|
of inheriting a global fallback.
|
|
268
298
|
"""
|
|
269
299
|
if isinstance(model, ModelInfo):
|
|
@@ -35,6 +35,7 @@ from flash.cli.commands import ( # noqa: F401
|
|
|
35
35
|
cmd_env_setup,
|
|
36
36
|
cmd_export,
|
|
37
37
|
cmd_gpus,
|
|
38
|
+
cmd_log,
|
|
38
39
|
cmd_login,
|
|
39
40
|
cmd_models,
|
|
40
41
|
cmd_runs,
|
|
@@ -83,7 +84,8 @@ _HELP_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
|
83
84
|
"training",
|
|
84
85
|
[
|
|
85
86
|
("train", "submit a managed run from a TOML config"),
|
|
86
|
-
("status", "show a run's status
|
|
87
|
+
("status", "show a run's current status"),
|
|
88
|
+
("log", "print or follow a run's logs"),
|
|
87
89
|
("runs", "list runs with their state and cost"),
|
|
88
90
|
("checkpoints", "list a run's deployable RL checkpoints"),
|
|
89
91
|
("cancel", "cancel a running job"),
|
|
@@ -309,21 +311,28 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
309
311
|
)
|
|
310
312
|
train.set_defaults(func=cmd_train)
|
|
311
313
|
|
|
312
|
-
status = sub.add_parser("status", help="show a run's status
|
|
314
|
+
status = sub.add_parser("status", help="show a run's current status")
|
|
313
315
|
status.add_argument("run_id")
|
|
314
316
|
status.add_argument(
|
|
315
|
-
"
|
|
317
|
+
"-f",
|
|
318
|
+
"--follow",
|
|
316
319
|
action="store_true",
|
|
317
|
-
help="
|
|
318
|
-
"stdout + traceback (console_/error_<phase>.txt) fetched from the run's HF artifact repo",
|
|
320
|
+
help="poll status until the run ends without replaying logs",
|
|
319
321
|
)
|
|
320
|
-
status.
|
|
322
|
+
status.set_defaults(func=cmd_status)
|
|
323
|
+
|
|
324
|
+
log = sub.add_parser(
|
|
325
|
+
"log",
|
|
326
|
+
help="print a run's full logs, including worker console/error artifacts",
|
|
327
|
+
)
|
|
328
|
+
log.add_argument("run_id")
|
|
329
|
+
log.add_argument(
|
|
321
330
|
"-f",
|
|
322
331
|
"--follow",
|
|
323
332
|
action="store_true",
|
|
324
|
-
help="stream logs until the run
|
|
333
|
+
help="stream logs until the run reaches a terminal state",
|
|
325
334
|
)
|
|
326
|
-
|
|
335
|
+
log.set_defaults(func=cmd_log)
|
|
327
336
|
|
|
328
337
|
runs = sub.add_parser("runs", help="list runs and their state/cost")
|
|
329
338
|
runs.set_defaults(func=cmd_runs)
|
|
@@ -401,7 +401,7 @@ def cmd_train(args) -> int:
|
|
|
401
401
|
else:
|
|
402
402
|
print(
|
|
403
403
|
f"run {run_id} submitted; following logs "
|
|
404
|
-
f"(Ctrl-C detaches, `flash
|
|
404
|
+
f"(Ctrl-C detaches, `flash log {run_id} --follow` resumes)",
|
|
405
405
|
file=sys.stderr,
|
|
406
406
|
)
|
|
407
407
|
return _follow_run(client, run_id)
|
|
@@ -437,28 +437,65 @@ def _follow_run(client: ApiClient, run_id: str) -> int:
|
|
|
437
437
|
return 0 if state in _OK_STATES else 1
|
|
438
438
|
|
|
439
439
|
|
|
440
|
+
def _follow_status(client: ApiClient, run_id: str, interval: float = 2.0) -> int:
|
|
441
|
+
"""Poll run status until terminal, without replaying worker logs."""
|
|
442
|
+
last_rendered: str | None = None
|
|
443
|
+
while True:
|
|
444
|
+
status = client.get_run(run_id)
|
|
445
|
+
rendered = render.run_status(status) if render.styled() else json.dumps(status, indent=2)
|
|
446
|
+
if rendered != last_rendered:
|
|
447
|
+
print(rendered)
|
|
448
|
+
last_rendered = rendered
|
|
449
|
+
state = str(status.get("state") or "")
|
|
450
|
+
if state in _CLI_DONE_STATES:
|
|
451
|
+
return 0 if state in _OK_STATES else 1
|
|
452
|
+
time.sleep(interval)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _print_log_text(text: str) -> bool:
|
|
456
|
+
if not text:
|
|
457
|
+
return False
|
|
458
|
+
print(text, end="")
|
|
459
|
+
if not text.endswith("\n"):
|
|
460
|
+
print()
|
|
461
|
+
return True
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _print_log_snapshot(client: ApiClient, run_id: str) -> bool:
|
|
465
|
+
"""Print a finite snapshot from the log endpoint without following new bytes."""
|
|
466
|
+
page = client.get_logs(run_id, offset=0)
|
|
467
|
+
return _print_log_text(str(page.get("logs") or ""))
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _print_worker_output(client: ApiClient, run_id: str, *, printed_any: bool = False) -> bool:
|
|
471
|
+
for name, text in (client.get_worker_output(run_id) or {}).items():
|
|
472
|
+
if not text:
|
|
473
|
+
continue
|
|
474
|
+
sep = "\n" if printed_any else ""
|
|
475
|
+
if render.styled():
|
|
476
|
+
print(f"{sep}{render.log_section(name)}")
|
|
477
|
+
else:
|
|
478
|
+
print(f"{sep}----- {name} -----")
|
|
479
|
+
print(text, end="" if text.endswith("\n") else "\n")
|
|
480
|
+
printed_any = True
|
|
481
|
+
return printed_any
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def cmd_log(args) -> int:
|
|
485
|
+
client = client_from_config()
|
|
486
|
+
if getattr(args, "follow", False):
|
|
487
|
+
state = _poll_logs(client, args.run_id, interval=2.0)
|
|
488
|
+
_print_worker_output(client, args.run_id, printed_any=True)
|
|
489
|
+
return 0 if state in _OK_STATES else 1
|
|
490
|
+
printed_any = _print_log_snapshot(client, args.run_id)
|
|
491
|
+
_print_worker_output(client, args.run_id, printed_any=printed_any)
|
|
492
|
+
return 0
|
|
493
|
+
|
|
494
|
+
|
|
440
495
|
def cmd_status(args) -> int:
|
|
441
496
|
client = client_from_config()
|
|
442
497
|
if getattr(args, "follow", False):
|
|
443
|
-
return
|
|
444
|
-
if getattr(args, "logs", False):
|
|
445
|
-
logs = client.get_logs(args.run_id).get("logs", "")
|
|
446
|
-
printed_any = False
|
|
447
|
-
if logs:
|
|
448
|
-
print(logs, end="")
|
|
449
|
-
if not logs.endswith("\n"):
|
|
450
|
-
print()
|
|
451
|
-
printed_any = True
|
|
452
|
-
for name, text in (client.get_worker_output(args.run_id) or {}).items():
|
|
453
|
-
if not text:
|
|
454
|
-
continue
|
|
455
|
-
sep = "\n" if printed_any else ""
|
|
456
|
-
if render.styled():
|
|
457
|
-
print(f"{sep}{render.log_section(name)}")
|
|
458
|
-
else:
|
|
459
|
-
print(f"{sep}----- {name} -----")
|
|
460
|
-
print(text, end="" if text.endswith("\n") else "\n")
|
|
461
|
-
printed_any = True
|
|
498
|
+
return _follow_status(client, args.run_id)
|
|
462
499
|
status = client.get_run(args.run_id)
|
|
463
500
|
if render.styled():
|
|
464
501
|
print(render.run_status(status))
|
|
@@ -190,8 +190,23 @@ _ENV_PUSH_SIDECAR_SUFFIXES = frozenset(
|
|
|
190
190
|
def _normalize_env_name(raw: str) -> str | None:
|
|
191
191
|
import re
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
def normalize_segment(value: str) -> str | None:
|
|
194
|
+
segment = re.sub(r"[^a-z0-9._-]+", "-", value.lower()).strip("-")
|
|
195
|
+
if segment in {"", ".", ".."} or not re.search(r"[a-z0-9]", segment):
|
|
196
|
+
return None
|
|
197
|
+
return segment
|
|
198
|
+
|
|
199
|
+
text = str(raw or "").strip()
|
|
200
|
+
if "/" not in text:
|
|
201
|
+
return normalize_segment(text)
|
|
202
|
+
parts = [part.strip() for part in text.split("/")]
|
|
203
|
+
if len(parts) != 2 or not all(parts):
|
|
204
|
+
return None
|
|
205
|
+
namespace = normalize_segment(parts[0])
|
|
206
|
+
name = normalize_segment(parts[1])
|
|
207
|
+
if not namespace or not name:
|
|
208
|
+
return None
|
|
209
|
+
return f"{namespace}/{name}"
|
|
195
210
|
|
|
196
211
|
|
|
197
212
|
def _with_syspath_bootstrap(env_source: str) -> str:
|
|
@@ -381,7 +381,7 @@ def version(value: str) -> str:
|
|
|
381
381
|
def submitted(run_id: str) -> str:
|
|
382
382
|
"""The `flash train` hand-off note (printed to stderr before logs start streaming)."""
|
|
383
383
|
head = ok(f"run {_paint(run_id, _ACCENT2)} submitted")
|
|
384
|
-
hint = _dim(f"following logs — Ctrl-C detaches; resume with `flash
|
|
384
|
+
hint = _dim(f"following logs — Ctrl-C detaches; resume with `flash log {run_id} --follow`")
|
|
385
385
|
return _safe(f"{head}\n{hint}")
|
|
386
386
|
|
|
387
387
|
|
|
@@ -659,7 +659,7 @@ def chat_label() -> str:
|
|
|
659
659
|
|
|
660
660
|
|
|
661
661
|
def log_section(name: str) -> str:
|
|
662
|
-
"""A themed divider above a passthrough worker-log section in `flash
|
|
662
|
+
"""A themed divider above a passthrough worker-log section in `flash log` — the same
|
|
663
663
|
idiom as chat_label sitting above a raw chat reply (the log body stays raw). The machine path
|
|
664
664
|
keeps the plain ``----- name -----`` divider that scripts and tests match on."""
|
|
665
665
|
rule = _paint(_glyph("─", "-") * 3, _FAINT)
|
|
@@ -127,8 +127,9 @@ flash train configs/rl.toml --background # submit and return immediately
|
|
|
127
127
|
|
|
128
128
|
```bash
|
|
129
129
|
flash status <run-id> # state + accrued cost
|
|
130
|
-
flash
|
|
131
|
-
flash
|
|
130
|
+
flash log <run-id> # reward/loss trend + worker console/error logs + any traceback
|
|
131
|
+
flash log <run-id> --follow # stream a live run to completion
|
|
132
|
+
flash status <run-id> # current run state, cost, and deployment info
|
|
132
133
|
flash runs # all your runs and their state/cost
|
|
133
134
|
flash cancel <run-id> # stop a run
|
|
134
135
|
```
|
|
@@ -205,8 +206,8 @@ spending another GPU run:
|
|
|
205
206
|
| All-zero or flat GRPO reward | `reward_mean` stays near 0 and outputs do not improve | Make the reward dense: give partial credit for parse/format/execution/correctness tiers, and log a separate clean `success` metric. Do not keep rerunning an all-zero reward. |
|
|
206
207
|
| Reward rises but behavior is worse | Short, templated, malformed, or reward-hacked outputs score well | Deploy the adapter and probe real examples. Add hard validity gates before judge calls, penalize degenerate shortcuts, and judge the outcome rather than the surface string. |
|
|
207
208
|
| Output is truncated | Correct-looking answers cut off mid-response or JSON is incomplete | Increase `max_tokens` for GRPO rollouts or `max_length` for SFT only after seeing truncation. Oversizing them by default just burns memory/cost. |
|
|
208
|
-
| Infrastructure, CUDA, OOM, vLLM, or kernel failure | Run errors before useful metrics, often during setup/model load | Treat this as infrastructure pressure, not proof the model is too large. Read `flash
|
|
209
|
-
| Run looks stuck after disconnecting | Terminal stopped streaming but the job may still be alive | Ctrl-C detaches. Use `flash
|
|
209
|
+
| Infrastructure, CUDA, OOM, vLLM, or kernel failure | Run errors before useful metrics, often during setup/model load | Treat this as infrastructure pressure, not proof the model is too large. Read `flash log <run-id>`, reduce footprint (`max_length`, `max_tokens`, `group_size`) if needed, and let Flash retry/allocate another fitting GPU class. |
|
|
210
|
+
| Run looks stuck after disconnecting | Terminal stopped streaming but the job may still be alive | Ctrl-C detaches. Use `flash log <run-id> --follow` to reattach, `flash log <run-id>` for the console/error output, or `flash cancel <run-id>` if you intentionally want to stop it. |
|
|
210
211
|
| Final checkpoint regresses | Last step is worse than an earlier checkpoint | Run `flash checkpoints <run-id>`, deploy a specific step with `flash deploy <run-id> --step N`, and compare with held-out probes before exporting or relying on the final adapter. |
|
|
211
212
|
| Export fails before upload | CLI says no HuggingFace token | Pass `flash export --api-key hf_...`, or set `HF_TOKEN` in your shell, `.env`, or `.env.local`. Exports are private unless you pass `--public`. |
|
|
212
213
|
| SFT loss improves but quality does not | Train loss falls while held-out behavior stalls or degrades | Keep a held-out split outside training. Deploy and score that split; if quality drops, reduce epochs or improve data instead of adding more passes. |
|
|
@@ -222,15 +223,15 @@ spending another GPU run:
|
|
|
222
223
|
- **Read the model's outputs, not just the metrics.** A rising reward can come from
|
|
223
224
|
reward-hacking or a degenerate output the reward still credits — metrics alone never
|
|
224
225
|
establish that the model got better. Flash does not expose training-time rollouts
|
|
225
|
-
through the CLI (
|
|
226
|
+
through the CLI (`flash log` gives you the metric trend and the worker's console/error
|
|
226
227
|
logs, not the sampled generations), so to read real outputs **deploy the adapter and
|
|
227
228
|
probe it**: `flash deploy <run-id>` then `flash chat <run-id> -m "..."` on at least a
|
|
228
229
|
few real inputs, including ones it should get wrong.
|
|
229
230
|
|
|
230
231
|
```bash
|
|
231
232
|
flash status <run-id> # state + accrued cost
|
|
232
|
-
flash
|
|
233
|
-
flash
|
|
233
|
+
flash log <run-id> # metric trend + worker console/error logs (+ traceback)
|
|
234
|
+
flash log <run-id> --follow # stream a live run until completion
|
|
234
235
|
flash deploy <run-id> # serve the adapter, then `flash chat` it to read real outputs
|
|
235
236
|
```
|
|
236
237
|
|
|
@@ -472,8 +473,8 @@ flash train configs/rl.toml --dry-run # validate the config locally (no GPU, no
|
|
|
472
473
|
flash train configs/rl.toml --cost # pre-flight USD estimate, then exit
|
|
473
474
|
flash train configs/rl.toml # submit and follow logs (Ctrl-C detaches; --background to skip following)
|
|
474
475
|
flash status <run-id> # state + accrued cost
|
|
475
|
-
flash
|
|
476
|
-
flash
|
|
476
|
+
flash log <run-id> # reward/loss trend + worker console/error logs
|
|
477
|
+
flash log <run-id> --follow # stream a live run to completion
|
|
477
478
|
flash runs # list your runs and their state/cost
|
|
478
479
|
flash cancel <run-id> # stop a live run
|
|
479
480
|
flash checkpoints <run-id> # list deployable RL checkpoints
|
|
@@ -110,6 +110,14 @@ def _validate_checkpoint_step(step: int | float) -> int:
|
|
|
110
110
|
return int(step)
|
|
111
111
|
|
|
112
112
|
|
|
113
|
+
def _validate_chat_messages(messages: list[dict]) -> None:
|
|
114
|
+
if not isinstance(messages, list):
|
|
115
|
+
raise ClientError("chat messages must be a list")
|
|
116
|
+
for index, message in enumerate(messages):
|
|
117
|
+
if not isinstance(message, dict):
|
|
118
|
+
raise ClientError(f"chat messages[{index}] must be an object")
|
|
119
|
+
|
|
120
|
+
|
|
113
121
|
class ApiClient:
|
|
114
122
|
def __init__(
|
|
115
123
|
self,
|
|
@@ -225,14 +233,7 @@ class ApiClient:
|
|
|
225
233
|
return self._request("GET", f"/v1/runs/{run_id}/logs?offset={int(offset)}")
|
|
226
234
|
|
|
227
235
|
def get_worker_output(self, run_id: str) -> dict[str, str]:
|
|
228
|
-
|
|
229
|
-
# paths; real run-not-found 404s carry "unknown run_id: ..." so we can distinguish them.
|
|
230
|
-
try:
|
|
231
|
-
return self._request("GET", f"/v1/runs/{run_id}/worker").get("worker", {})
|
|
232
|
-
except ApiError as exc:
|
|
233
|
-
if exc.status == 404 and str(exc).strip().lower() == "not found":
|
|
234
|
-
return {}
|
|
235
|
-
raise
|
|
236
|
+
return self._request("GET", f"/v1/runs/{run_id}/worker").get("worker", {})
|
|
236
237
|
|
|
237
238
|
def cancel_run(self, run_id: str) -> dict:
|
|
238
239
|
return self._request("POST", f"/v1/runs/{run_id}/cancel")
|
|
@@ -289,6 +290,7 @@ class ApiClient:
|
|
|
289
290
|
temperature: float = 0.0,
|
|
290
291
|
max_tokens: int = 512,
|
|
291
292
|
) -> dict:
|
|
293
|
+
_validate_chat_messages(messages)
|
|
292
294
|
return self._request(
|
|
293
295
|
"POST",
|
|
294
296
|
f"/v1/runs/{run_id}/chat",
|
|
@@ -303,6 +305,7 @@ class ApiClient:
|
|
|
303
305
|
temperature: float = 0.0,
|
|
304
306
|
max_tokens: int = 512,
|
|
305
307
|
) -> Iterator[str]:
|
|
308
|
+
_validate_chat_messages(messages)
|
|
306
309
|
headers = {"Content-Type": "application/json"}
|
|
307
310
|
if self.api_key:
|
|
308
311
|
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
@@ -7,6 +7,8 @@ from flash.catalog import MODELS
|
|
|
7
7
|
from flash.providers.base import GPU_INFO, GpuClass, providers_for
|
|
8
8
|
|
|
9
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,
|
|
10
12
|
"RTX 4090": 165.0,
|
|
11
13
|
"RTX 5090": 210.0,
|
|
12
14
|
"A100 PCIe": 312.0,
|
|
@@ -1032,10 +1032,11 @@ class FreesoloEnvironment(BaseEnvironment):
|
|
|
1032
1032
|
episode_turns: int | None = int(self._env.max_episode_turns(task))
|
|
1033
1033
|
except Exception:
|
|
1034
1034
|
episode_turns = None
|
|
1035
|
+
messages = [dict(message) for message in prompt]
|
|
1035
1036
|
return {
|
|
1036
1037
|
"task": task,
|
|
1037
|
-
"prompt":
|
|
1038
|
-
"messages":
|
|
1038
|
+
"prompt": prompt,
|
|
1039
|
+
"messages": messages,
|
|
1039
1040
|
"turns": [],
|
|
1040
1041
|
"done": False,
|
|
1041
1042
|
"response_text": "",
|
|
@@ -59,6 +59,66 @@ def _gpu_rate(gpu_type: str) -> float:
|
|
|
59
59
|
return 0.80
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
def charge_usd_for_spec(spec, *, steps: int | None = None, fallback: float = 0.0) -> float:
|
|
63
|
+
"""The customer charge for a run: the flash.cost estimate (training-only steps x sec/step x $/hr).
|
|
64
|
+
|
|
65
|
+
This is the price the run was QUOTED at submit. ``steps=None`` prices the spec's planned steps
|
|
66
|
+
(a completed run is charged exactly its quote); pass the actual steps that ran to re-price a
|
|
67
|
+
CANCELLED run at how far it got. Returns ``fallback`` if the spec can't be priced (so a charge is
|
|
68
|
+
never blocked by a pricing failure)."""
|
|
69
|
+
try:
|
|
70
|
+
from flash.cost.analytical import estimate_cost
|
|
71
|
+
from flash.cost.spec import estimate_for_spec, runconfig_from_spec
|
|
72
|
+
|
|
73
|
+
if steps is None:
|
|
74
|
+
return float(estimate_for_spec(spec).total_usd)
|
|
75
|
+
n = max(0, int(steps))
|
|
76
|
+
if n == 0:
|
|
77
|
+
return 0.0 # cancelled before any training step -> nothing to charge
|
|
78
|
+
from dataclasses import replace
|
|
79
|
+
|
|
80
|
+
cfg = runconfig_from_spec(spec)
|
|
81
|
+
planned = int(cfg.steps or 0)
|
|
82
|
+
if planned > 0:
|
|
83
|
+
n = min(n, planned)
|
|
84
|
+
# SFT is priced from train_tokens (not steps), so lowering steps ALONE wouldn't prorate a
|
|
85
|
+
# cancel -- estimate_cost would still charge the full-run token estimate. Scale the token
|
|
86
|
+
# count to the fraction of steps that ran so a mid-training SFT cancel is charged its share,
|
|
87
|
+
# mirroring GRPO's steps-based proration.
|
|
88
|
+
if not cfg.is_grpo and cfg.train_tokens and planned > 0:
|
|
89
|
+
scaled_tokens = max(1, int(cfg.train_tokens * n / planned))
|
|
90
|
+
cfg = replace(cfg, steps=n, train_tokens=scaled_tokens)
|
|
91
|
+
else:
|
|
92
|
+
cfg = replace(cfg, steps=n)
|
|
93
|
+
return float(estimate_cost(cfg).total_usd)
|
|
94
|
+
except Exception:
|
|
95
|
+
return float(fallback)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Heartbeat stages that mean the worker has entered training (GPU work underway). The per-step
|
|
99
|
+
# `step` field is 1-indexed and only appears once a step COMPLETES, so the expensive first step (a
|
|
100
|
+
# GRPO rollout can be ~17 min) streams one of these stages with NO step yet -- still real GPU time.
|
|
101
|
+
_TRAINING_STAGES = frozenset({"rl_step", "sft_step"})
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def actual_steps_run(status: RunStatus) -> int:
|
|
105
|
+
"""How many optimizer steps to bill a (cancelled) run for.
|
|
106
|
+
|
|
107
|
+
The worker streams a per-step heartbeat whose ``step`` field is the last COMPLETED optimizer step
|
|
108
|
+
(1-indexed; the last one we persisted is the furthest it reached). Cancelled after N steps -> N.
|
|
109
|
+
The first step reports no ``step`` until it completes, so a cancel mid-first-step would look like
|
|
110
|
+
0 steps despite real GPU time -- we floor to 1 whenever a training-stage heartbeat is present.
|
|
111
|
+
Returns 0 only when no training heartbeat was seen (cancelled during cold-start/setup) -> $0."""
|
|
112
|
+
hb = status.last_heartbeat if isinstance(status.last_heartbeat, dict) else {}
|
|
113
|
+
step = hb.get("step")
|
|
114
|
+
if isinstance(step, (int, float)) and step > 0:
|
|
115
|
+
return int(step)
|
|
116
|
+
# Training started (rl_step/sft_step) but no completed step yet -> mid-first-step -> bill 1.
|
|
117
|
+
if hb.get("stage") in _TRAINING_STAGES:
|
|
118
|
+
return 1
|
|
119
|
+
return 0
|
|
120
|
+
|
|
121
|
+
|
|
62
122
|
@dataclass
|
|
63
123
|
class RunStatus:
|
|
64
124
|
run_id: str
|
|
@@ -74,9 +134,9 @@ class RunStatus:
|
|
|
74
134
|
remote: dict | None = None
|
|
75
135
|
# Realized provider cost (COGS), pulled from the provider's billing API after the run
|
|
76
136
|
# finishes by the reconciliation job (flash/server/reconcile.py) and reported to the
|
|
77
|
-
# freesolo backend for estimator accuracy. Distinct from ``cost_usd`` (the
|
|
78
|
-
#
|
|
79
|
-
# re-pulled. Both stay None for un-reconciled / pre-instrumentation runs.
|
|
137
|
+
# freesolo backend for estimator accuracy. Distinct from ``cost_usd`` (the flash.cost ESTIMATE
|
|
138
|
+
# we charge the customer); ``reconciled_at`` marks that the realized pull has happened so it
|
|
139
|
+
# isn't re-pulled. Both stay None for un-reconciled / pre-instrumentation runs.
|
|
80
140
|
realized_cost_usd: float | None = None
|
|
81
141
|
reconciled_at: float | None = None
|
|
82
142
|
# Stamped ONCE on first terminal transition; survives later updated_at bumps from deploy/reconcile.
|
|
@@ -234,7 +294,9 @@ _WEIGHT_CACHE_PEAK_FACTOR = 2.0
|
|
|
234
294
|
def _fits_weight_cache(info: ModelInfo) -> bool:
|
|
235
295
|
"""Whether the model's peak download footprint fits the shared weight-cache volume."""
|
|
236
296
|
if not info.params_b:
|
|
237
|
-
return
|
|
297
|
+
return (
|
|
298
|
+
True # unknown size -> keep the (attach) default; curated catalog models always set it
|
|
299
|
+
)
|
|
238
300
|
download_gb = info.params_b * 2.0 # bf16: 2 bytes/param (mirrors cost.facts.download_weight_gb)
|
|
239
301
|
return _WEIGHT_CACHE_PEAK_FACTOR * download_gb <= WEIGHT_CACHE_VOLUME_GB
|
|
240
302
|
|