freesolo-flash-dev 0.2.44__tar.gz → 0.2.45__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/PKG-INFO +6 -4
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/README.md +5 -3
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/catalog.py +27 -9
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cli/commands.py +44 -2
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cli/render.py +53 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cli/training_doc.py +43 -3
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/client/runtime_secrets.py +7 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cost/analytical.py +63 -4
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cost/facts.py +43 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cost/spec.py +15 -3
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cost/types.py +37 -15
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/accounting.py +5 -1
- freesolo_flash_dev-0.2.45/flash/engine/recipe.py +88 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/vram.py +83 -14
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/__init__.py +47 -17
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/adapter.py +7 -10
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/finalize.py +18 -1
- freesolo_flash_dev-0.2.45/flash/engine/worker/heartbeat.py +357 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/hf.py +11 -7
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/lora.py +114 -313
- freesolo_flash_dev-0.2.45/flash/engine/worker/opd.py +1083 -0
- freesolo_flash_dev-0.2.45/flash/engine/worker/opd_gkd.py +255 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/rl.py +109 -101
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/sft.py +67 -43
- freesolo_flash_dev-0.2.45/flash/engine/worker/teacher.py +318 -0
- freesolo_flash_dev-0.2.45/flash/engine/worker/tokenizer_align.py +119 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/lora_rank.py +45 -21
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/_poll.py +32 -6
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/_worker.py +24 -10
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/runner/__init__.py +4 -3
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/schema/__init__.py +86 -14
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/schema/fields.py +10 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/spec.py +3 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/pyproject.toml +2 -2
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_algorithms.py +46 -1
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_allocator.py +22 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_charge_pricing.py +12 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cli_commands.py +61 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cli_render_theme.py +49 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_flash_worker.py +25 -1
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_lora_rank_preflight.py +35 -1
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_metrics_schema_agent_contract.py +2 -0
- freesolo_flash_dev-0.2.45/tests/test_opd.py +2462 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_providers_symmetry.py +5 -1
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_resume_on_retry.py +4 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_runmgmt.py +36 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_vl_warmstart_recombine.py +5 -3
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_worker_hardexit.py +21 -0
- freesolo_flash_dev-0.2.45/tests/test_worker_init_heartbeat.py +842 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/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.45}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.env.example +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cli/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/client/http.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/envs/adapter.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/envs/loader.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/train/deps.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/vast/api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/serve/deploy.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/app.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/conftest.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_client.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_jobs.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_serve.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_server_api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_sft_example_selection.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_spec_and_validation.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_train_context_preflight.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_vast_api.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/tests/test_worker_thinking.py +0 -0
- {freesolo_flash_dev-0.2.44 → freesolo_flash_dev-0.2.45}/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.45
|
|
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
|
+
SFT and GRPO train off this file; OPD (configs/opd.toml) does NOT support multi-turn:
|
|
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) distils one sampled completion per prompt and cannot drive the
|
|
219
|
+
turn loop, so it is single-turn only — `flash train configs/opd.toml` fails fast here.
|
|
220
|
+
Use OPD with a single-turn env (`flash env setup --single-turn`).
|
|
218
221
|
"""
|
|
219
222
|
|
|
220
223
|
from __future__ import annotations
|
|
@@ -487,6 +490,37 @@ 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) is SINGLE-TURN only: it samples one completion per prompt and
|
|
496
|
+
# cannot drive a multi-turn/tool episode loop, so `flash train configs/opd.toml` FAILS FAST on a
|
|
497
|
+
# multi-turn env (run_opd rejects it at setup). Scaffold it in both modes for reference, but say
|
|
498
|
+
# so loudly in the multi-turn one so the config isn't a footgun.
|
|
499
|
+
opd_multiturn_note = (
|
|
500
|
+
"# NOTE: opd is SINGLE-TURN only — it distils one sampled completion per prompt and cannot\n"
|
|
501
|
+
"# drive this multi-turn environment's episode loop, so `flash train configs/opd.toml` will\n"
|
|
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"
|
|
504
|
+
if multi_turn
|
|
505
|
+
else ""
|
|
506
|
+
)
|
|
507
|
+
opd.write_text(
|
|
508
|
+
f"{opd_multiturn_note}"
|
|
509
|
+
'model = "Qwen/Qwen3.5-4B"\n'
|
|
510
|
+
'algorithm = "opd" # on-policy distillation from a Fireworks GLM teacher (single-turn only)\n\n'
|
|
511
|
+
"# Environment: upload this project folder with\n"
|
|
512
|
+
"# `flash env push --name my-env .`, then paste the returned id below.\n"
|
|
513
|
+
"# FIREWORKS_API_KEY (the GLM teacher key) is read from your shell/.env at submit time.\n"
|
|
514
|
+
"[environment]\n"
|
|
515
|
+
'id = ""\n'
|
|
516
|
+
'secrets = ["FIREWORKS_API_KEY"]\n\n'
|
|
517
|
+
"[train]\n"
|
|
518
|
+
"steps = 100 # opd is step-driven (like GRPO)\n"
|
|
519
|
+
"lora_rank = 32\n"
|
|
520
|
+
'# teacher_model = "accounts/fireworks/models/glm-5p2" # Fireworks GLM teacher (default)\n'
|
|
521
|
+
"# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
|
|
522
|
+
"# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
|
|
523
|
+
)
|
|
490
524
|
training = Path("TRAINING.md")
|
|
491
525
|
if not training.exists():
|
|
492
526
|
# Explicit UTF-8: TRAINING_MD has non-ASCII chars that raise UnicodeEncodeError under a non-UTF-8 locale.
|
|
@@ -496,6 +530,7 @@ def cmd_env_setup(args) -> int:
|
|
|
496
530
|
"dataset/train.jsonl",
|
|
497
531
|
"configs/sft.toml",
|
|
498
532
|
"configs/rl.toml",
|
|
533
|
+
"configs/opd.toml",
|
|
499
534
|
"TRAINING.md",
|
|
500
535
|
]
|
|
501
536
|
if render.styled():
|
|
@@ -655,6 +690,13 @@ def _log_follow_progress(status: dict | None, fallback_state: str) -> tuple[str,
|
|
|
655
690
|
step = heartbeat.get("step")
|
|
656
691
|
if step is not None:
|
|
657
692
|
parts.append(f"step={step}")
|
|
693
|
+
# live heartbeat age so a long quiet phase reads as "alive, throttled" not "frozen".
|
|
694
|
+
# minute granularity: the non-TTY follow path prints a line whenever this string changes,
|
|
695
|
+
# so a seconds-precision age would emit one line per poll.
|
|
696
|
+
ts = heartbeat.get("ts")
|
|
697
|
+
if isinstance(ts, (int, float)) and ts > 0:
|
|
698
|
+
mins = int(max(0.0, time.time() - ts) // 60)
|
|
699
|
+
parts.append(f"hb={mins}m" if mins else "hb=<1m")
|
|
658
700
|
realized = status.get("realized_cost_usd")
|
|
659
701
|
if realized is not None:
|
|
660
702
|
if isinstance(realized, (int, float)):
|
|
@@ -817,7 +859,7 @@ def cmd_checkpoints(args) -> int:
|
|
|
817
859
|
if not checkpoints:
|
|
818
860
|
message = (
|
|
819
861
|
f"no deployable checkpoints for {args.run_id} yet "
|
|
820
|
-
"(RL
|
|
862
|
+
"(RL/opd stream one per save interval; SFT-only runs have none)."
|
|
821
863
|
)
|
|
822
864
|
if render.styled():
|
|
823
865
|
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,45 @@ 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
|
+
- **Provide the teacher key.** Distillation needs `FIREWORKS_API_KEY` at submit time. List it under
|
|
386
|
+
`[environment] secrets = ["FIREWORKS_API_KEY"]` and export it in your shell / local `.env`; the
|
|
387
|
+
value travels out-of-band and is never stored in the spec or needed at serving time.
|
|
388
|
+
- **The student (Qwen / MiniCPM / Kimi) and the teacher (GLM) have different tokenizers.** Flash
|
|
389
|
+
bridges the vocabulary mismatch with **groupwise reverse-KL** (the collinear-ai *spider* / Tinker
|
|
390
|
+
method): it aligns the two tokenizations by shared decoded-text spans and applies per-span reverse
|
|
391
|
+
KL using only realized-token logprobs — no vocabulary projection, so it covers every token exactly
|
|
392
|
+
and works for any student tokenizer. When the tokenizers happen to agree it reduces to plain
|
|
393
|
+
per-token reverse KL (Thinking Machines, *On-Policy Distillation*). Nothing to configure.
|
|
394
|
+
- **Judge it like SFT.** Distillation logs a falling per-token loss; a low loss alone is not proof.
|
|
395
|
+
Keep a held-out split, `flash deploy` the adapter, and score it — confirm the student actually
|
|
396
|
+
moved toward the teacher's behavior, not just its surface tokens.
|
|
397
|
+
|
|
398
|
+
```toml
|
|
399
|
+
# configs/opd.toml
|
|
400
|
+
model = "Qwen/Qwen3.5-4B"
|
|
401
|
+
algorithm = "opd"
|
|
402
|
+
|
|
403
|
+
[environment]
|
|
404
|
+
id = "your-org/my-env"
|
|
405
|
+
secrets = ["FIREWORKS_API_KEY"]
|
|
406
|
+
|
|
407
|
+
[train]
|
|
408
|
+
steps = 100
|
|
409
|
+
lora_rank = 32
|
|
410
|
+
# teacher_model = "accounts/fireworks/models/glm-5p2" # Fireworks GLM teacher (default)
|
|
411
|
+
# kl_penalty_coef = 1.0 # reverse-KL scale
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
376
416
|
## GRPO knobs that matter
|
|
377
417
|
|
|
378
418
|
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.
|
|
@@ -77,6 +82,14 @@ def setup_seconds(config: RunConfig) -> float:
|
|
|
77
82
|
return s
|
|
78
83
|
|
|
79
84
|
|
|
85
|
+
def _opd_step_shape(n: RunConfig) -> tuple[int, int]:
|
|
86
|
+
"""(completions per step, prompt+completion tokens per step) for one OPD step, from a NORMALIZED
|
|
87
|
+
config. completions = batch x group; each is billed over the FULL n.seq_len (prompt+completion,
|
|
88
|
+
not completion-only) since the loss forward runs model(prompt_ids + student_ids)."""
|
|
89
|
+
completions = n.batch_size * n.group_size
|
|
90
|
+
return completions, completions * n.seq_len
|
|
91
|
+
|
|
92
|
+
|
|
80
93
|
def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
81
94
|
"""Steady-state wall time for one optimizer step on ``gpu``."""
|
|
82
95
|
n = config.normalized()
|
|
@@ -85,6 +98,21 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
|
85
98
|
params = active_params_b(n.model_id) * 1e9
|
|
86
99
|
peak = gpu_tflops(gpu) * 1e12 # FLOP/s
|
|
87
100
|
|
|
101
|
+
if n.is_opd:
|
|
102
|
+
# OPD step = on-policy student rollout (like GRPO) + remote teacher scoring (serial
|
|
103
|
+
# Fireworks round-trips, replaces reward grading) + policy update (fwd+bwd only, NO local
|
|
104
|
+
# reference forward — the teacher is the API). Bill local compute on the FULL prompt+completion
|
|
105
|
+
# sequence (see _opd_step_shape), not completion-only, or long-prompt opd is underquoted.
|
|
106
|
+
completions, seq_tokens = _opd_step_shape(n)
|
|
107
|
+
gen_s = (GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_DECODE)
|
|
108
|
+
update_s = (OPD_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_TRAIN)
|
|
109
|
+
teacher_lat = teacher_seconds_per_completion()
|
|
110
|
+
# run_opd scores each completion serially (nested prompt/group loops each await
|
|
111
|
+
# teacher.score before the next), so the wall cost is the full serial sum — do NOT divide
|
|
112
|
+
# by a concurrency factor or the quote materially understates teacher-bound steps.
|
|
113
|
+
teacher_s = completions * teacher_lat
|
|
114
|
+
return gen_s + teacher_s + update_s
|
|
115
|
+
|
|
88
116
|
if not n.is_grpo:
|
|
89
117
|
flops = SFT_FLOPS_PER_TOKEN_PER_PARAM * params * (n.batch_size * n.seq_len)
|
|
90
118
|
return flops / (peak * MFU_SFT_TRAIN)
|
|
@@ -95,7 +123,9 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
|
|
|
95
123
|
gen_s = (GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM * params * gen_tokens) / (peak * MFU_DECODE)
|
|
96
124
|
update_s = (GRPO_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * gen_tokens) / (peak * MFU_TRAIN)
|
|
97
125
|
latency = reward_seconds_per_completion(n.reward_seconds_per_completion)
|
|
98
|
-
reward_s =
|
|
126
|
+
reward_s = (
|
|
127
|
+
math.ceil(completions / REWARD_CONCURRENCY) * latency
|
|
128
|
+
) # ceil: a partial wave still costs one latency
|
|
99
129
|
return gen_s + reward_s + update_s
|
|
100
130
|
|
|
101
131
|
def sft_seconds_for_tokens(config: RunConfig, gpu: str, train_tokens: float) -> float:
|
|
@@ -114,7 +144,9 @@ def select_gpu(config: RunConfig, *, max_wall_seconds: float = 0.0) -> tuple[str
|
|
|
114
144
|
it considers every fitting class, validated or not — so the estimate reflects the cheapest
|
|
115
145
|
card that *could* run the job. The live allocator restricts to the validated pool, so the
|
|
116
146
|
actually-provisioned class can be pricier than this. Catalog sizing is offline/deterministic."""
|
|
117
|
-
total_params_b(
|
|
147
|
+
total_params_b(
|
|
148
|
+
config.model_id
|
|
149
|
+
) # catalog-only: reject a non-catalog model before any (HF) sizing
|
|
118
150
|
need = required_vram_gb(
|
|
119
151
|
config.model_id,
|
|
120
152
|
config.method,
|
|
@@ -125,12 +157,22 @@ def select_gpu(config: RunConfig, *, max_wall_seconds: float = 0.0) -> tuple[str
|
|
|
125
157
|
return gpu, need
|
|
126
158
|
|
|
127
159
|
|
|
128
|
-
def _notes(
|
|
160
|
+
def _notes(
|
|
161
|
+
config: RunConfig, raw_train_s: float, wall_capped: bool, cap_s: float
|
|
162
|
+
) -> tuple[str, ...]:
|
|
129
163
|
n = config.normalized()
|
|
130
164
|
notes: list[str] = []
|
|
131
165
|
if (quant := model_quant(n.model_id)) != "bf16":
|
|
132
166
|
notes.append(f"{quant}: smaller VRAM footprint -> cheaper GPU class fits")
|
|
133
|
-
if n.
|
|
167
|
+
if n.is_opd:
|
|
168
|
+
comps, _ = _opd_step_shape(n)
|
|
169
|
+
tsec = teacher_seconds_per_completion()
|
|
170
|
+
notes.append(
|
|
171
|
+
f"opd step = student rollout of {n.batch_size}x{n.group_size}={comps} completions "
|
|
172
|
+
f"@ {n.completion_len} tok + GLM teacher scoring ({tsec:.2f}s/completion) + policy "
|
|
173
|
+
"update (no local reference forward)"
|
|
174
|
+
)
|
|
175
|
+
elif n.is_grpo:
|
|
134
176
|
comps = n.batch_size * n.group_size
|
|
135
177
|
rsec = reward_seconds_per_completion(n.reward_seconds_per_completion)
|
|
136
178
|
notes.append(
|
|
@@ -187,6 +229,18 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
|
|
|
187
229
|
train = max(0.0, cap_s - setup) if wall_capped else raw_train
|
|
188
230
|
wall = setup + train
|
|
189
231
|
|
|
232
|
+
# OPD: add the external Fireworks teacher token spend. The teacher echo-scores every
|
|
233
|
+
# sampled completion (input ~ prompt+completion per completion), so bill INPUT tokens over the
|
|
234
|
+
# EFFECTIVE (wall-capped) step count — not the uncapped `steps` — so a wall-capped run's teacher
|
|
235
|
+
# bill tracks the GPU time it is actually billed for.
|
|
236
|
+
teacher_api_usd = 0.0
|
|
237
|
+
if config.is_opd:
|
|
238
|
+
n = config.normalized()
|
|
239
|
+
effective_steps = (train / sps) if sps > 0 else config.steps
|
|
240
|
+
_, tokens_per_step = _opd_step_shape(n)
|
|
241
|
+
teacher_input_tokens = effective_steps * tokens_per_step
|
|
242
|
+
teacher_api_usd = teacher_token_cost_usd(teacher_input_tokens, 0.0, config.teacher_model)
|
|
243
|
+
|
|
190
244
|
return CostEstimate(
|
|
191
245
|
model_id=config.model_id,
|
|
192
246
|
method=config.method,
|
|
@@ -201,6 +255,11 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
|
|
|
201
255
|
train_seconds=train,
|
|
202
256
|
wall_clock_seconds=wall,
|
|
203
257
|
wall_capped=wall_capped,
|
|
258
|
+
# GPU (platform-billed) time only. OPD's teacher tokens are billed by Fireworks DIRECTLY to
|
|
259
|
+
# the user's FIREWORKS_API_KEY (a user-supplied [environment].secret), so folding
|
|
260
|
+
# teacher_api_usd into the charged total would bill it a second time — keep it itemized as a
|
|
261
|
+
# diagnostic instead (codex[bot]).
|
|
204
262
|
total_usd=train / 3600.0 * hourly,
|
|
263
|
+
teacher_api_usd=teacher_api_usd,
|
|
205
264
|
notes=_notes(config, raw_train, wall_capped, cap_s),
|
|
206
265
|
)
|
|
@@ -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
|
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
import math
|
|
9
9
|
|
|
10
|
+
from flash.catalog import samples_on_policy
|
|
10
11
|
from flash.cost.analytical import estimate_cost
|
|
11
12
|
from flash.cost.types import CostEstimate, RunConfig
|
|
12
13
|
|
|
@@ -77,6 +78,12 @@ def spec_steps(spec) -> int:
|
|
|
77
78
|
if t.steps is not None:
|
|
78
79
|
return max(1, int(t.steps))
|
|
79
80
|
return RECIPE.rl.num_steps
|
|
81
|
+
if spec.algorithm == "opd":
|
|
82
|
+
# Step-driven like GRPO (on-policy sampling), NOT example-driven — so a opd quote never
|
|
83
|
+
# demands [train].max_examples (the SFT fallback below would raise).
|
|
84
|
+
if t.steps is not None:
|
|
85
|
+
return max(1, int(t.steps))
|
|
86
|
+
return RECIPE.opd.num_steps
|
|
80
87
|
# max_examples is a CAP; 0 (like None) means "no cap" (worker trains the full dataset), so
|
|
81
88
|
# don't let max_examples=0 price a single step.
|
|
82
89
|
return _sft_steps_from_examples(spec, _sft_example_count(spec), apply_cap=True)
|
|
@@ -87,20 +94,25 @@ def runconfig_from_spec(spec) -> RunConfig:
|
|
|
87
94
|
estimate covers a single job. The estimate doesn't pin a GPU -- it does its own cheapest-fit
|
|
88
95
|
(provider="auto")."""
|
|
89
96
|
t, g = spec.train, spec.gpu
|
|
90
|
-
|
|
97
|
+
# Both grpo and opd sample on-policy student completions, so both carry the rollout
|
|
98
|
+
# dimensions (completion length + group size) into the cost model.
|
|
99
|
+
has_rollout = samples_on_policy(spec.algorithm)
|
|
91
100
|
return RunConfig(
|
|
92
101
|
model_id=spec.model,
|
|
93
102
|
method=spec.algorithm,
|
|
94
103
|
steps=spec_steps(spec),
|
|
95
104
|
seq_len=t.max_length,
|
|
96
|
-
completion_len=t.max_tokens if
|
|
105
|
+
completion_len=t.max_tokens if has_rollout else None,
|
|
97
106
|
batch_size=t.batch_size,
|
|
98
|
-
group_size=t.group_size if
|
|
107
|
+
group_size=t.group_size if has_rollout else None,
|
|
99
108
|
lora_rank=t.lora_rank,
|
|
100
109
|
thinking=spec.thinking,
|
|
101
110
|
provider="auto",
|
|
102
111
|
max_wall_seconds=g.max_wall_seconds,
|
|
103
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 "",
|
|
104
116
|
)
|
|
105
117
|
|
|
106
118
|
|