senn-techsenn-tech
← All references02 · LLM

Enterprise AI stack, built in-house

Reference client: Own operations: the studio's in-house AI infrastructure

Customer data in copy and images has been neutralised.

4hosts, three of them with GPUs
1model family behind every chat lane
262.144tokens of context on the main lane
5weeks of rebuilding to today's setup
The fleet, today

Four hosts, three roles

Head node
virtual machine
orchestration
6 vCPU EPYC 7F52 · 47 GB RAM · no GPU (R9700 removed 09-01, ROCm purged 09-07)
Runs: LibreChat, LiteLLM, auto-router, document pipeline, dsh, ~15 MCP sidecars
Main host
bare metal
GPU host
16-core EPYC 8024P · 31 GB RAM · 4× RTX 5090 = 130 GB VRAM
Runs: flashnext-hybrid, the thinking / nothink / dsh / vision / CNC lanes
Standby
bare metal
GPU host
64-core EPYC 7502 · 125 GB RAM · 1× RTX 5090 = 32.6 GB VRAM
Runs: Qwen3.8-27B-AWQ-INT4, automatic fallback only
Helper
virtual machine
GPU host
8 vCPU EPYC 7F52 · 23 GB RAM · 1× RTX 4060 Ti = 16.4 GB VRAM (passthrough)
Runs: embed, rerank, Docling, dots.mocr; 15.4 of 16.4 GB used, ~0% load
Where we came from, where we are now

Five weeks, host by host

Dates are the real ordering here; this is a change log and not a countdown. Each entry is a decision that was actually made and measured and not a plan.

Main host
through 2026-08-19
Qwen3.5-122B-A10B, on a cron timer

A single huge MoE model, swapped morning and evening by a cron job. The switcher outlived the model it was written for and kept trying to resurrect it for weeks after the fleet had moved on.

time-scheduled122B, one lane at a time
Standby
through 2026-08-18
Served the document pipeline's vision leg directly

Several models in turn (Qwen3-VL, then a dense Qwen3.8-27B) read invoice and delivery-note images before the pipeline moved to routing everything through the gateway.

Head node
through 2026-08-31
AMD R9700 running vision-moe

Qwen3.6-35B-A3B, a genuinely different architecture (MoE rather than dense), used as the fourth independent vote in the document pipeline. Briefly also hosted the briefing bot, 08-28 to 09-01.

Helper
2026-08-17
Docling moved onto this card

Deterministic markdown and table parsing for the document pipeline's anchor lane.

Helper
2026-08-18 → now
dots.mocr added as a fourth voting laneNOW

A deterministic transcriber alongside the two LLM lanes: an LLM can invent a digit, a transcriber can't. Since then embed, rerank, Docling and mocr share this card, quietly full at 15.4 of 16.4 GB and ~0% GPU load.

stable since 08-18
Main host
2026-08-19
Qwen3.8-27B-FP8 (vLLM 0.27.1, TP=4)

The dense 27B replaces the 122B MoE. First time all seven legacy names (vision, text, gemma, gemma-4, qwen3.8-27b, thinking, nothink) point at one model.

9.03× @ 262,144~172 tok/s saturation
Standby
2026-08-20
Dedicated exclusively to the briefing bot

Qwen3.8-27B-AWQ-INT4, turboquant KV. A whole card for one Telegram bot, deliberately kept apart from LibreChat traffic.

1.64× @ 262,144 worst case
Standby
2026-08-28
The bot moves out, to the AMD card on the head node

Freed for another project. First stop on the bot's three-host, three-week tour.

Standby
2026-08-29
Repurposed as a manual standby for the main host

A safety net so thinking and nothink didn't go dark while the main host was tied up testing Flash-Next.

Main host
2026-08-31
Qwen3.8-27B-Cold-Fusion-GAIN-V1.1

Community fine-tune, dynamic FP8 quant. Matched the FP8 baseline's 18 of 21 battery score, improved tool-calling and vision, leaner output.

same battery scorebetter tool-calling
Main host
2026-08-29 → 09-03
Flash-Next tried on llama.cpp: didn't bundle

A new architecture tested across all four cards via llama.cpp. Solo throughput was excellent; concurrent throughput collapsed (see the numbers below). Production chat ran on the standby for the duration of the test.

96.8 → 50.7 tok/s at 8 parallel
Standby
2026-09-01
The bot comes back

Its AMD card on the head node was being physically removed. The bot returns here on llama.cpp, Cold-Fusion-GAIN IQ4_XS with a native MTP head.

Head node
2026-09-01
R9700 physically removed

Passthrough freed for another project. This is the moment the pipeline's fourth independent vote quietly went away.

document pipeline drops to 3 independent lanes
Standby
2026-09-02
The bot gains eyes

