Nav apraksta

Alexander Korolev 8e20fd1dad feat: versioned registry tags (build checksum) + latest; publish --push; duplicate guard via publication ledger with --force 1 mēnesi atpakaļ
.agents 0ebe775ceb skill: web-topology cable mode (S-curve + bus/bridges) 1 mēnesi atpakaļ
dcp2 0d8166de95 port DCP2 onto our tree (22-file superset wrapper + a2a recipe); A/B: 162/176, 5.34M-token KV @512K, -5 vs b12x — the high-KV lane 1 mēnesi atpakaļ
docker-images 3717c3a4de feat: deps.lock — fetch-deps.sh verifies staged deps against a committed fingerprint lock (regen via --regen-lock / REGEN_LOCK=1) 1 mēnesi atpakaļ
history 498137d8b2 history: 09-18 end-state note (power off policy) 1 mēnesi atpakaļ
host-scripts 65198e60fc feat(recipe): add built-in script aliases and after_stop hook 2 mēneši atpakaļ
locales 8e20fd1dad feat: versioned registry tags (build checksum) + latest; publish --push; duplicate guard via publication ledger with --force 1 mēnesi atpakaļ
patches 960dc62483 glm53-full-mxfp4-4x-dspark8: full GLM-5.3 + DSpark K8 drafter recipe (131K DCP1, 9 GiB KV pin, skip-layers for draft KV, in-container patch for shared-page inheritance); boot 6 up, quality gate clean 1 mēnesi atpakaļ
recipes e3d6c0401b feat: graceful MikroTik/RouterOS power-off via ROS API ([mikrotik] config) 1 mēnesi atpakaļ
runs bc5aa791bf 8nodes-switch cluster in config (dual-rail 200G, ranks 0-7), dgx7/8 nas mount fixed, night plan 09-18: 8x ready, DS4.1-8x in-memory engram 1 mēnesi atpakaļ
scripts 84f4cb456a feat(workflow): add land-branch.sh for solo-git integrate gate 2 mēneši atpakaļ
specs 79727cca30 spark lanes: r35 + mxfp8 kernel fallback; r38 pool-deadlock bug report 1 mēnesi atpakaļ
src 8e20fd1dad feat: versioned registry tags (build checksum) + latest; publish --push; duplicate guard via publication ledger with --force 1 mēnesi atpakaļ
.gitignore 1d73fd0945 serve/stop reliability + run ledger + archive curation 1 mēnesi atpakaļ
AGENTS.md 8e20fd1dad feat: versioned registry tags (build checksum) + latest; publish --push; duplicate guard via publication ledger with --force 1 mēnesi atpakaļ
CLAUDE.md 5ab8a05080 restore recipes/ in-tree — recipe + docker-images must share one revision 1 mēnesi atpakaļ
CODE_REVIEW.md 1ba1d99e05 fix: resolve review findings across modules 2 mēneši atpakaļ
CONVENTIONS.md 3379f83f32 chore(bigpowers): adopt bigpowers lifecycle and conventions 2 mēneši atpakaļ
Cargo.lock e3d6c0401b feat: graceful MikroTik/RouterOS power-off via ROS API ([mikrotik] config) 1 mēnesi atpakaļ
Cargo.toml e3d6c0401b feat: graceful MikroTik/RouterOS power-off via ROS API ([mikrotik] config) 1 mēnesi atpakaļ
DEVELOPMENT.md f4abb53cbf recipe [env] refs: values can resolve from recipe fields ({ from = "port" }) 1 mēnesi atpakaļ
Makefile 65cbf60fce build: enforce quality gates via scripts/check.sh 4 mēneši atpakaļ
README.md 5ab8a05080 restore recipes/ in-tree — recipe + docker-images must share one revision 1 mēnesi atpakaļ

README.md

sm121-launcher

Launch vLLM / SGLang inference clusters across DGX Spark machines via SSH.

Quick Install

# From source
cargo install --path .

# Or download a pre-built binary (requires Rust toolchain to build)
cargo build --release
# Binary at target/release/sm121-launcher

Quick Start

