Save inference parity implementation and evaluation harness

This commit is contained in:
Georg Bauer
2026-09-10 22:45:59 +02:00
parent b99ce2aa10
commit 02db0968ae
198 changed files with 111205 additions and 586 deletions
+551
View File
@@ -0,0 +1,551 @@
MTPLX Qwen runtime shaders
=========================
mtplx-runtime-0.32.2.metallib is an unchanged copy of mlx/lib/mlx.metallib
from the existing MTPLX reference environment (runtime version 0.32.2).
SHA256: dc59d1cceb1a5c7e578232e6e41e28e2c73c9463ac6dbc3886c3ee17ffc270ed
Source tag: v0.32.2
Source commit: 1f8e74e3f12f31365464a6867c6579f0e9b29d85
Source: https://github.com/ml-explore/mlx/tree/v0.32.2
License: MIT, reproduced in MLX-LM-LICENSE.txt (identical license text).
Only GPU shader code is included. No libmlx.dylib, Python inference code,
or C/C++ application/backend code is linked into DS4Server. Rust selects
the shader entry points, binds model buffers and owns graph execution.
The reference's get_quantized_kernel implementation was verified to call
Device::get_kernel on the default precompiled library, matching
mlx/backend/metal/nojit_kernels.cpp at the pinned commit. The shader library
is reused byte-for-byte, including its precompiled specializations, rather
than independently rebuilding different kernels or compiler settings.
Provenance on the evaluation machine:
/private/tmp/MTPLX-analysis-20260901/.venv/lib/python3.12/site-packages/mlx/lib/mlx.metallib
The Rust test mtplx_runtime_shaders_are_pinned enforces the complete file
hash. Existing resource packaging includes the metal directory recursively.
This artifact adds approximately 174 MiB to the resources; it is not a model
download. Loading is lazy on the first runtime-shader dispatch.
The shader identity is not a claim that the complete Rust model graph,
scheduling, cache behavior, or end-to-end performance has reached parity.
Compiled QSA moving offsets
--------------------------
tests/fixtures/mtplx-qsa-update-jit.json records the actual compiled indexer
scalar shaders and compute_dynamic_offset_int32, observed without changing
the original compilation by tools/mtplx-jit-reference.py --operation qsa-update.
Only the contiguous int32[1] scalar specializations are retained; full observed
library source hashes are included. --check reruns the original compiled core.
The Dynamic Offset body is used unchanged by the Rust dynamic-copy path;
its original full source hash is
48a7309664f797e749aa42d2c2c4db0cf3abedf97297f0068d06b7847d988b93.
It is Copyright Apple Inc., MIT as reproduced in MLX-LM-LICENSE.txt.
The following gg1/gg2_dynamic_copybfloat16bfloat16 kernels are taken directly
from the unchanged runtime metallib. No frontiers are read back to the CPU.
Clamp/Multiply now feed the connected qsa_compiled_cache_window stage.
mtplx-qsa-compiled-scalars.json retains the actual generated scalar kernels,
including all three constant-CSE layouts for the Clamp and an independent
257/255 specialization check. Only structural integer literals and exported
symbols change when Rust specializes a kernel; the computations are unchanged.
mtplx-qsa-compiled-header.metal is the unmodified original compiler prefix;
SHA256: 2665a76463f3f6ee283c6a50b66e4a527318a114080b31441dfa900042097a39.
--qsa-header --check compares that prefix with a fresh original compilation.
Both resources contain runtime shader code, not host runtime code. Most is
Apple MIT. The unchanged full prefix also retains the Apache-2.0 cexpf.h
notice (Apple, NVIDIA, Filipe RNC Maia; license text in MTPLX-LICENSE.txt) and
the full BSD-2-Clause expm1f.h notice/disclaimer (Norbert Juffa 2015-2023).
Those overloads are retained as original header dependencies, not new Qwen
complex/exponential computation in the integer scalar kernels.
26 compiled reference calls verify the complete retained-input cache window.
They do not establish graph-bank replay, allocation/donation, BFS scheduling
or production inference parity.
tests/fixtures/mtplx-qsa-select-jit.json also retains the actual compiled Add
kernels for selector row offsets 0/1/2/3. Rust substitutes only the structural
offset literal and exported symbol, preserving the original +0 dispatch.
--operation qsa-update --qsa-mode blocks --qsa-score-budget 4096 --qsa-header
--check reproduces the connected reference selector and its exact sources.
Query preparation and both selector families now accept GPU frontier leaves
through the same original kernel dispatch used by the host-frontier entry.
66 actual compiled reference calls cover all five output modes, chunked
selection and the connected cache state for retained old input leaves. These
are functional Q/K-entry checks, not graph-bank, ownership or UI performance
acceptance. Host integration and the runtime evaluator remain open.
The retained-input Hidden entry has since been connected through the already
verified original affine projection kernels to that same Q/K implementation.
132 actual select_hidden calls cover 4/8-bit, group32/64 projections and all
five output modes. The combined entry test covers 198 reference calls. No new
Metal bodies or alternative projection/selection arithmetic are introduced.
The installed B1/BF16 cache/phase routing is now connected to backing reserve,
explicit GPU frontiers, those same retained-input arithmetic entries and cache
commit. 720 actual original host-method decisions and 108 additional complete
indexer calls cover routing and ongoing state/output transitions. The combined
host-flow test includes the previous 92 non-compiled calls through the same
entry. A parameter-bound QSA graph bank now replaces the direct compiled
expression chains. It rebinds explicit inputs to cached primitive dependencies
and uses the pinned degree/BFS-width algorithm for a single indexer graph.
99 optimized original graph contracts, all 198 arithmetic calls, stride-changing
replay, parameter invalidation and the original connected-call trace/entry
counters pass. Evaluated constants are omitted from structural fingerprints;
kernel-source and output checks remain separate. No alternate Metal kernel was
introduced. The dtype/shape-generic guard, donation/allocator, early release,
global model scheduling and production integration remain open. Last-use
graph leaves are now detached after their consumer, separately from explicit
completion ownership that protects GPU work until its existing CB finishes.
The shared canonical dispatch bridge holds bound Metal resources through
completion as well; this fixes four Invalid Resource failures exposed with
unretained command buffers. Compile/dispatch use scoped autorelease pools.
The normal and strengthened model-free collections cover 34 tests. This is
not performance acceptance or evidence of matching whole-model Metal encoder
timelines. Donation must still account for both descriptor and shared Data
ownership, including outstanding GPU evaluator holds.
The canonical test-bound Buffer now separates array/view identity from shared
Data ownership, including nested native views and completion holds. QSA COW
checks both Rc<Buffer> sharing and underlying Data sharing. A direct pinned
QSACache alias/view update and its negative Rust regression check prove that
array aliases observe replacement while distinct views retain old values.
The QSA graph now applies the pinned primitive input/sibling-minus-primary-output
Data retention protocol before the next primitive, rather than re-holding leaves
at their last consumer. Its scheduler tracks the actually selected sibling, not
just the producer node. CPU scheduling and GPU ownership-count checks cover that
distinction, duplicate Data, empty-batch fallback and completion.
QSA raw/pool DynamicSliceUpdate now performs actual BF16 vector-copy donation
for exclusive mutable cache state. Retained inputs, snapshots, views and GPU
Data holds select copying instead. The 16 KiB bound uses root allocation size.
All 198 core reference cases also run with state snapshots and exclusive state,
checking old/new hashes and actual Data reuse. No kernels or synchronization
boundaries changed. Model-wide integration, other primitive donation and generic
dtype/layout contracts remain open.
The allocator policy is now ported from the pinned buffer_cache.h with the same
best-fit/oldest-equal-size choice, strict reuse ceiling and age-based/90%-clear
eviction. A 101-event trace from the real installed runtime checks allocation
identities and active/cached bytes, including cache-limit transitions. Reference page rounding and the
device maxBufferLength precheck are connected to the test-bound Buffer methods;
logical views preserve tensor bounds while Data records the rounded root size.
The explicit Rust Allocator now owns a real 1 MiB untracked/shared Metal heap,
uses it for requests below 256 bytes with device-allocation fallback, and
recycles native roots only after the final physical allocation hold releases.
Its active/cache/peak/resource accounting, cache and memory limits, resource
pressure GC (including the original unsigned subtraction), zero/null result
and actual cached storage reuse are checked against the 101-event receipt.
Residency/wired limits are now connected to this explicit allocator, including
heap registration, cache retention and erase-before-release. Set selection and
budgets follow resident.cpp: first fit, oversize/empty-set reuse, 32-set ceiling,
emptiest fallback and touched-set commits on resize. Native membership and the
ten original residency lifecycle scenarios are tested. Queue attachment uses
the published set count and is exercised immediately before test commits;
The new test-bound Submission owner now attaches automatically at actual native
commit boundaries through a scoped encoding-thread callback, including flush,
readback, finish and cleanup paths. Its queue cursor persists across batches;
scope teardown drains before unregistering the callback. Legacy work outside
the scope and other queues do not inherit it. Externally wrapped storage,
the process-wide owner and model-wide routing remain open.
Existing canonical helpers
are not globally switched to untracked buffers before encoder dependencies
are ported. No whole-model allocator/performance parity is claimed.
The test-bound Rust Encoder now owns an independent queue with unretained
command buffers and Concurrent compute encoders, following the pinned
device.cpp/event.cpp/error.h dependency and completion rules. Access roles,
barrier epochs, deferred concurrent outputs, cross-encoder fences, temporary
exclusion and shared-event error propagation are managed in Rust. The bridge
only issues Metal API calls and reuses the same original kernel dispatcher.
Commit thresholds count array.data_size() ELEMENTS (as the reference does),
not allocation bytes; counters persist across encoder boundaries. The three
new checks include dependent untracked GPU copies, two-queue event transfers
and safe synthetic error-completion tests. The 48-test suite passes in both
legacy retention modes. Only these new encoder tests use the independent
queue. Complete operator access metadata, stream/evaluator integration and
production routing are still open; this is not a model-performance receipt.
Operator scopes now connect the existing normalization and full MoE chain to
the independent Concurrent encoder and its pooled untracked allocator. Explicit
binding roles/data_size spans cover routing, sorting, gather/unsort, Gate/Up,
SwiGLU, casts/norms, affine and gathered quantized projections, Split-K and both
stock/fused experts plus the shared expert. Sort and split-reduction scratch is
registered as backend temporaries. RMSNorm's default one and GatherSort's divisor
are real scalar array bindings, not setBytes replacements. No shaders changed.
The nine existing fixture groups (540 cases) execute through BOTH encoders with
unchanged reference-output checks. This does not multiply independent fixtures.
QSA routing, general array/donation semantics, evaluator/stream integration,
production instrumentation and complete model/performance acceptance remain open.
GDN routing now uses the same typed dispatch/allocator path, including conv,
mask/cache, Q/K normalization, compute_g/beta, recurrence, fused step and output.
The direct native fused-step bypass is removed. Scalar operands and custom T
are original scalar array inputs. Concatenate uses the original concurrent
disjoint slice writes and dependency join; checked-input copies retain the
original order and backend-temporary registration. No shader bodies changed.
Four further existing groups (476 GDN cases) execute in both encoders, bringing
the dual-encoder total to 1,016 existing cases. This remains operator-level
correctness coverage, not whole-model scheduling or production parity.
QSA static/dynamic copies now bind explicit array data_size metadata rather
than treating the copied region as the whole bound array. Dynamic offset arrays
are inputs/backend temporaries. Zero fill, COW/General copies, compiled frontier
operations and fused query/pool preparation carry access roles. KV concatenate
uses the reference concurrent slice-write region. The existing 12 backing/copy
and 26 compiled cache-window cases now run in both encoders (1,054 existing
dual-encoder cases in total). Eager preparation, score/select operators and full
graph/production integration remain open. Shader sources/geometries unchanged.
Eager QSA preparation now carries access roles and exact slice spans through
RoPE, mean/RMS pooling and projections; all three RoPE concatenates use the
reference concurrent writer regions. Scalar arithmetic uses array inputs.
The fused selector and chunk concatenation also use the same encoder; its three
frontiers are distinct scalar arrays and its private score plane remains a
custom-kernel output, not a backend temporary. The existing 210 preparation,
88 ongoing cache and 75 selector/chunk cases now execute in both encoders,
bringing the existing dual-encoder total to 1,427 cases. Empty-array placeholder
storage is unchanged; general zero-storage array semantics and the remaining
score/eager-selection, evaluator and production paths are still open.
Both QSA prefill score producers and the connected score/top-k/chunk route now
use the independent encoder. FP32 GEMV/Steel/NAX/Split-K, ReLU and reduction
carry explicit array spans; Maximum/Divide scalars are array inputs. Matmul's
output is allocated before transpose-copy preparation; those copies and the
Split-K plane are registered as backend temporaries after their consumers.
The MPP producer retains its original stride-aware inputs. Prefill top-k
allocates outputs before checked-input copies, registers those copies after
dispatch and uses three separately allocated frontier arrays. Shader bodies,
specializations and dispatch geometries are unchanged. The existing 384 FP32
score, 146 MPP/top-k/prefill and 96 mixed-producer cases now run in both encoders,
bringing dual-encoder coverage to 2,053 existing cases. These manual fixture
batches do not establish general evaluator/stream boundaries or production
parity; eager selection/output and full graph integration remain open.
Eager QSA score masking, ranking and all output branches now carry explicit
input/output array spans through the independent encoder. Scalar operands are
real arrays; per-tile mx.eval(top_t) synchronizes that queue before constructing
the next tile. Tiled index concatenation, decode-tail concatenation, rows-gather
outputs and dense padding use the original concurrent slice-write regions.
Dense padding is materialized before concatenation, and rows-gather builds its
two separate Arange expressions; these restore previously collapsed operations.
No Metal body or specialization changed.
The fixed-signature indexer graph now defers input/sibling Data on the active
queue rather than retaining fallback descriptors in its returned state. The
selected output's Data remains excluded, preserving donation. Compiled chunk
concatenation joins concurrent writers and moving frontiers are four separate
int32 arrays. Existing empty/nonempty Data-ownership checks run in both queues.
The existing 540 eager cases and 200 ongoing indexer calls (including compiled
routes) also execute in both encoders: 2,793 existing dual-encoder cases total.
Explicit tile eval counts, output/state hashes and graph engagement are checked.
These bounded fixture batches still do not apply the complete model-wide
evaluator/stream scheduler and its automatic primitive-level commit policy.
Production routing/instrumentation and whole-model performance acceptance remain
open, as does general zero-storage array handling.
Primitive submission and task scheduler
--------------------------------------
The fixed indexer tape now checks the original encoder thresholds after a whole
primitive, never after an individual kernel dispatch. Input/sibling Data and
backend holds are attached before a possible commit. Counted GPU tasks complete
from the command callback; failed submissions balance ownership/accounting.
The single-stream tape also applies the original active-task/memory pressure
condition, finalizes its stream and waits for progress without inserting sleeps.
The Rust scheduler uses stdlib FIFO workers per CPU stream, earliest-error
preservation, non-consuming cross-stream event error propagation and draining
shutdown. CPU event waits/signals use the existing Metal shared-event bridge.
Explicit tile/final fixture synchronizations are checked separately from
automatic commits, with independent expectations for the two full-prefill
indexer cases that cross the pinned Max data-size threshold.
CPU dispatch now counts every tenth operation, with completion as a separate
FIFO task so failed work still completes its activity accounting. The CPU
primitive cleanup task participates in that count and retains complete backend
temporary descriptors until earlier work has run. CPU and GPU now share the
input/sibling Data-selection function, excluding donated primary output Data.
The private CPU temporary wrapper is Send only for drop-only worker ownership;
Buffer itself remains !Send/!Sync. Five scheduler tests and the existing GPU
ownership/connected-indexer checks pass, including unretained indexer execution.
An explicit runtime-owned stream registry now connects CPU/GPU encoders for the
installed single-CPU/single-Metal backend. Defaults and template resolution are
per thread/device. Local encoders are destroyed at thread exit; global streams
allow sequential cross-thread use. Explicit clear preserves the reference's
metadata/stale default handles and global CPU/GPU cleanup distinction.
Registry locks do not cover encoding or waits. GPU selection reuses the same
Rc/TLS encoder and allocator; exclusive ownership is checked before returning
an encoder to storage. CPU/GPU events and finalize-all pressure handling use
the existing scheduler and native bridge. Two new checks include actual
two-queue copies, eleven blocked CPU tasks, thread cleanup and error unwinding.
The 200 ongoing indexer cases now use a registered GPU stream in the independent
path; the full model-free collection passes 57/57 normal and unretained.
Graph events now preserve per-copy values/origin streams while sharing native
events and errors. Inter-stream fences use either the reference SharedEvent
path or its opt-in Metal3/macOS15 fast path. Fast synchronization remains off
by default. The existing pinned metallib supplies input_coherent, fence_update
and fence_wait unchanged. Rust preserves array output registration, raw timestamp
bindings, explicit update barriers, cross-device coherence and CPU SeqCst
timestamp operations. Shared fence counts and per-dispatch snapshots are
separate; completion/task ownership retains timestamp storage until work ends.
The bridge now accepts empty dispatch grids, including the original zero-work
coherence dispatch. Three new checks cover events/errors, partial-word/empty
coherence and CPU/GPU/GPU/CPU transfer in both modes with a test-only deadlock
rescue. All 60 model-free tests pass normal and unretained; no rescue fired.
No shader body, metallib, inference default or production routing was changed.
This is still test-bound. Full array/evaluator cross-stream dependency construction, actual
CPU model primitives, compile-cache cleanup integration and the complete model
graph remain open. Production Qwen and performance acceptance remain unchanged;
stream/operator checks do not establish whole-model parity.
Canonical dispatch now transfers deduplicated Rust root-allocation ownership
to its existing Metal completion callback. This replaces the native resource
set in that path and prevents physical release without adding Data aliases
that would disable donation. Typed bindings also retain their descriptor/scalar
borrow until dispatch. Early failures keep ownership with Rust; registration
transfers it even when a submitted standalone command later reports failure.
No shader, flush, commit, wait or additional completion-handler change.
Normal and unretained collections pass 45/45 tests including native allocation,
heap exhaustion, limits, residency and in-flight cache exclusion. This is not model parity.
Binding input/output roles, barrier epochs, concurrent contexts, inter-encoder
fences and reference commit thresholds still require the full encoder port.
Runtime-generated indexing shader
--------------------------------
The same pinned runtime JIT-compiles gather_front instead of using the
precompiled library. tools/mtplx-kernel-source.py resolves its two headers
into metal/mtplx_qwen.metal without editing shader code and supplies the
BF16/U32/FP32 template instantiations. Original notices and MIT attribution
are retained. Header identities:
indexing/indexing.h:
e820b8ee2b5132a97122780c12433ebb5100d8078d31e211d0429400a11415bb
indexing/gather_front.h:
64aacebf6576dfcd389383564fa1214bc87f2a091dd33cc64c598c5367ecab96
The generator requires the pinned runtime source checkout alongside the
MTPLX reference checkout, named mtplx-runtime-0.32.2. The product requires
only the generated shader resource, not this reference source checkout.
Runtime-generated SiLU shader
----------------------------
tools/mtplx-jit-reference.py observes original runtime compilation without
changing it. tests/fixtures/mtplx-silu-jit.json pins the generated19variants;
full observed source SHA256:
76cafb45db55a91efba66503dde59b37628ea360f220f35dd860f4ac3c3d0111
The shader exporter retains the generated computation and its original
BF16 math, Sigmoid, Multiply, cast and stride helpers. Only host-name aliases
change. All supporting header identities are enforced by the exporter and
the emitted unit hashes by the focused Rust test. Copyright Apple Inc.; MIT
as reproduced in MLX-LM-LICENSE.txt. This does not link a host runtime.
Runtime-generated GatherAxis shader
-----------------------------------
The pinned GatherAxis source and generic elem_to_loc helper are retained
unchanged, with BF16/U32 index instantiations for contiguous/strided inputs
and int/int64 offsets. The router retains the strided last-ten-column view;
no replacement top-k kernel is used. Header identities:
indexing/gather_axis.h:
e1a745391ff4990f3f1ad75c5687c3b102dcdc4833d8fbbac38e10f54af29af4
utils.h:
5e1568e9edde9d05dbf86f68fa0d6c6240f2c32b973c7c6a76166b9c0d91543d
Copyright Apple Inc.; MIT as reproduced in MLX-LM-LICENSE.txt. Softmax,
reduction, binary operations and index copies use the pinned metallib.
Runtime-generated SwiGLU shader
------------------------------
The original compiled activations.swiglu used by non-sanitize-fused SwitchGLU
and Qwen3NextMLP is captured separately from nn.silu. Full observed source SHA:
bf78eee5cf96ea7c112c4e61546c12bcacf57fe512e0572182604cb94137510b.
tests/fixtures/mtplx-swiglu-jit.json preserves all 19 original generated variants.
The exporter changes only host aliases and reuses the already pinned BF16,
Sigmoid, Multiply and cast/stride dependencies. Copyright Apple Inc.; MIT.
Runtime-generated compute_g shader and staged GDN
------------------------------------------------
The original compiled gated_delta.compute_g is captured with the same observer:
tests/fixtures/mtplx-compute_g-jit.json. Full observed source SHA256:
34143a98046f8af5538767734fc169a5cab22a4920c26f9ba7ea45b8097152de.
Receipt SHA256:
701e2f54b7cb8bf97616f83256657e6c5e8cc8b46f4b65c559ccc030ab111dbf.
All 19 variants retain their original BF16 Add/LogAddExp intermediates and
FP32 final exponential. Only host aliases change. The exporter pins the
additional Exp, Negative, Add, LogAddExp, Limits and log1p shader dependencies.
complex.h SHA256:
16e8a815b2cbdb6070e0824e64fe33fccb6e918f1b84ea5c792bd89d33e57bf1.
cexpf.h SHA256:
88b6e15a52a5800d98d9bc6da840ca5cf70bf572fda136409580c1f17b1e0aab.
The complex overload dependencies are retained unchanged, not used to add a
complex-valued Qwen path. complex.h is Apple MIT. cexpf.h is Apache-2.0,
Copyright Apple 2025, NVIDIA 2008-2013 and Filipe RNC Maia 2013. Its full original
copyright/license notice remains embedded in the generated shader; the Apache
license text is included in MTPLX-LICENSE.txt.
Stock depthwise Conv1D, copies, casts, reductions and elementary operations
use the unchanged runtime metallib. Cache valid-length GatherAxis additionally
instantiates the original signed INT32-index template; router indices remain
UINT32. No shader body is replaced by a hand-written equivalent.
Original QSA indexer preparation
-------------------------------
qsa_indexer_prepare.py SHA256:
a77f6ca5ae805e729519c4629ae88b455a6dbf473a457a6e1c8219174eb59091.
The exporter reads _prepare_queries_kernel and _pool_keys_kernel as AST data;
it does not execute the model or kernel module. Both original source strings
are unchanged. Header substitutions match the installed geometry: four query
heads, width128, rotary64, ratio4, epsilon1e-6, attention scaling1. Includes are
resolved at translation-unit scope; separate namespaces avoid collisions among
the original header constants. Only entry-point declarations, host aliases and
template instantiations are adapted. Stride metadata retains the original
constant int64_t address space. Original Metal math and BF16 rounding remain.
Copyright MTPLX; Apache-2.0, see MTPLX-LICENSE.txt and MTPLX-NOTICE.txt.
This is the preparation portion, not the full QSA indexer, selection,
attention graph or production integration. Runtime frequencies are input buffers,
not host replacements for the model's frequency construction.
QSACache/KVCache host lifecycle now uses the original scalar/vector/general
copy and BF16/FP32 cast entries from this runtime, including positional writes,
growth, strided restored state and the derived mirror. Rust distinguishes array
object identity (__setitem__ overwrites its descriptor) from shared slice storage.
Retained state aliases are checked against actual MTPLX cache operations, not
assumed immutable. No additional shader bodies or runtime host library are used.
The connected canonical cache remains test-only until product graph integration.
Original dynamic QSA selector
-----------------------------
qsa_indexer_select.py SHA256:
a3c74af27a7045c12f2893a8b7a91724c00d8a4148315c3165f3480c83016cf3.
metal/mtplx-qsa-select.json preserves the original header, common body and all
three epilogues (blocks, dense_mask, row_tokens), extracted without importing
the model. Rust substitutes the original literal header parameters and supplies
only the entry-point ABI. Tests additionally compare full generated header/body
hashes against the actual MTPLX factory. H4/D128/ratio4 match the installed model;
BF16/FP32 operands, backing capacity, top-k and TF32 remain specializations.
Native compilation follows runtime 0.32.2 CustomKernel defaults: Safe math and
its platform-selected Metal language version. No runtime host library is linked.
The original 32MiB score-scratch chunk planner and typed output concatenation
are connected to the cache/preparation port. General submission/concurrency,
the complete eager indexer and production integration remain open.
Copyright MTPLX; Apache-2.0, see MTPLX-LICENSE.txt and MTPLX-NOTICE.txt.
Original vectorized QSA prefill
------------------------------
qsa_indexer_prefill.py SHA256:
4d6fd428243c001746f69f8aed45991356772c2bd4a45586eb3c6813c91998d3.
The same JSON export retains _MPP_SCORE_HEADER/_MPP_SCORE_SOURCE, the original
top-k body and literal f-string header segments. Rust resolves only their named
constants and provides entry-point ABI/type aliases. TensorOps tile layout,
ordered per-head ReLU reduction, adaptive radix/insertion and all epilogues are
unchanged. The original required General FP32 copy is used for non-contiguous
score views; MPP input views keep their strides without added copies.
The 128MiB producer-aware planner and score -> top-k -> concat chain are connected
for the installed BF16/M5 geometry, including a 2K continuation from live cache.
Full indexer branch routing, compiled graph bank and production integration
remain open. Copyright MTPLX; Apache-2.0.
The general FP32 score expression now shares that prefill entry point. Rust
ports runtime matmul.cpp's H4/D128 M5 Max routing: AsType Vector/General layout,
check_transpose and broadcast copies, batch collapse, GEMV, regular Steel/NAX
and both Split-K variants, original per-head Maximum, row/column Sum and Divide.
All shader entries come from the unchanged pinned metallib; no shader body or
host runtime library was added. The pooled cast is retained once across chunks;
producer selection and the H4+1 workspace budget follow the reference.
384 score cases and 96 connected selection cases are exact with real runtime
MLX_ENABLE_TF32=0/1 in separate processes. These remain canonical correctness
fixtures, not production integration or performance-parity evidence.
Original eager QSA selection
---------------------------
The untiled QSAIndexer._select_eager score/top-k path uses the original
runtime Arange, Add, integer Divide, Less, casts, Select, Subtract and
ArgPartition (implemented by the pinned runtime as argsort). The chronological
flash_prefill block epilogue adds original int32 Sort, int64 index conversion,
bool GatherAxis and Select. The exporter adds only the required
gather_axis<bool,int64_t,int,true,true> instantiation of the already preserved
GatherAxis body; no body is changed. Its original file/unit hashes are unchanged.
All untiled output epilogues are connected: dense mask (original bool
ScatterAxis, repeat/concatenate and causal/tail mask), rows-gather (argsort-order
tokens and validity), decode flash (chronological blocks, host tail bound) and
decode gather (chronological tokens and variable-length tail). The flash branch
retains precedence; neither decode branch evaluates the dead selected-mask DAG.
Shared original cast/sort/vector dispatch helpers do not alter the shader bodies.
ScatterAxis adds these unchanged pinned Apple MIT runtime source units:
- atomic.h, full-file SHA256:
4c35ea2798a2335502865247aee878149fc9ada0d7e84c05d771baef0c7fcc60
- reduction/ops.h None operation, full-file SHA256:
78d06730fc9564a73944e7f1fe3897d25c8789b28a939bf418e1968db311da41
- indexing/scatter_axis.h, full-file SHA256:
43eabd0216101f8e32f5cdd19ce40b7f954564be27fad98a5e0fe345e7b94ce5
Only include/pragma-once placement, namespace and the two required
scatter_axis<bool,int64_t,int,None,false/true,true> instantiations are added
outside the preserved bodies. The exporter guards full-file and body hashes.
No host C/C++ runtime implementation is linked.
The 408 eager receipts tap actual QSAIndexer calls: 60 score/top-k, 24 prefill
blocks, 144 dense masks, 108 rows-gather and 72 decode outputs. They include 2K
queries, 65,536 blocks, tails 0/1/3 and separate real TF32-on/off processes.
The tiled path shares the same score/rank functions, pooled FP32 input and tie
vector. Each original mx.eval(top_t) is a synchronous command completion before
the next tile, not an asynchronous flush. Only evaluated index views/backings
are retained through the original GeneralGeneral uint32 concatenate. Index
stride changes N -> K, independently of the N-strided validity. No new shader
source or specialization is needed. Rust shares the original output-branch
priority, including the tiled rows-gather exclusion and decode flash precedence.
132 further actual-call receipts (108 tiled, 24 tile-off boundaries) bring the
eager total to 540. They include observed reference eval row counts, 2K queries,
65,536 blocks, tail 0/1, partial tiles and stride-2 FP32 query views.
The existing GPU busy counters additionally verify one completed command buffer
per observed reference tile eval, plus the final output batch.
Full indexer routing, the general scheduling tape/allocator and production
integration are not established by these checks.
Original eager QSA preparation
------------------------------
The installed BF16 H4/D128 query and H1/D128 pool paths now use the original
eager preparation expression as well as the fused custom-kernel branch.
RMSNorm uses the pinned runtime kernel and its required General-Copy for sliced
projection inputs. Pool mean is FP32 sum multiplied by 0.25 and cast to BF16
before weighted RMSNorm. RoPE preserves all Arange, casts, concatenations,
Cos/Sin, BF16 Negative, FP32 Multiply/Add and final BF16/pass-through stages.
The installed rotary64/ratio4/eps1e-6/scaling1 contract is unchanged.
All entries come from the unchanged pinned metallib; no shader body was added.
The existing projection and cache-extension entry points select either branch
and retain the eager intermediates through their consuming operations. 105
additional actual QSAIndexer receipts cover bare and quantized-projection
preparation, including 2K rows, padded/stride-2 inputs and high positions. Another
44 real QSACache/KVCache transitions cover eager pooling, capacity growth,
reservation, trim, state aliases/restore and FP32 mirror rebuilding. These are
not a complete indexer, production-integration or performance-parity receipt.
Connected non-compiled indexer entry
----------------------------------
The Rust entry after compiled-route rejection now connects projection/supplied
QK views, query preparation, raw/pool state and the original large-prefill,
legacy-fused and eager selection order. All existing original shader dispatches
are reused. Query preparation is dead when dense==sparse; KV.offset advances
only in the subsequent Attention step. Shared return variants preserve the
model-visible outputs while retaining other encoded kernel outputs.
92 actual MTPLX indexer calls in 24 ongoing sequences verify lane choice, 82
selection hashes, 268 raw/pool/mirror hashes, capacities/frontiers and command
completion counts. They cover 2K rows, 32K history, supplied 704-stride QK views,
prefill crossover, gather/flash priority and tiling. No compiled path is silently
replaced. Compiled eligibility/core, attention, graph scheduling and production
integration are still open; these are correctness, not performance receipts.