The GGUF had been running without its vision projector the whole time. Every image request had been failing silently. One missing file, found and fixed.

blind since 08-29fixed 09-02
Standby
2026-09-03
Production leaves for good; the bot is retiredNOW

Chat lanes move back to the main host permanently. The bot itself is parked; its cron and agent jobs migrated to dsh the same day. This card's job today: sit quietly and catch thinking/nothink if the main host errors or times out.

automatic fallback only65,536 ctx cap
Main host
2026-09-06
Flash-Next-Hybrid on vLLM, community FP8-KV patch

Same new model family, moved to vLLM where concurrent requests actually bundle. An FP8 KV-cache patch nearly doubled concurrency at the same context length.

5.94× @ 192,000up from 3.20× BF16
Main host
2026-09-07
Context raised to the model's native ceilingNOW

262,144 tokens, the same ceiling the 2026-08-19 model reached, this time on the new family, genuinely at its real maximum.

4.39× @ 262,144flashnext-hybrid, FP8-KV, 8 slots
Head node
2026-09-07
Whole AMD/ROCm stack purgedNOW

93 ROCm/HIP/HSA packages, 9 amdgpu/libdrm packages, 22 GB gone. This host now runs LibreChat, LiteLLM, the auto-router, the document pipeline, dsh and ~15 MCP sidecars, and not one GPU workload.

disk 87% → 78%head only, zero compute
01

Starting point

Running your own AI models on your own hardware means making every decision a rented API would otherwise make for you: which model, how much context, which card, which fallback path. In return, the data stays in-house, and nobody else decides when a model gets retired or swapped.

This reference comes from our own operations: the fleet that carries LibreChat, our own agents, and internal document processing every day. When a card here hits its limit, we find out first and not a customer left waiting.

Abb. 01The fleet today: a head node without GPU, a main host with four cards, a standby, a helper.
The four hosts of the AI fleet with roles and GPUs

Abb. 01The fleet today: a head node without GPU, a main host with four cards, a standby, a helper.

02

The hard part

It started with a single large model that a cron job swapped morning and evening. That held up right until the schedule outlived the model: the switcher kept trying to resurrect a model that had long since been replaced, weeks after the fact, because nobody had told it otherwise.

A second card read documents for invoice processing, deliberately on its own independent model architecture, so two readers wouldn't sit on the same foundation and be wrong together. That card was specialised hardware incompatible with the rest of the fleet, and it had to make way for another project.

A third service, a Telegram bot for the daily briefing, moved across three different cards within three weeks, always to wherever capacity happened to be free. On the last one it ran blind for five days, because a single file (the vision module) was simply forgotten during the move. No error, no warning, just an answer that never matched the picture.

03

Solution

The fleet today runs a single model family across every chat lane (reasoning on, reasoning off, agent traffic, document vision) from one host with four graphics cards, instead of seven different model names for seven different purposes.

If the main host fails or takes too long, a second card with the same model family steps in automatically: smaller context, different quantisation, but nothing a user would notice.

The small, constantly-used services (embedding for search, re-ranking results, text recognition) now sit together on one small card instead of scattered across the fleet.

vLLMLiteLLMQwen3.8llama.cppDoclingRTX 5090
Abb. 02PDF to ERP: four lanes per document, two of them on the same language model.
Document pipeline with four lanes and a shared language model

Abb. 02PDF to ERP: four lanes per document, two of them on the same language model.

04

How it is built

A gateway is the only thing any caller talks to: LibreChat, the agents, internal automation, even the CNC line on the shop floor. Which card actually answers stays hidden behind it, and that's exactly what makes the fallback possible without any client ever needing to change.

The road to today's 262,144-token context wasn't a straight one. A new model first needed a lower, more honest ceiling, because the old maximum no longer applied to the new architecture, a mistake that would otherwise have surfaced only at the first conversation that got cut off. A patch to the cards' cache format then nearly doubled concurrent capacity, from 3.2 to 5.9 full-length conversations, before context was raised back to its real maximum.

A different piece of serving software for the same new model looked excellent running alone and collapsed to less than half its solo throughput under eight concurrent requests. What runs today instead saturates past 30 concurrent conversations rather than collapsing at eight. That comparison alone decided which serving software the fleet runs.

In document processing, four independent readings vote on every field: two language models, two deterministic text-recognition passes. Since the specialised card was retired, two of those four votes now share the same server. That's documented openly and not glossed over: it's three independent opinions today and not four.

LibreChat29 agentsdshCNC lineGatewayroutes onwardMain host4 GPUs · one modelFallback hostsame model familyHelperembedding · text recognitionon failure
A caller only ever sees the gateway. Which card actually answers is its own business.
Reference

