How to run Ornith-1.5-35B-A3B MiniPlus V2.1 on RX 9060 XT 16 GB

Retrieval is flawless: 30/30 across the full 131,072-token slot, prefill decaying smoothly from 1,011 to 465 tok/s with no cliff at the edge. Decode at 57.9 tok/s sits about 4 % under the card’s own recorded 60.5 tok/s for this profile and trails every CUDA-side row on the board (74.9–151 tok/s), so read the rate as this box’s ceiling. The code deliverable is the only row here whose three pipelines all run after repair — 0/3 as filed, 3/3 with targeted fixes — which makes it a long-context drafting partner, not an unsupervised Elixir coder.

Evaluated by tizzyt566/Ornith-1.5-35B-A3B-NVFP4-GGUF · Written by Isaak Tsalicoglou

Results

LoadWallPPTGPredFinishQuality
182.73 s 1227.33 s 819.65 57.92 33468 stop 2/5

Medians of three runs (wall 1245.54 / 1227.33 / 1052.47 s); per-run quality 3 / 2 / 2. pp and tg are llama-server’s own timers and carry no contention; wall does — runs 1–2 paid about 650 s of GPU swap. Thinking tax 9.4× / 10.2× / 7.3× reasoning-to-content characters; whole-answer dup-8 57.6 / 56.7 / 55.1 %. Delivered-pipeline gate 0/3 as filed, 3/3 after repair.

Needle retrieval

Window
131072
Depth
13,107 → 131,008 (every 10 % of the slot)
Result
hit
pp/tg
30/30, no cliff; pp 1,011→465 · tg 57.5→42.8 across the ladder
Anchor
tesseract-1c7d3f-vault

Quality of result

Adversarial review before code 5/5 Median run: seven gap → resolution lines, each locking one decision, one of them grounded by a search on :erlang.memory/0 semantics before writing. Run 1 writes eight with no filler; run 3 repeats its duplicate-start policy twice and scores 4.
Public API present (put/get/delete/stats) 3/5 start_link/1,2, put/3,4, get/2, delete/2, stats/1 in every run, with typespecs and documented return shapes. The median run stores the payload under a private tag and hands the tag back from get/2; run 1’s surface is the clean one and scores 5.
TTL + periodic eviction + named instances + hit/miss stats 3/5 Timer loop, named instances and hit/miss counters exist in all three runs. The median run’s sweep calls Map.filter/2 with a 2-arity function and raises FunctionClauseError on the first tick; run 1’s recursive send_after loop is sound and scores 5.
Idiomatic OTP / would it compile? 3/5 use GenServer with sane callback shapes throughout. The median run compiles with warnings and breaks at runtime; run 1 stops at kv_1.ex:25 with a SyntaxError; run 3 compiles and then calls two stdlib functions that do not exist, so it scores 2.
Tests shipped, not promised 3/5 7 / 9 / 14 real ExUnit cases covering TTL expiry, per-put TTL, stats and named instances. None runs as filed — the median suite’s six assigns uses do not resolve; all three pass after repair (7/7, 9/9, 14/14).
Finish reason + thinking tax 5/5 Clean stop in all three runs. Reasoning ran 9.4× / 10.2× / 7.3× the content characters — 109,662 / 113,921 / 74,563 against 11,719 / 11,142 / 10,277.

Scale: 5 strong · 4 capable · 3 mixed · 2 weak · 1 broken.

Prompt

This run used v5 (The benchmarking prompt) — It answers a single Elixir-specific question, so YMMV.

