Skip to content

et-backend: F32 vecdot GEMV + matrix-engine GEMM - #29

Draft
RehanQasim-dev wants to merge 95 commits into
aifoundry-org:etfrom
RehanQasim-dev:upstream-f32-gemv-gemm
Draft

et-backend: F32 vecdot GEMV + matrix-engine GEMM#29
RehanQasim-dev wants to merge 95 commits into
aifoundry-org:etfrom
RehanQasim-dev:upstream-f32-gemv-gemm

Conversation

@RehanQasim-dev

@RehanQasim-dev RehanQasim-dev commented Jul 23, 2026

Copy link
Copy Markdown

Overview

Improves the ET backend's F32 MUL_MAT path for both decode (GEMV) and prefill (GEMM):

GEMV (decode, N <= 2): Stripes output elements across every hart of all 32 shires instead of blocking work into 16-element chunks, which only filled 8 shires for a typical decode GEMV. Adds a register-resident f32 row-dot helper with L2 prefetch for the streamed weight row, and stages the reused B activation vector into per-shire L2 SCP. Also fixes the matrix-engine GEMV dispatch check, which compared src1->ne[0] instead of src1->ne[1] and so never actually caught the decode case, sending it to the matrix engine kernel where it stalled on a single/near-single-column matmul.

GEMM (prefill, N > 2): Adds a double-buffered producer/consumer F32 matrix-engine kernel — hart 1 transposes weights into double-buffered L2 SCP while hart 0 runs tensor-engine compute — with a weight-reuse path and software prefetch for both weights and activations.

Dispatch now routes N <= 2 to the vecdot GEMV kernel and N > 2 to this matrix-engine GEMM kernel.

Additional information

Performance (Llama-3.2-1B-Instruct F32, ET-SoC-1):

Prefill t/s

N et optimized speedup
100 210.11 185.72 0.88x
220 299.64 344.39 1.15x
512 345.19 526.19 1.52x
700 320.69 429.39 1.34x
900 327.94 480.64 1.47x

Verified with llama-bench on ET-SoC-1 hardware (Llama-3.2-1B-Instruct F32), comparing this branch ("optimized") against unmodified et ("et") at the same prompt sizes used in the Q4_0/Q8_0 matrix-engine PRs.

Note: N=100 is a regression (0.88x), reproduced twice (4 repetitions each). The new weight-reuse matrix-engine kernel appears to have per-call setup overhead that doesn't amortize until N is large enough — gains are consistent and substantial from N=220 upward, but the smallest prefill size is currently worse than baseline et. Flagging for review before merge; may need a higher matrix-engine dispatch threshold for F32 than the current N > 2, or further tuning of the small-N case in the kernel itself.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES - the optimization strategies and design decisions are my own. AI assisted with understanding the hardware reference manual, some pieces of code implementation and guided debugging. I have thoroughly reviewed the code.

@github-actions github-actions Bot added the ggml label Jul 23, 2026
taronaeo and others added 29 commits July 23, 2026 20:32
* args: overhaul mmap/mlock/dio into single arg

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: update docs with llama-gen-docs

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* chore: satisfy code quality

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* args: make the `+` sign an actual modifier now

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* chore: general code clean up + comments

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: fix deprecated flags support

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: quick sanity check

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* bench: sync llama-bench argument parsing

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* fix: bugfix variable behaviour + llama-bench lm column size

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: inverse commands should do the opposite instead of doing nothing

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* bench: fix incorrect dash

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* bench: fix missing modifiers for deprecated flags

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* llama: switch back to thread_local

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: switch back to single enum

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: update arg docs

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* chore: fix missing `mlock` from llama_load_mode_from_str + cleanup llama-bench

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* llama: fix mlock not activating

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: add deprecation warning when old and new flags are combined

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* arg: cont add comment for todo in the future

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: sync with upstream

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: re-sync with upstream again

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

---------

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
…ggml-org#26049)

* hex-l2: use dirty ranges for flushing

* hex-l2: simplify range based flush logic

* hex-l2: optimize dirty range scans

* hex-hvx: support for reduce_max_i32

* hex-mm: optimize fused MUL_MAT+ADD to use vtcm for bias when it fits

* hex-mmid: optimize mmid row-mapping generation

* hex-mmid: optimize mmid row-mapping generation

* hex-mmid: optimize mmid row-mapping generation (round2)

* hmx-mm: optimize output proc by tiling (col-chunking)

