feat(ai): wire experimental ONNX through autoindex - #5
Merged
xerj-team merged 3 commits intoJul 24, 2026
Conversation
Build on the merged ONNX backend prototype by connecting the experimental runtime to the ordinary server and autoindex paths. Explicit --embed-mode onnx-experimental configuration now reaches semantic_text mappings created by autoindex, while lexical remains the default and Candle neural behavior is unchanged.\n\nBatch semantic documents across bulk requests using bounded, length-aware windows and preserve bulk item ordering, status, and WAL semantics. Share one process-wide ONNX session, suppress runtime optimizer noise by default, and avoid loading or hashing model assets for system indices that have no semantic fields.\n\nFail closed on incompatible model identity, dimensions, pooling, token limits, asset replacement, and caller-supplied derived vectors. Bound concurrent calls and bytes before model loading, tokenization, or blocking execution; expose saturation as HTTP 429 and keep failed autoindex files resumable instead of journaling them as complete.\n\nDocument the complete export, build, server, autoindex, map, and query workflow plus the current GNU/Linux and MiniLM contract. Update machine-readable catalog/help descriptions so agents can discover the experimental mode without implying that it is the default.\n\nMeasured on the controlled 128-document embedding corpus with the same length-aware plan, scheduled ONNX produced 116.671 docs/s versus 9.045 docs/s for Candle (12.90x embedding-layer throughput). This is not an end-to-end indexing claim. Output agreement remained at minimum cosine 0.9999991655 with identical top-10 results and order.\n\nValidated after rebasing onto upstream main:\n- xerj-ai: 36 passed, 1 ignored\n- xerj-autoindex: 29 passed\n- engine/server scoped suites: all enabled tests passed\n- ES-YAML: 1360 passed, 0 failed, 3 skipped
buger
marked this pull request as draft
July 23, 2026 19:09
Apply the stable rustfmt output required by the repository-wide CI check, including formatting debt already present on the branch base. Rename an unused test binding exposed by -D warnings.\n\nMake ONNX admission detection compile cleanly with the feature disabled, gate the ONNX-only file hashing helper, and avoid allocating a lowercase mode string.\n\nValidated with cargo fmt --all --check, workspace Clippy across all targets with warnings denied, ONNX-enabled xerj-server Clippy across all targets, and git diff --check.
Keep the default build's fail-closed coverage for an ONNX configuration while gating tests that require the compiled experimental runtime. This prevents default-feature test runs from trying to persist an ONNX identity marker they cannot construct.\n\nThe feature-enabled identity, admission, and integration paths remain covered when onnx-experimental is selected. A targeted ONNX CI command was validated locally, but the workflow edit is omitted because the available GitHub credentials do not have workflow scope.\n\nValidated with default release engine tests, ONNX-enabled release engine/server tests, workspace and ONNX-enabled Clippy with warnings denied, formatting, and diff checks.
xerj-team
marked this pull request as ready for review
July 24, 2026 02:46
Collaborator
|
Thank you! this was really helpful! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
This is the end-to-end integration follow-up to merged
#2.
PR #2 deliberately landed the reviewable foundation:
xerj-aiFP32 ONNX Runtime backend;It did not expose ONNX through the server or
autoindex.This follow-up makes that already-merged backend usable through XERJ's real
agent-first workflow:
Beyond #2, an explicitly built GNU/Linux glibc server can now:
onnx-experimentalthrough CLI, environment, or TOML configuration;semantic_text;autoindexjournal correctness and resume unfinished files;data maps;
activation event.
This remains experimental and off by default:
does not become neural because this PR lands.
--embed-mode neuralstill selects the existing Candle backend.matching tokenizer path.
The Candle-sharing implementation and the underlying ONNX inference/scheduler
are background supplied by merged #2, not new code introduced by this
follow-up. Their verified measurements remain below because they explain the
integration's motivation and runtime choices.
Copy-paste end-to-end workflow
1. Export the exact supported model shape
This is deliberately not a generic "run any ONNX model" interface. Export
the FP32
sentence-transformers/all-MiniLM-L6-v2feature-extraction graph:The interface must contain:
input_ids:int64;attention_mask:int64;token_type_ids:int64;last_hidden_stateortoken_embeddings;tokenizer.jsonfrom the same model/export.XERJ applies attention-mask mean pooling and row-wise L2 normalization itself
and truncates at 512 tokens. A semantic mapping declaring a width other than
384 is rejected. Unknown input/output names or a different model shape are not
guessed.
2. Build and start the opt-in server
cd engine cargo build --release -j 32 -p xerj-server --features onnx-experimental target/release/xerj \ --insecure \ --data-dir /tmp/xerj-onnx-data \ --embed-mode onnx-experimental \ --onnx-model /tmp/xerj-minilm-onnx/model.onnx \ --onnx-tokenizer /tmp/xerj-minilm-onnx/tokenizer.jsonEquivalent environment variables are:
CLI flags win over environment configuration.
Startup validates that:
onnx-experimental;The server hashes the assets for the backend configuration but does not load
and optimize the approximately 90 MiB graph until a real semantic field needs
inference. Non-semantic indices therefore do not pay model-loading cost.
3. Autoindex a folder
ONNX runs only for fields inferred as
semantic_text, normally a sufficientlylong body-like field. A short or structured dataset can legitimately infer no
semantic field.
Before attributing an indexing result to ONNX, check all three:
autoindex --dry-runor the created mapping containssemantic_text;autoindex mapreportssemantic=trueand asemantic_field;This distinction prevents accidentally benchmarking the lexical or
non-semantic path and calling it ONNX.
4. Query the discovered semantic field
If
autoindex mapreportsbody:Query text is embedded through the same pinned backend and vector identity as
the stored documents.
What was validated live
A clean debug server was exercised through the actual server, autoindex, map,
semantic query, and shutdown paths.
Startup without semantic work
This verifies lazy activation and the log filter on a real server start. An
additional release initialization observation reached 390 ms, but that process
then failed to bind because another server already occupied the ports; it is
not used as the startup result.
Real autoindex and semantic retrieval
The live corpus contained three finance text records. The ordinary command,
not a direct library harness, produced:
semantic=true;semantic_field=body;The semantic query returned:
0.787737;Clean shutdown flushed in 113 ms.
This is a smoke test proving that the feature is wired through the product
workflow and that lazy loading/log behavior is comprehensible. Three tiny
records are not a throughput benchmark, and the 4.9 seconds include fixed
startup/first-model-use effects.
Semantic ingestion architecture
Batch document embeddings before storage
The regular bulk path previously treated neural document embedding too much
like per-document work. The integrated path now identifies semantic index
operations, prepares their text/chunks, embeds compatible work as batches, and
then commits document/vector results while preserving original bulk-item
ordering and per-item errors.
The backend receives multiple texts so ONNX Runtime can amortize transformer
execution. The scheduler:
Defaults:
onnx_max_pendingonnx_max_batchonnx_padded_token_budgetbatch × longest_sequenceworkBoth document count and padded tokens are required. On heterogeneous text,
one long row can otherwise make every short row in the same batch cost as a
long row.
One safe shared session
Every complete ONNX configuration shares one process-wide lazy session.
Configuration identity includes paths, content fingerprints, thread settings,
scheduler limits, and admission limits.
The public safe
ortAPI used here requires mutable session access. XERJ keepsone session behind a mutex rather than bypassing Rust's API with raw pointers or
an
unsafe impl Sync.Consequences:
Session::runcalls are serialized;because their paths match.
Immediately before the first load, XERJ rereads and rehashes both files. If
either changed after configuration, startup/inference refuses to silently
activate a different vector space from the same path.
Vector-space identity and restart safety
Every ONNX semantic index persists an
embedding_identity.jsonmarker:{ "version": 1, "backend": "onnx-experimental", "model_sha256": "<sha256>", "tokenizer_sha256": "<sha256>", "dimensions": 384, "pooling": "attention-mask-mean+l2-normalize", "max_tokens": 512 }The marker is created when a semantic field/index becomes ONNX-backed, before
vectors can be silently mixed.
On reopen or later writes, XERJ refuses:
verified.
The corrective path is explicit: restore the original assets, or create a
fresh index/vector space, normally by rerunning
autoindex --freshunder a newprefix.
This is intentionally stricter than "the model filename is unchanged." A
vector index is only meaningful when query and document vectors share the same
model, tokenizer, truncation, pooling, normalization, and dimensions.
Admission, overload, and resumability
The shared model/session also owns global admission controls:
onnx_max_inflight_callsonnx_max_input_bytes_per_callonnx_max_inflight_input_bytesAdmission is checked before model load or tokenization where possible.
Rejected work therefore does not consume transformer memory merely to discover
that the node is overloaded.
The implementation distinguishes:
Transient admission failure maps to retryable HTTP 429, including per-item
bulk errors. It is not flattened into an opaque 500.
autoindextreats a failed semantic bulk correctly:That is important for large corpora: overload must not create a false
"successfully indexed" journal state.
Logs and diagnostics
The ONNX path is quiet until it is actually used.
On the first real semantic inference, it emits one concise activation event
containing:
This gives operators an auditable way to prove which vector space produced an
index without flooding normal startup logs.
ONNX Runtime messages below warning are hidden by default. For diagnosis:
XERJ_ONNX_LOG=info target/release/xerj ... # or: debug, verbose, traceThis avoids exposing optimizer/allocator chatter during ordinary XERJ startup
while retaining an explicit troubleshooting switch.
Error messages provide both the reason and next action, including:
--features onnx-experimental;--onnx-modeland--onnx-tokenizer;Performance evidence
There are three different measurements below. They answer different questions
and must not be multiplied.
Controlled backend comparison: 12.90x embedding throughput
The strongest backend benchmark used a deterministic 128-document mixed-length
finance-like corpus and an identical length-aware batch plan for Candle and
ONNX:
sentence-transformers/all-MiniLM-L6-v2semantics;64, 18, 14, 10, 8, 8, 6;The ONNX arm included its real double-tokenization scheduling cost. Candle used
the identical precomputed grouping, so the runtime comparison did not give
ONNX an easier input shape.
Equivalent wording:
Candle;
CPU time for the complete 128-document arm:
The wall-time gain exceeds the CPU-efficiency gain because ONNX Runtime used
the host's cores/kernel implementations more effectively. ONNX was configured
with 16 intra-op threads for the 16-CPU test host.
Quality checks:
0.9999991655;1.0000006(floating-point rounding);This is an embedding-layer benchmark. It excludes extraction, HTTP parsing,
lexical indexing, persistence, journaling, HNSW construction, and other
autoindex work. It is not evidence that the complete product is 12.90x faster.
Contextual scheduler smoke: 1.80x
A separate committed-backend smoke embedded 256 mixed-length documents:
0.9999990463;A later post-log-filter smoke measured 163.59 scheduled docs/s but did not run
the singleton arm, so it is not presented as another speedup comparison.
The 1.80x result only shows that length-aware batching helps this ONNX workload.
It is contextual evidence, not the Candle-versus-ONNX headline and not
end-to-end indexing.
Why length-aware batching was retained
In a broader 256-document scheduling experiment:
Naive FIFO batching was 29.6% slower than singleton because padding
inflated work. This is why the product integration does not simply select
"batch 64" and assume bigger is faster.
Background from merged #2: Candle multi-index sharing
Merged #2's Candle ownership fix is useful even if ONNX remains experimental.
It is not part of this follow-up's 18-file integration delta, but its
before/after result is included to make the overall backend ownership model
reviewable in one place.
Controlled before/after with 16 identically configured neural indices:
Warm ingestion, eight documents per request:
Five repetitions are sufficient to reject a large concurrency regression, but
not to claim the observed 3–4% concurrent gain as intrinsic. The defensible
result is the large model-load/memory reduction with no observed throughput
loss at useful concurrency.
Identical documents across all 16 persisted indices produced bit-for-bit equal
384-dimensional Candle vectors; maximum component delta was
0.0.Binary, runtime, and model cost
Measured stripped server binaries:
The static ONNX Runtime archive used during compilation was approximately
90.6 MiB, but static linking and dead-code elimination mean it is not copied
wholesale into the executable. The measured dual-backend binary increase is
18.75 MiB.
The verified FP32 ONNX model is approximately 90.4 MiB, comparable to the
existing approximately 90 MiB Candle safetensors model:
double model-asset storage.
The measured GNU/Linux build statically links ONNX Runtime and does not require
a deployed
libonnxruntime.so.The upstream
ort-sysprebuilt artifact matrix does not cover XERJ'sx86_64/aarch64 musl release targets. Standard musl binaries must remain
Candle-only unless the project adopts a reproducible ONNX Runtime musl build
or explicitly narrows the ONNX target matrix.
Benefits
autoindex -> map -> semantic queryintegration.supported behavior.
Costs and limitations
arbitrary ONNX models.
ort2.0.0-rc.12 is still a release candidate.batching.
not a general online queue with deadlines, per-tenant fairness, or
cancellation.
measured yet.
separate end-to-end backend processes.
evidence Recall@k and near-tie ANN evaluation.
runtime and are not fixed by ONNX.
Rejected/deferred approaches
ort::Session.end-to-end benefit.
Validation
The follow-up was prepared from current upstream base:
Its integration delta modifies 18 existing files across
xerj-ai,xerj-autoindex,xerj-common,xerj-engine,xerj-server, the lockfile,and the already-landed experimental guide. It does not re-add the standalone
prototype commit from #2.
The integrated source adds tests for:
The final integrated diff passed:
The integrated server path was additionally exercised live as described above:
The mandatory ES-YAML hard gate was rerun on the final integrated diff:
Reviewer checklist
compatibility?
resumability?
runtime internals?
artifacts until musl and full FinanceBench gates pass?
Follow-up gates
Before changing ONNX from experimental:
and ONNX.
tokens/s, CPU-seconds/document, p50/p95/p99, RSS/HWM, and disk growth.
multi-hour corpus.
Honest claim boundary
Supported:
Not yet supported:
The full FinanceBench end-to-end time has not been measured.