
Memo is engineered for low-latency, high-throughput local AI. Every component is optimized for the constraints of consumer hardware — laptops, desktops, and workstations.
Messages are delivered token-by-token using Server-Sent Events (SSE), providing immediate visual feedback:
HTTP GET /api/chat/stream
│
├─▶ data: "Hello"
├─▶ data: ","
├─▶ data: " world"
├─▶ data: "!"
└─▶ data: [DONE]
| Metric | Local (llama.cpp) | External Provider |
|---|---|---|
| Time to First Token (TTFT) | 50–200ms | 300–800ms |
| Tokens per Second (TPS) | 20–60 (GPU) / 5–15 (CPU) | 30–100 (API-dependent) |
| Buffer Size | 1 token | 1 token |
| Connection | Persistent SSE | Persistent SSE |
The Flutter frontend uses Riverpod state management with per-token granularity updates. There is no batching delay — each token is rendered as it arrives.
The most impactful performance setting is GPU layer offloading:
Model: llama-3.1-8b-instruct (Q4_K_M, 4.9 GB)
n_gpu_layers = 0 → CPU only → 8–12 tok/s
n_gpu_layers = 16 → Partial GPU → 18–25 tok/s
n_gpu_layers = 33 → Full GPU (8GB) → 35–55 tok/s
Memo auto-configures n_gpu_layers based on detected VRAM. The formula:
func optimalLayers(modelSizeGB, vramGB float64) int {
maxLayers := totalLayers(modelSizeGB)
gpuRatio := vramGB / modelSizeGB
if gpuRatio >= 0.9 {
return maxLayers
}
return int(float64(maxLayers) * gpuRatio)
}
Users can override the auto-configuration in Settings → Performance.
GGUF models are memory-mapped (mmap) for efficient loading:
The context window size (--ctx-size) directly affects RAM usage:
| Context Size | RAM Overhead |
|---|---|
| 2048 | ~512 MB |
| 4096 | ~1 GB |
| 8192 | ~2 GB |
| 16384 | ~4 GB |
| 32768 | ~8 GB |
Reducing context size is the most effective way to fit a model on limited hardware.
| Operation | p50 | p95 | p99 |
|---|---|---|---|
| Model load (GPU) | 3s | 8s | 15s |
| First token (GPU) | 80ms | 200ms | 500ms |
| First token (CPU) | 150ms | 400ms | 1s |
| Vector embedding (single chunk) | 30ms | 80ms | 150ms |
| Vector search (10K docs) | 3ms | 8ms | 15ms |
| Agent tool dispatch | 5ms | 20ms | 50ms |
| Workload | GPU (RTX 3060) | CPU (Ryzen 7) |
|---|---|---|
| Chat tokens/sec | 40–55 | 10–15 |
| Embeddings/sec (768-dim) | 50–80 | 20–30 |
| Ingest (pages/sec) | 15–25 | 5–10 |
| Strategy | Impact | Trade-off |
|---|---|---|
| Increase GPU layers | 2–4× faster inference | Requires VRAM headroom |
| Use Q4_K_M quantization | 50% less RAM vs Q8_0 | Minimal quality loss |
| Reduce context window | Less RAM pressure | Shorter conversation memory |
| Disable unused providers | Faster startup | Must re-enable to use |
| Limit agent iterations | Faster task completion | Complex tasks may need more |
| Use local model for simple queries | Zero network latency | Complex reasoning may need cloud |
Memo exposes runtime performance metrics via the API and diagnostic dashboard:
GET /api/diagnostics
{
"llama_server": {
"status": "running",
"uptime_seconds": 12345,
"tokens_generated": 458200,
"avg_tps": 42.3,
"vram_used_mb": 5800,
"vram_total_mb": 8192
},
"embedding_server": {
"status": "running",
"uptime_seconds": 12340,
"embeddings_generated": 12400,
"avg_ms_per_embed": 45.2
},
"memory": {
"total_chunks": 8420,
"index_size_mb": 128,
"last_vacuum": "2026-07-04T10:15:00Z"
}
}
If you seeavg_tpsdrop below expected levels, check that GPU layers are offloaded (Settings → Performance) and that no other GPU-intensive application is competing for VRAM.