Rename vllm/gemma-int8→gemma-int8-mtp + vllm/gemma-mtp→gemma-bf16-mtp (both carry MTP n=4; only KV format differs); kv-calc alias fix. Ladder gemma-bf16-mtp default 32K→131K (measured BF16 KV pool 196,527 tok @ 0.95; verify-stress 8/8, NIAH→120K, bench 118.8/154.3, zero TPS regression). Codify the dual-card max-context-priority rule in DUAL_CARD.md. Suite 41/41.
18 KiB
Pull-Gate — scripts/pull.sh (v0.8.0, #147)
Contributor/maintainer guide for the v0.8.0 Pull-Gate. pull takes one HF
repo slug, derives a ModelProfile-shaped spec from the repo's own files,
runs it through the locked 6-stratum abort taxonomy, and — only for a
curated, [D]-emittable model — hands the validated profile to the #141
compose generator. It is honest about confidence and never silently
gate-passes.
The honest scope of this phase is exactly:
evaluate any safetensors HF repo; pull-and-emit only curated (Tier-1) models — and only when the gates pass (or an explicit override is accepted)
The authoritative spec is the test, not this doc
Per the locked v0.8.0 stop-condition,
scripts/tests/test-pull.shis the canonical state-machine specification — an exhaustive, network-mocked truth table over the §4.1 nine cells, all six strata, stratum ordering, every flag interaction, and golden cases g0–g15. This document is an explanatory companion. Where this prose and the test ever disagree, the test is correct; fix the doc.
For the [D] substrate this hands off to, see
docs/COMPOSE_GENERATOR.md. For the underlying
patch/arch/profile data model see
docs/PATCH_ATTRIBUTION.md; for the locked design,
the v0.8.x consolidated design (§3/§4.1/§5.1/§5.2/§7).
1. Purpose & scope — the two paths
pull runs in exactly one of two paths. Path selection in
run_pull (scripts/lib/profiles/pull.py): an explicit driver path=
wins; else --dry-run forces Path B; else Path A iff the deriver returns a
Tier-1 curated hit and an --out target is given; otherwise Path B.
| Path | Trigger | What it does | Calls [D]? |
Downloads? |
|---|---|---|---|---|
| A — curated pull-and-emit | curated (Tier-1) slug + --out, not --dry-run |
full gate → stratum-6 [D] dry-run → real [D] emit of the validated registry key |
yes (read-only dry-run, then real gc.generate) |
no (download itself deferred — see below) |
| B — universal evaluate | any slug, or --dry-run |
full gate → print the §7-caveated verdict → STOP | never | never |
Path A is the only path that ever invokes the #141 generator, and it does
so only after a download-eligible [C1] terminal and a clean stratum-6
[D] dry-run. Path B evaluates any safetensors repo (curated or not) and
stops at a verdict — it is structurally incapable of emit/download
(test-pull.sh sweeps vllm/minimal/vllm/dual/vllm/tools-text to
assert emitted and compose_text stay unset on Path B).
Explicitly OUT of v0.8.0 Pull-Gate (named deferred phases)
| Deferred capability | Deferred to |
|---|---|
Pull-Emit-Derived: dynamic-[D]-from-derived, the [E] weight download, static-route fallback for non-emittable profiles |
Pull-Emit-Derived phase |
[F] capture / classify / trust / loop; override-accepted telemetry capture; --experimental-arch outcome capture |
Loop phase |
The two UX doc tracks + the end-user recommend nudge |
post-Pull-Gate (after this phase) |
GGUF / .bin backend, multi-quant auto-pick, whichllm hardware slice |
v0.8.1 |
derived confidence tier (override-registry promotion/consensus) |
Loop phase (the derived row is RESERVED, still total in §4.1) |
override-accepted is a first-class terminal but not a gate-pass: this
phase records the state + a telemetry notice and downloads nothing
(run_pull returns ok=True with a "download deferred to the Loop phase"
notice). --force-download is therefore a NO-OP + notice this phase.
2. CLI reference
Observed from python3 scripts/lib/profiles/pull.py --help (pull.sh is a
thin argv pass-through to it):
pull.sh [-h] --profile-like PROFILE_LIKE [--dry-run] [--yes]
[--force-download] [--experimental-arch] [--trust-remote-code]
[--hf-home HF_HOME] [--out OUT] [--hardware HARDWARE]
slug
| Flag / arg | Required | Semantics (as shipped) |
|---|---|---|
slug (positional) |
yes | HF repo slug, e.g. org/Model-Name. |
--profile-like PROFILE_LIKE |
yes | A curated COMPOSE_REGISTRY key supplying the runtime shape. Path A: must name the curated model+variant and be [D]-emittable. Path B: any vLLM profile, runtime shape only. |
--dry-run |
no | Force Path B (evaluate only; never emit/download). |
--yes |
no | Accept a confirm→proceed terminal (§4.1). Without it, a confirm→proceed cell is an honest non-pass. |
--force-download |
no | Advisory low-confidence wont-fit → override-accepted. NO-OP + notice this phase; the download is deferred to the Loop phase. |
--experimental-arch |
no | Bypass ONLY [C0] engine-support-unknown/no-arch-row. Never bypasses runtime-incompatible; Path B only this phase. |
--trust-remote-code |
no | Bypass [C0] needs-trust-remote-code-ack. |
--hf-home HF_HOME |
no | Override the HF_HOME resolution chain (--hf-home > $HF_HOME > $XDG_CACHE_HOME/huggingface > ~/.cache/huggingface). |
--out OUT |
no | Path A only: write the emitted compose here. Its presence (with a curated slug, no --dry-run) is what selects Path A. |
--hardware HARDWARE |
no | Override detected GPU compute capability (e.g. 8.6 for RTX 3090). Default = nvidia-smi --query-gpu=compute_cap detection (the min SM across visible GPUs). |
Exit codes (CLI main)
| Code | Meaning |
|---|---|
0 |
ok — download-eligible (Path A emitted, or Path-B clean verdict, or override-accepted). |
2 |
Any honest hard-stop: a stratum-1→6 abort, or a [C1] hard-block. |
3 |
A confirm→proceed / override-accepted terminal not yet satisfied (re-run adding the named flag). |
64 |
argparse usage error. |
If no GPU is detected and no --hardware is given, run_pull fails
closed at stratum-3 with hardware-sm-undetermined rather than fabricate
a fit (the §1 honesty rule).
3. The 6-stratum abort taxonomy
Evaluated strictly in order and monotonically: each stratum depends
only on prior strata; nothing downstream rewrites an upstream verdict. The
earliest applicable stratum wins (test-pull.sh Section 8 stacks failing
conditions and asserts earliest-wins). Stratum.DECIDED (value 0) means
the run reached a [C1] terminal / Path-B verdict with no abort.
| # | Stratum | Code | States / sub-reasons | Bypass flag (if any) |
|---|---|---|---|---|
| 1 | Deriver structured errors | deriver.derive |
repo-not-found, gated-no-token, unsupported-format, ambiguous-weight-set, quant-dtype-unknown |
none — pick a different repo |
| 2 | --profile-like validity precondition |
gates.stratum2_profile_like |
unknown-profile-like, unsupported-runtime-engine, profile-not-emittable (Path A), profile-mismatch (Path A) |
none — pick another profile |
| 3 | [C0] engine-support / runtime / SM |
gates.c0_engine_support |
engine-supported · engine-support-unknown (+ sub-reason no-arch-row | runtime-incompatible) · needs-trust-remote-code-ack |
see below |
| 4 | [C2a] disk pre-gate |
gates.c2a_disk |
disk-ok | disk-short |
none — non-negotiable |
| 5 | Pre-[B] generic-dense eligibility |
pull.run_pull (P4) |
no-fit-model |
none — non-bypassable |
| 6 | Path-A [D] dry-run |
pull.run_pull (P4) |
d-refused:<pin-mismatch | tp-or-kv | trc | foundational-drift | degraded-drift | scope | other> |
none |
Then [B] (kv-calc raw_verdict) produces the raw fit verdict and [C1]
(§4.1) maps it to a terminal. On Path A only, a download-eligible terminal
is followed by stratum-6.
Stratum-3 [C0] bypass scoping (exact)
[C0] tags which flags (if any) can bypass each non-engine-supported
verdict on .bypassable_by; run_pull only bypasses when every tagged
condition is covered by a provided flag (tags.issubset(provided)).
[C0] outcome |
.bypassable_by |
Cleared by |
|---|---|---|
engine-support-unknown/no-arch-row |
(--experimental-arch,) |
--experimental-arch (Path B only this phase) |
engine-support-unknown/runtime-incompatible |
() — empty |
nothing (non-bypassable; includes hardware-SM mismatch) |
needs-trust-remote-code-ack (known arch + auto_map) |
(--trust-remote-code,) |
--trust-remote-code only |
needs-trust-remote-code-ack + no arch row |
(--trust-remote-code, --experimental-arch) |
both flags (subset rule) |
--experimental-arch therefore bypasses only no-arch-row. It does
not bypass runtime-incompatible (bypassable_by=(), asserted by g10
and g14), and it does not bypass stratum-5 no-fit-model (g11 runs
with the flag and still aborts). disk-short (stratum-4) and
no-fit-model (stratum-5) are non-negotiable: there is no flag, and for
stratum-5 "there is no model to force."
4. [C0] and [C1] are design-locked state sets
[C0] emits exactly three top-level states —
engine-supported, engine-support-unknown,
needs-trust-remote-code-ack (locked design §5.1). no-arch-row and
runtime-incompatible are sub-reasons carried on C0Result.sub_reason
(a structured side field), not new top-level states. Likewise [C1]
has exactly four terminals — proceed, confirm→proceed,
hard-block, override-accepted — frozen in pull.LOCKED_TERMINALS;
test-pull.sh asserts LOCKED_TERMINALS == {proceed, confirm→proceed, hard-block, override-accepted} and that the enum adds nothing.
[C0]'s determination order (gates.c0_engine_support) is monotonic:
trust-remote-code (fail-closed: requires_trust_remote_code ∈ {true, unverified} or config.json auto_map) → no arch row → arch row
exists but runtime/hardware not loadable → else engine-supported.
5. [C1] — the §4.1 confidence × raw-verdict total function
[B] (kv-calc.raw_verdict) is pure measurement: FAIL → wont-fit,
TIGHT → fits-constrained, PASS → fits-clean. [C1]
(pull.c1_terminal, backed by the data table _C1_TABLE) is the single
authoritative mapping, reproduced verbatim from the locked design §4.1:
| Confidence | fits-clean |
fits-constrained |
wont-fit |
|---|---|---|---|
exact |
proceed (silent) | confirm→proceed (constraint changed the requested config — user must accept the applied ctx/KV constraint even though math is trusted) | hard-block (math trusted; suggest closest-fit) |
derived |
confirm→proceed (--yes; "best-effort, validate post-boot") |
confirm→proceed (--yes + constraint notice) |
advisory → --force-download → override-accepted |
estimated-lower-bound |
confirm→proceed (--yes + "VRAM is a floor; likely under-modeled") |
confirm→proceed (--yes + floor + constraint notice) |
advisory → --force-download → override-accepted |
The table is total over {exact, derived, estimated-lower-bound} × {fits-clean, fits-constrained, wont-fit} (nine cells; test-pull.sh
asserts totality and exactly nine entries). Per the locked design §4.1
footnote: only exact × fits-clean is silent; every other cell needs
--yes (a confirm→proceed cell) or --force-download (a low-confidence
wont-fit advisory) to be satisfied. exact × wont-fit is an
unconditional hard-block — no flag clears it (g3 asserts this even
with --yes --force-download).
Critically, a flag-bypassed low-confidence model is not a silent pass.
After --trust-remote-code clears stratum-3 for an
estimated-lower-bound model (g4), [C1] still lands on
confirm→proceed which itself requires --yes. Without --yes it is an
honest non-pass (abort_reason begins confirm→proceed, exit 3); the
caveat verdict is only printed once the cell is satisfied. derived is
RESERVED for the future override-registry (Loop) phase and is never
assigned in v0.8.0, but its §4.1 row is present so the function stays
total.
Every download-eligible terminal and every Path-B verdict carries the
locked design §7 caveat verbatim (pull.CAVEAT_S7):
boot-fit satisfied; this does NOT guarantee stability under sustained / accumulated-context workloads — validate with soak-continuous before relying on it (recommend: scripts/soak.sh SOAK_MODE=continuous).
This is presentation-only and changes no decision.
6. Hardware-SM gating (why a 3090 cannot get a confidently-wrong "fits")
Stratum-3 enforces, as part of runtime loadability:
need_sm = max(engine.min_sm, registry.required_sm, arch-kernel SM rule)
(gates._required_sm). The arch-kernel rule (gates._ARCH_KERNEL_SM)
pins fp8_e4m3 and turboquant_3bit_nc (Gemma-TQ3) at SM 9.0. If the
detected/--hardware SM is below need_sm, [C0] returns
engine-support-unknown/runtime-incompatible — non-bypassable.
Concretely (g14): a curated Gemma-4-31B fp8_e4m3 / required_sm:9.0
profile on a detected RTX 3090 (sm_86) aborts at stratum-3
runtime-incompatible; --experimental-arch does not bypass it; the
same profile on sm_90 clears the SM gate (proving it is the SM gate).
Without this check a 3090 would receive a false "fits" verdict — exactly
the §1 confidently-wrong outcome the design forbids.
7. How each path consumes --profile-like
--profile-like is REQUIRED on both paths but consumed differently:
| Aspect | Path A (curated pull-and-emit) | Path B (universal evaluate) |
|---|---|---|
| What the profile supplies | the full registry key (the validated key is handed to [D]) |
runtime shape only: engine, kv_format, tp, max_ctx, max_num_seqs, mem_util, topology |
| Model/variant match | profile model must equal the curated resolved model and weights_variant must equal the matched variant, else stratum-2 profile-mismatch (g3b) |
not checked — the pulled repo's own files determine weight format/quant |
[D]-emittable required |
yes — reuses [D]'s scope-gate (engine.type==vllm ∧ profile_runtime entry exists ∧ genesis_equipped==false); a Genesis/TQ3 profile (vllm/dual-turbo) → stratum-2 profile-not-emittable (g0) |
no |
weights_variant compat |
Path-A only ([C0] checks the curated variant against arch constraints) |
n/a — uses deriver-resolved weight_format/quant |
drafter |
from the curated profile | none for non-curated (drafter profiles expose model_compat, not arch-compat) |
Non-vLLM --profile-like |
refused stratum-2 unsupported-runtime-engine (g13: llamacpp/default, engine=llama-cpp-local, mem_util=None) |
same — refused on both paths |
8. [D] handoff & the --project-directory requirement
On Path A, after a satisfied download-eligible terminal, run_pull runs
the stratum-6 [D] dry-run via the real generate_compose.generate
(pure — no container). If [D] refuses at one of its later points (pin
mismatch / TP·KV / trc / foundational-or-degraded patch drift), that is a
stratum-6 Path-A abort (d-refused:<token>) and the run is not
reported download-eligible — stratum-2's scope-gate is necessary but not
sufficient for [D] emit (g15: an injected refusing runner →
d-refused:foundational-drift, ok=False, no file written).
On a clean dry-run, the validated key's compose text is written to
--out. The emitted compose's relative overlay mounts resolve from the
compose file's own directory, so generated composes are not
relocatable — run_pull surfaces a notice that the consumer must run:
docker compose --project-directory <repo-root> -f <out> up
See docs/COMPOSE_GENERATOR.md for the full
--project-directory rationale and the governed-slot invariant (the
in-scope Path-A emitted body contains zero --trust-remote-code, asserted
by test-pull.sh via patch_attribution.service_body()==0). For a
fits-constrained Path-A run, [D] emits the chosen registry profile
unchanged; the run prints a "known effective-cap warning" (vLLM
internally caps effective KV on this hardware) — no compose config is
rewritten (g2 asserts the notice says "no compose config rewritten" and
not "applied constraint").
9. Golden cases (the test's truth table)
scripts/tests/test-pull.sh is the canonical spec. Golden cases as
asserted there:
| Case | Path | Asserted behavior |
|---|---|---|
| g0 | A | vllm/dual-turbo (genesis_equipped) → stratum-2 profile-not-emittable before [C0] |
| g1 | A | curated + emittable, gate-passing, clean [D] dry-run → compose emitted (integration analogue of the satisfied g2) |
| g2 | A | curated effective-capped → exact × fits-constrained → confirm→proceed; --yes → "known effective-cap warning" (no rewrite) → [D] emit; without --yes → honest non-pass, no file |
| g3 | A | curated too big → exact × wont-fit → hard-block; no [D] even with --yes --force-download |
| g3b | A | model/variant mismatch (vllm/gemma-bf16-mtp vs curated Qwen) → stratum-2 profile-mismatch |
| g4 | B | Llama, trc:unverified → needs-trust-remote-code-ack; --trust-remote-code → [C1] confirm→proceed (still needs --yes); + --yes → Path-B caveat verdict, no [D] |
| g5 | B | no-arch-row → stratum-3; --experimental-arch (+ --yes, eligible) → Path-B verdict + bypass notice |
| g6a | B | known arch + auto_map → needs-trc-ack, bypassable_by == ['--trust-remote-code'] only; --experimental-arch alone does not clear it |
| g6b | B | no-arch-row + auto_map → bypassable_by == {--trust-remote-code, --experimental-arch}; needs both (subset rule) |
| g7 | B | 200 GiB model, 2 GiB free, clean [C0] → stratum-4 disk-short; [B] never ran (no fit verdict) |
| g8 | B | curated + --dry-run (with --out) → Path B, no [D] emit, no file written |
| g9 | B | derived dense → estimated-lower-bound confidence, real verdict; --calibration stays 22/22 |
| g10 | B | loads:false pin → runtime-incompatible; --experimental-arch does not bypass |
| g11 | B | SWA-only / MoE-on-known-arch → [C0] stays engine-supported → stratum-5 no-fit-model; --experimental-arch does not bypass |
| g12 | — | no *.safetensors → unsupported-format; multiple sets → ambiguous-weight-set; HF 404 → repo-not-found; stratum-1 emits no fit verdict |
| g13 | both | --profile-like llamacpp/default → stratum-2 unsupported-runtime-engine before [C0]/[B] |
| g14 | A | Gemma fp8_e4m3 / required_sm:9.0 on sm_86 → stratum-3 runtime-incompatible; non-bypassable; clears on sm_90 |
| g15 | A | stratum-2 + [C1] download-eligible but [D] dry-run refuses → stratum-6 Path-A abort, not download-eligible |
Run the canonical spec from the repo root:
bash scripts/tests/test-pull.sh