freesolo-flash-dev 0.2.45__tar.gz → 0.2.47__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.45 → freesolo_flash_dev-0.2.47}/PKG-INFO +2 -2
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/commands.py +28 -21
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/training_doc.py +12 -6
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cost/analytical.py +11 -9
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cost/facts.py +4 -7
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cost/spec.py +0 -3
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cost/types.py +7 -7
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/multiturn_rollout.py +174 -42
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/recipe.py +2 -3
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/__init__.py +6 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/gpu_setup.py +46 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/heartbeat.py +26 -6
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/hf.py +48 -20
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/opd.py +148 -42
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/rl.py +11 -2
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/sft.py +6 -1
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_worker.py +6 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/schema/__init__.py +5 -37
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/spec.py +0 -3
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/pyproject.toml +3 -3
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_algorithms.py +3 -2
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_commands.py +40 -12
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_analytical.py +23 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_flash_worker.py +33 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_multiturn_rollout.py +101 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_opd.py +267 -23
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_init_heartbeat.py +41 -1
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/uv.lock +1 -1
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.env.example +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/README.md +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/catalog.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/render.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/client/http.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/adapter.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/opd_gkd.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/teacher.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/tokenizer_align.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/adapter.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/loader.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/lora_rank.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/train/deps.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/api.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/runner/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/serve/deploy.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/app.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/conftest.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_charge_pricing.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_client.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_jobs.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_lora_rank_preflight.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_serve.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_server_api.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_sft_example_selection.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_spec_and_validation.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_train_context_preflight.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_vast_api.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_vl_warmstart_recombine.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_thinking.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: Flash — managed LoRA post-training (SFT/GRPO) for Freesolo environments, driven by the `flash` CLI
|
|
3
|
+
Version: 0.2.47
|
|
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
|
|
7
7
|
Author: Freesolo
|
|
@@ -210,14 +210,14 @@ A managed run should use the returned [environment] id from
|
|
|
210
210
|
This starter implements a tiny "guess the secret number" game so you can see the
|
|
211
211
|
episode hooks wired end-to-end. Replace it with your real task before a real run.
|
|
212
212
|
|
|
213
|
-
|
|
213
|
+
All three algorithms train off this file:
|
|
214
214
|
- GRPO (configs/rl.toml) rolls out full episodes and optimizes `score_episode`.
|
|
215
215
|
- SFT (configs/sft.toml) learns the gold trajectory. Provide it per row as
|
|
216
216
|
`output = {"messages": [...]}` (a full assistant/tool trajectory) or a scalar
|
|
217
217
|
`output` for a single gold assistant turn.
|
|
218
|
-
- OPD (configs/opd.toml)
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
- OPD (configs/opd.toml) rolls out each episode and distils EVERY assistant turn against
|
|
219
|
+
the Fireworks GLM teacher, conditioned on the transcript so far — the multi-turn
|
|
220
|
+
on-policy-distillation objective. The teacher key is platform-managed (nothing to set).
|
|
221
221
|
"""
|
|
222
222
|
|
|
223
223
|
from __future__ import annotations
|
|
@@ -492,32 +492,27 @@ def cmd_env_setup(args) -> int:
|
|
|
492
492
|
)
|
|
493
493
|
opd = Path("configs/opd.toml")
|
|
494
494
|
if not opd.exists():
|
|
495
|
-
# opd (on-policy distillation)
|
|
496
|
-
#
|
|
497
|
-
#
|
|
498
|
-
# so loudly in the multi-turn one so the config isn't a footgun.
|
|
495
|
+
# opd (on-policy distillation) works in BOTH modes: single-turn distils one sampled completion
|
|
496
|
+
# per prompt; multi-turn rolls out each episode and distils every assistant turn against the
|
|
497
|
+
# transcript so far. The scaffold differs only by a one-line note pointing at the mode.
|
|
499
498
|
opd_multiturn_note = (
|
|
500
|
-
"# NOTE: opd
|
|
501
|
-
"#
|
|
502
|
-
"# fail fast here. Use configs/rl.toml (grpo) or configs/sft.toml for multi-turn; keep opd for\n"
|
|
503
|
-
"# a single-turn env (`flash env setup --single-turn`).\n\n"
|
|
499
|
+
"# NOTE: opd rolls out each episode and distils EVERY assistant turn (conditioned on the\n"
|
|
500
|
+
"# transcript so far) against the Fireworks GLM teacher — the multi-turn distillation path.\n\n"
|
|
504
501
|
if multi_turn
|
|
505
502
|
else ""
|
|
506
503
|
)
|
|
507
504
|
opd.write_text(
|
|
508
505
|
f"{opd_multiturn_note}"
|
|
509
506
|
'model = "Qwen/Qwen3.5-4B"\n'
|
|
510
|
-
'algorithm = "opd" # on-policy distillation from
|
|
507
|
+
'algorithm = "opd" # on-policy distillation from the managed GLM 5.2 teacher\n\n'
|
|
511
508
|
"# Environment: upload this project folder with\n"
|
|
512
509
|
"# `flash env push --name my-env .`, then paste the returned id below.\n"
|
|
513
|
-
"#
|
|
510
|
+
"# The GLM 5.2 teacher and key are platform-managed — nothing to set up or export.\n"
|
|
514
511
|
"[environment]\n"
|
|
515
|
-
'id = ""\n'
|
|
516
|
-
'secrets = ["FIREWORKS_API_KEY"]\n\n'
|
|
512
|
+
'id = ""\n\n'
|
|
517
513
|
"[train]\n"
|
|
518
514
|
"steps = 100 # opd is step-driven (like GRPO)\n"
|
|
519
515
|
"lora_rank = 32\n"
|
|
520
|
-
'# teacher_model = "accounts/fireworks/models/glm-5p2" # Fireworks GLM teacher (default)\n'
|
|
521
516
|
"# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
|
|
522
517
|
"# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
|
|
523
518
|
)
|
|
@@ -842,13 +837,25 @@ def cmd_cancel(args) -> int:
|
|
|
842
837
|
else:
|
|
843
838
|
print(json.dumps(payload, indent=2))
|
|
844
839
|
if checkpoints:
|
|
845
|
-
|
|
840
|
+
# Best-effort hint (the cancel already succeeded), so never crash on a malformed checkpoint
|
|
841
|
+
# shape: coerce steps defensively — a dict missing 'step' or carrying a non-int must not raise
|
|
842
|
+
# a traceback here. Only surface the `step-N` deploy example when we recovered a real step.
|
|
843
|
+
steps = []
|
|
844
|
+
for c in checkpoints:
|
|
845
|
+
try:
|
|
846
|
+
steps.append(int(c["step"]))
|
|
847
|
+
except (KeyError, TypeError, ValueError):
|
|
848
|
+
continue
|
|
846
849
|
# stderr in the plain path so the machine-readable stdout JSON stays untouched.
|
|
847
850
|
out = sys.stdout if render.styled() else sys.stderr
|
|
848
|
-
|
|
851
|
+
base = (
|
|
849
852
|
f"{len(checkpoints)} deployable checkpoint(s) survive this cancel — list with "
|
|
850
|
-
f"`flash checkpoints {args.run_id}
|
|
851
|
-
|
|
853
|
+
f"`flash checkpoints {args.run_id}`"
|
|
854
|
+
)
|
|
855
|
+
msg = (
|
|
856
|
+
f"{base}, deploy one with `flash deploy {args.run_id}/step-{max(steps)}`."
|
|
857
|
+
if steps
|
|
858
|
+
else f"{base}."
|
|
852
859
|
)
|
|
853
860
|
print(render.note(msg) if render.styled() else msg, file=out)
|
|
854
861
|
return 0
|
|
@@ -377,20 +377,28 @@ SFT is **epoch-driven** (`epochs`); GRPO is **step-driven** (`steps`).
|
|
|
377
377
|
## On-policy distillation (`algorithm = "opd"`)
|
|
378
378
|
|
|
379
379
|
Pick distillation when a much stronger **teacher** model can grade your student's work
|
|
380
|
-
token-by-token. The student samples on-policy (like GRPO),
|
|
380
|
+
token-by-token. The student samples on-policy (like GRPO), the managed GLM 5.2 teacher scores
|
|
381
381
|
each of *its own* completions, and a dense per-token loss teaches the student to match the teacher —
|
|
382
382
|
far more sample-efficient than reward-based RL and with no reward to design. It is **step-driven**
|
|
383
383
|
(`steps`) and produces a LoRA served exactly like SFT.
|
|
384
384
|
|
|
385
|
-
- **
|
|
386
|
-
|
|
387
|
-
|
|
385
|
+
- **No teacher key or model override to set up.** The GLM 5.2 teacher and its Fireworks key are platform-managed: the
|
|
386
|
+
service supplies its own key to every opd run, so there is nothing to export or declare — an opd
|
|
387
|
+
run submits like any other. Bring-your-own teacher models and keys are not supported; a
|
|
388
|
+
`FIREWORKS_API_KEY` in your shell is simply ignored. The key is never stored in the spec or
|
|
389
|
+
needed at serving time.
|
|
388
390
|
- **The student (Qwen / MiniCPM / Kimi) and the teacher (GLM) have different tokenizers.** Flash
|
|
389
391
|
bridges the vocabulary mismatch with **groupwise reverse-KL** (the collinear-ai *spider* / Tinker
|
|
390
392
|
method): it aligns the two tokenizations by shared decoded-text spans and applies per-span reverse
|
|
391
393
|
KL using only realized-token logprobs — no vocabulary projection, so it covers every token exactly
|
|
392
394
|
and works for any student tokenizer. When the tokenizers happen to agree it reduces to plain
|
|
393
395
|
per-token reverse KL (Thinking Machines, *On-Policy Distillation*). Nothing to configure.
|
|
396
|
+
- **Works for multi-turn envs too.** Against an `EnvironmentMultiTurn`, opd rolls out each episode
|
|
397
|
+
(driving `step_episode` / observations just like GRPO) and distils EVERY assistant turn against the
|
|
398
|
+
teacher, each conditioned on the transcript up to that turn — the episode's total reverse-KL over
|
|
399
|
+
the student's generated tokens is the sum of its per-turn reverse-KLs. Env/observation tokens are
|
|
400
|
+
never distilled (they're context, not the student's output). Set `[train] max_length` to bound the
|
|
401
|
+
transcript; the teacher must cover it (GLM-5.2's context far exceeds the default budget).
|
|
394
402
|
- **Judge it like SFT.** Distillation logs a falling per-token loss; a low loss alone is not proof.
|
|
395
403
|
Keep a held-out split, `flash deploy` the adapter, and score it — confirm the student actually
|
|
396
404
|
moved toward the teacher's behavior, not just its surface tokens.
|
|
@@ -402,12 +410,10 @@ algorithm = "opd"
|
|
|
402
410
|
|
|
403
411
|
[environment]
|
|
404
412
|
id = "your-org/my-env"
|
|
405
|
-
secrets = ["FIREWORKS_API_KEY"]
|
|
406
413
|
|
|
407
414
|
[train]
|
|
408
415
|
steps = 100
|
|
409
416
|
lora_rank = 32
|
|
410
|
-
# teacher_model = "accounts/fireworks/models/glm-5p2" # Fireworks GLM teacher (default)
|
|
411
417
|
# kl_penalty_coef = 1.0 # reverse-KL scale
|
|
412
418
|
```
|
|
413
419
|
|
|
@@ -99,7 +99,7 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
|
99
99
|
peak = gpu_tflops(gpu) * 1e12 # FLOP/s
|
|
100
100
|
|
|
101
101
|
if n.is_opd:
|
|
102
|
-
# OPD step = on-policy student rollout (like GRPO) + remote teacher scoring (
|
|
102
|
+
# OPD step = on-policy student rollout (like GRPO) + remote teacher scoring (CONCURRENT
|
|
103
103
|
# Fireworks round-trips, replaces reward grading) + policy update (fwd+bwd only, NO local
|
|
104
104
|
# reference forward — the teacher is the API). Bill local compute on the FULL prompt+completion
|
|
105
105
|
# sequence (see _opd_step_shape), not completion-only, or long-prompt opd is underquoted.
|
|
@@ -107,10 +107,12 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
|
107
107
|
gen_s = (GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_DECODE)
|
|
108
108
|
update_s = (OPD_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_TRAIN)
|
|
109
109
|
teacher_lat = teacher_seconds_per_completion()
|
|
110
|
-
# run_opd scores
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
|
|
110
|
+
# run_opd's primary path scores a step's completions CONCURRENTLY over Fireworks with a fan-out
|
|
111
|
+
# cap of the step's OWN completion count (prompts_per_step * group_size, opd.py Phase 2), so
|
|
112
|
+
# every completion in a step is scored in ONE parallel wave — the teacher wall is a single
|
|
113
|
+
# latency, NOT the full serial sum (that describes only the CPU-test fallback that can't
|
|
114
|
+
# batch-generate). The teacher endpoint's rate limit is the real ceiling on this fan-out.
|
|
115
|
+
teacher_s = teacher_lat if completions else 0.0
|
|
114
116
|
return gen_s + teacher_s + update_s
|
|
115
117
|
|
|
116
118
|
if not n.is_grpo:
|
|
@@ -255,10 +257,10 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
|
|
|
255
257
|
train_seconds=train,
|
|
256
258
|
wall_clock_seconds=wall,
|
|
257
259
|
wall_capped=wall_capped,
|
|
258
|
-
# GPU (platform-billed) time only. OPD's teacher tokens are billed by Fireworks
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
260
|
+
# GPU (platform-billed) time only. OPD's teacher tokens are billed by Fireworks to the
|
|
261
|
+
# platform-managed teacher key — never through this GPU charge — so folding teacher_api_usd
|
|
262
|
+
# into the charged total would bill it a second time. Keep it itemized as a diagnostic
|
|
263
|
+
# instead (codex[bot]).
|
|
262
264
|
total_usd=train / 3600.0 * hourly,
|
|
263
265
|
teacher_api_usd=teacher_api_usd,
|
|
264
266
|
notes=_notes(config, raw_train, wall_capped, cap_s),
|
|
@@ -166,10 +166,9 @@ def reward_seconds_per_completion(override: float | None = None) -> float:
|
|
|
166
166
|
# NO FIREWORKS_API_KEY. Source: https://fireworks.ai/models/fireworks/glm-5p2 lists
|
|
167
167
|
# $1.40 / $0.14 / $4.40 (input / cached input / output) per 1M. opd echo-scores completions
|
|
168
168
|
# (max_tokens=0), so only INPUT tokens are billed (the teacher never generates) — but the table keeps
|
|
169
|
-
# both so a mispriced entry is obvious.
|
|
169
|
+
# both so a mispriced entry is obvious.
|
|
170
170
|
TEACHER_USD_PER_1M: dict[str, tuple[float, float]] = {
|
|
171
171
|
"accounts/fireworks/models/glm-5p2": (1.40, 4.40),
|
|
172
|
-
"accounts/fireworks/models/glm-5p1": (1.40, 4.40),
|
|
173
172
|
}
|
|
174
173
|
# Fireworks echo-scoring round-trip per completion (wall time, concurrency-bound like reward grading).
|
|
175
174
|
AVG_TEACHER_SECONDS_PER_COMPLETION = 2.0
|
|
@@ -178,11 +177,9 @@ AVG_TEACHER_SECONDS_PER_COMPLETION = 2.0
|
|
|
178
177
|
def teacher_price_per_1m(teacher_model: str) -> tuple[float, float]:
|
|
179
178
|
"""(input, output) $/1M tokens for a teacher model.
|
|
180
179
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
unknown non-default teacher falls back to the default teacher's rate (spec-parse already rejects
|
|
185
|
-
an unpriced teacher_model, so this is only a defensive backstop)."""
|
|
180
|
+
OPD uses the recipe's fixed GLM 5.2 teacher. ``teacher_model`` is accepted only for internal
|
|
181
|
+
diagnostics/tests; user configs cannot override it. Unknown values fall back defensively to
|
|
182
|
+
the fixed teacher rate."""
|
|
186
183
|
from flash.engine.recipe import RECIPE
|
|
187
184
|
|
|
188
185
|
default = TEACHER_USD_PER_1M[RECIPE.opd.teacher_model]
|
|
@@ -110,9 +110,6 @@ def runconfig_from_spec(spec) -> RunConfig:
|
|
|
110
110
|
provider="auto",
|
|
111
111
|
max_wall_seconds=g.max_wall_seconds,
|
|
112
112
|
environment=spec.environment.id or None,
|
|
113
|
-
# "" is the unset sentinel (RunConfig.teacher_model); facts.teacher_price_per_1m resolves it
|
|
114
|
-
# to the recipe default. Non-opd carries no teacher.
|
|
115
|
-
teacher_model=t.teacher_model if spec.algorithm == "opd" else "",
|
|
116
113
|
)
|
|
117
114
|
|
|
118
115
|
|
|
@@ -27,9 +27,8 @@ class RunConfig:
|
|
|
27
27
|
thinking: bool = False
|
|
28
28
|
# GRPO only: seconds to score one completion. None -> the single average grader latency.
|
|
29
29
|
reward_seconds_per_completion: float | None = None
|
|
30
|
-
# OPD only:
|
|
31
|
-
#
|
|
32
|
-
# teacher (RECIPE.opd.teacher_model) at the pricing boundary (facts.teacher_price_per_1m).
|
|
30
|
+
# OPD only: internal Fireworks teacher id for teacher-token diagnostics. User configs cannot
|
|
31
|
+
# override this; an empty value resolves to the recipe's fixed GLM 5.2 teacher.
|
|
33
32
|
teacher_model: str = ""
|
|
34
33
|
|
|
35
34
|
max_wall_seconds: int | None = None # wall cap (spec gpu.max_wall_seconds); None = 24h
|
|
@@ -150,8 +149,9 @@ class CostEstimate:
|
|
|
150
149
|
wall_capped: bool
|
|
151
150
|
total_usd: float
|
|
152
151
|
# opd only: external Fireworks GLM teacher token spend (0.0 for sft/grpo). Billed by Fireworks
|
|
153
|
-
#
|
|
154
|
-
#
|
|
152
|
+
# to the platform-managed teacher key (users don't supply one), tracked separately from the
|
|
153
|
+
# platform-billed GPU charge — so it is NOT part of total_usd; shown as its own itemized
|
|
154
|
+
# diagnostic line only.
|
|
155
155
|
teacher_api_usd: float = 0.0
|
|
156
156
|
notes: tuple[str, ...] = ()
|
|
157
157
|
|
|
@@ -181,8 +181,8 @@ class CostEstimate:
|
|
|
181
181
|
]
|
|
182
182
|
if self.teacher_api_usd > 0:
|
|
183
183
|
lines.append(
|
|
184
|
-
f"Teacher API: ${self.teacher_api_usd:.2f} (Fireworks GLM token spend on
|
|
185
|
-
"
|
|
184
|
+
f"Teacher API: ${self.teacher_api_usd:.2f} (Fireworks GLM token spend on the "
|
|
185
|
+
"platform-managed teacher key — tracked separately, NOT included in TOTAL)"
|
|
186
186
|
)
|
|
187
187
|
lines.append(f"TOTAL : ${self.total_usd:.2f}")
|
|
188
188
|
if self.notes:
|
|
@@ -27,6 +27,26 @@ class RolloutResult(TypedDict):
|
|
|
27
27
|
reward: float
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
class TurnRecord(TypedDict):
|
|
31
|
+
"""One assistant turn of a multi-turn episode, as :func:`rollout_one_records` emits it.
|
|
32
|
+
|
|
33
|
+
``prefix_ids`` are the student token ids of the whole transcript BEFORE this turn (initial prompt
|
|
34
|
+
+ every prior assistant turn + every inter-turn env "glue"), i.e. the exact on-policy context the
|
|
35
|
+
student sampled this turn's completion after. ``context_messages`` is the parallel message list at
|
|
36
|
+
the same point (prompt + prior turns + prior env replies), for callers that render a separate
|
|
37
|
+
teacher/scoring prompt from it. ``gen`` is whatever the injected ``generate`` callable returned for
|
|
38
|
+
this turn (opaque to the driver beyond the four attributes it reads: ``completion_ids``,
|
|
39
|
+
``completion_text``, ``truncated``, ``skip``) — e.g. OPD hands back its ``_GenResult`` so the
|
|
40
|
+
per-turn record feeds straight into the existing single-turn scoring/loss path. Distilling each
|
|
41
|
+
turn against ``prefix_ids`` is the multi-turn on-policy-distillation objective: the episode's total
|
|
42
|
+
reverse-KL over student-generated tokens is the sum of per-turn reverse-KLs, each conditioned on the
|
|
43
|
+
transcript so far."""
|
|
44
|
+
|
|
45
|
+
prefix_ids: list[int]
|
|
46
|
+
gen: object
|
|
47
|
+
context_messages: list[dict]
|
|
48
|
+
|
|
49
|
+
|
|
30
50
|
_ROLLOUT_FIELDS: tuple[str, ...] = (
|
|
31
51
|
"prompt_ids",
|
|
32
52
|
"completion_ids",
|
|
@@ -99,6 +119,20 @@ def index_collisions(rows: list[dict], prompt_of: Callable[[dict], object]) -> i
|
|
|
99
119
|
return len(rows) - len({_prompt_key(prompt_of(r)) for r in rows})
|
|
100
120
|
|
|
101
121
|
|
|
122
|
+
def _dedup_seam_terminator(prev_completion_ids: list[int], glue: list[int]) -> list[int]:
|
|
123
|
+
"""Collapse a duplicate turn terminator at the turn/env seam.
|
|
124
|
+
|
|
125
|
+
``env_glue`` leads with the assistant turn's terminator (e.g. ``<|im_end|>``), which a
|
|
126
|
+
naturally-stopped assistant turn also keeps as its final token — so the raw stream would carry two.
|
|
127
|
+
When the previous turn's last token equals ``glue[0]`` drop the glue copy, keeping the assistant's
|
|
128
|
+
own token (real logprob / model-generated) so the stream has exactly one terminator per turn,
|
|
129
|
+
matching the chat template + SFT transcripts. Shared by :func:`rollout_one`,
|
|
130
|
+
:func:`_advance_after_turn`, and :func:`rollout_one_records` so the three paths can't drift."""
|
|
131
|
+
if glue and prev_completion_ids and prev_completion_ids[-1] == glue[0]:
|
|
132
|
+
return glue[1:]
|
|
133
|
+
return glue
|
|
134
|
+
|
|
135
|
+
|
|
102
136
|
def rollout_one(
|
|
103
137
|
*,
|
|
104
138
|
example: dict,
|
|
@@ -152,13 +186,7 @@ def rollout_one(
|
|
|
152
186
|
if active_env.rollout_done(state, max_turns):
|
|
153
187
|
break
|
|
154
188
|
|
|
155
|
-
glue = env_glue(env_msgs)
|
|
156
|
-
# Collapse a duplicate turn terminator at the seam: env_glue leads with the assistant turn's
|
|
157
|
-
# terminator (e.g. <|im_end|>), which vLLM also keeps as the last token of a naturally-stopped
|
|
158
|
-
# turn — keep the assistant's own (env_mask=1, real logprob) over the env copy. See
|
|
159
|
-
# _advance_after_turn (the rollout_async twin) for the same fix.
|
|
160
|
-
if glue and completion_ids and completion_ids[-1] == glue[0]:
|
|
161
|
-
glue = glue[1:]
|
|
189
|
+
glue = _dedup_seam_terminator(completion_ids, env_glue(env_msgs))
|
|
162
190
|
if token_budget is not None and len(completion_ids) + len(glue) > token_budget:
|
|
163
191
|
break
|
|
164
192
|
completion_ids.extend(glue)
|
|
@@ -176,6 +204,102 @@ def rollout_one(
|
|
|
176
204
|
}
|
|
177
205
|
|
|
178
206
|
|
|
207
|
+
def rollout_one_records(
|
|
208
|
+
*,
|
|
209
|
+
example: dict,
|
|
210
|
+
active_env,
|
|
211
|
+
render: Callable[[list, bool], list[int]],
|
|
212
|
+
generate: Callable[[list, int], object],
|
|
213
|
+
env_glue: Callable[[list], list[int]],
|
|
214
|
+
max_turns: int,
|
|
215
|
+
per_turn_max_tokens: int,
|
|
216
|
+
engine_max_len: int | None = None,
|
|
217
|
+
on_turn_generated: Callable[[], None] | None = None,
|
|
218
|
+
) -> list[TurnRecord]:
|
|
219
|
+
"""Drive one multi-turn episode and return a per-turn :class:`TurnRecord` list (NOT a flat masked
|
|
220
|
+
sequence like :func:`rollout_one`). Used by OPD, which distils each assistant turn as an independent
|
|
221
|
+
single-turn sample conditioned on the transcript so far.
|
|
222
|
+
|
|
223
|
+
The env turn loop is identical to :func:`rollout_one` (``new_rollout_state`` → generate →
|
|
224
|
+
``record_model_turn`` → ``rollout_done`` → ``env_reply`` → glue), and the tokenizer-sensitive seam
|
|
225
|
+
dedup / engine-budget accounting reuse the same helpers, so the two paths can't drift. The
|
|
226
|
+
differences are all OPD-shaped:
|
|
227
|
+
|
|
228
|
+
- ``generate(prefix_ids, max_new)`` returns an OBJECT (opaque here) exposing ``completion_ids``
|
|
229
|
+
(``list[int] | None``), ``completion_text`` (``str``), ``truncated`` (``bool``) and ``skip``
|
|
230
|
+
(``bool``) — OPD passes its ``_GenResult`` from ``_generate_one`` (termination/trim/U+FFFD gates
|
|
231
|
+
already applied), so the record drops straight into the existing teacher-scoring + gkd-loss path.
|
|
232
|
+
- Every turn (including a truncated/empty one) is recorded so the caller counts it; a
|
|
233
|
+
truncated/skip turn ENDS the episode (a student that didn't terminate its turn, or emitted an
|
|
234
|
+
empty/invalid completion, can't meaningfully continue — and its bad turn is skipped, not
|
|
235
|
+
distilled, by the caller's ``_resolve_sample``).
|
|
236
|
+
- ``on_turn_generated`` (optional) fires AFTER each turn's generation to refresh the worker stall
|
|
237
|
+
clock and advance the sample counter — a many-turn episode is a long serial stretch of GPU
|
|
238
|
+
generates + teacher-less env steps that would otherwise emit no progress ping.
|
|
239
|
+
|
|
240
|
+
``prefix_ids`` and ``context_messages`` are snapshotted BEFORE generation, so each record carries
|
|
241
|
+
the exact student-token and message context the teacher must condition on for that turn.
|
|
242
|
+
"""
|
|
243
|
+
state = active_env.new_rollout_state(example)
|
|
244
|
+
initial_messages = state.get("prompt") or state.get("messages")
|
|
245
|
+
if not isinstance(initial_messages, list):
|
|
246
|
+
raise KeyError("multi-turn rollout state must include prompt or messages")
|
|
247
|
+
messages = [dict(m) for m in initial_messages]
|
|
248
|
+
prompt_ids = render(messages, True)
|
|
249
|
+
cur_ids = list(prompt_ids) # invariant: cur_ids == prompt_ids + completion tokens so far
|
|
250
|
+
token_budget = (engine_max_len - len(prompt_ids) - 8) if engine_max_len else None
|
|
251
|
+
records: list[TurnRecord] = []
|
|
252
|
+
|
|
253
|
+
turns = 0
|
|
254
|
+
while True:
|
|
255
|
+
completion_so_far = len(cur_ids) - len(prompt_ids)
|
|
256
|
+
max_new = per_turn_max_tokens
|
|
257
|
+
if token_budget is not None:
|
|
258
|
+
remaining = token_budget - completion_so_far
|
|
259
|
+
if remaining <= 0:
|
|
260
|
+
break
|
|
261
|
+
max_new = min(max_new, remaining)
|
|
262
|
+
# Snapshot the student-token prefix and message context BEFORE generation: this is what the
|
|
263
|
+
# teacher must condition on to score THIS turn (the transcript up to, but excluding, the turn).
|
|
264
|
+
prefix_ids = list(cur_ids)
|
|
265
|
+
context_messages = [dict(m) for m in messages]
|
|
266
|
+
gen = generate(prefix_ids, max(1, max_new))
|
|
267
|
+
if on_turn_generated is not None:
|
|
268
|
+
on_turn_generated()
|
|
269
|
+
records.append(
|
|
270
|
+
{"prefix_ids": prefix_ids, "gen": gen, "context_messages": context_messages}
|
|
271
|
+
)
|
|
272
|
+
text = getattr(gen, "completion_text", "") or ""
|
|
273
|
+
active_env.record_model_turn(state, text)
|
|
274
|
+
messages.append({"role": "assistant", "content": text})
|
|
275
|
+
turns += 1
|
|
276
|
+
# A turn that didn't terminate naturally (truncated) or produced no usable text (skip) ends the
|
|
277
|
+
# episode: it's recorded (counted) but not distilled, and continuing from a broken turn is
|
|
278
|
+
# pointless (the student can't end its turn / said nothing).
|
|
279
|
+
if getattr(gen, "truncated", False) or getattr(gen, "skip", False):
|
|
280
|
+
break
|
|
281
|
+
asst_ids = getattr(gen, "completion_ids", None) or []
|
|
282
|
+
cur_ids.extend(asst_ids)
|
|
283
|
+
completion_so_far = len(cur_ids) - len(prompt_ids)
|
|
284
|
+
if token_budget is not None and completion_so_far >= token_budget:
|
|
285
|
+
break
|
|
286
|
+
if turns >= max_turns or active_env.rollout_done(state, max_turns):
|
|
287
|
+
break
|
|
288
|
+
env_msgs = active_env.env_reply(messages, state)
|
|
289
|
+
if not env_msgs:
|
|
290
|
+
break
|
|
291
|
+
messages.extend(env_msgs)
|
|
292
|
+
# Don't append glue if the env step finished — no next model turn.
|
|
293
|
+
if active_env.rollout_done(state, max_turns):
|
|
294
|
+
break
|
|
295
|
+
glue = _dedup_seam_terminator(asst_ids, env_glue(env_msgs))
|
|
296
|
+
if token_budget is not None and completion_so_far + len(glue) > token_budget:
|
|
297
|
+
break
|
|
298
|
+
cur_ids.extend(glue)
|
|
299
|
+
|
|
300
|
+
return records
|
|
301
|
+
|
|
302
|
+
|
|
179
303
|
class _RolloutState:
|
|
180
304
|
"""Mutable per-rollout accumulator for :func:`rollout_async` (mirrors :func:`rollout_one` locals)."""
|
|
181
305
|
|
|
@@ -248,13 +372,7 @@ def _advance_after_turn(
|
|
|
248
372
|
if active_env.rollout_done(r.state, max_turns):
|
|
249
373
|
r.done = True
|
|
250
374
|
return
|
|
251
|
-
glue = env_glue(env_msgs)
|
|
252
|
-
# env_glue's text starts at the assistant turn's terminator (e.g. <|im_end|>); vLLM also keeps that
|
|
253
|
-
# terminator as the final token of a naturally-stopped turn (it's in asst_ids). Collapse the duplicate
|
|
254
|
-
# at the seam so the stream has exactly one terminator per turn (matches the chat template + SFT
|
|
255
|
-
# transcripts), keeping the assistant's own token (env_mask=1, real logprob) over the env copy.
|
|
256
|
-
if glue and r.completion_ids and r.completion_ids[-1] == glue[0]:
|
|
257
|
-
glue = glue[1:]
|
|
375
|
+
glue = _dedup_seam_terminator(r.completion_ids, env_glue(env_msgs))
|
|
258
376
|
if r.budget is not None and len(r.completion_ids) + len(glue) > r.budget:
|
|
259
377
|
r.done = True
|
|
260
378
|
return
|
|
@@ -429,6 +547,47 @@ def render_message_ids(tok, messages, add_generation_prompt: bool, *, thinking:
|
|
|
429
547
|
return [int(t) for t in tok(text, add_special_tokens=False).input_ids]
|
|
430
548
|
|
|
431
549
|
|
|
550
|
+
def make_env_glue(tok, *, thinking: bool, cache_size: int = 8192) -> Callable[[list], list[int]]:
|
|
551
|
+
"""Build the inter-turn "glue" tokenizer used by both the GRPO rollout (:func:`build_rollout_func`)
|
|
552
|
+
and OPD's per-turn rollout (:func:`rollout_one_records`).
|
|
553
|
+
|
|
554
|
+
Given the env's reply messages for a turn, returns the token ids that sit BETWEEN the assistant
|
|
555
|
+
turn and the next assistant generation prompt (the turn terminator + the env/observation messages +
|
|
556
|
+
the next assistant header). Computed with the probe trick — render ``[{assistant: PROBE}, *env]``
|
|
557
|
+
with ``add_generation_prompt=True`` and take everything after the probe — so the history is never
|
|
558
|
+
re-rendered (Qwen3's template doesn't round-trip a re-rendered transcript). Results are LRU-cached
|
|
559
|
+
by the env messages. Raises ``ValueError`` if the model's chat template doesn't insert assistant
|
|
560
|
+
content verbatim (token-aligned multi-turn is then unsupported for that model)."""
|
|
561
|
+
cache = _LRUCache(cache_size)
|
|
562
|
+
probe = "flash-env-glue-probe"
|
|
563
|
+
|
|
564
|
+
def env_glue(env_messages: list) -> list[int]:
|
|
565
|
+
cache_key = json.dumps(env_messages, sort_keys=True, default=str)
|
|
566
|
+
cached = cache.get(cache_key)
|
|
567
|
+
if cached is not None:
|
|
568
|
+
return cached
|
|
569
|
+
text = tok.apply_chat_template(
|
|
570
|
+
[{"role": "assistant", "content": probe}, *env_messages],
|
|
571
|
+
add_generation_prompt=True,
|
|
572
|
+
tokenize=False,
|
|
573
|
+
enable_thinking=thinking,
|
|
574
|
+
)
|
|
575
|
+
first = text.find(probe)
|
|
576
|
+
if first == -1 or text.find(probe, first + len(probe)) != -1:
|
|
577
|
+
raise ValueError(
|
|
578
|
+
"multi-turn env_glue could not uniquely locate its probe in the rendered chat "
|
|
579
|
+
"template; this model's template does not insert assistant content verbatim, so "
|
|
580
|
+
"token-aligned multi-turn rollout is unsupported for it (use a single-turn/tool "
|
|
581
|
+
"env or a different model)."
|
|
582
|
+
)
|
|
583
|
+
glue_text = text[first + len(probe) :]
|
|
584
|
+
glue = [int(t) for t in tok(glue_text, add_special_tokens=False).input_ids]
|
|
585
|
+
cache.put(cache_key, glue)
|
|
586
|
+
return glue
|
|
587
|
+
|
|
588
|
+
return env_glue
|
|
589
|
+
|
|
590
|
+
|
|
432
591
|
def _engine_vocab_size(engine) -> int | None:
|
|
433
592
|
"""Best-effort vocab size from the colocate vLLM engine, or None. Never raises."""
|
|
434
593
|
try:
|
|
@@ -482,34 +641,7 @@ def build_rollout_func(
|
|
|
482
641
|
_render_cache.put(cache_key, ids)
|
|
483
642
|
return ids
|
|
484
643
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
def env_glue(env_messages: list) -> list[int]:
|
|
488
|
-
cache_key = json.dumps(env_messages, sort_keys=True, default=str)
|
|
489
|
-
cached = _glue_cache.get(cache_key)
|
|
490
|
-
if cached is not None:
|
|
491
|
-
return cached
|
|
492
|
-
# Render a probe assistant turn + env messages, then take everything after the probe
|
|
493
|
-
# to get the inter-turn glue without re-rendering history (Qwen3 doesn't round-trip).
|
|
494
|
-
probe = "flash-env-glue-probe"
|
|
495
|
-
text = tok.apply_chat_template(
|
|
496
|
-
[{"role": "assistant", "content": probe}, *env_messages],
|
|
497
|
-
add_generation_prompt=True,
|
|
498
|
-
tokenize=False,
|
|
499
|
-
enable_thinking=thinking,
|
|
500
|
-
)
|
|
501
|
-
first = text.find(probe)
|
|
502
|
-
if first == -1 or text.find(probe, first + len(probe)) != -1:
|
|
503
|
-
raise ValueError(
|
|
504
|
-
"multi-turn env_glue could not uniquely locate its probe in the rendered chat "
|
|
505
|
-
"template; this model's template does not insert assistant content verbatim, so "
|
|
506
|
-
"token-aligned multi-turn rollout is unsupported for it (use a single-turn/tool "
|
|
507
|
-
"env or a different model)."
|
|
508
|
-
)
|
|
509
|
-
glue_text = text[first + len(probe) :]
|
|
510
|
-
glue = [int(t) for t in tok(glue_text, add_special_tokens=False).input_ids]
|
|
511
|
-
_glue_cache.put(cache_key, glue)
|
|
512
|
-
return glue
|
|
644
|
+
env_glue = make_env_glue(tok, thinking=thinking)
|
|
513
645
|
|
|
514
646
|
def rollout_func(prompts, trainer):
|
|
515
647
|
engine = trainer.vllm_generation.llm
|
|
@@ -50,9 +50,8 @@ class OPDConfig:
|
|
|
50
50
|
"""On-policy distillation: student samples, a remote teacher scores its tokens, and a groupwise
|
|
51
51
|
reverse-KL loss (the collinear-ai spider / Tinker cross-tokenizer method) trains the student."""
|
|
52
52
|
|
|
53
|
-
# Fireworks-hosted teacher reached over the OpenAI-compatible API
|
|
54
|
-
#
|
|
55
|
-
# supervise the (much smaller) student.
|
|
53
|
+
# Fixed Fireworks-hosted GLM 5.2 teacher reached over the OpenAI-compatible API. This is
|
|
54
|
+
# platform-managed and intentionally not configurable from [train].
|
|
56
55
|
teacher_model: str = "accounts/fireworks/models/glm-5p2"
|
|
57
56
|
teacher_base_url: str = "https://api.fireworks.ai/inference/v1"
|
|
58
57
|
# OPD is step-driven like GRPO (on-policy sampling), not epoch-driven like SFT.
|
|
@@ -35,6 +35,7 @@ from flash.engine.worker.decoding import (
|
|
|
35
35
|
from flash.engine.worker.finalize import write_train_meta
|
|
36
36
|
from flash.engine.worker.gpu_setup import (
|
|
37
37
|
finalize_alloc_conf_for_sleep,
|
|
38
|
+
force_vit_sdpa_on_blackwell,
|
|
38
39
|
force_vllm_backend_for_sm120,
|
|
39
40
|
patch_trl_colocate_llm_kwargs,
|
|
40
41
|
)
|
|
@@ -54,6 +55,8 @@ from flash.engine.worker.heartbeat import (
|
|
|
54
55
|
_HB_TERMINAL_ONLY_INTERVAL_S,
|
|
55
56
|
_HB_TERMINAL_STAGES,
|
|
56
57
|
_HB_THROTTLED_STAGES,
|
|
58
|
+
_HB_TIGHT_LIVENESS_STAGES,
|
|
59
|
+
_HB_UPLOAD_LIVENESS_STAGES,
|
|
57
60
|
_HB_UPLOAD_LOCK,
|
|
58
61
|
_SFT_HEARTBEAT_INTERVAL_S,
|
|
59
62
|
_STEP_GPU_DIAG_INTERVAL_S,
|
|
@@ -371,6 +374,8 @@ __all__ = [
|
|
|
371
374
|
"_HB_TERMINAL_ONLY_INTERVAL_S",
|
|
372
375
|
"_HB_TERMINAL_STAGES",
|
|
373
376
|
"_HB_THROTTLED_STAGES",
|
|
377
|
+
"_HB_TIGHT_LIVENESS_STAGES",
|
|
378
|
+
"_HB_UPLOAD_LIVENESS_STAGES",
|
|
374
379
|
"_HB_UPLOAD_LOCK",
|
|
375
380
|
"_SFT_HEARTBEAT_INTERVAL_S",
|
|
376
381
|
"_STEP_GPU_DIAG_INTERVAL_S",
|
|
@@ -412,6 +417,7 @@ __all__ = [
|
|
|
412
417
|
"disable_liger_grpo_torch_compile",
|
|
413
418
|
"error_artifact_name",
|
|
414
419
|
"finalize_alloc_conf_for_sleep",
|
|
420
|
+
"force_vit_sdpa_on_blackwell",
|
|
415
421
|
"force_vllm_backend_for_sm120",
|
|
416
422
|
"free_gpu",
|
|
417
423
|
"fused_optim_name",
|