Files
club-3090/scripts/lib/profiles/gates.py
T
noonghunnaandClaude Opus 4.7 ed1507122c llama.cpp single: thinking-off policy alignment + MTP profile family
Closes the dataextract 0/15 quality regression caused by mounting the
vLLM-only froggeric Jinja template on the llama.cpp engine, which
silently suppressed --reasoning off. Native template + --reasoning off
restores it. Stack-wide Qwen3.6 thinking-off-by-default policy now
shipped on the llama.cpp path (matches 22/24 vLLM composes).

Adds three named single-card profiles:
- llamacpp/default (262K vanilla, unchanged) — cliff-immune fallback
- llamacpp/mtp (NEW: 131K + MTP n=2) — single-card workhorse, ~60 TPS code
- llamacpp/mtp-vision (NEW: 49K + MTP + vision) — first stack profile
  combining MTP + vision on build 9235 (the older "strip mmproj when
  MTP" rule was obsolete; sweep-verified MTP + vision coexist)

Retires single/concurrent.yml — single-card concurrency is anti-value
(per-slot ctx ~48K, worse than every other profile). Concurrency
belongs on dual.

Fixes a latent user-facing bug: engines/llama-cpp-mainline.yml had
supported_drafters: [draft-mtp] (the llama.cpp CLI flag value) while
the compat-layer C7 gate compares against drafter spec_method (mtp).
Net effect pre-fix: llamacpp/mtp would have been silently filtered
out of launch.sh candidate lists. Fixed [draft-mtp] -> [mtp].

Lowers -ub default 2048 -> 1024 on the llama.cpp single composes. The
per-pass activation peak halves; verify-stress goes 5/7 -> 7/7
including the 60K + 91K needle rungs previously treated as
architectural Cliff 2 territory. Cliff 2 single-prompt at 50-60K was
config-driven on llama.cpp, not architectural; CLIFFS.md note added
(vLLM Cliff 2 narrative unchanged — different kernel-level failure).

Migrates 6 stale call sites for the engine-id rename
(llama-cpp-mainline -> llama-cpp-local) that earlier work lagged.

Measured impact, Config A (llamacpp/mtp, 131K, MTP n=2, ub=1024):
- bench: 51.28 narr / 59.72 code decode TPS (n=3, CV 1.9%/0.5%)
- verify-stress: 7/7 PASS (incl. 60K + 91K needle recall)
- quality 8-pack: 102/150 (68%) — beats every Qwen vLLM dual config in
  discussion #119 by 6-16 pp
- aider-polyglot-30: 17/30 (56.7%) — matches Qwen vLLM bf16 dual
  exactly (17/30) on half the hardware
- per-GPU code TPS (59.72) ~equal to vLLM dual configs (60-63),
  confirming the engine-side per-card rate is identical and vLLM
  dual's aggregate advantage is purely from the second card

Config B (llamacpp/mtp-vision, 49K, MTP + vision):
- multimodal vision probe passed
- bench: 56.52 narr / 66.17 code decode TPS (n=5)
- verify-stress: 7/7 PASS

Tests green: test-launch-compat, test-profiles-compat,
test-switch-registry-parity, test-pullgate-gates, test-patch-attribution.
Leak-grep clean. YAML lint clean. Performance charts regenerated to
include the two new entries.

Doc alignment: SINGLE_CARD, CLIFFS, FAQ, EXAMPLES, README, llama-cpp
README, COMPOSE_GENERATOR, PULL_GATE all reflect the new family.
CHANGELOG.md + UPSTREAM.md left intact per append-only history rule.

Followups (queued, not blocking): #400 MTP retune A/B (n=4 + p-min=0
on the fixed config), #403 soak-test container glob (unlocks Cliff 2b
validation on llama.cpp), #405 benchlocal-cli port-offset for parallel
rebench, #401 + #404 ik_llama + Tom's TurboQuant trackers.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-20 01:01:26 +00:00

