fleet: expose + register rig 4090 and laptop 5070 Ti Ollama endpoints #653

Closed
opened 2026-08-18 19:24:20 +00:00 by kert · 5 comments
Owner

Make the remote GPUs reachable, addressable, and health-checkable from the rack:

  • ollama serve on rig (4090) and laptop (5070 Ti), bound so the rack can reach them (LAN/tailnet address; document which). Neither host is currently resolvable from the rack (no tailscale, no rig hostname — verified 2026-08-18 during the palliative screening run, which is why it ran on the 3060 alone).
  • Register endpoints: LLM_OLLAMA_HOSTS in .env (comma-separated, beats [llm].ollama per llm/config.py) and/or a [llm].hosts list in stack.toml with per-host comments (rack/rig/laptop, VRAM).
  • A stack llm hosts (or similar) CLI that prints each host's reachability, loaded models, and VRAM — thin wrapper over HostPool.check().
  • Model distribution: ollama pull the working set on each host (currently qwen2.5:14b-ctx8k — note it is a local Modelfile variant with num_ctx 8192; the create step must be repeated per host — plus nomic-embed-text/bge-m3 for the embed pool).
  • Fix the stale hardware comment in llm/pool.py (says "laptop 5080"; the laptop is a 5070 Ti 16GB).

Laptop caveat to document: it sleeps/roams — the pool's check() already tolerates a missing host, but batch runners should re-check between batches, not only at startup.

Make the remote GPUs reachable, addressable, and health-checkable from the rack: - `ollama serve` on rig (4090) and laptop (5070 Ti), bound so the rack can reach them (LAN/tailnet address; document which). Neither host is currently resolvable from the rack (no tailscale, no `rig` hostname — verified 2026-08-18 during the palliative screening run, which is why it ran on the 3060 alone). - Register endpoints: `LLM_OLLAMA_HOSTS` in `.env` (comma-separated, beats `[llm].ollama` per `llm/config.py`) and/or a `[llm].hosts` list in stack.toml with per-host comments (rack/rig/laptop, VRAM). - A `stack llm hosts` (or similar) CLI that prints each host's reachability, loaded models, and VRAM — thin wrapper over `HostPool.check()`. - Model distribution: `ollama pull` the working set on each host (currently `qwen2.5:14b-ctx8k` — note it is a local Modelfile variant with `num_ctx 8192`; the create step must be repeated per host — plus `nomic-embed-text`/`bge-m3` for the embed pool). - Fix the stale hardware comment in `llm/pool.py` (says "laptop 5080"; the laptop is a **5070 Ti 16GB**). Laptop caveat to document: it sleeps/roams — the pool's `check()` already tolerates a missing host, but batch runners should re-check between batches, not only at startup.
kert added this to the P45: GPU Fleet — pooled inference across rack 3060 / rig 4090 / laptop 5070 Ti milestone 2026-08-18 19:24:20 +00:00
kert added the infrallm labels 2026-08-18 19:24:20 +00:00
Author
Owner

Design decision (user direction 2026-08-18): the laptop joins as a Docker Swarm worker running the ollama/ollama image, not a bare endpoint.

  • GPU in swarm mode: services cannot use --gpus; use default-runtime: nvidia in the laptop daemon.json (nvidia-container-toolkit) + NVIDIA_VISIBLE_DEVICES=all, pinned via node.labels.gpu==5070ti. (Alternative: node-generic-resources GPU advertisement + generic_resources reservation — more ceremony, unneeded for a one-GPU node.)
  • Routing mesh is the payoff: publish 11435:11434 and the rack reaches the laptop model at http://127.0.0.1:11435 regardless of the laptop's DHCP address — LLM_OLLAMA_HOSTS gets a stable local URL.
  • Rack = manager (docker swarm init is additive next to the compose stack; open 2377/tcp 7946/tcp+udp 4789/udp between the machines).
  • Caveats: laptop sleep → node Down → mesh 502 until wake + model re-load (fleet pool check() between batches already tolerates); Windows/Docker Desktop cannot do swarm+GPU (no default-runtime, no device requests in services) — if the laptop is not Linux, fall back to native Ollama.
  • Model volume ollama_laptop persists pulls across service restarts; pulls can be driven remotely via POST /api/pull through the mesh port.
Design decision (user direction 2026-08-18): the laptop joins as a **Docker Swarm worker** running the `ollama/ollama` image, not a bare endpoint. - GPU in swarm mode: services cannot use `--gpus`; use `default-runtime: nvidia` in the laptop daemon.json (nvidia-container-toolkit) + `NVIDIA_VISIBLE_DEVICES=all`, pinned via `node.labels.gpu==5070ti`. (Alternative: `node-generic-resources` GPU advertisement + `generic_resources` reservation — more ceremony, unneeded for a one-GPU node.) - **Routing mesh is the payoff**: publish `11435:11434` and the rack reaches the laptop model at `http://127.0.0.1:11435` regardless of the laptop's DHCP address — `LLM_OLLAMA_HOSTS` gets a stable local URL. - Rack = manager (`docker swarm init` is additive next to the compose stack; open 2377/tcp 7946/tcp+udp 4789/udp between the machines). - Caveats: laptop sleep → node Down → mesh 502 until wake + model re-load (fleet pool `check()` between batches already tolerates); **Windows/Docker Desktop cannot do swarm+GPU** (no default-runtime, no device requests in services) — if the laptop is not Linux, fall back to native Ollama. - Model volume `ollama_laptop` persists pulls across service restarts; pulls can be driven remotely via `POST /api/pull` through the mesh port.
Author
Owner