# Create config.toml (see Configuration below)
# Launch a recipe
sm121-launcher serve --recipe deepseek-v4-flash

# Follow logs
sm121-launcher logs --recipe deepseek-v4-flash --follow

# Stop containers
sm121-launcher stop --recipe deepseek-v4-flash

Commands

Command Description
serve --recipe <name> Launch recipe on cluster
logs --recipe <name> [--follow] Show container logs
stop --recipe <name> Stop and remove containers
health [--cluster <name>] Check cluster health
doctor --recipe <name> [--repair] Check/repair node prerequisites
exec --cluster <name> -- <command> Run command on cluster nodes
exec --cluster <name> --alias <name> Run predefined alias
power off --cluster <name> [--node <name>] Shutdown + HA turn_off
power on --cluster <name> [--node <name>] HA turn_on (wake nodes)
power switch --from <src> --to <tgt> Switch clusters
tmux serve --recipe <name> Launch via tmux (no Docker)
tmux status --recipe <name> Check tmux session health
tmux logs --recipe <name> Capture tmux pane output
init-host-keys Auto-write SSH host keys
-d / --dry-run Preview commands without executing

Cluster selection

sm121-launcher serve --recipe deepseek-v4-flash              # auto (smallest match)
sm121-launcher serve --recipe glm51 --cluster 4nodes-switch  # exact name
sm121-launcher serve --recipe glm51 --cluster 4             # prefix match
sm121-launcher serve --recipe glm51 --cluster 2             # picks 2nodes-dac

Prefix must be unambiguous — error if multiple clusters match.

Recipes

Recipes are TOML files in recipes/<name>/recipe.toml, resolved relative to the current directory (then ~/.config/sm121-launcher/recipes/). This repo is the source of truth: a recipe and its image build context (docker-images/<dir>/ — Dockerfile, patches, version pins) are versioned together, since the served image must match the recipe's revision exactly. sm121 publish flattens both into a self-contained bundle; the registry data repo https://git.kilork.org/sm121/recipes stores those published bundles (discovery/sharing artifact, never the source).

Creating a recipe

engine = "vllm"                        # or "sglang" (default: "vllm")
name = "My Model"
docker_image = "my-image:tag"
model = "org/model-name"
required_nodes = 2                     # optional
tensor_parallel_size = 2               # TP across nodes
pipeline_parallel_size = 1             # vLLM only, 0 = auto (equals nnodes)
distributed_backend = "mp"             # "mp" (default) or "ray"
shm_size = "64gb"                      # optional, only added when set

entrypoint = ["vllm", "serve"]         # optional, appended after image

args = [
    "--kv-cache-dtype", "fp8",
    "--max-model-len", "32768",
]

before_serve = [                       # recipe-level pre-serve hooks
    { command = "echo ready", on_failure = "abort" },
]

[env]
VLLM_LOGGING_COLOR = "1"              # override any launcher default

Configuration

The launcher reads config.toml from the current directory, falling back to ~/.config/sm121-launcher/config.toml.

Minimal

ssh_user = "kilork"
ssh_key_path = "/path/to/ssh/key"

[[clusters]]
name = "4nodes-switch"
master_ib_ip = "192.168.200.15"
nodes = [
    { name = "dgx1", ssh_ip = "10.0.0.1", ib_ip = "192.168.200.15", rank = 0 },
    { name = "dgx2", ssh_ip = "10.0.0.2", ib_ip = "192.168.200.13", rank = 1 },
    { name = "dgx3", ssh_ip = "10.0.0.3", ib_ip = "192.168.200.17", rank = 2 },
    { name = "dgx4", ssh_ip = "10.0.0.4", ib_ip = "192.168.200.19", rank = 3 },
]

With Home Assistant (power management)

