freesolo-flash-dev 1.0.1__tar.gz → 1.0.2__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-1.0.1 → freesolo_flash_dev-1.0.2}/PKG-INFO +1 -1
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cli/training_doc.py +42 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/runner/verified_revisions.py +7 -1
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/serve/export.py +60 -17
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/_deps.py +35 -4
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/routes/runs.py +22 -6
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/pyproject.toml +2 -2
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_export.py +78 -6
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_server_api.py +109 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_verified_revisions.py +29 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/uv.lock +1 -1
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.claude/skills/verify/SKILL.md +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.dockerignore +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.env.example +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/bake-kernel-cache.yml +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/notify-tests-repo.yml +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.gitignore +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/Dockerfile +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/LICENSE +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/README.md +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/_channel.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/_logging.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/adapter_artifacts.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/catalog.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cli/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cli/_tty.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cli/commands.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cli/env_setup.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cli/render.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/client/config.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/client/http.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cost/analytical.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cost/facts.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/diagnostics.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/steps.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/structured_outputs.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/adapter.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/hf.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/opd.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/opd_gkd.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/opd_vllm.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/rl.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/rng.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/sft.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/teacher.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/tokenizer_align.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/engine/worker_entrypoint.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/envs/adapter.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/envs/archive.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/envs/archive_policy.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/envs/loader.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/lora_rank.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/opd_retry_contract.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/_deadline.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/_hf_artifacts.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/_hf_retry.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/_instance_poll.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/_instance_provider.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/_worker.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/vast/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/vast/api.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/vast/auth.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/vast/jobs/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/vast/jobs/builders.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/vast/preflight.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/providers/vast/pricing.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/py.typed +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/runner/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/schema/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/serve/deploy.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/serve/urls.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/app.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/auth.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/db.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/envs.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/repo_cleanup.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/routes/meta.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/flash/spec.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/_helpers/vast.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/conftest.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/live/test_vast_live.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_accounting_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_base_model_provenance.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_billing_retry.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_charge_pricing.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cli_commands.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cli_commands_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cli_help.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cli_main_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cli_render_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_client.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_client_server_integration.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_controlled_experiment_repairs.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_cache_evict.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_download.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_download_api.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_publish.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_pull_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_pull_loader_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_pull_managed_control_plane.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_flash_worker.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_health_liveness.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_hf_retry.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_instance_bootstrap_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_jobs.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_lambda_api_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_lora_rank_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_lora_rank_preflight.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_multiturn_rollout_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_multiturn_rollout_records.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_multiturn_rollout_request_policy.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_opd.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_opd_full_state_resume.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_opd_resume_safety.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_opd_vllm.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_packing_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_provider_preflight_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_repo_cleanup.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_serve.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_server_app_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_server_billing.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_server_checkpoints_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_server_db.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_server_envs_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_server_locks_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_serving_context_preflight.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_serving_pricing_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_serving_schema_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_sft_example_selection.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_sft_max_context.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_spec_and_validation.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_structured_outputs.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_train_context_preflight.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_training_controls.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_vast_api.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_vast_api_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_vast_offers.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_vast_provider_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_vast_runner.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_version.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_wandb_log_coverage.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/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: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Flash — managed LoRA post-training (SFT/GRPO/OPD) for Freesolo environments, driven by the `flash` CLI
|
|
5
5
|
Project-URL: Homepage, https://github.com/freesolo-co/flash
|
|
6
6
|
Project-URL: Repository, https://github.com/freesolo-co/flash
|
|
@@ -154,6 +154,47 @@ flash undeploy <run-id> # tear the endpoint down
|
|
|
154
154
|
flash export --adapter-id <run-id> --repository <you>/<repo> # copy adapter weights to your HF repo
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
+
### Loading an exported adapter locally (transformers + peft)
|
|
158
|
+
|
|
159
|
+
Flash trains the Qwen3.5/3.6 family against the full multimodal module tree, so adapter
|
|
160
|
+
weights saved by the worker carry a `language_model.` infix in their tensor keys
|
|
161
|
+
(`base_model.model.model.language_model.layers.*`). During `flash export`, adapters that
|
|
162
|
+
can be represented in the text-only namespace are normalized to
|
|
163
|
+
`base_model.model.model.layers.*`. When an export retains non-LM tensors, it keeps the
|
|
164
|
+
multimodal namespace instead. Do not infer the namespace solely from whether `flash export`
|
|
165
|
+
was used: inspect the keys. For normalized keys, load with vanilla peft and
|
|
166
|
+
`AutoModelForCausalLM`:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from peft import PeftModel
|
|
170
|
+
from transformers import AutoModelForCausalLM
|
|
171
|
+
|
|
172
|
+
base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3.5-0.8B") # qwen3.5 causal-lm class
|
|
173
|
+
model = PeftModel.from_pretrained(base, "<you>/<repo>")
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Before you trust any local eval, check the key namespace matches the model class you
|
|
177
|
+
loaded.** peft does NOT error on mismatched keys — it emits a `UserWarning` about missing
|
|
178
|
+
adapter keys and silently applies *nothing*, so you would benchmark the bare base model
|
|
179
|
+
believing it is your adapter. If the inspected keys carry the `language_model.` infix,
|
|
180
|
+
load the base with the multimodal class whose parameters live under that namespace instead:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
# infixed keys (base_model.model.model.language_model.layers.*) need the multimodal class:
|
|
184
|
+
from transformers import AutoModelForImageTextToText
|
|
185
|
+
|
|
186
|
+
base = AutoModelForImageTextToText.from_pretrained("Qwen/Qwen3.5-0.8B")
|
|
187
|
+
# qwen3.5/3.6 dense models resolve to Qwen3_5ForConditionalGeneration
|
|
188
|
+
# qwen3.6-35b-a3b (moe) resolves to Qwen3_5MoeForConditionalGeneration
|
|
189
|
+
model = PeftModel.from_pretrained(base, "<you>/<repo>")
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
To check which form you have, read the safetensors key names (stdlib, no torch needed):
|
|
193
|
+
the JSON header of `adapter_model.safetensors` lists every tensor key. Rule of thumb:
|
|
194
|
+
`model.layers.*` keys pair with `AutoModelForCausalLM`; `model.language_model.layers.*`
|
|
195
|
+
keys pair with the `*ForConditionalGeneration` class. After loading, confirm the adapter
|
|
196
|
+
actually applied: outputs (or a probe metric) must differ from the bare base model.
|
|
197
|
+
|
|
157
198
|
The rest of this file is about doing the above *well* — designing a reward that teaches,
|
|
158
199
|
and deciding honestly whether a run improved.
|
|
159
200
|
|
|
@@ -221,6 +262,7 @@ spending another GPU run:
|
|
|
221
262
|
| Run looks stuck after disconnecting | Terminal stopped streaming but the job may still be alive | Ctrl-C detaches. Use `flash log <run-id> --follow` to reattach, `flash log <run-id>` for the console/error output, or `flash cancel <run-id>` if you intentionally want to stop it. |
|
|
222
263
|
| Final checkpoint regresses | Last step is worse than an earlier checkpoint | Run `flash checkpoints <run-id>`, deploy a specific step with `flash deploy <run-id>/step-N`, and compare with held-out probes before exporting or relying on the final adapter. |
|
|
223
264
|
| Export fails before upload | CLI says no HuggingFace token | Pass `flash export --api-key hf_...`, or set `HF_TOKEN` in your shell, `.env`, or `.env.local`. Exports are private unless you pass `--public`. |
|
|
265
|
+
| Exported adapter is a silent no-op locally | peft warns about missing adapter keys and local eval matches the bare base model | The adapter's key namespace does not match the loaded model class. `model.layers.*` keys pair with `AutoModelForCausalLM`; `model.language_model.layers.*` keys pair with `Qwen3_5ForConditionalGeneration` / `Qwen3_5MoeForConditionalGeneration` (via `AutoModelForImageTextToText`). See "Loading an exported adapter locally". |
|
|
224
266
|
| SFT loss improves but quality does not | Train loss falls while held-out behavior stalls or degrades | Keep a held-out split outside training. Deploy and score that split; if quality drops, reduce epochs or improve data instead of adding more passes. |
|
|
225
267
|
| Cost surprises | A quick experiment uses more GPU time than intended | Start with `--dry-run` and `--cost`, keep `epochs` and `max_examples` small for smoke tests, and scale only after reward/data wiring is proven. Setup time is reported for observability; customer cost is based on training-loop GPU time. |
|
|
226
268
|
|
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import contextlib
|
|
6
|
-
import fcntl
|
|
7
6
|
import json
|
|
8
7
|
import os
|
|
9
8
|
import tempfile
|
|
10
9
|
from collections.abc import Callable, Iterator
|
|
11
10
|
from contextlib import contextmanager
|
|
12
11
|
|
|
12
|
+
try:
|
|
13
|
+
import fcntl
|
|
14
|
+
except ImportError: # pragma: no cover - linux production fails closed below
|
|
15
|
+
fcntl = None
|
|
16
|
+
|
|
13
17
|
from flash.schema import parse_adapter_revision
|
|
14
18
|
|
|
15
19
|
_LEDGER_SUFFIX = ".verified-revisions"
|
|
@@ -29,6 +33,8 @@ def _paths(run_id: str) -> tuple[str, str, str]:
|
|
|
29
33
|
|
|
30
34
|
@contextmanager
|
|
31
35
|
def _locked_ledger(run_id: str, *, exclusive: bool) -> Iterator[tuple[str, str]]:
|
|
36
|
+
if fcntl is None:
|
|
37
|
+
raise RuntimeError("interprocess verified-revision locking is unavailable")
|
|
32
38
|
runs_dir, ledger_path, lock_path = _paths(run_id)
|
|
33
39
|
fd = os.open(lock_path, os.O_CREAT | os.O_RDWR, 0o600)
|
|
34
40
|
with os.fdopen(fd, "a+") as lock_file:
|
|
@@ -14,6 +14,7 @@ import shutil
|
|
|
14
14
|
import struct
|
|
15
15
|
import tempfile
|
|
16
16
|
from pathlib import Path
|
|
17
|
+
from typing import BinaryIO
|
|
17
18
|
|
|
18
19
|
from flash._logging import get_logger
|
|
19
20
|
from flash.serve.deploy import ServingError
|
|
@@ -29,11 +30,11 @@ _TEMP_MERGED_BASE_MODEL_RE = re.compile(
|
|
|
29
30
|
_MAX_SAFETENSORS_HEADER_BYTES = 100 * 1024 * 1024
|
|
30
31
|
|
|
31
32
|
# module-path segments outside the language model; mirrors _VL_EXCLUDE_SEGMENTS in
|
|
32
|
-
# flash/engine/worker/lora.py.
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
33
|
+
# flash/engine/worker/lora.py. managed adapters can contain inert non-lm lora entries when
|
|
34
|
+
# broad target-module names select vision or mtp modules, so key presence alone is not a
|
|
35
|
+
# reason to preserve the mixed namespace. only a nonzero lora_b contribution, or an
|
|
36
|
+
# unrecognized non-lm tensor, proves the adapter genuinely trains those modules and must
|
|
37
|
+
# keep the file unchanged.
|
|
37
38
|
_NON_LM_KEY_SEGMENTS = (".visual.", ".vision_tower.", ".multi_modal_projector.", ".mtp.")
|
|
38
39
|
|
|
39
40
|
|
|
@@ -41,6 +42,42 @@ def _references_non_lm_modules(key: str) -> bool:
|
|
|
41
42
|
return any(segment in key for segment in _NON_LM_KEY_SEGMENTS)
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
def _non_lm_tensor_is_live(
|
|
46
|
+
source: BinaryIO,
|
|
47
|
+
key: str,
|
|
48
|
+
descriptor: object,
|
|
49
|
+
*,
|
|
50
|
+
data_start: int,
|
|
51
|
+
file_size: int,
|
|
52
|
+
) -> bool:
|
|
53
|
+
is_lora_a = ".lora_A." in key
|
|
54
|
+
is_lora_b = ".lora_B." in key
|
|
55
|
+
if is_lora_a and not is_lora_b:
|
|
56
|
+
return False
|
|
57
|
+
if not is_lora_b or is_lora_a:
|
|
58
|
+
return True
|
|
59
|
+
if not isinstance(descriptor, dict):
|
|
60
|
+
raise ValueError(f"safetensors descriptor for {key!r} is not an object")
|
|
61
|
+
offsets = descriptor.get("data_offsets")
|
|
62
|
+
if (
|
|
63
|
+
not isinstance(offsets, list)
|
|
64
|
+
or len(offsets) != 2
|
|
65
|
+
or any(not isinstance(offset, int) or isinstance(offset, bool) for offset in offsets)
|
|
66
|
+
):
|
|
67
|
+
raise ValueError(f"safetensors data_offsets for {key!r} are invalid")
|
|
68
|
+
lo, hi = offsets
|
|
69
|
+
data_size = file_size - data_start
|
|
70
|
+
if lo < 0 or hi < lo or hi > data_size:
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f"safetensors data_offsets for {key!r} are outside the {data_size}-byte data section"
|
|
73
|
+
)
|
|
74
|
+
source.seek(data_start + lo)
|
|
75
|
+
tensor_bytes = source.read(hi - lo)
|
|
76
|
+
if len(tensor_bytes) != hi - lo:
|
|
77
|
+
raise ValueError(f"safetensors tensor data for {key!r} is truncated")
|
|
78
|
+
return any(tensor_bytes)
|
|
79
|
+
|
|
80
|
+
|
|
44
81
|
def _strip_language_model_infix(key: str) -> str:
|
|
45
82
|
# mirrors _LANGUAGE_MODEL_INFIX namespace semantics in flash/engine/worker/lora.py
|
|
46
83
|
infix = ".language_model."
|
|
@@ -90,18 +127,23 @@ def _normalize_export_adapter_keys(adapter_dir: Path) -> bool:
|
|
|
90
127
|
if not isinstance(header, dict):
|
|
91
128
|
raise ValueError("safetensors header is not a JSON object")
|
|
92
129
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
)
|
|
104
|
-
|
|
130
|
+
data_start = 8 + header_length
|
|
131
|
+
for key, descriptor in header.items():
|
|
132
|
+
if key == "__metadata__" or not _references_non_lm_modules(key):
|
|
133
|
+
continue
|
|
134
|
+
if _non_lm_tensor_is_live(
|
|
135
|
+
source,
|
|
136
|
+
key,
|
|
137
|
+
descriptor,
|
|
138
|
+
data_start=data_start,
|
|
139
|
+
file_size=file_size,
|
|
140
|
+
):
|
|
141
|
+
logger.info(
|
|
142
|
+
"exported adapter has live non-lm weights (e.g. %s); keeping the "
|
|
143
|
+
"multimodal key namespace unchanged",
|
|
144
|
+
key,
|
|
145
|
+
)
|
|
146
|
+
return False
|
|
105
147
|
|
|
106
148
|
normalized: dict[str, object] = {}
|
|
107
149
|
remapped = 0
|
|
@@ -124,6 +166,7 @@ def _normalize_export_adapter_keys(adapter_dir: Path) -> bool:
|
|
|
124
166
|
separators=(",", ":"),
|
|
125
167
|
).encode("utf-8")
|
|
126
168
|
new_header += b" " * (-len(new_header) % 8)
|
|
169
|
+
source.seek(data_start)
|
|
127
170
|
with tempfile.NamedTemporaryFile(
|
|
128
171
|
mode="wb",
|
|
129
172
|
dir=path.parent,
|
|
@@ -29,16 +29,47 @@ def require_key(authorization: str | None = Header(default=None)) -> dict:
|
|
|
29
29
|
return key
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def
|
|
33
|
-
"""
|
|
34
|
-
if db.run_owner(run_id) != key["id"]:
|
|
35
|
-
raise HTTPException(status_code=404, detail=f"unknown run_id: {run_id}")
|
|
32
|
+
def _load_status(run_id: str):
|
|
33
|
+
"""Resolve `run_id` to its status via the module (so `app.get_status` patches are honored)."""
|
|
36
34
|
try:
|
|
37
35
|
return _app.get_status(run_id)
|
|
38
36
|
except FileNotFoundError as exc:
|
|
39
37
|
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
40
38
|
|
|
41
39
|
|
|
40
|
+
def owned_run(run_id: str, key: dict):
|
|
41
|
+
"""Load a run's status iff `key` owns it; 404 otherwise (don't leak existence)."""
|
|
42
|
+
if db.run_owner(run_id) != key["id"]:
|
|
43
|
+
raise HTTPException(status_code=404, detail=f"unknown run_id: {run_id}")
|
|
44
|
+
return _load_status(run_id)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def readable_run(run_id: str, key: dict, org_id: str | None = None):
|
|
48
|
+
"""Load a run's status for READ-only endpoints (`/logs`, `/worker`) via one of two paths.
|
|
49
|
+
|
|
50
|
+
Mirrors the env-delete posture (a user key carries its own identity; the org-agnostic
|
|
51
|
+
internal key is the trusted platform caller):
|
|
52
|
+
* exact-key owner -- the API key that created the run. This is the `flash log` CLI path.
|
|
53
|
+
* internal/service key + matching ``X-Freesolo-Org-Id`` -- the platform web proxy. The
|
|
54
|
+
browser has no per-run API key, so the platform authenticates the user, checks org
|
|
55
|
+
membership on the mirror row, then calls with the internal key and the run's org here.
|
|
56
|
+
The header is honored only for the internal key and only when it matches the run's
|
|
57
|
+
PERSISTED org, so a proxy bug can never cross orgs. A non-owner user key does reach this
|
|
58
|
+
check but fails the ``auth_kind == "internal"`` gate, so its org header is never honored.
|
|
59
|
+
404 (never 403) on every failure so we never leak whether a run exists.
|
|
60
|
+
"""
|
|
61
|
+
if db.run_owner(run_id) == key["id"]:
|
|
62
|
+
return _load_status(run_id)
|
|
63
|
+
org = (org_id or "").strip()
|
|
64
|
+
if key.get("auth_kind") == "internal" and org:
|
|
65
|
+
from flash.runner import _status_org_id
|
|
66
|
+
|
|
67
|
+
status = _load_status(run_id)
|
|
68
|
+
if _status_org_id(status) == org:
|
|
69
|
+
return status
|
|
70
|
+
raise HTTPException(status_code=404, detail=f"unknown run_id: {run_id}")
|
|
71
|
+
|
|
72
|
+
|
|
42
73
|
def _require_bool(payload: dict, field: str, default: bool) -> bool:
|
|
43
74
|
"""Read ``payload[field]`` as a real JSON boolean; 400 on non-bool. Never coerce — "false" (str) is truthy."""
|
|
44
75
|
value = payload.get(field, default)
|
|
@@ -7,7 +7,7 @@ import logging
|
|
|
7
7
|
import os
|
|
8
8
|
from typing import Annotated
|
|
9
9
|
|
|
10
|
-
from fastapi import APIRouter, Depends, HTTPException
|
|
10
|
+
from fastapi import APIRouter, Depends, Header, HTTPException
|
|
11
11
|
|
|
12
12
|
from flash.runner import (
|
|
13
13
|
DeploymentRevocationError,
|
|
@@ -19,7 +19,14 @@ from flash.runner import (
|
|
|
19
19
|
from flash.schema import train_schema_metadata
|
|
20
20
|
from flash.server import app as _app
|
|
21
21
|
from flash.server import db
|
|
22
|
-
from flash.server._deps import
|
|
22
|
+
from flash.server._deps import (
|
|
23
|
+
_parse_spec,
|
|
24
|
+
_require_bool,
|
|
25
|
+
_runtime_secrets,
|
|
26
|
+
owned_run,
|
|
27
|
+
readable_run,
|
|
28
|
+
require_key,
|
|
29
|
+
)
|
|
23
30
|
from flash.spec import JobSpec
|
|
24
31
|
|
|
25
32
|
_LOG = logging.getLogger("flash.server.runs")
|
|
@@ -255,8 +262,13 @@ def run_status(run_id: str, key: Annotated[dict, Depends(require_key)]):
|
|
|
255
262
|
|
|
256
263
|
|
|
257
264
|
@router.get("/v1/runs/{run_id}/logs")
|
|
258
|
-
def run_logs(
|
|
259
|
-
|
|
265
|
+
def run_logs(
|
|
266
|
+
run_id: str,
|
|
267
|
+
key: Annotated[dict, Depends(require_key)],
|
|
268
|
+
offset: int = 0,
|
|
269
|
+
x_freesolo_org_id: Annotated[str | None, Header()] = None,
|
|
270
|
+
):
|
|
271
|
+
status = readable_run(run_id, key, x_freesolo_org_id)
|
|
260
272
|
log_path = runs_file_path(run_id, ".log")
|
|
261
273
|
chunk, end = "", max(0, offset)
|
|
262
274
|
if os.path.exists(log_path):
|
|
@@ -280,8 +292,12 @@ def run_logs(run_id: str, key: Annotated[dict, Depends(require_key)], offset: in
|
|
|
280
292
|
|
|
281
293
|
|
|
282
294
|
@router.get("/v1/runs/{run_id}/worker")
|
|
283
|
-
def run_worker_output(
|
|
284
|
-
|
|
295
|
+
def run_worker_output(
|
|
296
|
+
run_id: str,
|
|
297
|
+
key: Annotated[dict, Depends(require_key)],
|
|
298
|
+
x_freesolo_org_id: Annotated[str | None, Header()] = None,
|
|
299
|
+
):
|
|
300
|
+
status = readable_run(run_id, key, x_freesolo_org_id)
|
|
285
301
|
return {"run_id": run_id, "worker": _app._worker_artifacts(JobSpec.from_dict(status.spec))}
|
|
286
302
|
|
|
287
303
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "freesolo-flash-dev"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.2"
|
|
4
4
|
description = "Flash — managed LoRA post-training (SFT/GRPO/OPD) for Freesolo environments, driven by the `flash` CLI"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
# RunPod Flash supports Python 3.11-3.12 (not 3.13 yet).
|
|
@@ -113,7 +113,7 @@ artifacts = ["flash/py.typed"]
|
|
|
113
113
|
# auto-published by .github/workflows/publish-dev.yml: a push to `dev` whose version isn't yet on
|
|
114
114
|
# PyPI publishes it; pushes that don't change it find it already published and no-op. So cutting a
|
|
115
115
|
# dev-channel release = bump this (and [project].version) and merge to `dev`.
|
|
116
|
-
version = "1.0.
|
|
116
|
+
version = "1.0.2"
|
|
117
117
|
|
|
118
118
|
[tool.pytest.ini_options]
|
|
119
119
|
pythonpath = ["."]
|
|
@@ -305,18 +305,52 @@ def test_export_adapter_key_collision_leaves_safetensors_unchanged(monkeypatch):
|
|
|
305
305
|
assert uploaded["weights"] == source_bytes
|
|
306
306
|
|
|
307
307
|
|
|
308
|
+
def test_export_adapter_normalizes_lm_keys_with_inert_vision_weights(tmp_path):
|
|
309
|
+
from flash.serve import export
|
|
310
|
+
|
|
311
|
+
lm_a = "base_model.model.model.language_model.layers.0.mlp.up_proj.lora_A.default.weight"
|
|
312
|
+
lm_b = "base_model.model.model.language_model.layers.0.mlp.up_proj.lora_B.default.weight"
|
|
313
|
+
vision_a = "base_model.model.model.visual.blocks.0.attn.qkv.lora_A.default.weight"
|
|
314
|
+
vision_b = "base_model.model.model.visual.blocks.0.attn.qkv.lora_B.default.weight"
|
|
315
|
+
header = {
|
|
316
|
+
lm_a: {"dtype": "F16", "shape": [1], "data_offsets": [0, 2]},
|
|
317
|
+
lm_b: {"dtype": "F16", "shape": [1], "data_offsets": [2, 4]},
|
|
318
|
+
vision_a: {"dtype": "F16", "shape": [1], "data_offsets": [4, 6]},
|
|
319
|
+
vision_b: {"dtype": "F16", "shape": [1], "data_offsets": [6, 8]},
|
|
320
|
+
}
|
|
321
|
+
data = b"\x01\x02\x03\x04\x05\x06\x00\x00"
|
|
322
|
+
path = tmp_path / "adapter_model.safetensors"
|
|
323
|
+
path.write_bytes(_safetensors_bytes(header, data))
|
|
324
|
+
|
|
325
|
+
assert export._normalize_export_adapter_keys(tmp_path) is True
|
|
326
|
+
|
|
327
|
+
normalized_header, normalized_data = _parse_safetensors_bytes(path.read_bytes())
|
|
328
|
+
normalized_lm_a = lm_a.replace(".language_model.", ".", 1)
|
|
329
|
+
normalized_lm_b = lm_b.replace(".language_model.", ".", 1)
|
|
330
|
+
assert set(normalized_header) == {normalized_lm_a, normalized_lm_b, vision_a, vision_b}
|
|
331
|
+
assert normalized_header[normalized_lm_a] == header[lm_a]
|
|
332
|
+
assert normalized_header[normalized_lm_b] == header[lm_b]
|
|
333
|
+
assert normalized_header[vision_a] == header[vision_a]
|
|
334
|
+
assert normalized_header[vision_b] == header[vision_b]
|
|
335
|
+
assert normalized_data == data
|
|
336
|
+
|
|
337
|
+
|
|
308
338
|
def test_export_adapter_with_vision_keys_leaves_safetensors_unchanged(monkeypatch):
|
|
309
|
-
# a genuinely multimodal adapter
|
|
339
|
+
# a genuinely multimodal adapter with nonzero vision lora_b must not be normalized:
|
|
310
340
|
# stripping only its lm keys would leave a mixed namespace no transformers class loads.
|
|
311
341
|
uploaded: dict = {}
|
|
312
|
-
|
|
313
|
-
|
|
342
|
+
lm_a = "base_model.model.model.language_model.layers.0.mlp.up_proj.lora_A.default.weight"
|
|
343
|
+
lm_b = "base_model.model.model.language_model.layers.0.mlp.up_proj.lora_B.default.weight"
|
|
344
|
+
vision_a = "base_model.model.model.visual.blocks.0.attn.proj.lora_A.default.weight"
|
|
345
|
+
vision_b = "base_model.model.model.visual.blocks.0.attn.proj.lora_B.default.weight"
|
|
314
346
|
source_bytes = _safetensors_bytes(
|
|
315
347
|
{
|
|
316
|
-
|
|
317
|
-
|
|
348
|
+
lm_a: {"dtype": "F16", "shape": [1], "data_offsets": [0, 2]},
|
|
349
|
+
lm_b: {"dtype": "F16", "shape": [1], "data_offsets": [2, 4]},
|
|
350
|
+
vision_a: {"dtype": "F16", "shape": [1], "data_offsets": [4, 6]},
|
|
351
|
+
vision_b: {"dtype": "F16", "shape": [1], "data_offsets": [6, 8]},
|
|
318
352
|
},
|
|
319
|
-
b"\x01\x02\x03\x04",
|
|
353
|
+
b"\x01\x02\x03\x04\x05\x06\x07\x08",
|
|
320
354
|
)
|
|
321
355
|
|
|
322
356
|
def fake_snapshot_download(*, local_dir, **kw):
|
|
@@ -357,6 +391,44 @@ def test_export_adapter_with_vision_keys_leaves_safetensors_unchanged(monkeypatc
|
|
|
357
391
|
assert uploaded["weights"] == source_bytes
|
|
358
392
|
|
|
359
393
|
|
|
394
|
+
def test_export_adapter_with_out_of_bounds_non_lm_offsets_is_unchanged(tmp_path):
|
|
395
|
+
from flash.serve import export
|
|
396
|
+
|
|
397
|
+
lm_key = "base_model.model.model.language_model.layers.0.mlp.up_proj.lora_A.default.weight"
|
|
398
|
+
vision_b = "base_model.model.model.visual.blocks.0.attn.proj.lora_B.default.weight"
|
|
399
|
+
source_bytes = _safetensors_bytes(
|
|
400
|
+
{
|
|
401
|
+
lm_key: {"dtype": "F16", "shape": [1], "data_offsets": [0, 2]},
|
|
402
|
+
vision_b: {"dtype": "F16", "shape": [1], "data_offsets": [2, 5]},
|
|
403
|
+
},
|
|
404
|
+
b"\x01\x02\x00\x00",
|
|
405
|
+
)
|
|
406
|
+
path = tmp_path / "adapter_model.safetensors"
|
|
407
|
+
path.write_bytes(source_bytes)
|
|
408
|
+
|
|
409
|
+
assert export._normalize_export_adapter_keys(tmp_path) is False
|
|
410
|
+
assert path.read_bytes() == source_bytes
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def test_export_adapter_with_unrecognized_non_lm_tensor_is_unchanged(tmp_path):
|
|
414
|
+
from flash.serve import export
|
|
415
|
+
|
|
416
|
+
lm_key = "base_model.model.model.language_model.layers.0.mlp.up_proj.lora_A.default.weight"
|
|
417
|
+
vision_saved = "base_model.model.model.visual.proj.modules_to_save.default.weight"
|
|
418
|
+
source_bytes = _safetensors_bytes(
|
|
419
|
+
{
|
|
420
|
+
lm_key: {"dtype": "F16", "shape": [1], "data_offsets": [0, 2]},
|
|
421
|
+
vision_saved: {"dtype": "F16", "shape": [1], "data_offsets": [2, 4]},
|
|
422
|
+
},
|
|
423
|
+
b"\x01\x02\x00\x00",
|
|
424
|
+
)
|
|
425
|
+
path = tmp_path / "adapter_model.safetensors"
|
|
426
|
+
path.write_bytes(source_bytes)
|
|
427
|
+
|
|
428
|
+
assert export._normalize_export_adapter_keys(tmp_path) is False
|
|
429
|
+
assert path.read_bytes() == source_bytes
|
|
430
|
+
|
|
431
|
+
|
|
360
432
|
def test_export_adapter_without_safetensors_still_exports(monkeypatch):
|
|
361
433
|
uploaded: dict = {}
|
|
362
434
|
bin_weights = b"legacy-adapter-weights"
|
|
@@ -1145,6 +1145,115 @@ def test_worker_output_route(api, monkeypatch):
|
|
|
1145
1145
|
assert api.get(f"/v1/runs/{run_id}/worker", headers=_bearer(other)).status_code == 404
|
|
1146
1146
|
|
|
1147
1147
|
|
|
1148
|
+
def test_internal_key_reads_logs_and_worker_with_matching_org(api, monkeypatch):
|
|
1149
|
+
# The platform web proxy has no per-run API key, so it reads a user's run logs with the shared
|
|
1150
|
+
# internal key plus the run's org in X-Freesolo-Org-Id. The header is honored ONLY for the
|
|
1151
|
+
# internal key and ONLY when it matches the run's persisted org. All failures are 404 so run
|
|
1152
|
+
# existence is never leaked, and a rejected request must not trigger the expensive worker fetch.
|
|
1153
|
+
import flash.server.app as app_mod
|
|
1154
|
+
|
|
1155
|
+
owner = _login()
|
|
1156
|
+
org = f"org-{owner.removeprefix(_USER_PREFIX)}"
|
|
1157
|
+
run_id = api.post(
|
|
1158
|
+
"/v1/runs", json={"spec": SPEC, "dry_run": True}, headers=_bearer(owner)
|
|
1159
|
+
).json()["run_id"]
|
|
1160
|
+
with open(os.path.join(_orch.RUNS_DIR, f"{run_id}.log"), "w") as f:
|
|
1161
|
+
f.write("orchestrator line\n")
|
|
1162
|
+
|
|
1163
|
+
worker_calls = {"n": 0}
|
|
1164
|
+
|
|
1165
|
+
def counting_worker(spec):
|
|
1166
|
+
worker_calls["n"] += 1
|
|
1167
|
+
return {"console_sft.txt": "worker stdout\n"}
|
|
1168
|
+
|
|
1169
|
+
monkeypatch.setattr(app_mod, "_worker_artifacts", counting_worker)
|
|
1170
|
+
|
|
1171
|
+
internal = _bearer("fslo-internal-test")
|
|
1172
|
+
matched = {**internal, "X-Freesolo-Org-Id": org}
|
|
1173
|
+
|
|
1174
|
+
logs = api.get(f"/v1/runs/{run_id}/logs", headers=matched)
|
|
1175
|
+
assert logs.status_code == 200, logs.text
|
|
1176
|
+
assert logs.json()["logs"] == "orchestrator line\n"
|
|
1177
|
+
worker = api.get(f"/v1/runs/{run_id}/worker", headers=matched)
|
|
1178
|
+
assert worker.status_code == 200, worker.text
|
|
1179
|
+
assert worker.json()["worker"] == {"console_sft.txt": "worker stdout\n"}
|
|
1180
|
+
assert worker_calls["n"] == 1
|
|
1181
|
+
|
|
1182
|
+
# wrong org, empty/whitespace org, or no header at all -> 404, and none of these rejected
|
|
1183
|
+
# /worker requests reach the expensive artifact fetch (the call count stays at 1).
|
|
1184
|
+
for headers in (
|
|
1185
|
+
{**internal, "X-Freesolo-Org-Id": "org-someone-else"},
|
|
1186
|
+
{**internal, "X-Freesolo-Org-Id": ""},
|
|
1187
|
+
{**internal, "X-Freesolo-Org-Id": " "},
|
|
1188
|
+
internal,
|
|
1189
|
+
):
|
|
1190
|
+
assert api.get(f"/v1/runs/{run_id}/logs", headers=headers).status_code == 404
|
|
1191
|
+
assert api.get(f"/v1/runs/{run_id}/worker", headers=headers).status_code == 404
|
|
1192
|
+
assert worker_calls["n"] == 1
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
def test_org_header_is_ignored_for_non_internal_keys(api):
|
|
1196
|
+
# A non-owner user key can't read another tenant's logs even if it forges the org header: the
|
|
1197
|
+
# header is consulted only for the internal service key, so the owner check still governs users.
|
|
1198
|
+
owner = _login()
|
|
1199
|
+
org = f"org-{owner.removeprefix(_USER_PREFIX)}"
|
|
1200
|
+
run_id = api.post(
|
|
1201
|
+
"/v1/runs", json={"spec": SPEC, "dry_run": True}, headers=_bearer(owner)
|
|
1202
|
+
).json()["run_id"]
|
|
1203
|
+
|
|
1204
|
+
intruder = _login()
|
|
1205
|
+
forged = {**_bearer(intruder), "X-Freesolo-Org-Id": org}
|
|
1206
|
+
assert api.get(f"/v1/runs/{run_id}/logs", headers=forged).status_code == 404
|
|
1207
|
+
assert api.get(f"/v1/runs/{run_id}/worker", headers=forged).status_code == 404
|
|
1208
|
+
|
|
1209
|
+
# the real owner still reads its own run with no org header (unchanged CLI path).
|
|
1210
|
+
assert api.get(f"/v1/runs/{run_id}/logs", headers=_bearer(owner)).status_code == 200
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
def test_internal_org_header_does_not_widen_owner_only_endpoints(api):
|
|
1214
|
+
# readable_run only governs the read-only /logs and /worker endpoints. /status, /cancel, and
|
|
1215
|
+
# /checkpoints stay owner-only via owned_run, so the internal key plus a matching org header
|
|
1216
|
+
# must NOT grant access to them.
|
|
1217
|
+
owner = _login()
|
|
1218
|
+
org = f"org-{owner.removeprefix(_USER_PREFIX)}"
|
|
1219
|
+
run_id = api.post(
|
|
1220
|
+
"/v1/runs", json={"spec": SPEC, "dry_run": True}, headers=_bearer(owner)
|
|
1221
|
+
).json()["run_id"]
|
|
1222
|
+
|
|
1223
|
+
matched = {**_bearer("fslo-internal-test"), "X-Freesolo-Org-Id": org}
|
|
1224
|
+
assert api.get(f"/v1/runs/{run_id}", headers=matched).status_code == 404
|
|
1225
|
+
assert api.post(f"/v1/runs/{run_id}/cancel", headers=matched).status_code == 404
|
|
1226
|
+
assert api.get(f"/v1/runs/{run_id}/checkpoints", headers=matched).status_code == 404
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
def test_internal_key_cannot_read_run_without_persisted_org(api, monkeypatch):
|
|
1230
|
+
# A run with no persisted org context cannot be matched by the internal path even with a
|
|
1231
|
+
# non-empty header: _status_org_id is empty, so it never equals a real org and fails closed.
|
|
1232
|
+
import flash.server.app as app_mod
|
|
1233
|
+
|
|
1234
|
+
owner = _login()
|
|
1235
|
+
run_id = api.post(
|
|
1236
|
+
"/v1/runs", json={"spec": SPEC, "dry_run": True}, headers=_bearer(owner)
|
|
1237
|
+
).json()["run_id"]
|
|
1238
|
+
|
|
1239
|
+
real_get_status = app_mod.get_status
|
|
1240
|
+
|
|
1241
|
+
def stripped(rid):
|
|
1242
|
+
status = real_get_status(rid)
|
|
1243
|
+
if rid == run_id:
|
|
1244
|
+
status.platform_context = None
|
|
1245
|
+
status.billing_context = None
|
|
1246
|
+
return status
|
|
1247
|
+
|
|
1248
|
+
monkeypatch.setattr(app_mod, "get_status", stripped)
|
|
1249
|
+
|
|
1250
|
+
internal = _bearer("fslo-internal-test")
|
|
1251
|
+
for org in ("org-anything", f"org-{owner.removeprefix(_USER_PREFIX)}"):
|
|
1252
|
+
headers = {**internal, "X-Freesolo-Org-Id": org}
|
|
1253
|
+
assert api.get(f"/v1/runs/{run_id}/logs", headers=headers).status_code == 404
|
|
1254
|
+
assert api.get(f"/v1/runs/{run_id}/worker", headers=headers).status_code == 404
|
|
1255
|
+
|
|
1256
|
+
|
|
1148
1257
|
def test_latest_error_artifact_name_picks_highest_attempt(monkeypatch):
|
|
1149
1258
|
"""The logs fetcher resolves the newest attempt-scoped error file, so a retried-then-failed run
|
|
1150
1259
|
surfaces the FINAL attempt's traceback, not attempt0's stale one."""
|
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import multiprocessing
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
|
|
7
9
|
import pytest
|
|
@@ -199,3 +201,30 @@ def test_verified_revision_ledger_survives_new_process(monkeypatch, tmp_path):
|
|
|
199
201
|
|
|
200
202
|
assert process.exitcode == 0
|
|
201
203
|
assert output.get(timeout=2) == (revision,)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_verified_revision_ledger_fails_closed_without_fcntl(monkeypatch):
|
|
207
|
+
# fcntl is unix-only; on platforms without it the ledger must fail closed
|
|
208
|
+
# rather than silently skip its cross-process lock.
|
|
209
|
+
from flash.runner import verified_revisions
|
|
210
|
+
|
|
211
|
+
monkeypatch.setattr(verified_revisions, "fcntl", None)
|
|
212
|
+
with pytest.raises(RuntimeError, match="verified-revision locking is unavailable"):
|
|
213
|
+
verified_revisions.read_verified_adapter_revisions("run-without-fcntl")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_cli_import_chain_survives_missing_fcntl():
|
|
217
|
+
# reproduces the windows `flash login` crash: fcntl is unix-only, so the cli
|
|
218
|
+
# import chain (flash.cli -> flash.runner -> verified_revisions) must not
|
|
219
|
+
# hard-depend on it. blocking fcntl mimics a platform that lacks the module.
|
|
220
|
+
code = (
|
|
221
|
+
"import sys; sys.modules['fcntl'] = None; "
|
|
222
|
+
"import flash.runner.verified_revisions; "
|
|
223
|
+
"import flash.cli"
|
|
224
|
+
)
|
|
225
|
+
result = subprocess.run(
|
|
226
|
+
[sys.executable, "-c", code],
|
|
227
|
+
capture_output=True,
|
|
228
|
+
text=True,
|
|
229
|
+
)
|
|
230
|
+
assert result.returncode == 0, result.stderr
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/bake-kernel-cache.yml
RENAMED
|
File without changes
|
|
File without changes
|
{freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/main-source-guard.yml
RENAMED
|
File without changes
|
{freesolo_flash_dev-1.0.1 → freesolo_flash_dev-1.0.2}/.github/workflows/notify-tests-repo.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|