freesolo-flash-dev 0.2.33__tar.gz → 0.2.35__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.35/.github/workflows/notify-tests-repo.yml +35 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/PKG-INFO +1 -1
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/catalog.py +44 -14
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/cli/__init__.py +34 -22
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/cli/commands.py +115 -44
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/cli/envpush.py +16 -3
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/cli/render.py +24 -8
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/cli/training_doc.py +18 -16
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/client/http.py +40 -26
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/cost/facts.py +2 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/vram.py +52 -3
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/adapter.py +14 -17
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/hf.py +123 -37
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/rl.py +3 -3
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/sft.py +1 -1
- freesolo_flash_dev-0.2.35/flash/envs/adapter.py +447 -0
- freesolo_flash_dev-0.2.33/flash/envs/adapter.py → freesolo_flash_dev-0.2.35/flash/envs/loader.py +98 -398
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/envs/pull.py +1 -1
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/train/endpoints.py +64 -29
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/runner/__init__.py +173 -16
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/runner/checkpoints.py +44 -14
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/runner/deploy.py +92 -15
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/runner/lifecycle.py +37 -10
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/schema/__init__.py +55 -5
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/serve/deploy.py +173 -13
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/_runtime.py +2 -2
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/app.py +18 -1
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/billing.py +26 -6
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/billing_retry.py +33 -34
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/envs.py +27 -7
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/reconcile.py +10 -6
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/routes/runs.py +1 -1
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/routes/serving.py +242 -45
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/pyproject.toml +2 -2
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/conftest.py +29 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_agent_flash_cli_contract.py +2 -4
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_billing_retry.py +61 -41
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cancel_remote.py +123 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_catalog_consistency.py +28 -15
- freesolo_flash_dev-0.2.35/tests/test_charge_pricing.py +139 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_checkpoints.py +115 -1
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cli_commands.py +108 -28
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cli_render_theme.py +3 -5
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_client.py +62 -64
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_client_server_integration.py +6 -0
- freesolo_flash_dev-0.2.35/tests/test_env_cache_evict.py +70 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_env_publish.py +50 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_env_pull.py +1 -1
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_env_push.py +10 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_env_rate_limit_resolve.py +8 -8
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_flash_worker.py +1 -1
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_github_urlopen_retry.py +1 -1
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_gpus.py +18 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_grpo_params.py +54 -109
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_jobs.py +361 -4
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_kv_util.py +8 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_orchestrator_flash.py +6 -2
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_serve.py +52 -11
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_server_api.py +180 -13
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_server_billing.py +50 -5
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_serving_contract.py +191 -3
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_spec_and_validation.py +6 -6
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_verifiers.py +202 -12
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_vl_warmstart_recombine.py +10 -10
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_warmstart_cross_repo.py +21 -4
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/uv.lock +1 -1
- freesolo_flash_dev-0.2.33/docs/cli-theme/README.md +0 -32
- freesolo_flash_dev-0.2.33/docs/cli-theme/before-after-dark.png +0 -0
- freesolo_flash_dev-0.2.33/docs/cli-theme/before-after-light.png +0 -0
- freesolo_flash_dev-0.2.33/docs/cli-theme/gallery-dark.png +0 -0
- freesolo_flash_dev-0.2.33/docs/cli-theme/gallery-light.png +0 -0
- freesolo_flash_dev-0.2.33/docs/cli-theme/help-dark.png +0 -0
- freesolo_flash_dev-0.2.33/docs/cli-theme/help-light.png +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.env.example +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/README.md +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docs/cli-style/README.md +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docs/cli-style/generate.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docs/cli-style/index.html +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docs/cli-style/preview.png +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docs/cli-style/themed-errors.png +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/docs/kernel-cache.md +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/cost/analytical.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/lambdalabs/train.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/providers/runpod/train/deps.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/flash/spec.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_vl_weight_sync.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-0.2.33 → freesolo_flash_dev-0.2.35}/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.35
|
|
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)
|
|
@@ -339,14 +348,17 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
339
348
|
checkpoints.set_defaults(func=cmd_checkpoints)
|
|
340
349
|
|
|
341
350
|
deploy = sub.add_parser("deploy", help="deploy a run's adapter to a serving endpoint")
|
|
342
|
-
deploy.add_argument(
|
|
351
|
+
deploy.add_argument(
|
|
352
|
+
"run_id",
|
|
353
|
+
metavar="RUN_ID[/step-N]",
|
|
354
|
+
help="run id for the final adapter, or RUN_ID/step-N for a saved checkpoint",
|
|
355
|
+
)
|
|
343
356
|
deploy.add_argument("--dry-run", action="store_true")
|
|
344
357
|
deploy.add_argument(
|
|
345
|
-
"--
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
"instead of the run's final adapter; works even for a run cancelled mid-RL",
|
|
358
|
+
"--no-verify",
|
|
359
|
+
action="store_true",
|
|
360
|
+
help="skip the server-side post-deploy smoke generation (registration alone does NOT "
|
|
361
|
+
"guarantee the adapter serves; only ready deployments should be scored by evals)",
|
|
350
362
|
)
|
|
351
363
|
deploy.set_defaults(func=cmd_deploy)
|
|
352
364
|
|
|
@@ -359,7 +371,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
359
371
|
"--adapter-id",
|
|
360
372
|
dest="adapter_id",
|
|
361
373
|
required=True,
|
|
362
|
-
|
|
374
|
+
metavar="RUN_ID[/step-N]",
|
|
375
|
+
help="run id for the final adapter, or RUN_ID/step-N for a saved checkpoint",
|
|
363
376
|
)
|
|
364
377
|
export.add_argument(
|
|
365
378
|
"--repository",
|
|
@@ -371,13 +384,6 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
371
384
|
help="HuggingFace token with write access to --repository "
|
|
372
385
|
"(default: HF_TOKEN from your shell or a local .env / .env.local)",
|
|
373
386
|
)
|
|
374
|
-
export.add_argument(
|
|
375
|
-
"--step",
|
|
376
|
-
type=int,
|
|
377
|
-
default=None,
|
|
378
|
-
help="export a specific intermediate checkpoint (see `flash checkpoints <adapter-id>`) "
|
|
379
|
-
"instead of the run's final adapter; works even for a run cancelled mid-RL",
|
|
380
|
-
)
|
|
381
387
|
export.add_argument(
|
|
382
388
|
"--public",
|
|
383
389
|
action="store_true",
|
|
@@ -391,6 +397,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
391
397
|
chat = sub.add_parser("chat", help="chat with a deployed adapter")
|
|
392
398
|
chat.add_argument("run_id")
|
|
393
399
|
chat.add_argument("-m", "--message", required=True)
|
|
400
|
+
chat.add_argument(
|
|
401
|
+
"--system",
|
|
402
|
+
default=None,
|
|
403
|
+
help="optional system prompt sent ahead of the user message "
|
|
404
|
+
"(training-prompt parity for evals)",
|
|
405
|
+
)
|
|
394
406
|
chat.add_argument("--max-tokens", type=int, default=512)
|
|
395
407
|
chat.add_argument("--temperature", type=float, default=0.0)
|
|
396
408
|
chat.set_defaults(func=cmd_chat)
|
|
@@ -401,15 +401,18 @@ 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)
|
|
408
408
|
|
|
409
409
|
|
|
410
|
-
def _poll_logs(client: ApiClient, run_id: str, interval: float) -> str:
|
|
411
|
-
"""Stream offset-paged logs until the run reaches a terminal state
|
|
410
|
+
def _poll_logs(client: ApiClient, run_id: str, interval: float) -> tuple[str, bool]:
|
|
411
|
+
"""Stream offset-paged logs until the run reaches a terminal state.
|
|
412
|
+
|
|
413
|
+
Returns (terminal state, whether any log bytes were printed)."""
|
|
412
414
|
offset = 0
|
|
415
|
+
printed_any = False
|
|
413
416
|
spinner = _LogFollowSpinner(run_id)
|
|
414
417
|
try:
|
|
415
418
|
while True:
|
|
@@ -417,53 +420,75 @@ def _poll_logs(client: ApiClient, run_id: str, interval: float) -> str:
|
|
|
417
420
|
if page["logs"]:
|
|
418
421
|
spinner.clear()
|
|
419
422
|
print(page["logs"], end="", flush=True)
|
|
423
|
+
printed_any = True
|
|
420
424
|
offset = page["offset"]
|
|
421
425
|
if page["state"] in _CLI_DONE_STATES:
|
|
422
426
|
spinner.clear()
|
|
423
|
-
return page["state"]
|
|
427
|
+
return page["state"], printed_any
|
|
424
428
|
_sleep_with_spinner(interval, spinner, page["state"])
|
|
425
429
|
finally:
|
|
426
430
|
spinner.clear()
|
|
427
431
|
|
|
428
432
|
|
|
433
|
+
def _render_status(status: dict) -> str:
|
|
434
|
+
"""One rendering of a run status: themed panel on a TTY, indented JSON on the machine path."""
|
|
435
|
+
return render.run_status(status) if render.styled() else json.dumps(status, indent=2)
|
|
436
|
+
|
|
437
|
+
|
|
429
438
|
def _follow_run(client: ApiClient, run_id: str) -> int:
|
|
430
439
|
"""Poll logs until the run reaches a terminal state, then print the final status."""
|
|
431
|
-
state = _poll_logs(client, run_id, interval=2.0)
|
|
432
|
-
|
|
433
|
-
if render.styled():
|
|
434
|
-
print(render.run_status(status))
|
|
435
|
-
else:
|
|
436
|
-
print(json.dumps(status, indent=2))
|
|
440
|
+
state, _ = _poll_logs(client, run_id, interval=2.0)
|
|
441
|
+
print(_render_status(client.get_run(run_id)))
|
|
437
442
|
return 0 if state in _OK_STATES else 1
|
|
438
443
|
|
|
439
444
|
|
|
445
|
+
def _follow_status(client: ApiClient, run_id: str, interval: float = 2.0) -> int:
|
|
446
|
+
"""Poll run status until terminal, without replaying worker logs."""
|
|
447
|
+
last_rendered: str | None = None
|
|
448
|
+
while True:
|
|
449
|
+
status = client.get_run(run_id)
|
|
450
|
+
rendered = _render_status(status)
|
|
451
|
+
if rendered != last_rendered:
|
|
452
|
+
print(rendered)
|
|
453
|
+
last_rendered = rendered
|
|
454
|
+
state = str(status.get("state") or "")
|
|
455
|
+
if state in _CLI_DONE_STATES:
|
|
456
|
+
return 0 if state in _OK_STATES else 1
|
|
457
|
+
time.sleep(interval)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _print_worker_output(client: ApiClient, run_id: str, *, printed_any: bool = False) -> bool:
|
|
461
|
+
for name, text in (client.get_worker_output(run_id) or {}).items():
|
|
462
|
+
if not text:
|
|
463
|
+
continue
|
|
464
|
+
sep = "\n" if printed_any else ""
|
|
465
|
+
if render.styled():
|
|
466
|
+
print(f"{sep}{render.log_section(name)}")
|
|
467
|
+
else:
|
|
468
|
+
print(f"{sep}----- {name} -----")
|
|
469
|
+
print(text, end="" if text.endswith("\n") else "\n")
|
|
470
|
+
printed_any = True
|
|
471
|
+
return printed_any
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def cmd_log(args) -> int:
|
|
475
|
+
client = client_from_config()
|
|
476
|
+
if getattr(args, "follow", False):
|
|
477
|
+
state, printed_any = _poll_logs(client, args.run_id, interval=2.0)
|
|
478
|
+
_print_worker_output(client, args.run_id, printed_any=printed_any)
|
|
479
|
+
return 0 if state in _OK_STATES else 1
|
|
480
|
+
text = str(client.get_logs(args.run_id, offset=0).get("logs") or "")
|
|
481
|
+
if text:
|
|
482
|
+
print(text, end="" if text.endswith("\n") else "\n")
|
|
483
|
+
_print_worker_output(client, args.run_id, printed_any=bool(text))
|
|
484
|
+
return 0
|
|
485
|
+
|
|
486
|
+
|
|
440
487
|
def cmd_status(args) -> int:
|
|
441
488
|
client = client_from_config()
|
|
442
489
|
if getattr(args, "follow", False):
|
|
443
|
-
return
|
|
444
|
-
|
|
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
|
|
462
|
-
status = client.get_run(args.run_id)
|
|
463
|
-
if render.styled():
|
|
464
|
-
print(render.run_status(status))
|
|
465
|
-
else:
|
|
466
|
-
print(json.dumps(status, indent=2))
|
|
490
|
+
return _follow_status(client, args.run_id)
|
|
491
|
+
print(_render_status(client.get_run(args.run_id)))
|
|
467
492
|
return 0
|
|
468
493
|
|
|
469
494
|
|
|
@@ -516,20 +541,37 @@ def cmd_checkpoints(args) -> int:
|
|
|
516
541
|
if render.styled():
|
|
517
542
|
print(render.checkpoints_table(args.run_id, checkpoints))
|
|
518
543
|
return 0
|
|
544
|
+
from flash.schema import format_checkpoint_ref
|
|
545
|
+
|
|
519
546
|
for c in checkpoints:
|
|
520
|
-
|
|
547
|
+
# single-space, unpadded columns so a plain `grep "step N"` / awk split works; the ref is
|
|
548
|
+
# the canonical short form, paste-able into train.init_from_adapter.
|
|
549
|
+
print(f"step {c['step']} {format_checkpoint_ref(args.run_id, c['step'])}")
|
|
521
550
|
print(
|
|
522
|
-
f"\ndeploy one with `flash deploy {args.run_id}
|
|
551
|
+
f"\ndeploy one with `flash deploy {args.run_id}/step-<STEP>`.",
|
|
523
552
|
file=sys.stderr,
|
|
524
553
|
)
|
|
525
554
|
return 0
|
|
526
555
|
|
|
527
556
|
|
|
528
557
|
def cmd_deploy(args) -> int:
|
|
529
|
-
|
|
558
|
+
from flash.schema import parse_checkpoint_ref
|
|
559
|
+
|
|
560
|
+
# `flash deploy <run_id>/step-N` is the same checkpoint ref `flash checkpoints` prints.
|
|
561
|
+
parsed = parse_checkpoint_ref(args.run_id)
|
|
562
|
+
if parsed is None:
|
|
563
|
+
print(
|
|
564
|
+
f"invalid run/checkpoint reference {args.run_id!r} "
|
|
565
|
+
"(expected <run_id> or <run_id>/step-N)",
|
|
566
|
+
file=sys.stderr,
|
|
567
|
+
)
|
|
568
|
+
return 1
|
|
569
|
+
base_run_id, _step = parsed
|
|
570
|
+
client = client_from_config()
|
|
571
|
+
dep = client.deploy(
|
|
530
572
|
args.run_id,
|
|
531
573
|
dry_run=args.dry_run,
|
|
532
|
-
|
|
574
|
+
verify=not getattr(args, "no_verify", False),
|
|
533
575
|
)
|
|
534
576
|
if render.styled():
|
|
535
577
|
print(render.deployed(dep))
|
|
@@ -537,11 +579,35 @@ def cmd_deploy(args) -> int:
|
|
|
537
579
|
print(json.dumps(dep, indent=2))
|
|
538
580
|
# a dry run creates no deployment, so the billing / undeploy hint would be misleading.
|
|
539
581
|
if dep.get("state") != "dry_run":
|
|
582
|
+
endpoint = str(dep.get("endpoint_name") or "").rstrip("/")
|
|
583
|
+
openai_base = dep.get("url") or (f"{endpoint}/v1" if endpoint else "")
|
|
540
584
|
note = (
|
|
541
|
-
f"serving is billed per token only; use `flash undeploy {
|
|
585
|
+
f"serving is billed per token only; use `flash undeploy {base_run_id}` "
|
|
542
586
|
"to deregister the adapter."
|
|
543
587
|
)
|
|
544
588
|
print(render.arrow(note) if render.styled() else f"note: {note}", file=sys.stderr)
|
|
589
|
+
if openai_base:
|
|
590
|
+
url_note = (
|
|
591
|
+
f"OpenAI-compatible base URL: {openai_base} — point clients at this /v1 base, "
|
|
592
|
+
"not the bare endpoint (which 404s on /chat/completions)."
|
|
593
|
+
)
|
|
594
|
+
print(render.arrow(url_note) if render.styled() else f"note: {url_note}", file=sys.stderr)
|
|
595
|
+
if dep.get("state") != "dry_run":
|
|
596
|
+
state = dep.get("state", "deploying")
|
|
597
|
+
status_note = (
|
|
598
|
+
f"deployment state is {state!r}; run `flash deployments` to check progress "
|
|
599
|
+
"and use `flash chat` once it is ready."
|
|
600
|
+
)
|
|
601
|
+
print(
|
|
602
|
+
render.arrow(status_note) if render.styled() else f"note: {status_note}",
|
|
603
|
+
file=sys.stderr,
|
|
604
|
+
)
|
|
605
|
+
if getattr(args, "no_verify", False):
|
|
606
|
+
skip_note = "server-side smoke verification was skipped for this deployment."
|
|
607
|
+
print(
|
|
608
|
+
render.arrow(skip_note) if render.styled() else f"note: {skip_note}",
|
|
609
|
+
file=sys.stderr,
|
|
610
|
+
)
|
|
545
611
|
return 0
|
|
546
612
|
|
|
547
613
|
|
|
@@ -555,9 +621,8 @@ def cmd_export(args) -> int:
|
|
|
555
621
|
"(export it in your shell or put it in a local .env / .env.local)"
|
|
556
622
|
)
|
|
557
623
|
client = client_from_config()
|
|
558
|
-
where = f" (step {args.step})" if args.step is not None else ""
|
|
559
624
|
progress = (
|
|
560
|
-
f"exporting adapter {args.adapter_id}
|
|
625
|
+
f"exporting adapter {args.adapter_id} to {args.repository} — "
|
|
561
626
|
"downloading then re-uploading; this can take a minute..."
|
|
562
627
|
)
|
|
563
628
|
print(render.note(progress) if render.styled() else progress, file=sys.stderr)
|
|
@@ -565,7 +630,6 @@ def cmd_export(args) -> int:
|
|
|
565
630
|
args.adapter_id,
|
|
566
631
|
repository=args.repository,
|
|
567
632
|
hf_token=hf_token,
|
|
568
|
-
step=args.step,
|
|
569
633
|
private=not args.public,
|
|
570
634
|
)
|
|
571
635
|
if render.styled():
|
|
@@ -602,16 +666,23 @@ def cmd_deployments(args) -> int:
|
|
|
602
666
|
if render.styled():
|
|
603
667
|
print(render.deployments_table(rows))
|
|
604
668
|
return 0
|
|
605
|
-
print(f"{'RUN_ID':<32} {'GPU':<9} ENDPOINT")
|
|
669
|
+
print(f"{'RUN_ID':<32} {'STATE':<10} {'GPU':<9} {'ENDPOINT':<32} DETAIL")
|
|
606
670
|
for r in rows:
|
|
607
671
|
d = r.get("deployment") or {}
|
|
608
|
-
|
|
672
|
+
detail = str(d.get("error") or d.get("detail") or "")
|
|
673
|
+
print(
|
|
674
|
+
f"{r['run_id']:<32} {d.get('state', '?'):<10} "
|
|
675
|
+
f"{d.get('gpu', '?'):<9} {d.get('endpoint_name', ''):<32} {detail}"
|
|
676
|
+
)
|
|
609
677
|
return 0
|
|
610
678
|
|
|
611
679
|
|
|
612
680
|
def cmd_chat(args) -> int:
|
|
613
681
|
client = client_from_config()
|
|
614
682
|
messages = [{"role": "user", "content": args.message}]
|
|
683
|
+
system = getattr(args, "system", None)
|
|
684
|
+
if system:
|
|
685
|
+
messages.insert(0, {"role": "system", "content": system})
|
|
615
686
|
if render.styled():
|
|
616
687
|
print(render.chat_label())
|
|
617
688
|
stream = getattr(client, "chat_stream", None)
|
|
@@ -188,10 +188,23 @@ _ENV_PUSH_SIDECAR_SUFFIXES = frozenset(
|
|
|
188
188
|
|
|
189
189
|
|
|
190
190
|
def _normalize_env_name(raw: str) -> str | None:
|
|
191
|
-
|
|
191
|
+
"""Normalize only the NAME segment; a namespace prefix is passed through verbatim.
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
The server is the sole authority on namespace grammar and ownership — rewriting the
|
|
194
|
+
namespace client-side would silently target a different (possibly forbidden) slug.
|
|
195
|
+
"""
|
|
196
|
+
from flash.schema import normalize_env_name_segment
|
|
197
|
+
|
|
198
|
+
text = str(raw or "").strip()
|
|
199
|
+
if "/" not in text:
|
|
200
|
+
return normalize_env_name_segment(text)
|
|
201
|
+
parts = [part.strip() for part in text.split("/")]
|
|
202
|
+
if len(parts) != 2 or not all(parts):
|
|
203
|
+
return None
|
|
204
|
+
name = normalize_env_name_segment(parts[1])
|
|
205
|
+
if name is None:
|
|
206
|
+
return None
|
|
207
|
+
return f"{parts[0]}/{name}"
|
|
195
208
|
|
|
196
209
|
|
|
197
210
|
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
|
|
|
@@ -439,28 +439,37 @@ def deployments_table(rows: list[dict]) -> str:
|
|
|
439
439
|
body = []
|
|
440
440
|
for r in rows:
|
|
441
441
|
d = r.get("deployment") or {}
|
|
442
|
+
state = str(d.get("state") or "?")
|
|
443
|
+
color = _GREEN if state in {"ready", "deployed"} else _RED if state == "failed" else _AMBER
|
|
444
|
+
detail = str(d.get("error") or d.get("detail") or "")
|
|
445
|
+
if len(detail) > 64:
|
|
446
|
+
detail = detail[:61] + "..."
|
|
442
447
|
body.append(
|
|
443
448
|
[
|
|
444
449
|
(r["run_id"], _ACCENT2),
|
|
450
|
+
(state, color),
|
|
445
451
|
(d.get("gpu", "?"), _GRAY),
|
|
446
452
|
(d.get("endpoint_name", ""), _GREEN),
|
|
453
|
+
(detail, _GRAY),
|
|
447
454
|
]
|
|
448
455
|
)
|
|
449
|
-
table = _table(["RUN ID", "GPU", "ENDPOINT"], body)
|
|
456
|
+
table = _table(["RUN ID", "STATE", "GPU", "ENDPOINT", "DETAIL"], body)
|
|
450
457
|
return _safe(f"{header('deployments', f'{len(rows)} active')}\n{table}")
|
|
451
458
|
|
|
452
459
|
|
|
453
460
|
def checkpoints_table(run_id: str, rows: list[dict]) -> str:
|
|
454
|
-
"""Deployable per-step RL checkpoints:
|
|
461
|
+
"""Deployable per-step RL checkpoints: step number + the canonical `<run_id>/step-N` ref."""
|
|
462
|
+
from flash.schema import format_checkpoint_ref
|
|
463
|
+
|
|
455
464
|
body = [
|
|
456
465
|
[
|
|
457
466
|
(str(c.get("step", "")), _TEAL),
|
|
458
|
-
(
|
|
467
|
+
(format_checkpoint_ref(run_id, c.get("step", 0)), _ACCENT2),
|
|
459
468
|
]
|
|
460
469
|
for c in sorted(rows, key=lambda c: c.get("step", 0))
|
|
461
470
|
]
|
|
462
471
|
table = _table(["STEP", "CHECKPOINT"], body, aligns=["r", "l"])
|
|
463
|
-
foot = arrow(f"deploy one with: flash deploy {run_id}
|
|
472
|
+
foot = arrow(f"deploy one with: flash deploy {run_id}/step-<STEP>")
|
|
464
473
|
return _safe(f"{header('checkpoints', f'{len(rows)} deployable')}\n{table}\n\n{foot}")
|
|
465
474
|
|
|
466
475
|
|
|
@@ -541,11 +550,14 @@ def cancelled(payload: dict) -> str:
|
|
|
541
550
|
|
|
542
551
|
def deployed(dep: dict) -> str:
|
|
543
552
|
"""`flash deploy`: the endpoint, gpu, and serving url as an aligned card (not a JSON dump)."""
|
|
553
|
+
endpoint = str(dep.get("endpoint_name") or "")
|
|
554
|
+
# OpenAI clients need the /v1 base; older servers omit `url`, so derive it from the endpoint.
|
|
555
|
+
url = dep.get("url") or (f"{endpoint.rstrip('/')}/v1" if endpoint else None)
|
|
544
556
|
pairs = [
|
|
545
557
|
("run", _paint(dep.get("run_id", ""), _ACCENT2)),
|
|
546
|
-
("endpoint", _paint(
|
|
558
|
+
("endpoint", _paint(endpoint, _GREEN) if endpoint else None),
|
|
547
559
|
("gpu", dep.get("gpu")),
|
|
548
|
-
("url", _paint(
|
|
560
|
+
("url", _paint(url, _ACCENT2) if url else None),
|
|
549
561
|
]
|
|
550
562
|
state = dep.get("state", "deployed")
|
|
551
563
|
if state == "dry_run":
|
|
@@ -571,6 +583,10 @@ def undeployed(result: dict) -> str:
|
|
|
571
583
|
line = ok(f"torn down {rid}")
|
|
572
584
|
if deleted:
|
|
573
585
|
line += "\n" + _dim(f" deregistered {', '.join(deleted)}")
|
|
586
|
+
else:
|
|
587
|
+
line += "\n" + _dim(
|
|
588
|
+
" serving had no registered adapter to deregister (already gone or never registered)"
|
|
589
|
+
)
|
|
574
590
|
return _safe(line)
|
|
575
591
|
|
|
576
592
|
|
|
@@ -659,7 +675,7 @@ def chat_label() -> str:
|
|
|
659
675
|
|
|
660
676
|
|
|
661
677
|
def log_section(name: str) -> str:
|
|
662
|
-
"""A themed divider above a passthrough worker-log section in `flash
|
|
678
|
+
"""A themed divider above a passthrough worker-log section in `flash log` — the same
|
|
663
679
|
idiom as chat_label sitting above a raw chat reply (the log body stays raw). The machine path
|
|
664
680
|
keeps the plain ``----- name -----`` divider that scripts and tests match on."""
|
|
665
681
|
rule = _paint(_glyph("─", "-") * 3, _FAINT)
|