[[clusters]]
name = "4nodes-switch"
master_ib_ip = "192.168.200.15"
extra_entities = ["switch.network_switch"]   # cluster-level switches
nodes = [
    { name = "dgx1", ssh_ip = "10.0.0.1", ib_ip = "192.168.200.15", rank = 0, entity = "switch.dgx1" },
    { name = "dgx2", ssh_ip = "10.0.0.2", ib_ip = "192.168.200.13", rank = 1, entity = "switch.dgx2" },
    { name = "dgx3", ssh_ip = "10.0.0.2", ib_ip = "192.168.200.17", rank = 2, entity = "switch.dgx3" },
    { name = "dgx4", ssh_ip = "10.0.0.2", ib_ip = "192.168.200.19", rank = 3, entity = "switch.dgx4" },
]

[nccl."4nodes-switch"]
ib_hca = "rocep1s0f0,roceP2p1s0f0"
socket_ifname = "enp1s0f0np0"
ib_gid_index = 3
ib_disable = 0

[home_assistant]
url = "http://homeassistant.local:8123"
token = "long-lived-access-token"
shutdown_delay_seconds = 30

[exec_aliases]
gpu-lock = "sudo nvidia-smi -lgc 300,2300"
shutdown = "sudo shutdown now"

[before_serve."4nodes-switch"]
commands = [
    "sudo nvidia-smi -lgc 300,2300",
    { command = "sudo nvidia-smi -pm 1", on_failure = "warn" },
]

NCCL config

Required for each cluster. Controls InfiniBand/RoCE communication:

Field Example Purpose
ib_hca rocep1s0f0,roceP2p1s0f0 RDMA devices
socket_ifname enp1s0f0np0 Network interface
ib_gid_index 3 RoCE GID index
ib_disable 0 Keep IB enabled

Power Management

Requires [home_assistant] in config.toml.

# Graceful shutdown + wait + turn off smart plugs
sm121-launcher power off --cluster 4nodes-switch

# Wake nodes via smart plugs
sm121-launcher power on --cluster 4nodes-switch

# Switch clusters (disable 4 → enable 2)
sm121-launcher power switch --from 4 --to 2

# Single node
sm121-launcher power off --cluster 4nodes-switch --node dgx1

# Dry-run
sm121-launcher -d power off --cluster 4nodes-switch

Power-off flow: SSH sudo shutdown now → wait shutdown_delay_seconds → HA turn_off entity → HA turn_off cluster extras.

Power-on flow: HA turn_on (no SSH, assumes smart plug wakes the machine).

Switch flow: disable nodes/extras only in source → enable all in target.

Environment Variables

The launcher sets these automatically. Recipe [env] overrides any.

Auto-set (always)

Env var Source Description
NCCL_IB_DISABLE nccl.<name>.ib_disable InfiniBand on/off
NCCL_IB_HCA nccl.<name>.ib_hca RDMA devices
NCCL_SOCKET_IFNAME nccl.<name>.socket_ifname NCCL control interface
GLOO_SOCKET_IFNAME same Gloo (PyTorch) interface
TP_SOCKET_IFNAME same vLLM TP coordinator
MN_IF_NAME same Multi-node interface
UCX_NET_DEVICES same UCX devices
OMPI_MCA_btl_tcp_if_include same OpenMPI TCP interface
NCCL_IB_GID_INDEX nccl.<name>.ib_gid_index RoCE GID
VLLM_HOST_IP / SGLANG_WORKER_IP node.ib_ip Engine host IP
HF_HOME /root/.cache/huggingface HF cache (volume-mounted)
OMP_NUM_THREADS 4 OpenMP threads
NCCL_IGNORE_CPU_AFFINITY 1 Don't pin NCCL threads
NCCL_NET IB NCCL IB plugin
NCCL_DEBUG WARN NCCL log level
VLLM_LOGGING_COLOR 1 Colored logs (head only)
TERM xterm-256color Terminal type (head only)

Ray backend (when distributed_backend = "ray")

RAY_ADDRESS, RAY_PORT, RAY_NODE_IP_ADDRESS, RAY_OVERRIDE_NODE_IP_ADDRESS

Internationalization

Messages are localized. Auto-detects system locale.

# Override locale (works on both macOS and Linux)
LANG=ru_RU.UTF-8 sm121-launcher serve --recipe deepseek-v4-flash

Available: English (fallback), Russian, Spanish, Italian, French, German.

Project Links