cc-os/docs/memory-system/benchmark/local-llm-findings-2026-06-...

275 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Local-LLM Doc-Extraction Gut-Check — 2026-06-04
_Last updated: 2026-06-04_ | _Status: gut-check complete; gemma4:e4b is the candidate; GPU confirmed post-reboot (~74 tok/s, see appendix); Graphify validation is the next step._
This document consolidates the local-LLM doc-extraction gut-check run on 2026-06-04. It is
the authoritative findings record for that session and feeds forward into the Graphify-ollama
validation step.
**Four things this document keeps strictly separate:**
- **(A) Model-capability signal** — what carries forward; the durable conclusion.
- **(B) Raw-ollama harness gotchas** — benchmark-process notes only; explicitly NOT production config.
- **(C) GPU driver fix** — real diagnosis; timing estimate, not a measured result.
- **(D) Graphify config surface** — the actual production guidance.
Cross-references:
- Claude-tier reference run: `docs/memory-system/benchmark/run-findings-2026-06-04.md`
- 18 Claude reference fragments: `docs/memory-system/benchmark/reference-outputs/` (`.haiku.md`, `.sonnet.md`, `.opus.md` files only)
- Authoritative Graphify-ollama section: `docs/graphify/05-local-models-and-backends.md`
---
## TL;DR — What worked
- The local model class is **good-enough** for the doc-extraction role. Best installed model:
**`gemma4:e4b`** (~9.6 GB, fits the RTX 3060's 12 GB). It produced valid schema on 6/6
fixtures with clean, well-namespaced facets (0 bad facets out of 6) and note-grounded
entities (no contamination).
- It catches the high-value entities (~4560% of Opus's entity recall), missing Opus's long
tail — acceptable for the role.
- **Critical reframe:** in production we will NOT hand-invoke ollama. Graphify owns the ollama
call (prompt, chunking, context window, parsing) over its HTTP API. The hand-rolled benchmark
validated *model capability*, not the production pipeline. The raw-ollama tuning discovered
during this run is Graphify-internal, not our config surface.
---
## (A) Model-capability signal — measured via a hand-rolled prompt/parser (NOT Graphify)
This signal was gathered through a custom prompt and a custom YAML parser, NOT Graphify's
extraction path. It says "the model class is good-enough." It does NOT say "this is what
Graphify will output" — Graphify uses a different prompt, schema, and parser.
### Results across the 3 installed models (all reasoning models; only these 3 are on the machine)
| Model | Size | Parse rate | Entities/note | Facet quality | Verdict |
|---|---|---|---|---|---|
| `gemma4:e4b` | ~9.6 GB | 6/6 (2 needed trivial colon-quote repair) | 1334 (~4560% of Opus) | 0 bad/6 | **Usable — best pick** |
| `gemma4:e2b` | ~7.2 GB | 4/6 (2 hard YAML breaks — dropped `source:` key) | 2331 when valid | 0 bad/6 | Usable only behind a tolerant parser + retry |
| `qwen3.5:2b` | ~2.7 GB | parses but collapses to ~1 entity/note | 1 | n/a | Not usable as tested |
All three land below haiku (the weakest Claude frontier tier: 1937 entities, 6/6 clean parse,
clean referential integrity). "Below frontier-floor" does not mean "unusable" — `gemma4:e4b`'s
misses are the long tail; the high-value entities are present.
### gemma4:e4b quirks observed
- Occasional top-level key rename (e.g. `entity_relationships` instead of the spec's key).
- A `target: Concept` placeholder leak on the longest note — it echoed the schema's example
value rather than a concrete entity. Seen once across 6 fixtures.
These quirks were caught by the hand-rolled parser and are noted as model-class behavior.
Whether Graphify's parser handles them gracefully is a separate question (see section D).
---
## (B) Raw-ollama harness gotchas — benchmark-process notes ONLY
> **EXPLICIT CAVEAT:** This section records why the first benchmark pass produced junk and
> what was done to fix the harness. These knobs are NOT our production settings. They are
> Graphify-internal concerns (see section D). Do not treat this section as production config.
### Problem: default context truncation produced garbage output
Bare `ollama run` with the default (~4K) context truncated output mid-stream. Reasoning models
burn their token budget on chain-of-thought first; the model got guillotined mid-output, leaving
malformed YAML.
### Harness fix (benchmark only)
- Called the HTTP API at `/api/generate` instead of `ollama run`.
- Set `num_ctx=8192` (per-request via the API body).
- Set `think=false` to suppress chain-of-thought tokens and keep the output budget for the
extraction result.
- Added a tolerant YAML parser that retries on soft parse errors and auto-quotes bare scalars
containing `:`.
These changes produced the results in section A. They explain the benchmark outcome; they do
not define the production path, which Graphify handles internally.
---
## (C) GPU not used during this run — root cause and fix
### Root cause: NVIDIA driver version mismatch
| Item | Value |
|---|---|
| In-kernel module | 580.142 |
| On-disk / userspace driver | 580.159.03 |
The driver was updated on disk on 2026-06-03 but the stale module stayed resident. Result:
`nvidia-smi` fails — "Failed to initialize NVML: Driver/library version mismatch." Because
NVML/CUDA cannot initialize, ollama finds no CUDA device (`total_vram="0 B"`) and runs 100%
on CPU.
Same-machine proof: the ollama log at 07:57 on 2026-06-03 DID find the RTX 3060
(`compute=8.6 ... 12.0 GiB`); the 16:12 service instance the benchmark ran under found no CUDA.
**CPU eval rate observed:** ~10.6 tok/s → ~187313 s/note. The 9.6 GB model fits the 12 GB
card; VRAM is not the bottleneck — the driver mismatch is.
### Fix (user must run — non-interactive sudo was blocked during this session)
The running kernel already matches the on-disk 580.159.03 module, so a reboot loads the correct
module:
```bash
sudo reboot
# then verify:
nvidia-smi # should print the RTX 3060 table, no NVML error
# only if CUDA still fails after reboot:
sudo dnf reinstall 'akmod-nvidia*' 'xorg-x11-drv-nvidia*' # then reboot again
```
### ESTIMATE — post-reboot GPU timing (NOT measured; blocked by sudo this session)
With the RTX 3060 and the 4-bit `gemma4:e4b` model fully resident, ~4060 tok/s plus faster
prompt processing should drop extraction to roughly single-digit to ~20 s/note — i.e.,
plausibly within a "well under a minute" target.
**This is an estimate to be confirmed post-reboot using the curl command in the appendix below.**
It is not a measured result.
---
## (D) Graphify config surface — production guidance
> This is the only section that contains production configuration guidance. Sections B and C
> are process notes; section A is a model-capability signal only.
### How Graphify talks to Ollama
Graphify talks to Ollama over its **HTTP API** at `http://localhost:11434`, not by shelling out
to `ollama run`. `[github-inferred]` from the env-var/timeout/num_ctx design; the exact endpoint
(`/api/generate` vs `/api/chat`) is `[unverified]` — package internals are not browseable on
GitHub.
Graphify **owns** the prompt template, chunking, context sizing (auto-sized `num_ctx` by
default), and output parsing. `[github]` for the env-var knobs; prompt and parser specifics are
`[github-inferred]` / `[unverified]`. Whether Graphify sets `think=false` and whether it does
tolerant-YAML / retry are `[unverified]`.
Implication: the hand-rolled extraction-spec prompt and raw-ollama settings from section B
likely do **not** apply to Graphify's ollama path. Our levers are the documented external knobs
only.
### Text-vs-code split
Confirmed `[github]` (verbatim README): code is extracted locally via tree-sitter AST with zero
LLM calls; docs/PDFs/images go through the LLM backend (Ollama here). Ollama is invoked **for
text documents only.**
### Model selection
No official Graphify-recommended Ollama model. `[github]` — absence verified at v0.8.30.
Community starting points: `qwen2.5:7b`, `phi4:14b`. `[community]` Default behavior: auto-detect
the running model. Override via `OLLAMA_MODEL`.
Per this benchmark, `gemma4:e4b` is the best installed candidate — but this must be validated
through Graphify's own extraction path, not the hand-rolled benchmark (see Deferred
follow-ups §1 below).
### Environment variables and CLI flags (all `[github]` from v0.8.30 README)
| Knob | Default | Notes |
|---|---|---|
| `OLLAMA_BASE_URL` | `http://localhost:11434` | Ollama endpoint |
| `OLLAMA_MODEL` | auto-detect | Override to pin a model |
| `GRAPHIFY_OLLAMA_NUM_CTX` | auto-sized | Override context window |
| `GRAPHIFY_OLLAMA_KEEP_ALIVE` | unspecified | Minutes to keep model loaded; `0` = unload after each chunk |
| `--token-budget` | — | Chunk size (tokens) |
| `--max-concurrency` | — | Parallel extraction workers |
| `--api-timeout` | 600 s | Increase for slower hardware |
### Known sharp edge
Context saturation across consecutive chunks on the Ollama backend can exhaust VRAM after a
few chunks. `[community, issue #798]` Mitigate by lowering `GRAPHIFY_OLLAMA_NUM_CTX` and/or
setting `GRAPHIFY_OLLAMA_KEEP_ALIVE=0`.
---
## Appendix — Exact commands
### Graphify Ollama path `[github]`
```bash
# Install (package is graphifyy — double-y; command is graphify):
uv tool install "graphifyy[ollama]"
# Pull the candidate model (gemma4:e4b is the local best pick per this benchmark):
ollama pull gemma4:e4b
# Start Ollama if not already running as a service:
ollama serve
# Basic extraction:
graphify extract ./docs --backend ollama
# Constrained single-GPU tuning:
GRAPHIFY_OLLAMA_NUM_CTX=8192 graphify extract ./docs --backend ollama \
--token-budget 4000 --max-concurrency 2 --api-timeout 900
# Free VRAM between chunks (mitigates issue #798):
GRAPHIFY_OLLAMA_KEEP_ALIVE=0 graphify extract ./docs --backend ollama
```
### Post-reboot GPU re-timing (benchmark verification only — NOT production)
Run this after rebooting to confirm the GPU is now used and to measure actual tok/s:
```bash
F="$HOME/Documents/SecondBrain/2026-03-31-10dlc-isv-setup-guide-oncadence.md"
PROMPT=$(python3 -c "import json;print(json.dumps('Extract entities from this note:\n\n'+open('$F').read()))")
time curl -s http://127.0.0.1:11434/api/generate \
-d "{\"model\":\"gemma4:e4b\",\"prompt\":$PROMPT,\"stream\":false,\"think\":false,\"options\":{\"num_ctx\":8192}}" \
| python3 -c "import json,sys;d=json.load(sys.stdin);ec=d['eval_count'];ed=d['eval_duration'];print('eval tok/s:',round(ec/(ed/1e9),2),'| eval_count:',ec,'| total wall (s):',round(d['total_duration']/1e9,2))"
```
This measures raw model tok/s via the hand-rolled harness — it does NOT measure Graphify's
production extraction performance.
#### Measured results (run 2026-06-04, post-reboot)
GPU is now fully active and resident — the driver mismatch diagnosed in section C is
**RESOLVED**. `nvidia-smi` is healthy (Driver 580.159.03, RTX 3060, no NVML error). The
`gemma4:e4b` model loaded fully GPU-resident: `/api/ps` reports `size_vram` 3.29 GB ==
`size` 3.29 GB (no CPU fallback), with 4462 MiB used on the card.
| Pass | eval tok/s | eval_count | total wall (s) | load (s) | prompt_eval (s) | prompt_eval_count |
|---|---|---|---|---|---|---|
| Cold | 74.27 | 701 | 76.04 | 38.15 | 28.44 | 905 |
| Warm | 74.25 | 717 | 10.13 | 0.43 | 0.04 | — |
Conclusion: steady-state generation is **~74 tok/s** — at or above the section C estimate of
4060 tok/s. Warm extraction lands at **~10 s/note**; the cold pass is ~76 s, dominated by the
one-time 38 s model load plus a 28 s cold prompt-eval. This confirms section C's "well under a
minute once the model is resident" prediction.
> **Caveat retained:** these are raw model tok/s from the hand-rolled curl harness, NOT
> Graphify production extraction performance (see Deferred follow-up §1).
---
## Deferred follow-ups
1. **Definitive validation: run Graphify's own ollama path over the fixtures** — NOT re-scoring
the hand-rolled benchmark. Do NOT conclude the next step is "score Graphify output against
the 18 Opus references": that is apples-to-oranges (different prompt/schema/output). The 18
Opus references validated model capability; they are not a scoring rubric for Graphify's
graph output.
2. **Resolve `[unverified]` Graphify internals** (HTTP endpoint, think-mode, retry/parsing):
after `uv tool install "graphifyy[ollama]"`, read the installed client source under the
tool's site-packages. This does not require the GPU — a cheap deferred follow-up.
3. **Confirm GPU timing post-reboot****RESOLVED (2026-06-04).** GPU re-timing ran post-reboot;
measured ~74 tok/s steady-state (at/above the section C estimate). See the "Measured results"
table under the Post-reboot GPU re-timing appendix above.
4. **Raw per-model benchmark outputs from this run** were throwaway scratch (wrong harness,
hand-rolled prompt) and are not committed.