freesolo-flash-dev 1.0.4__tar.gz → 1.0.5__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.4 → freesolo_flash_dev-1.0.5}/PKG-INFO +1 -1
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/training_doc.py +11 -8
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/client/http.py +16 -7
- freesolo_flash_dev-1.0.5/flash/engine/multiturn_reward_scoring.py +73 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/multiturn_rollout.py +43 -35
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/adapter.py +68 -15
- freesolo_flash_dev-1.0.5/flash/engine/worker/grpo_perturn_trainer.py +138 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/heartbeat.py +66 -1
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/lora.py +14 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/opd_vllm.py +77 -14
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/rl.py +110 -4
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/sft.py +713 -134
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/adapter.py +74 -8
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/base.py +40 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_poll.py +5 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/schema/__init__.py +2 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/schema/fields.py +23 -1
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/serve/deploy.py +141 -34
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/auth.py +44 -19
- freesolo_flash_dev-1.0.5/flash/server/db.py +248 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/spec.py +32 -1
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/pyproject.toml +2 -2
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/conftest.py +2 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_commands.py +11 -0
- freesolo_flash_dev-1.0.5/tests/test_client_stream_reads.py +109 -0
- freesolo_flash_dev-1.0.5/tests/test_deploy_latency.py +418 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_flash_worker.py +20 -36
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_grpo_params.py +1 -0
- freesolo_flash_dev-1.0.5/tests/test_grpo_perturn.py +431 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multimodal_training.py +17 -0
- freesolo_flash_dev-1.0.5/tests/test_multiturn_per_turn_reward.py +379 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout.py +7 -3
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout_coverage.py +6 -2
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout_request_policy.py +2 -0
- freesolo_flash_dev-1.0.5/tests/test_named_reward_metrics.py +79 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_opd.py +11 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_opd_vllm.py +142 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_serve.py +44 -9
- freesolo_flash_dev-1.0.5/tests/test_server_auth_singleflight.py +82 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_db.py +238 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_serving_contract.py +21 -4
- freesolo_flash_dev-1.0.5/tests/test_sft_activation_shaping.py +140 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_sft_example_selection.py +15 -0
- freesolo_flash_dev-1.0.5/tests/test_sft_tokenize_cache.py +375 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_spec_and_validation.py +42 -2
- freesolo_flash_dev-1.0.5/tests/test_warmstart_adapter_download.py +282 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_warmstart_cross_repo.py +15 -1
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_worker_init_heartbeat.py +49 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/uv.lock +1 -1
- freesolo_flash_dev-1.0.4/flash/server/db.py +0 -134
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.claude/skills/verify/SKILL.md +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.dockerignore +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.env.example +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.gitignore +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/Dockerfile +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/LICENSE +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/README.md +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/_channel.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/_logging.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/catalog.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/commands.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/env_setup.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/render.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/client/config.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cost/analytical.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cost/facts.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/diagnostics.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/steps.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/structured_outputs.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/grpo_multimodal.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/hf.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/opd.py +1 -1
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/opd_gkd.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/rng.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/teacher.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/tokenizer_align.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker_entrypoint.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/archive.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/loader.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/lora_rank.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/multimodal.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/opd_retry_contract.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_deadline.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_worker.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/api.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/py.typed +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/runner/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/runner/verified_revisions.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/serve/urls.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/app.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/repo_cleanup.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_accounting_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_async_uploads.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_base_model_provenance.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_charge_pricing.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_commands_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_main_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_render_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_client.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_controlled_experiment_repairs.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_pull_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_pull_loader_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_export.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_hf_retry.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_instance_bootstrap_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_jobs.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_lambda_api_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_lora_rank_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_lora_rank_preflight.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multimodal_input_grads.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_image_rollout.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout_records.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_opd_full_state_resume.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_opd_resume_safety.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_packing_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_provider_preflight_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_repo_cleanup.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_api.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_app_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_checkpoints_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_envs_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_locks_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_serving_context_preflight.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_serving_pricing_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_serving_schema_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_sft_max_context.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_single_turn_multimodal_grpo.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_structured_outputs.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_train_context_preflight.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_training_controls.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vast_api.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vast_api_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vast_provider_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_verified_revisions.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_version.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_wandb_log_coverage.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/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.5
|
|
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
|
|
@@ -109,6 +109,8 @@ epochs = 1 # one pass over the retained train rows
|
|
|
109
109
|
max_examples = 2 # rows to train on (the starter dataset has 2)
|
|
110
110
|
# max_steps = 100 # positive values set the exact optimizer-update horizon
|
|
111
111
|
# save_at_steps = [10, 50, 100] # requires max_steps; overrides save_every
|
|
112
|
+
# multi-turn GRPO defaults to one reward per rollout; choose "per_turn" for turn-level credit.
|
|
113
|
+
# credit_assignment = "per_episode"
|
|
112
114
|
lora_rank = 32
|
|
113
115
|
lora_alpha = 64
|
|
114
116
|
# All SFT/GRPO knobs live under [train]. Do not add [sft] or [grpo] tables.
|
|
@@ -339,10 +341,12 @@ def score_response(self, example, response_text) -> RewardResult:
|
|
|
339
341
|
)
|
|
340
342
|
```
|
|
341
343
|
|
|
342
|
-
`score` is what GRPO optimizes (it becomes the run's `total`).
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
344
|
+
`score` is what GRPO optimizes (it becomes the run's `total`). In standard (single-turn)
|
|
345
|
+
GRPO, each `RewardMetric` is averaged across scored completions and logged by name at
|
|
346
|
+
the managed heartbeat cadence, which is not guaranteed to be every optimizer step. That
|
|
347
|
+
is how the clean success rate becomes visible. Multi-turn scoring currently reports only
|
|
348
|
+
the scalar reward. Use the shaped `score` to confirm the model is learning *at all*, and
|
|
349
|
+
judge the run on the explicit `success` metric.
|
|
346
350
|
|
|
347
351
|
When `thinking = true`, score the final answer unless you intentionally need the
|
|
348
352
|
reasoning trace. Flash passes a string-compatible response object to `score_response`;
|
|
@@ -550,10 +554,9 @@ every run, the last two matter more the smaller the model:
|
|
|
550
554
|
frontier one outright; a frontier `teacher_model` only earns its keep once the student is large
|
|
551
555
|
enough to track it (~9B+). Early-stopping also largely neutralizes this gap, since the teacher-driven
|
|
552
556
|
over-sharpening only compounds over many steps.
|
|
553
|
-
- **Diagnose it
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
and compare an early checkpoint against the final one to watch the loop emerge over steps.
|
|
557
|
+
- **Diagnose it at serving.** Evaluate at **temperature=0** and flag
|
|
558
|
+
`finish_reason=length` completions that never emit your answer token. Compare an early checkpoint
|
|
559
|
+
against the final one to watch the loop emerge over steps.
|
|
557
560
|
|
|
558
561
|
### Distilling from base with no format anchor
|
|
559
562
|
|
|
@@ -512,13 +512,22 @@ class ApiClient:
|
|
|
512
512
|
if content:
|
|
513
513
|
yield str(content)
|
|
514
514
|
return
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
515
|
+
read1 = getattr(resp, "read1", None)
|
|
516
|
+
read = read1 if read1 is not None else resp.read
|
|
517
|
+
read_size = 4096 if read1 is not None else 1
|
|
518
|
+
while raw := read(read_size):
|
|
519
|
+
state = decoder.getstate()
|
|
520
|
+
try:
|
|
521
|
+
decoded = decoder.decode(raw)
|
|
522
|
+
except UnicodeDecodeError as exc:
|
|
523
|
+
decoder.setstate(state)
|
|
524
|
+
prefix_end = max(0, exc.start - len(state[0]))
|
|
525
|
+
yield from decoder.decode(raw[:prefix_end])
|
|
526
|
+
# bind + re-raise explicitly: the yield above clears the active exception, so a
|
|
527
|
+
# bare `raise` here would fail with "No active exception to reraise".
|
|
528
|
+
raise exc
|
|
529
|
+
yield from decoded
|
|
530
|
+
yield from decoder.decode(b"", final=True)
|
|
522
531
|
|
|
523
532
|
|
|
524
533
|
def client_from_config(require_key: bool = True) -> ApiClient:
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Typed terminal reward scoring for multi-turn GRPO rollouts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from flash.envs.base import BaseEnvironment, RolloutReward
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class RolloutScoreRequest:
|
|
13
|
+
example: dict
|
|
14
|
+
state: dict
|
|
15
|
+
turn_count: int
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _validated_reward(reward: RolloutReward, request: RolloutScoreRequest) -> RolloutReward:
|
|
19
|
+
episode = float(reward.episode)
|
|
20
|
+
# a non-finite episode reward is unscorable and has no valid scalar fallback. canonicalize it to
|
|
21
|
+
# nan, which is trl's ONLY unscorable marker (trl uses torch.isnan to exclude the row from the
|
|
22
|
+
# group baseline and then nan_to_num zeros its advantage, matching stock grpo). forwarding a raw
|
|
23
|
+
# inf instead would NOT be recognized as unscorable and would contaminate the whole group with
|
|
24
|
+
# huge advantages. per-turn credit is disabled so the unscorable row is never revived.
|
|
25
|
+
if not math.isfinite(episode):
|
|
26
|
+
print(
|
|
27
|
+
"[grpo][warn] episode reward non-finite; rollout unscorable, per-turn credit disabled"
|
|
28
|
+
)
|
|
29
|
+
return RolloutReward(episode=float("nan"), turns=None)
|
|
30
|
+
if reward.turns is None:
|
|
31
|
+
return RolloutReward(episode=episode, turns=None)
|
|
32
|
+
if not isinstance(reward.turns, (list, tuple)):
|
|
33
|
+
# only an ordered list/tuple carries a well-defined per-turn order. a str, bytes,
|
|
34
|
+
# bytearray, mapping, or unordered set would still iterate into floats and could pass
|
|
35
|
+
# the count check while assigning rewards to the wrong turns -- reject and fall back.
|
|
36
|
+
print(
|
|
37
|
+
"[grpo][warn] per-turn rewards unavailable (turns is not an ordered list/tuple); "
|
|
38
|
+
"using episode reward"
|
|
39
|
+
)
|
|
40
|
+
return RolloutReward(episode=episode, turns=None)
|
|
41
|
+
|
|
42
|
+
reason: str | None = None
|
|
43
|
+
coerced: tuple[float, ...] | None = None
|
|
44
|
+
try:
|
|
45
|
+
coerced = tuple(float(value) for value in reward.turns)
|
|
46
|
+
except (TypeError, ValueError):
|
|
47
|
+
reason = "per-turn rewards contain a non-number"
|
|
48
|
+
else:
|
|
49
|
+
if len(coerced) != request.turn_count:
|
|
50
|
+
reason = f"received {len(coerced)} reward(s) for {request.turn_count} assistant turn(s)"
|
|
51
|
+
elif not all(math.isfinite(value) for value in coerced):
|
|
52
|
+
reason = "per-turn rewards contain a non-finite value"
|
|
53
|
+
|
|
54
|
+
if reason is not None:
|
|
55
|
+
print(f"[grpo][warn] per-turn rewards unavailable ({reason}); using episode reward")
|
|
56
|
+
return RolloutReward(episode=episode, turns=None)
|
|
57
|
+
return RolloutReward(episode=episode, turns=coerced)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def score_rollouts(active_env, requests: list[RolloutScoreRequest]) -> list[RolloutReward]:
|
|
61
|
+
"""Score terminal rollout states once and return normalized typed rewards."""
|
|
62
|
+
items = [(request.example, request.state) for request in requests]
|
|
63
|
+
rollout_rewards_many = getattr(active_env, "rollout_rewards_many", None)
|
|
64
|
+
if callable(rollout_rewards_many):
|
|
65
|
+
rewards = rollout_rewards_many(items)
|
|
66
|
+
else:
|
|
67
|
+
rewards = BaseEnvironment.rollout_rewards_many(active_env, items)
|
|
68
|
+
if len(rewards) != len(requests):
|
|
69
|
+
raise RuntimeError("env.rollout_rewards_many returned the wrong number of rewards")
|
|
70
|
+
return [
|
|
71
|
+
_validated_reward(reward, request)
|
|
72
|
+
for reward, request in zip(rewards, requests, strict=True)
|
|
73
|
+
]
|
|
@@ -16,10 +16,11 @@ import threading
|
|
|
16
16
|
import time
|
|
17
17
|
from collections import OrderedDict
|
|
18
18
|
from collections.abc import Callable
|
|
19
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
20
19
|
from dataclasses import dataclass
|
|
21
20
|
from typing import TypedDict
|
|
22
21
|
|
|
22
|
+
from flash.engine.multiturn_reward_scoring import RolloutScoreRequest, score_rollouts
|
|
23
|
+
|
|
23
24
|
|
|
24
25
|
class RolloutResult(TypedDict):
|
|
25
26
|
"""Token-aligned fields returned per rollout."""
|
|
@@ -29,6 +30,8 @@ class RolloutResult(TypedDict):
|
|
|
29
30
|
logprobs: list[float]
|
|
30
31
|
env_mask: list[int]
|
|
31
32
|
reward: float
|
|
33
|
+
turn_spans: list[tuple[int, int]]
|
|
34
|
+
turn_rewards: list[float] | None
|
|
32
35
|
|
|
33
36
|
|
|
34
37
|
RolloutCompletion = tuple[str, list[int], list[float], str]
|
|
@@ -71,6 +74,8 @@ _ROLLOUT_FIELDS: tuple[str, ...] = (
|
|
|
71
74
|
"logprobs",
|
|
72
75
|
"env_mask",
|
|
73
76
|
"reward",
|
|
77
|
+
"turn_spans",
|
|
78
|
+
"turn_rewards",
|
|
74
79
|
)
|
|
75
80
|
|
|
76
81
|
|
|
@@ -174,6 +179,7 @@ def rollout_one(
|
|
|
174
179
|
completion_ids: list[int] = []
|
|
175
180
|
logprobs: list[float] = []
|
|
176
181
|
env_mask: list[int] = []
|
|
182
|
+
turn_spans: list[tuple[int, int]] = []
|
|
177
183
|
|
|
178
184
|
turns = 0
|
|
179
185
|
while True:
|
|
@@ -184,7 +190,9 @@ def rollout_one(
|
|
|
184
190
|
break
|
|
185
191
|
max_new = min(max_new, remaining)
|
|
186
192
|
asst_ids, asst_lp, text = generate(cur_ids, max_new)
|
|
193
|
+
turn_start = len(completion_ids)
|
|
187
194
|
completion_ids.extend(asst_ids)
|
|
195
|
+
turn_spans.append((turn_start, len(completion_ids)))
|
|
188
196
|
logprobs.extend(asst_lp)
|
|
189
197
|
env_mask.extend([1] * len(asst_ids))
|
|
190
198
|
cur_ids.extend(asst_ids)
|
|
@@ -212,13 +220,18 @@ def rollout_one(
|
|
|
212
220
|
env_mask.extend([0] * len(glue))
|
|
213
221
|
cur_ids.extend(glue)
|
|
214
222
|
|
|
215
|
-
|
|
223
|
+
score = score_rollouts(
|
|
224
|
+
active_env,
|
|
225
|
+
[RolloutScoreRequest(example=example, state=state, turn_count=len(turn_spans))],
|
|
226
|
+
)[0]
|
|
216
227
|
return {
|
|
217
228
|
"prompt_ids": prompt_ids,
|
|
218
229
|
"completion_ids": completion_ids,
|
|
219
230
|
"logprobs": logprobs,
|
|
220
231
|
"env_mask": env_mask,
|
|
221
|
-
"reward":
|
|
232
|
+
"reward": score.episode,
|
|
233
|
+
"turn_spans": turn_spans,
|
|
234
|
+
"turn_rewards": list(score.turns) if score.turns is not None else None,
|
|
222
235
|
}
|
|
223
236
|
|
|
224
237
|
|
|
@@ -331,6 +344,7 @@ class _RolloutState:
|
|
|
331
344
|
"messages",
|
|
332
345
|
"prompt_ids",
|
|
333
346
|
"state",
|
|
347
|
+
"turn_spans",
|
|
334
348
|
"turns",
|
|
335
349
|
)
|
|
336
350
|
|
|
@@ -343,18 +357,21 @@ class _RolloutState:
|
|
|
343
357
|
self.completion_ids: list[int] = []
|
|
344
358
|
self.logprobs: list[float] = []
|
|
345
359
|
self.env_mask: list[int] = []
|
|
360
|
+
self.turn_spans: list[tuple[int, int]] = []
|
|
346
361
|
self.state = state
|
|
347
362
|
self.turns = 0
|
|
348
363
|
self.budget = budget
|
|
349
364
|
self.done = False
|
|
350
365
|
|
|
351
|
-
def result(self, reward: float) -> RolloutResult:
|
|
366
|
+
def result(self, reward: float, turn_rewards: list[float] | None) -> RolloutResult:
|
|
352
367
|
return {
|
|
353
368
|
"prompt_ids": self.prompt_ids,
|
|
354
369
|
"completion_ids": self.completion_ids,
|
|
355
370
|
"logprobs": self.logprobs,
|
|
356
371
|
"env_mask": self.env_mask,
|
|
357
372
|
"reward": float(reward),
|
|
373
|
+
"turn_spans": self.turn_spans,
|
|
374
|
+
"turn_rewards": turn_rewards,
|
|
358
375
|
}
|
|
359
376
|
|
|
360
377
|
|
|
@@ -369,7 +386,9 @@ def _advance_after_turn(
|
|
|
369
386
|
max_turns: int,
|
|
370
387
|
) -> None:
|
|
371
388
|
"""Fold one assistant turn into ``r`` and run its env step. Sets ``r.done`` when finished."""
|
|
389
|
+
turn_start = len(r.completion_ids)
|
|
372
390
|
r.completion_ids.extend(asst_ids)
|
|
391
|
+
r.turn_spans.append((turn_start, len(r.completion_ids)))
|
|
373
392
|
r.logprobs.extend(asst_lp)
|
|
374
393
|
r.env_mask.extend([1] * len(asst_ids))
|
|
375
394
|
r.cur_ids.extend(asst_ids)
|
|
@@ -465,31 +484,6 @@ def _turn_budget(r: _RolloutState, per_turn_max_tokens: int) -> int | None:
|
|
|
465
484
|
return max(1, max_new)
|
|
466
485
|
|
|
467
486
|
|
|
468
|
-
def _score_rollouts(active_env, rollouts: list[_RolloutState]) -> list[float]:
|
|
469
|
-
"""Reward each rollout in input order, using reward_many, concurrent, or serial scoring."""
|
|
470
|
-
reward_many = getattr(active_env, "reward_many", None)
|
|
471
|
-
if callable(reward_many):
|
|
472
|
-
rewards = reward_many([(r.example, r.state) for r in rollouts])
|
|
473
|
-
if len(rewards) != len(rollouts):
|
|
474
|
-
raise RuntimeError("env.reward_many returned the wrong number of rewards")
|
|
475
|
-
return [float(x) for x in rewards]
|
|
476
|
-
|
|
477
|
-
def _score(r: _RolloutState) -> float:
|
|
478
|
-
return float(active_env.reward("", r.example, r.state))
|
|
479
|
-
|
|
480
|
-
if len(rollouts) <= 1 or not getattr(active_env, "reward_thread_safe", True):
|
|
481
|
-
return [_score(r) for r in rollouts]
|
|
482
|
-
pool = ThreadPoolExecutor(max_workers=min(16, len(rollouts)))
|
|
483
|
-
try:
|
|
484
|
-
futures = {pool.submit(_score, r): i for i, r in enumerate(rollouts)}
|
|
485
|
-
scores: list[float] = [0.0] * len(rollouts)
|
|
486
|
-
for fut in as_completed(futures):
|
|
487
|
-
scores[futures[fut]] = fut.result() # re-raises the first failed scorer
|
|
488
|
-
finally:
|
|
489
|
-
pool.shutdown(wait=True, cancel_futures=True)
|
|
490
|
-
return scores
|
|
491
|
-
|
|
492
|
-
|
|
493
487
|
_PHYSICAL_REQUEST_COUNTER = itertools.count()
|
|
494
488
|
_PHYSICAL_REQUEST_COUNTER_LOCK = threading.Lock()
|
|
495
489
|
_ROLLOUT_REQUEST_TIMEOUT_MIN_SECONDS = 600.0
|
|
@@ -690,8 +684,22 @@ def rollout_async(
|
|
|
690
684
|
to_env.put(None)
|
|
691
685
|
worker.join()
|
|
692
686
|
|
|
693
|
-
|
|
694
|
-
|
|
687
|
+
requests = [
|
|
688
|
+
RolloutScoreRequest(
|
|
689
|
+
example=rollout.example,
|
|
690
|
+
state=rollout.state,
|
|
691
|
+
turn_count=len(rollout.turn_spans),
|
|
692
|
+
)
|
|
693
|
+
for rollout in rollouts
|
|
694
|
+
]
|
|
695
|
+
scores = score_rollouts(active_env, requests)
|
|
696
|
+
return [
|
|
697
|
+
rollout.result(
|
|
698
|
+
score.episode,
|
|
699
|
+
list(score.turns) if score.turns is not None else None,
|
|
700
|
+
)
|
|
701
|
+
for rollout, score in zip(rollouts, scores, strict=True)
|
|
702
|
+
]
|
|
695
703
|
|
|
696
704
|
|
|
697
705
|
def render_message_ids(tok, messages, add_generation_prompt: bool, *, thinking: bool) -> list[int]:
|
|
@@ -964,10 +972,10 @@ def build_rollout_func(
|
|
|
964
972
|
monotonic=monotonic,
|
|
965
973
|
request_id_factory=request_id_factory,
|
|
966
974
|
)
|
|
967
|
-
out: dict[str, list] = {
|
|
968
|
-
for
|
|
969
|
-
for
|
|
970
|
-
out[
|
|
975
|
+
out: dict[str, list] = {key: [] for key in _ROLLOUT_FIELDS}
|
|
976
|
+
for rollout in rollouts:
|
|
977
|
+
for key in out:
|
|
978
|
+
out[key].append(rollout[key])
|
|
971
979
|
return out
|
|
972
980
|
finally:
|
|
973
981
|
# Abort in-flight requests on error so they don't corrupt the next GRPO step.
|
|
@@ -2,11 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
import os
|
|
7
|
+
import shutil
|
|
6
8
|
|
|
7
9
|
from flash.engine.recipe import RECIPE
|
|
8
10
|
from flash.engine.worker._pkg import W as _w
|
|
11
|
+
from flash.engine.worker.hf import (
|
|
12
|
+
RetriableInfraError,
|
|
13
|
+
_has_deployable_adapter,
|
|
14
|
+
_prefetch_error_is_retriable,
|
|
15
|
+
_require_hf_deadline_allowance,
|
|
16
|
+
_sleep_with_hf_deadline,
|
|
17
|
+
)
|
|
9
18
|
from flash.engine.worker.lora import (
|
|
19
|
+
_read_adapter_tensor_keys,
|
|
10
20
|
adapter_is_vl_warmstart,
|
|
11
21
|
assert_adapter_delta_nonzero,
|
|
12
22
|
assert_adapter_load_clean,
|
|
@@ -15,6 +25,9 @@ from flash.engine.worker.lora import (
|
|
|
15
25
|
)
|
|
16
26
|
from flash.engine.worker.perf import optimal_attn_impl
|
|
17
27
|
|
|
28
|
+
_ADAPTER_DOWNLOAD_RETRIES = 4
|
|
29
|
+
_ADAPTER_DOWNLOAD_BACKOFF_S = 5.0
|
|
30
|
+
|
|
18
31
|
|
|
19
32
|
def make_lora(model_id: str | None = None):
|
|
20
33
|
"""Build LoRA config targeting all linear layers (VL models included: the vision tower /
|
|
@@ -196,6 +209,20 @@ def _resolve_adapter_ref(adapter_ref: str) -> tuple[str, str] | None:
|
|
|
196
209
|
return parse_adapter_storage_ref(adapter_ref)
|
|
197
210
|
|
|
198
211
|
|
|
212
|
+
def _warmstart_adapter_is_loadable(adir: str) -> bool:
|
|
213
|
+
"""return true only for a structurally complete adapter config and weight file."""
|
|
214
|
+
if not _has_deployable_adapter(adir):
|
|
215
|
+
return False
|
|
216
|
+
try:
|
|
217
|
+
with open(os.path.join(adir, "adapter_config.json"), encoding="utf-8") as config_file:
|
|
218
|
+
config = json.load(config_file)
|
|
219
|
+
if not isinstance(config, dict) or str(config.get("peft_type", "")).upper() != "LORA":
|
|
220
|
+
return False
|
|
221
|
+
return bool(_read_adapter_tensor_keys(adir))
|
|
222
|
+
except Exception:
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
|
|
199
226
|
def _download_adapter(adapter_prefix: str | None) -> str | None:
|
|
200
227
|
"""Download an init_from_adapter LoRA to /tmp/evdl/<prefix>/adapter and return its dir.
|
|
201
228
|
|
|
@@ -210,19 +237,45 @@ def _download_adapter(adapter_prefix: str | None) -> str | None:
|
|
|
210
237
|
repo, prefix = resolved
|
|
211
238
|
from huggingface_hub import snapshot_download
|
|
212
239
|
|
|
213
|
-
try:
|
|
214
|
-
snapshot_download(
|
|
215
|
-
repo_id=repo,
|
|
216
|
-
repo_type="dataset",
|
|
217
|
-
allow_patterns=[f"{prefix}/adapter/*"],
|
|
218
|
-
local_dir="/tmp/evdl",
|
|
219
|
-
token=os.environ.get("HF_TOKEN"),
|
|
220
|
-
revision=(_w.JOB_SPEC.train.init_from_adapter_revision if _w.JOB_SPEC else None)
|
|
221
|
-
or None,
|
|
222
|
-
)
|
|
223
|
-
except Exception:
|
|
224
|
-
raise RuntimeError(
|
|
225
|
-
"the prepared warm-start source adapter could not be downloaded"
|
|
226
|
-
) from None
|
|
227
240
|
adir = os.path.join("/tmp/evdl", prefix, "adapter")
|
|
228
|
-
|
|
241
|
+
# start from a clean path so the loadable-check can only ever accept files THIS download
|
|
242
|
+
# materialized -- leftover materialization from an earlier worker subprocess, attempt, or a
|
|
243
|
+
# different run sharing the same prefix must not satisfy the post-exception loadable check and
|
|
244
|
+
# mask a terminal 404/403/429 for the current repo/revision.
|
|
245
|
+
shutil.rmtree(adir, ignore_errors=True)
|
|
246
|
+
for attempt in range(_ADAPTER_DOWNLOAD_RETRIES):
|
|
247
|
+
_require_hf_deadline_allowance()
|
|
248
|
+
try:
|
|
249
|
+
snapshot_download(
|
|
250
|
+
repo_id=repo,
|
|
251
|
+
repo_type="dataset",
|
|
252
|
+
allow_patterns=[f"{prefix}/adapter/*"],
|
|
253
|
+
local_dir="/tmp/evdl",
|
|
254
|
+
token=os.environ.get("HF_TOKEN"),
|
|
255
|
+
revision=(_w.JOB_SPEC.train.init_from_adapter_revision if _w.JOB_SPEC else None)
|
|
256
|
+
or None,
|
|
257
|
+
)
|
|
258
|
+
except Exception as error:
|
|
259
|
+
# a later nonessential sidecar may fail after the config and weights are already complete.
|
|
260
|
+
if _warmstart_adapter_is_loadable(adir):
|
|
261
|
+
return adir
|
|
262
|
+
if not _prefetch_error_is_retriable(error):
|
|
263
|
+
raise RuntimeError(
|
|
264
|
+
"the prepared warm-start source adapter could not be downloaded"
|
|
265
|
+
) from None
|
|
266
|
+
else:
|
|
267
|
+
# a returned snapshot can still be incomplete: an interrupted transfer, or hf falling
|
|
268
|
+
# back to a partial local_dir when a throttled metadata call cannot confirm the file set.
|
|
269
|
+
if _warmstart_adapter_is_loadable(adir):
|
|
270
|
+
return adir
|
|
271
|
+
# discard partial local_dir materialization so the next attempt cannot reuse stale files.
|
|
272
|
+
shutil.rmtree(adir, ignore_errors=True)
|
|
273
|
+
if attempt + 1 < _ADAPTER_DOWNLOAD_RETRIES:
|
|
274
|
+
try:
|
|
275
|
+
if not _sleep_with_hf_deadline(_ADAPTER_DOWNLOAD_BACKOFF_S * (attempt + 1)):
|
|
276
|
+
break
|
|
277
|
+
except Exception:
|
|
278
|
+
break
|
|
279
|
+
raise RetriableInfraError(
|
|
280
|
+
"the prepared warm-start source adapter could not be downloaded after transient failures"
|
|
281
|
+
) from None
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""GRPO trainer support for group-relative per-turn credit assignment."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
8
|
+
import torch
|
|
9
|
+
from trl import GRPOTrainer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class TurnCreditRow:
|
|
14
|
+
spans: tuple[tuple[int, int], ...]
|
|
15
|
+
turns: tuple[float, ...] | None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_per_turn_advantages(
|
|
19
|
+
turn_spans_per_completion: list[list[tuple[int, int]]],
|
|
20
|
+
turn_rewards_per_completion: list[list[float] | None],
|
|
21
|
+
num_generations: int,
|
|
22
|
+
completion_len: int,
|
|
23
|
+
*,
|
|
24
|
+
episode_advantages: torch.Tensor,
|
|
25
|
+
) -> torch.Tensor:
|
|
26
|
+
"""Build token-aligned advantages from per-turn rewards in consecutive GRPO groups."""
|
|
27
|
+
batch_size = len(turn_spans_per_completion)
|
|
28
|
+
if len(turn_rewards_per_completion) != batch_size:
|
|
29
|
+
raise ValueError("turn span and reward row counts must match")
|
|
30
|
+
if num_generations <= 0 or batch_size % num_generations != 0:
|
|
31
|
+
raise ValueError("batch size must be divisible by num_generations")
|
|
32
|
+
if completion_len < 0:
|
|
33
|
+
raise ValueError("completion_len must be non-negative")
|
|
34
|
+
if episode_advantages.dim() != 1 or episode_advantages.numel() != batch_size:
|
|
35
|
+
raise ValueError("episode_advantages must have shape [B]")
|
|
36
|
+
|
|
37
|
+
rows: list[TurnCreditRow] = []
|
|
38
|
+
for row_index, (spans, turns) in enumerate(
|
|
39
|
+
zip(turn_spans_per_completion, turn_rewards_per_completion, strict=True)
|
|
40
|
+
):
|
|
41
|
+
normalized_spans = tuple((int(start), int(end)) for start, end in spans)
|
|
42
|
+
for start, end in normalized_spans:
|
|
43
|
+
if not 0 <= start <= end <= completion_len:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"turn span [{start}, {end}) for row {row_index} exceeds completion width "
|
|
46
|
+
f"{completion_len}"
|
|
47
|
+
)
|
|
48
|
+
rows.append(
|
|
49
|
+
TurnCreditRow(
|
|
50
|
+
spans=normalized_spans,
|
|
51
|
+
turns=None if turns is None else tuple(turns),
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
advantages = episode_advantages.new_zeros((batch_size, completion_len))
|
|
56
|
+
for group_start in range(0, batch_size, num_generations):
|
|
57
|
+
group_end = group_start + num_generations
|
|
58
|
+
group = rows[group_start:group_end]
|
|
59
|
+
if any(row.turns is None for row in group):
|
|
60
|
+
for row_index in range(group_start, group_end):
|
|
61
|
+
completion_end = rows[row_index].spans[-1][1] if rows[row_index].spans else 0
|
|
62
|
+
advantages[row_index, :completion_end] = episode_advantages[row_index]
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
max_turns = max(len(row.turns or ()) for row in group)
|
|
66
|
+
for turn_index in range(max_turns):
|
|
67
|
+
member_indexes = [
|
|
68
|
+
row_index
|
|
69
|
+
for row_index in range(group_start, group_end)
|
|
70
|
+
if turn_index < len(rows[row_index].turns or ())
|
|
71
|
+
and rows[row_index].spans[turn_index][1] > rows[row_index].spans[turn_index][0]
|
|
72
|
+
]
|
|
73
|
+
if not member_indexes:
|
|
74
|
+
# every member's span for this turn is zero-width (no emitted tokens); an
|
|
75
|
+
# empty turn contributes no advantage and must not skew the group baseline.
|
|
76
|
+
continue
|
|
77
|
+
mean_reward = sum(
|
|
78
|
+
cast(tuple[float, ...], rows[row_index].turns)[turn_index]
|
|
79
|
+
for row_index in member_indexes
|
|
80
|
+
) / len(member_indexes)
|
|
81
|
+
for row_index in member_indexes:
|
|
82
|
+
row = rows[row_index]
|
|
83
|
+
reward = cast(tuple[float, ...], row.turns)[turn_index]
|
|
84
|
+
start, end = row.spans[turn_index]
|
|
85
|
+
advantages[row_index, start:end] = reward - mean_reward
|
|
86
|
+
|
|
87
|
+
if not bool(torch.isfinite(advantages).all()):
|
|
88
|
+
raise ValueError("per-turn advantages must be finite")
|
|
89
|
+
return advantages
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class GRPOPerTurnTrainer(GRPOTrainer):
|
|
93
|
+
"""Replace scalar GRPO advantages with aligned per-turn advantages when supplied."""
|
|
94
|
+
|
|
95
|
+
def _generate_and_score_completions(self, inputs: list[dict[str, object]]) -> dict[str, object]:
|
|
96
|
+
output = cast(dict[str, object], super()._generate_and_score_completions(inputs))
|
|
97
|
+
turn_rewards = cast(
|
|
98
|
+
list[list[float] | None],
|
|
99
|
+
[item.get("turn_rewards") for item in inputs],
|
|
100
|
+
)
|
|
101
|
+
if not any(rewards is not None for rewards in turn_rewards):
|
|
102
|
+
return output
|
|
103
|
+
if self.accelerator.num_processes > 1:
|
|
104
|
+
raise NotImplementedError(
|
|
105
|
+
"per-turn GRPO advantages currently support single-process training only; "
|
|
106
|
+
"distributed group centering requires gather-aligned turn metadata"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
turn_spans = cast(
|
|
110
|
+
list[list[tuple[int, int]] | None],
|
|
111
|
+
[item.get("turn_spans") for item in inputs],
|
|
112
|
+
)
|
|
113
|
+
if any(spans is None for spans in turn_spans):
|
|
114
|
+
raise ValueError("per-turn rollout rows must all include turn_spans")
|
|
115
|
+
aligned_turn_spans = cast(list[list[tuple[int, int]]], turn_spans)
|
|
116
|
+
scalar_advantages = cast(torch.Tensor, output["advantages"])
|
|
117
|
+
if scalar_advantages.dim() != 1:
|
|
118
|
+
raise ValueError("expected TRL scalar advantages with shape [B]")
|
|
119
|
+
completion_ids = cast(torch.Tensor, output["completion_ids"])
|
|
120
|
+
batch_size, completion_len = completion_ids.shape
|
|
121
|
+
if len(inputs) != batch_size:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"per-turn metadata has {len(inputs)} row(s) for output batch size {batch_size}"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
output["advantages"] = build_per_turn_advantages(
|
|
127
|
+
aligned_turn_spans,
|
|
128
|
+
turn_rewards,
|
|
129
|
+
num_generations=(
|
|
130
|
+
self.num_generations if self.model.training else self.num_generations_eval
|
|
131
|
+
),
|
|
132
|
+
completion_len=completion_len,
|
|
133
|
+
episode_advantages=scalar_advantages,
|
|
134
|
+
).to(device=scalar_advantages.device, dtype=scalar_advantages.dtype)
|
|
135
|
+
if not getattr(self, "_per_turn_credit_logged", False):
|
|
136
|
+
print("[rl] multi-turn per-turn group-relative credit is active")
|
|
137
|
+
self._per_turn_credit_logged = True
|
|
138
|
+
return output
|
|
@@ -9,7 +9,9 @@ from __future__ import annotations
|
|
|
9
9
|
import contextlib
|
|
10
10
|
import faulthandler
|
|
11
11
|
import json
|
|
12
|
+
import math
|
|
12
13
|
import os
|
|
14
|
+
import re
|
|
13
15
|
import sys
|
|
14
16
|
import threading
|
|
15
17
|
import time
|
|
@@ -235,7 +237,60 @@ def _maybe_attach_gpu_diag(payload: dict, last_gpu_diag_at: float, now: float) -
|
|
|
235
237
|
return last_gpu_diag_at
|
|
236
238
|
|
|
237
239
|
|
|
238
|
-
|
|
240
|
+
_REWARD_METRIC_NAME_DISALLOWED = re.compile(r"[^A-Za-z0-9_.-]")
|
|
241
|
+
_REWARD_METRIC_RESERVED_NAMES = frozenset(
|
|
242
|
+
{
|
|
243
|
+
"reward",
|
|
244
|
+
"reward_last",
|
|
245
|
+
"step",
|
|
246
|
+
"epoch",
|
|
247
|
+
"loss",
|
|
248
|
+
"grad_norm",
|
|
249
|
+
"learning_rate",
|
|
250
|
+
"stage",
|
|
251
|
+
"gpu",
|
|
252
|
+
"diag",
|
|
253
|
+
}
|
|
254
|
+
)
|
|
255
|
+
_REWARD_METRIC_LIMIT = 12
|
|
256
|
+
# names TRAINING.md tells users to judge on: never dropped by the alphabetical cap.
|
|
257
|
+
_REWARD_METRIC_PRIORITY_NAMES = ("success",)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _bounded_reward_metrics(metrics) -> dict[str, float]:
|
|
261
|
+
if not isinstance(metrics, dict):
|
|
262
|
+
return {}
|
|
263
|
+
surviving: dict[str, float] = {}
|
|
264
|
+
for name, value in metrics.items():
|
|
265
|
+
sanitized_name = _REWARD_METRIC_NAME_DISALLOWED.sub("", str(name))[:64]
|
|
266
|
+
if not sanitized_name or sanitized_name in _REWARD_METRIC_RESERVED_NAMES:
|
|
267
|
+
continue
|
|
268
|
+
try:
|
|
269
|
+
score = float(value)
|
|
270
|
+
except (TypeError, ValueError):
|
|
271
|
+
continue
|
|
272
|
+
if not math.isfinite(score):
|
|
273
|
+
continue
|
|
274
|
+
# distinct source names that sanitize to the same key must not silently overwrite each
|
|
275
|
+
# other; disambiguate with a numeric suffix (kept within the 64-char bound, allowed chars).
|
|
276
|
+
unique_name = sanitized_name
|
|
277
|
+
suffix = 2
|
|
278
|
+
while unique_name in surviving:
|
|
279
|
+
tail = f"_{suffix}"
|
|
280
|
+
unique_name = sanitized_name[: 64 - len(tail)] + tail
|
|
281
|
+
suffix += 1
|
|
282
|
+
surviving[unique_name] = score
|
|
283
|
+
if len(surviving) <= _REWARD_METRIC_LIMIT:
|
|
284
|
+
return dict(sorted(surviving.items()))
|
|
285
|
+
# the cap must not drop metrics users are told to judge on (e.g. success); keep those first,
|
|
286
|
+
# then fill the remaining slots alphabetically.
|
|
287
|
+
priority = [n for n in _REWARD_METRIC_PRIORITY_NAMES if n in surviving]
|
|
288
|
+
remaining = max(0, _REWARD_METRIC_LIMIT - len(priority))
|
|
289
|
+
rest = sorted(n for n in surviving if n not in priority)[:remaining]
|
|
290
|
+
return {n: surviving[n] for n in sorted(priority + rest)}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def make_reward_heartbeat_callback(reward_metrics=None):
|
|
239
294
|
"""Return a TRL callback that streams per-step reward to the HF heartbeat channel."""
|
|
240
295
|
from transformers import TrainerCallback
|
|
241
296
|
|
|
@@ -243,6 +298,12 @@ def make_reward_heartbeat_callback():
|
|
|
243
298
|
def __init__(self):
|
|
244
299
|
self.reward_history = []
|
|
245
300
|
self.last_gpu_diag_at = 0.0
|
|
301
|
+
self.latest_reward_metrics: dict[str, float] = {}
|
|
302
|
+
|
|
303
|
+
def latest_fields(self) -> dict:
|
|
304
|
+
if not self.latest_reward_metrics:
|
|
305
|
+
return {}
|
|
306
|
+
return {"reward_metrics": dict(self.latest_reward_metrics)}
|
|
246
307
|
|
|
247
308
|
def on_log(self, args, state, control, logs=None, **kwargs):
|
|
248
309
|
if not logs:
|
|
@@ -261,6 +322,10 @@ def make_reward_heartbeat_callback():
|
|
|
261
322
|
"reward": r,
|
|
262
323
|
"reward_last": self.reward_history[-8:],
|
|
263
324
|
}
|
|
325
|
+
latest_metrics = reward_metrics() if callable(reward_metrics) else reward_metrics
|
|
326
|
+
self.latest_reward_metrics = _bounded_reward_metrics(latest_metrics)
|
|
327
|
+
if self.latest_reward_metrics:
|
|
328
|
+
payload["reward_metrics"] = dict(self.latest_reward_metrics)
|
|
264
329
|
now = time.monotonic()
|
|
265
330
|
self.last_gpu_diag_at = _maybe_attach_gpu_diag(payload, self.last_gpu_diag_at, now)
|
|
266
331
|
_w.heartbeat("rl_step", **payload)
|