freesolo-flash-dev 0.2.28__tar.gz → 0.2.29__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.28 → freesolo_flash_dev-0.2.29}/Dockerfile.worker +20 -4
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/PKG-INFO +1 -1
- freesolo_flash_dev-0.2.29/flash/__init__.py +13 -0
- freesolo_flash_dev-0.2.29/flash/_channel.py +11 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/_fileio.py +4 -9
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/_logging.py +4 -16
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/_update_check.py +14 -82
- freesolo_flash_dev-0.2.29/flash/catalog.py +264 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/cli/__init__.py +36 -11
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/cli/commands.py +5 -45
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/cli/envpush.py +132 -12
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/cli/render.py +32 -63
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/cli/training_doc.py +1 -11
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/client/config.py +3 -11
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/client/http.py +82 -133
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/client/runtime_secrets.py +3 -19
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/client/specs.py +1 -5
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/cost/__init__.py +1 -2
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/cost/analytical.py +8 -13
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/cost/facts.py +9 -32
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/cost/spec.py +1 -2
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/cost/types.py +1 -13
- freesolo_flash_dev-0.2.29/flash/engine/__init__.py +1 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/engine/accounting.py +3 -7
- freesolo_flash_dev-0.2.29/flash/engine/chalk_kernels.py +65 -0
- freesolo_flash_dev-0.2.29/flash/engine/multiturn_rollout.py +598 -0
- freesolo_flash_dev-0.2.29/flash/engine/recipe.py +53 -0
- freesolo_flash_dev-0.2.29/flash/engine/vram.py +571 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/__init__.py +417 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/_pkg.py +24 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/engine/worker/adapter.py +67 -99
- freesolo_flash_dev-0.2.29/flash/engine/worker/decoding.py +69 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/engine/worker/finalize.py +1 -12
- freesolo_flash_dev-0.2.29/flash/engine/worker/gpu_setup.py +158 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/grpo.py +225 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/heartbeat.py +240 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/hf.py +477 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/engine/worker/kernel_warmup.py +104 -66
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/engine/worker/lora.py +36 -250
- freesolo_flash_dev-0.2.29/flash/engine/worker/packing.py +313 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/perf/__init__.py +363 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/perf/attn.py +107 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/engine/worker/perf/diagnostics.py +6 -20
- freesolo_flash_dev-0.2.29/flash/engine/worker/perf/lifecycle.py +240 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/perf/liger.py +49 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/perf/loraplus.py +18 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/perf/memory.py +128 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/rl.py +767 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/sft.py +532 -0
- freesolo_flash_dev-0.2.29/flash/engine/worker/wandb_log.py +110 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/envs/adapter.py +244 -238
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/envs/base.py +2 -7
- freesolo_flash_dev-0.2.29/flash/envs/pull.py +215 -0
- freesolo_flash_dev-0.2.29/flash/envs/registry.py +26 -0
- freesolo_flash_dev-0.2.29/flash/providers/__init__.py +37 -0
- freesolo_flash_dev-0.2.29/flash/providers/_auth.py +10 -0
- freesolo_flash_dev-0.2.29/flash/providers/_http.py +166 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/_instance.py +23 -161
- freesolo_flash_dev-0.2.29/flash/providers/_instance_bootstrap.py +384 -0
- freesolo_flash_dev-0.2.29/flash/providers/_poll.py +309 -0
- freesolo_flash_dev-0.2.29/flash/providers/allocator.py +112 -0
- freesolo_flash_dev-0.2.29/flash/providers/base.py +341 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/lambdalabs/__init__.py +4 -35
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/lambdalabs/api.py +12 -73
- freesolo_flash_dev-0.2.29/flash/providers/lambdalabs/auth.py +15 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/lambdalabs/gpus.py +1 -6
- freesolo_flash_dev-0.2.29/flash/providers/lambdalabs/jobs/__init__.py +617 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/lambdalabs/jobs/builders.py +7 -25
- freesolo_flash_dev-0.2.29/flash/providers/lambdalabs/preflight.py +15 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/lambdalabs/pricing.py +2 -9
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/lambdalabs/train.py +1 -8
- freesolo_flash_dev-0.2.29/flash/providers/preflight.py +65 -0
- freesolo_flash_dev-0.2.29/flash/providers/realized.py +56 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/runpod/__init__.py +11 -31
- freesolo_flash_dev-0.2.29/flash/providers/runpod/api.py +212 -0
- freesolo_flash_dev-0.2.29/flash/providers/runpod/auth.py +23 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/runpod/cost.py +1 -6
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/runpod/gpus.py +1 -9
- freesolo_flash_dev-0.2.29/flash/providers/runpod/jobs.py +801 -0
- freesolo_flash_dev-0.2.29/flash/providers/runpod/keys.py +97 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/runpod/preflight.py +1 -7
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/runpod/preload.py +36 -264
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/runpod/slots.py +2 -11
- freesolo_flash_dev-0.2.29/flash/providers/runpod/train/__init__.py +62 -0
- freesolo_flash_dev-0.2.29/flash/providers/runpod/train/deps.py +239 -0
- freesolo_flash_dev-0.2.29/flash/providers/runpod/train/endpoints.py +616 -0
- freesolo_flash_dev-0.2.29/flash/runner/__init__.py +525 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/runner/checkpoints.py +1 -1
- freesolo_flash_dev-0.2.29/flash/runner/deploy.py +220 -0
- freesolo_flash_dev-0.2.29/flash/runner/lifecycle.py +591 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/schema/__init__.py +18 -74
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/schema/fields.py +11 -126
- freesolo_flash_dev-0.2.29/flash/serve/deploy.py +309 -0
- freesolo_flash_dev-0.2.29/flash/serve/export.py +107 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/serve/pricing.py +2 -7
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/_deps.py +12 -12
- freesolo_flash_dev-0.2.29/flash/server/_internal_client.py +58 -0
- freesolo_flash_dev-0.2.29/flash/server/_locks.py +55 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/_runtime.py +75 -4
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/app.py +14 -4
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/auth.py +18 -85
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/billing.py +11 -39
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/billing_retry.py +10 -9
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/checkpoints.py +14 -49
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/db.py +5 -31
- freesolo_flash_dev-0.2.29/flash/server/environment_registry.py +104 -0
- freesolo_flash_dev-0.2.29/flash/server/envs.py +608 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/reconcile.py +3 -4
- freesolo_flash_dev-0.2.29/flash/server/repo_cleanup.py +296 -0
- freesolo_flash_dev-0.2.29/flash/server/routes/__init__.py +1 -0
- freesolo_flash_dev-0.2.29/flash/server/routes/envs.py +67 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/routes/runs.py +6 -38
- freesolo_flash_dev-0.2.29/flash/server/routes/serving.py +332 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/run_registry.py +7 -18
- freesolo_flash_dev-0.2.29/flash/spec.py +246 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/pyproject.toml +2 -2
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/conftest.py +11 -0
- freesolo_flash_dev-0.2.29/tests/test_b200_rollout_opt.py +144 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cancel_remote.py +81 -2
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_checkpoints.py +46 -1
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cli_render_theme.py +24 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_client.py +52 -2
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cost_analytical.py +10 -8
- freesolo_flash_dev-0.2.29/tests/test_env_delete.py +116 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_env_publish.py +411 -0
- freesolo_flash_dev-0.2.29/tests/test_env_pull.py +625 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_flash_worker.py +49 -5
- freesolo_flash_dev-0.2.29/tests/test_flashinfer_cache_dirs.py +54 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_github_urlopen_retry.py +75 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_grpo_params.py +104 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_grpo_sleep_gate.py +94 -0
- freesolo_flash_dev-0.2.29/tests/test_internal_client.py +30 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_jobs.py +256 -20
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_kernel_cache.py +8 -14
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_kv_util.py +43 -1
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_lambda_runner.py +84 -4
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_login_perms.py +15 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_mig_guard.py +9 -68
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_multiturn_rollout.py +50 -0
- freesolo_flash_dev-0.2.29/tests/test_oom_escalate_gpu.py +121 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_poll_helpers.py +4 -8
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_provider_routing.py +81 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_provider_teardown_robustness.py +0 -18
- freesolo_flash_dev-0.2.29/tests/test_repo_cleanup.py +513 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_resume_on_retry.py +23 -7
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_runmgmt.py +33 -30
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_runpod_key_waterfall.py +32 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_serve.py +87 -25
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_server_api.py +300 -9
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_server_billing.py +2 -1
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_serving_contract.py +14 -6
- freesolo_flash_dev-0.2.29/tests/test_sft_gc_off.py +158 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_spec_and_validation.py +49 -11
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_verifiers.py +73 -17
- freesolo_flash_dev-0.2.29/tests/test_vl_warmstart_adapter_keys.py +296 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_vl_warmstart_recombine.py +63 -21
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_worker_hardexit.py +42 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_worker_stack.py +51 -16
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/uv.lock +1 -1
- freesolo_flash_dev-0.2.28/flash/__init__.py +0 -29
- freesolo_flash_dev-0.2.28/flash/_channel.py +0 -28
- freesolo_flash_dev-0.2.28/flash/catalog.py +0 -331
- freesolo_flash_dev-0.2.28/flash/engine/__init__.py +0 -6
- freesolo_flash_dev-0.2.28/flash/engine/chalk_kernels.py +0 -111
- freesolo_flash_dev-0.2.28/flash/engine/multiturn_rollout.py +0 -780
- freesolo_flash_dev-0.2.28/flash/engine/recipe.py +0 -74
- freesolo_flash_dev-0.2.28/flash/engine/vram.py +0 -639
- freesolo_flash_dev-0.2.28/flash/engine/worker/__init__.py +0 -599
- freesolo_flash_dev-0.2.28/flash/engine/worker/_pkg.py +0 -30
- freesolo_flash_dev-0.2.28/flash/engine/worker/decoding.py +0 -144
- freesolo_flash_dev-0.2.28/flash/engine/worker/gpu_setup.py +0 -101
- freesolo_flash_dev-0.2.28/flash/engine/worker/grpo.py +0 -297
- freesolo_flash_dev-0.2.28/flash/engine/worker/heartbeat.py +0 -322
- freesolo_flash_dev-0.2.28/flash/engine/worker/hf.py +0 -563
- freesolo_flash_dev-0.2.28/flash/engine/worker/packing.py +0 -486
- freesolo_flash_dev-0.2.28/flash/engine/worker/perf/__init__.py +0 -495
- freesolo_flash_dev-0.2.28/flash/engine/worker/perf/attn.py +0 -176
- freesolo_flash_dev-0.2.28/flash/engine/worker/perf/lifecycle.py +0 -274
- freesolo_flash_dev-0.2.28/flash/engine/worker/perf/liger.py +0 -73
- freesolo_flash_dev-0.2.28/flash/engine/worker/perf/loraplus.py +0 -40
- freesolo_flash_dev-0.2.28/flash/engine/worker/perf/memory.py +0 -84
- freesolo_flash_dev-0.2.28/flash/engine/worker/rl.py +0 -939
- freesolo_flash_dev-0.2.28/flash/engine/worker/sft.py +0 -683
- freesolo_flash_dev-0.2.28/flash/engine/worker/wandb_log.py +0 -145
- freesolo_flash_dev-0.2.28/flash/envs/registry.py +0 -37
- freesolo_flash_dev-0.2.28/flash/providers/__init__.py +0 -54
- freesolo_flash_dev-0.2.28/flash/providers/_auth.py +0 -16
- freesolo_flash_dev-0.2.28/flash/providers/_http.py +0 -230
- freesolo_flash_dev-0.2.28/flash/providers/_instance_bootstrap.py +0 -515
- freesolo_flash_dev-0.2.28/flash/providers/_poll.py +0 -444
- freesolo_flash_dev-0.2.28/flash/providers/allocator.py +0 -165
- freesolo_flash_dev-0.2.28/flash/providers/base.py +0 -442
- freesolo_flash_dev-0.2.28/flash/providers/lambdalabs/auth.py +0 -18
- freesolo_flash_dev-0.2.28/flash/providers/lambdalabs/jobs/__init__.py +0 -831
- freesolo_flash_dev-0.2.28/flash/providers/lambdalabs/preflight.py +0 -27
- freesolo_flash_dev-0.2.28/flash/providers/preflight.py +0 -92
- freesolo_flash_dev-0.2.28/flash/providers/realized.py +0 -80
- freesolo_flash_dev-0.2.28/flash/providers/runpod/api.py +0 -278
- freesolo_flash_dev-0.2.28/flash/providers/runpod/auth.py +0 -37
- freesolo_flash_dev-0.2.28/flash/providers/runpod/jobs.py +0 -1064
- freesolo_flash_dev-0.2.28/flash/providers/runpod/keys.py +0 -145
- freesolo_flash_dev-0.2.28/flash/providers/runpod/train/__init__.py +0 -101
- freesolo_flash_dev-0.2.28/flash/providers/runpod/train/deps.py +0 -461
- freesolo_flash_dev-0.2.28/flash/providers/runpod/train/endpoints.py +0 -830
- freesolo_flash_dev-0.2.28/flash/runner/__init__.py +0 -817
- freesolo_flash_dev-0.2.28/flash/runner/deploy.py +0 -326
- freesolo_flash_dev-0.2.28/flash/runner/lifecycle.py +0 -653
- freesolo_flash_dev-0.2.28/flash/serve/deploy.py +0 -332
- freesolo_flash_dev-0.2.28/flash/serve/export.py +0 -176
- freesolo_flash_dev-0.2.28/flash/server/_locks.py +0 -54
- freesolo_flash_dev-0.2.28/flash/server/environment_registry.py +0 -102
- freesolo_flash_dev-0.2.28/flash/server/envs.py +0 -350
- freesolo_flash_dev-0.2.28/flash/server/routes/__init__.py +0 -6
- freesolo_flash_dev-0.2.28/flash/server/routes/envs.py +0 -48
- freesolo_flash_dev-0.2.28/flash/server/routes/serving.py +0 -427
- freesolo_flash_dev-0.2.28/flash/spec.py +0 -338
- freesolo_flash_dev-0.2.28/tests/test_vl_warmstart_adapter_keys.py +0 -581
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.env.example +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/README.md +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-style/README.md +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-style/generate.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-style/index.html +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-style/preview.png +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-style/themed-errors.png +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-theme/README.md +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-theme/before-after-dark.png +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-theme/before-after-light.png +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-theme/gallery-dark.png +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-theme/gallery-light.png +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-theme/help-dark.png +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/cli-theme/help-light.png +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/docs/kernel-cache.md +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cli_commands.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_vl_weight_sync.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.28 → freesolo_flash_dev-0.2.29}/tests/test_worker_thinking.py +0 -0
|
@@ -25,9 +25,22 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
|
|
25
25
|
# JIT on a cold worker writes here too. These ENVs are always set (cheap); whether the dir is
|
|
26
26
|
# pre-populated is what the opt-in bake controls. engine.worker._load_kernel_cache_if_present reads
|
|
27
27
|
# /opt/flash/kernelcache at boot.
|
|
28
|
+
# FLASHINFER_CUBIN_DIR / FLASHINFER_CACHE_DIR / FLASHINFER_WORKSPACE_BASE: FlashInfer's attention-backend
|
|
29
|
+
# cubins (the Blackwell sm120 / B200 rollout attention backend) are NOT bundled in the wheel — flashinfer
|
|
30
|
+
# downloads them from NVIDIA Artifactory on first use. Bake the cubin store + JIT cache into the same
|
|
31
|
+
# persistent kernelcache tree so
|
|
32
|
+
# a baked/volume cache serves them and a cold worker never re-fetches from Artifactory (kernel_warmup
|
|
33
|
+
# also sets these). FLASHINFER_WORKSPACE_BASE is the var flashinfer 0.6.x actually reads to relocate the
|
|
34
|
+
# JIT host-wrapper cache (FLASHINFER_CACHE_DIR is a DERIVED constant there, not an env knob), so set it
|
|
35
|
+
# too — otherwise a B200 path that touches flashinfer before kernel_warmup runs lands the wrapper under
|
|
36
|
+
# the default home workspace and re-JITs instead of using the baked cache.
|
|
28
37
|
ENV TRITON_CACHE_DIR=/opt/flash/kernelcache/triton \
|
|
29
|
-
TORCHINDUCTOR_CACHE_DIR=/opt/flash/kernelcache/inductor
|
|
30
|
-
|
|
38
|
+
TORCHINDUCTOR_CACHE_DIR=/opt/flash/kernelcache/inductor \
|
|
39
|
+
FLASHINFER_CUBIN_DIR=/opt/flash/kernelcache/flashinfer_cubin \
|
|
40
|
+
FLASHINFER_CACHE_DIR=/opt/flash/kernelcache/flashinfer \
|
|
41
|
+
FLASHINFER_WORKSPACE_BASE=/opt/flash/kernelcache/flashinfer
|
|
42
|
+
RUN mkdir -p /opt/flash/kernelcache/triton /opt/flash/kernelcache/inductor \
|
|
43
|
+
/opt/flash/kernelcache/flashinfer_cubin /opt/flash/kernelcache/flashinfer
|
|
31
44
|
|
|
32
45
|
# build-essential for Triton/Inductor (Liger) + any source builds; git for pip VCS installs.
|
|
33
46
|
RUN apt-get update && apt-get install -y --no-install-recommends build-essential git curl \
|
|
@@ -39,6 +52,7 @@ RUN pip install --no-cache-dir \
|
|
|
39
52
|
"trl>=1.6,<1.7" \
|
|
40
53
|
"peft>=0.19" \
|
|
41
54
|
"vllm==0.19.1" \
|
|
55
|
+
"flashinfer-python==0.6.6" \
|
|
42
56
|
"bitsandbytes>=0.49" \
|
|
43
57
|
"datasets>=4.7,<6" \
|
|
44
58
|
"huggingface_hub>=0.25" \
|
|
@@ -187,10 +201,12 @@ fa_ok = False; \
|
|
|
187
201
|
exec('try:\n import flash_attn\n fa_ok = True\nexcept Exception:\n fa_ok = False'); \
|
|
188
202
|
fa3_ok = False; \
|
|
189
203
|
exec('try:\n import flash_attn_interface\n fa3_ok = True\nexcept Exception:\n fa3_ok = False'); \
|
|
204
|
+
fi_ok = False; \
|
|
205
|
+
exec('try:\n import flashinfer\n fi_ok = True\nexcept Exception:\n fi_ok = False'); \
|
|
190
206
|
cudnn = torch.backends.cudnn.version(); assert cudnn, 'cuDNN not compiled into torch'; \
|
|
191
207
|
ver = lambda n: M.version(n); \
|
|
192
|
-
print('worker stack OK | torch', torch.__version__, '| cudnn', cudnn, '| triton', triton.__version__, '| flash_attn', 'present' if fa_ok else 'ABSENT (SDPA fallback)', '| flash_attn_3', 'present (Hopper FA3)' if fa3_ok else 'ABSENT (SDPA on Hopper)'); \
|
|
193
|
-
[print(' ', d, ver(d)) for d in ['transformers','trl','peft','vllm','datasets','huggingface-hub', \
|
|
208
|
+
print('worker stack OK | torch', torch.__version__, '| cudnn', cudnn, '| triton', triton.__version__, '| flash_attn', 'present' if fa_ok else 'ABSENT (SDPA fallback)', '| flash_attn_3', 'present (Hopper FA3)' if fa3_ok else 'ABSENT (SDPA on Hopper)', '| flashinfer', 'present (B200 MoE/attn)' if fi_ok else 'ABSENT (Triton fused-MoE fallback)'); \
|
|
209
|
+
[print(' ', d, ver(d)) for d in ['transformers','trl','peft','vllm','flashinfer-python','datasets','huggingface-hub', \
|
|
194
210
|
'accelerate','wandb','liger-kernel','flash-linear-attention','bitsandbytes', \
|
|
195
211
|
'hf-transfer','freesolo','runpod']]"
|
|
196
212
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.29
|
|
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
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Flash — managed LoRA post-training: log in with your freesolo key, train."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version as _dist_version
|
|
4
|
+
|
|
5
|
+
from flash._channel import DIST_NAME as _DIST_NAME
|
|
6
|
+
|
|
7
|
+
__all__ = ["__version__"]
|
|
8
|
+
|
|
9
|
+
# Read from dist metadata so version literal doesn't drift from pyproject (caused a nag loop in 0.2.20).
|
|
10
|
+
try:
|
|
11
|
+
__version__ = _dist_version(_DIST_NAME)
|
|
12
|
+
except Exception:
|
|
13
|
+
__version__ = "0+unknown"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Single switch between prod and dev channels; ``scripts/build_dev_dist.py`` rewrites CHANNEL to "dev"."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
CHANNEL = "dev"
|
|
6
|
+
|
|
7
|
+
# Must stay in lockstep with [project.scripts] in pyproject.toml.
|
|
8
|
+
CLI_NAME = "flash-dev" if CHANNEL == "dev" else "flash"
|
|
9
|
+
|
|
10
|
+
# Must stay in lockstep with [project].name in pyproject.toml.
|
|
11
|
+
DIST_NAME = "freesolo-flash-dev" if CHANNEL == "dev" else "freesolo-flash"
|
|
@@ -9,21 +9,16 @@ from pathlib import Path
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def read_json_or_empty(path: Path) -> dict:
|
|
12
|
-
"""Parse a JSON object file, returning ``{}``
|
|
12
|
+
"""Parse a JSON object file, returning ``{}`` on any error or if the root is not a dict."""
|
|
13
13
|
try:
|
|
14
|
-
|
|
14
|
+
data = json.loads(path.read_text())
|
|
15
15
|
except (OSError, ValueError):
|
|
16
16
|
return {}
|
|
17
|
+
return data if isinstance(data, dict) else {}
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def secure_json_write(path: Path, data: dict) -> None:
|
|
20
|
-
"""Write ``data`` as JSON with
|
|
21
|
-
|
|
22
|
-
Creates the parent dir (0700) and opens the file 0600 from the start — never
|
|
23
|
-
write_text + chmod, which leaves it umask-readable in between. ``O_NOFOLLOW``
|
|
24
|
-
(where available) refuses to follow a symlink planted at ``path`` so the write
|
|
25
|
-
can't be redirected to clobber an arbitrary file.
|
|
26
|
-
"""
|
|
21
|
+
"""Write ``data`` as JSON 0600. Opens with O_CREAT|0o600 — never write_text+chmod (TOCTOU). O_NOFOLLOW blocks symlink redirect."""
|
|
27
22
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
28
23
|
with contextlib.suppress(OSError):
|
|
29
24
|
os.chmod(path.parent, 0o700)
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
"""Package logging helpers.
|
|
2
|
-
|
|
3
|
-
Library code logs through the ``flash`` logger and never configures handlers on import (it
|
|
4
|
-
attaches a :class:`logging.NullHandler`), so importing Flash stays silent for downstream
|
|
5
|
-
applications. The CLI calls :func:`configure_logging` to attach a console handler whose
|
|
6
|
-
level is controlled by ``-v/--verbose``.
|
|
7
|
-
"""
|
|
1
|
+
"""Package logging helpers."""
|
|
8
2
|
|
|
9
3
|
from __future__ import annotations
|
|
10
4
|
|
|
@@ -12,8 +6,7 @@ import logging
|
|
|
12
6
|
|
|
13
7
|
_ROOT_NAME = "flash"
|
|
14
8
|
|
|
15
|
-
#
|
|
16
|
-
# importing the library produces no output unless the app opts in.
|
|
9
|
+
# NullHandler: keep library import silent; app opts in via configure_logging.
|
|
17
10
|
_root = logging.getLogger(_ROOT_NAME)
|
|
18
11
|
if not any(isinstance(h, logging.NullHandler) for h in _root.handlers):
|
|
19
12
|
_root.addHandler(logging.NullHandler())
|
|
@@ -29,20 +22,15 @@ def get_logger(name: str | None = None) -> logging.Logger:
|
|
|
29
22
|
|
|
30
23
|
|
|
31
24
|
def configure_logging(verbosity: int = 0, level: int | None = None) -> None:
|
|
32
|
-
"""Attach a console handler to the ``flash`` logger and set its level.
|
|
33
|
-
|
|
34
|
-
``verbosity`` maps repeated ``-v`` flags to levels (0=WARNING, 1=INFO, >=2=DEBUG).
|
|
35
|
-
An explicit ``level`` overrides the verbosity mapping.
|
|
36
|
-
"""
|
|
25
|
+
"""Attach a console handler to the ``flash`` logger and set its level."""
|
|
37
26
|
if level is None:
|
|
38
27
|
level = {0: logging.WARNING, 1: logging.INFO}.get(verbosity, logging.DEBUG)
|
|
39
28
|
|
|
40
29
|
logger = logging.getLogger(_ROOT_NAME)
|
|
41
30
|
logger.setLevel(level)
|
|
42
|
-
# Replace any prior console handler we installed so repeated calls don't stack handlers.
|
|
43
31
|
for h in [h for h in logger.handlers if getattr(h, "_flash_console", False)]:
|
|
44
32
|
logger.removeHandler(h)
|
|
45
|
-
handler = logging.StreamHandler()
|
|
33
|
+
handler = logging.StreamHandler()
|
|
46
34
|
handler.setLevel(level)
|
|
47
35
|
handler.setFormatter(logging.Formatter("%(levelname)s %(name)s: %(message)s"))
|
|
48
36
|
handler._flash_console = True # type: ignore[attr-defined]
|
|
@@ -1,23 +1,4 @@
|
|
|
1
|
-
"""Background "a new release is available" notice for the `flash` CLI.
|
|
2
|
-
|
|
3
|
-
The client CLI is pure standard library (no extra deps), so this is too: it queries PyPI
|
|
4
|
-
with ``urllib`` and compares the published version against the installed ``__version__``.
|
|
5
|
-
|
|
6
|
-
Design constraints that keep it from ever getting in the way:
|
|
7
|
-
|
|
8
|
-
- **Stays out of the way.** The PyPI lookup runs in a daemon thread (so it overlaps the
|
|
9
|
-
command) and every failure (offline, timeout, bad JSON) is swallowed. Only the once-a-day
|
|
10
|
-
refresh waits briefly for that thread; every other command builds the notice from cache with
|
|
11
|
-
zero network I/O.
|
|
12
|
-
- **Cached once per day.** The latest version is stored in ``~/.flash/update_check.json``;
|
|
13
|
-
we only hit PyPI when that cache is older than :data:`_CHECK_INTERVAL_S`. The check time is
|
|
14
|
-
stamped synchronously before the lookup so the daily back-off holds even if the worker thread
|
|
15
|
-
is killed at process exit before it records a result.
|
|
16
|
-
- **stderr only, TTY only.** The notice prints to stderr (never stdout), so it can't corrupt
|
|
17
|
-
JSON piped to ``jq`` or captured output, and it's suppressed entirely when stderr isn't a
|
|
18
|
-
terminal (pipes, redirects, CI, tests). Color is dropped when ``NO_COLOR`` is set.
|
|
19
|
-
- **Opt-out.** Set ``FLASH_NO_UPDATE_CHECK=1`` to disable the check and notice completely.
|
|
20
|
-
"""
|
|
1
|
+
"""Background "a new release is available" notice for the `flash` CLI."""
|
|
21
2
|
|
|
22
3
|
from __future__ import annotations
|
|
23
4
|
|
|
@@ -39,32 +20,23 @@ from flash.client.config import CONFIG_DIR
|
|
|
39
20
|
|
|
40
21
|
logger = get_logger("flash.update_check")
|
|
41
22
|
|
|
42
|
-
# The PyPI distribution name (== pyproject `name`) and the command that upgrades it. Follows the
|
|
43
|
-
# installed channel (freesolo-flash, or freesolo-flash-dev for the dev build) — see flash/_channel.py.
|
|
44
23
|
PACKAGE_NAME = DIST_NAME
|
|
45
24
|
UPGRADE_COMMAND = f"uv tool upgrade {PACKAGE_NAME}"
|
|
46
25
|
_PYPI_JSON_URL = f"https://pypi.org/pypi/{PACKAGE_NAME}/json"
|
|
47
26
|
|
|
48
27
|
CACHE_PATH = CONFIG_DIR / "update_check.json"
|
|
49
28
|
|
|
50
|
-
# Re-check PyPI at most once a day; the notice itself is shown on every command from cache.
|
|
51
29
|
_CHECK_INTERVAL_S = 24 * 60 * 60
|
|
52
|
-
#
|
|
53
|
-
# command. Keep the join >= the fetch timeout so the worker thread finishes (and records its
|
|
54
|
-
# result) within the wait instead of being killed at process exit mid-write.
|
|
30
|
+
# join >= fetch timeout so the worker finishes writing before process exit kills it.
|
|
55
31
|
_FETCH_TIMEOUT_S = 1.5
|
|
56
32
|
_JOIN_TIMEOUT_S = 2.0
|
|
57
33
|
|
|
58
34
|
_OPT_OUT_ENV = "FLASH_NO_UPDATE_CHECK"
|
|
59
35
|
|
|
60
|
-
#
|
|
61
|
-
# sequences, newlines) before printing the value to a terminal, so a poisoned cache or a hostile
|
|
62
|
-
# response can't inject escape codes into the notice. The length bound is just a sanity cap.
|
|
36
|
+
# Reject anything outside the PEP 440 charset before printing — prevents escape-code injection.
|
|
63
37
|
_SAFE_VERSION = re.compile(r"\A[A-Za-z0-9][A-Za-z0-9.+!_-]{0,63}\Z")
|
|
64
38
|
|
|
65
|
-
#
|
|
66
|
-
# Enough to order the simple versions this package ships and to spot pre-releases; this is not a
|
|
67
|
-
# full PEP 440 implementation (the stdlib-only client can't depend on `packaging`).
|
|
39
|
+
# Coarse PEP 440 subset; stdlib-only client can't depend on `packaging`.
|
|
68
40
|
_VERSION_RE = re.compile(
|
|
69
41
|
r"""\A\s*v?
|
|
70
42
|
(?P<release>\d+(?:\.\d+)*)
|
|
@@ -77,14 +49,12 @@ _VERSION_RE = re.compile(
|
|
|
77
49
|
|
|
78
50
|
|
|
79
51
|
def _enabled() -> bool:
|
|
80
|
-
"""
|
|
52
|
+
"""Off unless stderr is a TTY and the user hasn't opted out."""
|
|
81
53
|
if os.environ.get(_OPT_OUT_ENV):
|
|
82
54
|
return False
|
|
83
55
|
try:
|
|
84
56
|
return bool(sys.stderr.isatty())
|
|
85
57
|
except Exception:
|
|
86
|
-
# stderr may be detached/closed/replaced (e.g. some embedded contexts); any failure
|
|
87
|
-
# here is treated as "not a TTY" so the check can never crash a command.
|
|
88
58
|
return False
|
|
89
59
|
|
|
90
60
|
|
|
@@ -97,13 +67,7 @@ def _normalize_release(release: tuple[int, ...]) -> tuple[int, ...]:
|
|
|
97
67
|
|
|
98
68
|
|
|
99
69
|
def _version_key(version: str) -> tuple[tuple[int, ...], int, int]:
|
|
100
|
-
"""
|
|
101
|
-
|
|
102
|
-
The release segment is normalized (``1.0 == 1.0.0``). A pre-release/dev version ranks below
|
|
103
|
-
the final release of the same number (``final_rank`` 0 vs 1); a post-release ranks above it
|
|
104
|
-
via ``post``. Epochs and local versions are ignored — the catalog ships only simple versions.
|
|
105
|
-
Returns an empty release for unparseable input, which compares as "older than everything".
|
|
106
|
-
"""
|
|
70
|
+
"""Sort key ``(release, final_rank, post)`` where higher means newer."""
|
|
107
71
|
match = _VERSION_RE.match(version or "")
|
|
108
72
|
if not match:
|
|
109
73
|
return ((), 1, 0)
|
|
@@ -127,19 +91,12 @@ def _is_newer(latest: str, current: str) -> bool:
|
|
|
127
91
|
|
|
128
92
|
|
|
129
93
|
def _clean_version(value: object) -> str | None:
|
|
130
|
-
"""Return ``value``
|
|
131
|
-
|
|
132
|
-
Guards both the PyPI response and the on-disk cache: ``_version_key`` parses just the
|
|
133
|
-
numeric/marker prefix, so without this an injected suffix (ANSI codes, newlines) could reach
|
|
134
|
-
the terminal. Non-strings (and anything outside the PEP 440 charset) are rejected.
|
|
135
|
-
"""
|
|
94
|
+
"""Return ``value`` if it's a safe version string, else ``None``."""
|
|
136
95
|
return value if isinstance(value, str) and _SAFE_VERSION.match(value) else None
|
|
137
96
|
|
|
138
97
|
|
|
139
98
|
def _read_cache() -> dict:
|
|
140
|
-
#
|
|
141
|
-
# make the ``.get()`` callers raise, and _check_due runs before main()'s error handling — so
|
|
142
|
-
# coerce anything that isn't a dict back to an empty one.
|
|
99
|
+
# Coerce non-dict results (e.g. []) since _check_due runs before main()'s error handling.
|
|
143
100
|
cache = read_json_or_empty(CACHE_PATH)
|
|
144
101
|
return cache if isinstance(cache, dict) else {}
|
|
145
102
|
|
|
@@ -168,21 +125,13 @@ def _fetch_latest_version(timeout: float = _FETCH_TIMEOUT_S) -> str | None:
|
|
|
168
125
|
except (urllib.error.URLError, OSError, ValueError, TimeoutError) as exc:
|
|
169
126
|
logger.debug("update check: PyPI lookup failed: %s", exc)
|
|
170
127
|
return None
|
|
171
|
-
# Expected shape is {"info": {"version": ...}}; tolerate anything else (a proxy error page,
|
|
172
|
-
# ``[]``, ``{"info": null}``, ...) instead of letting a dereference raise into the caller.
|
|
173
128
|
info = payload.get("info") if isinstance(payload, dict) else None
|
|
174
129
|
version = info.get("version") if isinstance(info, dict) else None
|
|
175
130
|
return _clean_version(version)
|
|
176
131
|
|
|
177
132
|
|
|
178
133
|
def _stamp_check_time() -> None:
|
|
179
|
-
"""Record "checked just now"
|
|
180
|
-
|
|
181
|
-
Done before the background lookup starts so the daily back-off holds even if the daemon worker
|
|
182
|
-
is killed at process exit before it records its own result — otherwise a stale/missing cache
|
|
183
|
-
would make every command re-run (and wait on) the lookup. Never raises (runs before main()'s
|
|
184
|
-
error handling).
|
|
185
|
-
"""
|
|
134
|
+
"""Record "checked just now" synchronously so the daily back-off holds even if the daemon is killed."""
|
|
186
135
|
with contextlib.suppress(Exception):
|
|
187
136
|
cache = _read_cache()
|
|
188
137
|
cache["checked_at"] = time.time()
|
|
@@ -190,11 +139,7 @@ def _stamp_check_time() -> None:
|
|
|
190
139
|
|
|
191
140
|
|
|
192
141
|
def _refresh_cache() -> None:
|
|
193
|
-
"""Fetch from PyPI and persist the version
|
|
194
|
-
|
|
195
|
-
The attempt time is already stamped by :func:`_stamp_check_time`, so a failed lookup just
|
|
196
|
-
returns and lets the daily back-off (set there) stand.
|
|
197
|
-
"""
|
|
142
|
+
"""Fetch from PyPI and persist the version; runs in a daemon thread, never raises."""
|
|
198
143
|
try:
|
|
199
144
|
latest = _fetch_latest_version()
|
|
200
145
|
if not latest:
|
|
@@ -203,7 +148,7 @@ def _refresh_cache() -> None:
|
|
|
203
148
|
cache["checked_at"] = time.time()
|
|
204
149
|
cache["pypi_version"] = latest
|
|
205
150
|
secure_json_write(CACHE_PATH, cache)
|
|
206
|
-
except Exception as exc:
|
|
151
|
+
except Exception as exc:
|
|
207
152
|
logger.debug("update check: refresh failed: %s", exc)
|
|
208
153
|
|
|
209
154
|
|
|
@@ -218,7 +163,6 @@ def _red(text: str) -> str:
|
|
|
218
163
|
def _build_notice() -> str | None:
|
|
219
164
|
"""Build the upgrade notice from the cached PyPI version, or ``None`` if up to date."""
|
|
220
165
|
latest = _clean_version(_read_cache().get("pypi_version"))
|
|
221
|
-
# Only nudge toward stable releases: never advertise a pre-release (rc/dev) as an upgrade.
|
|
222
166
|
if not latest or _is_prerelease(latest) or not _is_newer(latest, __version__):
|
|
223
167
|
return None
|
|
224
168
|
return _red(
|
|
@@ -228,38 +172,26 @@ def _build_notice() -> str | None:
|
|
|
228
172
|
|
|
229
173
|
|
|
230
174
|
def maybe_start_update_check() -> threading.Thread | None:
|
|
231
|
-
"""Kick off a background PyPI refresh if one is due
|
|
232
|
-
|
|
233
|
-
Pass the return value to :func:`emit_update_notice`. No-ops (returns ``None``) when the
|
|
234
|
-
feature is disabled or the cached check is still fresh, so the common path is free.
|
|
235
|
-
"""
|
|
175
|
+
"""Kick off a background PyPI refresh if one is due; returns the thread (or ``None``)."""
|
|
236
176
|
if not _enabled() or not _check_due(time.time()):
|
|
237
177
|
return None
|
|
238
|
-
# Stamp the attempt synchronously before spawning the worker, so the daily back-off holds even
|
|
239
|
-
# if the daemon is killed at process exit before it writes (see _stamp_check_time).
|
|
240
178
|
_stamp_check_time()
|
|
241
179
|
thread = threading.Thread(target=_refresh_cache, name="flash-update-check", daemon=True)
|
|
242
180
|
try:
|
|
243
181
|
thread.start()
|
|
244
182
|
except RuntimeError:
|
|
245
|
-
# can't spawn a thread (e.g. interpreter shutting down) — skip the check silently.
|
|
246
183
|
return None
|
|
247
184
|
return thread
|
|
248
185
|
|
|
249
186
|
|
|
250
187
|
def emit_update_notice(notifier: threading.Thread | None = None) -> None:
|
|
251
|
-
"""Print the upgrade notice (if any) to stderr at the end of a command.
|
|
252
|
-
|
|
253
|
-
Briefly waits for an in-flight refresh so a freshly fetched version can be shown the same
|
|
254
|
-
run; if it doesn't finish in time we just use whatever is already cached.
|
|
255
|
-
"""
|
|
188
|
+
"""Print the upgrade notice (if any) to stderr at the end of a command."""
|
|
256
189
|
if not _enabled():
|
|
257
190
|
return
|
|
258
191
|
if notifier is not None:
|
|
259
192
|
with contextlib.suppress(RuntimeError):
|
|
260
193
|
notifier.join(timeout=_JOIN_TIMEOUT_S)
|
|
261
|
-
#
|
|
262
|
-
# (`flash ... | head`), full disk, or closed stderr would otherwise crash the command.
|
|
194
|
+
# Must never raise: broken pipe or closed stderr would crash the command.
|
|
263
195
|
with contextlib.suppress(Exception):
|
|
264
196
|
notice = _build_notice()
|
|
265
197
|
if notice:
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""Curated model catalog for one-consumer-GPU LoRA jobs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from dataclasses import asdict, dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
ALGORITHMS = ("sft", "grpo")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def normalize_algorithm(value: str) -> str:
|
|
13
|
+
"""Canonical (lowercased, validated) algorithm name."""
|
|
14
|
+
if not value:
|
|
15
|
+
value = "grpo"
|
|
16
|
+
elif not isinstance(value, str):
|
|
17
|
+
# A truthy non-string (e.g. a JSON number/bool/array) would AttributeError on .lower(), which
|
|
18
|
+
# escapes the callers' ValueError/ConfigError guards -> uncaught 500. Raise ValueError instead.
|
|
19
|
+
raise ValueError(f"algorithm must be a string, got {type(value).__name__}")
|
|
20
|
+
value = value.lower()
|
|
21
|
+
if value not in ALGORITHMS:
|
|
22
|
+
raise ValueError(f"unsupported algorithm: {value}; known: {', '.join(ALGORITHMS)}")
|
|
23
|
+
return value
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
DEFAULT_GPU = "RTX 5090"
|
|
27
|
+
|
|
28
|
+
# Over-estimating is memory-safe (larger VRAM estimate, smaller cap); fallback = largest catalog vocab.
|
|
29
|
+
_DEFAULT_VOCAB_SIZE = 248_320
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class ModelInfo:
|
|
34
|
+
id: str
|
|
35
|
+
display_name: str
|
|
36
|
+
params: str
|
|
37
|
+
algos: tuple[str, ...]
|
|
38
|
+
min_vram_gb: int
|
|
39
|
+
# Total parameters in billions — the numeric model size the cost estimator + VRAM equations read
|
|
40
|
+
# DIRECTLY (no parsing of the ``params`` display string). Drives the memory/size terms (VRAM, disk,
|
|
41
|
+
# download), which always size the FULL checkpoint. REQUIRED: every ModelInfo must state it — a
|
|
42
|
+
# curated catalog model sets its true count, and the open-model policy passes the HF/estimated count
|
|
43
|
+
# (or 0.0 when the size is genuinely "unknown size"). ``test_every_catalog_entry_sets_params_b``
|
|
44
|
+
# asserts every curated MODELS entry sets it > 0, so a new entry can never silently fall back to a
|
|
45
|
+
# parsed string again.
|
|
46
|
+
params_b: float
|
|
47
|
+
quant: str = "bf16"
|
|
48
|
+
recommended_gpu: str = DEFAULT_GPU
|
|
49
|
+
# 0 => GRPO uses min_vram_gb like SFT; set when colocated vLLM rollout needs a bigger card.
|
|
50
|
+
grpo_min_vram_gb: int = 0
|
|
51
|
+
# 0 => SFT sizes from param-based estimate; set only when a model must not down-route to the cheapest card.
|
|
52
|
+
sft_min_vram_gb: int = 0
|
|
53
|
+
# vLLM sleep mode (offload the colocate rollout engine between GRPO steps) is NON-FUNCTIONAL for
|
|
54
|
+
# this model: the wake/reload HANGS the rollout (a ~70 GB weight reallocation can't be placed in
|
|
55
|
+
# the fragmented non-expandable allocator sleep forces -- live-confirmed on the 35B-A3B, every
|
|
56
|
+
# attempt stalled). So this model is RESIDENT-ONLY: a config that doesn't fit resident must be
|
|
57
|
+
# REJECTED (model_required_vram_gb sizes it on the resident peak) rather than routed to the hanging
|
|
58
|
+
# sleep path. grpo_sleep_mode raises for it instead of ever returning True. Dense/small models that
|
|
59
|
+
# sleep cleanly leave this False.
|
|
60
|
+
sleep_unsupported: bool = False
|
|
61
|
+
notes: str = ""
|
|
62
|
+
# 0 = platform default (64 GB) suffices. Runner raises gpu.disk_gb to at least this.
|
|
63
|
+
min_disk_gb: int = 0
|
|
64
|
+
# "none" / "hybrid" (Qwen3-style) / "always" (can't disable) / "unknown" (open-model policy)
|
|
65
|
+
thinking: str = "none"
|
|
66
|
+
vocab_size: int = _DEFAULT_VOCAB_SIZE
|
|
67
|
+
# Parameters ACTIVE per token in billions — only meaningful for an MoE, where a token routes
|
|
68
|
+
# through a small subset of experts. The cost estimator's per-token FLOPs/step-time term reads
|
|
69
|
+
# this (a token exercises only the active params), while VRAM/disk/download keep using the total
|
|
70
|
+
# ``params_b``. 0.0 (the dense default) means "same as params_b" — every token hits every param.
|
|
71
|
+
active_params_b: float = 0.0
|
|
72
|
+
# Transformer geometry (decoder layers x hidden width) — the SFT gradient-checkpointing-OFF gate
|
|
73
|
+
# sizes the no-recompute activation peak from these (engine.vram.sft_gc_off_peak_gb). 0/0 (the
|
|
74
|
+
# default) means "unknown": the worker falls back to reading the HF config at runtime, and the
|
|
75
|
+
# GC-off gate stays conservative (keeps GC on) if neither is available. Curated for the MoE whose
|
|
76
|
+
# SFT runs the gate — a live B200 SFT showed the runtime AutoConfig probe returning (0, 0) on the
|
|
77
|
+
# multimodal-nested config, so the curated values are what actually engage the gate.
|
|
78
|
+
num_layers: int = 0
|
|
79
|
+
hidden_size: int = 0
|
|
80
|
+
|
|
81
|
+
def to_dict(self) -> dict[str, Any]:
|
|
82
|
+
return asdict(self)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
DEFAULT_MODEL = "Qwen/Qwen3.5-4B"
|
|
86
|
+
|
|
87
|
+
MODELS: dict[str, ModelInfo] = {
|
|
88
|
+
"openbmb/MiniCPM5-1B": ModelInfo(
|
|
89
|
+
id="openbmb/MiniCPM5-1B",
|
|
90
|
+
display_name="MiniCPM5 1B",
|
|
91
|
+
params="1.2B dense (Llama arch)",
|
|
92
|
+
params_b=1.2,
|
|
93
|
+
vocab_size=130_560,
|
|
94
|
+
algos=("sft", "grpo"),
|
|
95
|
+
min_vram_gb=12,
|
|
96
|
+
recommended_gpu="RTX 4090",
|
|
97
|
+
thinking="hybrid",
|
|
98
|
+
notes="On-device class SLM (131k ctx); standard Llama architecture.",
|
|
99
|
+
),
|
|
100
|
+
"Qwen/Qwen3.5-0.8B": ModelInfo(
|
|
101
|
+
id="Qwen/Qwen3.5-0.8B",
|
|
102
|
+
display_name="Qwen3.5 0.8B",
|
|
103
|
+
params="0.9B (text-only fine-tune)",
|
|
104
|
+
params_b=0.9,
|
|
105
|
+
vocab_size=248_320,
|
|
106
|
+
algos=("sft", "grpo"),
|
|
107
|
+
min_vram_gb=12,
|
|
108
|
+
recommended_gpu="RTX 4090",
|
|
109
|
+
thinking="hybrid",
|
|
110
|
+
notes="Smallest Qwen3.5; cheap smoke/dev runs with the modern arch.",
|
|
111
|
+
),
|
|
112
|
+
"Qwen/Qwen3.5-2B": ModelInfo(
|
|
113
|
+
id="Qwen/Qwen3.5-2B",
|
|
114
|
+
display_name="Qwen3.5 2B",
|
|
115
|
+
params="2.3B (text-only fine-tune)",
|
|
116
|
+
params_b=2.3,
|
|
117
|
+
vocab_size=248_320,
|
|
118
|
+
algos=("sft", "grpo"),
|
|
119
|
+
min_vram_gb=16,
|
|
120
|
+
recommended_gpu="RTX 4090",
|
|
121
|
+
thinking="hybrid",
|
|
122
|
+
),
|
|
123
|
+
"Qwen/Qwen3.5-4B": ModelInfo(
|
|
124
|
+
id="Qwen/Qwen3.5-4B",
|
|
125
|
+
display_name="Qwen3.5 4B",
|
|
126
|
+
params="4.7B (text-only fine-tune)",
|
|
127
|
+
params_b=4.7,
|
|
128
|
+
vocab_size=248_320,
|
|
129
|
+
algos=("sft", "grpo"),
|
|
130
|
+
min_vram_gb=32,
|
|
131
|
+
recommended_gpu="RTX 5090",
|
|
132
|
+
thinking="hybrid",
|
|
133
|
+
notes="Current-gen 4B. GRPO uses the sleep-mode memory recipe (hybrid arch needs "
|
|
134
|
+
"extra engine state-cache); fused DeltaNet kernels ship in the default stack.",
|
|
135
|
+
),
|
|
136
|
+
"Qwen/Qwen3.5-9B": ModelInfo(
|
|
137
|
+
id="Qwen/Qwen3.5-9B",
|
|
138
|
+
display_name="Qwen3.5 9B",
|
|
139
|
+
params="9.7B (text-only fine-tune)",
|
|
140
|
+
params_b=9.7,
|
|
141
|
+
vocab_size=248_320,
|
|
142
|
+
algos=("sft", "grpo"),
|
|
143
|
+
min_vram_gb=48,
|
|
144
|
+
# NOT QLoRA: peft bnb merge during GRPO rollout diverges trainer precision -> TRL ratio collapses to 0.
|
|
145
|
+
grpo_min_vram_gb=80,
|
|
146
|
+
quant="bf16",
|
|
147
|
+
recommended_gpu="A100 PCIe",
|
|
148
|
+
thinking="hybrid",
|
|
149
|
+
notes="bf16 LoRA. ~19 GB of weights; SFT fits a 48 GB card, while colocated GRPO "
|
|
150
|
+
"(two bf16 copies + KV + the 248k-vocab fp32 logits) needs an 80 GB-class card "
|
|
151
|
+
"(grpo_min_vram_gb floor).",
|
|
152
|
+
),
|
|
153
|
+
"Qwen/Qwen3.6-35B-A3B": ModelInfo(
|
|
154
|
+
id="Qwen/Qwen3.6-35B-A3B",
|
|
155
|
+
display_name="Qwen3.6 35B-A3B (MoE)",
|
|
156
|
+
params="35B total / ~3B active (MoE)",
|
|
157
|
+
# 35.0 not 35.95: the marketing figure tips the SFT equation over the B200 budget (see test_sft_equation_covers_honest_peak_across_seq_boundary).
|
|
158
|
+
params_b=35.0,
|
|
159
|
+
active_params_b=3.0,
|
|
160
|
+
# Geometry for the SFT GC-off activation estimate (config.json text_config): 40 decoder
|
|
161
|
+
# layers x 2048 hidden (hybrid GatedDeltaNet + full-attention, 256 experts / 8 active).
|
|
162
|
+
num_layers=40,
|
|
163
|
+
hidden_size=2048,
|
|
164
|
+
vocab_size=248_320,
|
|
165
|
+
algos=("sft", "grpo"),
|
|
166
|
+
min_vram_gb=141,
|
|
167
|
+
# Floor to 100 GB so SFT lands on H200, not the thin-margin consumer Blackwell or 80 GB H100.
|
|
168
|
+
sft_min_vram_gb=100,
|
|
169
|
+
# Floor also engages grpo_seq_escalation_gb: long (>16k) rollouts are rejected at parse time instead of OOMing in vLLM.
|
|
170
|
+
grpo_min_vram_gb=180,
|
|
171
|
+
# vLLM sleep mode HANGS the 35B colocate rollout (wake/reload stalls — live-confirmed, every
|
|
172
|
+
# attempt). So GRPO is RESIDENT-ONLY: model_required_vram_gb sizes on the resident peak and a
|
|
173
|
+
# config too long to fit resident is REJECTED at parse time (not routed to the hanging sleep).
|
|
174
|
+
sleep_unsupported=True,
|
|
175
|
+
quant="bf16",
|
|
176
|
+
recommended_gpu="H200",
|
|
177
|
+
thinking="hybrid",
|
|
178
|
+
min_disk_gb=200,
|
|
179
|
+
notes="MoE (35B total / ~3B active), bf16 LoRA. SFT runs on the 141 GB H200 (the ~70 GB "
|
|
180
|
+
"weights dominate; active-3B compute keeps activations/KV tiny, so context is ~unbounded by "
|
|
181
|
+
"VRAM); colocated GRPO needs the 180 GB B200 (trainer + vLLM rollout = two 70 GB copies).",
|
|
182
|
+
),
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def list_models() -> list[ModelInfo]:
|
|
187
|
+
return sorted(MODELS.values(), key=lambda m: (m.min_vram_gb, m.id))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def get_model(model_id: str) -> ModelInfo:
|
|
191
|
+
try:
|
|
192
|
+
return MODELS[model_id]
|
|
193
|
+
except KeyError as exc:
|
|
194
|
+
allowed = ", ".join(MODELS)
|
|
195
|
+
raise ValueError(
|
|
196
|
+
f"unsupported model {model_id!r}; choose one of: {allowed} — or set "
|
|
197
|
+
f'model_policy = "allow" in the config to run any HF model that fits the GPU '
|
|
198
|
+
f"(open-model policy)"
|
|
199
|
+
) from exc
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def vocab_size_for(model_id: str) -> int:
|
|
203
|
+
"""Curated vocab_size for a model, or the safe default for open-model-policy entries."""
|
|
204
|
+
info = MODELS.get(model_id)
|
|
205
|
+
return info.vocab_size if info is not None else _DEFAULT_VOCAB_SIZE
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def resolve_model(
|
|
209
|
+
model_id: str,
|
|
210
|
+
algorithm: str,
|
|
211
|
+
policy: str = "catalog",
|
|
212
|
+
gpu: str | None = None,
|
|
213
|
+
) -> ModelInfo:
|
|
214
|
+
"""Resolve a model under the configured policy; "allow" accepts any HF model."""
|
|
215
|
+
algo = normalize_algorithm(algorithm)
|
|
216
|
+
if model_id in MODELS:
|
|
217
|
+
return validate_model_for_algorithm(model_id, algo)
|
|
218
|
+
if policy != "allow":
|
|
219
|
+
return get_model(model_id)
|
|
220
|
+
return _resolve_open_model(model_id, algo, gpu)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _resolve_open_model(model_id: str, algo: str, gpu: str | None) -> ModelInfo:
|
|
224
|
+
"""Synthesize a ModelInfo for the open-model "allow" policy via a coarse HF VRAM-fit estimate."""
|
|
225
|
+
from flash.engine.vram import check_fit
|
|
226
|
+
|
|
227
|
+
est = check_fit(model_id, algo, gpu or DEFAULT_GPU)
|
|
228
|
+
if est.verdict == "too_big":
|
|
229
|
+
raise ValueError(
|
|
230
|
+
f"{model_id} does not fit the requested GPU: {est.describe()}. "
|
|
231
|
+
f"Pick a smaller model or a larger supported GPU."
|
|
232
|
+
)
|
|
233
|
+
if est.verdict in ("tight", "unknown"):
|
|
234
|
+
print(f"warning: open-model policy: {est.describe()}")
|
|
235
|
+
params = f"{est.params_b:.1f}B" if est.params_b else "unknown size"
|
|
236
|
+
min_disk = int(est.params_b * 2) + 64 if est.params_b else 0
|
|
237
|
+
return ModelInfo(
|
|
238
|
+
id=model_id,
|
|
239
|
+
display_name=model_id,
|
|
240
|
+
params=params,
|
|
241
|
+
# Carry the estimated/HF param count straight through (0.0 when size is unknown) so downstream
|
|
242
|
+
# sizing reads ``params_b`` directly — no re-parsing the display string.
|
|
243
|
+
params_b=est.params_b or 0.0,
|
|
244
|
+
algos=ALGORITHMS,
|
|
245
|
+
min_vram_gb=math.ceil(est.est_gb) if est.est_gb else 24,
|
|
246
|
+
min_disk_gb=min_disk,
|
|
247
|
+
recommended_gpu=gpu or DEFAULT_GPU,
|
|
248
|
+
thinking="unknown",
|
|
249
|
+
notes="unlisted model accepted via the open-model policy (not curated/validated)",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def validate_model_for_algorithm(model_id: str, algorithm: str) -> ModelInfo:
|
|
254
|
+
info = get_model(model_id)
|
|
255
|
+
algo = normalize_algorithm(algorithm)
|
|
256
|
+
required = "grpo" if algo == "grpo" else "sft"
|
|
257
|
+
if required not in info.algos:
|
|
258
|
+
allowed = ", ".join(info.algos)
|
|
259
|
+
raise ValueError(f"{model_id} supports {allowed}, not {algo}")
|
|
260
|
+
return info
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def public_model_rows() -> list[dict[str, Any]]:
|
|
264
|
+
return [m.to_dict() for m in list_models()]
|