freesolo-flash-dev 1.0.43__tar.gz → 1.0.45__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.43 → freesolo_flash_dev-1.0.45}/PKG-INFO +1 -1
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/training_doc.py +33 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_verl.py +69 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/opd_retry_contract.py +21 -1
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/runner/__init__.py +17 -2
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/pyproject.toml +2 -2
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multi_gpu_gate.py +21 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_opd_resume_safety.py +56 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_opd_verl.py +194 -2
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/uv.lock +1 -1
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.dockerignore +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.env.example +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/dependabot.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.gitignore +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/CODE_OF_CONDUCT.md +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/CONTRIBUTING.md +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/Dockerfile +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/LICENSE +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/NOTICE +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/README.md +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/SECURITY.md +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/_channel.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/_logging.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/catalog.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/commands.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/env_setup.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/env_test.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/render.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/traces.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/client/config.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/client/http.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cost/analytical.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cost/facts.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/diagnostics.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/multiturn_reward_scoring.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/steps.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/structured_outputs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/adapter.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/grpo_multimodal.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/grpo_perturn_trainer.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/hf.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_gkd.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_verl_multiturn.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_verl_plugin.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_verl_structured.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_vllm.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/rl.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/rl_verl.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/rng.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/rollout_samples.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/sft.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/sft_verl.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/teacher.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/tokenizer_align.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/verl_common.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker_entrypoint.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/adapter.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/archive.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/loader.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/lora_rank.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/multimodal.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/opd_verl_validation.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_deadline.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_worker.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/api.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/weight_cache_preload.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/py.typed +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/runner/verified_revisions.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/schema/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/deploy.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/preflight.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/urls.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/app.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/db.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/projects.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/repo_cleanup.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/spec.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/scripts/scrub_history.sh +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/conftest.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_accounting_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_async_uploads.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_base_model_provenance.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_charge_pricing.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_commands.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_commands_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_main_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_render_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_traces.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_client.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_client_stream_reads.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_controlled_experiment_repairs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_deploy_latency.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_pull_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_pull_loader_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_test.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_export.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_flash_worker.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_gpu_type_single_field.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_grpo_cudagraphs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_grpo_perturn.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_hf_retry.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_instance_bootstrap_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_jobs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_lambda_api_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_lora_rank_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_lora_rank_preflight.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_managed_config_fields.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multimodal_input_grads.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multimodal_training.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_image_rollout.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_per_turn_reward.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_rollout_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_rollout_records.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_rollout_request_policy.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_named_reward_metrics.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_opd.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_opd_full_state_resume.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_opd_vllm.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_packing_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_projects.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_provider_preflight_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_repo_cleanup.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_rl_verl.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_rollout_samples.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_serve.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_api.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_app_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_auth_singleflight.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_checkpoints_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_envs_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_locks_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_serving_context_preflight.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_serving_pricing_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_serving_schema_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_activation_shaping.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_chunked_nll.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_example_selection.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_max_context.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_tokenize_cache.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_verl.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_single_turn_multimodal_grpo.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_spec_and_validation.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_structured_outputs.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_train_context_preflight.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_training_controls.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vast_api.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vast_api_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vast_provider_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_verified_revisions.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_verl_common.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_version.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vram_architecture.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_wandb_log_coverage.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_warmstart_adapter_download.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_thinking.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.45
|
|
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
|
|
@@ -697,6 +697,39 @@ on a beyond-noise improvement.
|
|
|
697
697
|
|
|
698
698
|
---
|
|
699
699
|
|
|
700
|
+
## Multi-GPU training (`gpu.count > 1`)
|
|
701
|
+
|
|
702
|
+
Flash trains on one card by default. The default `trl` backend is single-process and never
|
|
703
|
+
reads `gpu.count`, so raising the count alone would rent n cards and train on one — submit
|
|
704
|
+
rejects that rather than billing for idle hardware.
|
|
705
|
+
|
|
706
|
+
To shard one job across several cards, select the `verl` backend for the phase you are running:
|
|
707
|
+
|
|
708
|
+
```toml
|
|
709
|
+
[gpu]
|
|
710
|
+
type = "B200"
|
|
711
|
+
count = 4
|
|
712
|
+
# provider must also be pinned: not every provider puts all n cards on ONE machine, and
|
|
713
|
+
# submit names the ones that qualify when it rejects an unpinned multi-gpu spec.
|
|
714
|
+
|
|
715
|
+
[worker_env]
|
|
716
|
+
FLASH_SFT_BACKEND = "verl" # SFT
|
|
717
|
+
# FLASH_RL_BACKEND = "verl" # GRPO (the key is RL, not GRPO)
|
|
718
|
+
# FLASH_OPD_BACKEND = "verl" # OPD
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
One key per phase, and GRPO's is `FLASH_RL_BACKEND`. The verl worker launches one rank per
|
|
722
|
+
card with Ulysses sequence parallelism.
|
|
723
|
+
|
|
724
|
+
The verl GRPO backend covers single-turn, non-tool, non-multimodal runs; multi-turn, tool, and
|
|
725
|
+
image-prompt environments stay on `trl` (which is single-GPU). Warm-starting with
|
|
726
|
+
`init_from_adapter` while `kl_penalty_coef > 0` is also `trl`-only: verl computes its KL
|
|
727
|
+
reference with adapters disabled, so the reference would be the bare base model instead of your
|
|
728
|
+
SFT adapter, pulling the policy back toward base. Every one of these raises at startup rather
|
|
729
|
+
than quietly training on a different contract.
|
|
730
|
+
|
|
731
|
+
---
|
|
732
|
+
|
|
700
733
|
## Command reference
|
|
701
734
|
|
|
702
735
|
```bash
|
|
@@ -86,6 +86,20 @@ _TEXT_TEACHER_SHUTDOWN_WAIT_S = 5.0
|
|
|
86
86
|
_TEXT_TEACHER_REQUEST_BACKLOG = 64
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
def _align_granularity(groups, student_tokens) -> float:
|
|
90
|
+
"""mean student-tokens-per-group: the alignment-health signal coverage cannot provide.
|
|
91
|
+
|
|
92
|
+
a degenerate alignment that collapses every student token onto a single group still scores
|
|
93
|
+
coverage ~1.0, so coverage alone never flags it. this is the trl opd formula verbatim
|
|
94
|
+
(``opd.py``): non-empty student spans over surviving groups, counted after fully-forced groups
|
|
95
|
+
are dropped so the ratio describes the signal that actually reaches the loss.
|
|
96
|
+
"""
|
|
97
|
+
if not groups:
|
|
98
|
+
return 0.0
|
|
99
|
+
n_align = sum(1 for st in student_tokens if st.end > st.start)
|
|
100
|
+
return n_align / len(groups)
|
|
101
|
+
|
|
102
|
+
|
|
89
103
|
class _TeacherBridgeHTTPServer(ThreadingHTTPServer):
|
|
90
104
|
request_queue_size = _TEXT_TEACHER_REQUEST_BACKLOG
|
|
91
105
|
|
|
@@ -518,6 +532,12 @@ class _TeacherAlignmentBridge:
|
|
|
518
532
|
self.forced_tokens = int(state.get("forced_tokens", 0))
|
|
519
533
|
self.dropped_forced_groups = int(state.get("dropped_forced_groups", 0))
|
|
520
534
|
self.coverage_sum = float(state.get("coverage_sum", state.get("granularity_sum", 0.0)))
|
|
535
|
+
# alignment GRANULARITY (mean aligned-groups-per-sequence), distinct from coverage: a
|
|
536
|
+
# collapsed alignment that maps every student token onto one group still scores coverage
|
|
537
|
+
# ~1.0, so coverage alone cannot flag that failure mode. no legacy alias here -- the old
|
|
538
|
+
# granularity_* state keys held coverage, so reading them would restore the wrong quantity.
|
|
539
|
+
self.align_group_sum = float(state.get("align_group_sum", 0.0))
|
|
540
|
+
self.align_group_n = int(state.get("align_group_n", 0))
|
|
521
541
|
# resume: baseline the per-step delta counters at the restored cumulative mass, so the
|
|
522
542
|
# first resumed step reports its own coverage instead of the whole prior run's.
|
|
523
543
|
self._prev_aligned = self.aligned_sequences
|
|
@@ -648,6 +668,8 @@ class _TeacherAlignmentBridge:
|
|
|
648
668
|
"aligned_sequences": self.aligned_sequences,
|
|
649
669
|
"empty_alignments": self.empty_alignments,
|
|
650
670
|
"coverage_sum": self.coverage_sum,
|
|
671
|
+
"align_group_sum": self.align_group_sum,
|
|
672
|
+
"align_group_n": self.align_group_n,
|
|
651
673
|
}
|
|
652
674
|
|
|
653
675
|
def _empty(self, prompt_length: int, response_length: int) -> dict:
|
|
@@ -756,12 +778,15 @@ class _TeacherAlignmentBridge:
|
|
|
756
778
|
aligned_group_count = len(groups)
|
|
757
779
|
groups = _drop_fully_forced_groups(groups, kept_forced)
|
|
758
780
|
coverage = groupwise_coverage(groups, student_tokens)
|
|
781
|
+
granularity = _align_granularity(groups, student_tokens)
|
|
759
782
|
with self._stats_lock:
|
|
760
783
|
self.teacher_input_tokens += teacher_input_tokens
|
|
761
784
|
self.dropped_forced_groups += aligned_group_count - len(groups)
|
|
762
785
|
self.coverage_sum += coverage
|
|
763
786
|
if groups:
|
|
764
787
|
self.aligned_sequences += 1
|
|
788
|
+
self.align_group_sum += granularity
|
|
789
|
+
self.align_group_n += 1
|
|
765
790
|
else:
|
|
766
791
|
self.empty_alignments += 1
|
|
767
792
|
teacher_ids, teacher_logprobs = encode_shifted_group_metadata(
|
|
@@ -1084,11 +1109,14 @@ class _TeacherAlignmentBridge:
|
|
|
1084
1109
|
groups = groupwise_alignment(student_tokens, teacher_tokens)
|
|
1085
1110
|
groups = [(indices, logsum) for indices, logsum in groups if indices]
|
|
1086
1111
|
coverage = groupwise_coverage(groups, student_tokens)
|
|
1112
|
+
granularity = _align_granularity(groups, student_tokens)
|
|
1087
1113
|
with self._stats_lock:
|
|
1088
1114
|
self.teacher_input_tokens += len(turn["prompt_ids"]) + len(student_ids)
|
|
1089
1115
|
self.coverage_sum += coverage
|
|
1090
1116
|
if groups:
|
|
1091
1117
|
self.aligned_sequences += 1
|
|
1118
|
+
self.align_group_sum += granularity
|
|
1119
|
+
self.align_group_n += 1
|
|
1092
1120
|
else:
|
|
1093
1121
|
self.empty_alignments += 1
|
|
1094
1122
|
teacher_ids, teacher_logprobs = encode_shifted_group_metadata(
|
|
@@ -2515,6 +2543,17 @@ def run_opd_verl(spec=None) -> None:
|
|
|
2515
2543
|
"verl OPD produced no distillation-loss metrics for the whole run — the "
|
|
2516
2544
|
"distillation path never engaged; refusing to publish"
|
|
2517
2545
|
)
|
|
2546
|
+
if len(final_accounting["loss_curve"]) != final_step:
|
|
2547
|
+
# record_step only checks that each metric line FOLLOWS the last one, so a missing
|
|
2548
|
+
# trailing metric (on_line skips any step-tagged line whose loss it cannot parse) leaves
|
|
2549
|
+
# a curve shorter than the checkpoint verl actually wrote, and nothing later arrives to
|
|
2550
|
+
# catch it. opt_steps is published from this curve, so a short curve would understate the
|
|
2551
|
+
# updates applied. fail loud instead of reporting a number the curve cannot support.
|
|
2552
|
+
raise RuntimeError(
|
|
2553
|
+
f"verl OPD recorded {len(final_accounting['loss_curve'])} distillation-loss metrics "
|
|
2554
|
+
f"for {final_step} optimizer updates; refusing to publish an accounting that does "
|
|
2555
|
+
"not cover every update"
|
|
2556
|
+
)
|
|
2518
2557
|
if int(final_accounting.get("aligned_sequences", 0) or 0) <= 0:
|
|
2519
2558
|
# zeroed-mask pass-through batches still emit a (zero) loss metric, so the loss-curve
|
|
2520
2559
|
# check alone cannot distinguish real distillation from a run where the teacher never
|
|
@@ -2553,6 +2592,10 @@ def run_opd_verl(spec=None) -> None:
|
|
|
2553
2592
|
generated_tokens=int(final_accounting["generated_tokens"]),
|
|
2554
2593
|
notes={
|
|
2555
2594
|
"steps": update_horizon,
|
|
2595
|
+
# optimizer updates that actually produced a distillation loss. record_step enforces
|
|
2596
|
+
# loss_curve length == the metric step, and the guard above rejects a curve shorter
|
|
2597
|
+
# than final_step, so this is measured, not assumed.
|
|
2598
|
+
"opt_steps": len(final_accounting["loss_curve"]),
|
|
2556
2599
|
"epochs": knobs.epochs,
|
|
2557
2600
|
"retained_prompts": len(prompts),
|
|
2558
2601
|
"dropped_long_prompts": dropped_long,
|
|
@@ -2568,6 +2611,15 @@ def run_opd_verl(spec=None) -> None:
|
|
|
2568
2611
|
if final_accounting["aligned_sequences"]
|
|
2569
2612
|
else 0.0
|
|
2570
2613
|
),
|
|
2614
|
+
# the real alignment-health signal. mean_coverage reads ~1.0 even when the alignment
|
|
2615
|
+
# has collapsed every student token onto one group, so it cannot flag that failure
|
|
2616
|
+
# mode on its own; this ratio can.
|
|
2617
|
+
"mean_align_granularity": (
|
|
2618
|
+
float(final_accounting["align_group_sum"])
|
|
2619
|
+
/ int(final_accounting["align_group_n"])
|
|
2620
|
+
if final_accounting["align_group_n"]
|
|
2621
|
+
else 0.0
|
|
2622
|
+
),
|
|
2571
2623
|
"truncated_rollouts": int(final_accounting["truncated_rollouts"]),
|
|
2572
2624
|
"forced_tokens": int(final_accounting["forced_tokens"]),
|
|
2573
2625
|
"dropped_forced_groups": int(
|
|
@@ -2591,6 +2643,23 @@ def run_opd_verl(spec=None) -> None:
|
|
|
2591
2643
|
if multi_turn and final_accounting["episodes_seen"]
|
|
2592
2644
|
else None
|
|
2593
2645
|
),
|
|
2646
|
+
# the engine length actually handed to vllm (prompt + completion), already clamped to
|
|
2647
|
+
# the model's own limit. the prompt filter is carved out of this same number.
|
|
2648
|
+
"vllm_max_model_len": max_model_len,
|
|
2649
|
+
# teacher call shape. only the single-turn TEXT path goes through the batcher, which
|
|
2650
|
+
# holds a fixed cap and one serial scoring thread. the multimodal path scores one
|
|
2651
|
+
# item per call and the multi-turn path batches a whole episode, and both run on the
|
|
2652
|
+
# bridge's own request threads -- neither has a constant, so None records "not
|
|
2653
|
+
# batched by flash" instead of asserting a number nothing enforces.
|
|
2654
|
+
# the cap is bounded by the samples one step can actually produce (trl does the same
|
|
2655
|
+
# in _opd_teacher_batch_size): a step of 1 rollout can never fill a batch of 8, and
|
|
2656
|
+
# reporting the global cap there would describe a shape the run cannot reach.
|
|
2657
|
+
"opd_teacher_batch_size": (
|
|
2658
|
+
min(_TEXT_TEACHER_BATCH_SIZE, max(1, prompts_per_step * knobs.group_size))
|
|
2659
|
+
if not multimodal and not multi_turn
|
|
2660
|
+
else None
|
|
2661
|
+
),
|
|
2662
|
+
"opd_teacher_workers": 1 if not multimodal and not multi_turn else None,
|
|
2594
2663
|
"rollout_backend": "verl_vllm",
|
|
2595
2664
|
"verl_version": "0.8.0",
|
|
2596
2665
|
"verl_backend": "fsdp",
|
|
@@ -17,7 +17,12 @@ OPD_OPTIMIZER_START_PHASE = "opd"
|
|
|
17
17
|
OPD_RETRY_ARTIFACT_PREFIX = "_opd_retry"
|
|
18
18
|
OPD_OPTIMIZER_START_FILENAME = "optimizer-start.v1.json"
|
|
19
19
|
OPD_RESUME_REVISION_ENV = "FLASH_OPD_RESUME_REVISION"
|
|
20
|
-
|
|
20
|
+
# bumped to 3 when the verl worker began accumulating align_group_sum/align_group_n. a version-2
|
|
21
|
+
# state carries neither, so resuming one would restart the accumulator at zero and publish a
|
|
22
|
+
# post-resume-only mean_align_granularity as though it described the whole run. the version check
|
|
23
|
+
# below already rejects mismatches fail-closed, so the bump refuses those states outright instead of
|
|
24
|
+
# threading a completeness flag through the accounting.
|
|
25
|
+
OPD_RESUME_STATE_VERSION = 3
|
|
21
26
|
MAX_BOUNDED_NONNEGATIVE_INTEGER = (1 << 63) - 1
|
|
22
27
|
|
|
23
28
|
# a full-state opd resume checkpoint (the custom loop's counterpart to the hf trainer's checkpoint-n)
|
|
@@ -55,6 +60,13 @@ _OPD_RESUME_ACCOUNTING_SCHEMA = {
|
|
|
55
60
|
"opd_phase_seconds": "dict",
|
|
56
61
|
"opd_phase_counts": "dict",
|
|
57
62
|
}
|
|
63
|
+
# alignment granularity, accumulated only by the verl worker. the trl worker's granularity lives in
|
|
64
|
+
# the granularity_* slots above; in verl those same slots hold COVERAGE, so verl carries its own pair
|
|
65
|
+
# and both must be validated when present rather than coerced past the fail-closed contract.
|
|
66
|
+
_OPD_RESUME_OPTIONAL_ACCOUNTING_SCHEMA = {
|
|
67
|
+
"align_group_sum": "nonneg_number",
|
|
68
|
+
"align_group_n": "nonneg_int",
|
|
69
|
+
}
|
|
58
70
|
|
|
59
71
|
|
|
60
72
|
def bounded_nonnegative_integer(value: object, *, field: str) -> int:
|
|
@@ -136,6 +148,14 @@ def validate_opd_resume_state_metadata(
|
|
|
136
148
|
elif field_type == "dict" and not isinstance(value, dict):
|
|
137
149
|
raise ValueError(f"opd resume state {field} must be an object")
|
|
138
150
|
|
|
151
|
+
for field, field_type in _OPD_RESUME_OPTIONAL_ACCOUNTING_SCHEMA.items():
|
|
152
|
+
if field not in state:
|
|
153
|
+
continue
|
|
154
|
+
if field_type == "nonneg_int":
|
|
155
|
+
_nonnegative_integer(state[field], field=field)
|
|
156
|
+
else:
|
|
157
|
+
_finite_number(state[field], field=field, nonnegative=True)
|
|
158
|
+
|
|
139
159
|
for field in ("loss_curve", "coverage_curve"):
|
|
140
160
|
curve = state[field]
|
|
141
161
|
if len(curve) != opt_steps:
|
|
@@ -1187,6 +1187,15 @@ _MULTI_GPU_BACKENDS: frozenset[str] = frozenset({"verl"})
|
|
|
1187
1187
|
_MULTI_GPU_PROVIDERS: frozenset[str] = frozenset({"runpod"})
|
|
1188
1188
|
|
|
1189
1189
|
|
|
1190
|
+
def _backend_env_key(spec: JobSpec) -> str:
|
|
1191
|
+
"""the [worker_env] key that selects this spec's training backend.
|
|
1192
|
+
|
|
1193
|
+
one definition so the gate's remedy cannot name a different key than the resolver reads: the
|
|
1194
|
+
phase mapping is not derivable from the algorithm (grpo -> FLASH_RL_BACKEND).
|
|
1195
|
+
"""
|
|
1196
|
+
return f"FLASH_{spec.phase.upper()}_BACKEND"
|
|
1197
|
+
|
|
1198
|
+
|
|
1190
1199
|
def _effective_backend(spec: JobSpec) -> str:
|
|
1191
1200
|
"""the training backend this spec will actually run, as the worker resolves it.
|
|
1192
1201
|
|
|
@@ -1195,7 +1204,7 @@ def _effective_backend(spec: JobSpec) -> str:
|
|
|
1195
1204
|
empty dict and forwards a fixed credential allowlist, never the ambient control-plane env. so the
|
|
1196
1205
|
backend is a property of the SPEC and the gate can read it here rather than guessing.
|
|
1197
1206
|
"""
|
|
1198
|
-
key =
|
|
1207
|
+
key = _backend_env_key(spec)
|
|
1199
1208
|
worker_env = getattr(spec, "worker_env", None) or {}
|
|
1200
1209
|
for name, value in worker_env.items():
|
|
1201
1210
|
if str(name).upper() == key:
|
|
@@ -1215,10 +1224,16 @@ def _require_supported_gpu_count(spec: JobSpec) -> None:
|
|
|
1215
1224
|
return
|
|
1216
1225
|
backend = _effective_backend(spec)
|
|
1217
1226
|
if backend not in _MULTI_GPU_BACKENDS:
|
|
1227
|
+
# name the key that actually enables sharding. the backend is selected only through
|
|
1228
|
+
# [worker_env], which no other error or doc mentions, so a message that offers just
|
|
1229
|
+
# "set gpu.count to 1" leaves multi-gpu undiscoverable.
|
|
1230
|
+
key = _backend_env_key(spec)
|
|
1231
|
+
sharding = ", ".join(sorted(_MULTI_GPU_BACKENDS))
|
|
1218
1232
|
raise ValueError(
|
|
1219
1233
|
f"multi-gpu training (gpu.count={count}) is not supported by the {backend!r} backend "
|
|
1220
1234
|
f"for algorithm {spec.algorithm!r}; it trains in a single process and would leave "
|
|
1221
|
-
f"{count - 1} rented card(s) idle. set gpu.count to 1"
|
|
1235
|
+
f"{count - 1} rented card(s) idle. set gpu.count to 1, or select a sharding backend "
|
|
1236
|
+
f'with [worker_env] {key} = "{sharding}"'
|
|
1222
1237
|
)
|
|
1223
1238
|
provider = (getattr(spec.gpu, "provider", "") or "").strip().lower()
|
|
1224
1239
|
if provider not in _MULTI_GPU_PROVIDERS:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "freesolo-flash-dev"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.45"
|
|
4
4
|
description = "Flash — managed LoRA post-training (SFT/GRPO/OPD) 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).
|
|
@@ -116,7 +116,7 @@ artifacts = ["flash/py.typed"]
|
|
|
116
116
|
# auto-published by .github/workflows/publish-dev.yml: a push to `dev` whose version isn't yet on
|
|
117
117
|
# PyPI publishes it; pushes that don't change it find it already published and no-op. So cutting a
|
|
118
118
|
# dev-channel release = bump this (and [project].version) and merge to `dev`.
|
|
119
|
-
version = "1.0.
|
|
119
|
+
version = "1.0.45"
|
|
120
120
|
|
|
121
121
|
[tool.pytest.ini_options]
|
|
122
122
|
pythonpath = ["."]
|
|
@@ -110,3 +110,24 @@ def test_run_training_gates_effective_spec_on_recovery():
|
|
|
110
110
|
# before any provisioning (the gate precedes the first get_status/allocation touch).
|
|
111
111
|
with pytest.raises(ValueError, match="multi-gpu training"):
|
|
112
112
|
_run_training(_spec(2), io.StringIO(), prior_cost=0.0)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.parametrize(
|
|
116
|
+
("algorithm", "key"),
|
|
117
|
+
[("sft", "FLASH_SFT_BACKEND"), ("grpo", "FLASH_RL_BACKEND"), ("opd", "FLASH_OPD_BACKEND")],
|
|
118
|
+
)
|
|
119
|
+
def test_gate_message_names_the_key_that_enables_sharding(algorithm, key):
|
|
120
|
+
from flash import runner
|
|
121
|
+
|
|
122
|
+
# [worker_env] is the ONLY route to a sharding backend, and the phase key is not guessable from
|
|
123
|
+
# the algorithm (grpo -> FLASH_RL_BACKEND). a message offering only "set gpu.count to 1" leaves
|
|
124
|
+
# multi-gpu undiscoverable, so assert the remedy travels with the rejection.
|
|
125
|
+
# match the backend rejection specifically: the gate also raises for gpu.provider, and that
|
|
126
|
+
# path carries no backend key, so a bare raises() would pass while checking nothing.
|
|
127
|
+
with pytest.raises(ValueError, match="multi-gpu training") as excinfo:
|
|
128
|
+
runner._require_supported_gpu_count(_spec(4, algorithm))
|
|
129
|
+
|
|
130
|
+
message = str(excinfo.value)
|
|
131
|
+
assert key in message
|
|
132
|
+
assert "verl" in message
|
|
133
|
+
assert "worker_env" in message
|
|
@@ -1123,6 +1123,62 @@ def test_gate_blocks_pinned_checkpoint_metadata_download_failure(monkeypatch, tm
|
|
|
1123
1123
|
_run_gate()
|
|
1124
1124
|
|
|
1125
1125
|
|
|
1126
|
+
@pytest.mark.parametrize(
|
|
1127
|
+
"overrides",
|
|
1128
|
+
[
|
|
1129
|
+
{"align_group_sum": float("nan")},
|
|
1130
|
+
{"align_group_sum": float("inf")},
|
|
1131
|
+
{"align_group_sum": -1.0},
|
|
1132
|
+
{"align_group_n": -1},
|
|
1133
|
+
{"align_group_n": 1.5},
|
|
1134
|
+
{"align_group_n": True},
|
|
1135
|
+
],
|
|
1136
|
+
)
|
|
1137
|
+
def test_resume_validator_rejects_corrupt_alignment_granularity(overrides):
|
|
1138
|
+
# the verl worker resumes its granularity accumulators from these fields and publishes their
|
|
1139
|
+
# ratio into train_meta. a tampered or corrupt checkpoint must fail the fail-closed contract
|
|
1140
|
+
# here, not coerce past it and propagate a non-finite or negative mean into every later
|
|
1141
|
+
# checkpoint and the run's reported alignment health.
|
|
1142
|
+
state = _valid_resume_state(2, **overrides)
|
|
1143
|
+
# match on the corrupt field itself: a validator that rejected for some unrelated reason would
|
|
1144
|
+
# pass a bare raises() while leaving this pair entirely unchecked.
|
|
1145
|
+
(field,) = overrides
|
|
1146
|
+
|
|
1147
|
+
with pytest.raises(ValueError, match=field):
|
|
1148
|
+
validate_opd_resume_state_metadata(state, expected_seed=42, checkpoint_step=2)
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
def test_resume_state_version_rejects_states_predating_alignment_granularity():
|
|
1152
|
+
# the verl worker resumes align_group_sum/align_group_n and publishes their ratio as
|
|
1153
|
+
# mean_align_granularity. a state written before those accumulators existed carries neither, so
|
|
1154
|
+
# resuming one restarts the accumulator at zero and the ratio would describe only the
|
|
1155
|
+
# post-resume samples while being reported as the whole run's alignment health. the contract
|
|
1156
|
+
# version is the enforcement point: it must have moved past 2 so the existing fail-closed check
|
|
1157
|
+
# refuses those states outright.
|
|
1158
|
+
assert OPD_RESUME_STATE_VERSION > 2
|
|
1159
|
+
|
|
1160
|
+
stale = _valid_resume_state(2)
|
|
1161
|
+
stale["contract_version"] = 2
|
|
1162
|
+
|
|
1163
|
+
with pytest.raises(ValueError, match="contract_version"):
|
|
1164
|
+
validate_opd_resume_state_metadata(stale, expected_seed=42, checkpoint_step=2)
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
def test_resume_validator_accepts_alignment_granularity_and_states_without_it():
|
|
1168
|
+
# verl writes the pair; trl does not. both must validate, so the fields are checked when present
|
|
1169
|
+
# rather than required -- otherwise adding them would reject every trl checkpoint.
|
|
1170
|
+
with_granularity = _valid_resume_state(2, align_group_sum=3.0, align_group_n=2)
|
|
1171
|
+
validated = validate_opd_resume_state_metadata(
|
|
1172
|
+
with_granularity, expected_seed=42, checkpoint_step=2
|
|
1173
|
+
)
|
|
1174
|
+
assert validated["align_group_sum"] == 3.0
|
|
1175
|
+
assert validated["align_group_n"] == 2
|
|
1176
|
+
|
|
1177
|
+
without = _valid_resume_state(2)
|
|
1178
|
+
assert "align_group_sum" not in without
|
|
1179
|
+
validate_opd_resume_state_metadata(without, expected_seed=42, checkpoint_step=2)
|
|
1180
|
+
|
|
1181
|
+
|
|
1126
1182
|
def test_shared_resume_metadata_validator_returns_a_copy():
|
|
1127
1183
|
state = _valid_resume_state(3)
|
|
1128
1184
|
|
|
@@ -64,6 +64,7 @@ from flash.engine.worker.opd_verl_structured import (
|
|
|
64
64
|
canonical_structured_spec,
|
|
65
65
|
)
|
|
66
66
|
from flash.engine.worker.tokenizer_align import TeacherToken
|
|
67
|
+
from flash.opd_retry_contract import OPD_RESUME_STATE_VERSION
|
|
67
68
|
from flash.opd_verl_validation import validate_opd_verl_structured_outputs
|
|
68
69
|
|
|
69
70
|
|
|
@@ -719,7 +720,7 @@ class _BridgeTokenizer:
|
|
|
719
720
|
|
|
720
721
|
def _resume_accounting(step=2):
|
|
721
722
|
return {
|
|
722
|
-
"contract_version":
|
|
723
|
+
"contract_version": OPD_RESUME_STATE_VERSION,
|
|
723
724
|
"seed": 42,
|
|
724
725
|
"opt_steps": step,
|
|
725
726
|
"step": step,
|
|
@@ -841,7 +842,14 @@ def test_write_train_meta_integrates_canonical_failure_accounting_metadata():
|
|
|
841
842
|
assert "**_failure_accounting_metadata(final_accounting)" in write_train_meta_source
|
|
842
843
|
assert '"teacher_transient":' not in write_train_meta_source
|
|
843
844
|
assert '"teacher_error":' not in write_train_meta_source
|
|
844
|
-
|
|
845
|
+
# granularity IS reported, but only from its own accumulator. the snapshot's granularity_sum /
|
|
846
|
+
# granularity_n are legacy aliases holding COVERAGE, so deriving the ratio from them would
|
|
847
|
+
# publish coverage a second time under the name of the one signal coverage cannot provide.
|
|
848
|
+
assert '"mean_align_granularity":' in write_train_meta_source
|
|
849
|
+
assert 'final_accounting["align_group_sum"]' in write_train_meta_source
|
|
850
|
+
assert 'final_accounting["align_group_n"]' in write_train_meta_source
|
|
851
|
+
assert "granularity_sum" not in write_train_meta_source
|
|
852
|
+
assert "granularity_n" not in write_train_meta_source
|
|
845
853
|
|
|
846
854
|
|
|
847
855
|
class _BridgeTeacher:
|
|
@@ -1051,6 +1059,87 @@ def test_bridge_verifies_prompt_and_serializes_aligned_native_fields():
|
|
|
1051
1059
|
bridge.score(0, 2, [10, 12, 65, 99])
|
|
1052
1060
|
|
|
1053
1061
|
|
|
1062
|
+
def test_bridge_granularity_separates_a_collapsed_alignment_from_a_healthy_one():
|
|
1063
|
+
# both teachers cover every student token, so coverage is 1.0 for BOTH and cannot tell them
|
|
1064
|
+
# apart. the merged teacher returns one span where the healthy one returns two, which is the
|
|
1065
|
+
# degenerate collapse mean_coverage is blind to -- granularity is the signal that sees it.
|
|
1066
|
+
healthy = _text_bridge(_BridgeTeacher())
|
|
1067
|
+
healthy.score(0, 2, [10, 11, 65, 66, 99])
|
|
1068
|
+
collapsed = _text_bridge(_MergedBridgeTeacher())
|
|
1069
|
+
collapsed.score(0, 2, [10, 11, 65, 66, 99])
|
|
1070
|
+
|
|
1071
|
+
assert healthy.coverage_sum == collapsed.coverage_sum == 1.0
|
|
1072
|
+
assert healthy.aligned_sequences == collapsed.aligned_sequences == 1
|
|
1073
|
+
# two student tokens over two groups vs two student tokens over one merged group.
|
|
1074
|
+
assert healthy.align_group_sum == 1.0
|
|
1075
|
+
assert collapsed.align_group_sum == 2.0
|
|
1076
|
+
assert healthy.align_group_n == collapsed.align_group_n == 1
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
def test_bridge_granularity_counts_only_sequences_that_reached_the_loss():
|
|
1080
|
+
# a teacher that returns no tokens still reaches the accounting, but its sequence carries no
|
|
1081
|
+
# groups and contributes nothing to the loss. folding its 0.0 into the mean would report a
|
|
1082
|
+
# collapse that never happened -- it belongs in empty_alignments only.
|
|
1083
|
+
class _EmptyAlignmentTeacher:
|
|
1084
|
+
def score(self, _prompt_text, _completion_text):
|
|
1085
|
+
return []
|
|
1086
|
+
|
|
1087
|
+
bridge = _text_bridge(_BridgeTeacher())
|
|
1088
|
+
bridge.score(0, 2, [10, 11, 65, 66, 99])
|
|
1089
|
+
assert bridge.align_group_n == 1
|
|
1090
|
+
assert bridge.align_group_sum == 1.0
|
|
1091
|
+
|
|
1092
|
+
bridge.teacher = _ScoreManyTeacherAdapter(_EmptyAlignmentTeacher())
|
|
1093
|
+
bridge._text_teacher_batcher = None
|
|
1094
|
+
bridge.score(0, 2, [10, 11, 65, 66, 99])
|
|
1095
|
+
|
|
1096
|
+
assert bridge.empty_alignments == 1
|
|
1097
|
+
# the empty sequence moved empty_alignments, and left the granularity mean untouched.
|
|
1098
|
+
assert bridge.align_group_n == 1
|
|
1099
|
+
assert bridge.align_group_sum == 1.0
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
def test_bridge_granularity_survives_resume_without_reading_the_coverage_aliases():
|
|
1103
|
+
# granularity_sum / granularity_n are LEGACY ALIASES for coverage. resuming granularity from
|
|
1104
|
+
# them would silently restore coverage under the granularity name, so an old-format state must
|
|
1105
|
+
# restart the accumulator at zero rather than inherit the wrong quantity.
|
|
1106
|
+
resumed = _text_bridge(_BridgeTeacher())
|
|
1107
|
+
state = dict(_resume_accounting())
|
|
1108
|
+
state.update({"align_group_sum": 6.0, "align_group_n": 3})
|
|
1109
|
+
carried = _TeacherAlignmentBridge(
|
|
1110
|
+
prompts=list(resumed.prompts),
|
|
1111
|
+
tokenizer=_BridgeTokenizer(),
|
|
1112
|
+
teacher=_ScoreManyTeacherAdapter(_BridgeTeacher()),
|
|
1113
|
+
thinking_prefill="",
|
|
1114
|
+
eos_token_ids=frozenset({99}),
|
|
1115
|
+
stop_sequences=(),
|
|
1116
|
+
mutation_callback=lambda: None,
|
|
1117
|
+
initial_state=state,
|
|
1118
|
+
)
|
|
1119
|
+
assert carried.align_group_sum == 6.0
|
|
1120
|
+
assert carried.align_group_n == 3
|
|
1121
|
+
snapshot = carried.accounting_snapshot()
|
|
1122
|
+
assert snapshot["align_group_sum"] == 6.0
|
|
1123
|
+
assert snapshot["align_group_n"] == 3
|
|
1124
|
+
|
|
1125
|
+
legacy_only = _TeacherAlignmentBridge(
|
|
1126
|
+
prompts=list(resumed.prompts),
|
|
1127
|
+
tokenizer=_BridgeTokenizer(),
|
|
1128
|
+
teacher=_ScoreManyTeacherAdapter(_BridgeTeacher()),
|
|
1129
|
+
thinking_prefill="",
|
|
1130
|
+
eos_token_ids=frozenset({99}),
|
|
1131
|
+
stop_sequences=(),
|
|
1132
|
+
mutation_callback=lambda: None,
|
|
1133
|
+
initial_state=_resume_accounting(),
|
|
1134
|
+
)
|
|
1135
|
+
# granularity_sum is 3.5 and granularity_n is 5 in that state; both must be ignored here.
|
|
1136
|
+
assert legacy_only.align_group_sum == 0.0
|
|
1137
|
+
assert legacy_only.align_group_n == 0
|
|
1138
|
+
# the coverage accumulators, whose aliases those really are, DO still resume from them.
|
|
1139
|
+
assert legacy_only.coverage_sum == 3.5
|
|
1140
|
+
assert legacy_only.aligned_sequences == 5
|
|
1141
|
+
|
|
1142
|
+
|
|
1054
1143
|
def test_text_teacher_batcher_enforces_max_batch_size_across_concurrent_requests():
|
|
1055
1144
|
prompt_texts = [f"question-{index}" for index in range(17)]
|
|
1056
1145
|
teacher = _BatchingTeacher(prompt_texts)
|
|
@@ -4919,6 +5008,109 @@ def test_deterministic_seed_uses_every_rollout_identity_component():
|
|
|
4919
5008
|
|
|
4920
5009
|
|
|
4921
5010
|
|
|
5011
|
+
def test_train_meta_records_the_optimizer_steps_that_actually_produced_a_loss():
|
|
5012
|
+
import inspect
|
|
5013
|
+
|
|
5014
|
+
from flash.engine.worker.opd_verl import run_opd_verl
|
|
5015
|
+
|
|
5016
|
+
notes = inspect.getsource(run_opd_verl)
|
|
5017
|
+
notes = notes[notes.index("_w.write_train_meta(") :]
|
|
5018
|
+
# `steps` is the REQUESTED horizon. a step whose batch carried no teacher signal never applies
|
|
5019
|
+
# an update, so reporting the horizon as the work done would overstate a partly-starved run.
|
|
5020
|
+
assert '"steps": update_horizon,' in notes
|
|
5021
|
+
assert '"opt_steps": len(final_accounting["loss_curve"]),' in notes
|
|
5022
|
+
|
|
5023
|
+
|
|
5024
|
+
def test_worker_refuses_to_publish_a_loss_curve_shorter_than_the_final_checkpoint():
|
|
5025
|
+
import inspect
|
|
5026
|
+
|
|
5027
|
+
from flash.engine.worker.opd_verl import run_opd_verl
|
|
5028
|
+
|
|
5029
|
+
source = inspect.getsource(run_opd_verl)
|
|
5030
|
+
# record_step only checks that each metric line FOLLOWS the previous one, so it cannot notice a
|
|
5031
|
+
# MISSING TRAILING metric: on_line silently skips any step-tagged line whose loss it cannot
|
|
5032
|
+
# parse, and no later step ever arrives to trip the sequence check. that leaves a curve of
|
|
5033
|
+
# length N-1 for a run verl actually checkpointed at step N, and opt_steps is published straight
|
|
5034
|
+
# from that curve. the guard must compare against final_step, not merely require non-emptiness.
|
|
5035
|
+
assert 'if len(final_accounting["loss_curve"]) != final_step:' in source
|
|
5036
|
+
guard = source[source.index('if len(final_accounting["loss_curve"]) != final_step:') :]
|
|
5037
|
+
assert "raise RuntimeError(" in guard[:600]
|
|
5038
|
+
# and it must sit BEFORE the publish, not after it.
|
|
5039
|
+
assert source.index('if len(final_accounting["loss_curve"]) != final_step:') < source.index(
|
|
5040
|
+
"_w.write_train_meta("
|
|
5041
|
+
)
|
|
5042
|
+
|
|
5043
|
+
|
|
5044
|
+
def test_train_meta_reports_the_teacher_call_shape_only_where_one_is_enforced():
|
|
5045
|
+
import inspect
|
|
5046
|
+
|
|
5047
|
+
from flash.engine.worker.opd_verl import run_opd_verl
|
|
5048
|
+
|
|
5049
|
+
notes = inspect.getsource(run_opd_verl)
|
|
5050
|
+
notes = notes[notes.index("_w.write_train_meta(") :]
|
|
5051
|
+
# only the single-turn text path runs through the batcher, and it holds one serial scoring
|
|
5052
|
+
# thread. multimodal scores one item per call and multi-turn batches an episode, both on the
|
|
5053
|
+
# bridge's own request threads -- neither has a constant to report.
|
|
5054
|
+
# the reported cap is bounded by the samples one step can actually produce, matching trl's
|
|
5055
|
+
# _opd_teacher_batch_size: a 1-rollout step can never fill a batch of 8, so publishing the
|
|
5056
|
+
# global cap there would describe a shape the run is structurally unable to reach.
|
|
5057
|
+
assert (
|
|
5058
|
+
'"opd_teacher_batch_size": (\n'
|
|
5059
|
+
" min(_TEXT_TEACHER_BATCH_SIZE, max(1, prompts_per_step * knobs.group_size))\n"
|
|
5060
|
+
" if not multimodal and not multi_turn\n"
|
|
5061
|
+
" else None\n"
|
|
5062
|
+
" ),"
|
|
5063
|
+
) in notes
|
|
5064
|
+
assert (
|
|
5065
|
+
'"opd_teacher_workers": 1 if not multimodal and not multi_turn else None,'
|
|
5066
|
+
) in notes
|
|
5067
|
+
# the engine length handed to vllm, not a hardcoded default: prompt filtering is carved out of
|
|
5068
|
+
# this same number, so a fabricated one would disagree with the budget the run actually used.
|
|
5069
|
+
assert '"vllm_max_model_len": max_model_len,' in notes
|
|
5070
|
+
|
|
5071
|
+
|
|
5072
|
+
def test_text_teacher_batcher_scores_one_batch_at_a_time():
|
|
5073
|
+
# opd_teacher_workers reports 1 for the text path. that is only honest while the batcher scores
|
|
5074
|
+
# serially, so assert the observable property rather than the thread count: no two score_many
|
|
5075
|
+
# calls may ever overlap, however the batcher is wired internally.
|
|
5076
|
+
from flash.engine.worker.opd_verl import _TextTeacherBatcher
|
|
5077
|
+
|
|
5078
|
+
class _OverlapDetectingTeacher:
|
|
5079
|
+
def __init__(self):
|
|
5080
|
+
self.lock = threading.Lock()
|
|
5081
|
+
self.in_flight = 0
|
|
5082
|
+
self.max_in_flight = 0
|
|
5083
|
+
|
|
5084
|
+
def score_many(self, items):
|
|
5085
|
+
with self.lock:
|
|
5086
|
+
self.in_flight += 1
|
|
5087
|
+
self.max_in_flight = max(self.max_in_flight, self.in_flight)
|
|
5088
|
+
# hold the call open long enough that a second scoring thread would land inside it.
|
|
5089
|
+
time.sleep(0.05)
|
|
5090
|
+
with self.lock:
|
|
5091
|
+
self.in_flight -= 1
|
|
5092
|
+
return [
|
|
5093
|
+
[TeacherToken(text=completion, logprob=-1.0, start=0, end=len(completion))]
|
|
5094
|
+
for _prompt, completion in items
|
|
5095
|
+
]
|
|
5096
|
+
|
|
5097
|
+
teacher = _OverlapDetectingTeacher()
|
|
5098
|
+
batcher = _TextTeacherBatcher(teacher, max_batch_size=1, flush_wait_s=0.01)
|
|
5099
|
+
batcher.start()
|
|
5100
|
+
try:
|
|
5101
|
+
with ThreadPoolExecutor(max_workers=8) as executor:
|
|
5102
|
+
futures = [
|
|
5103
|
+
executor.submit(batcher.score, f"prompt-{index}", f"completion-{index}")
|
|
5104
|
+
for index in range(8)
|
|
5105
|
+
]
|
|
5106
|
+
for future in futures:
|
|
5107
|
+
future.result(timeout=10.0)
|
|
5108
|
+
finally:
|
|
5109
|
+
batcher.close()
|
|
5110
|
+
|
|
5111
|
+
assert teacher.max_in_flight == 1
|
|
5112
|
+
|
|
5113
|
+
|
|
4922
5114
|
def test_worker_structured_validator_runs_before_model_download():
|
|
4923
5115
|
import inspect
|
|
4924
5116
|
|
|
File without changes
|
|
File without changes
|
{freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/ISSUE_TEMPLATE/bug_report.yml
RENAMED
|
File without changes
|
|
File without changes
|
{freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/ISSUE_TEMPLATE/feature_request.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/bake-kernel-cache.yml
RENAMED
|
File without changes
|
|
File without changes
|
{freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/main-source-guard.yml
RENAMED
|
File without changes
|
{freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/notify-tests-repo.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|