* hex-fa: start the next q dmas a bit earlier

* hex-fa: prefetch Q even earlier

* hvx-fa: optimize softmax to keep things in hvx registers

* hex-fa: hoist const register init in softmax loop

* hmx-fa: kick off next-qkv DMAs before o-proc

* hmx-fa: hoist various checks out of the inner loop

* hmx-fa: adjust the cost model to better balance softmax work across hvx threads

* hmx-fa: overlap diag rescale build with last HMX task

* hmx-fa: optimize idx update in output proc

* hmx-fa: unroll the softmax loops for improved perf

* hmx-fa: overlap qk-dot with softmax, double-buffer p and s tiles

* hex-trace: double the default number of trace entries

* hex-trace: add trace events for opbatch and buffer mgmt

* hex-trace: overhaul tracing to simplify runtime event handling and support opbatch stats

* hex-trace: replace ascii timeline diagram with pipeline bubbles detector

* hex-trace: handle missing start/stop events

* hex-dma: always log stop/start trace events even for dummy dmas

* hex-scripts: fix flake warnings
* skill: add new model

* add common pitfalls

* add code review skill

* nits

* add codeowners

* add security review section

* mention about skills in agents.md

* add design review

* exclude ggml-gh-bot

* Apply suggestions from code review

Co-authored-by: Piotr Wilkin (ilintar) <piotr.wilkin@syndatis.com>
Co-authored-by: Xuan-Son Nguyen <thichthat@gmail.com>

* conversion-time weight modification

* nits

---------

Co-authored-by: Piotr Wilkin (ilintar) <piotr.wilkin@syndatis.com>
* support "reasoning_effort": "none" in OAI API

* handle reasoning.effort: "none" in OAI responses API

* clarify non-"none" values of reasoning_effort have no effect

* use json_value instead of body.at
)

* ui: fix MCP server display name conflicts in tools lists

Tool groups were keyed by display label so two servers reporting
the same name broke the keyed each blocks and only one was visible.
Key rendering, expand state and toggles by the stable server id
instead, and suffix duplicate labels with a counter in config order.

* ui: customizable MCP server display name with autofill

Add a display name field to the MCP server form, add and edit alike.
The custom name takes precedence over the server-reported one, so two
servers reporting the same name can be told apart; clearing the field
returns to the automatic label. In the add dialog a debounced preview
handshake prefills the field with the server-reported name: a manual
edit freezes the autofill, stale responses are discarded, failures
stay silent, and an unedited prefill is not persisted so the label
keeps following the server.

* ui: fix recursive fetch passthrough in the client test setup

The original fetch was captured inside beforeEach, where it is the
previous test's spy since vi.spyOn returns the existing one, so the
default passthrough recursed on itself for any URL outside the
mocked set. Capture the real fetch once at module load.
* Start building graph - reuse deepseek32

* Enable kv cache and rotation for glm_dsa architecture

Just follow Deepseek 3.2 for now.

* Reuse prev_top_k for "shared" indexer layers

* GLM 5.2 uses LLAMA_ROPE_TYPE_NORM for the indexer.

This is transformers' `apply_rotary_pos_emb_interleave`

* Default indexer types to GLM pattern

Previous converted GGUFs like https://huggingface.co/unsloth/GLM-5.2-GGUF write indexer weights to _all_ layers, even if they are only required for "full" types. This PR relies on a new key "%s.attention.indexer.types"; if absent, it will use the default GLM 5.2 schedule as defined in https://huggingface.co/zai-org/GLM-5.2/blob/main/config.json#L26.

Note that conversion is not saving this key yet.

* Save indexer types to gguf, restore on load

* Use ggml_lightning_indexer when cparams.fused_lid

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>

* GLM 5 and 5.1 use full indexers

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>

* Fix indentation

* Ensure array is zero-filled

* Prefer explicit std::fill

* Assert prev_top_k exists for shared indexer

---------

Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>
* ui: remove viewport fade in and smooth autoscroll bottom snap

fadeInView mounted every message and markdown block at opacity 0 and
relied on an IntersectionObserver to reveal it. When the observer never
fires (blocks mounted offscreen during long agentic loops) the content
stays invisible forever while still present in the DOM. Remove the
action, its orphaned isElementInViewport util and all call sites:
blocks now render visible immediately.

