freesolo-flash-dev 0.2.40__tar.gz → 0.2.42__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.40 → freesolo_flash_dev-0.2.42}/PKG-INFO +1 -1
- freesolo_flash_dev-0.2.42/docs/cli-style/env-setup-survey-dark.png +0 -0
- freesolo_flash_dev-0.2.42/docs/cli-style/env-setup-survey-light.png +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/catalog.py +38 -2
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cli/__init__.py +41 -2
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cli/commands.py +264 -10
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cli/render.py +34 -2
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cli/training_doc.py +16 -15
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/chalk_kernels.py +10 -2
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/__init__.py +2 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/perf/__init__.py +2 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/perf/memory.py +44 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/rl.py +5 -1
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/sft.py +25 -44
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/envs/adapter.py +8 -2
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/lora_rank.py +42 -7
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/_worker.py +5 -4
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/runner/__init__.py +6 -4
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/schema/__init__.py +1 -1
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/spec.py +1 -1
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/pyproject.toml +2 -2
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_catalog_consistency.py +3 -1
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_chalk_kernels.py +42 -1
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cli_commands.py +104 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_flash_worker.py +10 -7
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_sft_gc_off.py +44 -6
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_spec_and_validation.py +7 -4
- freesolo_flash_dev-0.2.42/tests/test_train_context_preflight.py +82 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_verifiers.py +11 -8
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/uv.lock +1 -1
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.env.example +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/README.md +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docs/cli-style/README.md +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docs/cli-style/generate.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docs/cli-style/index.html +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docs/cli-style/preview.png +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docs/cli-style/themed-errors.png +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docs/cli-style/train-config-error-after.png +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docs/cli-style/train-config-error-before.png +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/docs/kernel-cache.md +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/client/http.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cost/analytical.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cost/facts.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/adapter.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/hf.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/envs/loader.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/train/deps.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/vast/api.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/serve/deploy.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/app.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/conftest.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_charge_pricing.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_client.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_jobs.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_lora_rank_preflight.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_serve.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_server_api.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_vast_api.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_vl_warmstart_recombine.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_vl_weight_sync.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.42}/tests/test_worker_thinking.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.42
|
|
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
|
|
Binary file
|
|
Binary file
|
|
@@ -12,7 +12,7 @@ ALGORITHMS = ("sft", "grpo")
|
|
|
12
12
|
def normalize_algorithm(value: str) -> str:
|
|
13
13
|
"""Canonical (lowercased, validated) algorithm name."""
|
|
14
14
|
if not value:
|
|
15
|
-
value = "
|
|
15
|
+
value = "sft"
|
|
16
16
|
elif not isinstance(value, str):
|
|
17
17
|
# A truthy non-string (e.g. a JSON number/bool/array) would AttributeError on .lower(), which
|
|
18
18
|
# escapes the callers' ValueError/ConfigError guards -> uncaught 500. Raise ValueError instead.
|
|
@@ -95,6 +95,16 @@ class ModelInfo:
|
|
|
95
95
|
num_layers: int = 0
|
|
96
96
|
hidden_size: int = 0
|
|
97
97
|
|
|
98
|
+
@property
|
|
99
|
+
def is_moe(self) -> bool:
|
|
100
|
+
"""True for a mixture-of-experts model — a token routes through only a subset of experts.
|
|
101
|
+
|
|
102
|
+
Keyed off ``active_params_b`` (0.0 == dense, "every token hits every param"). Used by the
|
|
103
|
+
GRPO worker to pick REENTRANT gradient checkpointing for MoE (its router re-dispatches tokens
|
|
104
|
+
on recompute, which the non-reentrant metadata-equality assert rejects).
|
|
105
|
+
"""
|
|
106
|
+
return 0.0 < self.active_params_b < self.params_b
|
|
107
|
+
|
|
98
108
|
def to_dict(self) -> dict[str, Any]:
|
|
99
109
|
data = asdict(self)
|
|
100
110
|
serving = data.get("serving")
|
|
@@ -263,9 +273,15 @@ MODELS: dict[str, ModelInfo] = {
|
|
|
263
273
|
# rank-64 at only 6 hot slots: the fused-MoE LoRA buffer scales with
|
|
264
274
|
# max_loras x rank x num_experts, so the A100-80GB ceiling is ~max_loras x rank = 384
|
|
265
275
|
# (6 x 64 fits at 99.3% util; 16 x 64 OOMs on every single/multi GPU). Serving-validated.
|
|
276
|
+
# The 6 x 64 ceiling is WEIGHT-bound, not context-bound: the FP8 checkpoint's MoE experts
|
|
277
|
+
# load as bf16 under the LoRA path (~76 GiB on the 80 GiB card), so weights + the 6 x 64
|
|
278
|
+
# buffer leave only ~0.44 GiB for KV. Canary-measured 2026-07-04: at 8192 ctx that KV pool
|
|
279
|
+
# gives 2.2x concurrency; dropping to 4096 does NOT free any LoRA-slot room (7 x 64 and
|
|
280
|
+
# 8 x 64 both OOM at 4096 exactly as at 8192) but ~doubles concurrency to ~4.4x. So context
|
|
281
|
+
# is 4096 (concurrency win, matches the training-context cap) while slots stay 6 x 64.
|
|
266
282
|
max_loras=6,
|
|
267
283
|
max_lora_rank=64,
|
|
268
|
-
max_model_len=
|
|
284
|
+
max_model_len=4096,
|
|
269
285
|
max_num_seqs=8,
|
|
270
286
|
max_num_batched_tokens=4096,
|
|
271
287
|
gpu_memory_utilization=0.98,
|
|
@@ -313,6 +329,26 @@ def serving_lora_rank_cap(model: str | ModelInfo | None) -> int | None:
|
|
|
313
329
|
return int(info.serving.max_lora_rank)
|
|
314
330
|
|
|
315
331
|
|
|
332
|
+
def serving_context_cap(model: str | ModelInfo | None) -> int | None:
|
|
333
|
+
"""Return the model's serving ``max_model_len`` (the context it is actually served at), or None
|
|
334
|
+
when Flash has no local serving entry (open-policy / uncataloged).
|
|
335
|
+
|
|
336
|
+
A LoRA trained at a longer context than it is served wastes compute and learns positions that are
|
|
337
|
+
never used at inference, so the control plane caps a run's training context to this (see
|
|
338
|
+
``flash.lora_rank.preflight_train_context_within_serving``). Resolution mirrors
|
|
339
|
+
``serving_lora_rank_cap``: unknown/open-policy models return None rather than a global fallback.
|
|
340
|
+
"""
|
|
341
|
+
if isinstance(model, ModelInfo):
|
|
342
|
+
info = model
|
|
343
|
+
elif isinstance(model, str) and model.strip():
|
|
344
|
+
info = MODELS.get(model.strip())
|
|
345
|
+
else:
|
|
346
|
+
info = None
|
|
347
|
+
if info is None or info.serving is None:
|
|
348
|
+
return None
|
|
349
|
+
return int(info.serving.max_model_len)
|
|
350
|
+
|
|
351
|
+
|
|
316
352
|
def vocab_size_for(model_id: str) -> int:
|
|
317
353
|
"""Curated vocab_size for a model, or the safe default for open-model-policy entries."""
|
|
318
354
|
info = MODELS.get(model_id)
|
|
@@ -168,7 +168,7 @@ class _FlashParser(_ThemedParser):
|
|
|
168
168
|
footers = [
|
|
169
169
|
f"new here? run `{CLI_NAME} login`, then `{CLI_NAME} env setup`",
|
|
170
170
|
f"train after publishing: `{CLI_NAME} env push --name my-env .`, "
|
|
171
|
-
f"then `{CLI_NAME} train configs/
|
|
171
|
+
f"then `{CLI_NAME} train configs/sft.toml`",
|
|
172
172
|
f"any command in depth: `{CLI_NAME} <command> --help`",
|
|
173
173
|
"docs: https://freesolo.co/docs",
|
|
174
174
|
]
|
|
@@ -234,7 +234,46 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
234
234
|
env = sub.add_parser("env", help="manage Freesolo environments")
|
|
235
235
|
env_sub = env.add_subparsers(dest="env_cmd", required=True)
|
|
236
236
|
setup = env_sub.add_parser("setup", help="create a starter Freesolo environment scaffold")
|
|
237
|
-
setup.
|
|
237
|
+
setup_mode = setup.add_mutually_exclusive_group()
|
|
238
|
+
setup_mode.add_argument(
|
|
239
|
+
"--single-turn",
|
|
240
|
+
dest="turn_mode",
|
|
241
|
+
action="store_const",
|
|
242
|
+
const="single",
|
|
243
|
+
help="scaffold a single-turn environment (prompt -> one response). This is the default.",
|
|
244
|
+
)
|
|
245
|
+
setup_mode.add_argument(
|
|
246
|
+
"--multi-turn",
|
|
247
|
+
dest="turn_mode",
|
|
248
|
+
action="store_const",
|
|
249
|
+
const="multi",
|
|
250
|
+
help="scaffold a multi-turn environment (bounded episode with step_episode / score_episode).",
|
|
251
|
+
)
|
|
252
|
+
setup_reason = setup.add_mutually_exclusive_group()
|
|
253
|
+
setup_reason.add_argument(
|
|
254
|
+
"--reasoning",
|
|
255
|
+
dest="reasoning",
|
|
256
|
+
action="store_const",
|
|
257
|
+
const=True,
|
|
258
|
+
help="scaffold configs with reasoning enabled (thinking = true, with a raised token budget).",
|
|
259
|
+
)
|
|
260
|
+
setup_reason.add_argument(
|
|
261
|
+
"--no-reasoning",
|
|
262
|
+
dest="reasoning",
|
|
263
|
+
action="store_const",
|
|
264
|
+
const=False,
|
|
265
|
+
help="scaffold configs without reasoning. This is the default.",
|
|
266
|
+
)
|
|
267
|
+
setup.add_argument(
|
|
268
|
+
"-y",
|
|
269
|
+
"--yes",
|
|
270
|
+
dest="yes",
|
|
271
|
+
action="store_true",
|
|
272
|
+
help="accept defaults without prompting (single-turn, no reasoning).",
|
|
273
|
+
)
|
|
274
|
+
# turn_mode / reasoning default to None (unset) so the scaffold can tell an explicit flag
|
|
275
|
+
# apart from "not chosen yet" and, on an interactive terminal, ask instead of assuming.
|
|
276
|
+
setup.set_defaults(func=cmd_env_setup, turn_mode=None, reasoning=None, yes=False)
|
|
238
277
|
|
|
239
278
|
env_list = env_sub.add_parser("list", help="list local environment sources")
|
|
240
279
|
env_list.set_defaults(func=cmd_env_list)
|
|
@@ -193,15 +193,247 @@ _STARTER_DATASET_JSONL = """\
|
|
|
193
193
|
"""
|
|
194
194
|
|
|
195
195
|
|
|
196
|
+
_STARTER_ENV_MULTITURN_PY = '''\
|
|
197
|
+
"""Starter Freesolo multi-turn environment.
|
|
198
|
+
|
|
199
|
+
A multi-turn environment runs a bounded episode: the model produces an assistant
|
|
200
|
+
action, `step_episode` advances the world (optionally appending an observation
|
|
201
|
+
message), and the loop repeats until `done` or `max_episode_turns`. The finished
|
|
202
|
+
transcript is graded by `score_episode`.
|
|
203
|
+
|
|
204
|
+
Edit dataset/train.jsonl and the episode logic, then upload with
|
|
205
|
+
`flash env push --name my-env .`.
|
|
206
|
+
|
|
207
|
+
A managed run should use the returned [environment] id from
|
|
208
|
+
`flash env push --name my-env .`.
|
|
209
|
+
|
|
210
|
+
This starter implements a tiny "guess the secret number" game so you can see the
|
|
211
|
+
episode hooks wired end-to-end. Replace it with your real task before a real run.
|
|
212
|
+
|
|
213
|
+
Both SFT and GRPO train off this file:
|
|
214
|
+
- GRPO (configs/rl.toml) rolls out full episodes and optimizes `score_episode`.
|
|
215
|
+
- SFT (configs/sft.toml) learns the gold trajectory. Provide it per row as
|
|
216
|
+
`output = {"messages": [...]}` (a full assistant/tool trajectory) or a scalar
|
|
217
|
+
`output` for a single gold assistant turn.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
from __future__ import annotations
|
|
221
|
+
|
|
222
|
+
import json
|
|
223
|
+
from pathlib import Path
|
|
224
|
+
|
|
225
|
+
from freesolo.datasets.types import TaskExample
|
|
226
|
+
from freesolo.environments import (
|
|
227
|
+
EnvironmentEpisode,
|
|
228
|
+
EnvironmentMultiTurn,
|
|
229
|
+
EnvironmentStepResult,
|
|
230
|
+
RewardResult,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
DEFAULT_DATASET_PATH = Path(__file__).parent / "dataset" / "train.jsonl"
|
|
235
|
+
|
|
236
|
+
# How many assistant guesses the model gets before the episode is forced terminal.
|
|
237
|
+
MAX_TURNS = 5
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def load_jsonl(path: str | Path):
|
|
241
|
+
rows = []
|
|
242
|
+
with Path(path).open() as f:
|
|
243
|
+
for line in f:
|
|
244
|
+
line = line.strip()
|
|
245
|
+
if line:
|
|
246
|
+
rows.append(json.loads(line))
|
|
247
|
+
return rows
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _secret(example: TaskExample) -> int:
|
|
251
|
+
return int(str(example.output).strip())
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class StarterMultiTurnEnv(EnvironmentMultiTurn):
|
|
255
|
+
dataset = load_jsonl(DEFAULT_DATASET_PATH)
|
|
256
|
+
|
|
257
|
+
def start_episode(self, example: TaskExample, prompt_text: str):
|
|
258
|
+
# The opening prompt shown to the model. `example.input` describes the range.
|
|
259
|
+
return [
|
|
260
|
+
{
|
|
261
|
+
"role": "user",
|
|
262
|
+
"content": (
|
|
263
|
+
f"{example.input}\\n"
|
|
264
|
+
f"Reply with a single integer per turn. I will say 'higher', "
|
|
265
|
+
f"'lower', or 'correct'. You have {MAX_TURNS} guesses."
|
|
266
|
+
),
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
def max_episode_turns(self, example: TaskExample) -> int:
|
|
271
|
+
return MAX_TURNS
|
|
272
|
+
|
|
273
|
+
def step_episode(
|
|
274
|
+
self,
|
|
275
|
+
example: TaskExample,
|
|
276
|
+
messages: list,
|
|
277
|
+
assistant_response: str,
|
|
278
|
+
) -> EnvironmentStepResult:
|
|
279
|
+
# Advance the world after one assistant action. Return done=True to end the
|
|
280
|
+
# episode, or append an observation message and keep going.
|
|
281
|
+
try:
|
|
282
|
+
guess = int(assistant_response.strip().split()[0])
|
|
283
|
+
except (ValueError, IndexError):
|
|
284
|
+
return EnvironmentStepResult(
|
|
285
|
+
done=False,
|
|
286
|
+
messages=[{"role": "user", "content": "Please reply with a single integer."}],
|
|
287
|
+
)
|
|
288
|
+
secret = _secret(example)
|
|
289
|
+
if guess == secret:
|
|
290
|
+
return EnvironmentStepResult(done=True, final_response_text=str(guess))
|
|
291
|
+
hint = "higher" if guess < secret else "lower"
|
|
292
|
+
return EnvironmentStepResult(
|
|
293
|
+
done=False,
|
|
294
|
+
messages=[{"role": "user", "content": hint}],
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def score_episode(
|
|
298
|
+
self,
|
|
299
|
+
example: TaskExample,
|
|
300
|
+
episode: EnvironmentEpisode,
|
|
301
|
+
) -> RewardResult:
|
|
302
|
+
# Grade the finished transcript. Reward a correct final guess; give partial
|
|
303
|
+
# credit for getting close so GRPO has a usable gradient.
|
|
304
|
+
secret = _secret(example)
|
|
305
|
+
try:
|
|
306
|
+
final = int(str(episode.response_text).strip())
|
|
307
|
+
except ValueError:
|
|
308
|
+
return RewardResult(score=0.0, threshold=1.0)
|
|
309
|
+
if final == secret:
|
|
310
|
+
return RewardResult(score=1.0, threshold=1.0, success=True)
|
|
311
|
+
# Closeness in [0, 1): further guesses score lower.
|
|
312
|
+
closeness = max(0.0, 1.0 - abs(final - secret) / 100.0)
|
|
313
|
+
return RewardResult(score=closeness * 0.5, threshold=1.0, success=False)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def load_environment(dataset_path: str | None = None, **kwargs) -> StarterMultiTurnEnv:
|
|
317
|
+
env = StarterMultiTurnEnv()
|
|
318
|
+
if dataset_path:
|
|
319
|
+
env.dataset = load_jsonl(dataset_path)
|
|
320
|
+
return env
|
|
321
|
+
'''
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# Multi-turn rows: `input` sets up the episode, `output` is the secret number.
|
|
325
|
+
# The scalar `output` also serves as the gold single-turn SFT target; swap in
|
|
326
|
+
# `{"messages": [...]}` to teach a full gold trajectory.
|
|
327
|
+
_STARTER_DATASET_MULTITURN_JSONL = """\
|
|
328
|
+
{"input":"I picked a secret whole number between 1 and 100.","output":"42"}
|
|
329
|
+
{"input":"I picked a secret whole number between 1 and 100.","output":"73"}
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
_TURN_OPTIONS = [
|
|
334
|
+
("single", "single-turn", "one prompt -> one response"),
|
|
335
|
+
("multi", "multi-turn", "bounded episode: step_episode / score_episode"),
|
|
336
|
+
]
|
|
337
|
+
_REASONING_OPTIONS = [
|
|
338
|
+
("off", "no reasoning", "the model answers directly"),
|
|
339
|
+
("on", "reasoning", "thinking = true; spends more tokens per answer"),
|
|
340
|
+
]
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _setup_interactive(args) -> bool:
|
|
344
|
+
"""Whether to ask the setup questions interactively.
|
|
345
|
+
|
|
346
|
+
Prompt only on a real terminal a human can answer from. Fall back to defaults (no prompt) when
|
|
347
|
+
--yes is passed, under CI, or when stdin is closed/redirected, so automation never blocks on an
|
|
348
|
+
unanswered prompt. A pseudo-TTY in CI can report isatty()=True, so CI is checked explicitly; a
|
|
349
|
+
closed fd 0 can make sys.stdin None, so that is guarded before .isatty()."""
|
|
350
|
+
if getattr(args, "yes", False):
|
|
351
|
+
return False
|
|
352
|
+
if os.environ.get("CI", "").strip().lower() not in ("", "0", "false", "no"):
|
|
353
|
+
return False
|
|
354
|
+
stdin = sys.stdin
|
|
355
|
+
if stdin is None or not stdin.isatty():
|
|
356
|
+
return False
|
|
357
|
+
return render.styled()
|
|
358
|
+
|
|
359
|
+
|
|
196
360
|
def cmd_env_setup(args) -> int:
|
|
361
|
+
starter_env = Path("environment.py")
|
|
362
|
+
dataset = Path("dataset/train.jsonl")
|
|
363
|
+
# An existing environment.py is the authoritative signal for which turn mode this
|
|
364
|
+
# scaffold already uses (the dataset is plain JSONL with no reliable mode marker).
|
|
365
|
+
# Anchor to it so a re-run never leaves a single-turn env beside a multi-turn
|
|
366
|
+
# dataset (or vice versa); the flag/answer only decides the mode when starting fresh.
|
|
367
|
+
existing_multi: bool | None = None
|
|
368
|
+
anchor = "environment.py"
|
|
369
|
+
if starter_env.exists():
|
|
370
|
+
existing_multi = "EnvironmentMultiTurn" in starter_env.read_text(encoding="utf-8")
|
|
371
|
+
elif dataset.exists():
|
|
372
|
+
# No env.py to anchor on, but the starter multi-turn dataset carries a
|
|
373
|
+
# distinctive prompt; use it so we don't drop a single-turn env beside it.
|
|
374
|
+
existing_multi = "secret whole number" in dataset.read_text(encoding="utf-8")
|
|
375
|
+
anchor = "dataset/train.jsonl"
|
|
376
|
+
|
|
377
|
+
# Resolve the turn mode. An existing scaffold wins (warn if a flag disagrees); otherwise an
|
|
378
|
+
# explicit --single-turn/--multi-turn flag wins; otherwise ask on a terminal, else single-turn.
|
|
379
|
+
flag_mode = getattr(args, "turn_mode", None)
|
|
380
|
+
if existing_multi is not None:
|
|
381
|
+
if flag_mode is not None and (flag_mode == "multi") != existing_multi:
|
|
382
|
+
have = "multi-turn" if existing_multi else "single-turn"
|
|
383
|
+
want = "multi-turn" if flag_mode == "multi" else "single-turn"
|
|
384
|
+
msg = (
|
|
385
|
+
f"existing {anchor} is {have}; keeping it and ignoring --{want}. "
|
|
386
|
+
f"Delete environment.py and dataset/train.jsonl first to re-scaffold as {want}."
|
|
387
|
+
)
|
|
388
|
+
print(render.warn(msg) if render.styled() else f"warning: {msg}", file=sys.stderr)
|
|
389
|
+
multi_turn = existing_multi
|
|
390
|
+
elif flag_mode is not None:
|
|
391
|
+
multi_turn = flag_mode == "multi"
|
|
392
|
+
elif _setup_interactive(args):
|
|
393
|
+
multi_turn = (
|
|
394
|
+
render.select("How does the model interact with your task?", _TURN_OPTIONS) == "multi"
|
|
395
|
+
)
|
|
396
|
+
else:
|
|
397
|
+
multi_turn = False
|
|
398
|
+
|
|
399
|
+
# Resolve reasoning. Like the turn mode, an existing config is authoritative: configs are only
|
|
400
|
+
# written when absent, so applying a reasoning flag to an already-scaffolded project would
|
|
401
|
+
# silently no-op (or write one config with reasoning and leave the other without). Anchor to the
|
|
402
|
+
# existing config's `thinking` state and warn if a flag disagrees; otherwise the flag wins;
|
|
403
|
+
# otherwise ask on a terminal; else off.
|
|
404
|
+
rl = Path("configs/rl.toml")
|
|
405
|
+
sft = Path("configs/sft.toml")
|
|
406
|
+
existing_reasoning: bool | None = None
|
|
407
|
+
for cfg in (rl, sft):
|
|
408
|
+
if cfg.exists():
|
|
409
|
+
existing_reasoning = "thinking = true" in cfg.read_text(encoding="utf-8")
|
|
410
|
+
break
|
|
411
|
+
flag_reason = getattr(args, "reasoning", None)
|
|
412
|
+
if existing_reasoning is not None:
|
|
413
|
+
if flag_reason is not None and flag_reason != existing_reasoning:
|
|
414
|
+
have = "reasoning" if existing_reasoning else "no reasoning"
|
|
415
|
+
want = "reasoning" if flag_reason else "no-reasoning"
|
|
416
|
+
msg = (
|
|
417
|
+
f"existing configs are {have}; keeping them and ignoring --{want}. "
|
|
418
|
+
f"Delete configs/rl.toml and configs/sft.toml first to re-scaffold with --{want}."
|
|
419
|
+
)
|
|
420
|
+
print(render.warn(msg) if render.styled() else f"warning: {msg}", file=sys.stderr)
|
|
421
|
+
reasoning = existing_reasoning
|
|
422
|
+
elif flag_reason is not None:
|
|
423
|
+
reasoning = flag_reason
|
|
424
|
+
elif _setup_interactive(args):
|
|
425
|
+
reasoning = render.select("Train with reasoning (thinking)?", _REASONING_OPTIONS) == "on"
|
|
426
|
+
else:
|
|
427
|
+
reasoning = False
|
|
428
|
+
|
|
429
|
+
env_py = _STARTER_ENV_MULTITURN_PY if multi_turn else _STARTER_ENV_PY
|
|
430
|
+
dataset_jsonl = _STARTER_DATASET_MULTITURN_JSONL if multi_turn else _STARTER_DATASET_JSONL
|
|
197
431
|
Path("configs").mkdir(exist_ok=True)
|
|
198
432
|
Path("dataset").mkdir(exist_ok=True)
|
|
199
|
-
dataset = Path("dataset/train.jsonl")
|
|
200
433
|
if not dataset.exists():
|
|
201
|
-
dataset.write_text(
|
|
202
|
-
starter_env = Path("environment.py")
|
|
434
|
+
dataset.write_text(dataset_jsonl)
|
|
203
435
|
if not starter_env.exists():
|
|
204
|
-
starter_env.write_text(
|
|
436
|
+
starter_env.write_text(env_py)
|
|
205
437
|
env_comment = (
|
|
206
438
|
"# Environment: upload this project folder with\n"
|
|
207
439
|
"# `flash env push --name my-env .`, then paste the returned id below.\n"
|
|
@@ -211,27 +443,47 @@ def cmd_env_setup(args) -> int:
|
|
|
211
443
|
'id = ""\n\n'
|
|
212
444
|
'# secrets = ["SERPAPI_API_KEY"]\n\n'
|
|
213
445
|
)
|
|
214
|
-
|
|
446
|
+
# `thinking = true` opts the run into reasoning mode. Reasoning shares the generation budget with
|
|
447
|
+
# the answer, so GRPO also gets a raised max_tokens. These strings are empty when reasoning is
|
|
448
|
+
# off, keeping the default scaffold byte-for-byte identical.
|
|
449
|
+
thinking_line = "thinking = true\n" if reasoning else ""
|
|
450
|
+
rl_reasoning_train = (
|
|
451
|
+
"max_tokens = 2048 # reasoning shares this budget with the answer; raised so it isn't truncated\n"
|
|
452
|
+
if reasoning
|
|
453
|
+
else ""
|
|
454
|
+
)
|
|
455
|
+
sft_reasoning_note = (
|
|
456
|
+
"# reasoning is on (thinking = true): each gold `output` must contain a <think>...</think>\n"
|
|
457
|
+
"# block; validate locally with freesolo.datasets.warn_missing_think_tags before a real run.\n"
|
|
458
|
+
if reasoning
|
|
459
|
+
else ""
|
|
460
|
+
)
|
|
215
461
|
if not rl.exists():
|
|
216
462
|
rl.write_text(
|
|
217
463
|
'model = "Qwen/Qwen3.5-4B"\n'
|
|
218
|
-
'algorithm = "grpo"\n
|
|
464
|
+
'algorithm = "grpo"\n'
|
|
465
|
+
f"{thinking_line}"
|
|
466
|
+
"\n"
|
|
219
467
|
f"{env_comment}"
|
|
220
468
|
"[train]\n"
|
|
221
469
|
"steps = 150\n"
|
|
470
|
+
f"{rl_reasoning_train}"
|
|
222
471
|
"lora_rank = 32\n"
|
|
223
472
|
"# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
|
|
224
473
|
"# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
|
|
225
474
|
)
|
|
226
|
-
sft = Path("configs/sft.toml")
|
|
227
475
|
if not sft.exists():
|
|
228
476
|
sft.write_text(
|
|
229
477
|
'model = "Qwen/Qwen3.5-4B"\n'
|
|
230
|
-
'algorithm = "sft"\n
|
|
478
|
+
'algorithm = "sft"\n'
|
|
479
|
+
f"{thinking_line}"
|
|
480
|
+
"\n"
|
|
231
481
|
f"{env_comment}"
|
|
232
482
|
"[train]\n"
|
|
233
483
|
"epochs = 1\n"
|
|
484
|
+
"max_examples = 2 # rows to train on; the starter dataset has 2 (raise as your dataset grows)\n"
|
|
234
485
|
"lora_rank = 32\n"
|
|
486
|
+
f"{sft_reasoning_note}"
|
|
235
487
|
"# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
|
|
236
488
|
"# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
|
|
237
489
|
)
|
|
@@ -242,8 +494,8 @@ def cmd_env_setup(args) -> int:
|
|
|
242
494
|
scaffolded = [
|
|
243
495
|
"environment.py",
|
|
244
496
|
"dataset/train.jsonl",
|
|
245
|
-
"configs/rl.toml",
|
|
246
497
|
"configs/sft.toml",
|
|
498
|
+
"configs/rl.toml",
|
|
247
499
|
"TRAINING.md",
|
|
248
500
|
]
|
|
249
501
|
if render.styled():
|
|
@@ -605,7 +857,9 @@ def cmd_deploy(args) -> int:
|
|
|
605
857
|
f"OpenAI-compatible base URL: {openai_base} — point clients at this /v1 base, "
|
|
606
858
|
"not the bare endpoint (which 404s on /chat/completions)."
|
|
607
859
|
)
|
|
608
|
-
print(
|
|
860
|
+
print(
|
|
861
|
+
render.arrow(url_note) if render.styled() else f"note: {url_note}", file=sys.stderr
|
|
862
|
+
)
|
|
609
863
|
if dep.get("state") != "dry_run":
|
|
610
864
|
state = dep.get("state", "deploying")
|
|
611
865
|
if state == "failed":
|
|
@@ -273,6 +273,38 @@ def arrow(msg: str) -> str:
|
|
|
273
273
|
return _safe(f"{_paint(_glyph('→', '->'), _ACCENT2)} {_dim(msg)}")
|
|
274
274
|
|
|
275
275
|
|
|
276
|
+
def select(title: str, options: list[tuple[str, str, str]], default: int = 0) -> str:
|
|
277
|
+
"""Themed single-choice prompt; returns the chosen option's value.
|
|
278
|
+
|
|
279
|
+
``options`` is a list of ``(value, label, hint)``. The default option is marked and taken on
|
|
280
|
+
an empty answer (enter). Reads via ``input()`` so it is easy to drive in tests; the caller
|
|
281
|
+
decides *when* to prompt (interactive stdin), so this always prompts when called. On EOF the
|
|
282
|
+
default is returned so a closed stdin never hangs the scaffold."""
|
|
283
|
+
q = _paint(_glyph("?", "?"), _ACCENT, "1")
|
|
284
|
+
print(f"{q} {_bold(_safe(title))}")
|
|
285
|
+
for i, (_value, label, hint) in enumerate(options):
|
|
286
|
+
num = _paint(f"{i + 1})", _ACCENT2)
|
|
287
|
+
lab = _bold(label) if i == default else label
|
|
288
|
+
tail = f" {_dim(_safe(hint))}" if hint else ""
|
|
289
|
+
mark = _paint(" (default)", _GREEN) if i == default else ""
|
|
290
|
+
print(f" {num} {_safe(lab)}{tail}{mark}")
|
|
291
|
+
pointer = _paint(_glyph("›", ">"), _ACCENT2) # noqa: RUF001 (the glyph is the point)
|
|
292
|
+
for _ in range(5):
|
|
293
|
+
try:
|
|
294
|
+
raw = input(f"{pointer} ").strip()
|
|
295
|
+
except EOFError:
|
|
296
|
+
print()
|
|
297
|
+
return options[default][0]
|
|
298
|
+
if not raw:
|
|
299
|
+
return options[default][0]
|
|
300
|
+
if raw.isdigit():
|
|
301
|
+
idx = int(raw) - 1
|
|
302
|
+
if 0 <= idx < len(options):
|
|
303
|
+
return options[idx][0]
|
|
304
|
+
print(note(f"enter 1-{len(options)}, or press enter for the default"))
|
|
305
|
+
return options[default][0]
|
|
306
|
+
|
|
307
|
+
|
|
276
308
|
def money(value: float, decimals: int = 4) -> str:
|
|
277
309
|
return _paint(f"${value:.{decimals}f}", _TEAL)
|
|
278
310
|
|
|
@@ -391,7 +423,7 @@ def models_table(rows: list[dict]) -> str:
|
|
|
391
423
|
"""Supported base models — a clean themed list of ids (the CLI lists ids only)."""
|
|
392
424
|
dot = _glyph("•", "-")
|
|
393
425
|
ids = "\n".join(f" {_paint(dot, _FAINT)} {_paint(r['id'], _ACCENT2)}" for r in rows)
|
|
394
|
-
foot = arrow("train one with: flash train configs/
|
|
426
|
+
foot = arrow("train one with: flash train configs/sft.toml")
|
|
395
427
|
return _safe(f"{header('models', 'supported base models')}\n{ids}\n\n{foot}")
|
|
396
428
|
|
|
397
429
|
|
|
@@ -643,8 +675,8 @@ def env_setup(paths: list[str]) -> str:
|
|
|
643
675
|
labels = {
|
|
644
676
|
"environment.py": "env entrypoint — edit the reward + prompt",
|
|
645
677
|
"dataset/train.jsonl": "starter training rows",
|
|
646
|
-
"configs/rl.toml": "GRPO run config",
|
|
647
678
|
"configs/sft.toml": "SFT run config",
|
|
679
|
+
"configs/rl.toml": "GRPO run config",
|
|
648
680
|
"TRAINING.md": "how to train well — read this first",
|
|
649
681
|
}
|
|
650
682
|
keyw = max(len(p) for p in paths)
|
|
@@ -40,8 +40,8 @@ flash gpus # managed GPU classes with estimated $/hr
|
|
|
40
40
|
```text
|
|
41
41
|
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
|
-
configs/rl.toml # a GRPO (RL) run config
|
|
44
43
|
configs/sft.toml # an SFT run config
|
|
44
|
+
configs/rl.toml # a GRPO (RL) run config
|
|
45
45
|
TRAINING.md # this file
|
|
46
46
|
```
|
|
47
47
|
|
|
@@ -92,7 +92,7 @@ edit to `environment.py` or `dataset/` so the managed run uses your change.
|
|
|
92
92
|
|
|
93
93
|
```toml
|
|
94
94
|
model = "Qwen/Qwen3.5-4B" # see `flash models`
|
|
95
|
-
algorithm = "
|
|
95
|
+
algorithm = "sft" # "sft" (supervised) or "grpo" (RL)
|
|
96
96
|
# thinking = true # opt-in reasoning mode, for models that support it
|
|
97
97
|
|
|
98
98
|
[environment]
|
|
@@ -102,10 +102,11 @@ id = "your-org/my-env" # the id printed by `flash env push`
|
|
|
102
102
|
# never stored in the spec
|
|
103
103
|
|
|
104
104
|
[train]
|
|
105
|
-
|
|
105
|
+
epochs = 1 # SFT is epoch-driven; GRPO is step-driven (steps = N)
|
|
106
|
+
max_examples = 2 # rows to train on (the starter dataset has 2)
|
|
106
107
|
lora_rank = 32
|
|
107
108
|
lora_alpha = 64
|
|
108
|
-
# All GRPO
|
|
109
|
+
# All SFT/GRPO knobs live under [train]. Do not add [sft] or [grpo] tables.
|
|
109
110
|
```
|
|
110
111
|
|
|
111
112
|
GPU and HF artifacts are **fully managed** — do not pick `gpu.type` or set
|
|
@@ -117,10 +118,10 @@ extra.toml` (deep-merge) and `--set key=value` (e.g. `--set train.steps=300`).
|
|
|
117
118
|
### 4. Submit
|
|
118
119
|
|
|
119
120
|
```bash
|
|
120
|
-
flash train configs/
|
|
121
|
-
flash train configs/
|
|
122
|
-
flash train configs/
|
|
123
|
-
flash train configs/
|
|
121
|
+
flash train configs/sft.toml --dry-run # validate the config locally — no GPU, no charge
|
|
122
|
+
flash train configs/sft.toml --cost # pre-flight USD estimate, then exit
|
|
123
|
+
flash train configs/sft.toml # submit and follow logs (Ctrl-C detaches)
|
|
124
|
+
flash train configs/sft.toml --background # submit and return immediately
|
|
124
125
|
```
|
|
125
126
|
|
|
126
127
|
### 5. Monitor
|
|
@@ -159,9 +160,9 @@ Work in tight, attributable iterations. Each one is a hypothesis:
|
|
|
159
160
|
1. Reconstruct state — what's the best run so far, and what have you already tried?
|
|
160
161
|
2. Form a hypothesis — pick ONE lever and say WHY it will move the metric.
|
|
161
162
|
3. Change that ONE lever.
|
|
162
|
-
4. Validate locally — `flash train configs/
|
|
163
|
+
4. Validate locally — `flash train configs/sft.toml --dry-run` (catches config errors
|
|
163
164
|
for free; a paid run on a broken config or an all-zero reward is wasted budget).
|
|
164
|
-
5. Submit — `flash train configs/
|
|
165
|
+
5. Submit — `flash train configs/sft.toml`.
|
|
165
166
|
6. Judge — read the metric trend AND a sample of real rollouts (see below).
|
|
166
167
|
7. Keep the best run; revert the change if it didn't beat the noise band. Repeat.
|
|
167
168
|
```
|
|
@@ -181,7 +182,7 @@ different value.
|
|
|
181
182
|
A run is only evidence of improvement when **all** of these hold:
|
|
182
183
|
|
|
183
184
|
- [ ] The run reached `done` (confirmed via `flash status <run-id>`), not merely submitted.
|
|
184
|
-
- [ ] The reward trend rose (GRPO `reward_mean`)
|
|
185
|
+
- [ ] The SFT loss fell or the reward trend rose (GRPO `reward_mean`) — **beyond the noise band**, not within it.
|
|
185
186
|
- [ ] You **probed the trained adapter on real inputs** (`flash deploy` + `flash chat`), including cases it should fail — not just the metrics.
|
|
186
187
|
- [ ] The score is real behavior, not empty/truncated/templated outputs, skipped rows, leakage, a swallowed exception, or a format-only win.
|
|
187
188
|
- [ ] If you track a clean success signal separately from the shaped reward (an explicit `RewardMetric`), *that* moved too.
|
|
@@ -219,7 +220,7 @@ spending another GPU run:
|
|
|
219
220
|
## Judge the run, don't just finish it
|
|
220
221
|
|
|
221
222
|
- **Judge the trend, not a single number.** The proof of training is the curve:
|
|
222
|
-
`reward_mean` rising over steps (GRPO)
|
|
223
|
+
loss falling (SFT) or `reward_mean` rising over steps (GRPO). Record the base/early
|
|
223
224
|
value and the final value. A flat or noisy trend with no improvement is not success.
|
|
224
225
|
- **Read the model's outputs, not just the metrics.** A rising reward can come from
|
|
225
226
|
reward-hacking or a degenerate output the reward still credits — metrics alone never
|
|
@@ -480,9 +481,9 @@ flash env setup # scaffold environment.py, dataset/, confi
|
|
|
480
481
|
flash env push --name my-env . # publish the environment; paste the returned id into [environment]
|
|
481
482
|
flash env pull your-org/my-env # download a published environment into the current folder
|
|
482
483
|
flash env delete your-org/my-env -y # delete a published environment
|
|
483
|
-
flash train configs/
|
|
484
|
-
flash train configs/
|
|
485
|
-
flash train configs/
|
|
484
|
+
flash train configs/sft.toml --dry-run # validate the config locally (no GPU, no charge)
|
|
485
|
+
flash train configs/sft.toml --cost # pre-flight USD estimate, then exit
|
|
486
|
+
flash train configs/sft.toml # submit and follow logs (Ctrl-C detaches; --background to skip following)
|
|
486
487
|
flash status <run-id> # state + accrued cost
|
|
487
488
|
flash log <run-id> # reward/loss trend + worker console/error logs
|
|
488
489
|
flash log <run-id> --follow # stream a live run to completion
|
|
@@ -22,7 +22,6 @@ _KERNELS: list[tuple[str, bool]] = [
|
|
|
22
22
|
("trainable_attn_epilogue", True),
|
|
23
23
|
("fused_embedding", True),
|
|
24
24
|
("gdn", True),
|
|
25
|
-
("fused_mlp", False), # off: eval-only bf16 MLP forward, not the training activation path
|
|
26
25
|
("attn_epilogue", False), # off (eval-only; needs q/k/v out of LoRA)
|
|
27
26
|
("fp8_frozen_base", False), # off by default: speed/memory tradeoff, enable only after run A/B
|
|
28
27
|
]
|
|
@@ -54,15 +53,24 @@ def chalk_fused_ce_available(model_id: str | None = None) -> bool:
|
|
|
54
53
|
return callable(_apply)
|
|
55
54
|
|
|
56
55
|
|
|
57
|
-
def install_chalk_kernels(model=None) -> dict:
|
|
56
|
+
def install_chalk_kernels(model=None, *, fused_ce: bool = True) -> dict:
|
|
58
57
|
"""Apply chalk standalone kernels to ``model``; call AFTER TRL builds the trainer.
|
|
59
58
|
|
|
59
|
+
``fused_ce=False`` skips the fused-linear-CE kernel. flce computes the loss inside a chunked
|
|
60
|
+
LM-head+CE kernel and returns ``logits=None`` (the whole point — it never materialises the
|
|
61
|
+
``[batch, seq, vocab]`` logits). trl's ``SFTTrainer.compute_loss`` reads ``outputs.logits`` and
|
|
62
|
+
only skips it under ``use_liger_kernel=True`` — which flash can't set, since that makes trl apply
|
|
63
|
+
Liger and clash with chalk. So the SFT (trl) path passes ``fused_ce=False`` and lets the model
|
|
64
|
+
materialise logits; the custom GRPO/opd loops read the fused loss directly and keep it on.
|
|
65
|
+
|
|
60
66
|
Returns chalk's per-kernel report, or ``{}`` when freesolo-chalk isn't installed.
|
|
61
67
|
"""
|
|
62
68
|
if model is None:
|
|
63
69
|
return {}
|
|
64
70
|
|
|
65
71
|
kwargs = dict(_KERNELS)
|
|
72
|
+
if not fused_ce:
|
|
73
|
+
kwargs["fused_linear_cross_entropy"] = False
|
|
66
74
|
try:
|
|
67
75
|
from chalk.transformers import apply_chalk_kernel_to_qwen35
|
|
68
76
|
except ImportError:
|
|
@@ -114,6 +114,7 @@ from flash.engine.worker.perf import (
|
|
|
114
114
|
gpu_diagnostics,
|
|
115
115
|
grad_checkpointing_on,
|
|
116
116
|
grpo_sleep_mode,
|
|
117
|
+
grpo_use_reentrant,
|
|
117
118
|
is_cuda_oom,
|
|
118
119
|
liger_on,
|
|
119
120
|
loraplus_optimizer_cls,
|
|
@@ -377,6 +378,7 @@ __all__ = [
|
|
|
377
378
|
"grpo_mask_truncated_completions",
|
|
378
379
|
"grpo_overrides",
|
|
379
380
|
"grpo_sleep_mode",
|
|
381
|
+
"grpo_use_reentrant",
|
|
380
382
|
"heartbeat",
|
|
381
383
|
"hf_api",
|
|
382
384
|
"hf_prefix",
|