Repository navigation
bankML 0.4.2 — logprobs on Ollama's API, receipts over logprobs, max_tokens: 0; with 0.4.1
0.4.1 was never tagged (its gate run stopped mid-way), so this release carries it. From 0.4.1: a native run stops
when its client goes away; every GPU object is released on drop; the ternary kernel runs on the GPU bit-exact; CI on
aarch64; the console's SELF says who is speaking. From 0.4.2: Ollama 0.20's logprobs/top_logprobs on /api/chat
and /api/generate with llama-server b11192's entries; logprobs_sha256 on receipts; max_tokens: 0 and
num_predict: 0 as llama-server and Ollama read them; one theme and a dark-mode switch for the console and Savante;
Savante's exact calculator; install.sh restart. The batched GPU submissions and the F16 and eight-lane ternary GPU
kernels are written but unverified on the card, and stay for 0.5.0. Record: testing/results/0.4.2.txt.
Measured (2026-10-08, Bonsai-1.7B-Q1_0, against llama-server b11192)
logprobs_ollama_oracle_live19 / 19:/api/chatand/api/generate, streamed and not, entries and receipts.maxzero_oracle_live7 / 7: the sixmax_tokens: 0cases (recorded from llama-server: it samples one token) and
num_predict: 0answered as-1.logprobs_oracle_live(/v1) 14 / 14 unchanged;cargo test --release106 passed; clippy clean.- The oracle's own extraction of the logprobs array ended a string at
\"(its escape flag was recomputed before the
quote was checked), so a top-20 entry whose token is a quote or a backslash failed the receipt check (18 / 19). Fixed
inlogprobs_ollama_oracle.py; the receipt was right.
./install.sh restart and models.py restart
- A fresh
bankml serveon the model it serves now, after a build or a pull: the same pinned file, resources and
origin, the whole file hashed and verified again, then the UIs that were running. Before, nothing did this:
models.py use FILEon the file already served returns at once (its sha256 already answers), andinstall.sh modelstarts the default model, and only when nothing serves. Measured: 10 s on Bonsai-8B, pid replaced, verified. install.sh --helpprints up to its-hline instead of a fixed line range (two lines added had cut it short).- Fixed:
./install.sh stopstopped only the first port.pid_onfor a port where nothing listens (7874, the
LAN view, usually off) madegrepfail, and underset -euo pipefailthe script ended there without a word: 7873,
18093 and 18092 kept running.pid_onnow answers empty instead.
The interfaces: one theme, a dark-mode switch, a way back, and a calculator
- Console and Savante share one look (
sAGI/console/theme.css,sAGI/savante_theme.css): depth from the border
(an edge, an inset highlight, a near and a far shadow), glass surfaces over a faint wash, and a question field with
strong contrast against its page and its text. T mode is terminal green on black in both themes. Savante keeps its
own colours (paper, ink, gold) and a serif wordmark. - A ☾/☀ button in each bar flips light and dark; the system's choice is the default and a click is remembered per
browser (Gradio'sdarkclass on Savante;data-themeon the console). - A way back: Savante's masthead now carries the same Savante | bankML switch as the console.
- The DeltaVerse $ is the console's tab icon too (
/favicon.ico, the file Savante and view already serve). - Savante's calculator (
sAGI/calc.py, docs/modules/calc.md): exact arithmetic withouteval(ast, bounded;
integers and typed decimals exact,0.1 + 0.2is0.3). A Calculator panel beside the conversation (keypad,
terminal display, a tape of results); arithmetic in a question is computed and handed to the model with it, and
shown under the answer (🧮 … — exact), recorded in.historyundercalculator; a message starting with=is
answered by the calculator alone.testing/test_calc.py(48 checks) runs in the gate.
Logprobs on /api/chat and /api/generate (logprobs_ollama_oracle_live)
- Ollama 0.20's request fields:
logprobs: trueandtop_logprobs0–20. Out of range is a 400 with Ollama's own
message, before any model is loaded; withoutlogprobs,top_logprobsis ignored, as Ollama ignores it. - The answer takes the per-token path
/v1takes (NativeChat::run_steps_while), so the entries are llama-server
b11192's: the same tokens, texts, bytes and 32-bit floats, written in Ollama's shape (noid;bytesand
top_logprobsleft out when empty). Not streamed, the list is the top-levellogprobs; streamed, each line carries
its tokens' entries. top_logprobs: 0is the chosen token only, computed as llama-server'sn_probs1: the chosen token's float depends
on how many top tokens are sorted, so the oracle compares it with llama-server's top-1 answer.- Requests without logprobs keep the answer path they had.
- Asked for by mindX: its Augur (calibrated yes/no forecasts) reads P(yes) from one forward pass instead of sampling
three answers.
max_tokens: 0 as llama-server answers it; Ollama's num_predict: 0 (maxzero_oracle_live)
- llama-server b11192 checks the generation limit only once a token is generated (
n_gen > 0), somax_tokens: 0
still samples one token, and with logprobs that token's entry is the next-token distribution after the prompt.
bankML stopped before the first token and answered nothing; it now checks the limit as llama-server does.
Recorded against llama-server (greedy, seeded, plain, streamed) before it was accepted. - Ollama's runner limits only when
numPredict > 0(0.20.0), sonum_predict: 0means no limit, as -1 does; bankML
had read it as zero tokens.
logprobs_sha256 on the receipt
response_sha256hashes the text, not the numbers a client may act on. An answer with logprobs now also carries
logprobs_sha256: the sha256 of the logprobs array exactly as written (the body's substring when not streamed; when
streamed, every entry the chunks carried, as one array). On/v1and on Ollama's API.- Without logprobs the receipt is unchanged, byte for byte.
receipts_cover_logprobs_as_writtenandlogprobs_as_ollama_asks_and_answers(unit); live in the oracle above,
which also checks the/v1receipts.
Fixed
- Savante's aivatar card opened trapped inside the side column (318 × 1075 px instead of the whole window): the
column'sbackdrop-filtermade it the containing block of the card'sposition: fixedmodal. The side column
now carries no backdrop-filter or transform, and has a denser surface instead. Its widgets also stay legible while
Gradio refreshes them; they had dimmed to 20 % on a busy machine. Checked in a browser: the card now spans the
window (1366 × 900, a 920 px card).
0.4.1 — never tagged; released in 0.4.2 — a request whose client has gone stops; GPU objects released; ternary on the GPU; CI on ARM
The first steps towards 0.5.0 (hardware), and the two 0.4.x items.
- A native run stops when its client goes away, and keeps what it computed.
- Every GPU object is released on drop. The test found an instance leaked per open.
- The ternary kernel runs on the GPU bit-exact, and
bankml gpu --verifychecks it. - CI builds and tests natively on aarch64.
- The console's SELF says who is speaking, in two lines.
The console's SELF: who is speaking, then one line of measurements
- An A/B by the ultimate-bankml-ui session (bankML 0.4.0, Bonsai-8B, "who are you?", three fresh conversations each,
every receipt ✓): with SELF's first line "the model I am running: Bonsai-8B …", bankML said "I am bankML" 1 of 3
times and "I am … Bonsai-8B" 2 of 3; with "I am bankML 0.4.0. The model file I run is Bonsai-8B (sha256 …): it is
what I compute with, not who I am", 3 of 3. The console's SELF now opens with that line. - The nine measurement lines are one line, each value still with its unit and "not measured" where it was not: the
block a question carries is two lines instead of ten, a smaller prefix to recompute whichever turn it rides on
(the same session measured why that matters for the prompt cache).
The ternary kernel on the GPU, bit-exact (gpu_q2_0_mat_vec_bit_exact, bankml gpu --verify)
q2_0_mat_vecis bankML's own SPIR-V, one invocation per row. Per block it takes the two 32-element integer sums,
thensumi = fma(d1₁, s₁, d1₀·s₀)andsumf = fma(d0, sumi, sumf), the shipped ggml's float order. Both FMAs are
built exactly, becaused1·scan need 25 bits and the Vega 3's driver does not fuseFma.- Bit-exact against
q2_0::vec_dot_refon every row of the 8B model's shapes on the Vega 3: 64×128, 1000×512,
4096×4096, 1024×12288 and 12288×4096, 18,472 rows in all. bankml gpu --verifynow runs it after the 1-bit kernels, in both regimes (uniform and layer-shaped), and refuses a
card that fails either.- One rule the test found: an activation whose f16 scale overflows makes the exact FMA give NaN where the CPU gives
inf, so such a product stays on the CPU. No real activation has one. - Not yet used for inference. At one invocation per row it takes 3.5–4.7 ms per 4096×4096 or 2048×12288 product,
slower than the CPU's ternary kernel; a layout with lanes per row (asq1_0_mat_vec8) comes next.
CI on ARM, natively
.github/workflows/ci.ymlruns a second job on GitHub'subuntu-24.04-arm: the build, the unit tests, clippy with
-D warningsand the guard, on real aarch64. Every kernel there takes its portable path, which the unit tests check
against the same reference models the x86 kernels are proven against. The toolchain is now rust-toolchain.toml's,
not a stale 1.95 pin; the x86 job adds the SPDX and console checks.- The first run found an ARM difference.
/proc/cpuinfothere has nomodel nameand nocpu MHz, so the status
page's CPU readings now fall back toHardware, the device tree's model and the implementer and part codes, and to
each CPU'scpufreq. The x86-only variables that warned on ARM are marked so; both targets are clippy-clean.
GPU objects are released on drop (gpu_objects_are_released_on_drop; the first 0.5.0 item)
- Found by the 2026-10-06 audit:
Gpudestroyed nothing on drop (device, buffers, pipelines), so dropping and reopening
a worker leaked its card memory; andunsafe impl Sync for Bufferlet twoGpus on two threads write one buffer. - Every object is now released on drop. The Vulkan instance, the device and its entry points live in one shared
Device, held by theGpuand by everyBufferandPipelinemade on it. The device, and then the instance, go
with the last of them, so nothing is used after its device is gone. ABufferis no longerSync, andwriteand
submitrefuse a buffer or pipeline of another device. - The new test found what the audit had not: each
Gpu::openalso made a Vulkan instance and never destroyed it.- Measured with the driver's own counters (sysfs VRAM + GTT), over 30 rounds of open, five 16 MB buffers, a
pipeline, a run and drop. - Before, card memory grew 65 MB, about 2.2 MB a round; after, 221.4 → 223.4 MB, within the desktop's own noise.
- Measured with the driver's own counters (sysfs VRAM + GTT), over 30 rounds of open, five 16 MB buffers, a
- The same bits: the GPU kernel oracle and
bankml gpu --verifyare bit-exact on every row, and the 1.7B forward
oracle is 840 / 840 with the card computing 35 % of each 1-bit matrix.
Abort a native run when its client goes away (testing/disconnect_oracle.py, 6 / 6)
- Found by the ultimate-bankml-ui session under contention:
servenoticed a closed connection only when a write
failed, so an abandoned request's whole prefill (and, unstreamed, its whole answer) ran for nobody, minutes per
request on the laptop, with the one slot held. Native::complete_whileasksalive()between prefill micro-batches (Weights::prefill_while: the same 512-token
micro-batches, so the same bits) and before each generated token.serve::client_aliveis a non-blocking peek at the
client's socket: end of file or a reset means gone, anything else (bytes waiting, nothing yet, any doubt) means
there./v1/chat/completions(streamed or not) and Ollama's/api/chatand/api/generateall use it.- A stopped run logs where it stopped (
bankml: the client went away; stopped after N of M prompt tokens (kept in the slot)), records no answer, and leaves the slot holding exactly what it computed: the next request reuses it
(cache_n> 0) and its answer is still an empty slot's. - Checked live: an abandoned 2,294-token prompt stopped after its first micro-batch; the reuse and its answer; an
abandoned 400-token stream stopped early;/api/generateabandoned the same way. No answer changed:
oracle_native_serve_o4,cache_prompt_false_is_an_empty_slot, the session (14 / 14), logprobs (14 / 14) and
Ollama-shape (9 / 9) oracles all pass.disconnect_oracle_liveis in the release gate.
Record: testing/results/0.4.2.txt — one run of the full release gate, every stage passed (2026-10-08, the laptop), then the fixes folded in after it checked again.
made with luv.pythai.net