Elixir code smells 11

  • kv_1.ex:25 — run 1 — start_link([{name: name} | opts]): a single-pair brace inside a list is not a keyword list; SyntaxError. Fixed to {:name, name}.
  • kv_2.ex:111,117 — run 2 — put stores {:value, expire_at}; get returns the tagged tuple to the caller, breaking the payload contract.
  • kv_2.ex:157-161 — run 2 — Map.filter called with a 2-arity function; the API takes a 1-arity function over {key, value} → FunctionClauseError.
  • kv_2.ex:150 — run 2 — :erlang.memory() returns a map against a non_neg_integer() spec; :erlang.memory(:total) is the integer form.
  • kv_2.ex:180,184,187 — run 2 — do:-oneliner reply clauses with ambiguous binds; salvage spells out {:reply, :ok, _}.
  • test_2.exs:12,18,23,30,63,94 — run 2 — assigns undefined six times (LiveView habit); ExUnit binds setup data to the test/3 context variable, which needs the comma.
  • kv_3.ex:70,98,111 — run 3 — System.time/1 does not exist; use System.convert_time_unit(:os.system_time(:native), :native, :millisecond).
  • kv_3.ex:133 — run 3 — Map.reduce/3 does not exist; Map.to_list/1 |> Enum.reduce/3 does.
  • kv_3.ex:53 — run 3 — stats/1 on an unregistered name EXITs instead of raising the ArgumentError its own test asserts; validate_name accepts any atom.
  • test_3.exs:5 — run 3 — helper start/1 returns {:ok, pid}; every GenServer.call gets a tuple and all 14 tests fail.
  • test_3.exs:34 — run 3 — on_exit stop in the duplicate-start test dies with the linked server; the linked-start design is the root cause.

Defects 4

  • kv_1.ex — [{name: name} | opts] — a single-pair brace inside a list is not a keyword list (SyntaxError, Elixir 1.20.4 / OTP 29 — run 1 module FAIL; one-line fix → 7/7 pass)
  • kv_2.ex — tagged payload round-trip, map-vs-integer memory, 2-arity Map.filter (compiles with warnings; evict raises FunctionClauseError at runtime — run 2 module broken)
  • kv_3.ex — System.time/1 ×3 and Map.reduce/3 — neither exists (compile warnings; runtime UndefinedFunctionError — run 3 module broken)
  • test_3.exs — helper returns {:ok, pid} instead of the pid (0/14 as shipped; helper fixed → 14/14)

Host

QuantAPEX-I MiniPlus V2.1
Hardware1× Radeon RX 9060 XT 16 GB (PCIe 3.0 x16), 48 GB DDR4-3200
Splitsingle card — no split; Vulkan backend
Runtimellama-server 0.7.0-dev (build 11945, commit d05706b4f), Vulkan
HarnessGrok Build (build label not reported by the harness)
Tested2026-11-01
Weightshuggingface.co/IsValorum/Ornith-1.5-35B-A3B-APEX-I-MiniPlus-V2.1-GGUF

Reproduce

Single 16 GB Radeon card on a llama.cpp Vulkan build. The file is 15.2 GB, so two expert layers stay on host RAM (-ncmoe 2) and K/V is q8_0; --load-mode none reads the whole file at boot, which is why the 182.7 s load is a genuine cold number and not an mmap first-touch. The actual spawn line carried the common macro’s sampler (--temp 0.6 --top_p 0.95 --top_k 20 --min-p 0.05) and then the profile override; llama.cpp keeps the last value, so the line above is the effective configuration. The MTP head is in the GGUF but is not wired, therefore: no --spec-type draft-mtp, no drafter K/V. The Q8_0 projector runs on host RAM (--no-mmproj-offload) and was unused, since every prompt was text. The GPU serves production workloads around the clock, so while pp/tg are llama-server’s own timers and carry none of the swap tax, wall_s carries all of it. Answer caps: 130,000 tokens for the benchmarking run, 512 for needle answers. --host/--port omitted.
llama-server \
  -m Ornith-1.5-35B-A3B.APEX-I-MiniPlus-V2.1.gguf \
  -c 131072 \
  --parallel 1 \
  -ncmoe 2 \
  --load-mode none \
  -b 4096 \
  -ub 1024 \
  --threads 4 \
  --threads-batch 4 \
  -ngl 999 \
  --split-mode none \
  -fa on \
  -ctk q8_0 \
  -ctv q8_0 \
  --kv-offload \
  --cache-ram 16384 \
  --no-context-shift \
  --jinja \
  --reasoning on \
  --reasoning-preserve \
  --cont-batching \
  --dry-multiplier 0.8 \
  --dry-base 1.75 \
  --dry-allowed-length 2 \
  --dry-penalty-last-n 0 \
  --temp 1.0 \
  --top_p 0.95 \
  --top_k 20 \
  --min-p 0.0 \
  --presence_penalty 0.0 \
  --chat-template-file froggeric-qwen-chat-template.jinja \
  --mmproj mmproj-Q8_0.gguf \
  --no-mmproj-offload \
  --image-min-tokens 1024