577 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""v0.8.0 Pull-Gate — STEP P3: stratum-2 precondition + `[C0]`
engine-support/runtime/hardware gate + `[C2a]` disk pre-gate.
These are PURE, injectable, testable functions. They run AFTER P2's
`deriver.derive()` succeeds (stratum-1 errors are already carried on
`DeriveResult.error`). P3 implements ONLY abort strata 2, 3, 4 of the
6-stratum taxonomy. It does NOT orchestrate, does NOT implement `[C1]`,
stratum-5 abort wiring, the `pull` orchestrator, or any download — those
are P4. P4 consumes the structured outputs here and owns the CLI flags
(`--experimental-arch`, `--trust-remote-code`, `--hardware`, ...).
Public API (stable for P4)
--------------------------
from scripts.lib.profiles import gates
s2 = gates.stratum2_profile_like(
registry_key, *, derive_result=None, path,
root=None, registry=None, runtime=None,
) # -> Stratum2Result
# .ok -> bool
# .refusal -> Refusal | None (refuse-reason, non-bypassable)
# .engine_id -> str | None
# .registry_entry -> dict | None
c0 = gates.c0_engine_support(
registry_key, derive_result, *, path,
hardware_sm, # injectable float (sm_86 -> 8.6)
root=None, registry=None, runtime=None, arches=None,
) # -> C0Result
# .state -> C0State (EXACTLY one of the locked 3)
# .sub_reason -> C0SubReason | None (structured SIDE field)
# .bypassable_by -> tuple[str, ...] (structured SIDE field; P4 acts)
# .detail -> str
c2a = gates.c2a_disk(
derive_result, *, hf_home=None, statvfs=None,
) # -> C2aResult
# .state -> C2aState ({disk-ok, disk-short})
# .required_gb / .available_gb / .detail
Design-lock (do not violate)
----------------------------
`[C0]` emits EXACTLY one of {engine-supported, engine-support-unknown,
needs-trust-remote-code-ack}. `no-arch-row` / `runtime-incompatible` are
sub-reasons carried in `.sub_reason` — NOT new top-level states. Whether
P4 may bypass a verdict is carried in `.bypassable_by` (a structured side
field); P3 only TAGS, P4 acts on flags.
`[D]` reuse (read-only, never reimplemented)
--------------------------------------------
- engine-pin resolver -> `generate_compose.validate_engine_pin`
- scope-gate predicate -> `generate_compose.scope_gate`
Both imported as functions. `generate_compose.py` got a small
behaviour-preserving refactor in this STEP solely to expose `scope_gate`
(the engine.type==vllm ∧ profile_runtime-exists ∧ genesis_equipped==false
predicate) as an importable pure function — `test-generate-compose.sh`
output is byte-identical pre/post.
"""
from __future__ import annotations
import os
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Optional
REPO_ROOT = Path(__file__).resolve().parents[3]
# Arch-kernel SM rule (locked design [C0] / brief): these KV formats require
# an SM-9.0-class kernel and are NOT loadable on Ampere sm_86 (RTX 3090).
# (fp8_e4m3 native compute + Gemma-TQ3 are the §1 confidently-wrong-on-3090
# risks Codex-r5 High-1 closes; mirrors compose_registry required_sm:9.0 +
# arch_patches Gemma kernel_constraints "fp8_e4m3 is not supported on sm_86".)
_ARCH_KERNEL_SM = {
"fp8_e4m3": 9.0,
"turboquant_3bit_nc": 9.0,
}
# ---------------------------------------------------------------------------
# [D] reuse — import the existing resolver + scope-gate as functions.
# ---------------------------------------------------------------------------
def _gc():
"""Import the [D] generator module (read-only reuse of its
engine-pin resolver + scope-gate predicate + loaders)."""
import sys
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
from scripts.lib import generate_compose as gc # noqa: E402
return gc
# ---------------------------------------------------------------------------
# Structured outputs
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class Refusal:
"""A stratum-2 (or hard) refusal. Not bypassable by any flag unless a
later stratum explicitly tags it so."""
reason: str
detail: str = ""
def __str__(self) -> str: # pragma: no cover - cosmetic
return f"{self.reason}: {self.detail}" if self.detail else self.reason
@dataclass
class Stratum2Result:
ok: bool
refusal: Optional[Refusal] = None
engine_id: Optional[str] = None
registry_entry: Optional[dict[str, Any]] = None
class C0State(str, Enum):
"""LOCKED design [C0] state set — exactly these three, never extended."""
ENGINE_SUPPORTED = "engine-supported"
ENGINE_SUPPORT_UNKNOWN = "engine-support-unknown"
NEEDS_TRC_ACK = "needs-trust-remote-code-ack"
class C0SubReason(str, Enum):
"""Structured SIDE field — NOT a top-level state (design-lock)."""
NO_ARCH_ROW = "no-arch-row"
RUNTIME_INCOMPATIBLE = "runtime-incompatible"
# Bypass-flag tokens P4 will act on (P3 only tags).
BYPASS_EXPERIMENTAL_ARCH = "--experimental-arch"
BYPASS_TRUST_REMOTE_CODE = "--trust-remote-code"
@dataclass
class C0Result:
state: C0State
sub_reason: Optional[C0SubReason] = None
bypassable_by: tuple[str, ...] = ()
detail: str = ""
resolved_pin: Optional[str] = None
arch: Optional[str] = None
class C2aState(str, Enum):
DISK_OK = "disk-ok"
DISK_SHORT = "disk-short"
@dataclass
class C2aResult:
state: C2aState
required_gb: Optional[float] = None
available_gb: Optional[float] = None
detail: str = ""
# ---------------------------------------------------------------------------
# Shared loaders (reuse [D]'s loaders so data parsing never drifts)
# ---------------------------------------------------------------------------
def _load(root: Path, registry, runtime, arches):
gc = _gc()
root = root or REPO_ROOT
if registry is None:
registry = gc.get_registry(root)
if runtime is None:
runtime = gc.load_runtime(root)
if arches is None:
arches = gc.load_arches(root)
return gc, root, registry, runtime, arches
# ---------------------------------------------------------------------------
# STRATUM 2 — --profile-like validity precondition (both paths, pre-[C0]).
# ---------------------------------------------------------------------------
def stratum2_profile_like(
registry_key: str,
*,
derive_result=None,
path: str = "B",
root: Optional[Path] = None,
registry=None,
runtime=None,
) -> Stratum2Result:
"""Validate the REQUIRED `--profile-like <COMPOSE_REGISTRY key>`.
Both paths: the named profile's `engine.type` must be `vllm` else
structured refuse `unsupported-runtime-engine` (covers
`llamacpp/default` engine=`llama-cpp-local`, mem_util=None).
Path A additionally:
- the profile must be `[D]`-emittable: REUSE `[D]`'s scope-gate
predicate (engine.type==vllm ∧ profile_runtime entry exists ∧
genesis_equipped==false). Non-emittable (Genesis/TQ3/llama.cpp,
e.g. `vllm/dual-turbo`) → refuse `profile-not-emittable` BEFORE
`[C0]`.
- `--profile-like`.model == resolved curated model AND
.weights_variant == matched variant (else refuse `profile-mismatch`).
Not bypassable by any flag (pick another profile).
"""
gc = _gc()
root = root or REPO_ROOT
if registry is None:
registry = gc.get_registry(root)
if runtime is None:
runtime = gc.load_runtime(root)
if registry_key not in registry:
return Stratum2Result(
ok=False,
refusal=Refusal(
"unknown-profile-like",
f"{registry_key!r} not in COMPOSE_REGISTRY",
),
)
entry = registry[registry_key]
engine_id = entry["engine"]
try:
engine = gc.load_engine(root, engine_id)
except gc.Refuse as r:
return Stratum2Result(
ok=False,
refusal=Refusal("unsupported-runtime-engine", str(r)),
engine_id=engine_id,
)
# --- both paths: engine.type must be vLLM ------------------------------
if engine.get("type") != "vllm":
return Stratum2Result(
ok=False,
refusal=Refusal(
"unsupported-runtime-engine",
f"--profile-like {registry_key!r} engine={engine_id} "
f"type={engine.get('type')!r} (mem_util="
f"{entry.get('mem_util')!r}); only vLLM profiles supply a "
f"valid Pull-Gate runtime shape",
),
engine_id=engine_id,
registry_entry=entry,
)
if path == "A":
# --- Path A: must be [D]-emittable (REUSE [D] scope-gate) ----------
try:
gc.scope_gate(engine_id, engine, runtime, registry_key)
except gc.Refuse as r:
return Stratum2Result(
ok=False,
refusal=Refusal("profile-not-emittable", str(r)),
engine_id=engine_id,
registry_entry=entry,
)
# --- Path A: model/variant must match the curated resolution ------
t1 = getattr(derive_result, "tier1", None) if derive_result else None
if t1 is None:
return Stratum2Result(
ok=False,
refusal=Refusal(
"profile-mismatch",
"Path A requires a curated (tier-1) deriver hit to "
"validate --profile-like model/variant",
),
engine_id=engine_id,
registry_entry=entry,
)
if entry.get("model") != t1.model_id or entry.get(
"weights_variant"
) != t1.weights_variant:
return Stratum2Result(
ok=False,
refusal=Refusal(
"profile-mismatch",
f"--profile-like {registry_key!r} is "
f"({entry.get('model')!r},{entry.get('weights_variant')!r}) "
f"but the curated slug resolves to "
f"({t1.model_id!r},{t1.weights_variant!r})",
),
engine_id=engine_id,
registry_entry=entry,
)
return Stratum2Result(
ok=True, engine_id=engine_id, registry_entry=entry
)
# ---------------------------------------------------------------------------
# [C0] runtime SM resolution helpers.
# ---------------------------------------------------------------------------
def _resolve_arch_row(gc, root, runtime, arches, model_slug: str):
"""REUSE [D]'s arch resolution (arch_model_xref.model_slugs join).
Returns (arch_name, arch_row) or (None, None) when there is no row."""
try:
return gc.resolve_arch(root, runtime, arches, model_slug)
except gc.Refuse:
return None, None
def _required_sm(engine: dict, entry: dict, kv_format: str) -> float:
"""hardware SM ≥ max(engine.min_sm, registry required_sm,
arch-kernel SM rule for the requested runtime).
- engine.min_sm : engines/<id>.yml `min_sm`
- registry required_sm : compose_registry `required_sm` (9.0 rows)
- arch-kernel SM rule : _ARCH_KERNEL_SM[kv_format] (fp8_e4m3 / Gemma-TQ3)
"""
eng_sm = float(engine.get("min_sm") or 0.0)
reg_sm = float(entry.get("required_sm") or 0.0)
arch_sm = float(_ARCH_KERNEL_SM.get(kv_format, 0.0))
return max(eng_sm, reg_sm, arch_sm)
# ---------------------------------------------------------------------------
# STRATUM 3 — [C0] engine-support gate.
# ---------------------------------------------------------------------------
def c0_engine_support(
registry_key: str,
derive_result,
*,
path: str = "B",
hardware_sm: float,
root: Optional[Path] = None,
registry=None,
runtime=None,
arches=None,
) -> C0Result:
"""`[C0]` — emit EXACTLY one locked design state + optional structured
sub-reason + structured bypassable-tag.
Order of determination (monotonic):
1. trust-remote-code (fail-closed): arch_patches
`requires_trust_remote_code ∈ {true, unverified}` OR config.json
`auto_map` present (P2 surfaces `profile.auto_map`) →
NEEDS_TRC_ACK. Bypassable only by `--trust-remote-code`; if there
is ALSO no arch row, P4 will additionally require
`--experimental-arch` (we tag both).
2. no arch row → ENGINE_SUPPORT_UNKNOWN / NO_ARCH_ROW
(bypassable by `--experimental-arch`).
3. arch row exists but runtime/hardware not loadable →
ENGINE_SUPPORT_UNKNOWN / RUNTIME_INCOMPATIBLE (NON-bypassable):
- REUSE [D] engine-pin resolver, assert loads:true
- tp ∈ arch valid_tp.tp_divisors
- kv_format ∈ engine supported_kv_formats AND vs arch
quant-kernel/MoE constraints
- hardware_sm ≥ max(engine.min_sm, registry required_sm,
arch-kernel SM rule)
4. else ENGINE_SUPPORTED.
Path A checks the curated profile's `weights_variant` against arch
constraints; Path B uses the deriver-resolved `weight_format`/quant
(NOT the cloned profile's weights_variant).
"""
gc, root, registry, runtime, arches = _load(root, registry, runtime, arches)
entry = registry[registry_key]
engine_id = entry["engine"]
engine = gc.load_engine(root, engine_id)
kv_format = entry.get("kv_format")
profile = getattr(derive_result, "profile", None) or {}
t1 = getattr(derive_result, "tier1", None)
# The arch / model under evaluation.
if path == "A" and t1 is not None:
model_slug = entry.get("model")
arch_from_config = None
else:
model_slug = derive_result.slug if derive_result else entry.get("model")
arch_from_config = profile.get("arch")
arch_name, arch_row = _resolve_arch_row(
gc, root, runtime, arches, model_slug
)
# Path B: the registry model_slug join won't resolve an uncurated slug;
# fall back to the config-derived architectures[0] for the matrix lookup.
if arch_row is None and arch_from_config:
arch_row = next(
(r for r in arches if r.get("arch") == arch_from_config), None
)
arch_name = arch_from_config if arch_row is not None else arch_name
has_auto_map = bool(profile.get("auto_map"))
# --- 1. trust-remote-code fail-closed ---------------------------------
trc = (arch_row or {}).get("requires_trust_remote_code")
trc_blocks = trc in {"true", "unverified"} or has_auto_map
if trc_blocks:
bypass = [BYPASS_TRUST_REMOTE_CODE]
why = []
if trc in {"true", "unverified"}:
why.append(f"requires_trust_remote_code={trc!r}")
if has_auto_map:
why.append("config.json auto_map present")
# If there is also no arch row, P4 additionally requires
# --experimental-arch (tag BOTH conditions; design-lock: still
# the single top-level NEEDS_TRC_ACK state).
if arch_row is None:
bypass.append(BYPASS_EXPERIMENTAL_ARCH)
why.append("no arch matrix row")
return C0Result(
state=C0State.NEEDS_TRC_ACK,
bypassable_by=tuple(bypass),
detail=(
f"arch {arch_name or arch_from_config or model_slug!r}: "
+ "; ".join(why)
),
arch=arch_name or arch_from_config,
)
# --- 2. no arch row → unknown / no-arch-row (experimental-arch only) ---
if arch_row is None:
return C0Result(
state=C0State.ENGINE_SUPPORT_UNKNOWN,
sub_reason=C0SubReason.NO_ARCH_ROW,
bypassable_by=(BYPASS_EXPERIMENTAL_ARCH,),
detail=(
f"no arch_patches matrix row for "
f"{arch_from_config or model_slug!r}; a pull would likely "
f"fail to boot"
),
arch=arch_from_config,
)
# --- 3. arch row exists: runtime + hardware loadability ----------------
def _incompat(detail: str) -> C0Result:
return C0Result(
state=C0State.ENGINE_SUPPORT_UNKNOWN,
sub_reason=C0SubReason.RUNTIME_INCOMPATIBLE,
bypassable_by=(), # NON-bypassable
detail=detail,
arch=arch_name,
)
# 3a. engine-pin resolver (REUSE [D]) — must map to a loads:true pin.
try:
resolved_pin = gc.validate_engine_pin(engine_id, engine, arch_row)
except gc.Refuse as r:
return _incompat(
f"engine-pin resolver: {r} (arch {arch_name})"
)
# 3b. tp ∈ arch valid_tp.tp_divisors
tp_divisors = (arch_row.get("valid_tp") or {}).get("tp_divisors") or []
if entry.get("tp") not in tp_divisors:
return _incompat(
f"tp={entry.get('tp')} not in arch {arch_name} "
f"valid_tp.tp_divisors {tp_divisors}"
)
# 3c. kv_format vs engine supported_kv_formats (quant-kernel constraint)
if kv_format not in (engine.get("supported_kv_formats") or []):
return _incompat(
f"kv_format {kv_format!r} not in engine {engine_id} "
f"supported_kv_formats {engine.get('supported_kv_formats')}"
)
# 3d. hardware SM ≥ max(engine.min_sm, registry required_sm, arch-kernel)
need_sm = _required_sm(engine, entry, kv_format)
if float(hardware_sm) < need_sm:
return _incompat(
f"hardware sm_{float(hardware_sm):g} < required sm_{need_sm:g} "
f"for runtime (engine.min_sm={engine.get('min_sm')}, "
f"registry.required_sm={entry.get('required_sm')}, "
f"arch-kernel[{kv_format}]="
f"{_ARCH_KERNEL_SM.get(kv_format, 0.0):g}); "
f"e.g. fp8_e4m3 / Gemma-TQ3 need SM 9.0 — NOT loadable on sm_86"
)
# --- 4. supported -----------------------------------------------------
return C0Result(
state=C0State.ENGINE_SUPPORTED,
detail=(
f"arch {arch_name} loadable on {resolved_pin} "
f"(tp={entry.get('tp')}, kv={kv_format}, "
f"sm_{float(hardware_sm):g} >= sm_{need_sm:g})"
),
resolved_pin=resolved_pin,
arch=arch_name,
)
# ---------------------------------------------------------------------------
# STRATUM 4 — [C2a] disk pre-gate (ordered AFTER [C0], before [B]).
# ---------------------------------------------------------------------------
def c2a_disk(
derive_result,
*,
hf_home: Optional[str] = None,
statvfs: Optional[Callable[[str], Any]] = None,
) -> C2aResult:
"""`[C2a]` — Σ(P2-selected weight blobs + required config/tokenizer
siblings) × 1.2 vs resolved HF_HOME free space.
`disk-short` → non-negotiable hard-abort (NO bypass tag — there is no
flag that can clear it; per design §5.2 / §4.1).
The footprint is the v0.8.0 [E] CONTRACT-3 SHARED `download_set()` total:
`[C2a]` sizes EXACTLY the union E2 fetches + E3 smokes — one function,
no parallel lists that can drift. When the derived profile carries the
raw HF siblings API (`profile["_hf_api"]`, the deriver's additive E2
surface) this gate sizes `deriver.sized_download_gb(api)` directly (which
internally calls the shared `download_set(api)`). Tier-1 curated hits
carry no API (the curated footprint is the variant `size_gb`) — fall
back to the precomputed footprint / variant size there. ×1.2 safety
margin (design §5.2). `resolve_hf_home()` is reused from P2's deriver
(the documented `--hf-home > $HF_HOME > $XDG_CACHE_HOME/huggingface > ~`
chain).
"""
from . import deriver as D
profile = getattr(derive_result, "profile", None) or {}
# v0.8.0 [E] CONTRACT-3: size the SHARED download_set() — the exact set
# E2 fetches. `sized_download_gb` calls `download_set(api)` internally,
# so [C2a] / E2 / E3 are provably the same set.
hf_api = profile.get("_hf_api")
if hf_api is not None:
footprint = D.sized_download_gb(hf_api)
else:
# tier-1 curated hit (no derived API) or a fixture without the API:
# fall back to the precomputed footprint / variant size.
footprint = profile.get("footprint_gb")
if footprint is None:
footprint = (
profile.get("weights_variant_size_gb")
or profile.get("weights_total_gb")
or 0.0
)
required_gb = round(float(footprint) * 1.2, 4)
target = D.resolve_hf_home(hf_home)
# Resolve free space against the nearest existing ancestor of the
# (possibly not-yet-created) target dir.
probe = target
while not probe.exists() and probe != probe.parent:
probe = probe.parent
sv = statvfs or os.statvfs
try:
st = sv(str(probe))
avail_bytes = st.f_bavail * st.f_frsize
except (OSError, AttributeError) as exc: # pragma: no cover - defensive
# Cannot determine free space → fail-closed to disk-short (honest:
# never print a fabricated fit per §1).
return C2aResult(
state=C2aState.DISK_SHORT,
required_gb=required_gb,
available_gb=None,
detail=f"could not stat {probe} for free space ({exc})",
)
available_gb = round(avail_bytes / (1024 ** 3), 4)
if available_gb < required_gb:
return C2aResult(
state=C2aState.DISK_SHORT,
required_gb=required_gb,
available_gb=available_gb,
detail=(
f"need {required_gb:g} GiB (footprint {float(footprint):g} "
f"GiB ×1.2) but only {available_gb:g} GiB free at {target} "
f"-> hard-abort (no bypass)"
),
)
return C2aResult(
state=C2aState.DISK_OK,
required_gb=required_gb,
available_gb=available_gb,
detail=(
f"{available_gb:g} GiB free >= {required_gb:g} GiB required "
f"at {target}"
),
)