AGPL-3.0-or-later · forever.

showcase · tier 2 · builders

Model bench

The same question fanned to three local models in parallel; a jq fan-in tabulates measured latency and length: no judge model, facts only.

Pick your default model on a measured table, not vibes: zero keys, zero spend. · conformance-gated in nika-spec ↗ · re-proven at every push

The whole file

5 tasks · 3 waves · the plan falls out of the bindings, nothing is scheduled by hand.

t2-model-bench.nika.yamlsource
nika: v1workflow:  id: model-bench  description: "One question → three local models → a measured comparison table"model: ollama/qwen3.5:4b # the house default · contenders below pick their own seatsvars:  question:    type: string    default: "Explain, in five lines, why a workflow should be audited before it runs."tasks:  ask_incumbent:    infer:      model: ollama/qwen2.5:14b # the quality bar · the seat to beat      prompt: "${{ vars.question }}"      max_tokens: 600  ask_challenger:    infer:      model: ollama/llama3.2:3b # another family · same size class      prompt: "${{ vars.question }}"      max_tokens: 600  ask_tiny:    infer:      model: ollama/qwen2.5:0.5b # the "is small enough?" probe      prompt: "${{ vars.question }}"      max_tokens: 600  tabulate:    with:      ask_incumbent_duration_ms: ${{ tasks.ask_incumbent.duration_ms }}      ask_incumbent: ${{ tasks.ask_incumbent.output }}      ask_challenger_duration_ms: ${{ tasks.ask_challenger.duration_ms }}      ask_challenger: ${{ tasks.ask_challenger.output }}      ask_tiny_duration_ms: ${{ tasks.ask_tiny.duration_ms }}      ask_tiny: ${{ tasks.ask_tiny.output }}    invoke:      tool: "nika:jq"      args:        input:          - model: "ollama/qwen2.5:14b"            ms: "${{ with.ask_incumbent_duration_ms }}"            answer: "${{ with.ask_incumbent }}"          - model: "ollama/llama3.2:3b"            ms: "${{ with.ask_challenger_duration_ms }}"            answer: "${{ with.ask_challenger }}"          - model: "ollama/qwen2.5:0.5b"            ms: "${{ with.ask_tiny_duration_ms }}"            answer: "${{ with.ask_tiny }}"        expression: >-          "| model | latency | chars | answer (first 160) |\n|---|---|---|---|\n"          + (map("| " + .model + " | "               + (if .ms == null then "cached" else (.ms | tostring) + "ms" end) + " | "               + (.answer | length | tostring) + " | "               + (.answer | gsub("\n"; " ") | .[0:160]) + " |")             | join("\n"))  persist:    with:      tabulate: ${{ tasks.tabulate.output }}    invoke:      tool: "nika:write"      args:        path: "./bench/model-bench.md"        create_dirs: true        content: |          # Model bench — same question, measured          ${{ with.tabulate }}          Facts only: latency from the run's clock, length in characters.          The answers are side by side — the quality call is yours.

nika inspect · engine 0.105.0 · vendored graph, never re-derived

7 declared edges · 3 waves · the plan falls out of the bindings