# One box dirty, one box clean: the two-node home AI lab that keeps its numbers honest

> Our two identical Strix Halo units live opposite lives: one carries the whole containerized service stack, the other serves a single model and stays otherwise empty. The split is not tidiness — the benchmark harness invalidates any run that finds a GPU neighbor on the host.

- Published: 2026-08-21
- Evidence level: lab_single_run
- Funding: Self-funded internal research
- Canonical: https://agmind.ai/reports/two-node-home-ai-lab/

The short version: this lab's two Strix Halo boxes are commercially
identical and deliberately opposite. One carries the entire containerized
service stack and is allowed to be messy. The other serves one model through
one runtime and is otherwise kept empty. The split is not housekeeping — it
is a precondition our benchmark harness physically checks. The
[Habr original, in Russian](https://habr.com/ru/articles/1058502/), tells
the operational half of the story; this page adds the half that only makes
sense next to our measurement rules.

Both machines are Beelink GTR9 Pro units: Ryzen AI Max+ 395, 128 GB of
unified memory, an integrated GPU that shares that memory with everything
else on the host. The last property decides the architecture. On a
workstation with a discrete card, a stray background service steals some CPU
and leaves your VRAM alone. On unified memory there is no "alone": every
neighbor lives on the same bandwidth your inference decodes through.

## What runs on which box?

The service node — the dirty one — carries the platform. Dify with its API,
worker and web pieces. RAGFlow with the MySQL, Elasticsearch and MinIO
retinue it brings along. A Milvus vector store. Small llama.cpp servers
doing embeddings and reranking on the integrated GPU, because those calls
are short, frequent and tolerant enough to share a busy host. Prometheus,
Grafana and Loki watching everything; Traefik, Authelia, n8n and Open WebUI
at the edge.
The count stood at 34 containers when the Habr article was written.

What the dirty box does not do is heavy generation. Large-model answers
arrive over the LAN from other hardware; this box is a platform, not an
engine.

The clean node runs a llama.cpp server on the Vulkan backend with one
Qwen3.6 MoE model loaded, and that is the complete inventory. No databases,
no proxy, no dashboards. Between measurements it sits close to idle, on
purpose.

## Why does a measurement need an empty host?

Because our harness refuses to take anyone's word for it, including ours.
Every benchmark run starts by recording a host inventory: the full list of
containers alive on the machine at that moment. When the run ends, the
inventory is taken again. Both snapshots go into the run manifest as
evidence, and if either one contains a GPU-capable neighbor — anything that
looks like another inference server or GPU runtime — the cell is invalidated
outright. A neighbor that appears mid-run kills the measurement as surely as
one that was there at the start.

This is the same posture as the rest of
[our benchmarking method](https://agmind.ai/reports/how-to-benchmark-local-llm/): a condition
that would invalidate a number must be machine-checked, not promised in
prose. And once "nothing else runs here" is a hard gate, the cheapest way to
pass it every day is a second machine where everything else runs. The dirty
box exists so the clean box can stay empty. That sentence is the whole
architecture.

## What operating the dirty box taught us

**Pin the stack and write the pins down.** On this GPU family the working
combination of kernel, firmware and driver versions is exactly that — a
combination. Upgrading one member because it looked harmless has produced
regressions before. A stack that works is recorded as a set and changed as a
set.

**Stock monitoring fails by omission.** The vendor GPU tool answers N/A for
key sensors on this silicon, so temperature and power are read from the
kernel's hwmon files by a small collector feeding node-exporter. Readers of
[our DGX Spark monitoring report](https://agmind.ai/reports/dgx-spark-gpu-monitoring/) will
recognize the disease: different vendor, same green-and-empty dashboard,
same boring cure.

**GPU offload can vanish without an error.** A misnamed Vulkan ICD manifest
silently disables the GPU; generation continues on the CPU, slower and
quieter. The only confirmation worth trusting is the runtime's own log line
reporting every model layer offloaded — which is why the harness also treats
a detected CPU fallback as an invalidation, not a footnote.

One more habit that earned its keep: restart counters as the primary health
signal. Nearly every container on the dirty box has never restarted; the one
that keeps a nonzero counter is named in the Habr piece and watched. A
counter that only moves when something actually died says more than a wall
of green panels.

## Scope

This page describes two specific units, one container stack and one runtime
choice, as they stood at the time of the Habr write-up. It is an operational
account, not a comparative study: we do not claim this split is optimal,
only that it is what lets every published number carry proof the host was
quiet. The configurations, the memory tuning and the measured figures live
in [the primary source on Habr](https://habr.com/ru/articles/1058502/), in
Russian.

---

Machine-readable claim registry: https://agmind.ai/claims.json · llms.txt: https://agmind.ai/llms.txt
