Repository navigation
Quick Start
From a fresh Debian 12 machine to a serving endpoint. Read the three mandatory parameters first — the launcher refuses to start without them, and that is by design.
Target OS: Debian 12 (bookworm). Commands also work on Ubuntu 22.04/24.04 with
apt.
| Parameter | Why | How to get it |
|---|---|---|
IMAGE |
Must be pinned by sha256 digest — floating tags are refused | step 3 |
MODEL_REVISION |
Required when the model is fetched by id (not from a local dir) | the model's commit SHA |
API_KEY |
Required even on 127.0.0.1 — only ALLOW_NO_AUTH=1 skips it, and only on loopback |
openssl rand -hex 24 |
- 4× NVIDIA RTX PRO 6000 (96 GB GDDR7),
nvidia-smishows all four - ≥ 1 TB RAM (cold expert banks live in host memory), ≥ 2 TB NVMe for model + container
- Debian 12, root or sudo
# Docker (Debian 12)
sudo apt-get update && sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io
# NVIDIA Container Toolkit
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -fsSL https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list > /dev/null
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker && sudo systemctl restart docker
# verify: must print GPU information inside a container
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smigit clone https://github.com/sikkatu/recipes.git ~/recipes
cd ~/recipes/rtx-pro-6000/mimo-v2.6-pro-hotsplitAll further commands run from this directory — no repeated cd guessing.
The launcher refuses floating tags. Resolve the digest once and use it everywhere:
export IMAGE="vllm/vllm-openai:<tag>" # use the tag your recipe README pins
# NOTE: the image is UNVERIFIED on target hardware -- hotsplit replaces model files
# inside vLLM, so the exact vLLM build matters. Treat the first boot as an experiment.
docker pull "$IMAGE"
export IMAGE="$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE")"
echo "$IMAGE" # must end in @sha256:<digest>Run these commands on the Linux host (the machine with the four GPUs), NOT inside a docker container. The model directory is a host path that the launcher later mounts read-only.
Two terms, two different things:
| Name | What it is | Example |
|---|---|---|
MODEL_ID |
the model's address on Hugging Face | XiaomiMiMo/MiMo-V2.6-Pro-MOPD |
MODEL_DIR |
a local directory on this host holding the downloaded files | /models/MiMo-V2.6-Pro-MOPD |
MODEL_REVISION |
the exact commit SHA of the model version — used for both download and launch |
f3a9... (40 hex chars) |
MODEL_REVISION identifies the version. Getting the SHA does not download the model.
Note: with MODEL_DIR the launcher mounts the directory and does not verify its contents
against a SHA -- MODEL_REVISION then only records which version you downloaded from. For
reproducible local runs, keep the revision string next to the files and verify integrity separately.
With MODEL_ID the revision is enforced by the launcher at fetch time. the
the download step below. Use the same SHA when you launch, so the server runs exactly the
files you verified.
# 1. install the Hugging Face CLI in its own venv (not the system Python)
python3 -m venv "$HOME/.venvs/hf-download"
source "$HOME/.venvs/hf-download/bin/activate"
python -m pip install --upgrade huggingface_hub
# 2. model id and target directory
export MODEL_ID="XiaomiMiMo/MiMo-V2.6-Pro-MOPD"
export MODEL_DIR="/models/MiMo-V2.6-Pro-MOPD"
# 3. create the directory and let the current user write to it
sudo mkdir -p "$MODEL_DIR"
sudo chown "$USER:$(id -gn)" "$MODEL_DIR"
# 4. resolve the exact commit SHA of the current model version
export MODEL_REVISION="$(
curl -fsSL "https://huggingface.co/api/models/$MODEL_ID" |
python3 -c 'import json,sys; print(json.load(sys.stdin)["sha"])'
)"
if ! [[ "$MODEL_REVISION" =~ ^[0-9a-f]{40}$ ]]; then
echo "ERROR: could not obtain a valid model revision SHA" >&2
exit 1
fi
echo "Model: $MODEL_ID"
echo "Revision: $MODEL_REVISION"
echo "Directory: $MODEL_DIR"
# 5. check disk space BEFORE downloading (these models are hundreds of GB)
df -h "$(dirname "$MODEL_DIR")"
# 6. download the entire model repository at that exact revision
hf download "$MODEL_ID" \
--revision "$MODEL_REVISION" \
--local-dir "$MODEL_DIR"
# 7. verify the download (config, weight index, all shards, tokenizer, revision)
test -s "$MODEL_DIR/config.json" || { echo "ERROR: config.json missing" >&2; exit 1; }
find "$MODEL_DIR" -maxdepth 1 -type f -name '*.safetensors' -print -quit | grep -q . \
|| { echo "ERROR: no safetensors files found" >&2; exit 1; }
# every shard listed in the weight index must exist and be non-empty
if ls "$MODEL_DIR"/*.index.json >/dev/null 2>&1; then
python3 -c '
import json, sys, pathlib
idx = next(pathlib.Path(sys.argv[1]).glob("*.index.json"))
shards = sorted(set(json.load(open(idx))["weight_map"].values()))
missing = [s for s in shards if not (pathlib.Path(sys.argv[1]) / s).is_file()
or (pathlib.Path(sys.argv[1]) / s).stat().st_size == 0]
if missing:
print("ERROR: missing or empty shards:", missing, file=sys.stderr); sys.exit(1)
print(f"shards OK: {len(shards)} files listed in {idx.name}")
' "$MODEL_DIR" || exit 1
fi
# tokenizer files (at least one of the standard names)
for f in tokenizer.json tokenizer_config.json; do
test -s "$MODEL_DIR/$f" || { echo "ERROR: tokenizer file $f missing" >&2; exit 1; }
done
# record the revision next to the files -- MODEL_DIR launches do NOT verify it at load time
echo "$MODEL_REVISION" > "$MODEL_DIR/REVISION.txt"
echo "Download completed:"
ls -lah "$MODEL_DIR"hf download with no file list downloads the whole repo (weights, config.json, tokenizer,
index) — better than picking files by hand and forgetting one. If the model needs authentication,
run hf auth login first. Public models need no login.
These checks verify config.json, every shard listed in the weight index (existence and
non-zero size), and the tokenizer files. They do not hash the bytes: for byte-level
integrity run sha256sum against the hashes published by the model author, if any.
With MODEL_DIR the launcher does not compare the directory against MODEL_REVISION at
load time -- the saved REVISION.txt is a record you keep yourself; verify it when you
need reproducibility.
The launcher can also fetch by MODEL_ID into the default HF cache (~/.cache/huggingface),
but then MODEL_REVISION (a 40-hex SHA) is mandatory — the launcher refuses a branch name.
Get the SHA the same way (step 4 above), export MODEL_ID and MODEL_REVISION instead of
MODEL_DIR, and start the server. The cached files live under
~/.cache/huggingface/hub/models--<org>--<name>/snapshots/<SHA>/ — that snapshot directory is
what the server loads. Note the cache is not wiped by rollback (see manage.sh clean-cache).
export API_KEY="$(openssl rand -hex 24)"For loopback-only debug you may instead run with ALLOW_NO_AUTH=1 BIND=127.0.0.1 — but a key is
required for every other bind.
Start with the unchanged baseline and a small context:
export IMAGE="vllm/vllm-openai:<tag>@sha256:<64-hex-digest>" # from step 3
export API_KEY="$(openssl rand -hex 24)" # from step 5
export MAX_MODEL_LEN=16384 CPU_OFFLOAD_GB=96
bash scripts/run-tp4-uva.sh
bash scripts/check-tp4-api.sh # waits for /v1/models, sends one chat request (uses $API_KEY)Only when the baseline is healthy, try hotsplit (it is not verified on a 4-GPU target host yet):
# stop the UVA baseline first -- both use --network host and the same port
docker stop mimo-pro-tp4-uva && docker rm mimo-pro-tp4-uva
export HOTSPLIT_DYNAMIC=0 # static first
bash scripts/run-tp4-hotsplit.sh
bash scripts/check-hotsplit-tp4.sh mimo-pro-tp4-hotsplit staticThen the dynamic experiment (separate run):
docker stop mimo-pro-tp4-hotsplit && docker rm mimo-pro-tp4-hotsplit
HOTSPLIT_DYNAMIC=1 bash scripts/run-tp4-hotsplit.sh
bash scripts/check-hotsplit-tp4.sh mimo-pro-tp4-hotsplit dynamic expect-progress-
check-hotsplit-tp4.sh <container> static— expectsdynamic_hook=INACTIVEon all ranks; a missinghotsplit doneper rank is a FAIL. -
check-hotsplit-tp4.sh <container> dynamic— expectsdynamic_hook=ACTIVEon every rank. After real traffic addexpect-progress(3rd arg orEXPECT_PROGRESS=1): zero dynamic progress then becomes a FAIL with non-zero exit, not a silent WARN. After real traffic thehotsplit dynamic updatecount must be non-zero; before generation a zero count is normal — it proves nothing either way.
After the first successful launch you do not need to remember docker flags and env variables:
./manage.sh status # containers, GPUs, API reachability — one screen
./manage.sh logs # follow the server log
./manage.sh stop # stop and remove the server container
./manage.sh start # start again (HOTSPLIT_DYNAMIC from env or tp4.env)
./manage.sh restart # stop + start, picks up a changed tp4.env
./manage.sh update # git pull + restart
./manage.sh rollback # container-level rollback (removes the container only)
./manage.sh clean-cache # show the HF cache size, delete only after explicit confirmation
./manage.sh check # hotsplit verification, mode follows HOTSPLIT_DYNAMICmanage.sh is a thin wrapper over the recipe launchers — it sources ./tp4.env if you created one
(copy tp4.env.example), and environment values you already exported win over the file.
-
Hot budget (
HOTSPLIT_HOT_GIB) is per rank. Empty = "use the GPU-resident expert bytes the stock placement used", not "fit the whole bank" — a whole bank does not fit on one card. See Models & Geometry for the arithmetic. -
HOTSPLIT_VRAM_RESERVE_GIB=2is a floor, not a promise. Long contexts and concurrency need more — measure with the recipe'sRUNNING.mdprocedures. - The model patch (
PATCH_MODEL_FILES=1) is verified againstpatches/MANIFEST.sha256before launch — do not edit patch files in place.
Full measurement procedures: the recipe's RUNNING.md and the FAQ.
English
Русский
中文