Эх сурвалжийг харах

docs: update AGENTS.md and README.md to match current implementation

AGENTS.md updated:
- Module layout: added missing modules (cli, docker/tests, tmux/*, power,
  home_assistant, host_keys, exec, pre_serve, before_serve, locales)
- Commands: added power on/off/switch, exec aliases, prefix matching
- Config: added entity per-node, extra_entities per-cluster, home_assistant,
  exec_aliases sections
- Recipe: added shm_size, before_serve, entrypoint, port=30000 default
- Docker launch: updated env vars (added MN_IF_NAME, UCX_NET_DEVICES,
  OMPI_MCA_btl_tcp_if_include, VLLM_LOGGING_COLOR, TERM; removed
  NCCL_CUMEM_ENABLE, NCCL_CROSS_NIC); vLLM mp backend is auto-detected;
  cluster auto-select picks smallest, supports prefix matching
- Dependencies: updated versions (russh 0.61, reqwest 0.13, dirs 6,
  sha2 0.11, fluent-i18n)

README.md updated:
- Available Recipes: added deepseek-v4-flash and minimax-m3-mxfp8
- Env vars table: removed NCCL_CUMEM_ENABLE and NCCL_CROSS_NIC;
  added MN_IF_NAME, UCX_NET_DEVICES, OMPI_MCA_btl_tcp_if_include,
  VLLM_LOGGING_COLOR
- Power: added switch command docs
- Dependencies: updated versions, added reqwest, dirs, fluent-i18n
- Added i18n section documenting all 6 locales
Alexander Korolev 4 сар өмнө
parent
commit
66a06964de
2 өөрчлөгдсөн 190 нэмэгдсэн , 37 устгасан
  1. 108 31
      AGENTS.md
  2. 82 6
      README.md

+ 108 - 31
AGENTS.md

@@ -5,11 +5,25 @@ Rust CLI tool for launching vLLM/SGLang containers across a DGX Spark cluster. S
 ## Project Layout
 
 - `Cargo.toml` — binary crate `vllm-spark-launcher`. Edition 2024.
-- `src/main.rs` — CLI parsing, Docker orchestration, Doctor mode, health check, log streaming.
+- `src/main.rs` — CLI entrypoint, command dispatch, i18n init.
+- `src/cli.rs` — `clap` derive-based CLI definitions (Commands, PowerAction, TmuxCommands).
+- `src/config.rs` — Config, Cluster, Node, Recipe, NcclConfig, HomeAssistantConfig deserialization; cluster resolution (exact + prefix match, auto-select smallest).
 - `src/ssh.rs` — SSH client session management, key loading, host key verification, retry logic.
-- `src/tmux.rs` — tmux session manager: start/stop/status/logs/attach across a cluster.
-- `src/health.rs` — tmux health-check helpers (unused, see below).
-- `config.toml` — runtime config: SSH key, cluster definitions, per-cluster NCCL settings.
+- `src/docker/mod.rs` — Docker command builder: serve, stop, health, logs, log streaming.
+- `src/docker/tests.rs` — Docker command builder tests (MP, SGLang, Ray, keep, port, env overrides).
+- `src/tmux/mod.rs` — tmux module root (session manager, serve, ops).
+- `src/tmux/serve.rs` — tmux serve logic (window/panel layout).
+- `src/tmux/ops.rs` — tmux stop/status/logs/attach operations.
+- `src/health.rs` — tmux health-check helpers.
+- `src/power.rs` — `power on/off/switch` flows (SSH shutdown + HA toggle).
+- `src/home_assistant.rs` — HA REST API client (turn_on/turn_off).
+- `src/host_keys.rs` — `init-host-keys` command (auto-write SSH host_keys).
+- `src/exec.rs` — `exec` subcommand: run commands on cluster/nodes, resolve aliases.
+- `src/doctor.rs` — Doctor mode: pre-flight checks and `--repair` (Dockerfile generation).
+- `src/pre_serve.rs` — BeforeServe/PreServeCommand types.
+- `src/before_serve.rs` — Pre-serve hook execution logic.
+- `locales/{en,ru,es,it,fr,de}/main.ftl` — Fluent i18n translation files.
+- `config.toml` — runtime config: SSH key, cluster definitions, per-cluster NCCL settings, HA, exec aliases.
 - `recipes/<name>/recipe.toml` — recipe files.
 
 ## Commands
@@ -25,14 +39,24 @@ cargo run health                                           # Health check all cl
 cargo run health --cluster 2nodes-dac                      # Single cluster
 cargo run health --fingerprint                             # Show host keys for config.toml
 cargo run init-host-keys                                   # Auto-write SSH host_keys
-cargo run exec --cluster 4nodes -- "sudo nvidia-smi -lgc 300,2300"   # Run on all nodes
-cargo run exec --cluster 4nodes --node dgx1 -- "uptime"            # Run on one node
-cargo run exec --recipe glm51 -- "cat /tmp/foo"                     # Use recipe's cluster
+cargo run exec --cluster 4nodes -- "..."                   # Run on all nodes
+cargo run exec --cluster 4nodes --node dgx1 -- "uptime"    # Run on one node
+cargo run exec --recipe glm51 -- "cat /tmp/foo"            # Use recipe's cluster
+cargo run exec --cluster 4nodes --alias gpu-lock           # Use exec alias
 cargo run logs --recipe glm51                              # Last 100 lines per node
 cargo run logs --recipe glm51 --follow                     # Follow head node logs
 cargo run doctor --recipe glm51                            # Check node prerequisites
 cargo run doctor --recipe glm51 --repair                   # Auto-fix (build repaired image)
 
+### Power management
+```
+cargo run power off --cluster 4nodes-switch                # SSH shutdown + HA turn_off
+cargo run power off --cluster 4 --node dgx1                # Prefix matching + single node
+cargo run power on --cluster 4nodes-switch                 # HA turn_on (wake nodes)
+cargo run power switch --from 4 --to 2                     # Switch clusters (disable → enable)
+cargo run -- -d power off --cluster 4nodes-switch          # Dry-run
+```
+
 ### SGLang recipe example
 ```
 cargo run serve --recipe minimax-m3                        # SGLang, 4 nodes, TP=4
@@ -66,9 +90,10 @@ name = "my-cluster"
 master_ib_ip = "1.2.3.4"
 nodes = [
     # host_key = "SHA256:..."  # run 'cargo run health --fingerprint' to discover
-    { name = "dgx1.lan", ssh_ip = "10.0.0.1", ib_ip = "1.2.3.4", rank = 0 },
-    { name = "dgx2.lan", ssh_ip = "10.0.0.2", ib_ip = "1.2.3.5", rank = 1 },
+    { name = "dgx1.lan", ssh_ip = "10.0.0.1", ib_ip = "1.2.3.4", rank = 0, entity = "switch.dgx1" },
+    { name = "dgx2.lan", ssh_ip = "10.0.0.2", ib_ip = "1.2.3.5", rank = 1, entity = "switch.dgx2" },
 ]
+extra_entities = ["switch.network_switch"]    # cluster-level switches (PDU, network)
 
 [nccl."my-cluster"]
 ib_hca = "rocep1s0f0,roceP2p1s0f0"
@@ -77,6 +102,27 @@ ib_gid_index = 3
 ib_disable = 0
 ```
 
+### Home Assistant config
+
+Optional `[home_assistant]` section for power management:
+
+```toml
+[home_assistant]
+url = "http://homeassistant.local:8123"
+token = "long-lived-access-token"
+shutdown_delay_seconds = 30
+```
+
+### Exec aliases
+
+```toml
+[exec_aliases]
+gpu-lock = "sudo nvidia-smi -lgc 300,2300"
+shutdown = "sudo shutdown now"
+```
+
+Use: `cargo run exec --cluster 4 --alias gpu-lock`
+
 ### Recipe-level cluster override
 
 A recipe can pin itself to a specific cluster (bypasses `--cluster` / auto-pick):
@@ -148,21 +194,24 @@ engine = "vllm"                        # or "sglang" (default: "vllm")
 name = "Human Readable Name"
 docker_image = "my-image:tag"
 model = "org/model-name"
-port = 8000
-required_nodes = 4                     # optional: 0 = any cluster size, omitted = no check
-pipeline_parallel_size = 0             # 0 = auto (equals nnodes), only for vLLM
-tensor_parallel_size = 1               # for vLLM: per-node TP (= 1 for single-GPU nodes);
-                                       # for SGLang: total TP across all GPUs (= nnodes * GPUs_per_node)
-distributed_backend = "mp"             # "mp" (multiprocessing) or "ray"
-cluster = "4nodes-switch"              # optional: pin to a specific cluster
+port = 30000                              # default: 30000
+required_nodes = 4                        # optional, omitted = no check
+pipeline_parallel_size = 0                # 0 = auto (equals nnodes), vLLM only
+tensor_parallel_size = 1                  # per-node TP for vLLM, total TP for SGLang
+distributed_backend = "mp"                # "mp" (multiprocessing) or "ray"
+shm_size = "64gb"                         # optional, only added when non-empty
+cluster = "4nodes-switch"                 # optional: pin to a specific cluster
 
 args = [
     "--kv-cache-dtype", "fp8",
     "--max-model-len", "32768",
 ]
 
-# Optional: override docker entrypoint (appended after image name)
-entrypoint = ["python", "-m", "vllm.entrypoints.openai.api_server"]
+entrypoint = ["vllm", "serve"]             # optional, appended after image name
+
+before_serve = [                           # recipe-level pre-serve hooks (after cluster)
+    { command = "echo recipe", on_failure = "abort" },
+]
 
 [env]
 SOME_VAR = "value"
@@ -192,30 +241,35 @@ Both engines use `--nnodes`, `--node-rank`, and `--master-addr`.
 
 ### vLLM engine (default)
 
-Appends these flags (cannot be overridden by recipe):
+Appends these flags:
 
 ```
---tensor-parallel-size 1
+--tensor-parallel-size <recipe.tensor_parallel_size>
 --pipeline-parallel-size <computed>
 --nnodes <total>
 --node-rank <rank>
 --master-addr <cluster.master_ib_ip>
---distributed-executor-backend mp|ray
+--master-port <config.init_port>          # default 25000
+--distributed-executor-backend ray         # only for ray backend (mp is auto-detected)
+--host 0.0.0.0
+--port <recipe.port>
 ```
 
+Non-head nodes also receive `--headless`.
+
 ### SGLang engine
 
 Appends:
 
 ```
---tp <tensor_parallel_size>   # from recipe (e.g. 4 for 4 × single-GPU nodes)
+--tp <tensor_parallel_size>
 --nnodes <total>
 --node-rank <rank>
---master-addr <cluster.master_ib_ip>
+--dist-init-addr <master_ib_ip>:<init_port>
+--host 0.0.0.0
+--port <recipe.port>
 ```
 
-The host IP env var is `SGLANG_WORKER_IP` and the model is passed as `--model-path <model>`.
-
 ### Ray backend
 
 For vLLM Ray backend, additionally appends:
@@ -223,19 +277,38 @@ For vLLM Ray backend, additionally appends:
 ```
 -e RAY_ADDRESS=<master_ib_ip>:6379
 -e RAY_PORT=<port>
+-e RAY_NODE_IP_ADDRESS=<node.ib_ip>
+-e RAY_OVERRIDE_NODE_IP_ADDRESS=<node.ib_ip>
 ```
 
-(For SGLang with Ray backend, the container's entrypoint must handle Ray setup; SGLang's native multi-node uses `mp` backend with NCCL.)
+### Env vars passed to every container
+
+**Cluster-driven** (from `[nccl."<name>"]`):
+`NCCL_IB_DISABLE`, `NCCL_IB_HCA`, `NCCL_SOCKET_IFNAME`, `NCCL_IB_GID_INDEX`, `GLOO_SOCKET_IFNAME`, `TP_SOCKET_IFNAME`
+
+**Network interface** (from `nccl.socket_ifname`):
+`MN_IF_NAME`, `UCX_NET_DEVICES`, `OMPI_MCA_btl_tcp_if_include`
+
+**Defaults**:
+`OMP_NUM_THREADS=4`, `NCCL_IGNORE_CPU_AFFINITY=1`, `NCCL_NET=IB`, `NCCL_DEBUG=WARN`
+
+**Engine IP** (from `node.ib_ip`):
+`VLLM_HOST_IP` (vLLM) or `SGLANG_WORKER_IP` (SGLang)
+
+**Head node only**:
+`-e VLLM_LOGGING_COLOR=1 -e TERM=xterm-256color` (colored logs)
+
+Recipe `[env]` entries are appended last and override any default.
 
 ### Common to all runs
 
-NCCL env vars come from the cluster's `[nccl."<name>"]` section in `config.toml`. Port mapping (`-p`) is applied on **head node only**.
+NCCL env vars come from the cluster's `[nccl."<name>"]` section. Port mapping `-p <port>:<port>` on the head node only. `HF_HOME=/root/.cache/huggingface` with volume mount.
 
-Containers are labelled with:
+Containers are labelled:
 - `sm121.recipe=<name>`
 - `sm121.rank=<rank>`
 
-`--cluster auto` picks the first cluster in `config.toml` that has an NCCL section.
+`--cluster auto` picks the **smallest** cluster (fewest nodes) that matches the recipe's `required_nodes` and has an NCCL config. Prefix matching works: `--cluster 4` matches `4nodes-switch`.
 
 ## How tmux launch works
 
@@ -264,11 +337,15 @@ With `--repair`, generates a Dockerfile that installs missing packages and rebui
 ## Dependencies
 
 - `clap` 4.6 + `derive` — derive-based CLI
-- `russh` 0.60 + `russh-keys` 0.49 — SSH client
+- `russh` 0.61 + `russh-keys` 0.50-beta — SSH client
 - `tokio` full — async runtime
 - `toml` 1 + `serde` 1 — config/recipe deserialization
 - `tracing` + `tracing-subscriber` — logging
 - `anyhow` — error handling
 - `futures` 0.3 — `join_all` for concurrent SSH
-- `sha2` 0.10 — SHA-256 for host key fingerprints
+- `sha2` 0.11 — SHA-256 for host key fingerprints
 - `base64` 0.22 — Base64 encoding for fingerprints
+- `dirs` 6 — OS config dir for default config fallback
+- `reqwest` 0.13 — HTTP client for Home Assistant REST API
+- `serde_json` 1 — JSON for HA requests
+- `fluent-i18n` — i18n via Mozilla Fluent (locales in `locales/`)

+ 82 - 6
README.md

@@ -62,6 +62,42 @@ cargo run tmux attach --recipe glm51
 
 ## Available Recipes
 
+### [`deepseek-v4-flash`](recipes/deepseek-v4-flash/recipe.toml) — vLLM (DeepSeek V4 Flash MXFP8)
+
+```toml
+engine = "vllm"
+docker_image = "aidendle94/sparkrun-vllm-ds4-gb10:production-v2"
+model = "deepseek-ai/DeepSeek-V4-Flash"
+required_nodes = 2
+tensor_parallel_size = 2
+pipeline_parallel_size = 1
+distributed_backend = "mp"
+```
+
+DeepSeek V4 Flash with 1M context, FP8 KV cache, MTP speculative decoding, and DeepSeek V4 reasoning/tool parsers. 2 nodes, TP=2 across nodes. Uses `sparkrun` DGX Spark image.
+
+```bash
+cargo run serve --recipe deepseek-v4-flash          # auto-selects 2nodes-dac
+cargo run logs --recipe deepseek-v4-flash --follow
+```
+
+### [`minimax-m3-mxfp8`](recipes/minimax-m3-mxfp8/recipe.toml) — vLLM (MiniMax M3 MXFP8)
+
+```toml
+engine = "vllm"
+docker_image = "aidendle94/sparkrun-vllm-ds4-gb10:production-v2"
+model = "MiniMaxAI/MiniMax-M3-MXFP8"
+required_nodes = 4
+tensor_parallel_size = 4
+distributed_backend = "mp"
+```
+
+MiniMax M3 vision-language MoE (428B params, 23B active) with MXFP8 quantization, MSA sparse attention (`--block-size 128` mandatory), expert parallelism, and `minimax_m3` parsers. Text-only mode skips the vision encoder. 4 nodes, TP=4.
+
+```bash
+cargo run serve --recipe minimax-m3-mxfp8
+```
+
 ### [`glm51`](recipes/glm51/recipe.toml) — vLLM (GLM-5.1 NVFP8)
 
 ```toml
@@ -73,7 +109,7 @@ required_nodes = 4
 distributed_backend = "mp"
 ```
 
-Launches GLM-5.1 with vLLM across 4 nodes. Uses pipeline parallelism (PP=4, TP=1 per node) with the `mp` distributed backend. Extra flags enable MTP speculative decoding, TRITON_MLA attention, prefix caching, and chunked prefill.
+GLM-5.1 with vLLM across 4 nodes. Pipeline parallelism (PP=4, TP=1). MTP speculative decoding, TRITON_MLA attention, prefix caching, chunked prefill.
 
 ```bash
 cargo run serve --recipe glm51
@@ -93,7 +129,7 @@ distributed_backend = "mp"
 entrypoint = ["sglang", "serve"]
 ```
 
-Launches Minimax-M3 with SGLang across 4 nodes. Uses tensor parallelism across all GPUs (TP=4, 1 GPU per node). NVFP4 quantization with the `cutlass` fp4 gemm backend and `triton` MoE runner. Uses `instanttensor` load format for fast model loading.
+Minimax-M3 with SGLang, 4 nodes, TP=4. NVFP4 quantization, `cutlass` fp4 gemm, `triton` MoE runner, `instanttensor` load format.
 
 ```bash
 cargo run serve --recipe minimax-m3
@@ -234,15 +270,17 @@ The launcher passes the following env vars to every container.  Recipe-level
 | `NCCL_IB_GID_INDEX` | `nccl.<cluster>.ib_gid_index` | RoCE GID index for InfiniBand routing |
 | `GLOO_SOCKET_IFNAME` | `nccl.<cluster>.socket_ifname` | Network interface for Gloo (vLLM distributed + PyTorch CPU collectives) |
 | `TP_SOCKET_IFNAME` | `nccl.<cluster>.socket_ifname` | vLLM tensor-parallel coordinator interface |
+| `MN_IF_NAME` | `nccl.<cluster>.socket_ifname` | Multi-node interface name |
+| `UCX_NET_DEVICES` | `nccl.<cluster>.socket_ifname` | UCX communication devices |
+| `OMPI_MCA_btl_tcp_if_include` | `nccl.<cluster>.socket_ifname` | OpenMPI TCP interface |
+| `VLLM_LOGGING_COLOR` | `1` | Force colored vLLM logs (head node only) |
 
 ### Hard-coded defaults
 
 | Env var | Value | Purpose |
 |---|---|---|
-| `NCCL_CUMEM_ENABLE` | `0` | Disable CUDA-managed memory in NCCL (use system allocator) |
 | `NCCL_IGNORE_CPU_AFFINITY` | `1` | Don't pin NCCL threads to a specific CPU set (needed on DGX Spark) |
 | `NCCL_NET` | `IB` | Force NCCL to use the InfiniBand plugin (`IB` = `libnccl-net.so`) |
-| `NCCL_CROSS_NIC` | `1` | Allow traffic across multiple RDMA NICs (multi-rail) |
 | `NCCL_DEBUG` | `WARN` | NCCL log level (override with `[env]` for `INFO`/`TRACE` while debugging) |
 | `OMP_NUM_THREADS` | `4` | OpenMP thread limit per process |
 | `HF_HOME` | `/root/.cache/huggingface` | HuggingFace cache root (volume-mounted from `ssh_user`'s host cache) |
@@ -337,6 +375,22 @@ sm121-launcher power on --cluster 4nodes-switch
 sm121-launcher power on --cluster 4nodes-switch --node dgx1
 ```
 
+### `power switch` flow
+
+Switch between clusters: disable nodes/entities unique to `from`,
+enable everything in `to`.
+
+```
+1. SSH sudo shutdown now on nodes in from but not to
+2. Wait shutdown_delay_seconds
+3. HA turn_off those nodes + extra_entities unique to from
+4. HA turn_on all nodes + extra_entities in to
+```
+
+```bash
+sm121-launcher power switch --from 4 --to 2   # prefix matching works
+```
+
 ## Doctor Mode
 
 ```bash
@@ -353,6 +407,25 @@ Checks each node for:
 
 With `--repair`, generates a Dockerfile that installs missing packages and rebuilds the image with a `.repaired` tag. The repaired image is auto-detected on subsequent `serve` runs.
 
+## Internationalization (i18n)
+
+Log messages are localized via Mozilla Fluent (`.ftl` files in `locales/`).
+Auto-detects system locale. Available languages:
+
+| Locale | File | Status |
+|--------|------|--------|
+| English | `locales/en/main.ftl` | Fallback |
+| Russian | `locales/ru/main.ftl` | Full |
+| Spanish | `locales/es/main.ftl` | Full |
+| Italian | `locales/it/main.ftl` | Full |
+| French | `locales/fr/main.ftl` | Full |
+| German | `locales/de/main.ftl` | Full |
+
+```bash
+# Override locale via environment
+LANG=ru_RU.UTF-8 cargo run serve --recipe deepseek-v4-flash
+```
+
 ## CLI Reference
 
 ```text
@@ -380,10 +453,13 @@ Global flags:
 | Crate | Purpose |
 |---|---|
 | `clap` 4.6 + derive | CLI argument parsing |
-| `russh` 0.60 | SSH client |
+| `russh` 0.61 + `russh-keys` 0.50-beta | SSH client |
 | `tokio` full | Async runtime |
 | `toml` 1 + `serde` 1 | Config / recipe deserialization |
 | `tracing` + `tracing-subscriber` | Structured logging |
 | `anyhow` | Error handling |
 | `futures` 0.3 | Concurrent SSH (`join_all`) |
-| `sha2` 0.10 + `base64` 0.22 | Host key fingerprinting |
+| `sha2` 0.11 + `base64` 0.22 | Host key fingerprinting |
+| `reqwest` 0.13 | HTTP client for Home Assistant |
+| `dirs` 6 | OS config dir discovery |
+| `fluent-i18n` | Mozilla Fluent i18n (locales: en, ru, es, it, fr, de) |