freesolo-flash-dev 0.2.58__tar.gz → 1.0.1__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.58 → freesolo_flash_dev-1.0.1}/PKG-INFO +1 -1
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/catalog.py +25 -6
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/commands.py +17 -13
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/client/http.py +2 -2
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cost/analytical.py +58 -12
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/opd_vllm.py +8 -1
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/loader.py +1 -1
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/runner/__init__.py +2 -2
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/schema/__init__.py +9 -1
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/serve/deploy.py +50 -11
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/serve/export.py +147 -4
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/_deps.py +9 -13
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/repo_cleanup.py +23 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/routes/runs.py +18 -3
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/pyproject.toml +2 -2
- freesolo_flash_dev-1.0.1/tests/test_accounting_coverage.py +76 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_catalog_consistency.py +30 -8
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_commands.py +21 -1
- freesolo_flash_dev-1.0.1/tests/test_cli_commands_coverage.py +213 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_estimate.py +24 -0
- freesolo_flash_dev-1.0.1/tests/test_cli_main_coverage.py +19 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_client.py +2 -2
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_cache_evict.py +10 -2
- freesolo_flash_dev-1.0.1/tests/test_env_pull_coverage.py +175 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_export.py +363 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_jobs.py +7 -3
- freesolo_flash_dev-1.0.1/tests/test_lora_rank_coverage.py +163 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_opd.py +45 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_opd_vllm.py +67 -0
- freesolo_flash_dev-1.0.1/tests/test_packing_coverage.py +149 -0
- freesolo_flash_dev-1.0.1/tests/test_provider_preflight_coverage.py +61 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_serve.py +118 -3
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_server_api.py +120 -7
- freesolo_flash_dev-1.0.1/tests/test_server_app_coverage.py +192 -0
- freesolo_flash_dev-1.0.1/tests/test_server_checkpoints_coverage.py +77 -0
- freesolo_flash_dev-1.0.1/tests/test_server_locks_coverage.py +55 -0
- freesolo_flash_dev-1.0.1/tests/test_serving_context_preflight.py +39 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_serving_contract.py +131 -6
- freesolo_flash_dev-1.0.1/tests/test_serving_pricing_coverage.py +21 -0
- freesolo_flash_dev-1.0.1/tests/test_serving_schema_coverage.py +203 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_train_context_preflight.py +6 -6
- freesolo_flash_dev-1.0.1/tests/test_vast_provider_coverage.py +131 -0
- freesolo_flash_dev-1.0.1/tests/test_wandb_log_coverage.py +109 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_warmstart_cross_repo.py +2 -2
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/uv.lock +1 -1
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.claude/skills/verify/SKILL.md +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.env.example +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/README.md +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/env_setup.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/render.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cli/training_doc.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cost/facts.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/steps.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/structured_outputs.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/adapter.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/hf.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/opd.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/opd_gkd.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/rl.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/rng.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/sft.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/teacher.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/tokenizer_align.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/engine/worker_entrypoint.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/adapter.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/archive.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/lora_rank.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/opd_retry_contract.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_deadline.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/_worker.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/api.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/runner/verified_revisions.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/serve/urls.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/app.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/flash/spec.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/conftest.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_base_model_provenance.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_charge_pricing.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_render_coverage.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_controlled_experiment_repairs.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_pull_loader_coverage.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_flash_worker.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_hf_retry.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_instance_bootstrap_coverage.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_lambda_api_coverage.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_lora_rank_preflight.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_multiturn_rollout_coverage.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_multiturn_rollout_records.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_multiturn_rollout_request_policy.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_opd_full_state_resume.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_opd_resume_safety.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_repo_cleanup.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_server_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_sft_example_selection.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_sft_max_context.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_spec_and_validation.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_structured_outputs.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_training_controls.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_vast_api.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_vast_api_coverage.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_verified_revisions.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-0.2.58 → freesolo_flash_dev-1.0.1}/tests/test_worker_thinking.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Flash — managed LoRA post-training (SFT/GRPO/OPD) for Freesolo environments, driven by the `flash` CLI
|
|
5
5
|
Project-URL: Homepage, https://github.com/freesolo-co/flash
|
|
6
6
|
Project-URL: Repository, https://github.com/freesolo-co/flash
|
|
@@ -111,6 +111,8 @@ class ModelInfo:
|
|
|
111
111
|
# multimodal-nested config, so the curated values are what actually engage the gate.
|
|
112
112
|
num_layers: int = 0
|
|
113
113
|
hidden_size: int = 0
|
|
114
|
+
# vllm hybrid-mamba cache block size in tokens. 0 means the model has no catalogued mamba floor.
|
|
115
|
+
mamba_block_size: int = 0
|
|
114
116
|
|
|
115
117
|
@property
|
|
116
118
|
def is_moe(self) -> bool:
|
|
@@ -124,6 +126,8 @@ class ModelInfo:
|
|
|
124
126
|
|
|
125
127
|
def to_dict(self) -> dict[str, Any]:
|
|
126
128
|
data = asdict(self)
|
|
129
|
+
if not data["mamba_block_size"]:
|
|
130
|
+
del data["mamba_block_size"]
|
|
127
131
|
serving = data["serving"]
|
|
128
132
|
if serving is None:
|
|
129
133
|
del data["serving"]
|
|
@@ -170,7 +174,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
170
174
|
serve_model_id=SERVING_FP8_MODEL_REPOS["openbmb/MiniCPM5-1B"],
|
|
171
175
|
max_loras=16,
|
|
172
176
|
max_lora_rank=128,
|
|
173
|
-
max_model_len=
|
|
177
|
+
max_model_len=32768,
|
|
174
178
|
),
|
|
175
179
|
thinking="hybrid",
|
|
176
180
|
notes="On-device class SLM (131k ctx); standard Llama architecture.",
|
|
@@ -189,7 +193,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
189
193
|
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-0.8B"],
|
|
190
194
|
max_loras=16,
|
|
191
195
|
max_lora_rank=128,
|
|
192
|
-
max_model_len=
|
|
196
|
+
max_model_len=32768,
|
|
193
197
|
),
|
|
194
198
|
thinking="hybrid",
|
|
195
199
|
notes="Smallest Qwen3.5; cheap smoke/dev runs with the modern arch.",
|
|
@@ -208,7 +212,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
208
212
|
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-2B"],
|
|
209
213
|
max_loras=16,
|
|
210
214
|
max_lora_rank=128,
|
|
211
|
-
max_model_len=
|
|
215
|
+
max_model_len=32768,
|
|
212
216
|
),
|
|
213
217
|
thinking="hybrid",
|
|
214
218
|
),
|
|
@@ -226,7 +230,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
226
230
|
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-4B"],
|
|
227
231
|
max_loras=16,
|
|
228
232
|
max_lora_rank=64,
|
|
229
|
-
max_model_len=
|
|
233
|
+
max_model_len=32768,
|
|
230
234
|
max_num_seqs=8,
|
|
231
235
|
gpu_memory_utilization=0.98,
|
|
232
236
|
),
|
|
@@ -247,11 +251,11 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
247
251
|
quant="bf16",
|
|
248
252
|
recommended_gpu="A100 PCIe",
|
|
249
253
|
serving=ServingCapacity(
|
|
250
|
-
gpu="
|
|
254
|
+
gpu="H100",
|
|
251
255
|
serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.5-9B"],
|
|
252
256
|
max_loras=16,
|
|
253
257
|
max_lora_rank=64,
|
|
254
|
-
max_model_len=
|
|
258
|
+
max_model_len=32768,
|
|
255
259
|
max_num_seqs=8,
|
|
256
260
|
gpu_memory_utilization=0.98,
|
|
257
261
|
),
|
|
@@ -271,6 +275,10 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
271
275
|
# layers x 2048 hidden (hybrid GatedDeltaNet + full-attention, 256 experts / 8 active).
|
|
272
276
|
num_layers=40,
|
|
273
277
|
hidden_size=2048,
|
|
278
|
+
# vllm 0.19.1 model_executor/models/config.py derives this from the qwen config via
|
|
279
|
+
# mamba_utils.py: the 1,097,728-byte gdn state page needs 1072 fp8 attention tokens
|
|
280
|
+
# after the 16-token backend alignment applied by hybridattentionmambamodelconfig.
|
|
281
|
+
mamba_block_size=1072,
|
|
274
282
|
vocab_size=248_320,
|
|
275
283
|
algos=ALGORITHMS,
|
|
276
284
|
min_vram_gb=141,
|
|
@@ -312,6 +320,17 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
312
320
|
}
|
|
313
321
|
|
|
314
322
|
|
|
323
|
+
def opd_mamba_batched_token_floor(
|
|
324
|
+
model_id: str, seq_cap: int, max_num_seqs: int | None
|
|
325
|
+
) -> int | None:
|
|
326
|
+
"""return a mamba floor only when vllm's derived opd scheduler budget is too small."""
|
|
327
|
+
info = MODELS.get(model_id)
|
|
328
|
+
if info is None or info.mamba_block_size <= 0 or max_num_seqs is None:
|
|
329
|
+
return None
|
|
330
|
+
derived_budget = int(max_num_seqs) * int(seq_cap)
|
|
331
|
+
return info.mamba_block_size if derived_budget < info.mamba_block_size else None
|
|
332
|
+
|
|
333
|
+
|
|
315
334
|
def list_models() -> list[ModelInfo]:
|
|
316
335
|
return sorted(MODELS.values(), key=lambda m: (m.min_vram_gb, m.id))
|
|
317
336
|
|
|
@@ -214,6 +214,7 @@ def _cmd_train_cost(args) -> int:
|
|
|
214
214
|
Catalog-only and deterministic. SFT cost never imports the environment; it requires a positive
|
|
215
215
|
[train].max_examples row count instead of guessing or locally counting a dataset."""
|
|
216
216
|
from flash.cost import estimate_cost
|
|
217
|
+
from flash.lora_rank import preflight_train_context_within_serving
|
|
217
218
|
|
|
218
219
|
spec = spec_from_file(
|
|
219
220
|
args.config,
|
|
@@ -221,6 +222,7 @@ def _cmd_train_cost(args) -> int:
|
|
|
221
222
|
overrides=args.overrides,
|
|
222
223
|
extra_configs=args.extra_configs,
|
|
223
224
|
)
|
|
225
|
+
preflight_train_context_within_serving(spec)
|
|
224
226
|
if spec.train.init_from_adapter:
|
|
225
227
|
# --cost is offline/catalog-only and cannot read the source adapter, so the rank stays at the
|
|
226
228
|
# local default. Warm starts train and are priced at the SOURCE adapter's authoritative rank
|
|
@@ -302,23 +304,24 @@ def cmd_train(args) -> int:
|
|
|
302
304
|
)
|
|
303
305
|
payload = spec_payload(spec, authored_train_keys=authored_train_keys)
|
|
304
306
|
client = client_from_config()
|
|
307
|
+
client_train_schema = {
|
|
308
|
+
"version": __version__,
|
|
309
|
+
"fields": train_schema_metadata(),
|
|
310
|
+
"authored_keys": sorted(authored_train_keys),
|
|
311
|
+
}
|
|
312
|
+
runtime_secrets = (
|
|
313
|
+
runtime_secrets_from_local_env(args.config, keys=spec.environment.secrets) or None
|
|
314
|
+
)
|
|
305
315
|
if args.dry_run:
|
|
306
|
-
#
|
|
307
|
-
#
|
|
308
|
-
#
|
|
309
|
-
# allocates no GPU and charges nothing. Local runtime secrets aren't sent — the server
|
|
310
|
-
# relaxes the required-[environment].secrets check for a preview, so `--dry-run` works before
|
|
311
|
-
# prod secrets are wired up. A rejection surfaces as the server's `error: ...` (exit 1),
|
|
312
|
-
# exactly as a real submit would.
|
|
316
|
+
# dry-run is a faithful server-side preview: it sends the same declared secrets and runs the
|
|
317
|
+
# same config, warm-start, serving, and cost preflights as a real submit, but allocates no gpu
|
|
318
|
+
# and charges nothing. a rejection surfaces as the server's error with exit status 1.
|
|
313
319
|
try:
|
|
314
320
|
status = client.create_run(
|
|
315
321
|
payload,
|
|
322
|
+
runtime_secrets=runtime_secrets,
|
|
316
323
|
dry_run=True,
|
|
317
|
-
client_train_schema=
|
|
318
|
-
"version": __version__,
|
|
319
|
-
"fields": train_schema_metadata(),
|
|
320
|
-
"authored_keys": sorted(authored_train_keys),
|
|
321
|
-
},
|
|
324
|
+
client_train_schema=client_train_schema,
|
|
322
325
|
)
|
|
323
326
|
except ApiError as exc:
|
|
324
327
|
detail = _legacy_train_key_rejection_detail(exc, authored_train_keys)
|
|
@@ -338,7 +341,8 @@ def cmd_train(args) -> int:
|
|
|
338
341
|
return 0
|
|
339
342
|
status = client.create_run(
|
|
340
343
|
payload,
|
|
341
|
-
runtime_secrets=
|
|
344
|
+
runtime_secrets=runtime_secrets,
|
|
345
|
+
client_train_schema=client_train_schema,
|
|
342
346
|
)
|
|
343
347
|
run_id = status["run_id"]
|
|
344
348
|
logger.info(
|
|
@@ -343,8 +343,8 @@ class ApiClient:
|
|
|
343
343
|
# Server-side preview: runs the same validation/preflights as a real submit and records a
|
|
344
344
|
# state=dry_run run, but allocates no GPU and charges nothing. Returns that status.
|
|
345
345
|
body["dry_run"] = True
|
|
346
|
-
|
|
347
|
-
|
|
346
|
+
if client_train_schema is not None:
|
|
347
|
+
body["client_train_schema"] = client_train_schema
|
|
348
348
|
return self._request("POST", "/v1/runs", body=body)
|
|
349
349
|
|
|
350
350
|
def list_runs(self) -> list[dict]:
|
|
@@ -40,6 +40,22 @@ MFU_TRAIN = 0.35 # GRPO policy/reference update
|
|
|
40
40
|
MFU_SFT_TRAIN = 0.25 # SFT fwd/bwd (smaller effective batch, long sequences)
|
|
41
41
|
MFU_DECODE = 0.12 # batched vLLM rollout (decode is memory-bandwidth-bound)
|
|
42
42
|
|
|
43
|
+
# --- MoE (mixture-of-experts) per-step correction ----------------------------------------------
|
|
44
|
+
# For an MoE model (active params << total, e.g. Qwen3.6-35B-A3B: ~3B active / 35B total) the wall
|
|
45
|
+
# time per step is NOT the tiny active-param FLOPs the dense model predicts. Routing, all-expert
|
|
46
|
+
# coordination, and grouped-GEMM under-utilization at small batch make the step scale with TOTAL
|
|
47
|
+
# params. Pricing 35B-A3B on active params under-quoted real RunPod runs by 13-27x (SFT ~1.2s vs
|
|
48
|
+
# ~42s realized/step; GRPO ~1.4s vs ~24s). So an MoE prices on TOTAL params at a reduced effective
|
|
49
|
+
# MFU + a per-step overhead + a one-time compile. DENSE models (active == total) are unaffected --
|
|
50
|
+
# they keep the original active-param path and the MFUs above.
|
|
51
|
+
MFU_SFT_TRAIN_MOE = 0.10 # MoE SFT fwd/bwd priced on total params
|
|
52
|
+
MFU_TRAIN_MOE = 0.09 # MoE GRPO/OPD policy update priced on total params
|
|
53
|
+
MOE_STEP_OVERHEAD_S = 2.0 # routing/dispatch/kernel-launch overhead an MoE pays every step
|
|
54
|
+
# One-time kernel/graph compile amortized into the first training step (fused Triton kernels;
|
|
55
|
+
# + vLLM cudagraph capture for rollout methods). MoE-only; the prior model omitted it entirely.
|
|
56
|
+
COMPILE_MOE_SFT_S = 35.0
|
|
57
|
+
COMPILE_MOE_ROLLOUT_S = 48.0 # GRPO / OPD (adds vLLM cudagraph capture)
|
|
58
|
+
|
|
43
59
|
# Reward grading is CONCURRENT: a step's completions score in parallel slots, so the reward
|
|
44
60
|
# wall is ceil(completions / slots) waves x latency, not completions x latency.
|
|
45
61
|
REWARD_CONCURRENCY = 16.0
|
|
@@ -116,13 +132,32 @@ def required_save_overhead_seconds(config: RunConfig) -> float:
|
|
|
116
132
|
return len(n.save_at_steps) * per_save
|
|
117
133
|
|
|
118
134
|
|
|
135
|
+
def _is_moe(model_id: str) -> bool:
|
|
136
|
+
"""True when the model routes each token through a subset of experts (active < total params)."""
|
|
137
|
+
return active_params_b(model_id) < total_params_b(model_id)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def compile_seconds(config: RunConfig, gpu: str) -> float:
|
|
141
|
+
"""One-time kernel/graph compile folded into the first training step. MoE-only; 0 for dense
|
|
142
|
+
(whose original timing did not model it). Larger for rollout methods (add vLLM cudagraph
|
|
143
|
+
capture)."""
|
|
144
|
+
_ = gpu
|
|
145
|
+
if not _is_moe(config.model_id):
|
|
146
|
+
return 0.0
|
|
147
|
+
return COMPILE_MOE_SFT_S if config.method == "sft" else COMPILE_MOE_ROLLOUT_S
|
|
148
|
+
|
|
149
|
+
|
|
119
150
|
def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
120
151
|
"""Steady-state wall time for one optimizer step on ``gpu``."""
|
|
121
152
|
n = config.normalized()
|
|
122
|
-
# Per-token FLOPs scale with the ACTIVE params (an MoE token routes through only a subset of
|
|
123
|
-
# experts); for a dense model this equals the total. Memory/size terms below keep total_params_b.
|
|
124
|
-
params = active_params_b(n.model_id) * 1e9
|
|
125
153
|
peak = gpu_tflops(gpu) * 1e12 # FLOP/s
|
|
154
|
+
# An MoE's per-step wall scales with TOTAL params (routing + all-expert coordination + grouped
|
|
155
|
+
# GEMM under-utilization), not the tiny active-param FLOPs; dense models keep active (== total).
|
|
156
|
+
moe = _is_moe(n.model_id)
|
|
157
|
+
params = (total_params_b(n.model_id) if moe else active_params_b(n.model_id)) * 1e9
|
|
158
|
+
overhead = MOE_STEP_OVERHEAD_S if moe else 0.0
|
|
159
|
+
sft_mfu = MFU_SFT_TRAIN_MOE if moe else MFU_SFT_TRAIN
|
|
160
|
+
update_mfu = MFU_TRAIN_MOE if moe else MFU_TRAIN
|
|
126
161
|
|
|
127
162
|
if n.is_opd:
|
|
128
163
|
# OPD step = on-policy student rollout (like GRPO) + remote teacher scoring (CONCURRENT
|
|
@@ -131,7 +166,7 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
|
131
166
|
# sequence (see _opd_step_shape), not completion-only, or long-prompt opd is underquoted.
|
|
132
167
|
completions, seq_tokens = _opd_step_shape(n)
|
|
133
168
|
gen_s = (GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_DECODE)
|
|
134
|
-
update_s = (OPD_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak *
|
|
169
|
+
update_s = (OPD_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * update_mfu)
|
|
135
170
|
teacher_lat = teacher_seconds_per_completion()
|
|
136
171
|
# run_opd's primary path scores a step's completions CONCURRENTLY over Fireworks with a fan-out
|
|
137
172
|
# cap of the step's OWN completion count (prompts_per_step * group_size, opd.py Phase 2), so
|
|
@@ -139,30 +174,33 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
|
139
174
|
# latency, NOT the full serial sum (that describes only the CPU-test fallback that can't
|
|
140
175
|
# batch-generate). The teacher endpoint's rate limit is the real ceiling on this fan-out.
|
|
141
176
|
teacher_s = teacher_lat
|
|
142
|
-
return gen_s + teacher_s + update_s
|
|
177
|
+
return overhead + gen_s + teacher_s + update_s
|
|
143
178
|
|
|
144
179
|
if not n.is_grpo:
|
|
145
180
|
flops = SFT_FLOPS_PER_TOKEN_PER_PARAM * params * (n.batch_size * n.seq_len)
|
|
146
|
-
return flops / (peak *
|
|
181
|
+
return overhead + flops / (peak * sft_mfu)
|
|
147
182
|
|
|
148
183
|
# GRPO step = rollout (G completions/prompt) + concurrent reward grading + policy/ref update.
|
|
149
184
|
completions = n.batch_size * n.group_size
|
|
150
185
|
gen_tokens = completions * n.completion_len
|
|
151
186
|
gen_s = (GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM * params * gen_tokens) / (peak * MFU_DECODE)
|
|
152
|
-
update_s = (GRPO_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * gen_tokens) / (peak *
|
|
187
|
+
update_s = (GRPO_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * gen_tokens) / (peak * update_mfu)
|
|
153
188
|
latency = reward_seconds_per_completion(n.reward_seconds_per_completion)
|
|
154
189
|
reward_s = (
|
|
155
190
|
math.ceil(completions / REWARD_CONCURRENCY) * latency
|
|
156
191
|
) # ceil: a partial wave still costs one latency
|
|
157
|
-
return gen_s + reward_s + update_s
|
|
192
|
+
return overhead + gen_s + reward_s + update_s
|
|
158
193
|
|
|
159
194
|
def sft_seconds_for_tokens(config: RunConfig, gpu: str, train_tokens: float) -> float:
|
|
160
195
|
"""SFT steady-state wall time for an actual token count on ``gpu``."""
|
|
161
196
|
n = config.normalized()
|
|
162
|
-
params
|
|
197
|
+
# MoE prices on total params at a reduced MFU (see seconds_per_step); dense keeps active.
|
|
198
|
+
moe = _is_moe(n.model_id)
|
|
199
|
+
params = (total_params_b(n.model_id) if moe else active_params_b(n.model_id)) * 1e9
|
|
200
|
+
mfu = MFU_SFT_TRAIN_MOE if moe else MFU_SFT_TRAIN
|
|
163
201
|
peak = gpu_tflops(gpu) * 1e12
|
|
164
202
|
flops = SFT_FLOPS_PER_TOKEN_PER_PARAM * params * train_tokens
|
|
165
|
-
return flops / (peak *
|
|
203
|
+
return flops / (peak * mfu)
|
|
166
204
|
|
|
167
205
|
|
|
168
206
|
def select_gpu(config: RunConfig, *, max_wall_seconds: float = 0.0) -> tuple[str, int]:
|
|
@@ -221,6 +259,11 @@ def _notes(
|
|
|
221
259
|
f"{len(n.save_at_steps)} synchronous required save(s) add "
|
|
222
260
|
f"~{_fmt_duration(required_save_s)}"
|
|
223
261
|
)
|
|
262
|
+
if _is_moe(n.model_id):
|
|
263
|
+
notes.append(
|
|
264
|
+
"MoE model: per-step time priced on total params (routing + all-expert coordination), "
|
|
265
|
+
"not just active-param FLOPs, plus a one-time kernel compile"
|
|
266
|
+
)
|
|
224
267
|
notes.append(f"GPU sized with {vram_headroom() - 1:.0%} VRAM headroom; static GPU $/hr")
|
|
225
268
|
if wall_capped:
|
|
226
269
|
notes.append(
|
|
@@ -283,9 +326,12 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
|
|
|
283
326
|
setup = setup_seconds(config)
|
|
284
327
|
sps = seconds_per_step(config, gpu)
|
|
285
328
|
required_save_s = required_save_overhead_seconds(config)
|
|
286
|
-
|
|
329
|
+
# A one-time kernel/graph compile is paid once on the first step (MoE-only; 0 for dense). It is
|
|
330
|
+
# training GPU time, so it belongs in the (billed) train term, not setup.
|
|
331
|
+
compile_s = compile_seconds(config, gpu)
|
|
332
|
+
raw_train = compile_s + config.steps * sps + required_save_s
|
|
287
333
|
if not config.is_grpo and config.train_tokens is not None:
|
|
288
|
-
raw_train = sft_seconds_for_tokens(config, gpu, config.train_tokens) + required_save_s
|
|
334
|
+
raw_train = compile_s + sft_seconds_for_tokens(config, gpu, config.train_tokens) + required_save_s
|
|
289
335
|
sps = raw_train / config.steps
|
|
290
336
|
|
|
291
337
|
# The cap is on total elapsed wall; setup is reported but not billed, so only training
|
|
@@ -127,7 +127,8 @@ def opd_vllm_kwargs(model_id: str, knobs: Any, seq_cap: int) -> dict[str, Any]:
|
|
|
127
127
|
|
|
128
128
|
fp8_kv = bool(cc >= (8, 9))
|
|
129
129
|
kwargs["kv_cache_dtype"] = "fp8" if fp8_kv else None
|
|
130
|
-
|
|
130
|
+
if card_gb >= 140:
|
|
131
|
+
kwargs["max_num_batched_tokens"] = max(8192, int(seq_cap))
|
|
131
132
|
|
|
132
133
|
if card_gb > 0:
|
|
133
134
|
try:
|
|
@@ -189,6 +190,12 @@ def opd_vllm_kwargs(model_id: str, knobs: Any, seq_cap: int) -> dict[str, Any]:
|
|
|
189
190
|
kwargs["rollout_batch_size"] = rollout_concurrency
|
|
190
191
|
except Exception as exc:
|
|
191
192
|
print(f"[opd] vLLM memory-util sizing failed; using 0.10: {exc}")
|
|
193
|
+
if card_gb < 140:
|
|
194
|
+
from flash.catalog import opd_mamba_batched_token_floor
|
|
195
|
+
|
|
196
|
+
kwargs["max_num_batched_tokens"] = opd_mamba_batched_token_floor(
|
|
197
|
+
model_id, seq_cap, kwargs["max_num_seqs"]
|
|
198
|
+
)
|
|
192
199
|
if startup_oom is not None:
|
|
193
200
|
raise startup_oom
|
|
194
201
|
|
|
@@ -36,7 +36,7 @@ from flash.envs.archive_policy import (
|
|
|
36
36
|
_DEFAULT_GITHUB_REF = "main"
|
|
37
37
|
_DEFAULT_ENVIRONMENT_PATH = "environment.py"
|
|
38
38
|
_DEFAULT_MANAGED_ENV_REPO = "freesolo-co/environment-hub"
|
|
39
|
-
_CACHE_ROOT = Path(
|
|
39
|
+
_CACHE_ROOT = Path("/tmp/flash-env-cache")
|
|
40
40
|
# bound the on-disk env cache so it cannot grow without limit (one subdir per env
|
|
41
41
|
# content-sha, ~30-80 MB each). evicted LRU by dir mtime, which we bump on cache hit.
|
|
42
42
|
_CACHE_MAX_ENTRIES = 32
|
|
@@ -691,7 +691,7 @@ def _prepare_init_from_adapter(
|
|
|
691
691
|
owner_key_id: int | None = None,
|
|
692
692
|
token: str | None = None,
|
|
693
693
|
) -> tuple[JobSpec, JobSpec, dict | None]:
|
|
694
|
-
"""
|
|
694
|
+
"""prepare public and worker specs with source-authoritative adapter metadata."""
|
|
695
695
|
_require_supported_adapter_continuation(spec)
|
|
696
696
|
ref = spec.train.init_from_adapter
|
|
697
697
|
if not ref:
|
|
@@ -776,7 +776,7 @@ def _prepare_init_from_adapter(
|
|
|
776
776
|
)
|
|
777
777
|
assert metadata is not None
|
|
778
778
|
identity = adapter_artifact_identity(storage, config, token, revision).to_dict()
|
|
779
|
-
public_spec = spec
|
|
779
|
+
public_spec = replace(spec, train=replace(spec.train, lora_alpha=metadata.alpha))
|
|
780
780
|
worker_spec = replace(
|
|
781
781
|
worker_spec,
|
|
782
782
|
train=replace(worker_spec.train, lora_rank=metadata.rank, lora_alpha=metadata.alpha),
|
|
@@ -546,7 +546,12 @@ def _validate_grpo(spec: JobSpec) -> None:
|
|
|
546
546
|
|
|
547
547
|
|
|
548
548
|
def _validate_opd(spec: JobSpec) -> None:
|
|
549
|
-
"""validate
|
|
549
|
+
"""validate opd-specific training constraints."""
|
|
550
|
+
if spec.train.kl_penalty_coef == 0.0:
|
|
551
|
+
raise ConfigError(
|
|
552
|
+
"[train] kl_penalty_coef must be > 0 for opd because it scales the gkd "
|
|
553
|
+
"distillation objective; omit it to use the default 1.0 or set a positive value"
|
|
554
|
+
)
|
|
550
555
|
if spec.train.max_context_tokens:
|
|
551
556
|
# Mirror run_opd's prompt-budget guard at PARSE time: a context budget that leaves no room
|
|
552
557
|
# for any prompt after the completion budget is rejected here, BEFORE a paid worker is
|
|
@@ -564,6 +569,9 @@ def _validate_opd(spec: JobSpec) -> None:
|
|
|
564
569
|
f"invalid budget fails before a GPU worker is provisioned."
|
|
565
570
|
)
|
|
566
571
|
|
|
572
|
+
# short hybrid-mamba contexts are safe because the worker compares vllm's derived scheduler
|
|
573
|
+
# budget with the catalogued block size and supplies an explicit floor only when required.
|
|
574
|
+
|
|
567
575
|
|
|
568
576
|
# Each algorithm's spec-level contract lives in ONE validator, dispatched by name so a new algorithm
|
|
569
577
|
# adds a function + a map entry rather than another scattered ``if spec.algorithm == ...`` block.
|
|
@@ -26,6 +26,7 @@ READBACK_DELAY_SECONDS = 2.0
|
|
|
26
26
|
REVISION_READY_BUDGET_SECONDS = 5 * 60.0
|
|
27
27
|
ACTIVATION_READBACK_ATTEMPTS = 3
|
|
28
28
|
THINKING_STRUCTURED_OUTPUTS_CAPABILITY = "thinking_structured_outputs_deferred_v1"
|
|
29
|
+
REVISION_PROVENANCE_CAPABILITY = "revision_provenance"
|
|
29
30
|
_RETRYABLE_SMOKE_503_CODES = frozenset({"adapter_loading", "engine_unavailable"})
|
|
30
31
|
|
|
31
32
|
|
|
@@ -382,7 +383,10 @@ def deploy_adapter(
|
|
|
382
383
|
dep.adapter_revision = revision
|
|
383
384
|
checkpoint = f"{run_id}/step-{checkpoint_step}" if checkpoint_step is not None else run_id
|
|
384
385
|
so_default = parse_structured_outputs(structured_outputs) if structured_outputs else None
|
|
385
|
-
_require_serving_capabilities(
|
|
386
|
+
advertised = _require_serving_capabilities(
|
|
387
|
+
thinking_structured_outputs=thinking and so_default is not None
|
|
388
|
+
)
|
|
389
|
+
require_provenance = REVISION_PROVENANCE_CAPABILITY in advertised
|
|
386
390
|
|
|
387
391
|
body = {
|
|
388
392
|
"adapter_id": revision,
|
|
@@ -391,7 +395,9 @@ def deploy_adapter(
|
|
|
391
395
|
"subfolder": subfolder,
|
|
392
396
|
"repo_type": "dataset",
|
|
393
397
|
"checkpoint": checkpoint,
|
|
394
|
-
"status"
|
|
398
|
+
# NOTE: do NOT send a client-chosen "status" -- the serving backend sets the record status
|
|
399
|
+
# itself on registration and rejects an incoming "status" as an extra field (HTTP 422
|
|
400
|
+
# extra_forbidden). Sending it blocked every deploy against the deployed serving backend.
|
|
395
401
|
"metadata": {
|
|
396
402
|
"record_type": "revision",
|
|
397
403
|
"run_id": run_id,
|
|
@@ -419,14 +425,18 @@ def deploy_adapter(
|
|
|
419
425
|
record = _registered_adapter(revision)
|
|
420
426
|
except ServingError as read_exc:
|
|
421
427
|
raise exc from read_exc
|
|
422
|
-
if record is None or not _matches_revision_identity(
|
|
428
|
+
if record is None or not _matches_revision_identity(
|
|
429
|
+
record, body, require_provenance=require_provenance
|
|
430
|
+
):
|
|
423
431
|
raise exc
|
|
424
432
|
logger.warning(
|
|
425
433
|
"adapter registration response was ambiguous; revision %s exists with matching identity",
|
|
426
434
|
revision,
|
|
427
435
|
)
|
|
428
436
|
|
|
429
|
-
_wait_revision_ready(
|
|
437
|
+
_wait_revision_ready(
|
|
438
|
+
revision, subfolder, expected_identity=body, require_provenance=require_provenance
|
|
439
|
+
)
|
|
430
440
|
if before_activate is not None:
|
|
431
441
|
before_activate(revision, checkpoint)
|
|
432
442
|
activation = _activate_revision(
|
|
@@ -469,9 +479,9 @@ def _registered_adapter(adapter_id: str, *, timeout_s: float | None = None) -> d
|
|
|
469
479
|
return record
|
|
470
480
|
|
|
471
481
|
|
|
472
|
-
def _matches_revision_identity(
|
|
473
|
-
|
|
474
|
-
|
|
482
|
+
def _matches_revision_identity(
|
|
483
|
+
record: dict, expected: dict, *, require_provenance: bool = True
|
|
484
|
+
) -> bool:
|
|
475
485
|
scalar_fields = (
|
|
476
486
|
"adapter_id",
|
|
477
487
|
"repo_id",
|
|
@@ -487,19 +497,35 @@ def _matches_revision_identity(record: dict, expected: dict) -> bool:
|
|
|
487
497
|
return False
|
|
488
498
|
if (record.get("structured_outputs") or None) != (expected.get("structured_outputs") or None):
|
|
489
499
|
return False
|
|
500
|
+
if not require_provenance:
|
|
501
|
+
# backends without revision_provenance do not echo provenance metadata; the immutable
|
|
502
|
+
# adapter_id already pins the artifact, so this cross-check is best effort here.
|
|
503
|
+
return True
|
|
504
|
+
metadata = record.get("metadata") if isinstance(record.get("metadata"), dict) else {}
|
|
505
|
+
expected_metadata = expected["metadata"]
|
|
490
506
|
return all(
|
|
491
507
|
metadata.get(field) == expected_metadata.get(field)
|
|
492
508
|
for field in ("record_type", "run_id", "checkpoint_step", "hf_revision")
|
|
493
509
|
)
|
|
494
510
|
|
|
495
511
|
|
|
496
|
-
def _require_serving_capabilities(*, thinking_structured_outputs: bool = False) ->
|
|
512
|
+
def _require_serving_capabilities(*, thinking_structured_outputs: bool = False) -> set[str]:
|
|
513
|
+
# SAFETY-CRITICAL capabilities the deploy correctness contract genuinely depends on: immutable
|
|
514
|
+
# revisions (a revision id always maps to one artifact) and an atomic alias compare-and-swap
|
|
515
|
+
# (the alias flip can't race). These are hard-required.
|
|
497
516
|
required = {
|
|
498
517
|
"immutable_adapter_revisions",
|
|
499
518
|
"alias_compare_and_swap",
|
|
500
|
-
"revision_provenance",
|
|
501
519
|
}
|
|
520
|
+
# PREFERRED (not required): `revision_provenance` only lets the serving backend echo back the
|
|
521
|
+
# run/checkpoint/hf_revision metadata, which is used ONLY on the rare 5xx-during-registration
|
|
522
|
+
# recovery path (`_matches_revision_identity`). Hard-requiring it blocked EVERY deploy org-wide
|
|
523
|
+
# whenever the serving build lagged on advertising it, even though the happy path never uses it.
|
|
524
|
+
# So its absence is a logged warning, not a deploy-blocking error.
|
|
525
|
+
preferred = {REVISION_PROVENANCE_CAPABILITY}
|
|
502
526
|
if thinking_structured_outputs:
|
|
527
|
+
# Genuinely required for this run: thinking + structured outputs needs the serving backend's
|
|
528
|
+
# deferred-constraint support (grammar applied after </think>) or served output is invalid.
|
|
503
529
|
required.add(THINKING_STRUCTURED_OUTPUTS_CAPABILITY)
|
|
504
530
|
url = f"{serving_base_url()}/healthz"
|
|
505
531
|
response = _serving_request("GET", url)
|
|
@@ -524,12 +550,24 @@ def _require_serving_capabilities(*, thinking_structured_outputs: bool = False)
|
|
|
524
550
|
f"serving_contract_unsupported: serving health check at {url} capabilities must be "
|
|
525
551
|
"strings"
|
|
526
552
|
)
|
|
527
|
-
|
|
553
|
+
advertised = set(capabilities)
|
|
554
|
+
missing = sorted(required - advertised)
|
|
528
555
|
if missing:
|
|
529
556
|
raise ServingError(
|
|
530
557
|
"serving_contract_unsupported: serving is missing required capabilities "
|
|
531
558
|
+ ", ".join(missing)
|
|
532
559
|
)
|
|
560
|
+
missing_preferred = sorted(preferred - advertised)
|
|
561
|
+
if missing_preferred:
|
|
562
|
+
# Not fatal: the happy-path deploy does not use these; only the ambiguous-registration
|
|
563
|
+
# recovery path degrades (best-effort identity check). Surface it so an operator can ship
|
|
564
|
+
# the serving build that advertises them, without blocking customers meanwhile.
|
|
565
|
+
logger.warning(
|
|
566
|
+
"serving backend does not advertise preferred capabilities %s; deploying anyway "
|
|
567
|
+
"(ambiguous-registration recovery will be best-effort)",
|
|
568
|
+
", ".join(missing_preferred),
|
|
569
|
+
)
|
|
570
|
+
return advertised
|
|
533
571
|
|
|
534
572
|
|
|
535
573
|
def _wait_revision_ready(
|
|
@@ -537,6 +575,7 @@ def _wait_revision_ready(
|
|
|
537
575
|
subfolder: str,
|
|
538
576
|
*,
|
|
539
577
|
expected_identity: dict | None = None,
|
|
578
|
+
require_provenance: bool = True,
|
|
540
579
|
budget_s: float = REVISION_READY_BUDGET_SECONDS,
|
|
541
580
|
) -> dict:
|
|
542
581
|
deadline = time.monotonic() + max(0.0, float(budget_s))
|
|
@@ -566,7 +605,7 @@ def _wait_revision_ready(
|
|
|
566
605
|
continue
|
|
567
606
|
last_read_error = None
|
|
568
607
|
if expected_identity is not None and not _matches_revision_identity(
|
|
569
|
-
record, expected_identity
|
|
608
|
+
record, expected_identity, require_provenance=require_provenance
|
|
570
609
|
):
|
|
571
610
|
raise ServingError(
|
|
572
611
|
f"adapter revision {revision} resolved to a different immutable identity"
|