freesolo-flash-dev 0.2.44__tar.gz → 0.2.46__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.44 → freesolo_flash_dev-0.2.46}/PKG-INFO +6 -4
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/README.md +5 -3
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/catalog.py +27 -9
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cli/commands.py +56 -6
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cli/render.py +53 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cli/training_doc.py +49 -3
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/client/runtime_secrets.py +7 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cost/analytical.py +69 -4
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cost/facts.py +43 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cost/spec.py +15 -3
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cost/types.py +38 -15
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/accounting.py +5 -1
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/multiturn_rollout.py +174 -42
- freesolo_flash_dev-0.2.46/flash/engine/recipe.py +88 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/vram.py +83 -14
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/__init__.py +53 -17
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/adapter.py +7 -10
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/finalize.py +18 -1
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/gpu_setup.py +46 -0
- freesolo_flash_dev-0.2.46/flash/engine/worker/heartbeat.py +377 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/hf.py +59 -27
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/lora.py +114 -313
- freesolo_flash_dev-0.2.46/flash/engine/worker/opd.py +1190 -0
- freesolo_flash_dev-0.2.46/flash/engine/worker/opd_gkd.py +255 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/rl.py +118 -101
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/sft.py +72 -43
- freesolo_flash_dev-0.2.46/flash/engine/worker/teacher.py +318 -0
- freesolo_flash_dev-0.2.46/flash/engine/worker/tokenizer_align.py +119 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/lora_rank.py +45 -21
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/_poll.py +32 -6
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/_worker.py +30 -10
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/runner/__init__.py +4 -3
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/schema/__init__.py +69 -14
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/schema/fields.py +10 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/spec.py +3 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/pyproject.toml +2 -2
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_algorithms.py +47 -1
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_allocator.py +22 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_charge_pricing.py +12 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cli_commands.py +89 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cli_render_theme.py +49 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cost_analytical.py +32 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_flash_worker.py +58 -1
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_lora_rank_preflight.py +35 -1
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_metrics_schema_agent_contract.py +2 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_multiturn_rollout.py +101 -0
- freesolo_flash_dev-0.2.46/tests/test_opd.py +2709 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_providers_symmetry.py +5 -1
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_resume_on_retry.py +4 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_runmgmt.py +36 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_vl_warmstart_recombine.py +5 -3
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_worker_hardexit.py +21 -0
- freesolo_flash_dev-0.2.46/tests/test_worker_init_heartbeat.py +882 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_worker_stack.py +12 -19
- freesolo_flash_dev-0.2.44/docs/cli-style/README.md +0 -33
- freesolo_flash_dev-0.2.44/docs/cli-style/env-setup-survey-dark.png +0 -0
- freesolo_flash_dev-0.2.44/docs/cli-style/env-setup-survey-light.png +0 -0
- freesolo_flash_dev-0.2.44/docs/cli-style/generate.py +0 -492
- freesolo_flash_dev-0.2.44/docs/cli-style/index.html +0 -343
- freesolo_flash_dev-0.2.44/docs/cli-style/preview.png +0 -0
- freesolo_flash_dev-0.2.44/docs/cli-style/themed-errors.png +0 -0
- freesolo_flash_dev-0.2.44/docs/cli-style/train-config-error-after.png +0 -0
- freesolo_flash_dev-0.2.44/docs/cli-style/train-config-error-before.png +0 -0
- freesolo_flash_dev-0.2.44/docs/kernel-cache.md +0 -100
- freesolo_flash_dev-0.2.44/flash/engine/recipe.py +0 -53
- freesolo_flash_dev-0.2.44/flash/engine/worker/heartbeat.py +0 -251
- freesolo_flash_dev-0.2.44/tests/test_vl_weight_sync.py +0 -205
- freesolo_flash_dev-0.2.44/tests/test_worker_init_heartbeat.py +0 -413
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.env.example +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cli/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/client/http.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/envs/adapter.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/envs/loader.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/train/deps.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/vast/api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/serve/deploy.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/app.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/conftest.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_client.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_jobs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_serve.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_server_api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_sft_example_selection.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_spec_and_validation.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_train_context_preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_vast_api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/tests/test_worker_thinking.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.46}/uv.lock +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.46
|
|
4
4
|
Summary: Flash — managed LoRA post-training (SFT/GRPO) 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
|
|
@@ -51,7 +51,8 @@ Description-Content-Type: text/markdown
|
|
|
51
51
|
|
|
52
52
|
# Flash
|
|
53
53
|
|
|
54
|
-
Managed LoRA post-training service: SFT and
|
|
54
|
+
Managed LoRA post-training service: SFT, GRPO, and on-policy distillation (from a
|
|
55
|
+
Fireworks-hosted GLM teacher) on managed Flash GPUs.
|
|
55
56
|
The allocator picks the cheapest validated managed GPU class that fits the run.
|
|
56
57
|
|
|
57
58
|
## Scope
|
|
@@ -79,8 +80,9 @@ The allocator picks the cheapest validated managed GPU class that fits the run.
|
|
|
79
80
|
- `flash/providers/` — managed GPU substrate code (pricing, GPU classes, durable
|
|
80
81
|
submit/poll, preflight) behind the `base.Provider` protocol, with an
|
|
81
82
|
`allocator.py` that picks the cheapest fitting managed GPU class
|
|
82
|
-
- `flash/engine/` — the on-GPU worker (TRL + colocated vLLM rollouts
|
|
83
|
-
|
|
83
|
+
- `flash/engine/` — the on-GPU worker (TRL + colocated vLLM rollouts; distillation
|
|
84
|
+
scores on-policy student samples against a remote GLM teacher) and the shared
|
|
85
|
+
recipe; SFT targets and RL rewards route through the active environment
|
|
84
86
|
(task-specific grading lives with its example, not in the engine)
|
|
85
87
|
- `flash/envs/` — environment machinery: registry and the adapter that loads
|
|
86
88
|
Freesolo SDK environments onto the worker's interface
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Flash
|
|
2
2
|
|
|
3
|
-
Managed LoRA post-training service: SFT and
|
|
3
|
+
Managed LoRA post-training service: SFT, GRPO, and on-policy distillation (from a
|
|
4
|
+
Fireworks-hosted GLM teacher) on managed Flash GPUs.
|
|
4
5
|
The allocator picks the cheapest validated managed GPU class that fits the run.
|
|
5
6
|
|
|
6
7
|
## Scope
|
|
@@ -28,8 +29,9 @@ The allocator picks the cheapest validated managed GPU class that fits the run.
|
|
|
28
29
|
- `flash/providers/` — managed GPU substrate code (pricing, GPU classes, durable
|
|
29
30
|
submit/poll, preflight) behind the `base.Provider` protocol, with an
|
|
30
31
|
`allocator.py` that picks the cheapest fitting managed GPU class
|
|
31
|
-
- `flash/engine/` — the on-GPU worker (TRL + colocated vLLM rollouts
|
|
32
|
-
|
|
32
|
+
- `flash/engine/` — the on-GPU worker (TRL + colocated vLLM rollouts; distillation
|
|
33
|
+
scores on-policy student samples against a remote GLM teacher) and the shared
|
|
34
|
+
recipe; SFT targets and RL rewards route through the active environment
|
|
33
35
|
(task-specific grading lives with its example, not in the engine)
|
|
34
36
|
- `flash/envs/` — environment machinery: registry and the adapter that loads
|
|
35
37
|
Freesolo SDK environments onto the worker's interface
|
|
@@ -6,7 +6,24 @@ import math
|
|
|
6
6
|
from dataclasses import asdict, dataclass
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
ALGORITHMS = ("sft", "grpo")
|
|
9
|
+
ALGORITHMS = ("sft", "grpo", "opd")
|
|
10
|
+
|
|
11
|
+
# Algorithms whose training step SAMPLES on-policy student completions (a rollout): GRPO and OPD,
|
|
12
|
+
# versus SFT which trains on a fixed dataset. Single source of truth for the "has rollouts" concept
|
|
13
|
+
# the cost model, VRAM sizing and worker allocator branch on — import ``samples_on_policy`` instead
|
|
14
|
+
# of hand-rolling the ("grpo", "opd") tuple at each site.
|
|
15
|
+
_ON_POLICY_ALGORITHMS = frozenset({"grpo", "opd"})
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def samples_on_policy(algorithm: str) -> bool:
|
|
19
|
+
"""True when a training step samples on-policy student completions (GRPO or OPD).
|
|
20
|
+
|
|
21
|
+
Tolerant of the ``rl`` phase-name alias for grpo (``JobSpec.phase``) so callers that pass a
|
|
22
|
+
phase rather than an algorithm resolve identically."""
|
|
23
|
+
algo = (algorithm or "").strip().lower()
|
|
24
|
+
if algo == "rl": # phase-name alias for grpo
|
|
25
|
+
algo = "grpo"
|
|
26
|
+
return algo in _ON_POLICY_ALGORITHMS
|
|
10
27
|
|
|
11
28
|
|
|
12
29
|
def normalize_algorithm(value: str) -> str:
|
|
@@ -145,7 +162,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
145
162
|
params="1.2B dense (Llama arch)",
|
|
146
163
|
params_b=1.2,
|
|
147
164
|
vocab_size=130_560,
|
|
148
|
-
algos=("sft", "grpo"),
|
|
165
|
+
algos=("sft", "grpo", "opd"),
|
|
149
166
|
min_vram_gb=12,
|
|
150
167
|
recommended_gpu="RTX 4090",
|
|
151
168
|
serving=ServingCapacity(
|
|
@@ -164,7 +181,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
164
181
|
params="0.9B (text-only fine-tune)",
|
|
165
182
|
params_b=0.9,
|
|
166
183
|
vocab_size=248_320,
|
|
167
|
-
algos=("sft", "grpo"),
|
|
184
|
+
algos=("sft", "grpo", "opd"),
|
|
168
185
|
min_vram_gb=12,
|
|
169
186
|
recommended_gpu="RTX 4090",
|
|
170
187
|
serving=ServingCapacity(
|
|
@@ -183,7 +200,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
183
200
|
params="2.3B (text-only fine-tune)",
|
|
184
201
|
params_b=2.3,
|
|
185
202
|
vocab_size=248_320,
|
|
186
|
-
algos=("sft", "grpo"),
|
|
203
|
+
algos=("sft", "grpo", "opd"),
|
|
187
204
|
min_vram_gb=16,
|
|
188
205
|
recommended_gpu="RTX 4090",
|
|
189
206
|
serving=ServingCapacity(
|
|
@@ -201,7 +218,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
201
218
|
params="4.7B (text-only fine-tune)",
|
|
202
219
|
params_b=4.7,
|
|
203
220
|
vocab_size=248_320,
|
|
204
|
-
algos=("sft", "grpo"),
|
|
221
|
+
algos=("sft", "grpo", "opd"),
|
|
205
222
|
min_vram_gb=32,
|
|
206
223
|
recommended_gpu="RTX 5090",
|
|
207
224
|
serving=ServingCapacity(
|
|
@@ -223,7 +240,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
223
240
|
params="9.7B (text-only fine-tune)",
|
|
224
241
|
params_b=9.7,
|
|
225
242
|
vocab_size=248_320,
|
|
226
|
-
algos=("sft", "grpo"),
|
|
243
|
+
algos=("sft", "grpo", "opd"),
|
|
227
244
|
min_vram_gb=48,
|
|
228
245
|
# NOT QLoRA: peft bnb merge during GRPO rollout diverges trainer precision -> TRL ratio collapses to 0.
|
|
229
246
|
grpo_min_vram_gb=80,
|
|
@@ -255,7 +272,7 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
255
272
|
num_layers=40,
|
|
256
273
|
hidden_size=2048,
|
|
257
274
|
vocab_size=248_320,
|
|
258
|
-
algos=("sft", "grpo"),
|
|
275
|
+
algos=("sft", "grpo", "opd"),
|
|
259
276
|
min_vram_gb=141,
|
|
260
277
|
# Floor to 100 GB so SFT lands on H200, not the thin-margin consumer Blackwell or 80 GB H100.
|
|
261
278
|
sft_min_vram_gb=100,
|
|
@@ -403,8 +420,9 @@ def _resolve_open_model(model_id: str, algo: str, gpu: str | None) -> ModelInfo:
|
|
|
403
420
|
def validate_model_for_algorithm(model_id: str, algorithm: str) -> ModelInfo:
|
|
404
421
|
info = get_model(model_id)
|
|
405
422
|
algo = normalize_algorithm(algorithm)
|
|
406
|
-
|
|
407
|
-
|
|
423
|
+
# Each algorithm gates on its own capability entry (sft/grpo/opd), so a model must
|
|
424
|
+
# explicitly opt into opd via its algos tuple — same contract as sft/grpo.
|
|
425
|
+
if algo not in info.algos:
|
|
408
426
|
allowed = ", ".join(info.algos)
|
|
409
427
|
raise ValueError(f"{model_id} supports {allowed}, not {algo}")
|
|
410
428
|
return info
|
|
@@ -210,11 +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) 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).
|
|
218
221
|
"""
|
|
219
222
|
|
|
220
223
|
from __future__ import annotations
|
|
@@ -487,6 +490,33 @@ def cmd_env_setup(args) -> int:
|
|
|
487
490
|
"# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
|
|
488
491
|
"# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
|
|
489
492
|
)
|
|
493
|
+
opd = Path("configs/opd.toml")
|
|
494
|
+
if not opd.exists():
|
|
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.
|
|
498
|
+
opd_multiturn_note = (
|
|
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"
|
|
501
|
+
if multi_turn
|
|
502
|
+
else ""
|
|
503
|
+
)
|
|
504
|
+
opd.write_text(
|
|
505
|
+
f"{opd_multiturn_note}"
|
|
506
|
+
'model = "Qwen/Qwen3.5-4B"\n'
|
|
507
|
+
'algorithm = "opd" # on-policy distillation from a Fireworks GLM teacher\n\n'
|
|
508
|
+
"# Environment: upload this project folder with\n"
|
|
509
|
+
"# `flash env push --name my-env .`, then paste the returned id below.\n"
|
|
510
|
+
"# The GLM teacher key is platform-managed — nothing to set up or export.\n"
|
|
511
|
+
"[environment]\n"
|
|
512
|
+
'id = ""\n\n'
|
|
513
|
+
"[train]\n"
|
|
514
|
+
"steps = 100 # opd is step-driven (like GRPO)\n"
|
|
515
|
+
"lora_rank = 32\n"
|
|
516
|
+
'# teacher_model = "accounts/fireworks/models/glm-5p2" # Fireworks GLM teacher (default)\n'
|
|
517
|
+
"# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
|
|
518
|
+
"# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
|
|
519
|
+
)
|
|
490
520
|
training = Path("TRAINING.md")
|
|
491
521
|
if not training.exists():
|
|
492
522
|
# Explicit UTF-8: TRAINING_MD has non-ASCII chars that raise UnicodeEncodeError under a non-UTF-8 locale.
|
|
@@ -496,6 +526,7 @@ def cmd_env_setup(args) -> int:
|
|
|
496
526
|
"dataset/train.jsonl",
|
|
497
527
|
"configs/sft.toml",
|
|
498
528
|
"configs/rl.toml",
|
|
529
|
+
"configs/opd.toml",
|
|
499
530
|
"TRAINING.md",
|
|
500
531
|
]
|
|
501
532
|
if render.styled():
|
|
@@ -655,6 +686,13 @@ def _log_follow_progress(status: dict | None, fallback_state: str) -> tuple[str,
|
|
|
655
686
|
step = heartbeat.get("step")
|
|
656
687
|
if step is not None:
|
|
657
688
|
parts.append(f"step={step}")
|
|
689
|
+
# live heartbeat age so a long quiet phase reads as "alive, throttled" not "frozen".
|
|
690
|
+
# minute granularity: the non-TTY follow path prints a line whenever this string changes,
|
|
691
|
+
# so a seconds-precision age would emit one line per poll.
|
|
692
|
+
ts = heartbeat.get("ts")
|
|
693
|
+
if isinstance(ts, (int, float)) and ts > 0:
|
|
694
|
+
mins = int(max(0.0, time.time() - ts) // 60)
|
|
695
|
+
parts.append(f"hb={mins}m" if mins else "hb=<1m")
|
|
658
696
|
realized = status.get("realized_cost_usd")
|
|
659
697
|
if realized is not None:
|
|
660
698
|
if isinstance(realized, (int, float)):
|
|
@@ -800,13 +838,25 @@ def cmd_cancel(args) -> int:
|
|
|
800
838
|
else:
|
|
801
839
|
print(json.dumps(payload, indent=2))
|
|
802
840
|
if checkpoints:
|
|
803
|
-
|
|
841
|
+
# Best-effort hint (the cancel already succeeded), so never crash on a malformed checkpoint
|
|
842
|
+
# shape: coerce steps defensively — a dict missing 'step' or carrying a non-int must not raise
|
|
843
|
+
# a traceback here. Only surface the `step-N` deploy example when we recovered a real step.
|
|
844
|
+
steps = []
|
|
845
|
+
for c in checkpoints:
|
|
846
|
+
try:
|
|
847
|
+
steps.append(int(c["step"]))
|
|
848
|
+
except (KeyError, TypeError, ValueError):
|
|
849
|
+
continue
|
|
804
850
|
# stderr in the plain path so the machine-readable stdout JSON stays untouched.
|
|
805
851
|
out = sys.stdout if render.styled() else sys.stderr
|
|
806
|
-
|
|
852
|
+
base = (
|
|
807
853
|
f"{len(checkpoints)} deployable checkpoint(s) survive this cancel — list with "
|
|
808
|
-
f"`flash checkpoints {args.run_id}
|
|
809
|
-
|
|
854
|
+
f"`flash checkpoints {args.run_id}`"
|
|
855
|
+
)
|
|
856
|
+
msg = (
|
|
857
|
+
f"{base}, deploy one with `flash deploy {args.run_id}/step-{max(steps)}`."
|
|
858
|
+
if steps
|
|
859
|
+
else f"{base}."
|
|
810
860
|
)
|
|
811
861
|
print(render.note(msg) if render.styled() else msg, file=out)
|
|
812
862
|
return 0
|
|
@@ -817,7 +867,7 @@ def cmd_checkpoints(args) -> int:
|
|
|
817
867
|
if not checkpoints:
|
|
818
868
|
message = (
|
|
819
869
|
f"no deployable checkpoints for {args.run_id} yet "
|
|
820
|
-
"(RL
|
|
870
|
+
"(RL/opd stream one per save interval; SFT-only runs have none)."
|
|
821
871
|
)
|
|
822
872
|
if render.styled():
|
|
823
873
|
print(render.empty("checkpoints", "0 deployable", message))
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
|
+
import time
|
|
7
8
|
|
|
8
9
|
from flash._channel import CLI_NAME
|
|
9
10
|
|
|
@@ -520,6 +521,47 @@ def _humanize_ts(value) -> str | None:
|
|
|
520
521
|
return datetime.datetime.fromtimestamp(value, datetime.UTC).strftime("%Y-%m-%d %H:%M UTC")
|
|
521
522
|
|
|
522
523
|
|
|
524
|
+
def _humanize_age(value) -> str | None:
|
|
525
|
+
"""Format an epoch seconds value as a compact age ("3m ago"), None for non-numbers."""
|
|
526
|
+
if not isinstance(value, (int, float)) or value <= 0:
|
|
527
|
+
return None
|
|
528
|
+
secs = max(0.0, time.time() - value)
|
|
529
|
+
if secs < 90:
|
|
530
|
+
return f"{int(secs)}s ago"
|
|
531
|
+
if secs < 5400:
|
|
532
|
+
return f"{int(secs // 60)}m ago"
|
|
533
|
+
return f"{secs / 3600:.1f}h ago"
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
# heartbeat age past which the panel reminds that quiet is normal: worker uploads are throttled
|
|
537
|
+
# (240s quiet phases, up to 900s mid-training), so a frozen ts is usually NOT a dead worker.
|
|
538
|
+
_HB_QUIET_HINT_AFTER_S = 300.0
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _heartbeat_pairs(obj: dict) -> list[tuple[str, str]]:
|
|
542
|
+
"""Worker heartbeat rows for the status panel: stage, step, age, and a quiet-is-normal hint."""
|
|
543
|
+
hb = obj.get("last_heartbeat")
|
|
544
|
+
if not isinstance(hb, dict) or not hb.get("stage"):
|
|
545
|
+
return []
|
|
546
|
+
worker = str(hb["stage"])
|
|
547
|
+
step = hb.get("step")
|
|
548
|
+
if step is not None:
|
|
549
|
+
worker += f" · step {step}"
|
|
550
|
+
if hb.get("liveness"):
|
|
551
|
+
worker += " · alive ping"
|
|
552
|
+
pairs = [("worker", worker)]
|
|
553
|
+
ts = hb.get("ts")
|
|
554
|
+
age = _humanize_age(ts)
|
|
555
|
+
if age:
|
|
556
|
+
running = str(obj.get("state") or "") == "running"
|
|
557
|
+
if running and isinstance(ts, (int, float)) and (time.time() - ts) > _HB_QUIET_HINT_AFTER_S:
|
|
558
|
+
age += _dim(
|
|
559
|
+
" (heartbeat uploads are throttled; quiet is not dead - check flash log -f <run-id>)"
|
|
560
|
+
)
|
|
561
|
+
pairs.append(("heartbeat", age))
|
|
562
|
+
return pairs
|
|
563
|
+
|
|
564
|
+
|
|
523
565
|
def run_status(obj: dict) -> str:
|
|
524
566
|
"""A curated status panel for `flash status`, with the full JSON below for completeness."""
|
|
525
567
|
spec = obj.get("spec") or {}
|
|
@@ -534,6 +576,7 @@ def run_status(obj: dict) -> str:
|
|
|
534
576
|
realized = obj.get("realized_cost_usd")
|
|
535
577
|
if realized is not None:
|
|
536
578
|
pairs.append(("realized", money(realized)))
|
|
579
|
+
pairs += _heartbeat_pairs(obj)
|
|
537
580
|
pairs += [
|
|
538
581
|
("created", _humanize_ts(obj.get("created_at"))),
|
|
539
582
|
("updated", _humanize_ts(obj.get("updated_at"))),
|
|
@@ -661,6 +704,16 @@ def cost_panel(est) -> str:
|
|
|
661
704
|
("wall clock", f"{est.wall_clock_hours:.2f} h"),
|
|
662
705
|
("billable", f"{est.billable_hours:.2f} h {_dim('(training only)')}"),
|
|
663
706
|
]
|
|
707
|
+
# opd teacher spend is itemized but NOT part of total_usd (billed by Fireworks on the user's key).
|
|
708
|
+
# Mirror CostEstimate.breakdown() so the styled panel doesn't silently drop it (cursor[bot]).
|
|
709
|
+
if getattr(est, "teacher_api_usd", 0) > 0:
|
|
710
|
+
pairs.append(
|
|
711
|
+
(
|
|
712
|
+
"teacher api",
|
|
713
|
+
f"{money(est.teacher_api_usd, 2)} "
|
|
714
|
+
f"{_dim('(Fireworks GLM token spend on your key — billed by Fireworks, NOT in TOTAL)')}",
|
|
715
|
+
)
|
|
716
|
+
)
|
|
664
717
|
panel = _kv(pairs)
|
|
665
718
|
total = f" {_paint('TOTAL'.ljust(10), _GRAY, '1')} {_paint(_glyph('·', '-'), _FAINT)} {_paint(f'${est.total_usd:.2f}', _TEAL, '1')}"
|
|
666
719
|
out = f"{header('train', 'pre-flight cost estimate')}\n{panel}\n{_rule()}\n{total}"
|
|
@@ -19,8 +19,8 @@ actually produced, and decide — honestly — whether the model got better.
|
|
|
19
19
|
## Using Flash
|
|
20
20
|
|
|
21
21
|
Flash is a **managed** training service with a thin CLI/client. You author an
|
|
22
|
-
environment (the task + its reward), publish it, and submit SFT
|
|
23
|
-
TOML config. Flash allocates the cheapest fitting managed GPU class, runs the job,
|
|
22
|
+
environment (the task + its reward), publish it, and submit SFT, GRPO, or on-policy
|
|
23
|
+
**distillation** runs from a TOML config. Flash allocates the cheapest fitting managed GPU class, runs the job,
|
|
24
24
|
streams logs back, and serves the result. You never handle infrastructure credentials —
|
|
25
25
|
you authenticate once with a freesolo API key, and everything below is a `flash` CLI
|
|
26
26
|
command.
|
|
@@ -42,6 +42,7 @@ environment.py # the task: how to prompt the model and how to score it
|
|
|
42
42
|
dataset/train.jsonl # training rows, one JSON object per line: {"input": ..., "output": ...}
|
|
43
43
|
configs/sft.toml # an SFT run config
|
|
44
44
|
configs/rl.toml # a GRPO (RL) run config
|
|
45
|
+
configs/opd.toml # an on-policy distillation run config
|
|
45
46
|
TRAINING.md # this file
|
|
46
47
|
```
|
|
47
48
|
|
|
@@ -92,7 +93,7 @@ edit to `environment.py` or `dataset/` so the managed run uses your change.
|
|
|
92
93
|
|
|
93
94
|
```toml
|
|
94
95
|
model = "Qwen/Qwen3.5-4B" # see `flash models`
|
|
95
|
-
algorithm = "sft" # "sft" (supervised)
|
|
96
|
+
algorithm = "sft" # "sft" (supervised), "grpo" (RL), or "opd" (on-policy distillation)
|
|
96
97
|
# thinking = true # opt-in reasoning mode, for models that support it
|
|
97
98
|
|
|
98
99
|
[environment]
|
|
@@ -373,6 +374,51 @@ SFT is **epoch-driven** (`epochs`); GRPO is **step-driven** (`steps`).
|
|
|
373
374
|
|
|
374
375
|
---
|
|
375
376
|
|
|
377
|
+
## On-policy distillation (`algorithm = "opd"`)
|
|
378
|
+
|
|
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), a Fireworks-hosted GLM teacher scores
|
|
381
|
+
each of *its own* completions, and a dense per-token loss teaches the student to match the teacher —
|
|
382
|
+
far more sample-efficient than reward-based RL and with no reward to design. It is **step-driven**
|
|
383
|
+
(`steps`) and produces a LoRA served exactly like SFT.
|
|
384
|
+
|
|
385
|
+
- **No teacher key to set up.** The Fireworks key for the GLM teacher is 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 keys are not supported; a `FIREWORKS_API_KEY`
|
|
388
|
+
in your shell is simply ignored.) The key is never stored in the spec or needed at serving time.
|
|
389
|
+
- **The student (Qwen / MiniCPM / Kimi) and the teacher (GLM) have different tokenizers.** Flash
|
|
390
|
+
bridges the vocabulary mismatch with **groupwise reverse-KL** (the collinear-ai *spider* / Tinker
|
|
391
|
+
method): it aligns the two tokenizations by shared decoded-text spans and applies per-span reverse
|
|
392
|
+
KL using only realized-token logprobs — no vocabulary projection, so it covers every token exactly
|
|
393
|
+
and works for any student tokenizer. When the tokenizers happen to agree it reduces to plain
|
|
394
|
+
per-token reverse KL (Thinking Machines, *On-Policy Distillation*). Nothing to configure.
|
|
395
|
+
- **Works for multi-turn envs too.** Against an `EnvironmentMultiTurn`, opd rolls out each episode
|
|
396
|
+
(driving `step_episode` / observations just like GRPO) and distils EVERY assistant turn against the
|
|
397
|
+
teacher, each conditioned on the transcript up to that turn — the episode's total reverse-KL over
|
|
398
|
+
the student's generated tokens is the sum of its per-turn reverse-KLs. Env/observation tokens are
|
|
399
|
+
never distilled (they're context, not the student's output). Set `[train] max_length` to bound the
|
|
400
|
+
transcript; the teacher must cover it (GLM-5.2's context far exceeds the default budget).
|
|
401
|
+
- **Judge it like SFT.** Distillation logs a falling per-token loss; a low loss alone is not proof.
|
|
402
|
+
Keep a held-out split, `flash deploy` the adapter, and score it — confirm the student actually
|
|
403
|
+
moved toward the teacher's behavior, not just its surface tokens.
|
|
404
|
+
|
|
405
|
+
```toml
|
|
406
|
+
# configs/opd.toml
|
|
407
|
+
model = "Qwen/Qwen3.5-4B"
|
|
408
|
+
algorithm = "opd"
|
|
409
|
+
|
|
410
|
+
[environment]
|
|
411
|
+
id = "your-org/my-env"
|
|
412
|
+
|
|
413
|
+
[train]
|
|
414
|
+
steps = 100
|
|
415
|
+
lora_rank = 32
|
|
416
|
+
# teacher_model = "accounts/fireworks/models/glm-5p2" # Fireworks GLM teacher (default)
|
|
417
|
+
# kl_penalty_coef = 1.0 # reverse-KL scale
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
376
422
|
## GRPO knobs that matter
|
|
377
423
|
|
|
378
424
|
Set these in `[train]`. Each is `None` by default — the worker's tuned recipe fills
|
|
@@ -5,6 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
+
# The global runtime-secret allow-list: keys collected/gated/injected for EVERY run regardless of
|
|
9
|
+
# algorithm. Only WANDB_API_KEY belongs here (optional experiment logging, useful to all arms). The
|
|
10
|
+
# opd teacher key (FIREWORKS_API_KEY) is NOT global: opd auto-declares it as a required environment
|
|
11
|
+
# secret in spec_from_dict, and each gate unions the spec's declared secrets on top of this set
|
|
12
|
+
# (client collection below; server create gate server/_deps.py; worker-env injection
|
|
13
|
+
# providers/runpod/train/deps.py). Keeping it out of the default means SFT/GRPO workers never
|
|
14
|
+
# receive the teacher key they don't need.
|
|
8
15
|
DEFAULT_RUNTIME_SECRET_KEYS = frozenset({"WANDB_API_KEY"})
|
|
9
16
|
|
|
10
17
|
|
|
@@ -20,6 +20,8 @@ from .facts import (
|
|
|
20
20
|
model_quant,
|
|
21
21
|
pick_gpu,
|
|
22
22
|
reward_seconds_per_completion,
|
|
23
|
+
teacher_seconds_per_completion,
|
|
24
|
+
teacher_token_cost_usd,
|
|
23
25
|
total_params_b,
|
|
24
26
|
)
|
|
25
27
|
from .types import CostEstimate, RunConfig
|
|
@@ -28,6 +30,9 @@ from .types import CostEstimate, RunConfig
|
|
|
28
30
|
SFT_FLOPS_PER_TOKEN_PER_PARAM = 6.0 # forward (2) + backward (4)
|
|
29
31
|
GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM = 2.0 # autoregressive rollout forward
|
|
30
32
|
GRPO_UPDATE_FLOPS_PER_TOKEN_PER_PARAM = 8.0 # policy fwd+bwd (6) + frozen-ref fwd (2)
|
|
33
|
+
# OPD's update is policy fwd+bwd ONLY (6): the teacher's per-token logprobs come from the
|
|
34
|
+
# Fireworks API, so there is NO local frozen-reference forward (GRPO's extra 2).
|
|
35
|
+
OPD_UPDATE_FLOPS_PER_TOKEN_PER_PARAM = 6.0
|
|
31
36
|
|
|
32
37
|
# Model-FLOPs utilization (fraction of peak sustained), calibrated against real RunPod
|
|
33
38
|
# wall clock. LoRA + small batches sit well below dense-pretraining MFU.
|
|
@@ -39,6 +44,11 @@ MFU_DECODE = 0.12 # batched vLLM rollout (decode is memory-bandwidth-bound)
|
|
|
39
44
|
# wall is ceil(completions / slots) waves x latency, not completions x latency.
|
|
40
45
|
REWARD_CONCURRENCY = 16.0
|
|
41
46
|
|
|
47
|
+
# Teacher scoring is CONCURRENT too: run_opd's primary path fans a step's completions across up to
|
|
48
|
+
# TEACHER_CONCURRENCY Fireworks calls (mirror of opd.py _TEACHER_SCORE_MAX_WORKERS), so the teacher
|
|
49
|
+
# wall is ceil(completions / slots) waves x latency, not completions x latency.
|
|
50
|
+
TEACHER_CONCURRENCY = 8.0
|
|
51
|
+
|
|
42
52
|
# Cold-start overhead (seconds): container boot + deps + model load (+ vLLM init for GRPO).
|
|
43
53
|
#
|
|
44
54
|
# Calibrated against a real fresh-worker run (0.8B SFT, RTX 3090 @ $0.239/hr) whose elapsed wall
|
|
@@ -77,6 +87,14 @@ def setup_seconds(config: RunConfig) -> float:
|
|
|
77
87
|
return s
|
|
78
88
|
|
|
79
89
|
|
|
90
|
+
def _opd_step_shape(n: RunConfig) -> tuple[int, int]:
|
|
91
|
+
"""(completions per step, prompt+completion tokens per step) for one OPD step, from a NORMALIZED
|
|
92
|
+
config. completions = batch x group; each is billed over the FULL n.seq_len (prompt+completion,
|
|
93
|
+
not completion-only) since the loss forward runs model(prompt_ids + student_ids)."""
|
|
94
|
+
completions = n.batch_size * n.group_size
|
|
95
|
+
return completions, completions * n.seq_len
|
|
96
|
+
|
|
97
|
+
|
|
80
98
|
def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
81
99
|
"""Steady-state wall time for one optimizer step on ``gpu``."""
|
|
82
100
|
n = config.normalized()
|
|
@@ -85,6 +103,22 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
|
85
103
|
params = active_params_b(n.model_id) * 1e9
|
|
86
104
|
peak = gpu_tflops(gpu) * 1e12 # FLOP/s
|
|
87
105
|
|
|
106
|
+
if n.is_opd:
|
|
107
|
+
# OPD step = on-policy student rollout (like GRPO) + remote teacher scoring (CONCURRENT
|
|
108
|
+
# Fireworks round-trips, replaces reward grading) + policy update (fwd+bwd only, NO local
|
|
109
|
+
# reference forward — the teacher is the API). Bill local compute on the FULL prompt+completion
|
|
110
|
+
# sequence (see _opd_step_shape), not completion-only, or long-prompt opd is underquoted.
|
|
111
|
+
completions, seq_tokens = _opd_step_shape(n)
|
|
112
|
+
gen_s = (GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_DECODE)
|
|
113
|
+
update_s = (OPD_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_TRAIN)
|
|
114
|
+
teacher_lat = teacher_seconds_per_completion()
|
|
115
|
+
# run_opd's primary path scores a step's completions CONCURRENTLY over Fireworks, bounded by
|
|
116
|
+
# TEACHER_CONCURRENCY workers (opd.py _TEACHER_SCORE_MAX_WORKERS), so the teacher wall is
|
|
117
|
+
# ceil(completions / slots) waves x latency — NOT the full serial sum (that describes only the
|
|
118
|
+
# CPU-test fallback that can't batch-generate). ceil keeps a partial last wave at one latency.
|
|
119
|
+
teacher_s = math.ceil(completions / TEACHER_CONCURRENCY) * teacher_lat
|
|
120
|
+
return gen_s + teacher_s + update_s
|
|
121
|
+
|
|
88
122
|
if not n.is_grpo:
|
|
89
123
|
flops = SFT_FLOPS_PER_TOKEN_PER_PARAM * params * (n.batch_size * n.seq_len)
|
|
90
124
|
return flops / (peak * MFU_SFT_TRAIN)
|
|
@@ -95,7 +129,9 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
|
95
129
|
gen_s = (GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM * params * gen_tokens) / (peak * MFU_DECODE)
|
|
96
130
|
update_s = (GRPO_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * gen_tokens) / (peak * MFU_TRAIN)
|
|
97
131
|
latency = reward_seconds_per_completion(n.reward_seconds_per_completion)
|
|
98
|
-
reward_s =
|
|
132
|
+
reward_s = (
|
|
133
|
+
math.ceil(completions / REWARD_CONCURRENCY) * latency
|
|
134
|
+
) # ceil: a partial wave still costs one latency
|
|
99
135
|
return gen_s + reward_s + update_s
|
|
100
136
|
|
|
101
137
|
def sft_seconds_for_tokens(config: RunConfig, gpu: str, train_tokens: float) -> float:
|
|
@@ -114,7 +150,9 @@ def select_gpu(config: RunConfig, *, max_wall_seconds: float = 0.0) -> tuple[str
|
|
|
114
150
|
it considers every fitting class, validated or not — so the estimate reflects the cheapest
|
|
115
151
|
card that *could* run the job. The live allocator restricts to the validated pool, so the
|
|
116
152
|
actually-provisioned class can be pricier than this. Catalog sizing is offline/deterministic."""
|
|
117
|
-
total_params_b(
|
|
153
|
+
total_params_b(
|
|
154
|
+
config.model_id
|
|
155
|
+
) # catalog-only: reject a non-catalog model before any (HF) sizing
|
|
118
156
|
need = required_vram_gb(
|
|
119
157
|
config.model_id,
|
|
120
158
|
config.method,
|
|
@@ -125,12 +163,22 @@ def select_gpu(config: RunConfig, *, max_wall_seconds: float = 0.0) -> tuple[str
|
|
|
125
163
|
return gpu, need
|
|
126
164
|
|
|
127
165
|
|
|
128
|
-
def _notes(
|
|
166
|
+
def _notes(
|
|
167
|
+
config: RunConfig, raw_train_s: float, wall_capped: bool, cap_s: float
|
|
168
|
+
) -> tuple[str, ...]:
|
|
129
169
|
n = config.normalized()
|
|
130
170
|
notes: list[str] = []
|
|
131
171
|
if (quant := model_quant(n.model_id)) != "bf16":
|
|
132
172
|
notes.append(f"{quant}: smaller VRAM footprint -> cheaper GPU class fits")
|
|
133
|
-
if n.
|
|
173
|
+
if n.is_opd:
|
|
174
|
+
comps, _ = _opd_step_shape(n)
|
|
175
|
+
tsec = teacher_seconds_per_completion()
|
|
176
|
+
notes.append(
|
|
177
|
+
f"opd step = student rollout of {n.batch_size}x{n.group_size}={comps} completions "
|
|
178
|
+
f"@ {n.completion_len} tok + GLM teacher scoring ({tsec:.2f}s/completion) + policy "
|
|
179
|
+
"update (no local reference forward)"
|
|
180
|
+
)
|
|
181
|
+
elif n.is_grpo:
|
|
134
182
|
comps = n.batch_size * n.group_size
|
|
135
183
|
rsec = reward_seconds_per_completion(n.reward_seconds_per_completion)
|
|
136
184
|
notes.append(
|
|
@@ -187,6 +235,18 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
|
|
|
187
235
|
train = max(0.0, cap_s - setup) if wall_capped else raw_train
|
|
188
236
|
wall = setup + train
|
|
189
237
|
|
|
238
|
+
# OPD: add the external Fireworks teacher token spend. The teacher echo-scores every
|
|
239
|
+
# sampled completion (input ~ prompt+completion per completion), so bill INPUT tokens over the
|
|
240
|
+
# EFFECTIVE (wall-capped) step count — not the uncapped `steps` — so a wall-capped run's teacher
|
|
241
|
+
# bill tracks the GPU time it is actually billed for.
|
|
242
|
+
teacher_api_usd = 0.0
|
|
243
|
+
if config.is_opd:
|
|
244
|
+
n = config.normalized()
|
|
245
|
+
effective_steps = (train / sps) if sps > 0 else config.steps
|
|
246
|
+
_, tokens_per_step = _opd_step_shape(n)
|
|
247
|
+
teacher_input_tokens = effective_steps * tokens_per_step
|
|
248
|
+
teacher_api_usd = teacher_token_cost_usd(teacher_input_tokens, 0.0, config.teacher_model)
|
|
249
|
+
|
|
190
250
|
return CostEstimate(
|
|
191
251
|
model_id=config.model_id,
|
|
192
252
|
method=config.method,
|
|
@@ -201,6 +261,11 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
|
|
|
201
261
|
train_seconds=train,
|
|
202
262
|
wall_clock_seconds=wall,
|
|
203
263
|
wall_capped=wall_capped,
|
|
264
|
+
# GPU (platform-billed) time only. OPD's teacher tokens are billed by Fireworks to the
|
|
265
|
+
# platform-managed teacher key — never through this GPU charge — so folding teacher_api_usd
|
|
266
|
+
# into the charged total would bill it a second time. Keep it itemized as a diagnostic
|
|
267
|
+
# instead (codex[bot]).
|
|
204
268
|
total_usd=train / 3600.0 * hourly,
|
|
269
|
+
teacher_api_usd=teacher_api_usd,
|
|
205
270
|
notes=_notes(config, raw_train, wall_capped, cap_s),
|
|
206
271
|
)
|
|
@@ -159,3 +159,46 @@ def reward_seconds_per_completion(override: float | None = None) -> float:
|
|
|
159
159
|
if override is not None:
|
|
160
160
|
return max(0.0, override)
|
|
161
161
|
return AVG_REWARD_SECONDS_PER_COMPLETION
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# Fireworks serverless pricing for the on-policy-distillation GLM teacher, $/1M tokens as
|
|
165
|
+
# (input, output). Static like gpu_hourly_usd so cost estimation is deterministic/offline and needs
|
|
166
|
+
# NO FIREWORKS_API_KEY. Source: https://fireworks.ai/models/fireworks/glm-5p2 lists
|
|
167
|
+
# $1.40 / $0.14 / $4.40 (input / cached input / output) per 1M. opd echo-scores completions
|
|
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. glm-5p1 shares the GLM-5 serverless rate.
|
|
170
|
+
TEACHER_USD_PER_1M: dict[str, tuple[float, float]] = {
|
|
171
|
+
"accounts/fireworks/models/glm-5p2": (1.40, 4.40),
|
|
172
|
+
"accounts/fireworks/models/glm-5p1": (1.40, 4.40),
|
|
173
|
+
}
|
|
174
|
+
# Fireworks echo-scoring round-trip per completion (wall time, concurrency-bound like reward grading).
|
|
175
|
+
AVG_TEACHER_SECONDS_PER_COMPLETION = 2.0
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def teacher_price_per_1m(teacher_model: str) -> tuple[float, float]:
|
|
179
|
+
"""(input, output) $/1M tokens for a teacher model.
|
|
180
|
+
|
|
181
|
+
The empty-string sentinel (an opd run that OMITS [train] teacher_model) resolves to the recipe's
|
|
182
|
+
default teacher (``RECIPE.opd.teacher_model``) — the SINGLE source of "who is the default
|
|
183
|
+
teacher" — so the quote uses that model's real rate with no forked default-price constant. An
|
|
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)."""
|
|
186
|
+
from flash.engine.recipe import RECIPE
|
|
187
|
+
|
|
188
|
+
default = TEACHER_USD_PER_1M[RECIPE.opd.teacher_model]
|
|
189
|
+
return TEACHER_USD_PER_1M.get(teacher_model or RECIPE.opd.teacher_model, default)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def teacher_token_cost_usd(
|
|
193
|
+
input_tokens: float, output_tokens: float = 0.0, teacher_model: str = ""
|
|
194
|
+
) -> float:
|
|
195
|
+
"""External teacher-API dollar cost for a token count. Deterministic; no network."""
|
|
196
|
+
inp, outp = teacher_price_per_1m(teacher_model)
|
|
197
|
+
return (max(0.0, input_tokens) * inp + max(0.0, output_tokens) * outp) / 1_000_000.0
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def teacher_seconds_per_completion(override: float | None = None) -> float:
|
|
201
|
+
"""Per-completion teacher-scoring latency (s): the explicit override, else the average."""
|
|
202
|
+
if override is not None:
|
|
203
|
+
return max(0.0, override)
|
|
204
|
+
return AVG_TEACHER_SECONDS_PER_COMPLETION
|