freesolo-flash-dev 1.0.3__tar.gz → 1.0.4__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-1.0.3 → freesolo_flash_dev-1.0.4}/Dockerfile.worker +1 -1
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/PKG-INFO +4 -4
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cli/__init__.py +5 -1
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cli/commands.py +16 -14
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cli/envpush.py +192 -54
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cli/render.py +76 -13
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/client/http.py +24 -10
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/__init__.py +6 -0
- freesolo_flash_dev-1.0.4/flash/engine/worker/grpo_multimodal.py +274 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/hf.py +342 -119
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/opd.py +37 -6
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/rl.py +18 -1
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/teacher.py +111 -1
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/_worker.py +5 -1
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/schema/__init__.py +35 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/routes/meta.py +8 -1
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/routes/serving.py +106 -37
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/pyproject.toml +11 -8
- freesolo_flash_dev-1.0.4/tests/test_async_uploads.py +448 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_checkpoints.py +15 -14
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cli_commands.py +89 -9
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cli_render_theme.py +108 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_client.py +58 -6
- freesolo_flash_dev-1.0.4/tests/test_env_push.py +700 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_health_liveness.py +6 -1
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_kernel_fingerprint.py +18 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_opd.py +157 -33
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_resume_on_retry.py +2 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_server_api.py +156 -1
- freesolo_flash_dev-1.0.4/tests/test_single_turn_multimodal_grpo.py +368 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_spec_and_validation.py +49 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_training_controls.py +2 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/uv.lock +4 -4
- freesolo_flash_dev-1.0.3/tests/test_env_push.py +0 -372
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.claude/skills/verify/SKILL.md +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.dockerignore +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.env.example +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/.gitignore +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/Dockerfile +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/LICENSE +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/README.md +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/_channel.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/_logging.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/catalog.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cli/env_setup.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cli/training_doc.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/client/config.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cost/analytical.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cost/facts.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/diagnostics.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/steps.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/structured_outputs.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/adapter.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/opd_gkd.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/opd_vllm.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/rng.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/sft.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/tokenizer_align.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/engine/worker_entrypoint.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/envs/adapter.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/envs/archive.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/envs/loader.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/lora_rank.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/multimodal.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/opd_retry_contract.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/_deadline.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/vast/api.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/py.typed +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/runner/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/runner/verified_revisions.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/serve/deploy.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/serve/urls.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/app.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/db.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/repo_cleanup.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/flash/spec.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/conftest.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_accounting_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_base_model_provenance.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_charge_pricing.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cli_commands_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cli_main_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cli_render_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_controlled_experiment_repairs.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_env_pull_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_env_pull_loader_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_export.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_flash_worker.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_hf_retry.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_instance_bootstrap_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_jobs.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_lambda_api_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_lora_rank_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_lora_rank_preflight.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_multimodal_input_grads.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_multimodal_training.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_multiturn_image_rollout.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_multiturn_rollout_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_multiturn_rollout_records.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_multiturn_rollout_request_policy.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_opd_full_state_resume.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_opd_resume_safety.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_opd_vllm.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_packing_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_provider_preflight_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_repo_cleanup.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_serve.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_server_app_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_server_checkpoints_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_server_envs_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_server_locks_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_serving_context_preflight.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_serving_pricing_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_serving_schema_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_sft_example_selection.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_sft_max_context.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_structured_outputs.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_train_context_preflight.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_vast_api.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_vast_api_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_vast_provider_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_verified_revisions.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_version.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_wandb_log_coverage.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.4}/tests/test_worker_thinking.py +0 -0
|
@@ -55,7 +55,7 @@ RUN pip install --no-cache-dir \
|
|
|
55
55
|
"flashinfer-python==0.6.6" \
|
|
56
56
|
"bitsandbytes>=0.49" \
|
|
57
57
|
"datasets>=4.7,<6" \
|
|
58
|
-
"huggingface_hub>=0
|
|
58
|
+
"huggingface_hub>=1.2.0" \
|
|
59
59
|
"accelerate>=1.4" \
|
|
60
60
|
"wandb>=0.17" \
|
|
61
61
|
"flash-linear-attention @ git+https://github.com/fla-org/flash-linear-attention.git@f0e213dbd8b5fb90c3c7eca869ac1706d5377139" \
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: Flash — managed LoRA post-training (SFT/GRPO/OPD) for Freesolo environments, driven by the `flash` CLI
|
|
5
5
|
Project-URL: Homepage, https://github.com/freesolo-co/flash
|
|
6
6
|
Project-URL: Repository, https://github.com/freesolo-co/flash
|
|
@@ -20,7 +20,7 @@ Requires-Dist: datasets>=2.19; extra == 'dev'
|
|
|
20
20
|
Requires-Dist: fastapi; extra == 'dev'
|
|
21
21
|
Requires-Dist: freesolo>=0.2.54; extra == 'dev'
|
|
22
22
|
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
23
|
-
Requires-Dist: huggingface-hub>=0
|
|
23
|
+
Requires-Dist: huggingface-hub>=1.2.0; extra == 'dev'
|
|
24
24
|
Requires-Dist: jsonschema>=4.23; extra == 'dev'
|
|
25
25
|
Requires-Dist: mypy>=1.13.0; extra == 'dev'
|
|
26
26
|
Requires-Dist: pytest>=9.0.3; extra == 'dev'
|
|
@@ -35,7 +35,7 @@ Requires-Dist: accelerate>=1.4; extra == 'gpu'
|
|
|
35
35
|
Requires-Dist: bitsandbytes>=0.49; extra == 'gpu'
|
|
36
36
|
Requires-Dist: datasets>=2.19; extra == 'gpu'
|
|
37
37
|
Requires-Dist: freesolo>=0.2.54; extra == 'gpu'
|
|
38
|
-
Requires-Dist: huggingface-hub>=0
|
|
38
|
+
Requires-Dist: huggingface-hub>=1.2.0; extra == 'gpu'
|
|
39
39
|
Requires-Dist: peft>=0.19; extra == 'gpu'
|
|
40
40
|
Requires-Dist: torch==2.10.0; extra == 'gpu'
|
|
41
41
|
Requires-Dist: transformers<5.11,>=5.6; extra == 'gpu'
|
|
@@ -46,7 +46,7 @@ Requires-Dist: datasets>=2.19; extra == 'server'
|
|
|
46
46
|
Requires-Dist: fastapi; extra == 'server'
|
|
47
47
|
Requires-Dist: freesolo>=0.2.54; extra == 'server'
|
|
48
48
|
Requires-Dist: httpx>=0.27; extra == 'server'
|
|
49
|
-
Requires-Dist: huggingface-hub>=0
|
|
49
|
+
Requires-Dist: huggingface-hub>=1.2.0; extra == 'server'
|
|
50
50
|
Requires-Dist: jsonschema>=4.23; extra == 'server'
|
|
51
51
|
Requires-Dist: referencing>=0.28.4; extra == 'server'
|
|
52
52
|
Requires-Dist: regex>=2024.11.6; extra == 'server'
|
|
@@ -424,7 +424,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
424
424
|
deployments.set_defaults(func=cmd_deployments)
|
|
425
425
|
|
|
426
426
|
chat = sub.add_parser("chat", help="chat with a deployed adapter")
|
|
427
|
-
chat.add_argument(
|
|
427
|
+
chat.add_argument(
|
|
428
|
+
"run_id",
|
|
429
|
+
help="a bare RUN_ID (its current deployment), RUN_ID/step-N (a specific deployed "
|
|
430
|
+
"checkpoint), or a full immutable adapter revision",
|
|
431
|
+
)
|
|
428
432
|
chat.add_argument("-m", "--message", required=True)
|
|
429
433
|
chat.add_argument(
|
|
430
434
|
"--system",
|
|
@@ -160,8 +160,9 @@ def cmd_gpus(args) -> int:
|
|
|
160
160
|
(info for info in GPU_INFO.values() if info.enum_member), key=lambda g: g.hourly_usd
|
|
161
161
|
)
|
|
162
162
|
tip = (
|
|
163
|
-
"Tip: GPU
|
|
164
|
-
"cheapest validated
|
|
163
|
+
"Tip: GPU allocation is automatic by default.\n"
|
|
164
|
+
"The allocator picks the cheapest validated class that fits. Pin a specific class by "
|
|
165
|
+
'adding exact_type = "<CLASS>" to the [gpu] section.'
|
|
165
166
|
)
|
|
166
167
|
if render.styled():
|
|
167
168
|
rows = [(info.name, info.vram_gb, runpod_rates.get(info.name)) for info in infos]
|
|
@@ -383,18 +384,26 @@ def _log_follow_progress(status: dict | None, fallback_state: str) -> tuple[str,
|
|
|
383
384
|
parts = [state]
|
|
384
385
|
heartbeat = status.get("last_heartbeat") if isinstance(status, dict) else None
|
|
385
386
|
if isinstance(heartbeat, dict):
|
|
387
|
+
heartbeat_age_seconds = render._heartbeat_age_seconds(heartbeat.get("ts"))
|
|
386
388
|
stage = heartbeat.get("stage")
|
|
387
389
|
if stage:
|
|
388
390
|
parts.append(f"stage={stage}")
|
|
391
|
+
if state == "running":
|
|
392
|
+
warmup = render.warmup_message(
|
|
393
|
+
stage,
|
|
394
|
+
heartbeat_age_seconds,
|
|
395
|
+
render.heartbeat_is_current_attempt(status, heartbeat),
|
|
396
|
+
)
|
|
397
|
+
if warmup:
|
|
398
|
+
parts.append(warmup)
|
|
389
399
|
step = heartbeat.get("step")
|
|
390
400
|
if step is not None:
|
|
391
401
|
parts.append(f"step={step}")
|
|
392
402
|
# live heartbeat age so a long quiet phase reads as "alive, throttled" not "frozen".
|
|
393
403
|
# minute granularity: the non-TTY follow path prints a line whenever this string changes,
|
|
394
404
|
# so a seconds-precision age would emit one line per poll.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
mins = int(max(0.0, time.time() - ts) // 60)
|
|
405
|
+
if heartbeat_age_seconds is not None:
|
|
406
|
+
mins = int(heartbeat_age_seconds // 60)
|
|
398
407
|
parts.append(f"hb={mins}m" if mins else "hb=<1m")
|
|
399
408
|
realized = status.get("realized_cost_usd")
|
|
400
409
|
if realized is not None:
|
|
@@ -737,18 +746,11 @@ def cmd_chat(args) -> int:
|
|
|
737
746
|
if revision is None and parsed is None:
|
|
738
747
|
print(
|
|
739
748
|
f"invalid chat target {args.run_id!r} "
|
|
740
|
-
"(expected a bare <run_id
|
|
741
|
-
file=sys.stderr,
|
|
742
|
-
)
|
|
743
|
-
return 1
|
|
744
|
-
if revision is None and parsed[1] is not None:
|
|
745
|
-
print(
|
|
746
|
-
"RUN_ID/step-N is not a valid chat target because it would route through the mutable "
|
|
747
|
-
"run alias; use the full immutable adapter revision returned by `flash deployments`",
|
|
749
|
+
"(expected a bare <run_id>, <run_id>/step-N, or full immutable adapter revision)",
|
|
748
750
|
file=sys.stderr,
|
|
749
751
|
)
|
|
750
752
|
return 1
|
|
751
|
-
chat_target = args.run_id
|
|
753
|
+
chat_target = args.run_id
|
|
752
754
|
client = client_from_config()
|
|
753
755
|
messages = [{"role": "user", "content": args.message}]
|
|
754
756
|
system = getattr(args, "system", None)
|
|
@@ -11,6 +11,7 @@ import os
|
|
|
11
11
|
import re
|
|
12
12
|
import sys
|
|
13
13
|
import tempfile
|
|
14
|
+
from collections.abc import Iterator
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import TYPE_CHECKING, Literal, TypeAlias
|
|
16
17
|
|
|
@@ -174,6 +175,10 @@ def cmd_env_delete(args) -> int:
|
|
|
174
175
|
|
|
175
176
|
|
|
176
177
|
_ENV_ENTRYPOINT = "environment.py"
|
|
178
|
+
_ENV_SYSPATH_BOOTSTRAP = (
|
|
179
|
+
"import os as _flash_os, sys as _flash_sys\n"
|
|
180
|
+
"_flash_sys.path.insert(0, _flash_os.path.dirname(__file__))\n"
|
|
181
|
+
)
|
|
177
182
|
_ENV_PUSH_IGNORED_NAMES = frozenset(
|
|
178
183
|
{
|
|
179
184
|
".prime",
|
|
@@ -183,26 +188,47 @@ _ENV_PUSH_IGNORED_NAMES = frozenset(
|
|
|
183
188
|
".venv",
|
|
184
189
|
".mypy_cache",
|
|
185
190
|
".pytest_cache",
|
|
186
|
-
"
|
|
187
|
-
"
|
|
191
|
+
".ruff_cache",
|
|
192
|
+
".idea",
|
|
193
|
+
".vscode",
|
|
194
|
+
".ds_store",
|
|
195
|
+
".git-worktrees",
|
|
196
|
+
".env",
|
|
197
|
+
".netrc",
|
|
198
|
+
".aws",
|
|
199
|
+
".ssh",
|
|
200
|
+
"venv",
|
|
201
|
+
"node_modules",
|
|
202
|
+
"credentials",
|
|
203
|
+
"secrets",
|
|
188
204
|
}
|
|
189
205
|
)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
".yml",
|
|
204
|
-
}
|
|
206
|
+
_ENV_PUSH_ROOT_IGNORED_NAMES = frozenset({"pyproject.toml", "source"})
|
|
207
|
+
_ENV_PUSH_SECRET_PATTERNS = (
|
|
208
|
+
".env.*",
|
|
209
|
+
"*.env",
|
|
210
|
+
"*.pem",
|
|
211
|
+
"*.key",
|
|
212
|
+
"*.pfx",
|
|
213
|
+
"*.p12",
|
|
214
|
+
"credentials*",
|
|
215
|
+
"id_rsa*",
|
|
216
|
+
"id_dsa*",
|
|
217
|
+
"id_ecdsa*",
|
|
218
|
+
"id_ed25519*",
|
|
205
219
|
)
|
|
220
|
+
# secret patterns match secret data FILES only (see _ignore_env_push_path): they never prune a
|
|
221
|
+
# directory, so a package tree like credentials_store/ still ships, and python source is exempt,
|
|
222
|
+
# so helper modules like credentials.py or id_ed25519_loader.py travel instead of being silently
|
|
223
|
+
# dropped and breaking the worker with ModuleNotFoundError. that keeps the prefixes broad enough
|
|
224
|
+
# to catch backup variants (credentials_backup, id_rsa_backup) without eating code or packages.
|
|
225
|
+
_ENV_PUSH_CODE_SUFFIXES = frozenset({".py", ".pyi"})
|
|
226
|
+
_ENV_PUSH_MAX_TOTAL_BYTES = 256 * 1024 * 1024
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
from flash.envs.archive_policy import ARCHIVE_MEMBER_LIMIT as _ENV_PUSH_MAX_FILES
|
|
230
|
+
except ImportError:
|
|
231
|
+
_ENV_PUSH_MAX_FILES = 5000
|
|
206
232
|
|
|
207
233
|
|
|
208
234
|
def _normalize_env_name(raw: str) -> str | None:
|
|
@@ -227,16 +253,12 @@ def _normalize_env_name(raw: str) -> str | None:
|
|
|
227
253
|
|
|
228
254
|
def _with_syspath_bootstrap(env_source: str) -> str:
|
|
229
255
|
"""Prepend a sys.path bootstrap so a published env can resolve shipped sibling helpers."""
|
|
230
|
-
bootstrap = (
|
|
231
|
-
"import os as _flash_os, sys as _flash_sys\n"
|
|
232
|
-
"_flash_sys.path.insert(0, _flash_os.path.dirname(__file__))\n"
|
|
233
|
-
)
|
|
234
256
|
import ast
|
|
235
257
|
|
|
236
258
|
try:
|
|
237
259
|
tree = ast.parse(env_source)
|
|
238
260
|
except SyntaxError:
|
|
239
|
-
return
|
|
261
|
+
return _ENV_SYSPATH_BOOTSTRAP + env_source
|
|
240
262
|
insert_after = 0
|
|
241
263
|
body = tree.body
|
|
242
264
|
i = 0
|
|
@@ -252,7 +274,16 @@ def _with_syspath_bootstrap(env_source: str) -> str:
|
|
|
252
274
|
insert_after = body[i].end_lineno or insert_after
|
|
253
275
|
i += 1
|
|
254
276
|
lines = env_source.splitlines(keepends=True)
|
|
255
|
-
return
|
|
277
|
+
return (
|
|
278
|
+
"".join(lines[:insert_after])
|
|
279
|
+
+ _ENV_SYSPATH_BOOTSTRAP
|
|
280
|
+
+ "".join(lines[insert_after:])
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _raise_walk_error(error: OSError) -> None:
|
|
285
|
+
"""fail env packaging loudly instead of silently skipping an unreadable directory."""
|
|
286
|
+
raise error
|
|
256
287
|
|
|
257
288
|
|
|
258
289
|
def _tar_b64(directory: Path) -> str:
|
|
@@ -266,44 +297,130 @@ def _tar_b64(directory: Path) -> str:
|
|
|
266
297
|
with tarfile.open(fileobj=buf, mode="w:gz") as tar:
|
|
267
298
|
for root, dirs, files in os.walk(directory):
|
|
268
299
|
root_path = Path(root)
|
|
269
|
-
dirs[:] = sorted(d for d in dirs if d not in _ENV_PUSH_IGNORED_NAMES)
|
|
270
|
-
for name in dirs:
|
|
271
|
-
child = root_path / name
|
|
272
|
-
tar.add(child, arcname=str(child.relative_to(directory)), recursive=False)
|
|
300
|
+
dirs[:] = sorted(d for d in dirs if d.lower() not in _ENV_PUSH_IGNORED_NAMES)
|
|
273
301
|
for name in sorted(files):
|
|
274
302
|
child = root_path / name
|
|
275
303
|
tar.add(child, arcname=str(child.relative_to(directory)), recursive=False)
|
|
276
304
|
return base64.b64encode(buf.getvalue()).decode()
|
|
277
305
|
|
|
278
306
|
|
|
279
|
-
def
|
|
280
|
-
"""
|
|
281
|
-
import
|
|
307
|
+
def _ignore_env_push_path(path: Path, *, env_root: Path, entrypoint: Path) -> bool:
|
|
308
|
+
"""Return whether a source path must not be included in an env package."""
|
|
309
|
+
import fnmatch
|
|
310
|
+
|
|
311
|
+
name = path.name
|
|
312
|
+
lowered_name = name.lower()
|
|
313
|
+
if path.is_symlink():
|
|
314
|
+
return True
|
|
315
|
+
if path.is_dir() and (path / "pyvenv.cfg").is_file():
|
|
316
|
+
return True
|
|
317
|
+
if path == entrypoint or (
|
|
318
|
+
path.parent == env_root and lowered_name == _ENV_ENTRYPOINT.lower()
|
|
319
|
+
):
|
|
320
|
+
return True
|
|
321
|
+
if lowered_name.startswith(".") or lowered_name in _ENV_PUSH_IGNORED_NAMES:
|
|
322
|
+
return True
|
|
323
|
+
if path.parent == env_root and lowered_name in _ENV_PUSH_ROOT_IGNORED_NAMES:
|
|
324
|
+
return True
|
|
325
|
+
if (
|
|
326
|
+
path.is_file()
|
|
327
|
+
and path.suffix.lower() not in _ENV_PUSH_CODE_SUFFIXES
|
|
328
|
+
and any(fnmatch.fnmatchcase(lowered_name, pattern) for pattern in _ENV_PUSH_SECRET_PATTERNS)
|
|
329
|
+
):
|
|
330
|
+
return True
|
|
331
|
+
return not (path.is_dir() or path.is_file())
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _iter_env_sidecar_files(
|
|
335
|
+
env_root: Path, *, entrypoint: Path, include_full_tree: bool
|
|
336
|
+
) -> Iterator[tuple[Path, Path]]:
|
|
337
|
+
"""Yield publishable sidecar files and their package-relative paths."""
|
|
338
|
+
import os
|
|
282
339
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
340
|
+
roots = [env_root]
|
|
341
|
+
if not include_full_tree:
|
|
342
|
+
# a single-file push carries only the entrypoint, its dataset sidecars, and its own
|
|
343
|
+
# docs. ship user-authored docs so the synthesized stub readme does not stand in for
|
|
344
|
+
# real training guidance the user shipped next to the module.
|
|
345
|
+
for doc_name in ("README.md", "TRAINING.md"):
|
|
346
|
+
doc = env_root / doc_name
|
|
347
|
+
if doc.is_file() and not _ignore_env_push_path(
|
|
348
|
+
doc, env_root=env_root, entrypoint=entrypoint
|
|
349
|
+
):
|
|
350
|
+
yield doc, doc.relative_to(env_root)
|
|
351
|
+
roots = [
|
|
352
|
+
env_root / name
|
|
353
|
+
for name in ("dataset", "datasets")
|
|
354
|
+
if (env_root / name).is_dir() and not (env_root / name).is_symlink()
|
|
355
|
+
]
|
|
356
|
+
|
|
357
|
+
for walk_root in roots:
|
|
358
|
+
for root, dirs, files in os.walk(
|
|
359
|
+
walk_root, topdown=True, followlinks=False, onerror=_raise_walk_error
|
|
288
360
|
):
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
shutil.copytree(
|
|
296
|
-
child,
|
|
297
|
-
target,
|
|
298
|
-
ignore=shutil.ignore_patterns(*_ENV_PUSH_IGNORED_NAMES),
|
|
361
|
+
root_path = Path(root)
|
|
362
|
+
dirs[:] = sorted(
|
|
363
|
+
name
|
|
364
|
+
for name in dirs
|
|
365
|
+
if not _ignore_env_push_path(
|
|
366
|
+
root_path / name, env_root=env_root, entrypoint=entrypoint
|
|
299
367
|
)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
368
|
+
)
|
|
369
|
+
for name in sorted(files):
|
|
370
|
+
child = root_path / name
|
|
371
|
+
if _ignore_env_push_path(child, env_root=env_root, entrypoint=entrypoint):
|
|
372
|
+
continue
|
|
373
|
+
yield child, child.relative_to(env_root)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _check_env_push_limits(
|
|
377
|
+
env_root: Path, *, entrypoint: Path, include_full_tree: bool, env_name: str
|
|
378
|
+
) -> None:
|
|
379
|
+
"""Reject oversized source trees before copying or building an in-memory archive."""
|
|
380
|
+
files = 1
|
|
381
|
+
total_bytes = entrypoint.stat().st_size + len(_ENV_SYSPATH_BOOTSTRAP.encode())
|
|
382
|
+
directories: set[Path] = set()
|
|
383
|
+
has_readme = False
|
|
384
|
+
for child, relative in _iter_env_sidecar_files(
|
|
385
|
+
env_root, entrypoint=entrypoint, include_full_tree=include_full_tree
|
|
386
|
+
):
|
|
387
|
+
files += 1
|
|
388
|
+
total_bytes += child.stat().st_size
|
|
389
|
+
has_readme = has_readme or relative == Path("README.md")
|
|
390
|
+
# the server counts every file AND directory against the same member cap when it
|
|
391
|
+
# repackages the checkout for download, so count unique parent dirs here too.
|
|
392
|
+
directories.update(parent for parent in relative.parents if parent != Path("."))
|
|
393
|
+
if not has_readme:
|
|
394
|
+
files += 1
|
|
395
|
+
total_bytes += len(f"# {env_name}\n\nFlash Freesolo environment.\n".encode())
|
|
396
|
+
|
|
397
|
+
members = files + len(directories)
|
|
398
|
+
if total_bytes > _ENV_PUSH_MAX_TOTAL_BYTES:
|
|
399
|
+
raise ValueError(
|
|
400
|
+
f"environment package totals {_human_bytes(total_bytes)} "
|
|
401
|
+
f"(limit {_human_bytes(_ENV_PUSH_MAX_TOTAL_BYTES)}); "
|
|
402
|
+
"remove large artifacts or use a smaller dataset"
|
|
403
|
+
)
|
|
404
|
+
if members > _ENV_PUSH_MAX_FILES:
|
|
405
|
+
raise ValueError(
|
|
406
|
+
f"selected environment contains {members:,} files and directories "
|
|
407
|
+
f"(limit {_ENV_PUSH_MAX_FILES:,}); "
|
|
408
|
+
"remove large artifacts or use a smaller dataset"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _copy_env_sidecars(
|
|
413
|
+
env_root: Path, dest: Path, *, entrypoint: Path, include_full_tree: bool
|
|
414
|
+
) -> None:
|
|
415
|
+
"""Copy publishable sidecar files beside environment.py, creating directories lazily."""
|
|
416
|
+
import shutil
|
|
417
|
+
|
|
418
|
+
for child, relative in _iter_env_sidecar_files(
|
|
419
|
+
env_root, entrypoint=entrypoint, include_full_tree=include_full_tree
|
|
420
|
+
):
|
|
421
|
+
target = dest / relative
|
|
422
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
423
|
+
shutil.copy2(child, target)
|
|
307
424
|
|
|
308
425
|
|
|
309
426
|
def _human_bytes(n: int) -> str:
|
|
@@ -385,8 +502,11 @@ def cmd_env_push(args) -> int:
|
|
|
385
502
|
src = Path(args.path)
|
|
386
503
|
if not src.exists():
|
|
387
504
|
return _err(f"no such path: {src}")
|
|
505
|
+
if src.is_symlink():
|
|
506
|
+
return _err(f"cannot publish {src}: symlinks are not allowed")
|
|
388
507
|
|
|
389
|
-
|
|
508
|
+
include_full_tree = src.is_dir()
|
|
509
|
+
if include_full_tree:
|
|
390
510
|
canonical_entrypoint = src / _ENV_ENTRYPOINT
|
|
391
511
|
if canonical_entrypoint.is_file():
|
|
392
512
|
entrypoint = canonical_entrypoint
|
|
@@ -410,11 +530,29 @@ def cmd_env_push(args) -> int:
|
|
|
410
530
|
else:
|
|
411
531
|
return _err(f"cannot publish {src}: expected a Freesolo .py module or an env directory.")
|
|
412
532
|
|
|
533
|
+
if entrypoint.is_symlink():
|
|
534
|
+
return _err(f"cannot publish {entrypoint}: symlinks are not allowed")
|
|
535
|
+
|
|
536
|
+
try:
|
|
537
|
+
_check_env_push_limits(
|
|
538
|
+
env_root,
|
|
539
|
+
entrypoint=entrypoint,
|
|
540
|
+
include_full_tree=include_full_tree,
|
|
541
|
+
env_name=env_name,
|
|
542
|
+
)
|
|
543
|
+
except (OSError, ValueError) as exc:
|
|
544
|
+
return _err(f"cannot publish {src}: {exc}")
|
|
545
|
+
|
|
413
546
|
with tempfile.TemporaryDirectory(prefix="flash-env-push-") as tmp:
|
|
414
547
|
pkg = Path(tmp)
|
|
415
548
|
module_source = entrypoint.read_text()
|
|
416
549
|
(pkg / _ENV_ENTRYPOINT).write_text(_with_syspath_bootstrap(module_source))
|
|
417
|
-
_copy_env_sidecars(
|
|
550
|
+
_copy_env_sidecars(
|
|
551
|
+
env_root,
|
|
552
|
+
pkg,
|
|
553
|
+
entrypoint=entrypoint,
|
|
554
|
+
include_full_tree=include_full_tree,
|
|
555
|
+
)
|
|
418
556
|
# Only synthesize a stub README when the env didn't ship its own (now carried as a
|
|
419
557
|
# ``.md`` sidecar) — don't clobber a user-authored README with boilerplate.
|
|
420
558
|
readme = pkg / "README.md"
|
|
@@ -533,21 +533,76 @@ def _humanize_ts(value) -> str | None:
|
|
|
533
533
|
return datetime.datetime.fromtimestamp(value, datetime.UTC).strftime("%Y-%m-%d %H:%M UTC")
|
|
534
534
|
|
|
535
535
|
|
|
536
|
-
def
|
|
537
|
-
"""
|
|
536
|
+
def _heartbeat_age_seconds(value: object) -> float | None:
|
|
537
|
+
"""Return heartbeat age in seconds, or None for an unusable timestamp."""
|
|
538
538
|
if not isinstance(value, (int, float)) or value <= 0:
|
|
539
539
|
return None
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
540
|
+
return max(0.0, time.time() - value)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _humanize_age_seconds(seconds: float | None) -> str | None:
|
|
544
|
+
if seconds is None:
|
|
545
|
+
return None
|
|
546
|
+
if seconds < 90:
|
|
547
|
+
return f"{int(seconds)}s ago"
|
|
548
|
+
if seconds < 5400:
|
|
549
|
+
return f"{int(seconds // 60)}m ago"
|
|
550
|
+
return f"{seconds / 3600:.1f}h ago"
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def _humanize_age(value) -> str | None:
|
|
554
|
+
"""Format an epoch seconds value as a compact age ("3m ago"), None for non-numbers."""
|
|
555
|
+
return _humanize_age_seconds(_heartbeat_age_seconds(value))
|
|
546
556
|
|
|
547
557
|
|
|
548
558
|
# heartbeat age past which the panel reminds that quiet is normal: worker uploads are throttled
|
|
549
|
-
# (240s quiet phases, up to 900s mid-training), so a frozen ts is usually
|
|
559
|
+
# (240s quiet phases, up to 900s mid-training), so a frozen ts is usually not a dead worker.
|
|
550
560
|
_HB_QUIET_HINT_AFTER_S = 300.0
|
|
561
|
+
_WARMUP_HEARTBEAT_FRESH_FOR_S = 1200.0
|
|
562
|
+
_WARMUP_STAGES = frozenset({"rl_train_start", "rl_initializing"})
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def heartbeat_is_current_attempt(obj: dict, heartbeat: dict) -> bool:
|
|
566
|
+
"""False only when the heartbeat provably belongs to a superseded retry attempt.
|
|
567
|
+
|
|
568
|
+
Retries reuse the seed's heartbeat path, so a recovered run flips back to ``running`` for the
|
|
569
|
+
replacement worker while ``last_heartbeat`` can still be the previous attempt's setup ping until
|
|
570
|
+
the new worker publishes one. ``remote.attempt`` is the live attempt; ``last_heartbeat.attempt``
|
|
571
|
+
is the one that produced the ping. When the live attempt is known, keep the reassurance only for a
|
|
572
|
+
heartbeat whose attempt matches it. When it is unknown (e.g. a managed status payload that omits
|
|
573
|
+
``remote``), fall back to ``warmup_message``'s age gating rather than suppress, so warmup still
|
|
574
|
+
reassures on planes that do not surface a live attempt.
|
|
575
|
+
"""
|
|
576
|
+
# reuse the poller's attempt-identity contract (a bounded nonnegative int, never a bool, string,
|
|
577
|
+
# or float) so the status display and stall detection agree on what a valid attempt is.
|
|
578
|
+
from flash.providers._poll import _attempt_int
|
|
579
|
+
|
|
580
|
+
remote = obj.get("remote")
|
|
581
|
+
current_attempt = _attempt_int(remote.get("attempt")) if isinstance(remote, dict) else None
|
|
582
|
+
if current_attempt is None:
|
|
583
|
+
return True
|
|
584
|
+
return _attempt_int(heartbeat.get("attempt")) == current_attempt
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def warmup_message(
|
|
588
|
+
stage: object,
|
|
589
|
+
heartbeat_age_seconds: float | None,
|
|
590
|
+
from_current_attempt: bool = True,
|
|
591
|
+
) -> str | None:
|
|
592
|
+
"""Explain healthy RL setup stages only while the heartbeat is fresh and from the live attempt."""
|
|
593
|
+
stage_name = str(stage)
|
|
594
|
+
if stage_name not in _WARMUP_STAGES:
|
|
595
|
+
return None
|
|
596
|
+
if not from_current_attempt:
|
|
597
|
+
return None
|
|
598
|
+
if heartbeat_age_seconds is None:
|
|
599
|
+
return None
|
|
600
|
+
if heartbeat_age_seconds > _WARMUP_HEARTBEAT_FRESH_FOR_S:
|
|
601
|
+
return None
|
|
602
|
+
return (
|
|
603
|
+
f"warming up (stage={stage_name}): initializing model, vLLM, and training kernels - "
|
|
604
|
+
"typically several minutes, sometimes 15-20 min; setup is not billed; do not cancel"
|
|
605
|
+
)
|
|
551
606
|
|
|
552
607
|
|
|
553
608
|
def _heartbeat_pairs(obj: dict) -> list[tuple[str, str]]:
|
|
@@ -562,11 +617,19 @@ def _heartbeat_pairs(obj: dict) -> list[tuple[str, str]]:
|
|
|
562
617
|
if hb.get("liveness"):
|
|
563
618
|
worker += " · alive ping"
|
|
564
619
|
pairs = [("worker", worker)]
|
|
565
|
-
|
|
566
|
-
|
|
620
|
+
heartbeat_age_seconds = _heartbeat_age_seconds(hb.get("ts"))
|
|
621
|
+
running = str(obj.get("state") or "") == "running"
|
|
622
|
+
if running:
|
|
623
|
+
warmup = warmup_message(
|
|
624
|
+
hb.get("stage"),
|
|
625
|
+
heartbeat_age_seconds,
|
|
626
|
+
heartbeat_is_current_attempt(obj, hb),
|
|
627
|
+
)
|
|
628
|
+
if warmup:
|
|
629
|
+
pairs.append(("warmup", warmup))
|
|
630
|
+
age = _humanize_age_seconds(heartbeat_age_seconds)
|
|
567
631
|
if age:
|
|
568
|
-
running
|
|
569
|
-
if running and isinstance(ts, (int, float)) and (time.time() - ts) > _HB_QUIET_HINT_AFTER_S:
|
|
632
|
+
if running and heartbeat_age_seconds > _HB_QUIET_HINT_AFTER_S:
|
|
570
633
|
age += _dim(
|
|
571
634
|
" (heartbeat uploads are throttled; quiet is not dead - check flash log -f <run-id>)"
|
|
572
635
|
)
|
|
@@ -121,6 +121,9 @@ class _ProgressReader:
|
|
|
121
121
|
return chunk
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
_CHAT_STEP_SELECTOR_CAPABILITY = "chat_step_selector_v1"
|
|
125
|
+
|
|
126
|
+
|
|
124
127
|
def _validate_chat_messages(messages: list[dict]) -> None:
|
|
125
128
|
if not isinstance(messages, list):
|
|
126
129
|
raise ClientError("chat messages must be a list")
|
|
@@ -129,24 +132,20 @@ def _validate_chat_messages(messages: list[dict]) -> None:
|
|
|
129
132
|
raise ClientError(f"chat messages[{index}] must be an object")
|
|
130
133
|
|
|
131
134
|
|
|
132
|
-
def _parse_chat_target(target: str) -> tuple[str, str | None]:
|
|
135
|
+
def _parse_chat_target(target: str) -> tuple[str, str | None, int | None]:
|
|
133
136
|
from flash.schema import parse_adapter_revision, parse_checkpoint_ref
|
|
134
137
|
|
|
135
138
|
revision = parse_adapter_revision(target)
|
|
136
139
|
if revision is not None:
|
|
137
|
-
return revision[0], target.strip()
|
|
140
|
+
return revision[0], target.strip(), None
|
|
138
141
|
parsed = parse_checkpoint_ref(target)
|
|
139
142
|
if parsed is None:
|
|
140
143
|
raise ClientError(
|
|
141
|
-
"invalid run id: expected a bare RUN_ID or full immutable adapter
|
|
144
|
+
"invalid run id: expected a bare RUN_ID, RUN_ID/step-N, or a full immutable adapter "
|
|
145
|
+
"revision"
|
|
142
146
|
)
|
|
143
147
|
run_id, step = parsed
|
|
144
|
-
|
|
145
|
-
raise ClientError(
|
|
146
|
-
"RUN_ID/step-N is not a valid chat target because it would route through the mutable "
|
|
147
|
-
"run alias; use the full immutable adapter revision returned by flash deployments"
|
|
148
|
-
)
|
|
149
|
-
return run_id, None
|
|
148
|
+
return run_id, None, step
|
|
150
149
|
|
|
151
150
|
|
|
152
151
|
def _prepare_chat_request(
|
|
@@ -157,7 +156,7 @@ def _prepare_chat_request(
|
|
|
157
156
|
*,
|
|
158
157
|
stream: bool = False,
|
|
159
158
|
) -> tuple[str, dict[str, Any]]:
|
|
160
|
-
base_run_id, adapter_revision = _parse_chat_target(target)
|
|
159
|
+
base_run_id, adapter_revision, step = _parse_chat_target(target)
|
|
161
160
|
_validate_chat_messages(messages)
|
|
162
161
|
body: dict[str, Any] = {
|
|
163
162
|
"messages": messages,
|
|
@@ -168,6 +167,8 @@ def _prepare_chat_request(
|
|
|
168
167
|
body["stream"] = True
|
|
169
168
|
if adapter_revision is not None:
|
|
170
169
|
body["adapter_revision"] = adapter_revision
|
|
170
|
+
elif step is not None:
|
|
171
|
+
body["step"] = step
|
|
171
172
|
return base_run_id, body
|
|
172
173
|
|
|
173
174
|
|
|
@@ -289,6 +290,15 @@ class ApiClient:
|
|
|
289
290
|
def health(self) -> dict:
|
|
290
291
|
return self._request("GET", "/v1/health", timeout=10.0)
|
|
291
292
|
|
|
293
|
+
def _require_chat_step_selector(self) -> None:
|
|
294
|
+
capabilities = self.health().get("capabilities")
|
|
295
|
+
if not isinstance(capabilities, list) or _CHAT_STEP_SELECTOR_CAPABILITY not in capabilities:
|
|
296
|
+
raise ClientError(
|
|
297
|
+
"chat checkpoint selectors require a control plane that advertises "
|
|
298
|
+
f"{_CHAT_STEP_SELECTOR_CAPABILITY}; use a full immutable adapter revision or "
|
|
299
|
+
"upgrade the control plane"
|
|
300
|
+
)
|
|
301
|
+
|
|
292
302
|
def publish_env(
|
|
293
303
|
self,
|
|
294
304
|
*,
|
|
@@ -458,6 +468,8 @@ class ApiClient:
|
|
|
458
468
|
temperature,
|
|
459
469
|
max_tokens,
|
|
460
470
|
)
|
|
471
|
+
if "step" in body:
|
|
472
|
+
self._require_chat_step_selector()
|
|
461
473
|
return self._request(
|
|
462
474
|
"POST",
|
|
463
475
|
f"/v1/runs/{base_run_id}/chat",
|
|
@@ -479,6 +491,8 @@ class ApiClient:
|
|
|
479
491
|
max_tokens,
|
|
480
492
|
stream=True,
|
|
481
493
|
)
|
|
494
|
+
if "step" in body:
|
|
495
|
+
self._require_chat_step_selector()
|
|
482
496
|
headers = {"Content-Type": "application/json", **self._auth_headers()}
|
|
483
497
|
req = urllib.request.Request(
|
|
484
498
|
f"{self.api_url}/v1/runs/{base_run_id}/chat",
|