Skip to content

bench: realtime caption eval set + Ollama-MLX vs llama.cpp-Metal comparison - #28

Open
PCcoding666 wants to merge 1 commit into
masterfrom
bench/engine-compare
Open

PCcoding666 wants to merge 1 commit into
masterfrom
bench/engine-compare

Conversation

@PCcoding666

Copy link
Copy Markdown
Owner

Summary

Answers "does the embedded llama.cpp path cost us anything versus Ollama's MLX runner on the same model?" with numbers, and measures the Qwen2.5-1.5B model that PR #27 currently bundles.

  • benchmark/realtime_eval_set.json: 60 cases in 8 categories shaped like real ASR output (short turns, half-sentence drafts, no punctuation + fillers, numbers/names, tech terms, long sentences, zh→en, context follow-ups), each with a reference and the terms that must survive.
  • benchmark/run_engine_compare.py: runs any Ollama or OpenAI-compatible server (llama-server, etc.) with streaming, thinking off, num_ctx 4096, and scores TTFT / end-to-end / tok/s, instruction compliance (preamble, echo, wrong script, over-long), term preservation and chrF. --append merges engines run at different times, --report-only rebuilds the report.
  • benchmark/engine_compare_report.md + engine_compare_results.json: this run (M2 Pro, 16 GB, 2 runs × 60 cases per engine).

Headline numbers (p50)

Ollama MLX (nvfp4) llama.cpp Metal (Q4_K_M) Qwen2.5-1.5B via llama.cpp (PR #27)
TTFT 160 ms 146 ms 40 ms
End-to-end 534 ms 542 ms 172 ms
Instruction compliance 98% 98% 90%
Numbers / names kept 92% 97% 89%
chrF 57.6 55.8 47.7

Same weights, different runtime: no measurable difference. The 1.5B model is fast but makes errors a caption translator cannot afford (38%→45% became 3.8%→4.5%, 五十万 became fifty thousand, several inputs echoed untranslated).

Note: in Ollama 0.40 both qwen3.5:4b and qwen3.5:4b-mlx are the same safetensors/nvfp4 weights on the MLX runner, so there is no Ollama-GGUF column; the second Ollama column is a repeat run. Memory: Ollama 4.1 GB resident at num_ctx 4096 (7.6 GB at its 32K default); llama-server 0.95 GB phys + 2.6 GB GPU.

🤖 Generated with Claude Code

Adds a 60-case eval set that mimics what on-device ASR actually emits
(short turns, unfinished drafts, no punctuation, fillers, numbers, names,
zh->en, context follow-ups) and a runner that scores any Ollama or
OpenAI-compatible server on streaming TTFT / end-to-end latency, generation
speed, instruction compliance, term preservation and chrF.

Results on an M2 Pro 16 GB with the same Qwen3.5-4B weights:
llama.cpp (Metal, Q4_K_M) and Ollama (MLX runner, nvfp4) are within noise
of each other (p50 end-to-end 542 vs 534 ms, TTFT 146 vs 160 ms, chrF
55.8 vs 57.6, terms kept 97% vs 92%). The Qwen2.5-1.5B model that PR #27
bundles is 3x faster but drops to 90% compliance, 89% terms kept and
chrF 47.7: it echoes inputs untranslated, turns 38% into 3.8% and
五十万 into fifty thousand.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-07T08:51:36.733328Z da5105e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da5105e178

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +147 to +149
for pat in VIOLATION_PATTERNS:
if pat.lower() in low:
problems.append(f"preamble:{pat}"); break

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish translated source text from added preambles

The substring check flags legitimate translations as instruction violations in the committed results. C03 starts with “okay okay”, so translating it as “好的,好的” is not an added preamble, yet both runs for every engine are penalized. Likewise, the 1.5B engine's G06 outputs contain “the translation” while translating the source's discussion of translation latency, not introducing an answer. These false positives distort the reported compliance comparison. Make detection source-aware or use case-specific expectations, then rescore the stored results.

Useful? React with 👍 / 👎.

Comment on lines +144 to +148
out = clean(output)
problems = []
low = out.lower()
for pat in VIOLATION_PATTERNS:
if pat.lower() in low:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check raw output for violations before cleaning it

When an engine emits thinking blocks or delimiter markers, clean(output) removes them before the violation checks run, making the <think> and 🔤 patterns ineffective. For example, both <think>I should translate this.</think>我们开始吧。 and 🔤我们开始吧。🔤 score as fully compliant for A01. This hides precisely the output-format failures the eval set claims to measure. Detect these violations on the raw response and reserve cleaning for reference and term scoring.

Useful? React with 👍 / 👎.

Comment on lines +165 to +169
for t in case.get("terms", []):
if t.lower() not in low:
missing.append(t)
for group in case.get("terms_any", []):
if not any(t.lower() in low for t in group):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match numeric terms as complete values rather than substrings

For numeric preservation cases, substring membership counts corrupted values as preserved. Using the supplied G03 case, “Last quarter our retention rate went from 138% to 145%.” receives terms_kept: 2 because it contains 38 and 45, despite changing both numbers. The same issue affects numeric alternatives in terms_any. Match complete numeric values with appropriate boundaries and explicit accepted representations so the reported numbers/names retention metric does not reward these errors.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant