Tutorial — Local Inference for Offline Venues

Augmented Traveler local tutorial. Back to AI Mind — Tutorials · the decision: Tutorial — Choosing Cloud or Local Models · GPU profiling: Tutorial — Performance Profiling · hardware: Hardware — Deployment Specifications

How to run an open-weight language model on the kiosk PC (or a small second PC in the kiosk cabinet) for offline venues and schools. It covers the runtime (Ollama or llama.cpp server), the OpenAI-compatible endpoint, model choice, quantisation, the VRAM budget next to Unreal, local embeddings, structured output for answer_meta, benchmarks, updates and licences.

Draft. Written from the runtime docs listed under Sources and the model table in AI Mind — Tutorials section 2. The AI conversation engineer and DevOps engineer validate it on the kiosk PC. Correct flags and setting names here after the first install.

1. Where it runs

Layout For Against
Same PC as Unreal (RTX 5090, 32 GB) One box; no extra cost Unreal, ASR, TTS, lip sync and the model share one GPU; frame drops if we overfill it
Second small PC in the cabinet Unreal keeps the whole 5090; model can be larger Extra cost, heat, noise; one more machine to update

Decide after section 7. Start on the same PC; move out if the frame rate or first-token time suffers.

2. Runtime: Ollama or llama.cpp server

Both run GGUF models and expose an OpenAI-compatible API, so the conversation service only changes its base URL.

Ollama llama.cpp llama-server
Set-up Easiest; installer; pulls models by name Download a build; point at a GGUF file
Endpoint http://localhost:11434/v1/ (chat, completions, embeddings, responses, models) Port 8080 by default; chat, responses, embeddings
Structured output format with a JSON schema (native API); response_format for JSON mode on /v1 JSON schema and grammar constraints
Key settings OLLAMA_KEEP_ALIVE, OLLAMA_CONTEXT_LENGTH (default 4096), OLLAMA_NUM_PARALLEL (default 1), OLLAMA_KV_CACHE_TYPE (f16, q8_0, q4_0), OLLAMA_FLASH_ATTENTION -m, -c, -ngl, --parallel, -fa, --jinja, --api-key
Monitoring ollama ps shows GPU/CPU placement /health; /metrics with --metrics; timings in each response
Licence MIT MIT

vLLM (Apache 2.0) suits many requests at once. It reserves 90% of GPU memory by default (--gpu-memory-utilization 0.9, in older docs; check current), which does not suit sharing a GPU with Unreal. LM Studio is fine for trying models on a desktop; it is proprietary.

Suggested: Ollama for the first venue (simplest to operate); llama.cpp server if we need finer control.

Install on the kiosk (Ollama):

  1. Install Ollama as a service (Windows installer).
  2. Keep it on localhost. Ollama binds to 127.0.0.1:11434 by default. Do not set OLLAMA_HOST to expose it unless the model runs on the second PC; then allow only the kiosk PC's address through the firewall.
  3. Set OLLAMA_KEEP_ALIVE=-1 so the model stays loaded (a cold load in front of a visitor is too slow).
  4. Set OLLAMA_CONTEXT_LENGTH to what the persona and retrieved passages need (measure; start at 8192).
  5. Pull the chosen model by exact tag. Record the tag and digest in the venue config.
  6. Run ollama ps; it must say 100% GPU.

3. Choosing the model

From AI Mind — Tutorials section 2 (September 2026; check each model card):

Family Size to try first Licence Note
Gemma 4 12B (fits easily); 26B-A4B MoE Apache 2.0 Good first candidate
gpt-oss 20B (about 16 GB) Apache 2.0 Tight on a shared GPU
Qwen 3.6 / 3.8 ⚠️ 35B-A3B MoE (3B active) Apache 2.0 Fast first token; version names unverified
Meta Muse Glimmer ⚠️ 30B Apache 2.0 (reported) Single report; verify licence and card first
Llama 4 — Llama Community Licence Conditions apply; counsel reads first