AutoScrollController.scrollToBottom defaulted to behavior smooth and is
invoked every 100 ms while streaming. Each tick restarts an easing
animation toward a moving scrollHeight, producing a random elastic bump
of a few pixels when the user reaches the bottom and autoscroll
reengages. Default to instant scrolling; the user facing scroll down
button keeps its smooth behavior.

* ui: skip rendering of offscreen chat messages via content-visibility

Apply content-visibility auto with contain-intrinsic-size to chat
messages so the browser skips layout and paint for messages outside
the viewport. The DOM stays complete: component state, find-in-page,
text selection, and the mutation based autoscroll are unaffected, and
browsers without support simply ignore the properties.

* ui: remove conversation switch fade

Switching conversations faded the message list out and in over 500 ms
plus a 300 ms route delay, deferring the message refresh behind two
requestAnimationFrame calls. Remove the fade, its navigation hooks and
dead state, and refresh messages directly so switching is only bound
by actual render time.

* ui: describe present behavior in comments and drop unused parameter

* ui: anchor the context gauge popup to the form with plain CSS

The stats card was portaled to body and repositioned in script on
every ancestor scroll event, trailing the page by one frame while
streaming. Render it as an absolutely positioned sibling of the input
box inside the already relative form, so nothing runs during scroll.

The card sits just above the dial, centered on it and overlapping the
textarea, from a single measurement of the dial center and top taken
when it opens; the dial and the card share the same positioning frame,
so the values stay exact while the card is open. Mouse pointers open
on hover with a grace delay to reach the card, touch pointers toggle
on tap, any press outside the card and the dial closes it, and Enter
and Space toggle from the keyboard. The card lives outside the input
box because its overflow-hidden and backdrop-filter would clip any
positioned descendant.

* ui: extract context gauge popup constants and relocate its state store

Move the placement values and the close grace delay to
lib/constants/context-gauge-popup.ts, matching the auto-scroll
constants layout, and move the popup state module from the component
folder to lib/stores where runes modules live in this codebase.

* ui: declare the context gauge popup card ref as $state
* performance harness - the empirical root

Assisted-by: Claude Opus 4.8

* 210.36ms -> 2.67ms per streamed token

Assisted-by: Claude Opus 4.8

* 11.58ms -> 0.62ms per streamed token

Assisted-by: Claude Opus 4.8

* 22.02ms -> 3.33ms per streamed token

Assisted-by: Claude Opus 4.8

* 3.07ms -> 1.36ms per streamed token at 40 messages

Assisted-by: Claude Opus 4.8

---------