Swarm approach invalidated — the rack runs ROOTLESS Docker (rootlesskit + slirp4netns, unix:///run/user/1000/docker.sock). Swarm mode does not function rootless: swarm init appears to succeed (node ls shows Leader) but the 2377 control listener stays inside the rootless netns (only published container ports are forwarded; laptop join → connection refused) and the VXLAN overlay data plane cannot work there. Phantom swarm state removed (docker swarm leave --force).

Revised design of record: containerized-but-swarmless —

  • laptop runs the SAME ollama/ollama image with the same nvidia device-request GPU mechanism the rack compose service uses (proven under rootless on this OS): docker run --gpus 1 -p 11434:11434 -e OLLAMA_KEEP_ALIVE=5m -v ollama_models:/root/.ollama ollama/ollama (+ nvidia-ctk config --set nvidia-container-cli.no-cgroups=true for rootless);
  • stable addressing via mDNS instead of the routing mesh: notebook.local resolves from the rack (verified, 192.168.1.7, ~2ms) — LLM_OLLAMA_HOSTS=http://127.0.0.1:11434,http://notebook.local:11434;
  • if swarm is ever truly wanted it requires a rootful dockerd on the rack (split image store / GPU config / compose migration) — out of scope for P45.
**Swarm approach invalidated — the rack runs ROOTLESS Docker** (rootlesskit + slirp4netns, `unix:///run/user/1000/docker.sock`). Swarm mode does not function rootless: `swarm init` appears to succeed (node ls shows Leader) but the 2377 control listener stays inside the rootless netns (only published container ports are forwarded; laptop join → connection refused) and the VXLAN overlay data plane cannot work there. Phantom swarm state removed (`docker swarm leave --force`). **Revised design of record:** containerized-but-swarmless — - laptop runs the SAME `ollama/ollama` image with the same nvidia device-request GPU mechanism the rack compose service uses (proven under rootless on this OS): `docker run --gpus 1 -p 11434:11434 -e OLLAMA_KEEP_ALIVE=5m -v ollama_models:/root/.ollama ollama/ollama` (+ `nvidia-ctk config --set nvidia-container-cli.no-cgroups=true` for rootless); - stable addressing via mDNS instead of the routing mesh: `notebook.local` resolves from the rack (verified, 192.168.1.7, ~2ms) — `LLM_OLLAMA_HOSTS=http://127.0.0.1:11434,http://notebook.local:11434`; - if swarm is ever truly wanted it requires a rootful dockerd on the rack (split image store / GPU config / compose migration) — out of scope for P45.
Author
Owner

Laptop is operational in the fleet (2026-08-18):

  • Same ollama/ollama image + nvidia device-request mechanism as the rack compose service; container sees CUDA0: RTX 5070 Ti Laptop GPU — note it is the Laptop variant with 11.5 GiB usable VRAM, not the desktop 16 GB (32B-class models remain a rig-4090-only option).
  • Reachability: notebook.local (mDNS) + ufw scoped to exactly the rack (allow from 192.168.1.5 to any port 11434 proto tcp). Rack IP should get a DHCP reservation so the rule stays valid.
  • qwen2.5:14b pulled remotely via /api/pull; placement verified fully resident: 9,803/9,803 MiB in VRAM.
  • Measured decode (identical /api/chat, num_ctx 8192, temp 0): rack 3060 = 30.7 tok/s, laptop 5070 Ti = 48.8 tok/s → 1.6x; combined fleet ≈ 2.6x a lone 3060 for decode-bound work. (Prefill numbers were KV-cache hits after warmup — uncached prefill advantage should track the compute gap, so expect ~1.6-2x per-item on real screening mixes.)
  • Registered: LLM_OLLAMA_HOSTS=http://127.0.0.1:11434,http://notebook.local:11434 in .env; stack prisma screen --hosts ... (fleet driver, #655) consumes it.

Remaining for this issue: the rig 4090 (same recipe), stack llm hosts healthcheck CLI, stale pool.py docstring.

**Laptop is operational in the fleet** (2026-08-18): - Same `ollama/ollama` image + nvidia device-request mechanism as the rack compose service; container sees `CUDA0: RTX 5070 Ti Laptop GPU` — note it is the **Laptop** variant with **11.5 GiB** usable VRAM, not the desktop 16 GB (32B-class models remain a rig-4090-only option). - Reachability: `notebook.local` (mDNS) + ufw scoped to exactly the rack (`allow from 192.168.1.5 to any port 11434 proto tcp`). Rack IP should get a DHCP reservation so the rule stays valid. - `qwen2.5:14b` pulled remotely via `/api/pull`; placement verified fully resident: 9,803/9,803 MiB in VRAM. - **Measured decode** (identical /api/chat, num_ctx 8192, temp 0): rack 3060 = 30.7 tok/s, laptop 5070 Ti = 48.8 tok/s → **1.6x**; combined fleet ≈ 2.6x a lone 3060 for decode-bound work. (Prefill numbers were KV-cache hits after warmup — uncached prefill advantage should track the compute gap, so expect ~1.6-2x per-item on real screening mixes.) - Registered: `LLM_OLLAMA_HOSTS=http://127.0.0.1:11434,http://notebook.local:11434` in .env; `stack prisma screen --hosts ...` (fleet driver, #655) consumes it. Remaining for this issue: the rig 4090 (same recipe), `stack llm hosts` healthcheck CLI, stale pool.py docstring.
Author
Owner

Rig 4090 is in the fleet (2026-08-18): same container recipe, rig.local mDNS, ufw scoped to the rack. qwen2.5:14b + qwen2.5:32b pulled remotely; 32B fully VRAM-resident (20,756 MiB).

Measured decode (identical request, num_ctx 8192):

GPU 14B tok/s vs 3060
rack 3060 30.9 1x
laptop 5070 Ti 48.9 1.6x
rig 4090 87.5 2.8x
rig 4090 (32B) 43.0 —

Combined 14B fleet ≈ 5.4x a lone 3060. First 4090 job: 32B adjudication of the 116 stage-2 uncertains + 2 no-tool-call stragglers (fleet driver, single-host mode). LLM_OLLAMA_HOSTS now lists all three hosts. Remaining here: stack llm hosts healthcheck CLI + pool.py docstring fix.

**Rig 4090 is in the fleet** (2026-08-18): same container recipe, `rig.local` mDNS, ufw scoped to the rack. `qwen2.5:14b` + `qwen2.5:32b` pulled remotely; 32B fully VRAM-resident (20,756 MiB). **Measured decode (identical request, num_ctx 8192):** | GPU | 14B tok/s | vs 3060 | |---|---|---| | rack 3060 | 30.9 | 1x | | laptop 5070 Ti | 48.9 | 1.6x | | rig 4090 | 87.5 | 2.8x | | rig 4090 (32B) | 43.0 | — | Combined 14B fleet ≈ 5.4x a lone 3060. First 4090 job: 32B adjudication of the 116 stage-2 uncertains + 2 no-tool-call stragglers (fleet driver, single-host mode). `LLM_OLLAMA_HOSTS` now lists all three hosts. Remaining here: `stack llm hosts` healthcheck CLI + pool.py docstring fix.
Author
Owner

Closing as delivered. State on 2026-09-11 (stack llm hosts, live): rack http://127.0.0.1:11434 12 GB up (bge-m3, llama3.1:8b, nomic-embed-text, qwen2.5:14b, qwen2.5:14b-ctx8k); rig http://rig.local:11434 24 GB up (nomic-embed-text, qwen2.5:14b, qwen2.5:14b-ctx32k, qwen2.5:32b) — chats generate there; laptop http://notebook.local:11434 registered, DOWN at the moment (it sleeps/roams — the documented caveat; HostPool.check tolerates it and pick_model falls back to the rack). LLM_OLLAMA_HOSTS carries url@vram for all three; the laptop's declared VRAM corrected from 12 to 16 GB (5070 Ti). stack llm hosts prints reachability, VRAM and models and names the generation host/model; the /hosts API endpoint does the same for the service. The stale '5080' comment in llm/pool.py was already fixed. Remaining operational note, not code: when the laptop is up, pull the working set there (qwen2.5:14b-ctx8k is a local Modelfile variant — create it per host; nomic-embed-text/bge-m3 for the embed pool), and batch runners should re-check hosts between batches (#655).

Closing as delivered. State on 2026-09-11 (stack llm hosts, live): rack http://127.0.0.1:11434 12 GB up (bge-m3, llama3.1:8b, nomic-embed-text, qwen2.5:14b, qwen2.5:14b-ctx8k); rig http://rig.local:11434 24 GB up (nomic-embed-text, qwen2.5:14b, qwen2.5:14b-ctx32k, qwen2.5:32b) — chats generate there; laptop http://notebook.local:11434 registered, DOWN at the moment (it sleeps/roams — the documented caveat; HostPool.check tolerates it and pick_model falls back to the rack). LLM_OLLAMA_HOSTS carries url@vram for all three; the laptop's declared VRAM corrected from 12 to 16 GB (5070 Ti). stack llm hosts prints reachability, VRAM and models and names the generation host/model; the /hosts API endpoint does the same for the service. The stale '5080' comment in llm/pool.py was already fixed. Remaining operational note, not code: when the laptop is up, pull the working set there (qwen2.5:14b-ctx8k is a local Modelfile variant — create it per host; nomic-embed-text/bge-m3 for the embed pool), and batch runners should re-check hosts between batches (#655).
kert closed this issue 2026-09-11 21:20:24 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: homelab/stack#653