Models marked ⚠️ come from one source. Do not ship one until the model card and licence have been read by the CTO.

4. Quantisation

5. VRAM budget on one RTX 5090 (32 GB)

Estimates to replace with measurements from Tutorial — Performance Profiling:

Item Estimate How to measure
Unreal: life-size MetaHuman, place kit, lighting Measure first; likely the largest item Unreal stats and GPU memory tools, Shipping build
ASR (Whisper large) about 3.9 GB (whisper.cpp figure); int8 less nvidia-smi with the service loaded
TTS Cloud: 0. Local Piper/Kokoro: small nvidia-smi
Lip sync (Audio2Face-3D, if used) Unknown; measure nvidia-smi
Embedding model Small (under 1 GB for common models; check) ollama ps
LLM weights 12B Q4_K_M ≈ 7 GB; 27B Q4_K_M ≈ 16 GB ollama ps
LLM context (KV cache) 2–6 GB ollama ps
Headroom 2–3 GB —

Rule: if Unreal plus speech needs more than about 14 GB, move the model to a second PC or drop to a 12B model.

6. Local embeddings and structured output

Embeddings: Ollama serves them at /api/embed (and /v1/embeddings). Its docs name embeddinggemma, qwen3-embedding and all-minilm. Use the same embedding model for indexing and querying. If the offline index was built with a cloud embedding model, rebuild it locally (Tutorial — Retrieval with Citations).

Structured output for answer_meta: the service needs answer ID, gesture tag, refusal flag and citation IDs before the audio starts (Tutorial — Conversation Client Plugin). Two ways:

  1. Two calls: a short constrained call returns JSON (gesture, citation_ids, confidence) using a JSON schema; then the answer text streams. Adds a little latency; simplest to validate.
  2. One streamed call: the answer streams as plain text with inline citation markers (e.g. [A-0142]); the service strips the markers before TTS and builds answer_meta from them. Faster; needs careful parsing.

Try option 2 first; fall back to option 1 if parsing proves unreliable. The policy layer checks the result either way (Tutorial — Policy Layer with Guardrails).

7. Benchmarks to run

Run with Unreal running the figure in a Shipping build, at kiosk settings.

  1. Time to first token (TTFT): 50 golden questions, median and 95th percentile. Target: under 600 ms (AI Mind — Process).
  2. Tokens per second: published 5090 figures (40–90 tokens/s) come from one blog; measure ours.
  3. Frame rate of the figure while the model generates. It must stay at target.
  4. Golden-set quality and a blind historian review of 50 answers (Tutorial — Evaluating Answers).
  5. Cold start: time from boot to first answer.
# skeleton — test before use. Needs: pip install openai
import time
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="local")

def bench(model, messages):
    t0 = time.perf_counter()
    first, chunks = None, 0
    stream = client.chat.completions.create(model=model, messages=messages, stream=True)
    for part in stream:
        if part.choices and part.choices[0].delta.content:
            chunks += 1
            if first is None:
                first = time.perf_counter() - t0
    total = time.perf_counter() - t0
    return {"ttft_s": first, "chunks_per_s": chunks / max(total - (first or 0), 1e-6)}

Chunks are close to tokens for most runtimes; for exact counts use the timings llama.cpp returns.

8. Update procedure

  1. Never update at a venue without a tested build.
  2. New model or runtime version → install on the dev workstation → run section 7 → historian review.
  3. Record the model tag, digest, quantisation, runtime version and settings in the venue config.
  4. Turn off auto-update on kiosk PCs (Ollama auto-updates on Windows; check how to disable it). Update in a maintenance window.
  5. Keep the previous model on disk for one week to roll back.

9. Licences

10. Practice exercise

On a dev workstation with the Mitchell figure running in Unreal, load a Gemma 4 12B Q4_K_M in Ollama. Run the benchmark on 20 golden questions. Then load a 27B-class model and repeat. Record TTFT, tokens/s, frame rate and VRAM for both on one page.

11. Check before an offline venue opens

Sources