freesolo-flash-dev 0.2.43__tar.gz → 0.2.44__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.43 → freesolo_flash_dev-0.2.44}/PKG-INFO +1 -1
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cli/commands.py +21 -1
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/hf.py +41 -112
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/_worker.py +6 -5
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/pyproject.toml +2 -2
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_checkpoints.py +59 -170
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cli_commands.py +34 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_flash_worker.py +1 -1
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.env.example +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/README.md +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docs/cli-style/README.md +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docs/cli-style/env-setup-survey-dark.png +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docs/cli-style/env-setup-survey-light.png +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docs/cli-style/generate.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docs/cli-style/index.html +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docs/cli-style/preview.png +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docs/cli-style/themed-errors.png +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docs/cli-style/train-config-error-after.png +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docs/cli-style/train-config-error-before.png +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/docs/kernel-cache.md +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/catalog.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cli/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cli/render.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cli/training_doc.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/client/http.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cost/analytical.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cost/facts.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/adapter.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/rl.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/sft.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/envs/adapter.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/envs/loader.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/lora_rank.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/train/deps.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/vast/api.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/runner/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/schema/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/serve/deploy.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/app.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/flash/spec.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/conftest.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_charge_pricing.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_client.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_jobs.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_lora_rank_preflight.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_serve.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_server_api.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_sft_example_selection.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_spec_and_validation.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_train_context_preflight.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_vast_api.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_vl_warmstart_recombine.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_vl_weight_sync.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/tests/test_worker_thinking.py +0 -0
- {freesolo_flash_dev-0.2.43 → freesolo_flash_dev-0.2.44}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.44
|
|
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
|
|
@@ -783,12 +783,32 @@ def cmd_runs(args) -> int:
|
|
|
783
783
|
|
|
784
784
|
|
|
785
785
|
def cmd_cancel(args) -> int:
|
|
786
|
-
|
|
786
|
+
client = client_from_config()
|
|
787
|
+
status = client.cancel_run(args.run_id)
|
|
787
788
|
payload = {"run_id": args.run_id, "state": status["state"]}
|
|
789
|
+
# A cancelled run is not necessarily worthless: every completed save interval already streamed
|
|
790
|
+
# a deployable checkpoint, even though the run shows adapter_ref=null / cost=0. Surface the
|
|
791
|
+
# surviving steps here so the run isn't discarded unseen. Best-effort: cancel never fails on it.
|
|
792
|
+
checkpoints: list[dict] = []
|
|
793
|
+
if payload["state"] == "cancelled":
|
|
794
|
+
try:
|
|
795
|
+
checkpoints = client.checkpoints(args.run_id)
|
|
796
|
+
except Exception:
|
|
797
|
+
checkpoints = []
|
|
788
798
|
if render.styled():
|
|
789
799
|
print(render.cancelled(payload))
|
|
790
800
|
else:
|
|
791
801
|
print(json.dumps(payload, indent=2))
|
|
802
|
+
if checkpoints:
|
|
803
|
+
top = max(c["step"] for c in checkpoints)
|
|
804
|
+
# stderr in the plain path so the machine-readable stdout JSON stays untouched.
|
|
805
|
+
out = sys.stdout if render.styled() else sys.stderr
|
|
806
|
+
msg = (
|
|
807
|
+
f"{len(checkpoints)} deployable checkpoint(s) survive this cancel — list with "
|
|
808
|
+
f"`flash checkpoints {args.run_id}`, deploy one with "
|
|
809
|
+
f"`flash deploy {args.run_id}/step-{top}`."
|
|
810
|
+
)
|
|
811
|
+
print(render.note(msg) if render.styled() else msg, file=out)
|
|
792
812
|
return 0
|
|
793
813
|
|
|
794
814
|
|
|
@@ -310,11 +310,13 @@ def publish_deployable_checkpoint(
|
|
|
310
310
|
return None
|
|
311
311
|
|
|
312
312
|
|
|
313
|
-
#
|
|
314
|
-
_CKPT_FLUSH_TIMEOUT_S = 300.0
|
|
315
|
-
# Retry/backoff for the lock-timeout fallback publish (rides out a transient concurrent-commit 409).
|
|
313
|
+
# Retry/backoff for the deployable-adapter publish (rides out a transient concurrent-commit 409).
|
|
316
314
|
_CKPT_FLUSH_RETRIES = 3
|
|
317
315
|
_CKPT_FLUSH_BACKOFF_S = 1.0
|
|
316
|
+
# Retry/backoff for each synchronous checkpoint upload. `on_save` BLOCKS the training loop on the
|
|
317
|
+
# upload, so a transient HF error is retried until the step lands rather than costing the step.
|
|
318
|
+
_CKPT_UPLOAD_RETRIES = 3
|
|
319
|
+
_CKPT_UPLOAD_BACKOFF_S = 5.0
|
|
318
320
|
|
|
319
321
|
|
|
320
322
|
def _latest_checkpoint_dir(output_dir: str) -> tuple[int, str] | None:
|
|
@@ -368,72 +370,14 @@ def _prune_stale_resume_checkpoints(keep_step: int) -> None:
|
|
|
368
370
|
api.delete_folder(path_in_repo=folder, repo_id=_w.HF_REPO, repo_type="dataset")
|
|
369
371
|
|
|
370
372
|
|
|
371
|
-
class _UploadPump:
|
|
372
|
-
"""Coalescing single-slot upload queue: at most one upload in flight, at most one queued.
|
|
373
|
-
|
|
374
|
-
ONE lock guards two fields — ``uploading`` and ``queued`` (newest wins). Every producer path
|
|
375
|
-
is enqueue-then-pump and the upload thread's finally is done()-then-pump, so a save that
|
|
376
|
-
lands while the in-flight upload is draining can never be lost (no check-then-set window
|
|
377
|
-
between separate locks).
|
|
378
|
-
"""
|
|
379
|
-
|
|
380
|
-
def __init__(self, start_upload) -> None:
|
|
381
|
-
self._cond = threading.Condition()
|
|
382
|
-
self._start_upload = start_upload # (step, ckpt_dir) -> spawns thread; must call done()
|
|
383
|
-
self.uploading = False
|
|
384
|
-
self.queued: tuple[int, str] | None = None
|
|
385
|
-
|
|
386
|
-
def enqueue(self, step: int, ckpt_dir: str) -> None:
|
|
387
|
-
"""Queue `step` (replacing any older queued step) and start it if idle."""
|
|
388
|
-
with self._cond:
|
|
389
|
-
busy = self.uploading
|
|
390
|
-
self.queued = (step, ckpt_dir)
|
|
391
|
-
if busy:
|
|
392
|
-
print(
|
|
393
|
-
f"[ckpt] upload busy; queued step {step} (uploads when the in-flight one finishes)"
|
|
394
|
-
)
|
|
395
|
-
self.pump()
|
|
396
|
-
|
|
397
|
-
def pump(self) -> None:
|
|
398
|
-
"""Atomically start the queued upload iff nothing is in flight."""
|
|
399
|
-
with self._cond:
|
|
400
|
-
if self.uploading or self.queued is None:
|
|
401
|
-
return
|
|
402
|
-
step, ckpt_dir = self.queued
|
|
403
|
-
self.queued = None
|
|
404
|
-
gone = not os.path.isdir(ckpt_dir)
|
|
405
|
-
if not gone:
|
|
406
|
-
self.uploading = True
|
|
407
|
-
if gone: # pruned by the trainer's save_total_limit meanwhile
|
|
408
|
-
print(f"[ckpt] queued step {step} checkpoint dir is gone; dropping")
|
|
409
|
-
return
|
|
410
|
-
self._start_upload(step, ckpt_dir)
|
|
411
|
-
|
|
412
|
-
def done(self) -> None:
|
|
413
|
-
"""Mark the in-flight upload finished, then pump any queued step."""
|
|
414
|
-
with self._cond:
|
|
415
|
-
self.uploading = False
|
|
416
|
-
self._cond.notify_all()
|
|
417
|
-
self.pump()
|
|
418
|
-
|
|
419
|
-
def take_queued(self) -> tuple[int, str] | None:
|
|
420
|
-
"""Remove and return the queued step, if any (for a synchronous train-end flush)."""
|
|
421
|
-
with self._cond:
|
|
422
|
-
queued, self.queued = self.queued, None
|
|
423
|
-
return queued
|
|
424
|
-
|
|
425
|
-
def wait_idle(self, timeout: float) -> bool:
|
|
426
|
-
"""Wait up to `timeout` seconds for the in-flight upload (if any) to finish."""
|
|
427
|
-
with self._cond:
|
|
428
|
-
return self._cond.wait_for(lambda: not self.uploading, timeout)
|
|
429
|
-
|
|
430
|
-
|
|
431
373
|
def make_checkpoint_upload_callback():
|
|
432
374
|
"""Return a TrainerCallback that streams each save to HF and publishes deployable per-step adapters.
|
|
433
375
|
|
|
434
|
-
Uploads
|
|
435
|
-
|
|
436
|
-
|
|
376
|
+
Uploads are SYNCHRONOUS: on_save blocks the training loop until the checkpoint is durably on
|
|
377
|
+
HF. A save therefore never returns while its upload is still running, so there is never a
|
|
378
|
+
second upload in flight when the next save fires — the "upload busy" contention (and the
|
|
379
|
+
coalescing/dropping it used to cause) cannot happen by construction. Every `save_every` step is
|
|
380
|
+
guaranteed to upload, retrying transient HF errors instead of skipping.
|
|
437
381
|
"""
|
|
438
382
|
from transformers import TrainerCallback
|
|
439
383
|
|
|
@@ -491,72 +435,57 @@ def make_checkpoint_upload_callback():
|
|
|
491
435
|
_prune_stale_resume_checkpoints(step)
|
|
492
436
|
_w.heartbeat("checkpoint_uploaded", step=step)
|
|
493
437
|
|
|
494
|
-
|
|
495
|
-
"""Upload `step` in a background thread; the pump is marked done (and re-pumped) after."""
|
|
438
|
+
uploaded_steps: set[int] = set()
|
|
496
439
|
|
|
497
|
-
|
|
440
|
+
def _upload_with_retries(step: int, ckpt_dir: str) -> bool:
|
|
441
|
+
"""Run the full upload for `step`, retrying transient failures; True once it lands."""
|
|
442
|
+
for attempt in range(_CKPT_UPLOAD_RETRIES):
|
|
498
443
|
try:
|
|
499
444
|
_upload_once(step, ckpt_dir)
|
|
445
|
+
uploaded_steps.add(step)
|
|
446
|
+
return True
|
|
500
447
|
except Exception as e:
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
448
|
+
if attempt + 1 < _CKPT_UPLOAD_RETRIES:
|
|
449
|
+
print(
|
|
450
|
+
f"[ckpt] step {step} upload retry {attempt + 1}/{_CKPT_UPLOAD_RETRIES}: {e}"
|
|
451
|
+
)
|
|
452
|
+
time.sleep(_CKPT_UPLOAD_BACKOFF_S * (attempt + 1))
|
|
453
|
+
else:
|
|
454
|
+
print(
|
|
455
|
+
f"[ckpt] step {step} upload FAILED after {_CKPT_UPLOAD_RETRIES} attempts: {e}"
|
|
456
|
+
)
|
|
457
|
+
with contextlib.suppress(Exception):
|
|
458
|
+
_w.heartbeat("checkpoint_upload_failed", step=step)
|
|
459
|
+
return False
|
|
513
460
|
|
|
514
461
|
class _CheckpointUpload(TrainerCallback):
|
|
515
462
|
def on_save(self, args, state, control, **kwargs):
|
|
463
|
+
# SYNCHRONOUS on the training thread: the trainer blocks here until this checkpoint is
|
|
464
|
+
# uploaded. `save_total_limit=1` rotation (which runs earlier, inside this same save's
|
|
465
|
+
# `_save_checkpoint`) deletes the PREVIOUS checkpoint, never the current one — and the
|
|
466
|
+
# next save can't begin until we return — so the dir is stable for the whole upload and
|
|
467
|
+
# no snapshot/queue is needed. Because the upload finishes before the save returns,
|
|
468
|
+
# there is never a concurrent upload for a later save to be "busy" against.
|
|
516
469
|
if not _w.HF_REPO:
|
|
517
470
|
return
|
|
518
471
|
step = int(state.global_step)
|
|
519
472
|
ckpt_dir = os.path.join(args.output_dir, f"checkpoint-{step}")
|
|
520
473
|
if not os.path.isdir(ckpt_dir):
|
|
521
474
|
return
|
|
522
|
-
|
|
475
|
+
_upload_with_retries(step, ckpt_dir)
|
|
523
476
|
|
|
524
477
|
def on_train_end(self, args, state, control, **kwargs):
|
|
525
|
-
#
|
|
478
|
+
# Safety net for a final checkpoint the trainer wrote without an on_save (e.g. a
|
|
479
|
+
# load_best_model_at_end / end-of-training save). Synchronous on_save already uploaded
|
|
480
|
+
# every step it saw, so this only publishes an as-yet-unuploaded latest step.
|
|
526
481
|
if not _w.HF_REPO:
|
|
527
482
|
return
|
|
528
483
|
latest = _latest_checkpoint_dir(args.output_dir)
|
|
529
484
|
if latest is None:
|
|
530
485
|
return
|
|
531
486
|
step, ckpt_dir = latest
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
idle = pump.wait_idle(_CKPT_FLUSH_TIMEOUT_S)
|
|
536
|
-
if queued is not None and os.path.isdir(queued[1]):
|
|
537
|
-
q_step, q_dir = queued
|
|
538
|
-
if idle:
|
|
539
|
-
try:
|
|
540
|
-
_upload_once(q_step, q_dir)
|
|
541
|
-
except Exception as e:
|
|
542
|
-
print("ckpt upload warn:", e)
|
|
543
|
-
else:
|
|
544
|
-
# Another upload is still pruning latest-only resume checkpoints. Publish only
|
|
545
|
-
# the durable per-step adapter here so concurrent prune passes cannot delete a
|
|
546
|
-
# newer resume checkpoint while it is being flushed.
|
|
547
|
-
try:
|
|
548
|
-
_publish_deployable_recombined(q_dir, q_step, with_retry=True)
|
|
549
|
-
except Exception as e:
|
|
550
|
-
print("ckpt deployable flush warn:", e)
|
|
551
|
-
if q_step == step:
|
|
552
|
-
return # the queued flush already published the final deployable
|
|
553
|
-
if idle:
|
|
554
|
-
_publish_deployable_recombined(ckpt_dir, step)
|
|
555
|
-
else:
|
|
556
|
-
print(
|
|
557
|
-
f"[ckpt] upload still in flight after {_CKPT_FLUSH_TIMEOUT_S:.0f}s; publishing "
|
|
558
|
-
f"final deployable (step {step}) alongside it"
|
|
559
|
-
)
|
|
560
|
-
_publish_deployable_recombined(ckpt_dir, step, with_retry=True)
|
|
487
|
+
if step in uploaded_steps:
|
|
488
|
+
return
|
|
489
|
+
_upload_with_retries(step, ckpt_dir)
|
|
561
490
|
|
|
562
491
|
return _CheckpointUpload()
|
|
@@ -112,13 +112,14 @@ def _effective_worker_env(spec=None) -> dict[str, str]:
|
|
|
112
112
|
# Chalk is published to PUBLIC PyPI on every version bump (chalk .github/workflows/publish.yml).
|
|
113
113
|
# Pin the exact PUBLIC version so worker + kernel-cache-bake pods install with NO GitHub auth — the
|
|
114
114
|
# chalk repo is INTERNAL, so a git+https default made installs fail wherever GITHUB_TOKEN is absent
|
|
115
|
-
# (bake pod, tokenless workers). freesolo-chalk 0.5.
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
DEFAULT_CHALK_VERSION
|
|
115
|
+
# (bake pod, tokenless workers). freesolo-chalk 0.5.3 = chalk dev->main sync #67 (adds the decode
|
|
116
|
+
# serving kernels rope + GDN gated-rmsnorm; GPU-A/B'd launch tuning: flce chunk_mult 16, rope
|
|
117
|
+
# num_warps=2, gdn_conv warps formula, lora fwd autotune candidate; OP_MEAS_OVERRIDE; flce
|
|
118
|
+
# chunk_mult tradeoff docs). Bump DEFAULT_CHALK_VERSION when the merged kernel surface moves.
|
|
119
|
+
DEFAULT_CHALK_VERSION = "0.5.3"
|
|
119
120
|
DEFAULT_CHALK_SPEC = f"freesolo-chalk=={DEFAULT_CHALK_VERSION}"
|
|
120
121
|
# Provenance only (kernel-cache fingerprint / traceability): the chalk commit this version was cut from.
|
|
121
|
-
LATEST_CHALK_MAIN_SHA = "
|
|
122
|
+
LATEST_CHALK_MAIN_SHA = "c63f0367e01795006bc0e91e2540ec85e740f19b"
|
|
122
123
|
|
|
123
124
|
|
|
124
125
|
def chalk_extra_pip(spec=None) -> list[str]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "freesolo-flash-dev"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.44"
|
|
4
4
|
description = "Flash — managed LoRA post-training (SFT/GRPO) for Freesolo environments, driven by the `flash` CLI"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
# RunPod Flash supports Python 3.11-3.12 (not 3.13 yet).
|
|
@@ -106,7 +106,7 @@ artifacts = ["flash/py.typed"]
|
|
|
106
106
|
# auto-published by .github/workflows/publish-dev.yml: a push to `dev` whose version isn't yet on
|
|
107
107
|
# PyPI publishes it; pushes that don't change it find it already published and no-op. So cutting a
|
|
108
108
|
# dev-channel release = bump this (and [project].version) and merge to `dev`.
|
|
109
|
-
version = "0.2.
|
|
109
|
+
version = "0.2.44"
|
|
110
110
|
|
|
111
111
|
[tool.pytest.ini_options]
|
|
112
112
|
pythonpath = ["."]
|
|
@@ -10,7 +10,6 @@ All HF/network boundaries are stubbed; nothing here touches a GPU or the network
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
-
import time
|
|
14
13
|
from types import SimpleNamespace
|
|
15
14
|
|
|
16
15
|
import pytest
|
|
@@ -197,19 +196,20 @@ def test_latest_checkpoint_dir_picks_highest_step(tmp_path):
|
|
|
197
196
|
def test_on_train_end_flushes_final_deployable_checkpoint(
|
|
198
197
|
tmp_path, monkeypatch, fake_trainer_callback
|
|
199
198
|
):
|
|
200
|
-
"""
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
"""on_train_end is the safety net for a final checkpoint the trainer wrote WITHOUT an on_save
|
|
200
|
+
(e.g. an end-of-training / load_best_model_at_end save). It must publish the latest on-disk
|
|
201
|
+
checkpoint as a deployable — otherwise `flash checkpoints` is empty even though the run trained
|
|
202
|
+
fine. (on_save uploads synchronously, so every step it sees is already up by this point.)"""
|
|
203
203
|
import flash.engine.worker as worker
|
|
204
204
|
|
|
205
205
|
rec = _RecordingHfApi()
|
|
206
206
|
_prime_worker(monkeypatch, rec)
|
|
207
207
|
out = tmp_path / "out"
|
|
208
208
|
out.mkdir()
|
|
209
|
-
_make_ckpt_dir(out, 8) #
|
|
209
|
+
_make_ckpt_dir(out, 8) # a final checkpoint on disk that no on_save uploaded
|
|
210
210
|
cb = worker.make_checkpoint_upload_callback()
|
|
211
211
|
|
|
212
|
-
# No on_save
|
|
212
|
+
# No on_save fired for this step, so on_train_end must publish it.
|
|
213
213
|
cb.on_train_end(SimpleNamespace(output_dir=str(out)), None, None)
|
|
214
214
|
|
|
215
215
|
deployable = [
|
|
@@ -219,72 +219,38 @@ def test_on_train_end_flushes_final_deployable_checkpoint(
|
|
|
219
219
|
assert "optimizer.pt" in deployable[0]["ignore_patterns"]
|
|
220
220
|
|
|
221
221
|
|
|
222
|
-
def
|
|
223
|
-
|
|
224
|
-
):
|
|
225
|
-
"""Regression: a slow on_save upload can still hold the upload lock at run end (and the final
|
|
226
|
-
step's own on_save may have been skipped on that busy lock). on_train_end must then publish the
|
|
227
|
-
final deployable WITHOUT the lock instead of timing out and skipping it — otherwise the worker
|
|
228
|
-
exits, kills the daemon mid-upload, and `flash checkpoints` is empty despite a successful run."""
|
|
229
|
-
import threading
|
|
230
|
-
|
|
231
|
-
from flash.engine.worker import hf as worker_hf
|
|
222
|
+
def test_on_train_end_no_checkpoints_is_noop(tmp_path, monkeypatch, fake_trainer_callback):
|
|
223
|
+
import flash.engine.worker as worker
|
|
232
224
|
|
|
233
225
|
rec = _RecordingHfApi()
|
|
234
|
-
|
|
235
|
-
monkeypatch.setattr(worker_hf, "_CKPT_FLUSH_TIMEOUT_S", 0.05) # give up on the held lock fast
|
|
236
|
-
|
|
226
|
+
_prime_worker(monkeypatch, rec)
|
|
237
227
|
out = tmp_path / "out"
|
|
238
228
|
out.mkdir()
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
holding = threading.Event()
|
|
243
|
-
release = threading.Event()
|
|
244
|
-
base_upload = rec.upload_folder
|
|
245
|
-
|
|
246
|
-
def upload(**kwargs):
|
|
247
|
-
base_upload(**kwargs)
|
|
248
|
-
if kwargs["path_in_repo"].endswith("checkpoint/checkpoint-4"):
|
|
249
|
-
holding.set() # the resume upload now holds the lock...
|
|
250
|
-
release.wait(5) # ...keep holding it across the on_train_end flush
|
|
251
|
-
|
|
252
|
-
rec.upload_folder = upload
|
|
253
|
-
|
|
254
|
-
started: list = []
|
|
255
|
-
real_thread = threading.Thread
|
|
256
|
-
monkeypatch.setattr(
|
|
257
|
-
worker.threading,
|
|
258
|
-
"Thread",
|
|
259
|
-
lambda *a, **k: started.append(real_thread(*a, **k)) or started[-1],
|
|
229
|
+
worker.make_checkpoint_upload_callback().on_train_end(
|
|
230
|
+
SimpleNamespace(output_dir=str(out)), None, None
|
|
260
231
|
)
|
|
261
|
-
|
|
262
|
-
cb = worker.make_checkpoint_upload_callback()
|
|
263
|
-
try:
|
|
264
|
-
cb.on_save(SimpleNamespace(output_dir=str(out)), SimpleNamespace(global_step=4), None)
|
|
265
|
-
assert holding.wait(5), "the step-4 upload should be holding the lock"
|
|
266
|
-
cb.on_train_end(SimpleNamespace(output_dir=str(out)), None, None)
|
|
267
|
-
deployable = [
|
|
268
|
-
u for u in rec.uploads if u["path_in_repo"].endswith("checkpoints/step-8/adapter")
|
|
269
|
-
]
|
|
270
|
-
assert len(deployable) == 1, "final deployable must publish even when the lock is held"
|
|
271
|
-
finally:
|
|
272
|
-
release.set()
|
|
273
|
-
for t in started:
|
|
274
|
-
t.join(5)
|
|
232
|
+
assert rec.uploads == []
|
|
275
233
|
|
|
276
234
|
|
|
277
|
-
def
|
|
235
|
+
def test_on_save_uploads_synchronously_before_returning(
|
|
236
|
+
tmp_path, monkeypatch, fake_trainer_callback
|
|
237
|
+
):
|
|
238
|
+
"""Uploads are synchronous: by the time on_save returns, the checkpoint is fully on HF — no
|
|
239
|
+
background thread is left running. This is what makes "upload busy" impossible by construction:
|
|
240
|
+
a later save can never find this one still in flight."""
|
|
278
241
|
import flash.engine.worker as worker
|
|
279
242
|
|
|
280
243
|
rec = _RecordingHfApi()
|
|
281
244
|
_prime_worker(monkeypatch, rec)
|
|
282
245
|
out = tmp_path / "out"
|
|
283
246
|
out.mkdir()
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
)
|
|
287
|
-
|
|
247
|
+
_make_ckpt_dir(out, 4)
|
|
248
|
+
cb = worker.make_checkpoint_upload_callback()
|
|
249
|
+
cb.on_save(SimpleNamespace(output_dir=str(out)), SimpleNamespace(global_step=4), None)
|
|
250
|
+
# Fully uploaded the moment on_save returns — no polling, no thread join needed.
|
|
251
|
+
paths = [u["path_in_repo"] for u in rec.uploads]
|
|
252
|
+
assert "rl/flash-ckpt-1/checkpoints/step-4/adapter" in paths
|
|
253
|
+
assert "rl/flash-ckpt-1/checkpoint/checkpoint-4" in paths
|
|
288
254
|
|
|
289
255
|
|
|
290
256
|
def test_on_save_publishes_deployable_before_resume(tmp_path, monkeypatch, fake_trainer_callback):
|
|
@@ -294,25 +260,11 @@ def test_on_save_publishes_deployable_before_resume(tmp_path, monkeypatch, fake_
|
|
|
294
260
|
|
|
295
261
|
rec = _RecordingHfApi()
|
|
296
262
|
_prime_worker(monkeypatch, rec)
|
|
297
|
-
|
|
298
|
-
class _SyncThread: # run the upload body inline so the unit test is deterministic
|
|
299
|
-
def __init__(self, target=None, daemon=None, **kw):
|
|
300
|
-
self._target = target
|
|
301
|
-
|
|
302
|
-
def start(self):
|
|
303
|
-
if self._target:
|
|
304
|
-
self._target()
|
|
305
|
-
|
|
306
|
-
monkeypatch.setattr(worker.threading, "Thread", _SyncThread)
|
|
307
263
|
out = tmp_path / "out"
|
|
308
264
|
out.mkdir()
|
|
309
265
|
_make_ckpt_dir(out, 4)
|
|
310
266
|
cb = worker.make_checkpoint_upload_callback()
|
|
311
|
-
cb.on_save(
|
|
312
|
-
SimpleNamespace(output_dir=str(out)),
|
|
313
|
-
SimpleNamespace(global_step=4),
|
|
314
|
-
None,
|
|
315
|
-
)
|
|
267
|
+
cb.on_save(SimpleNamespace(output_dir=str(out)), SimpleNamespace(global_step=4), None)
|
|
316
268
|
paths = [u["path_in_repo"] for u in rec.uploads]
|
|
317
269
|
assert paths == [
|
|
318
270
|
"rl/flash-ckpt-1/checkpoints/step-4/adapter", # deployable first
|
|
@@ -320,115 +272,61 @@ def test_on_save_publishes_deployable_before_resume(tmp_path, monkeypatch, fake_
|
|
|
320
272
|
]
|
|
321
273
|
|
|
322
274
|
|
|
323
|
-
def
|
|
324
|
-
"""
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
import
|
|
275
|
+
def test_consecutive_saves_each_upload_no_coalescing(tmp_path, monkeypatch, fake_trainer_callback):
|
|
276
|
+
"""Every save uploads its own step. Synchronous uploads make "upload busy" coalescing
|
|
277
|
+
impossible: back-to-back saves each publish their deployable, so the registered step list is
|
|
278
|
+
never thinned under contention (the old newest-wins slot dropped intermediate steps)."""
|
|
279
|
+
import flash.engine.worker as worker
|
|
328
280
|
|
|
329
281
|
rec = _RecordingHfApi()
|
|
330
|
-
|
|
331
|
-
|
|
282
|
+
_prime_worker(monkeypatch, rec)
|
|
332
283
|
out = tmp_path / "out"
|
|
333
284
|
out.mkdir()
|
|
334
|
-
_make_ckpt_dir(out, 4)
|
|
335
|
-
_make_ckpt_dir(out, 8)
|
|
336
|
-
|
|
337
|
-
holding = threading.Event()
|
|
338
|
-
release = threading.Event()
|
|
339
|
-
base_upload = rec.upload_folder
|
|
340
|
-
|
|
341
|
-
def upload(**kwargs):
|
|
342
|
-
base_upload(**kwargs)
|
|
343
|
-
if kwargs["path_in_repo"].endswith("checkpoint/checkpoint-4"):
|
|
344
|
-
holding.set() # the step-4 resume upload now holds the lock...
|
|
345
|
-
release.wait(5) # ...until we let it finish
|
|
346
|
-
|
|
347
|
-
rec.upload_folder = upload
|
|
348
|
-
|
|
349
285
|
cb = worker.make_checkpoint_upload_callback()
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
deployable = [
|
|
362
|
-
u for u in rec.uploads if u["path_in_repo"].endswith("checkpoints/step-8/adapter")
|
|
363
|
-
]
|
|
364
|
-
assert len(deployable) == 1, "a busy-lock save must be queued and uploaded, not skipped"
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
def test_upload_pump_no_lost_wakeup(tmp_path):
|
|
368
|
-
"""Regression: a save enqueued in the window where the in-flight upload has passed its own
|
|
369
|
-
drain check but not yet finished must still be started by done() — with the old two-lock
|
|
370
|
-
design it could be silently dropped."""
|
|
371
|
-
from flash.engine.worker.hf import _UploadPump
|
|
286
|
+
for step in (4, 8, 12):
|
|
287
|
+
_make_ckpt_dir(out, step)
|
|
288
|
+
cb.on_save(SimpleNamespace(output_dir=str(out)), SimpleNamespace(global_step=step), None)
|
|
289
|
+
deployables = sorted(
|
|
290
|
+
p["path_in_repo"] for p in rec.uploads if p["path_in_repo"].endswith("/adapter")
|
|
291
|
+
)
|
|
292
|
+
assert deployables == [
|
|
293
|
+
"rl/flash-ckpt-1/checkpoints/step-12/adapter",
|
|
294
|
+
"rl/flash-ckpt-1/checkpoints/step-4/adapter",
|
|
295
|
+
"rl/flash-ckpt-1/checkpoints/step-8/adapter",
|
|
296
|
+
], "each save must publish its own deployable — no step coalesced away"
|
|
372
297
|
|
|
373
|
-
ckpt = tmp_path / "checkpoint-8"
|
|
374
|
-
ckpt.mkdir()
|
|
375
|
-
started: list[int] = []
|
|
376
|
-
pump = _UploadPump(lambda step, d: started.append(step))
|
|
377
|
-
# Simulate an in-flight upload that has already drained the queue (the racy window).
|
|
378
|
-
pump.uploading = True
|
|
379
|
-
pump.enqueue(8, str(ckpt))
|
|
380
|
-
assert started == [], "enqueue while busy must queue, not start"
|
|
381
|
-
pump.done() # in-flight finishes: the queued step MUST be pumped, never lost
|
|
382
|
-
assert started == [8]
|
|
383
|
-
pump.done()
|
|
384
|
-
assert started == [8], "queue is single-slot; nothing left to start"
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
def test_on_train_end_flushes_queued_deployable_without_resume_race(
|
|
388
|
-
tmp_path, monkeypatch, fake_trainer_callback
|
|
389
|
-
):
|
|
390
|
-
"""A save queued behind a still-in-flight upload at run end must publish its deployable adapter
|
|
391
|
-
without starting a second resume-checkpoint upload/prune race."""
|
|
392
|
-
import threading
|
|
393
298
|
|
|
299
|
+
def test_on_save_retries_transient_upload_error(tmp_path, monkeypatch, fake_trainer_callback):
|
|
300
|
+
"""A transient HF error must not cost the step: the synchronous upload retries until it lands,
|
|
301
|
+
so `save_every` is a guarantee, not best-effort."""
|
|
394
302
|
from flash.engine.worker import hf as worker_hf
|
|
395
303
|
|
|
396
304
|
rec = _RecordingHfApi()
|
|
397
305
|
worker = _prime_worker(monkeypatch, rec)
|
|
398
|
-
monkeypatch.setattr(worker_hf, "
|
|
306
|
+
monkeypatch.setattr(worker_hf, "_CKPT_UPLOAD_BACKOFF_S", 0.0) # no real sleep between retries
|
|
399
307
|
|
|
400
308
|
out = tmp_path / "out"
|
|
401
309
|
out.mkdir()
|
|
402
310
|
_make_ckpt_dir(out, 4)
|
|
403
|
-
_make_ckpt_dir(out, 8)
|
|
404
311
|
|
|
405
|
-
|
|
406
|
-
release = threading.Event()
|
|
312
|
+
resume_calls = {"n": 0}
|
|
407
313
|
base_upload = rec.upload_folder
|
|
408
314
|
|
|
409
|
-
def
|
|
410
|
-
base_upload(**kwargs)
|
|
315
|
+
def flaky(**kwargs):
|
|
411
316
|
if kwargs["path_in_repo"].endswith("checkpoint/checkpoint-4"):
|
|
412
|
-
|
|
413
|
-
|
|
317
|
+
resume_calls["n"] += 1
|
|
318
|
+
if resume_calls["n"] == 1:
|
|
319
|
+
raise RuntimeError("transient 500 from HF")
|
|
320
|
+
return base_upload(**kwargs)
|
|
414
321
|
|
|
415
|
-
rec.upload_folder =
|
|
322
|
+
rec.upload_folder = flaky
|
|
416
323
|
|
|
417
324
|
cb = worker.make_checkpoint_upload_callback()
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
paths = [u["path_in_repo"] for u in rec.uploads]
|
|
424
|
-
assert "rl/flash-ckpt-1/checkpoints/step-8/adapter" in paths, (
|
|
425
|
-
"queued step's deployable must be flushed at train end"
|
|
426
|
-
)
|
|
427
|
-
assert "rl/flash-ckpt-1/checkpoint/checkpoint-8" not in paths, (
|
|
428
|
-
"queued step's resume checkpoint must not race the in-flight resume prune"
|
|
429
|
-
)
|
|
430
|
-
finally:
|
|
431
|
-
release.set()
|
|
325
|
+
cb.on_save(SimpleNamespace(output_dir=str(out)), SimpleNamespace(global_step=4), None)
|
|
326
|
+
assert resume_calls["n"] == 2, "the resume upload must be retried after a transient failure"
|
|
327
|
+
assert any(u["path_in_repo"].endswith("checkpoint/checkpoint-4") for u in rec.uploads), (
|
|
328
|
+
"the checkpoint must land on retry, not be skipped"
|
|
329
|
+
)
|
|
432
330
|
|
|
433
331
|
|
|
434
332
|
def test_prune_stale_resume_checkpoints_keeps_only_latest(monkeypatch):
|
|
@@ -485,15 +383,6 @@ def test_on_save_skips_deployable_when_vl_recombine_fails(
|
|
|
485
383
|
|
|
486
384
|
monkeypatch.setattr(worker, "recombined_warmstart_adapter_dir", _raise)
|
|
487
385
|
|
|
488
|
-
class _SyncThread:
|
|
489
|
-
def __init__(self, target=None, daemon=None, **kw):
|
|
490
|
-
self._target = target
|
|
491
|
-
|
|
492
|
-
def start(self):
|
|
493
|
-
if self._target:
|
|
494
|
-
self._target()
|
|
495
|
-
|
|
496
|
-
monkeypatch.setattr(worker.threading, "Thread", _SyncThread)
|
|
497
386
|
out = tmp_path / "out"
|
|
498
387
|
out.mkdir()
|
|
499
388
|
_make_ckpt_dir(out, 4)
|
|
@@ -77,6 +77,10 @@ class _FakeClient:
|
|
|
77
77
|
self.calls.append(("cancel", run_id))
|
|
78
78
|
return {"run_id": run_id, "state": "cancelled"}
|
|
79
79
|
|
|
80
|
+
def checkpoints(self, run_id: str) -> list[dict]:
|
|
81
|
+
self.calls.append(("checkpoints", run_id))
|
|
82
|
+
return [{"step": 20}, {"step": 40}]
|
|
83
|
+
|
|
80
84
|
def deploy(self, run_id: str, **kwargs) -> dict:
|
|
81
85
|
self.calls.append(("deploy", run_id, kwargs))
|
|
82
86
|
return {
|
|
@@ -372,6 +376,36 @@ def test_follow_logs_uses_status_progress_when_log_tail_lags(monkeypatch, capsys
|
|
|
372
376
|
assert "realized_cost=$1.2346" in err
|
|
373
377
|
|
|
374
378
|
|
|
379
|
+
def test_cancel_surfaces_surviving_checkpoints(fake_client, capsys) -> None:
|
|
380
|
+
"""`state=cancelled` + adapter_ref=null + cost=0 reads as discardable, yet the per-step
|
|
381
|
+
deployable checkpoints streamed before the cancel survive it — the cancel output must say
|
|
382
|
+
so (on stderr in the plain path, keeping the stdout JSON machine-readable)."""
|
|
383
|
+
import json as _json
|
|
384
|
+
|
|
385
|
+
assert _run(["cancel", "flash-1"]) == 0
|
|
386
|
+
assert ("checkpoints", "flash-1") in fake_client.calls
|
|
387
|
+
out, err = capsys.readouterr()
|
|
388
|
+
assert _json.loads(out)["state"] == "cancelled" # stdout stays pure JSON in the plain path
|
|
389
|
+
assert "2 deployable checkpoint(s) survive this cancel" in err
|
|
390
|
+
assert "flash checkpoints flash-1" in err
|
|
391
|
+
assert "flash deploy flash-1/step-40" in err # points at the newest surviving step
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def test_cancel_hint_is_best_effort_when_checkpoint_listing_fails(
|
|
395
|
+
fake_client, capsys, monkeypatch
|
|
396
|
+
) -> None:
|
|
397
|
+
"""The surviving-checkpoints lookup must never break `flash cancel` itself."""
|
|
398
|
+
|
|
399
|
+
def boom(run_id):
|
|
400
|
+
raise RuntimeError("backend hiccup")
|
|
401
|
+
|
|
402
|
+
monkeypatch.setattr(fake_client, "checkpoints", boom)
|
|
403
|
+
assert _run(["cancel", "flash-1"]) == 0
|
|
404
|
+
out, err = capsys.readouterr()
|
|
405
|
+
assert '"state": "cancelled"' in out
|
|
406
|
+
assert "deployable checkpoint" not in err
|
|
407
|
+
|
|
408
|
+
|
|
375
409
|
def test_cancel_deploy_undeploy_deployments(fake_client, capsys) -> None:
|
|
376
410
|
assert _run(["cancel", "flash-1"]) == 0
|
|
377
411
|
assert ("cancel", "flash-1") in fake_client.calls
|