Add 2x GB10 deployment recipe
Browse files- deploy/.env.hy3.example +30 -0
- deploy/README.md +81 -0
- deploy/docker-compose.hy3.yml +72 -0
- deploy/hy3-stack.service +23 -0
- deploy/hy3-watchdog.service +21 -0
- deploy/oom-guard.sh +23 -0
- deploy/start-hy3.sh +35 -0
- deploy/stop-hy3.sh +13 -0
- deploy/watchdog.sh +35 -0
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
|