Every lane, what backs it, what it's for

LaneBackendStatusPurpose
thinkingmain:8000primaryMain chat lane, reasoning on. LibreChat and all 29 agents.
nothinkmain:8000primarySame model, reasoning off: clean JSON, tool-heavy flows.
dshmain:8000primaryIdentical backend to thinking, its own spend-log row for agent-job traffic.
visionmain:8000primaryAlias for callers still asking for “vision” by name. No separate vision model exists any more.
thinking-cncmain:8000primaryCNC runner with a 30-minute timeout; the default 600 s killed long jobs at 601 s.
cnc-nothinkmain:8000primarySame runner, reasoning off when overthinking derails the CNC task.
text(Alias)→ nothinkhiddenLegacy name, transparently mapped.
auto(Alias)→ thinkinghiddenLegacy name, transparently mapped.
thinking-fallback / nothink-fallbackstandby:8080fallbackAuto-failover if the main host errors or times out. Smaller context, INT4 not FP8: continuity and not parity.
embedhelper:8010primaryRAG / vector search.
rerankhelper:8011primaryRAG result re-ranking.
mocrhelper:8123primaryDeterministic OCR, also the document pipeline's fourth voting lane.
hermesstandby:8080parkedThe briefing bot, retired 2026-09-03, jobs migrated to dsh.
securitystandby:8081defined, offAbliterated red-team model, commented out and not live.
PDF to ERP

Four lanes vote on every document

The document pipeline reads invoices and delivery notes for ERP and TMS import. Each lane extracts independently; a reconciler picks the consensus value per field.

DocumentVision lanereads the page imageText lanereads Docling markdownAnchor laneDocling, no LLMmocr lanedots.mocr, no LLMone language modelmain host, reads twiceReconcilerconsensus per field, then ERPThe shared box is the finding and not decoration: vision and text used to be two separate models.
!

3 independent opinions instead of 4. Vision and text both resolve to the same backend (nothink on the main host) since the 2026-08-19 gateway cutover: one LLM reads twice, plus two genuinely separate deterministic parses (Docling, dots.mocr).

No quick fix: the old dedicated vision backend (the AMD R9700's vision-moe) was physically removed 09-01 and its software stack purged 09-07. Restoring true four-way independence means standing up a new backend and not flipping a setting.

Comparisons

The two decisions the numbers actually made

Concurrency on the main lane (main host)

KV-cache pool size, expressed as a multiplier of full-context requests it can hold at once

0×2×4×6×8×10×9.03×08-1927B-FP83.20×09-03Flash-Next BF165.94×09-06+ FP8-KV patch4.39×09-07native ctx · now

The dashed segment is a model-family swap and not a tuning step; concurrency should reset there. The two solid segments are the same model family: the FP8-KV patch nearly doubled concurrency, and reaching the true 262k ceiling cost some of that back.

Why the main lane ended up on vLLM

Same Flash-Next-class model, two serving engines, tokens per second

050100150200 tok/sllama.cpp, solo96.8llama.cpp, ×8 (total)50.7vLLM, 32–64 concurrent172

llama.cpp was faster solo and slower bundled than itself: eight concurrent streams totalled less throughput than one. vLLM saturates instead of collapsing, which is the whole reason the main host runs it.

05

Day-to-day operation

A watchdog script automatically restarts stopped services, useful, until it misidentified a model and resurrected one that had long been replaced. The rule since is as simple as it is strict: a service only counts as 'should be running' if its own restart policy says so too.

The automatic fallback isn't just configured, it's regularly tested for real: the main host gets deliberately knocked out, and the only thing that counts is whether the second one actually takes over and not whether the configuration looks plausible.

Abb. 03The two measurements that decided the choice of serving software.
Two measurement charts: concurrency and throughput

Abb. 03The two measurements that decided the choice of serving software.

06

Outcome

Every chat lane now runs on one model family with automatic failover, at a native context ceiling of 262,144 tokens: measured and not claimed.

The fleet's former head node no longer carries a single graphics card; its entire specialised driver stack was removed, freeing 22 GB of disk.

07

What we learned

On a self-run AI stack, the hardware is the smaller job. Almost every fault during those five weeks of rebuilding was not a failed card but a rebuild where something got left behind. Maintenance now follows a short checklist rather than experience.

We now re-measure which model actually runs behind which name, on a schedule. We did that systematically for the first time while writing this report, and the result did not match the configuration. The measurement has been part of maintenance ever since and not part of the write-up.

Four cards cover our daily load. The limiting factor is not compute but the maintenance time left over alongside operations. Anyone planning a self-run AI stack should cost those hours first, since they decide whether the setup is still current in two years.

Similar problem?

Tell us what you're planning, a short call clarifies whether it pays off.