freesolo-flash-dev 0.2.56__tar.gz → 0.2.57__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/PKG-INFO +1 -1
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/recipe.py +11 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/opd.py +212 -25
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/sft.py +5 -1
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/schema/__init__.py +7 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/serve/deploy.py +42 -1
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/routes/serving.py +4 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/spec.py +7 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/pyproject.toml +2 -2
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_opd.py +265 -19
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_serve.py +107 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_server_api.py +30 -0
- freesolo_flash_dev-0.2.57/tests/test_sft_max_context.py +60 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/uv.lock +7 -7
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.env.example +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/README.md +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/catalog.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cli/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cli/commands.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cli/env_setup.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cli/render.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cli/training_doc.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/client/http.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cost/analytical.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cost/facts.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/steps.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/structured_outputs.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/adapter.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/hf.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/opd_gkd.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/opd_vllm.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/rl.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/teacher.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/tokenizer_align.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/envs/adapter.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/envs/loader.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/lora_rank.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/_worker.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/train/deps.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/vast/api.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/runner/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/app.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/repo_cleanup.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/conftest.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_charge_pricing.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cli_commands.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cli_render_coverage.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_client.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_env_pull_loader_coverage.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_flash_worker.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_instance_bootstrap_coverage.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_jobs.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_lambda_api_coverage.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_lora_rank_preflight.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_multiturn_rollout_coverage.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_opd_vllm.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_repo_cleanup.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_server_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_sft_example_selection.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_spec_and_validation.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_structured_outputs.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_train_context_preflight.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_vast_api.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_vast_api_coverage.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-0.2.56 → freesolo_flash_dev-0.2.57}/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: 0.2.
|
|
3
|
+
Version: 0.2.57
|
|
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
|
|
@@ -169,6 +169,17 @@ class OPDConfig:
|
|
|
169
169
|
# that pushes up log P(eos) at the position a rollout naturally terminated, restoring the stop
|
|
170
170
|
# signal the alignment cannot express. 0 disables it; overridable via [train].opd_eos_loss_coef.
|
|
171
171
|
eos_loss_coef: float = 0.5
|
|
172
|
+
# student-side entropy floor: a hinge on the per-sample MEAN completion entropy (nats), added to
|
|
173
|
+
# the reverse-KL loss. reverse-KL is mode-seeking and progressively over-sharpens small students
|
|
174
|
+
# (<=2b) until greedy (temp=0) serving falls into a repetition loop that never emits EOS — the
|
|
175
|
+
# teacher-gated fix (EOPD, arxiv 2603.07079) is unimplementable here (Fireworks echo-scoring
|
|
176
|
+
# returns no teacher distribution, and teacher/student are cross-tokenizer), so the STUDENT's own
|
|
177
|
+
# distribution — local, same-vocab, already computed — is regularized instead. the hinge is
|
|
178
|
+
# self-limiting: zero loss and zero gradient while mean entropy sits at/above the floor, so runs
|
|
179
|
+
# that don't collapse pay nothing. 0 disables the term (mean entropy is still logged);
|
|
180
|
+
# overridable via [train].opd_entropy_floor_coef / [train].opd_entropy_floor.
|
|
181
|
+
entropy_floor_coef: float = 0.0
|
|
182
|
+
entropy_floor: float = 0.5
|
|
172
183
|
|
|
173
184
|
|
|
174
185
|
@dataclass(frozen=True)
|
|
@@ -158,6 +158,12 @@ class OpdKnobs:
|
|
|
158
158
|
# over shared decoded-text spans cannot supervise the zero-width stop token, so this restores the
|
|
159
159
|
# stop signal; 0 disables it. [train].opd_eos_loss_coef, else RECIPE.opd.eos_loss_coef.
|
|
160
160
|
eos_loss_coef: float = RECIPE.opd.eos_loss_coef
|
|
161
|
+
# student-side entropy floor (see _entropy_floor_term): hinge weight and target (nats) on the
|
|
162
|
+
# per-sample mean completion entropy, countering reverse-KL over-sharpening on small students.
|
|
163
|
+
# coef 0 disables the loss term; the mean entropy is still computed (detached) for logging.
|
|
164
|
+
# [train].opd_entropy_floor_coef / opd_entropy_floor, else RECIPE.opd defaults.
|
|
165
|
+
entropy_floor_coef: float = RECIPE.opd.entropy_floor_coef
|
|
166
|
+
entropy_floor: float = RECIPE.opd.entropy_floor
|
|
161
167
|
save_every: int = 0
|
|
162
168
|
max_length: int = 0
|
|
163
169
|
# Student on-policy sampling stops at these delimiters (parity with GRPO), so the teacher never
|
|
@@ -224,6 +230,19 @@ def _resolve_opd_knobs() -> OpdKnobs:
|
|
|
224
230
|
_eos if (_eos := opt("opd_eos_loss_coef", None)) is not None else d.eos_loss_coef
|
|
225
231
|
),
|
|
226
232
|
),
|
|
233
|
+
# like eos_loss_coef: explicit 0 is valid (disable), only None falls back to the recipe.
|
|
234
|
+
entropy_floor_coef=max(
|
|
235
|
+
0.0,
|
|
236
|
+
float(
|
|
237
|
+
_ec
|
|
238
|
+
if (_ec := opt("opd_entropy_floor_coef", None)) is not None
|
|
239
|
+
else d.entropy_floor_coef
|
|
240
|
+
),
|
|
241
|
+
),
|
|
242
|
+
entropy_floor=max(
|
|
243
|
+
0.0,
|
|
244
|
+
float(_ef if (_ef := opt("opd_entropy_floor", None)) is not None else d.entropy_floor),
|
|
245
|
+
),
|
|
227
246
|
save_every=int(opt("save_every", 0) or 20),
|
|
228
247
|
max_length=int(opt("max_context_tokens", 0) or 0),
|
|
229
248
|
stop_sequences=tuple(getattr(t, "stop_sequences", ()) or ()),
|
|
@@ -315,7 +334,9 @@ def _student_model(model_id, model_init_kwargs, device):
|
|
|
315
334
|
|
|
316
335
|
# init_model is the base id (fresh run). VL runs load the full multimodal model so all-linear LoRA
|
|
317
336
|
# sees every target; non-VL runs keep the lighter causal-LM loader.
|
|
318
|
-
base = model_cls.from_pretrained(init_model, trust_remote_code=True, **model_init_kwargs).to(
|
|
337
|
+
base = model_cls.from_pretrained(init_model, trust_remote_code=True, **model_init_kwargs).to(
|
|
338
|
+
device
|
|
339
|
+
)
|
|
319
340
|
return get_peft_model(base, init_peft), str(init_model)
|
|
320
341
|
|
|
321
342
|
|
|
@@ -369,7 +390,8 @@ def run_opd():
|
|
|
369
390
|
f"[opd] gkd (groupwise reverse-KL) teacher={knobs.teacher_model} "
|
|
370
391
|
f"epochs={knobs.epochs} warm_start={warm_start or 'none'} "
|
|
371
392
|
f"mode={'multi-turn' if multi_turn else 'single-turn'} "
|
|
372
|
-
f"eos_loss_coef={knobs.eos_loss_coef}"
|
|
393
|
+
f"eos_loss_coef={knobs.eos_loss_coef} "
|
|
394
|
+
f"entropy_floor_coef={knobs.entropy_floor_coef} entropy_floor={knobs.entropy_floor}"
|
|
373
395
|
)
|
|
374
396
|
|
|
375
397
|
# The GLM teacher key is a platform-owned credential the control plane injects into the worker
|
|
@@ -621,12 +643,24 @@ def run_opd():
|
|
|
621
643
|
# EMA of the per-rollout truncation rate — the runaway signal that scales terminal-EOS reinforcement
|
|
622
644
|
# (see _runaway_eos_scale). Starts at 0 (assume the student stops fine); rises if rollouts run away.
|
|
623
645
|
trunc_ema = 0.0
|
|
646
|
+
# EMA of the per-rollout EMPTY-completion rate — the OVER-correction signal (see _overcorrection_damp).
|
|
647
|
+
# Terminal-EOS reinforcement raises the SHARED eos logit, so pushing it too hard makes the student
|
|
648
|
+
# emit eos FIRST (empty completion -> empty serve). trunc_ema can't see this: an empty rollout is
|
|
649
|
+
# truncated=False, so it reads as "terminated fine" and keeps the coef pinned high; empty_ema damps
|
|
650
|
+
# the coef back down as empties appear, closing the feedback loop.
|
|
651
|
+
empty_ema = 0.0
|
|
624
652
|
granularity_sum = 0.0
|
|
625
653
|
granularity_n = 0
|
|
626
654
|
# Running mean of the reinforced terminal log P(eos): should RISE over a run as the student learns
|
|
627
655
|
# to stop, the counterpart to truncated_rollouts FALLING. Surfaced in train_meta.
|
|
628
656
|
eos_logprob_sum = 0.0
|
|
629
657
|
eos_logprob_n = 0
|
|
658
|
+
# Running mean of the per-sample student completion entropy (nats) and how many distilled samples
|
|
659
|
+
# tripped the entropy-floor hinge — the in-band view of reverse-KL entropy collapse (see
|
|
660
|
+
# _entropy_floor_term). Surfaced in train_meta; the per-step mean goes to wandb/heartbeats.
|
|
661
|
+
entropy_sum = 0.0
|
|
662
|
+
entropy_n = 0
|
|
663
|
+
entropy_floor_active = 0
|
|
630
664
|
opt_steps = (
|
|
631
665
|
0 # optimizer steps actually applied (< steps if any iteration had no teacher signal)
|
|
632
666
|
)
|
|
@@ -688,9 +722,16 @@ def run_opd():
|
|
|
688
722
|
sample (scaled 1/accum_target for gradient accumulation), advance the step aggregates, and
|
|
689
723
|
refresh the stall clock. Called after vLLM generation and teacher scoring; ``samples_seen`` is
|
|
690
724
|
advanced by the caller once per generated rollout so its timing is unchanged."""
|
|
691
|
-
nonlocal
|
|
725
|
+
nonlocal \
|
|
726
|
+
teacher_ok, \
|
|
727
|
+
teacher_transient, \
|
|
728
|
+
teacher_error, \
|
|
729
|
+
truncated_rollouts, \
|
|
730
|
+
step_loss, \
|
|
731
|
+
step_cov
|
|
692
732
|
nonlocal granularity_sum, granularity_n, generated_tokens, teacher_input_tokens, nseq
|
|
693
|
-
nonlocal eos_logprob_sum, eos_logprob_n, trunc_ema
|
|
733
|
+
nonlocal eos_logprob_sum, eos_logprob_n, trunc_ema, empty_ema
|
|
734
|
+
nonlocal entropy_sum, entropy_n, entropy_floor_active, step_ent_sum, step_ent_n
|
|
694
735
|
if r.teacher_status == "ok":
|
|
695
736
|
teacher_ok += 1
|
|
696
737
|
elif r.teacher_status == "transient":
|
|
@@ -702,6 +743,10 @@ def run_opd():
|
|
|
702
743
|
# Update the runaway EMA once per accounted rollout (truncated or not) so terminal-EOS
|
|
703
744
|
# reinforcement tracks the student's live termination health (see _runaway_eos_scale).
|
|
704
745
|
trunc_ema += (1.0 - _EOS_TRUNC_EMA_DECAY) * ((1.0 if r.truncated else 0.0) - trunc_ema)
|
|
746
|
+
# An empty completion terminates (truncated=False), so trunc_ema reads it as healthy; track it
|
|
747
|
+
# separately so the controller can back OFF when its own EOS push starts collapsing rollouts.
|
|
748
|
+
_is_empty = r.loss is None and _sample_skip_reason(r) == "empty_completion"
|
|
749
|
+
empty_ema += (1.0 - _EOS_TRUNC_EMA_DECAY) * ((1.0 if _is_empty else 0.0) - empty_ema)
|
|
705
750
|
if r.loss is None:
|
|
706
751
|
reason = _sample_skip_reason(r)
|
|
707
752
|
skip_counts[reason] += 1
|
|
@@ -721,6 +766,13 @@ def run_opd():
|
|
|
721
766
|
if r.eos_logprob is not None:
|
|
722
767
|
eos_logprob_sum += r.eos_logprob
|
|
723
768
|
eos_logprob_n += 1
|
|
769
|
+
if r.entropy is not None:
|
|
770
|
+
entropy_sum += r.entropy
|
|
771
|
+
entropy_n += 1
|
|
772
|
+
step_ent_sum += r.entropy
|
|
773
|
+
step_ent_n += 1
|
|
774
|
+
if knobs.entropy_floor_coef > 0 and r.entropy < knobs.entropy_floor:
|
|
775
|
+
entropy_floor_active += 1
|
|
724
776
|
nseq += 1
|
|
725
777
|
# Non-liveness progress ping WITHIN the step (rationale in _opd_progress).
|
|
726
778
|
_opd_progress(opt_steps, nseq)
|
|
@@ -747,6 +799,7 @@ def run_opd():
|
|
|
747
799
|
max_teacher_workers = _opd_teacher_workers(
|
|
748
800
|
knobs.prompts_per_step * knobs.group_size, teacher_batch_size
|
|
749
801
|
)
|
|
802
|
+
|
|
750
803
|
# fields= carries opt_steps on the liveness thread's opd_step pings: opd_step is upload-throttled,
|
|
751
804
|
# so a stepless liveness ping could win the slot and overwrite the main thread's stepped heartbeat,
|
|
752
805
|
# leaving actual_steps_run to floor a cancelled run to 1 step (codex[bot]).
|
|
@@ -770,10 +823,15 @@ def run_opd():
|
|
|
770
823
|
for no_signal_attempt in range(1, max_no_signal_attempts + 1):
|
|
771
824
|
it = step # data-slice + display index for THIS rollout attempt
|
|
772
825
|
step += 1 # advance up front so the nseq==0 retry path can't spin forever
|
|
773
|
-
batch = [
|
|
826
|
+
batch = [
|
|
827
|
+
examples[(it * prompts_per_step + i) % len(examples)]
|
|
828
|
+
for i in range(prompts_per_step)
|
|
829
|
+
]
|
|
774
830
|
accum_target = max(1, prompts_per_step * group)
|
|
775
831
|
step_loss = 0.0
|
|
776
832
|
step_cov = 0.0
|
|
833
|
+
step_ent_sum = 0.0
|
|
834
|
+
step_ent_n = 0
|
|
777
835
|
nseq = 0
|
|
778
836
|
step_skip_counts: Counter[str] = Counter()
|
|
779
837
|
# Step pipeline. vLLM produces on-policy rollouts, scorable completions are immediately
|
|
@@ -848,6 +906,7 @@ def run_opd():
|
|
|
848
906
|
loss_microbatch_size,
|
|
849
907
|
backward_scale=1.0 / _accum_target,
|
|
850
908
|
runaway_rate=trunc_ema,
|
|
909
|
+
overcorr_rate=empty_ema,
|
|
851
910
|
)
|
|
852
911
|
opd_phase_seconds["loss_backward"] += time.perf_counter() - loss_started
|
|
853
912
|
opd_phase_counts["loss_batches"] += 1
|
|
@@ -991,6 +1050,8 @@ def run_opd():
|
|
|
991
1050
|
opd_phase_counts["vllm_syncs"] += 1
|
|
992
1051
|
avg_loss = step_loss / nseq
|
|
993
1052
|
avg_cov = step_cov / nseq
|
|
1053
|
+
# None when no distilled sample this step carried an entropy (bare test doubles).
|
|
1054
|
+
avg_ent = (step_ent_sum / step_ent_n) if step_ent_n else None
|
|
994
1055
|
loss_curve.append(avg_loss)
|
|
995
1056
|
coverage_curve.append(avg_cov)
|
|
996
1057
|
_w.heartbeat(
|
|
@@ -1004,6 +1065,7 @@ def run_opd():
|
|
|
1004
1065
|
step=opt_steps,
|
|
1005
1066
|
loss=avg_loss,
|
|
1006
1067
|
coverage=avg_cov,
|
|
1068
|
+
entropy=avg_ent,
|
|
1007
1069
|
gpu=gpu_diagnostics(include_torch=False),
|
|
1008
1070
|
force=True,
|
|
1009
1071
|
)
|
|
@@ -1012,11 +1074,17 @@ def run_opd():
|
|
|
1012
1074
|
with contextlib.suppress(Exception):
|
|
1013
1075
|
import wandb
|
|
1014
1076
|
|
|
1015
|
-
|
|
1077
|
+
wandb_metrics = {"opd/loss": avg_loss, "opd/coverage": avg_cov}
|
|
1078
|
+
if avg_ent is not None:
|
|
1079
|
+
# falls over a collapsing run; should flatten at/above opd_entropy_floor
|
|
1080
|
+
# once the floor is enabled (see _entropy_floor_term).
|
|
1081
|
+
wandb_metrics["opd/entropy"] = avg_ent
|
|
1082
|
+
wandb.log(wandb_metrics, step=opt_steps)
|
|
1016
1083
|
if it % 10 == 0:
|
|
1017
1084
|
print(
|
|
1018
1085
|
f"[opd] step {it + 1}/{steps} loss={avg_loss:.4f} "
|
|
1019
1086
|
f"coverage={avg_cov:.0%} seqs={nseq}"
|
|
1087
|
+
+ (f" entropy={avg_ent:.3f}" if avg_ent is not None else "")
|
|
1020
1088
|
)
|
|
1021
1089
|
# Checkpoint on OPTIMIZER-step count, not the loop index: a `step-N` artifact must
|
|
1022
1090
|
# contain N real updates (skipped no-signal iterations don't advance opt_steps), else a
|
|
@@ -1122,6 +1190,17 @@ def run_opd():
|
|
|
1122
1190
|
"eos_loss_coef": knobs.eos_loss_coef,
|
|
1123
1191
|
"eos_reinforced_samples": eos_logprob_n,
|
|
1124
1192
|
"mean_eos_logprob": (eos_logprob_sum / eos_logprob_n) if eos_logprob_n else None,
|
|
1193
|
+
# Final EMA of the empty-completion rate (_overcorrection_damp input). A non-trivial value
|
|
1194
|
+
# means the controller detected its own EOS push collapsing rollouts and damped the coef.
|
|
1195
|
+
"final_empty_rate_ema": round(empty_ema, 5),
|
|
1196
|
+
# Student entropy floor (see _entropy_floor_term): the knobs, the run-mean per-sample
|
|
1197
|
+
# completion entropy (nats), and how many distilled samples tripped the hinge. A
|
|
1198
|
+
# collapsing run shows mean_student_entropy FALLING step over step (opd/entropy in
|
|
1199
|
+
# wandb); with the floor enabled it should flatten at/above opd_entropy_floor.
|
|
1200
|
+
"entropy_floor_coef": knobs.entropy_floor_coef,
|
|
1201
|
+
"entropy_floor": knobs.entropy_floor,
|
|
1202
|
+
"mean_student_entropy": (entropy_sum / entropy_n) if entropy_n else None,
|
|
1203
|
+
"entropy_floor_active_samples": entropy_floor_active,
|
|
1125
1204
|
"teacher_transient_failures": teacher_transient,
|
|
1126
1205
|
"teacher_errors": teacher_error,
|
|
1127
1206
|
"no_signal_resamples": no_signal_resamples,
|
|
@@ -1169,7 +1248,9 @@ def run_opd():
|
|
|
1169
1248
|
else None
|
|
1170
1249
|
),
|
|
1171
1250
|
"opd_rollout_pipeline_max_chunks": (
|
|
1172
|
-
_opd_rollout_pipeline_max_chunks(prompts_per_step * group)
|
|
1251
|
+
_opd_rollout_pipeline_max_chunks(prompts_per_step * group)
|
|
1252
|
+
if not multi_turn
|
|
1253
|
+
else None
|
|
1173
1254
|
),
|
|
1174
1255
|
"opd_teacher_workers": max_teacher_workers,
|
|
1175
1256
|
"opd_teacher_batch_size": teacher_batch_size,
|
|
@@ -1218,6 +1299,10 @@ class SampleResult:
|
|
|
1218
1299
|
# Detached log P(eos) at the reinforced terminal position, when the EOS behaviour-cloning term
|
|
1219
1300
|
# was applied (else None). Surfaced as mean_eos_logprob so a run's rising termination is visible.
|
|
1220
1301
|
eos_logprob: float | None = None
|
|
1302
|
+
# Detached mean completion entropy (nats) of this sample's student predictive distributions
|
|
1303
|
+
# (distilled samples only, else None). Surfaced as opd/entropy + mean_student_entropy so the
|
|
1304
|
+
# reverse-KL entropy collapse behind the small-model greedy repetition loop is visible in-band.
|
|
1305
|
+
entropy: float | None = None
|
|
1221
1306
|
|
|
1222
1307
|
|
|
1223
1308
|
@dataclass(frozen=True)
|
|
@@ -1422,7 +1507,9 @@ def _score_many(
|
|
|
1422
1507
|
for _ in pendings
|
|
1423
1508
|
]
|
|
1424
1509
|
return [_ScoreResult(teacher_toks=toks, status="ok") for toks in scored]
|
|
1425
|
-
return [
|
|
1510
|
+
return [
|
|
1511
|
+
_ScoreResult(status="error", error="teacher batch attempts exhausted") for _ in pendings
|
|
1512
|
+
]
|
|
1426
1513
|
|
|
1427
1514
|
|
|
1428
1515
|
@dataclass(frozen=True)
|
|
@@ -1508,12 +1595,10 @@ def _gkd_loss_from_logps(sp_t, groups, kl_coef=1.0):
|
|
|
1508
1595
|
)
|
|
1509
1596
|
student_group_logsum = sp_det.new_zeros(len(prepared.group_lengths))
|
|
1510
1597
|
student_group_logsum.index_add_(0, group_ids_t, sp_det.index_select(0, flat_idx_t))
|
|
1511
|
-
teacher_logsum_t = torch.tensor(
|
|
1512
|
-
|
|
1598
|
+
teacher_logsum_t = torch.tensor(prepared.teacher_logsums, dtype=sp_t.dtype, device=sp_t.device)
|
|
1599
|
+
coeffs = (
|
|
1600
|
+
kl_coef * (student_group_logsum - teacher_logsum_t) / group_lengths_t.to(dtype=sp_t.dtype)
|
|
1513
1601
|
)
|
|
1514
|
-
coeffs = kl_coef * (
|
|
1515
|
-
student_group_logsum - teacher_logsum_t
|
|
1516
|
-
) / group_lengths_t.to(dtype=sp_t.dtype)
|
|
1517
1602
|
coeff_vec = coeffs.index_select(0, group_ids_t)
|
|
1518
1603
|
sp_sel = sp_t.index_select(0, flat_idx_t)
|
|
1519
1604
|
return (coeff_vec * sp_sel).mean()
|
|
@@ -1609,6 +1694,73 @@ def _eos_reinforce_term(
|
|
|
1609
1694
|
return (-float(eos_coef)) * logp, float(logp.detach())
|
|
1610
1695
|
|
|
1611
1696
|
|
|
1697
|
+
# Row-chunk size for _entropy_floor_term's full-vocab passes: 128 rows x ~150k vocab x 3 fp32
|
|
1698
|
+
# temporaries ~= 250 MB transient, comfortably inside the existing dense-logits headroom.
|
|
1699
|
+
_ENTROPY_CHUNK_ROWS = 128
|
|
1700
|
+
|
|
1701
|
+
|
|
1702
|
+
def _entropy_floor_term(rows, coef: float, floor: float):
|
|
1703
|
+
"""Student-side entropy floor: hinge the per-sample MEAN completion entropy (nats) at ``floor``.
|
|
1704
|
+
|
|
1705
|
+
Reverse-KL distillation is mode-seeking: on small students (<=2b) it progressively over-sharpens
|
|
1706
|
+
the next-token distribution over training steps until greedy (temperature=0) serving — the
|
|
1707
|
+
deploy default — falls into a repetition attractor and never emits EOS (observed on the 2b:
|
|
1708
|
+
step-20 checkpoint clean, step-63 loops on 80% of a 200-row eval; severity scales with
|
|
1709
|
+
rank x steps / model size). The principled fix gates the KL direction on the TEACHER's per-token
|
|
1710
|
+
entropy (EOPD, arxiv 2603.07079), but flash's teacher path cannot express it: Fireworks
|
|
1711
|
+
echo-scoring returns only realized-token scalars (no top-k, no entropy) and teacher/student are
|
|
1712
|
+
cross-tokenizer. So regularize the STUDENT's own distribution — local, same-vocab, already
|
|
1713
|
+
computed for the reverse-KL loss — to keep its entropy from collapsing.
|
|
1714
|
+
|
|
1715
|
+
The hinge is on the per-sample MEAN, not per token: individual tokens are often legitimately
|
|
1716
|
+
near-deterministic (syntax, format, ``</think>``), and a per-token floor would push every
|
|
1717
|
+
confident token up, fighting correct behaviour everywhere. The observed pathology is
|
|
1718
|
+
distribution-wide drift (mean entropy falls monotonically over OPD steps), which the mean hinge
|
|
1719
|
+
targets exactly. Like the terminal-EOS controller above, it is SELF-LIMITING: zero loss and zero
|
|
1720
|
+
gradient while the mean sits at/above the floor, so students that don't collapse (4b/9b, or the
|
|
1721
|
+
2b's early steps) pay nothing.
|
|
1722
|
+
|
|
1723
|
+
``rows`` is the same ``[completion_len, vocab]`` logits slice the reverse-KL loss consumes.
|
|
1724
|
+
Returns ``(term_or_none, mean_entropy)``: the differentiable addend
|
|
1725
|
+
``coef * (floor - mean_entropy)`` when the hinge is active (else ``None``), plus the detached
|
|
1726
|
+
mean entropy in nats for logging (always computed, even with coef 0, so a run's entropy
|
|
1727
|
+
trajectory is visible in-band — serving exposes no logprobs to measure it after the fact).
|
|
1728
|
+
|
|
1729
|
+
MEMORY: entropy needs the full-vocab distribution, and an unchunked fp32 log_softmax + exp over
|
|
1730
|
+
``[completion_len, vocab]`` would materialize multiple ~GB temporaries per sample — an
|
|
1731
|
+
unbudgeted addition to the dense-logits peak _opd_loss_microbatch_size was sized for, EVEN WITH
|
|
1732
|
+
THE FEATURE DISABLED (codex[bot]). Both passes therefore run in _ENTROPY_CHUNK_ROWS-row chunks,
|
|
1733
|
+
bounding the detached (logging) pass to a ~chunk x vocab transient. The AUTOGRAD pass still
|
|
1734
|
+
retains ~2 fp32 ``[completion_len, vocab]`` buffers for backward — but only for samples the
|
|
1735
|
+
hinge is actively penalizing (coef > 0 AND mean entropy below the floor), i.e. the opt-in
|
|
1736
|
+
experimental case, never the default path."""
|
|
1737
|
+
import torch
|
|
1738
|
+
import torch.nn.functional as F
|
|
1739
|
+
|
|
1740
|
+
if rows is None or rows.shape[0] == 0:
|
|
1741
|
+
return None, None
|
|
1742
|
+
n = rows.shape[0]
|
|
1743
|
+
|
|
1744
|
+
def _chunked_entropy_sum(grad: bool):
|
|
1745
|
+
total = None
|
|
1746
|
+
for i in range(0, n, _ENTROPY_CHUNK_ROWS):
|
|
1747
|
+
lp = F.log_softmax(rows[i : i + _ENTROPY_CHUNK_ROWS].float(), dim=-1)
|
|
1748
|
+
part = -(lp.exp() * lp).sum()
|
|
1749
|
+
if not grad:
|
|
1750
|
+
part = float(part)
|
|
1751
|
+
total = part if total is None else total + part
|
|
1752
|
+
return total
|
|
1753
|
+
|
|
1754
|
+
# detached pass first: cheap logging value, and the gate that decides whether the (autograd)
|
|
1755
|
+
# penalty pass is needed at all — at/above the floor the hinge is identically zero.
|
|
1756
|
+
with torch.no_grad():
|
|
1757
|
+
mean_entropy = _chunked_entropy_sum(grad=False) / n
|
|
1758
|
+
if coef <= 0 or mean_entropy >= floor:
|
|
1759
|
+
return None, mean_entropy
|
|
1760
|
+
ent = _chunked_entropy_sum(grad=True) / n
|
|
1761
|
+
return float(coef) * (float(floor) - ent), mean_entropy
|
|
1762
|
+
|
|
1763
|
+
|
|
1612
1764
|
# Terminal-EOS reinforcement is a PROPORTIONAL CONTROLLER, not a constant push. #482 added the EOS
|
|
1613
1765
|
# behaviour-cloning term on every distilled (cleanly-terminated) rollout unconditionally; because it
|
|
1614
1766
|
# raises the SHARED eos output logit — which governs P(eos) at EVERY position, not just the terminal
|
|
@@ -1619,7 +1771,9 @@ def _eos_reinforce_term(
|
|
|
1619
1771
|
# rollouts, then an all-empty collapse by step ~55/63. Fix: scale the coef by how much the student is
|
|
1620
1772
|
# CURRENTLY failing to terminate (an EMA of the per-rollout truncation rate), so a run that stops
|
|
1621
1773
|
# reliably gets ~zero EOS push and cannot ratchet, while genuine runaway still drives full reinforcement.
|
|
1622
|
-
_EOS_RUNAWAY_LO =
|
|
1774
|
+
_EOS_RUNAWAY_LO = (
|
|
1775
|
+
0.02 # truncation rate <= this: student stops fine -> NO reinforcement (no ratchet)
|
|
1776
|
+
)
|
|
1623
1777
|
_EOS_RUNAWAY_HI = 0.15 # truncation rate >= this: full opd_eos_loss_coef (clear runaway to correct)
|
|
1624
1778
|
_EOS_TRUNC_EMA_DECAY = 0.97 # per-rollout EMA (~30-rollout window): responsive yet smoothed
|
|
1625
1779
|
|
|
@@ -1636,6 +1790,30 @@ def _runaway_eos_scale(runaway_rate: float) -> float:
|
|
|
1636
1790
|
return (runaway_rate - _EOS_RUNAWAY_LO) / (_EOS_RUNAWAY_HI - _EOS_RUNAWAY_LO)
|
|
1637
1791
|
|
|
1638
1792
|
|
|
1793
|
+
# _runaway_eos_scale ramps the coef UP on runaway, but on its own it CANNOT stop an empty-collapse when
|
|
1794
|
+
# runaway is extreme (base-model / cold start): truncation stays high the whole run so the coef is pinned
|
|
1795
|
+
# at full, and an empty completion is truncated=False so it reads as "terminated" and never lowers the
|
|
1796
|
+
# ramp. Result (measured on a base-model 35B-thinking run, coef 0.5): 0 runaway but 58% of SAMPLED serves
|
|
1797
|
+
# come back empty. So damp the coef by an EMA of the empty-completion rate — the direct symptom of the
|
|
1798
|
+
# over-correction — giving two-sided negative feedback: reinforce EOS when running away, but SHUT THE
|
|
1799
|
+
# PUSH OFF the moment the student starts emitting eos first. Empties are always a failure (never
|
|
1800
|
+
# task-length like a truncation), so damp aggressively.
|
|
1801
|
+
_EOS_OVERCORR_LO = 0.01 # empty-rate EMA <= this: no damping (full reinforcement per _runaway_eos_scale)
|
|
1802
|
+
_EOS_OVERCORR_HI = 0.05 # empty-rate EMA >= this: reinforcement fully suppressed (student over-stops)
|
|
1803
|
+
|
|
1804
|
+
|
|
1805
|
+
def _overcorrection_damp(empty_rate: float) -> float:
|
|
1806
|
+
"""Fraction of the runaway-scaled EOS coef to KEEP given the CURRENT empty-completion rate (EMA).
|
|
1807
|
+
1.0 while the student isn't collapsing to empty (rate <= _EOS_OVERCORR_LO); ramps linearly to 0.0 by
|
|
1808
|
+
_EOS_OVERCORR_HI so the terminal-EOS push shuts off before it ratchets P(eos | position 0) past the
|
|
1809
|
+
sampling threshold. Composes multiplicatively with _runaway_eos_scale."""
|
|
1810
|
+
if empty_rate <= _EOS_OVERCORR_LO:
|
|
1811
|
+
return 1.0
|
|
1812
|
+
if empty_rate >= _EOS_OVERCORR_HI:
|
|
1813
|
+
return 0.0
|
|
1814
|
+
return 1.0 - (empty_rate - _EOS_OVERCORR_LO) / (_EOS_OVERCORR_HI - _EOS_OVERCORR_LO)
|
|
1815
|
+
|
|
1816
|
+
|
|
1639
1817
|
def _resolve_samples_batched(
|
|
1640
1818
|
model,
|
|
1641
1819
|
tok,
|
|
@@ -1646,6 +1824,7 @@ def _resolve_samples_batched(
|
|
|
1646
1824
|
*,
|
|
1647
1825
|
backward_scale: float | None = None,
|
|
1648
1826
|
runaway_rate: float = 1.0,
|
|
1827
|
+
overcorr_rate: float = 0.0,
|
|
1649
1828
|
) -> list[SampleResult]:
|
|
1650
1829
|
import torch
|
|
1651
1830
|
|
|
@@ -1657,11 +1836,16 @@ def _resolve_samples_batched(
|
|
|
1657
1836
|
# don't change across the chunk. getattr defaults keep bare test stand-ins (SimpleNamespace knobs)
|
|
1658
1837
|
# and eos-less fake tokenizers working with reinforcement OFF.
|
|
1659
1838
|
eos_coef = float(getattr(knobs, "eos_loss_coef", 0.0) or 0.0)
|
|
1660
|
-
#
|
|
1661
|
-
# terminate (
|
|
1662
|
-
#
|
|
1663
|
-
#
|
|
1664
|
-
|
|
1839
|
+
# Two-sided controller: reinforce the terminal EOS in proportion to the student's CURRENT failure to
|
|
1840
|
+
# terminate (_runaway_eos_scale), then DAMP by its current empty-completion rate (_overcorrection_damp)
|
|
1841
|
+
# so the push shuts off if it starts collapsing rollouts to empty. A cleanly-terminating run -> ~0
|
|
1842
|
+
# push; genuine runaway -> full push UNLESS that push is already producing empties. Defaults
|
|
1843
|
+
# runaway_rate=1.0 / overcorr_rate=0.0 keep bare callers (tests) at full strength = pre-controller.
|
|
1844
|
+
eos_coef *= _runaway_eos_scale(runaway_rate) * _overcorrection_damp(overcorr_rate)
|
|
1845
|
+
# student entropy floor (see _entropy_floor_term). getattr defaults keep bare test stand-ins
|
|
1846
|
+
# working with the penalty OFF; the detached mean entropy is computed either way for logging.
|
|
1847
|
+
ent_coef = float(getattr(knobs, "entropy_floor_coef", 0.0) or 0.0)
|
|
1848
|
+
ent_floor = float(getattr(knobs, "entropy_floor", 0.0) or 0.0)
|
|
1665
1849
|
eos_stop_sequences = tuple(getattr(knobs, "stop_sequences", ()) or ())
|
|
1666
1850
|
eos_ids = _generation_eos_ids(model, tok) if eos_coef > 0 else frozenset()
|
|
1667
1851
|
eos_primary = _primary_eos_id(tok, eos_ids) if eos_coef > 0 else None
|
|
@@ -1726,13 +1910,9 @@ def _resolve_samples_batched(
|
|
|
1726
1910
|
seqs = [list(p.prompt_ids) + list(p.student_ids) for p in chunk]
|
|
1727
1911
|
max_len = max(len(seq) for seq in seqs)
|
|
1728
1912
|
input_ids = torch.full((len(seqs), max_len), pad_id, dtype=torch.long, device=device)
|
|
1729
|
-
attention_mask = torch.zeros(
|
|
1730
|
-
(len(seqs), max_len), dtype=torch.long, device=device
|
|
1731
|
-
)
|
|
1913
|
+
attention_mask = torch.zeros((len(seqs), max_len), dtype=torch.long, device=device)
|
|
1732
1914
|
for row, seq in enumerate(seqs):
|
|
1733
|
-
input_ids[row, : len(seq)] = torch.tensor(
|
|
1734
|
-
seq, dtype=torch.long, device=device
|
|
1735
|
-
)
|
|
1915
|
+
input_ids[row, : len(seq)] = torch.tensor(seq, dtype=torch.long, device=device)
|
|
1736
1916
|
attention_mask[row, : len(seq)] = 1
|
|
1737
1917
|
logits = _forward_logits(model, input_ids, attention_mask)
|
|
1738
1918
|
losses = []
|
|
@@ -1771,6 +1951,12 @@ def _resolve_samples_batched(
|
|
|
1771
1951
|
if eos_out is not None:
|
|
1772
1952
|
eos_term, eos_logprob = eos_out
|
|
1773
1953
|
loss = loss + eos_term
|
|
1954
|
+
# entropy floor on the SAME rows the reverse-KL consumed; added to distilled
|
|
1955
|
+
# samples only (loss is not None), so skip accounting is unchanged. the detached
|
|
1956
|
+
# mean entropy is logged even when the penalty is off/inactive.
|
|
1957
|
+
ent_term, entropy = _entropy_floor_term(rows, ent_coef, ent_floor)
|
|
1958
|
+
if ent_term is not None:
|
|
1959
|
+
loss = loss + ent_term
|
|
1774
1960
|
loss_for_result = loss
|
|
1775
1961
|
if backward_scale is not None:
|
|
1776
1962
|
losses.append(loss)
|
|
@@ -1783,6 +1969,7 @@ def _resolve_samples_batched(
|
|
|
1783
1969
|
teacher_tokens=p.teacher_tokens,
|
|
1784
1970
|
group_granularity=p.group_granularity,
|
|
1785
1971
|
eos_logprob=eos_logprob,
|
|
1972
|
+
entropy=entropy,
|
|
1786
1973
|
)
|
|
1787
1974
|
if losses:
|
|
1788
1975
|
(sum(losses) * float(backward_scale)).backward()
|
|
@@ -196,8 +196,12 @@ def run_sft():
|
|
|
196
196
|
from flash.engine.vram import sft_grad_accum
|
|
197
197
|
|
|
198
198
|
sft_lr = _train_opt("learning_rate", RECIPE.sft.learning_rate)
|
|
199
|
+
# Total SFT sequence budget (prompt + completion), honoring [train] max_context_tokens like the
|
|
200
|
+
# cost/preflight path (flash.cost.spec._sft_seq_len); unset -> the tuned recipe cap. Read the
|
|
201
|
+
# RENAMED knob: reading a stale "max_length" here made getattr silently return the 1024 default.
|
|
199
202
|
sft_max_len = _train_opt(
|
|
200
|
-
"
|
|
203
|
+
"max_context_tokens",
|
|
204
|
+
RECIPE.sft.max_seq_len_thinking if _w.THINKING else RECIPE.sft.max_seq_len,
|
|
201
205
|
)
|
|
202
206
|
with liveness_heartbeat("sft_pretokenizing"):
|
|
203
207
|
texts, _pretok, _dropped = _pretokenize_completion_only(texts, tok, sft_max_len)
|
|
@@ -208,6 +208,8 @@ _TRAIN_KEYS = frozenset(
|
|
|
208
208
|
"advantage_clip",
|
|
209
209
|
"thinking_length_penalty_coef",
|
|
210
210
|
"opd_eos_loss_coef",
|
|
211
|
+
"opd_entropy_floor_coef",
|
|
212
|
+
"opd_entropy_floor",
|
|
211
213
|
"teacher_model",
|
|
212
214
|
"stop_sequences",
|
|
213
215
|
"structured_outputs",
|
|
@@ -216,6 +218,7 @@ _TRAIN_KEYS = frozenset(
|
|
|
216
218
|
}
|
|
217
219
|
)
|
|
218
220
|
|
|
221
|
+
|
|
219
222
|
def spec_from_dict(raw: dict[str, Any], run_id: str | None = None) -> JobSpec:
|
|
220
223
|
# Only reject table-valued unknowns — callers pass harmless scalar flags like dry_run alongside spec.
|
|
221
224
|
unknown = sorted(k for k in set(raw) - _TOP_LEVEL_KEYS if isinstance(raw[k], dict))
|
|
@@ -356,6 +359,10 @@ def spec_from_dict(raw: dict[str, Any], run_id: str | None = None) -> JobSpec:
|
|
|
356
359
|
),
|
|
357
360
|
# OPD-only terminal-EOS reinforcement weight; 0 disables. None -> recipe default (0.5).
|
|
358
361
|
opd_eos_loss_coef=_train_float(train_raw, "opd_eos_loss_coef", minimum=0.0),
|
|
362
|
+
# OPD-only student entropy floor (hinge weight + target nats); coef 0 disables the term.
|
|
363
|
+
# None -> recipe defaults (0.0 / 0.5, i.e. off until validated).
|
|
364
|
+
opd_entropy_floor_coef=_train_float(train_raw, "opd_entropy_floor_coef", minimum=0.0),
|
|
365
|
+
opd_entropy_floor=_train_float(train_raw, "opd_entropy_floor", minimum=0.0),
|
|
359
366
|
# OPD-only managed teacher alias, validated against the allow-list; "" -> default GLM 5.2.
|
|
360
367
|
teacher_model=_train_teacher(train_raw),
|
|
361
368
|
stop_sequences=_train_stops(train_raw),
|
|
@@ -11,6 +11,7 @@ from dataclasses import asdict, dataclass
|
|
|
11
11
|
import httpx
|
|
12
12
|
|
|
13
13
|
from flash._logging import get_logger
|
|
14
|
+
from flash.engine.structured_outputs import parse_structured_outputs
|
|
14
15
|
from flash.lora_rank import rank_from_adapter_config
|
|
15
16
|
|
|
16
17
|
logger = get_logger(__name__)
|
|
@@ -263,6 +264,34 @@ def adapter_artifact_lora_rank(hf_repo: str, subfolder: str) -> int:
|
|
|
263
264
|
return rank_from_adapter_config(config, source=f"{hf_repo}:{filename}")
|
|
264
265
|
|
|
265
266
|
|
|
267
|
+
def _structured_outputs_body(
|
|
268
|
+
run_id: str, structured_outputs: str, *, thinking: bool
|
|
269
|
+
) -> dict | None:
|
|
270
|
+
"""The per-adapter structured-outputs serving default for the registration body, or None.
|
|
271
|
+
|
|
272
|
+
Returns the parsed canonical StructuredOutputsParams-kwargs dict when the run configured a
|
|
273
|
+
constraint AND is non-thinking; None otherwise (no default is registered). Non-thinking only:
|
|
274
|
+
serving sets no reasoning-parser, so a per-adapter grammar would bind from the very first
|
|
275
|
+
generated token and force a thinking adapter's ``<think>`` phase to open the schema (e.g. ``{``)
|
|
276
|
+
— strictly worse than serving it unconstrained. Training stays compatible with thinking via a
|
|
277
|
+
DEFERRED grammar (``reasoning_parser="deepseek_r1"`` holds the constraint until ``</think>``),
|
|
278
|
+
but serving has no such deferral, so we skip the serve-time default for thinking runs rather
|
|
279
|
+
than break reasoning. Raises ValueError on a corrupt spec — a wiring bug, since
|
|
280
|
+
``[train].structured_outputs`` is already canonicalized at run creation (schema/fields.py), so
|
|
281
|
+
this cannot fire for a real run; it fails loudly rather than ever silently serving unconstrained."""
|
|
282
|
+
if not structured_outputs:
|
|
283
|
+
return None
|
|
284
|
+
if thinking:
|
|
285
|
+
logger.info(
|
|
286
|
+
"adapter %s trained with structured_outputs but thinking=True; not registering a "
|
|
287
|
+
"serving guided-decoding default — serving has no reasoning-parser deferral, so it "
|
|
288
|
+
"would bind the grammar from token 0 and suppress the <think> phase",
|
|
289
|
+
run_id,
|
|
290
|
+
)
|
|
291
|
+
return None
|
|
292
|
+
return parse_structured_outputs(structured_outputs)
|
|
293
|
+
|
|
294
|
+
|
|
266
295
|
def deploy_adapter(
|
|
267
296
|
run_id: str,
|
|
268
297
|
model: str,
|
|
@@ -272,9 +301,18 @@ def deploy_adapter(
|
|
|
272
301
|
dry_run: bool = False,
|
|
273
302
|
lora_rank: int = 32,
|
|
274
303
|
thinking: bool = False,
|
|
304
|
+
structured_outputs: str = "",
|
|
275
305
|
org_id: str | None = None,
|
|
276
306
|
) -> Deployment:
|
|
277
|
-
"""Register the trained adapter with the freesolo serving app.
|
|
307
|
+
"""Register the trained adapter with the freesolo serving app.
|
|
308
|
+
|
|
309
|
+
``structured_outputs`` is the run's ``[train].structured_outputs`` spec (canonical
|
|
310
|
+
StructuredOutputsParams-kwargs JSON, or "" for none). When set, it is registered as the
|
|
311
|
+
adapter's per-adapter guided-decoding DEFAULT so every serve request is constrained by the
|
|
312
|
+
SAME grammar the adapter trained under — otherwise a structured-outputs run drifts at
|
|
313
|
+
unconstrained serving (train/serve exposure bias: it over-generates arrays that truncate to
|
|
314
|
+
invalid JSON and emits keys the training grammar had masked). See ``_structured_outputs_body``.
|
|
315
|
+
"""
|
|
278
316
|
validate_serving_lora_rank(model, lora_rank, rank_source="configured train.lora_rank")
|
|
279
317
|
subfolder = f"{adapter_prefix}/adapter"
|
|
280
318
|
if not dry_run:
|
|
@@ -298,6 +336,9 @@ def deploy_adapter(
|
|
|
298
336
|
# Preserves thinking parity: without this, Qwen3.5 defaults to thinking ON regardless of training.
|
|
299
337
|
"thinking": bool(thinking),
|
|
300
338
|
}
|
|
339
|
+
so_default = _structured_outputs_body(run_id, structured_outputs, thinking=thinking)
|
|
340
|
+
if so_default is not None:
|
|
341
|
+
body["structured_outputs"] = so_default
|
|
301
342
|
normalized_org_id = (org_id or "").strip()
|
|
302
343
|
if normalized_org_id:
|
|
303
344
|
body["org_id"] = normalized_org_id
|
|
@@ -386,6 +386,10 @@ def deploy(run_id: str, key: Annotated[dict, Depends(require_key)], payload: dic
|
|
|
386
386
|
"lora_rank": spec.train.lora_rank,
|
|
387
387
|
# a run trained with thinking serves with thinking (per-run parity)
|
|
388
388
|
"thinking": spec.thinking,
|
|
389
|
+
# a run trained with structured_outputs serves under the SAME grammar (guided-decoding
|
|
390
|
+
# parity), so it doesn't drift at unconstrained serving; deploy_adapter registers it as
|
|
391
|
+
# the per-adapter default (non-thinking runs only — see _structured_outputs_body).
|
|
392
|
+
"structured_outputs": spec.train.structured_outputs,
|
|
389
393
|
"org_id": deploy_org_id,
|
|
390
394
|
}
|
|
391
395
|
if dry_run:
|
|
@@ -125,6 +125,11 @@ class TrainSpec:
|
|
|
125
125
|
# OPD only: weight of the terminal-EOS behaviour-cloning term (recipe default when None). 0
|
|
126
126
|
# disables it. Raise it for a student that keeps running past the length cap without emitting EOS.
|
|
127
127
|
opd_eos_loss_coef: float | None = None
|
|
128
|
+
# OPD only: student-side entropy floor — weight and target (nats) of the hinge on the per-sample
|
|
129
|
+
# mean completion entropy (recipe defaults when None; coef 0 disables the term). counteracts
|
|
130
|
+
# reverse-KL over-sharpening on small students (the greedy repetition loop at temp=0).
|
|
131
|
+
opd_entropy_floor_coef: float | None = None
|
|
132
|
+
opd_entropy_floor: float | None = None
|
|
128
133
|
# OPD only: the managed teacher to distil from, stored as its resolved Fireworks model id (parse
|
|
129
134
|
# canonicalizes any alias / spaced / raw-id form via recipe.resolve_teacher, e.g. "glm-5.2" =>
|
|
130
135
|
# "accounts/fireworks/models/glm-5p2"). "" => the recipe default (GLM 5.2).
|
|
@@ -218,6 +223,8 @@ class JobSpec:
|
|
|
218
223
|
advantage_clip=_opt_float(train.get("advantage_clip")),
|
|
219
224
|
thinking_length_penalty_coef=_opt_float(train.get("thinking_length_penalty_coef")),
|
|
220
225
|
opd_eos_loss_coef=_opt_float(train.get("opd_eos_loss_coef")),
|
|
226
|
+
opd_entropy_floor_coef=_opt_float(train.get("opd_entropy_floor_coef")),
|
|
227
|
+
opd_entropy_floor=_opt_float(train.get("opd_entropy_floor")),
|
|
221
228
|
teacher_model=str(train.get("teacher_model") or ""),
|
|
222
229
|
stop_sequences=_str_tuple(train.get("stop_sequences")),
|
|
223
230
|
structured_outputs=str(train.get("structured_outputs") or ""),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "freesolo-flash-dev"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.57"
|
|
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).
|
|
@@ -106,7 +106,7 @@ artifacts = ["flash/py.typed"]
|
|
|
106
106
|
# auto-published by .github/workflows/publish-dev.yml: a push to `dev` whose version isn't yet on
|
|
107
107
|
# PyPI publishes it; pushes that don't change it find it already published and no-op. So cutting a
|
|
108
108
|
# dev-channel release = bump this (and [project].version) and merge to `dev`.
|
|
109
|
-
version = "0.2.
|
|
109
|
+
version = "0.2.57"
|
|
110
110
|
|
|
111
111
|
[tool.pytest.ini_options]
|
|
112
112
|
pythonpath = ["."]
|