perf(embedding): add bounded ONNX throughput controls - #70
Merged
Conversation
ONNX inference dominates the measured FinanceBench ingest path, but the engine previously fixed caller scheduling at 64 passages and serialized every native call through one Runtime session. Operators could not test a larger length-sorting population or use a second session without editing source. Expose embedding.onnx_scheduling_window (1..=4096, default 64) and embedding.onnx_session_pool_size (1..=2, default 1). Construct the requested session pool atomically from one verified asset read, lease one member for each complete scheduling call, and run at most two caller windows concurrently. Buffer sibling results in ordinal order and preserve per-item retry behavior. Keep call and byte admission permits inside spawn_blocking so cancelling an async waiter cannot release capacity while native inference is still running. Add bounded-config, default-path, atomic-construction, cancellation, ordering, and real-asset bit-equivalence coverage. Document exact TOML usage and the memory tradeoff; model selection, pooling, dimensions, and default lexical behavior do not change. The same-binary FB20 A/B measured pool1/intra16/W512 at 86.151 s median and pool2/intra8/W512 at 75.015 s, a 12.93% end-to-end reduction (1.148x). FB4 reproduced 13.22%. Exact persisted vector-bit hashes, search/provenance results, and restart state matched. Pool two stays opt-in because memory signals were mixed. Focused gates: xerj-ai 40 passed / 1 ignored plus the explicit real-model ignored test passed; xerj-common config 14/14; xerj-engine default 230/230 and ONNX 236/236; cargo fmt; scoped clippy with -D warnings. The ES-YAML 1360/0/3 gate is run separately before publication.
xerj-org
approved these changes
Jul 29, 2026
xerj-org
left a comment
Owner
There was a problem hiding this comment.
Review: APPROVE
Full-diff correctness + security review:
- Bounds are real:
Config::validaterejectsonnx_scheduling_windowoutside1..=4096andonnx_session_pool_sizeoutside1..=2, with edge-value tests for both. - Defaults verified unchanged: window 64 (previously the hard-coded
MAX_PASSAGES_PER_WINDOW), pool 1; the dual-session scheduler is gated ononnx_pinned && pool_size == 2and unit-tested unreachable for defaults and non-ONNX backends. - Pool lifecycle is sound: lease-by-pop / return-on-
DropwithCondvarwakeup; sessions return to the pool even on panic (poisoned-mutex path handled viainto_inner); construction is atomic with a drop-partial-members regression test. - Net security improvement: admission
call/bytepermits previously lived in the async fn, so cancelling the HTTP waiter released capacity while native ONNX inference was still running (over-admission). Moving the permits into thespawn_blockingclosure closes that, andcancelled_waiter_keeps_admission_charged_until_blocking_work_returnspins it. - Model integrity path intact: SHA-256 verification of model + tokenizer bytes still runs before pool construction; pool size is part of the backend identity so mixed-pool handles can't share a backend.
- Nothing off-topic in the diff; benchmark claims are stated with unusual honesty (12.93% causal attribution vs the 23.61% historical figure, mixed RSS results disclosed).
Non-blocking nits
- The sequential path now does
embedder.embed_batch(texts.clone())and all windows' texts are materialized up-front — a transient extra copy of the batch's text even at defaults. Iteratingwindowsby value would drop the clone. collect_ordinal_buffered_twobarriers pairwise (join!on ordinals (0,1), then (2,3)…), so a slow window idles its sibling slot; a 2-slot free-running scheduler would pipeline better. Matches the documented behavior, so fine for an opt-in experimental control.
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.
Summary
This change adds two opt-in ONNX throughput controls while preserving the current default behavior:
embedding.onnx_scheduling_windowcontrols how many passages the engine collects before handing one scheduling window to the ONNX backend. Default:64. Valid range:1..=4096.embedding.onnx_session_pool_sizecontrols how many independent ONNX Runtime sessions are constructed for one shared model configuration. Default:1. Valid range:1..=2.The default remains one serialized ONNX session with a 64-passage caller window. This PR does not change the default lexical embedding mode, select a smaller model, quantize model weights, change the 384-dimensional vector contract, or alter pooling and normalization.
Defaults are subject to change, once all benchmarks are finished.
Why
The backend already grouped similar token lengths into bounded microbatches, but the engine exposed only a source-level 64-passage scheduling window and one serialized ONNX Runtime session. FinanceBench profiling showed that ONNX inference was the dominant ingest stage and that the single session did not keep the available CPU cores busy.
A larger caller window gives the length-aware planner a better population to group. An optional second session allows two complete scheduling windows to execute concurrently. Both controls are bounded and explicit because larger windows retain more passage data and a second session changes the memory/concurrency tradeoff.
Configuration
The existing behavior requires no configuration:
The measured FinanceBench candidate used:
The session-pool setting is part of the shared backend identity, so handles with different pool sizes cannot accidentally share one initialized backend.
Runtime behavior
End-to-end benchmark
Both A/B experiments used one binary per corpus and changed only runtime configuration between the control and candidate. Every run was fresh and passed exact count, map, mapping, ordered hit ID, exact score, selected source, page-local provenance, persisted f32 vector-bit, and restart-equivalence gates.
The FB20 control runs were 84.330 s and 87.973 s. The candidate runs were 73.764 s and 76.266 s. Both candidate runs were faster than both controls.
The causal number for this PR's dual-session comparison is 12.93% lower FB20 end-to-end time, not 23.61%. The 23.61% figure compares the 75.015 s candidate with an older 98.198 s W64 observation from a different experiment epoch. It is useful historical context for the combined W64-to-W512-plus-dual progression, but it is not a controlled same-binary attribution and is not used as the headline.
Correctness evidence
FB4 preserved all 2,190 × 384 persisted f32 vectors with aggregate SHA-256
73a9c9596724895fc639221d8e8ac56bff7bed019a23aee7395fe98f870aac6f.FB20 preserved all 4,553 × 384 persisted f32 vectors with aggregate SHA-256
eb8dd923f34f026eeff68729810f6f083cf3886d883d6720e631c8804cc5250b.FB20 also preserved the 20-path to 18-unique-content to 17-accepted-document accounting, two duplicate aliases, the pinned extraction rejection, exact company/year/quarter routing, grounded lexical and semantic answers, and identical results after restart.
Resource tradeoffs
The memory result is mixed, not a universal improvement. The second session remains opt-in because FB4 server RSS increased materially and the narrower same-timestamp combined measurement increased on both corpora. Final disk usage was effectively unchanged on FB4 and increased 0.46% on FB20.
Benchmark provenance
64afad2404fb04238869d411187edda0d0e83e9547d28dd6216b47d61d38b27eabc4730ddec1b04ae40ed27468e0db084e6eb58df2874ea31c6aeb8daf4f4e7e864189bc1d9e5859/workspace/.tmp/fb4-dual-session-ab-run-20260729/RESULTS.md7a522b87f8f4736826e34ff95f7e55550479461b6f071d16646577081d4baaf90159cc82d886d0c27bcb430ef79f6f429149f1410c8118c963721f4328b9b01d/workspace/.tmp/fb20-dual-session-ab-run-20260729/RESULTS.md9b28515b1634ecedbfc03f0b793d25f68dbe0d3cbe47ae21f39d27253e5eec130d488bf11c98fe524a77d3ff02fedc20d0d77b225c0e2e45ff77bf0329340f28The contribution branch is a minimal extraction onto current
main; it intentionally does not include the prototype's unrelated phase-attribution stack. The benchmark hashes above therefore identify the exact measured prototype, while the focused tests below cover the extracted production behavior.Tests
cargo fmt --all --checkcargo test -p xerj-ai --features onnx-experimental— 40 passed / 1 ignoredcargo test -p xerj-common config::tests::— 14 passedcargo test -p xerj-engine --features onnx-experimental semantic_embedding_window_tests::— focused order/window tests passedcargo clippy -p xerj-ai --all-targets --features onnx-experimental -- -D warningscargo clippy -p xerj-common --all-targets -- -D warningscargo clippy -p xerj-engine --lib --features onnx-experimental -- -D warningsLimitations