Files
club-3090/scripts/tests/test-compose-mounts-resolve.sh
T
noonghunnaandClaude Opus 4.8 45686808cb Prune Gemma vLLM variants (9->3) and pin survivors to v0.21.0
Every gemma-4-31b vLLM compose pinned a purged Docker Hub nightly
(e47c98ef / bf610c2f), un-bootable for fresh users (#250, #167 class).
Prune the 9-variant set to 3 and repoint survivors onto the immutable
stable release tag vllm/vllm-openai:v0.21.0 (never :latest).

Survivors: vllm/gemma-mtp (bf16 dual, default), vllm/gemma-int8 (int8-PTH
dual), vllm/gemma-mtp-tp1 (fp8 single). Dropped: gemma-dflash,
gemma-dflash-int8, gemma-int8-tq3, gemma-bf16, gemma-awq; the 262K path
folds into gemma-int8 via a CTX env override. Gemma is decoupled from the
shared Qwen nightly profiles via a new vllm-gemma-stable engine profile;
Qwen nightly profiles are untouched.

Diagnose decoupling: the pruned gemma dflash patch dirs doubled as the
diagnose tool's cross-model disk-source proxies for two Qwen overlays
(vllm-pr41703-dflash, vllm-pr42102-dflash-kv-quant). Those capabilities
are image-baked in the pinned nightly, not mountable files, so mark them
image_baked and have diagnose skip the disk-source check for image-baked
overlays. Runtime-neutral (no Qwen compose mounts them).

switch.sh launcher: handle the new VLLM_IMAGE engine-pin export (the twin
loop in launch.sh had it; switch.sh did not -> "unexpected engine pin
export"), and guard the VLLM_NIGHTLY_SHA echo for the image-only stable
profile (was unbound under set -u).

Validated on 2x RTX 3090 (Ampere, v0.21.0): full scripts/tests/*.sh suite
35/0; live boots of gemma-mtp (bf16) and gemma-int8 serve coherent output,
qwen vllm/dual regression-clean. gemma-mtp-tp1 (fp8_e4m3) is correctly
SM-gated (required_sm=9.0) -- confirmed Ampere-incompatible (Triton
"fp8e4nv not supported on sm_86"), a documented 32 GB+/Hopper variant;
beellama remains the single-card gemma path on Ampere.

Refs #451, #250, #167.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-05-29 07:59:42 +05:00

90 lines
3.2 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR"
export PYTHONPATH="$ROOT_DIR${PYTHONPATH:+:$PYTHONPATH}"
python3 - <<'PY'
from __future__ import annotations
import os
import re
from pathlib import Path
import yaml
root = Path.cwd().resolve()
compose_files = sorted(Path("models").glob("*/*/compose/*/*/*.yml"))
failures: list[str] = []
ENV_DEFAULT = re.compile(r"^\$\{[^}:]+:-(.+)\}$")
def host_source(source: str) -> str | None:
match = ENV_DEFAULT.match(source)
if match:
return match.group(1)
if source.startswith("${"):
return None
if source.startswith("/") or source.startswith("~"):
return None
if not source.startswith("."):
return None
return source
def split_volume(value: str) -> tuple[str, str | None]:
# Compose short-form host paths in this repo do not contain ':' except as
# the source/target separator. Preserve the first two fields and ignore ro/rw.
parts = value.split(":")
if len(parts) < 2:
return value, None
return parts[0], parts[1]
def check(cond: bool, msg: str) -> None:
if cond:
print(f"PASS: {msg}")
else:
print(f"FAIL: {msg}")
failures.append(msg)
for compose in compose_files:
data = yaml.safe_load(compose.read_text()) or {}
base = compose.parent.resolve()
services = data.get("services", {}) or {}
for service_name, service in sorted(services.items()):
for volume in service.get("volumes", []) or []:
if isinstance(volume, str):
raw_source, target = split_volume(volume)
source = host_source(raw_source)
elif isinstance(volume, dict):
raw_source = str(volume.get("source", ""))
target = str(volume.get("target", ""))
source = host_source(raw_source)
else:
continue
if source is None:
continue
raw_path = base / source
lexical = Path(os.path.abspath(raw_path))
resolved = raw_path.resolve()
label = f"{compose}:{service_name}:{target or raw_source}"
check(str(lexical).startswith(str(root)), f"{label}: resolves inside repo")
if "models-cache" in resolved.parts:
check(resolved == root / "models-cache" or root / "models-cache" in resolved.parents, f"{label}: models-cache points at repo root")
elif "cache" in resolved.parts:
cache_idx = resolved.parts.index("cache")
cache_root = Path(*resolved.parts[:cache_idx + 1])
check(cache_root.exists(), f"{label}: cache root exists")
elif "patches" in resolved.parts and "genesis" in resolved.parts:
patches_idx = resolved.parts.index("patches")
patches_root = Path(*resolved.parts[:patches_idx + 1])
check(patches_root.exists(), f"{label}: patches root exists")
else:
check(resolved.exists(), f"{label}: source exists")
if failures:
raise SystemExit(f"{len(failures)} mount-resolution checks failed")
PY
echo "test-compose-mounts-resolve: ok"