jon1012 commited on
Commit
370d886
·
verified ·
1 Parent(s): 4d87454

Add 2x GB10 deployment recipe

Browse files
deploy/.env.hy3.example ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hy3-preview NVFP4 — 2-node vLLM TP=2 serving (clone of dspark fabric, Hy3 args)
2
+ WORKER_HOST=192.168.1.75
3
+ WORKER_DIR=/home/jon/hy3-serve
4
+ MASTER_ADDR=10.10.10.1
5
+ MASTER_PORT=25000
6
+ NCCL_IB_HCA=rocep1s0f1,roceP2p1s0f1
7
+ NCCL_SOCKET_IFNAME=enp1s0f1np1
8
+ NCCL_IB_GID_INDEX=5
9
+ NCCL_CROSS_NIC=1
10
+ NCCL_NET=IB
11
+ NCCL_IB_DISABLE=0
12
+ NCCL_CUMEM_ENABLE=0
13
+ NCCL_IGNORE_CPU_AFFINITY=1
14
+ NCCL_DEBUG=WARN
15
+ NCCL_NVLS_ENABLE=0
16
+
17
+ HF_CACHE=/home/jon/.cache/huggingface
18
+ WORKER_HF_CACHE=/home/jon/.cache/huggingface
19
+ HY3_MODEL_DIR=/home/jon/models/Hy3-preview-NVFP4
20
+ SERVED_MODEL_NAME=hy3-preview-nvfp4
21
+ HY3_VLLM_IMAGE=vllm/vllm-openai:hy3-arm64-cu130
22
+
23
+ VLLM_HOST=0.0.0.0
24
+ VLLM_HOST_IP=10.10.10.1
25
+ WORKER_VLLM_HOST_IP=10.10.10.2
26
+
27
+ MAX_MODEL_LEN=65536
28
+ MAX_NUM_SEQS=4
29
+ MAX_NUM_BATCHED_TOKENS=8192
30
+ GPU_MEMORY_UTILIZATION=0.85
deploy/README.md ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hy3 NVFP4 on 2× NVIDIA GB10 (DGX Spark) — vLLM serving recipe
2
+
3
+ Serve **[LibertAIDAI/Hy3-NVFP4](https://huggingface.co/LibertAIDAI/Hy3-NVFP4)** — a weight-only NVFP4 quant of Tencent's 295B `HYV3` MoE — on a **2-node GB10 / DGX-Spark cluster** (aarch64, compute capability **sm_121a**, ~120 GB unified memory/node) with vLLM tensor-parallel over RoCE.
4
+
5
+ This recipe is the result of ~10 failed attempts; the notes below exist so you don't repeat them. **The GB10 (sm_121) NVFP4-MoE kernel situation is a minefield** — the exact image + backend + memory settings here are load-bearing.
6
+
7
+ ## TL;DR (what actually works)
8
+
9
+ | Setting | Value | Why |
10
+ |---|---|---|
11
+ | Image | **`vllm/vllm-openai:hy3-arm64-cu130`** | CUDA-13 aarch64 vLLM 0.23.1 with `HYV3ForCausalLM`. **CUDA 13 is mandatory** for the FP4 MoE kernels; the `cu129` image fails. |
12
+ | MoE backend | **`--moe-backend marlin`** | The only NVFP4-MoE backend that runs on sm_121 today (dequant FP4→FP16). See the dead-ends table. |
13
+ | Stability | **`--enforce-eager`** + docker **`mem_limit`** + an OOM guard | Prevents marlin's incremental repack / graph capture from overcommitting the 120 GB unified pool and swap-wedging the box. |
14
+ | Fabric | **`GLOO_SOCKET_IFNAME` = the RoCE iface** (=`NCCL_SOCKET_IFNAME`) | vLLM 0.23 multinode Gloo coord binds `127.0.0.1` otherwise → `Gloo connectFullMesh Connection refused`. |
15
+ | Parallelism | `--tensor-parallel-size 2 --nnodes 2 --distributed-executor-backend mp` | 169 GB checkpoint doesn't fit one 120 GB node. |
16
+
17
+ ## Hardware / prerequisites
18
+
19
+ - **2× GB10 / DGX Spark** (or MSI EdgeXpert GB10 etc.), aarch64, sm_121a, ~120 GB unified, NVIDIA driver **≥ 580** (CUDA-13 capable), Docker + `docker compose`.
20
+ - A **direct RoCE link** between the nodes (QSFP DAC) with static point-to-point IPs (e.g. `10.10.10.1/.2`) and `NCCL_IB_GID_INDEX` verified (`show_gids` → the RoCEv2 IPv4 GID; often 5, sometimes 3 — **verify per firmware**).
21
+ - The **169 GB** NVFP4 checkpoint present on **both** nodes at the same path (each TP rank reads all shards to extract its slice). `rsync` it to both; a fast cipher helps (`-e "ssh -c aes128-gcm@openssh.com"`).
22
+
23
+ ## Deploy
24
+
25
+ 1. **Pull the CUDA-13 image on both nodes:**
26
+ ```bash
27
+ docker pull vllm/vllm-openai:hy3-arm64-cu130
28
+ ```
29
+ 2. **Put the files** (`docker-compose.hy3.yml`, `.env.hy3`, `start-hy3.sh`, `stop-hy3.sh`, `oom-guard.sh`) in the same dir on the **head** node; edit `.env.hy3` (fabric IPs/iface, `HY3_MODEL_DIR`, model name). The start script SCPs the compose+env to the worker.
30
+ 3. **Start (worker-first):**
31
+ ```bash
32
+ ./start-hy3.sh # brings up worker (rank 1, headless) then head (rank 0), waits for /v1/models
33
+ ./oom-guard.sh & # optional but recommended: stops the container if free RAM enters the thrash zone
34
+ ```
35
+ First start takes ~11 min (weight load + marlin incremental repack + profiling).
36
+ 4. **Smoke test:**
37
+ ```bash
38
+ curl -s localhost:8888/v1/chat/completions -H 'Content-Type: application/json' -d \
39
+ '{"model":"hy3-preview-nvfp4","messages":[{"role":"user","content":"capital of France? one word"}],"max_tokens":8,"temperature":0}'
40
+ # -> "Paris"
41
+ ```
42
+
43
+ ## Memory math (the binding constraint)
44
+
45
+ At TP=2 each node holds ~**84.5 GB** of weights (169 GB / 2). That leaves ~**35 GB** of the 120 GB unified pool for the KV cache pool, activations, marlin repack scratch, and (if not eager) CUDA-graph capture. Keep it safe:
46
+ - `--enforce-eager` (skip graph capture — the biggest transient) — costs throughput but is the difference between "serves" and "swap-wedges the whole box".
47
+ - `--kv-cache-dtype fp8` + a bounded `--max-model-len` / `--max-num-seqs` so the KV pool fits `--gpu-memory-utilization` (0.85 works; KV is sharded across TP so 64K×4-seqs fits).
48
+ - **`mem_limit: 112g`** on the container so an overcommit gets the *container* OOM-killed, not the whole box (which needs a hard reboot when it swap-wedges — pingable but SSH-dead).
49
+
50
+ ## Dead-ends (do NOT waste time on these)
51
+
52
+ | Attempt | Result |
53
+ |---|---|
54
+ | Custom dspark fusion image (cu13.2) | any FP4 kernel JIT → `cudaErrorUnsupportedPtxVersion` (toolkit > driver). Dead end. |
55
+ | Stock `v0.23.0-aarch64-cu129`, default (flashinfer-cutlass) MoE | `cudaErrorNoKernelImageForDevice` — flashinfer ships **zero sm_120/121 cubins**; CUTLASS grouped FP4 GEMM is broken on sm120/121. |
56
+ | `--moe-backend flashinfer_cutedsl` | rejected: that kernel is gated to `family(100)` (datacenter sm_100), not sm_121. |
57
+ | `--moe-backend flashinfer_b12x` on `cu129` | `b12x fused MoE requires CUDA 13 or later. Current: 12.9`. |
58
+ | `--moe-backend flashinfer_b12x` on `hy3-arm64-cu130` (cutlass-dsl 4.5.2) | selects + loads, then **livelocks** in the CuTe-DSL JIT (4.5.x breaks sm121 PTX codegen). |
59
+ | Downgrade `nvidia-cutlass-dsl==4.4.2` | breaks flashinfer 0.6.13 import (`OperandMajorMode` missing). b12x is version-locked to a custom flashinfer build that isn't published. |
60
+ | `--moe-backend marlin` **on cu129/dspark** | kernel works but the marlin repack + graph capture **swap-wedged the box** (needed a reboot ×2). |
61
+ | **`--moe-backend marlin` on `hy3-arm64-cu130` + `--enforce-eager` + `mem_limit`** | ✅ **works.** Marlin repacks incrementally per-shard (stable memory), serves coherently. |
62
+
63
+ The upstream fix for a *native* fast FP4 MoE path on sm_121 is vLLM's **b12x / CuTe-DSL** backend (PR #40082) — but it currently needs a from-source flashinfer + a specific `nvidia-cutlass-dsl` built for sm_121, which no published image provides yet. When that lands, `--moe-backend flashinfer_b12x` on a CUDA-13 image is the intended (faster) replacement for marlin here.
64
+
65
+ ## The checkpoint
66
+
67
+ `LibertAIDAI/Hy3-NVFP4` — weight-only NVFP4 on the 192 routed-expert FFNs (46,080 tensors, ~98% of the model); attention, shared expert, routers, dense/MTP MLP, embeddings, `lm_head`, norms kept BF16. Per-expert round-trip cos ≈ 0.995. Produced with a memory-frugal shard-streaming ModelOpt pass (no calibration data). `config.json`'s `quantization_config.ignore` must use vLLM's **fused** module names (`*.self_attn.qkv_proj`, `*.mlp.gate_up_proj`, …) or vLLM mis-quantizes BF16 layers and crashes on a shape assert.
68
+
69
+ _Quantized & documented by [LibertAI](https://libertai.io). Not affiliated with Tencent. Subject to the Tencent Hy Community License._
70
+
71
+ ## Boot persistence (systemd)
72
+
73
+ `hy3-stack.service` (oneshot, `RemainAfterExit`, waits for the worker to be SSH-able, then worker-first start) + `hy3-watchdog.service` (`After=hy3-stack`; polls `/health`, restarts worker-first on death with an 11-min cooldown). Install on the head node:
74
+
75
+ ```bash
76
+ sudo cp hy3-stack.service hy3-watchdog.service /etc/systemd/system/
77
+ sudo systemctl daemon-reload
78
+ sudo systemctl enable --now hy3-stack.service hy3-watchdog.service
79
+ ```
80
+
81
+ To replace an existing DeepSeek/DSpark stack on the same `:8888`, disable its units first (`sudo systemctl disable --now dspark-stack dspark-watchdog`) so both don't fight for the port. A front proxy (nginx/Caddy) pointed at `:8888` needs no change if the served-model-name advertises the old name too (`--served-model-name hy3-preview-nvfp4 <old-name>`).
deploy/docker-compose.hy3.yml ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ vllm-hy3:
3
+ image: ${HY3_VLLM_IMAGE:-vllm/vllm-openai:v0.23.0-aarch64-cu129}
4
+ network_mode: host
5
+ ipc: host
6
+ shm_size: "64gb"
7
+ # hard cap so a marlin repack overcommit gets the CONTAINER OOM-killed, not the box
8
+ mem_limit: "112g"
9
+ ulimits:
10
+ memlock: -1
11
+ stack: 67108864
12
+ gpus: all
13
+ devices:
14
+ - /dev/infiniband:/dev/infiniband
15
+ volumes:
16
+ - ${HF_CACHE:-${HOME}/.cache/huggingface}:/root/.cache/huggingface
17
+ - ${HY3_MODEL_DIR:-/home/jon/models/Hy3-preview-NVFP4}:/models/hy3:ro
18
+ environment:
19
+ HF_HOME: /root/.cache/huggingface
20
+ VLLM_CACHE_ROOT: /root/.cache/huggingface/vllm-cache
21
+ # proven NVFP4-on-GB10 path (ASUS box): stock cu129 image + marlin
22
+ # Path 1: force the purpose-built sm_121 CuTe-DSL MoE kernel (vLLM PR #40082)
23
+ # via --moe-backend flashinfer_cutedsl below. Do NOT set
24
+ # VLLM_USE_FLASHINFER_MOE_FP4=0 (that removes the flashinfer backends incl cutedsl).
25
+ VLLM_HOST_IP: "${VLLM_HOST_IP:-}"
26
+ VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
27
+ NCCL_NET: "${NCCL_NET:-IB}"
28
+ NCCL_IB_DISABLE: "${NCCL_IB_DISABLE:-0}"
29
+ NCCL_IB_HCA: "${NCCL_IB_HCA}"
30
+ NCCL_SOCKET_IFNAME: "${NCCL_SOCKET_IFNAME}"
31
+ # vLLM 0.23.0's multinode Gloo coord group binds localhost unless told the
32
+ # fabric iface (older dspark image didn't need this).
33
+ GLOO_SOCKET_IFNAME: "${NCCL_SOCKET_IFNAME}"
34
+ NCCL_IB_GID_INDEX: "${NCCL_IB_GID_INDEX:-}"
35
+ NCCL_CROSS_NIC: "${NCCL_CROSS_NIC:-1}"
36
+ NCCL_CUMEM_ENABLE: "${NCCL_CUMEM_ENABLE:-0}"
37
+ NCCL_IGNORE_CPU_AFFINITY: "${NCCL_IGNORE_CPU_AFFINITY:-1}"
38
+ NCCL_DEBUG: "${NCCL_DEBUG:-WARN}"
39
+ NCCL_NVLS_ENABLE: "${NCCL_NVLS_ENABLE:-0}"
40
+ NODE_RANK: "${NODE_RANK}"
41
+ HEADLESS: "${HEADLESS:-}"
42
+ MASTER_ADDR: "${MASTER_ADDR}"
43
+ MASTER_PORT: "${MASTER_PORT:-25000}"
44
+ entrypoint:
45
+ - bash
46
+ - -lc
47
+ command:
48
+ - >
49
+ exec vllm serve /models/hy3
50
+ --served-model-name ${SERVED_MODEL_NAME:-hy3-preview-nvfp4} deepseek-v4-flash-dspark
51
+ --host ${VLLM_HOST:-0.0.0.0}
52
+ --port 8888
53
+ --trust-remote-code
54
+ --tensor-parallel-size 2
55
+ --pipeline-parallel-size 1
56
+ --moe-backend marlin
57
+ --kv-cache-dtype fp8
58
+ --max-model-len ${MAX_MODEL_LEN:-65536}
59
+ --max-num-seqs ${MAX_NUM_SEQS:-4}
60
+ --max-num-batched-tokens ${MAX_NUM_BATCHED_TOKENS:-8192}
61
+ --gpu-memory-utilization ${GPU_MEMORY_UTILIZATION:-0.85}
62
+ --enforce-eager
63
+ --enable-prefix-caching
64
+ --tool-call-parser hy_v3
65
+ --reasoning-parser hy_v3
66
+ --enable-auto-tool-choice
67
+ --distributed-executor-backend mp
68
+ --nnodes 2
69
+ --node-rank ${NODE_RANK}
70
+ --master-addr ${MASTER_ADDR}
71
+ --master-port ${MASTER_PORT:-25000}
72
+ ${HEADLESS:+--headless}
deploy/hy3-stack.service ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [Unit]
2
+ Description=Hy3-preview NVFP4 2-node vLLM stack (marlin, worker-first)
3
+ After=network-online.target docker.service
4
+ Wants=network-online.target
5
+ Requires=docker.service
6
+
7
+ [Service]
8
+ Type=oneshot
9
+ RemainAfterExit=yes
10
+ User=jon
11
+ Group=jon
12
+ SupplementaryGroups=docker
13
+ Environment=HOME=/home/jon
14
+ WorkingDirectory=/home/jon/hy3-serve
15
+ # Wait (up to 10 min) for the worker box to be SSH-able before orchestrating
16
+ ExecStartPre=/bin/bash -c 'set -a; . /home/jon/hy3-serve/.env.hy3; set +a; for i in $(seq 1 60); do ssh -o ConnectTimeout=5 -o BatchMode=yes "$WORKER_HOST" true && exit 0; sleep 10; done; echo "worker $WORKER_HOST not reachable" >&2; exit 1'
17
+ ExecStart=/home/jon/hy3-serve/start-hy3.sh
18
+ ExecStop=/home/jon/hy3-serve/stop-hy3.sh
19
+ TimeoutStartSec=1800
20
+ TimeoutStopSec=300
21
+
22
+ [Install]
23
+ WantedBy=multi-user.target
deploy/hy3-watchdog.service ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [Unit]
2
+ Description=Hy3 vLLM health watchdog (auto-restart on death)
3
+ # After=hy3-stack: at boot don't poll until the stack's start script returns
4
+ # (weight load + marlin repack + profiling ~11 min), else the failure threshold
5
+ # fires mid-init and stops the starting stack.
6
+ After=network-online.target docker.service hy3-stack.service
7
+ Wants=network-online.target
8
+
9
+ [Service]
10
+ Type=simple
11
+ User=jon
12
+ Group=jon
13
+ SupplementaryGroups=docker
14
+ Environment=HOME=/home/jon
15
+ WorkingDirectory=/home/jon/hy3-serve
16
+ ExecStart=/home/jon/hy3-serve/watchdog.sh
17
+ Restart=always
18
+ RestartSec=15
19
+
20
+ [Install]
21
+ WantedBy=multi-user.target
deploy/oom-guard.sh ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Runs locally on the head. Stops Hy3 gracefully if free RAM enters the thrash
3
+ # zone, so vLLM overcommit can't wedge the box (SSH-dead) like last time.
4
+ # Exits 0 when API is up (guard no longer needed), 2 if it had to intervene.
5
+ LOG=/tmp/hy3-guard.log
6
+ THRESH_MB=6000
7
+ echo "$(date -u) guard start (thresh ${THRESH_MB}MB)" > "$LOG"
8
+ for i in $(seq 1 240); do
9
+ cid=$(sg docker -c "docker ps -q --filter name=hy3" 2>/dev/null | head -1)
10
+ if [ -z "$cid" ]; then echo "$(date -u) container gone, exiting" >> "$LOG"; exit 0; fi
11
+ if curl -fsS --max-time 3 http://127.0.0.1:8888/v1/models >/dev/null 2>&1; then
12
+ echo "$(date -u) API up, guard done" >> "$LOG"; exit 0
13
+ fi
14
+ avail=$(free -m | awk 'NR==2{print $7}')
15
+ echo "$(date -u) avail=${avail}MB" >> "$LOG"
16
+ if [ "${avail:-0}" -lt "$THRESH_MB" ]; then
17
+ echo "$(date -u) !!! OOM GUARD TRIP avail=${avail}MB — stopping hy3 to protect the box" >> "$LOG"
18
+ ( cd /home/jon/hy3-serve && bash stop-hy3.sh ) >> "$LOG" 2>&1
19
+ exit 2
20
+ fi
21
+ sleep 5
22
+ done
23
+ echo "$(date -u) guard timeout" >> "$LOG"; exit 3
deploy/start-hy3.sh ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
4
+ ENV_FILE="$SCRIPT_DIR/.env.hy3"
5
+ COMPOSE_FILE="$SCRIPT_DIR/docker-compose.hy3.yml"
6
+ API_URL="http://127.0.0.1:8888/v1/models"
7
+ WAIT_ATTEMPTS="${WAIT_ATTEMPTS:-120}"
8
+ WAIT_SECONDS="${WAIT_SECONDS:-15}"
9
+
10
+ set -a; source "$ENV_FILE"; set +a
11
+ cd "$SCRIPT_DIR"
12
+ WORKER_DIR="${WORKER_DIR:-$SCRIPT_DIR}"
13
+ REMOTE="cd $WORKER_DIR && env -u MASTER_ADDR -u MASTER_PORT -u NODE_RANK -u HEADLESS COMPOSE_DISABLE_ENV_FILE=1"
14
+
15
+ echo "Syncing Hy3 files to ${WORKER_HOST}:${WORKER_DIR}"
16
+ ssh "$WORKER_HOST" "mkdir -p $WORKER_DIR"
17
+ scp -q "$COMPOSE_FILE" "${WORKER_HOST}:${WORKER_DIR}/docker-compose.hy3.yml"
18
+ scp -q "$ENV_FILE" "${WORKER_HOST}:${WORKER_DIR}/.env.hy3"
19
+
20
+ echo "Starting Hy3 worker (rank 1) on ${WORKER_HOST}..."
21
+ ssh "$WORKER_HOST" "$REMOTE NODE_RANK=1 HEADLESS=1 VLLM_HOST_IP='$WORKER_VLLM_HOST_IP' docker compose --env-file .env.hy3 -f docker-compose.hy3.yml up -d"
22
+
23
+ echo "Starting Hy3 head (rank 0)..."
24
+ COMPOSE_DISABLE_ENV_FILE=1 NODE_RANK=0 HEADLESS= docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" up -d
25
+
26
+ echo "Waiting for Hy3 vLLM API (up to $((WAIT_ATTEMPTS*WAIT_SECONDS))s)..."
27
+ for _ in $(seq 1 "$WAIT_ATTEMPTS"); do
28
+ if curl -fsS --max-time 5 "$API_URL" >/dev/null 2>&1; then
29
+ echo "Hy3 vLLM is UP: $API_URL"; exit 0
30
+ fi
31
+ sleep "$WAIT_SECONDS"
32
+ done
33
+ echo "TIMEOUT. head logs:" >&2
34
+ COMPOSE_DISABLE_ENV_FILE=1 docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" logs --tail=80 vllm-hy3 2>&1 | tail -80
35
+ exit 1
deploy/stop-hy3.sh ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -uo pipefail
3
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
4
+ ENV_FILE="$SCRIPT_DIR/.env.hy3"
5
+ COMPOSE_FILE="$SCRIPT_DIR/docker-compose.hy3.yml"
6
+ set -a; source "$ENV_FILE"; set +a
7
+ cd "$SCRIPT_DIR"
8
+ WORKER_DIR="${WORKER_DIR:-$SCRIPT_DIR}"
9
+ echo "Stopping Hy3 head..."
10
+ COMPOSE_DISABLE_ENV_FILE=1 docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" down || true
11
+ echo "Stopping Hy3 worker on ${WORKER_HOST}..."
12
+ ssh "$WORKER_HOST" "cd '$WORKER_DIR' && COMPOSE_DISABLE_ENV_FILE=1 docker compose --env-file .env.hy3 -f docker-compose.hy3.yml down" || true
13
+ echo "Hy3 stopped."
deploy/watchdog.sh ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # hy3-watchdog: poll vLLM /health and self-heal the 2-node Hy3 stack
3
+ # (worker-first) if the server dies. Logs to /home/jon/hy3-serve/watchdog.log.
4
+ set -uo pipefail
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+ HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:8888/health}"
7
+ POLL_INTERVAL="${POLL_INTERVAL:-30}"
8
+ FAIL_THRESHOLD="${FAIL_THRESHOLD:-3}"
9
+ COOLDOWN="${COOLDOWN:-900}" # min secs between restarts (init ~11 min)
10
+ LOG="${LOG:-$SCRIPT_DIR/watchdog.log}"
11
+ fails=0; last_restart=0
12
+ log(){ echo "$(date -u '+%Y-%m-%dT%H:%M:%SZ') $*" | tee -a "$LOG" >&2; }
13
+ log "watchdog start (health=$HEALTH_URL threshold=$FAIL_THRESHOLD)"
14
+ while true; do
15
+ if curl -fsS --max-time 10 "$HEALTH_URL" >/dev/null 2>&1; then
16
+ [ "$fails" -ne 0 ] && log "health recovered after $fails fail(s)"
17
+ fails=0
18
+ else
19
+ fails=$((fails+1))
20
+ log "/health DOWN (consecutive=$fails/$FAIL_THRESHOLD)"
21
+ if [ "$fails" -ge "$FAIL_THRESHOLD" ]; then
22
+ now=$(date +%s)
23
+ if [ $((now - last_restart)) -lt "$COOLDOWN" ]; then
24
+ log "in cooldown ($((now-last_restart))s < ${COOLDOWN}s); skip"
25
+ else
26
+ log "RESTARTING Hy3 stack (worker-first)"
27
+ bash "$SCRIPT_DIR/stop-hy3.sh" >>"$LOG" 2>&1 || true
28
+ bash "$SCRIPT_DIR/start-hy3.sh" >>"$LOG" 2>&1 || true
29
+ ( nohup bash "$SCRIPT_DIR/oom-guard.sh" >/dev/null 2>&1 & ) || true
30
+ last_restart=$(date +%s); fails=0
31
+ fi
32
+ fi
33
+ fi
34
+ sleep "$POLL_INTERVAL"
35
+ done