The CLI seam the c3 cluster UX (Phase C) will consume, and a full headless/ SSH cluster capability on its own. A "cluster" = a named model on a chosen GPU set + port (an estate instance). - estate_cli.py gains create / list / status / rm verbs (D2: NEW verbs go IN estate_cli.py, which owns the schema + validate_estate + boot/down — ONE validation path shared with hand-written estate files and the wizard). up/down reuse `boot`/`down --only <name>`. - create runs the D1 fit-vs-set logic (#610 addendum 3): kv-calc --card is single-card + registry TP, so count != compose TP is a HARD REJECT, a heterogeneous set is estimated against its min-VRAM card (+ note), and the whole set is re-validated (validate_estate: GPU collision, port collision, per-instance fits) before append. GPU indices stay index-based in the estate file; UUIDs resolve at boot (Phase A). - status carries the {requested, actual, placement} verdict per cluster (the shape the c3 badge reads); list/status take --json. - scripts/cluster.sh: the ergonomic bash front (create/list/status/up/down/ rm), a thin wrapper over estate_cli.py. - test-cluster-cli.sh (NEW): full lifecycle hardware-free via CLUB3090_FAKE_ GPUS — create/D1-reject/collision-reject/list/status/rm. LIVE-VERIFIED (2x3090): create chat (fit-clean ~20.2 GiB) → up → estate boot → "✓ placement verified" → status "serving ✓ placement=ok", GPU0 idle / GPU1 loaded → down → rm. D1 count!=TP + GPU-collision rejects confirmed. Part of #610 (Phase A′). Next: Phase C (c3 cluster view + wizard). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EfF565T9eSLaqGzidyJ1Pm
56 lines
1.9 KiB
Bash
Executable File
56 lines
1.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# cluster.sh — GPU-cluster management (#610 Phase A′).
|
||
#
|
||
# A "cluster" = a named model on a chosen GPU set + port (an estate instance).
|
||
# This is the ergonomic front over the estate CLI (estate_cli.py owns the
|
||
# schema, validate_estate, kv-calc fit, and boot/down — ONE validation path
|
||
# shared with hand-written estate files and, later, the c3 wizard).
|
||
#
|
||
# Usage:
|
||
# bash scripts/cluster.sh create <name> --gpus 1,2 --slug vllm/dual [--port N]
|
||
# bash scripts/cluster.sh list [--json]
|
||
# bash scripts/cluster.sh status [--json]
|
||
# bash scripts/cluster.sh up <name> # boot just this cluster
|
||
# bash scripts/cluster.sh down <name> # stop just this cluster
|
||
# bash scripts/cluster.sh rm <name> # remove from the estate file
|
||
#
|
||
# The artifact is the estate file (default scripts/lib/profiles/estate.yml;
|
||
# override with --file). GPU indices stay index-based in the file and are
|
||
# resolved to UUIDs at boot (#610 Phase A) so clusters land on the cards they
|
||
# claimed on both container runtimes (classic nvidia + CDI/NixOS).
|
||
set -euo pipefail
|
||
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
ESTATE_CLI="${ROOT_DIR}/scripts/lib/profiles/estate_cli.py"
|
||
|
||
usage() {
|
||
sed -n '2,26p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
||
}
|
||
|
||
cmd="${1:-}"
|
||
[[ -n "$cmd" ]] || { usage; exit 2; }
|
||
shift || true
|
||
|
||
case "$cmd" in
|
||
create|list|status|rm)
|
||
exec python3 "$ESTATE_CLI" "$cmd" "$@"
|
||
;;
|
||
up)
|
||
name="${1:-}"; shift || true
|
||
[[ -n "$name" ]] || { echo "cluster.sh up <name>" >&2; exit 2; }
|
||
exec python3 "$ESTATE_CLI" boot --only "$name" "$@"
|
||
;;
|
||
down)
|
||
name="${1:-}"; shift || true
|
||
[[ -n "$name" ]] || { echo "cluster.sh down <name>" >&2; exit 2; }
|
||
exec python3 "$ESTATE_CLI" down --only "$name" "$@"
|
||
;;
|
||
-h|--help|help)
|
||
usage
|
||
;;
|
||
*)
|
||
echo "cluster.sh: unknown command '$cmd'" >&2
|
||
usage
|
||
exit 2
|
||
;;
|
||
esac
|