Files
modelTesting/CLAUDE.md
T

7.0 KiB
Raw Blame History

CLAUDE.md — Local LLM Benchmark Testing Project

What this project is

A personal test harness for evaluating local LLM models running in LM Studio on an Apple M3 Max MacBook Pro (48 GB unified memory). A fixed coding prompt (LFU cache) is fed to each model, the model's Python output is graded against a strict rubric, and the results are accumulated into a persistent JSON store and rendered into a standalone HTML dashboard.

This is a git repo (remote: ssh://git@git.itsaygea.com:2222/admin/modelTesting.git, branch main) deployed to Coolify, but it has no runtime build/run cycle locally — it's a folder of prompt files, model outputs, a JSON results store, and a Python generator that emits static HTML.

Folder layout

prompts/
  lfu_cache_prompt.txt   # The fixed prompt given to every model (the "exam question")
  grading.txt            # The grader's instructions / rubric (read at session start)
outputs/                 # Raw model outputs (the .py files each model produced)
  <model-name>-<quant>.py
data/
  benchmark_history.json # Persistent results store (SOURCE OF TRUTH — committed)
generate_dashboard.py    # Reads the JSON → builds dashboard.html + pages/*.html
dashboard.html           # GENERATED — gitignored (built on deploy, not committed)
pages/                   # GENERATED — per-model detail pages, gitignored
Dockerfile               # Coolify: python build stage → nginx static serve
DEPLOY.md                # Gitea→Coolify deploy instructions
README.md

Git policy: source only is committed (prompts/, outputs/, data/benchmark_history.json, generate_dashboard.py, Dockerfile, docs). The generated dashboard.html + pages/ are gitignored — Coolify rebuilds them on each push via the Dockerfile's python3 generate_dashboard.py step. If you ever want to commit the built HTML instead, uncomment those lines in .gitignore.

Remote: ssh://git@git.itsaygea.com:2222/admin/modelTesting.git (SSH key auth as admin verified). Push to main → Coolify rebuilds.

prompts/
  lfu_cache_prompt.txt   # The fixed prompt given to every model (the "exam question")
  grading.txt            # The grader's instructions / rubric (read at session start)
outputs/                 # Raw model outputs (the .py files each model produced)
  <model-name>-<quant>.py
data/
  benchmark_history.json # Persistent results store (created on first grading run)
dashboard.html           # Generated standalone dashboard (dark-mode, Chart.js via CDN)
*.py                     # Loose model outputs in root (e.g. deepseekv4flash.py) — legacy/unsorted

The workflow (what to do when the user submits a model's output)

Follow the 4 steps in prompts/grading.txt. Captured metrics per model: tok_sec, total_tokens, ttft_sec (ask the user for these when grading — can't be inferred from files), plus optional speed_caveat text (e.g. the Gemma4 GPU-offload note) and tests_pass (bool — always run the model's .py and record whether it crashes).

  1. Audit the .py file against the 5 pillars (each 020 → 0100 total):
    • Complexity violations (true O(1) — no heaps, sorted(), linear scans)
    • Async race conditions / deadlocks (locks held across sleep/IO, unsynced shared state)
    • Transactional isolation leaks (read-your-own-writes, commit/rollback correctness)
    • Memory leaks & edge cases (DLL unlink, min_freq updates, time.monotonic(), __slots__)
    • Test coverage integrity (real edge cases vs trivial always-pass asserts)
  2. Extract metrics: model name, quant, tok/sec, verdict, archetype/best-for, critical bugs, patch code.
  3. Update data/benchmark_history.json — append/update the model's entry (schema in grading.txt). Create the file if missing.
  4. Regenerate the site by running python3 generate_dashboard.py (reads data/benchmark_history.json, overwrites dashboard.html + every pages/<id>.html). Style is locked: cyberpunk-terminal (dark #0a0a0f bg, neon cyan/magenta/lime accents, Fira Code/Sans, scanline+grid texture, glow-on-hover score bars). Dashboard = summary stats + Chart.js bar (score vs tok/sec) + radar (top-3 pillars) + leaderboard table linking to per-model detail pages. Each detail page = verdict/score header, metric tiles, pillar bars, "went right/wrong" columns, critical bugs, recommended use, and a refactored patch code block. tests_pass, cloud models (no tok/sec), and speed-caveats are all handled in the UI.

The exam prompt (lfu_cache_prompt.txt) requires a pure-stdlib Python 3.11+ async LFU cache with frequency-bucket O(1), dual-layer TTL eviction, ACID-like transactions, and an async def main() test suite. Use it as the spec when judging correctness.

Model output — naming convention

Model output filenames encode the model + quant/format so a model is identifiable from the filename alone. The convention the user uses:

<model-family><version>-<param-size>-<variant>-<quant-or-format>.py

Lowercase, hyphen-separated, no spaces. Examples seen so far:

Filename Reads as
qwen3.6-35b-a3b-6bit-mlx.py Qwen 3.6, 35B-A3B (MoE), 6-bit, MLX format
qwen3.6-35b-a3b-uncensored-hauhaucs-aggressive-gguf.py Qwen 3.6 35B-A3B, uncensored fine-tune, GGUF
gemma-4-31b-qat-gguf.py Gemma 4 31B, QAT, GGUF
gemma-4-12b-coder-fable5-composer2.5-v1-uncensored-heretic-mxfp8-mlx.py merged/model-card-style name, MLX
kat-coder-v2.5-dev-xl-mlx.py KAT Coder v2.5 Dev XL, MLX

Tokens: mlx = Apple MLX format; gguf = llama.cpp GGUF; quant suffixes like q6k, 6bit, 4bit, qat, mxfp8 go in the name. When in doubt about what a filename denotes, parse it loosely into model + quant rather than guessing a wrong label — the grading step pulls model/quant from "file name or user input," so either source is valid.

When the user pastes raw output instead of giving a file

If the user pastes a model's output text directly (no file), save it as a .py file in outputs/ using the naming convention above before grading. Ask the user for the model name + quant only if it cannot be reasonably inferred from context. Match the existing lowercase-hyphen style.

Hardware / runtime context

  • Machine: Apple M3 Max, 48 GB unified memory. Local inference only.
  • Inference server: LM Studio (OpenAI-compatible local endpoint).
  • Quants/formats that fit 48 GB comfortably: ~35B dense at 4-bit/6-bit, larger MoE models (only active params loaded). MLX is preferred for Apple Silicon; GGUF via llama.cpp also works.

Notes / gotchas

  • data/ starts emptybenchmark_history.json does not exist until the first grading run creates it. Don't assume it's there; read defensively, create on write.
  • dashboard.html is regenerated (overwritten), not appended to. The source of truth is the JSON file.
  • No build step, no tests to run, no dependencies to install — pure stdlib Python outputs + a static HTML file.
  • Treat the loose .py files in the project root (like deepseekv4flash.py) as unsorted outputs that belong in outputs/ per the convention. Don't move them unless asked.