Three v5 runs, each capped at 130,000 tokens (near the full 131,072 served slot). All three finished with review, module and tests. However, none of them runs as filed.

Retrieval is perfect: thirty hits in thirty tries across the ten context-depth rungs. Prefill decays smoothly from 1011 to 465 tok/s as the window fills, and so does decode, from 57.5 to 42.8 tok/s; no cliff or flat-lining of either pp or tg at the edge.

Decode at 57.9 tok/s sits about 4% under the card’s own recorded 60.5 tok/s for this profile, so nothing regressed on this box. It also trails every CUDA-side row on the board, which measure 74.9 to 151 tok/s. Read the rate as this card’s working ceiling.

Methodology, disclosed: the GPU serves other models around the clock, and live traffic interleaved with this benchmark. Runs 1 and 2, plus most of the 10–70 % needle repeats suffered under model-swapping, but this impacts only wall-clock time. The rest of the benchmark parts ran undisturbed by model-swapping.

The catch

The delivered-pipeline gate is 0/3 as filed, with three separate wound shapes:

  • run 1 kv_1.ex:25 — start_link([{name: name} | opts]): a single-pair brace inside a list is not a keyword list. One line to {:name, name} and the module compiles.
  • run 2 kv_2.ex:111,117 — put stores {:value, expires_at} and get hands the private tag back to the caller; behind it a 2-arity Map.filter/2 that raises on the first sweep (:157–161) and :erlang.memory() returned against an integer spec (:150).
  • run 3 kv_3.ex:70,98,111,133 — System.time/1 three times and Map.reduce/3 once, neither of which exists; stats/1 on an unregistered name exits instead of raising the ArgumentError its own test asserts (:53).

The test files carry their own two: run 2’s suite uses an undefined assigns six times (test_2.exs:12,18,23,30,63,94), and run 3’s helper returns {:ok, pid} to every caller (test_3.exs:5).

After targeted repair, the suites pass 7/7, 9/9 and 14/14. Run 1 is a one-line fix, whereas runs 2 and 3 take a handful of sites each, and run 2’s payload-tag break is semantic rather than cosmetic.

Run 1’s review is the cleanest of the three: eight gap-to-resolution lines, each pinning one decision (signatures, the start_link/2 delegation, TTL units and expiry semantics, timer-driven eviction, validation policy, the memory estimate, O(1) lookups and named instances) with no filler. Run 2 grounds one decision with a search on :erlang.memory/0 before writing. Run 3 repeats its duplicate-start policy across two of its ten lines.

Representative output

Run 1’s TTLStore — the start_link surface and the eviction loop, trimmed. Line 25 is the defect:

defmodule TTLStore do
  use GenServer

  def start_link(opts) when is_list(opts) do
    GenServer.start_link(__MODULE__, opts, registration(opts))
  end

  def start_link(name, opts) when is_atom(name) and is_list(opts) do
    start_link([{name: name} | opts])
  end

  @impl true
  def handle_info(:evict, state) do
    now = now_ms()

    entries =
      state.entries
      |> Enum.reject(fn {_key, %{expires_at: exp}} -> now >= exp end)
      |> Map.new()

    {:noreply, %{state | entries: entries} |> schedule_evict()}
  end

  defp registration(opts) do
    case Keyword.get(opts, :name) do
      nil -> []
      name when is_atom(name) -> [{:name, name}]
      other -> raise ArgumentError, "name must be an atom, got: #{inspect(other)}"
    end
  end
end

Route

Use it as a long-context general driver and a drafting partner on that GPU. Keep a compile gate between it and any Elixir repo: every run needs a repair pass before it builds, and one of the three runs here contained a real semantic break. The 131072-token context window is the whole window for this GPU’s 16 GB of VRAM, in order to offload only 2 expert layers to system RAM, and thus not drag tg down. The native 262144-tokens context window will be benchmarked separately.

Have something worth building?

A paragraph about what you are working on is enough to start.

Say hello