Co-authored-by: Zach Winter <dmtommy@icloud.com>
…et sampler (ggml-org#25544)

* common : extract trie/ac to a separate file

* common : support multiple token sequences in the reasoning budget sampler

* common/trie : return matched word index

* common/trie : rename "word" to "pattern"

* common/reasoning-budget : expose matched end sequence

* common/sampling : replay end sequence when reasoning budget is done

* cont : update to use multiple end sequences

* cont : clean up
…gguf_writer_base (ggml-org#25867)

Without a virtual destructor, deleting a derived object through a
base-class pointer only invokes the base destructor, skipping the
derived one.
Signed-off-by: Adrien Gallouët <angt@huggingface.co>
… in generation_settings (ggml-org#25830)

These two parameters were overlooked when task parameters were being JSONized
within `generation_settings` and have been added. A regression test has been
added to prevent the problem from recurring and it passes.

Fixes ggml-org#25803
The INI parser creates an implicit default section for top-level metadata.
After reserved keys such as `version` are skipped, that section can have no
model options but was still added and exposed in router mode.

Skip only the empty implicit default while preserving real default presets,
named presets, and the global `[*]` settings.

Signed-off-by: JS van Dijk <267467744+hogeheer499-commits@users.noreply.github.com>
Co-authored-by: JS van Dijk <267467744+hogeheer499-commits@users.noreply.github.com>
* hexagon: add IM2COL op

Add Hexagon IM2COL support targeting only patch-embedding convolutions.

* hexagon: im2col refactor and cleanup

* hex-im2col: instrument and update im2col.

* hex-im2col: add local htp_vtcm_layout computation.
* move server_pipe to common

* init impl

* vendor: update subprocess.h

* add server_mcp_stdio

* stderr drain

* server_mcp_transport

* server_mcp_stdio is now framing-only, no json

* internal/mcp-stdio: integration + tests + fixes (ggml-org#26075)

* server-mcp: harden transport and wire up the tool integration

Builds on the transport/manager architecture (server_mcp_transport + server_pipe)
with the hardening and integration the draft did not yet have.

Hardening:
* Reader and stderr pumps are polled (running-aware) instead of blocking on a read
  that only ends at EOF. subprocess_terminate() SIGKILLs only the direct child, so a
  grandchild the MCP server spawned that inherited the pipe would otherwise keep the
  write end open and hang teardown (both warmup shutdown at startup and process
  shutdown). The writer is likewise non-blocking + polled.
* Windows: resolve the command through PATHEXT so "npx" (npm ships npx.cmd, never
  npx.exe) spawns, matching POSIX's PATH search; and enumerate the parent environment
  as UTF-8 (GetEnvironmentStringsW) instead of the active code page.
* server_pipe gains an opt-in max_size (default unbounded, so the router's streaming
  use is unchanged); the MCP reply queue uses it so a server that streams unsolicited
  notifications between requests cannot grow it without bound.

Integration:
* --mcp-servers-config / --mcp-servers-json flags; enabling MCP restricts default CORS
  to localhost, same as --tools.
* MCP tools are exposed through /tools (and chat-completions) as <server>_<tool>,
  skipping names that collide with a built-in or another MCP tool.
* Manager lifecycle wired into llama_server(): warmup at start, shutdown() from the
  signal handler before the HTTP server drains, blocking teardown in clean_up().
* SIGPIPE ignored so a child dying mid-write yields EPIPE rather than killing us.

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>

* server-mcp: add MCP test suite with grandchild deadlock regression test

21 tests over the /tools endpoint: tool discovery/invocation, timeouts, crash
recovery and respawn cooldown, warmup partial failure, malformed and batched
notification+response output, tool-definition shape, and prompt shutdown during a
slow call.

The last test spawns an MCP server that leaves a grandchild inheriting its
stdout/stderr and asserts the server both starts and stops promptly. Verified it
fails (5s SIGKILL fallback on a deadlocked reader-join) when the pump is made to
ignore the running flag, and passes with the polled reader.

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>

* clean up

* clean up 2

* even stricter life cycle

* nits

* nits 2

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>

* fix some edge cases

* fix last_error data race

* fix response schema + docs

* server: fix MCP zombie leak and timeout-induced transport teardown

join_pumps() never reaped the child, leaking one zombie per spawn:
call subprocess_join() before subprocess_destroy().

A per-call timeout permanently closed from_server and got a healthy
transport evicted: add close_on_stop to server_pipe::read() and pass
false from send_rpc(), where should_stop is a per-request deadline
and a late reply is already skipped on id mismatch.

Also drop the unreachable disconnect cancellation in
server_mcp_tool::invoke(): support_stream is false, st is always null.

(cherry picked from commit e6de1ec)

Assisted-by: Claude Opus 4.8

* server: make MCP test fixtures JSON-RPC 2.0 compliant

Add the missing notification guard to mcp_malformed_server.py and
mcp_burst_server.py (the latter treated id 0 as a notification and
replied to unknown ones; its notification table is now unused).

Return -32602 instead of -32601 for unknown tools: tools/call is a
valid method, the tool name is the invalid parameter.

Also fix the test module docstring: tools are named <server>_<tool>.

(cherry picked from commit 74a08e8)

Assisted-by: Claude Opus 4.8

---------

Co-authored-by: Piotr Wilkin (ilintar) <piotr.wilkin@syndatis.com>
Co-authored-by: Pascal <admin@serveurperso.com>
* Fix emscripten compilation with openmp

* Separate wasm job to its own workflow

* Add flags necessary for newer emsdk

* Just disable openmp

* Update triggers
…nd (ggml-org#26099)

The context gauge card starts monitoring like the dial does, because
its own processing state instance only follows the live stream while
its monitoring flag is set. It also gets back the text-sm and ring
classes the removed hover card wrapper used to inject, which restores
its layout. Routing to a conversation now lands at the bottom
instantly and keeps the pin one frame at a time until the page height
settles, since content-visibility size realizations and syntax
highlight passes grow the page without DOM mutations.
timkhronos and others added 6 commits July 30, 2026 16:30
* docs : center badges and links, remove Hot topics

- Use <div align="center"> for GitHub-compatible centering
- Add dev branches and compile times links
- Remove Hot topics section

Assisted-by: llama.cpp:Qwen3.6-27B

* readme : remove sections

* docs : center badges, remove Hot topics, extract sections, remove tools

- Use <div align="center"> for GitHub-compatible centering
- Add dev branches and compile times links
- Add lib llama API and llama-server REST API links
- Remove Hot topics section
- Remove Recent API changes section
- Extract XCFramework section into docs/xcframework.md
- Extract Completions section into docs/completions.md
- Extract Obtaining and quantizing models into docs/models.md
- Remove tools usage sections (llama-cli, llama-server, etc.)
- Move Contributing section to the end

Assisted-by: llama.cpp:Qwen3.6-27B

* cont : arrange links

* cont : fix ws

* cont : remove seminal papers

* cont : change sample model

* cont : trim-down contributing section

* cont : sort backends alphabetically

* cont : words

* cont : add fig captions

* docs : models words

* readme : shorter caption

* cont : fix typo

* cont : add window frame to screenshot
When matrix's weights are shaped 1xK is leverage a transpose-free
computation to use mat_mul_vec_f.
RehanQasim-dev and others added 5 commits July 30, 2026 09:22
Adds the L2-SCP hart-to-hart counter primitives (already used
locally by the stock mul_mat_f16_matrix_engine.c and
mul_mat_Q4_0_matrix_engine.c kernels) to the shared platform.h so
new kernels can use them too, and switches both stock kernels to
the shared copy instead of their own private ones to avoid a
duplicate-definition conflict.
Stripe output elements across every hart of all 32 shires instead of
blocking work into 16-element chunks, which only filled 8 shires for a
typical decode GEMV. Adds a register-resident f32 row-dot helper with
L2 prefetch for the streamed weight row, and stages the reused B
activation vector into per-shire L2 SCP.

Also fixes the matrix-engine GEMV dispatch check, which compared
src1->ne[0] instead of src1->ne[1] and so never actually caught the
n=1 decode case, sending it to the matrix engine kernel where it
stalls on a single-column matmul.

Co-authored-by: Rehan Qasim <rehan.qasim@10xengineers.ai>
(cherry picked from commit b62eede)
Adds a double-buffered producer/consumer F32 matrix-engine kernel
(hart 1 transposes weights into double-buffered L2 SCP while hart 0
runs tensor-engine compute) with a weight-reuse path and software
prefetch for both weights and activations, and wires MUL_MAT dispatch
so N <= 2 uses the vecdot GEMV kernel and N > 2 uses this
matrix-engine GEMM kernel.

Co-authored-by: Rehan Qasim <rehan.qasim@10xengineers.ai>
(cherry picked from commit 637d9a9)
Kernel source (mul_mat_f32.c, mul_mat_f32_matrix_engine.c) confirmed
byte-identical to the et-perf-debug branch, so no kernel porting needed here —
only the dispatch threshold was stale. Re-measured independently on this branch
(no graph-profiler/perf-counter instrumentation here) to confirm the crossover.
Llama-3.2-1B-Instruct-f32.gguf, ET_DEVICES=0, llama-bench -n 0:

N | vec_dot | matrix-engine (forced) | final (threshold=7):
 2 |  9.46 |  5.25 |  9.46
 3 | 11.61 |  7.66 | 11.61
 4 | 13.11 | 10.01 | 13.11
 5 | 14.39 | 12.66 | 14.39
 6 | 15.11-15.15 (r=5) | 14.98-15.10 (r=5) | 15.11
 7 | 15.71-15.75 (r=5) | 17.64-17.73 (r=5) | 17.74
 8 | 16.18 | 20.79 | 20.39
 9 | 16.53 | 23.02 | 22.90
10 | 16.90 | 25.26 | 25.65
20 |   n/a |   n/a | 42.93
30 |   n/a |   n/a | 63.02
50 |   n/a |   n/a | 100.36
70 |   n/a |   n/a | 134.90
100 |   n/a |   n/a | 182.84
120 |   n/a |   n/a | 215.30

Result: N=7, identical to the value measured on et-perf-debug. Final column
tracks the better of the two forced curves. Correctness verified via
test-backend-ops (202/202) and llama-cli coherence.

(cherry picked from commit c1b6ac3e9209960483f3702700cbc54a0229691f)
The comment said N <= 2 but the dispatch threshold above it was
tuned to N >= 7 in the previous commit, so this fallback now also
covers N=3..6.
@RehanQasim-dev
RehanQasim-dev force-pushed the upstream-f32-gemv-gemm branch from c775a5a to e84d0da Compare July 30, 2026 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.