Skip to content

perf(onnx): pad each batch to its own longest text, not to 512 - #124

Merged
ManSio merged 2 commits into
mainfrom
perf/onnx-dynamic-padding
Nov 12, 2026
Merged

ManSio merged 2 commits into
mainfrom
perf/onnx-dynamic-padding

Conversation

@ManSio

@ManSio ManSio commented Nov 12, 2026

Copy link
Copy Markdown
Owner

perf(onnx): pad each batch to its own longest text, not to 512

The dense build spent 2 210.8 s on 7 307 chunks (3.3 ch/s). The cost did not
depend on text length: a batch of 32 short texts took 7.9 s, a batch of 32 long
texts 6.2 s. Root cause: the ONNX tokenizer was configured with
enable_padding(length=512) and both inference paths padded every input to
max_length before session.run, so a 31-token chunk paid for 512 tokens.

Direct session, batch=32 (this machine):
seq= 32 -> 0.17 s (190.7 ch/s)
seq=128 -> 0.86 s ( 37.1 ch/s)
seq=256 -> 2.30 s ( 13.9 ch/s)
seq=512 -> 6.33 s ( 5.1 ch/s)

Fix: pad to the longest text in the current batch; keep truncation at 512. The
indexer already sorts chunks by length (index_project_runner), so batch maxima
are close to the real lengths. After the fix, server timings:
32 x short 0.06 s (464 ch/s)
32 x medium 0.63 s ( 50 ch/s)
32 x long 6.16 s (5.2 ch/s, unchanged)
Full rebuild on the same corpus: 7 419 chunks in 1 014.7 s vs 2 210.8 s before
(the "before" run shared CPU with pytest, so the clean effect is the short-batch
measurement: 0.06 s vs ~6.4 s; corpus estimate 2.13x).

Compatibility: per-batch padding changes the tensor width, and quantised
inference is not bit-identical (cosine p50 0.998, p5 0.996, min 0.863 over 7 059
identical chunks; same-index top-1 query match 14/15). ONNX_FIXED_PADDING=512
restores the legacy width for deployments that must match an old index exactly.
No quality panel was run yet - that is the gate before making this the only mode.

Docs: ONNX_FIXED_PADDING in CONFIGURATION en/ru/zh; investigation in
experiments/onnx_dense_2026_11/REPORT.md; diary entry with numbers.

Gates: pytest -m "not integration and not benchmark" 2608 passed / 10 skipped
(was 2607/9), ruff OK, dense smoke_e2e PASSED (4 checks, step 4 = vector,
provider=onnx, dim=384, results=3), stale_detector and docs=config guards OK.

The dense build spent 2 210.8 s on 7 307 chunks (3.3 ch/s). The cost did not
depend on text length: a batch of 32 short texts took 7.9 s, a batch of 32 long
texts 6.2 s. Root cause: the ONNX tokenizer was configured with
enable_padding(length=512) and both inference paths padded every input to
max_length before session.run, so a 31-token chunk paid for 512 tokens.

Direct session, batch=32 (this machine):
  seq= 32 -> 0.17 s (190.7 ch/s)
  seq=128 -> 0.86 s ( 37.1 ch/s)
  seq=256 -> 2.30 s ( 13.9 ch/s)
  seq=512 -> 6.33 s (  5.1 ch/s)

Fix: pad to the longest text in the current batch; keep truncation at 512. The
indexer already sorts chunks by length (index_project_runner), so batch maxima
are close to the real lengths. After the fix, server timings:
  32 x short   0.06 s (464 ch/s)
  32 x medium  0.63 s ( 50 ch/s)
  32 x long    6.16 s (5.2 ch/s, unchanged)
Full rebuild on the same corpus: 7 419 chunks in 1 014.7 s vs 2 210.8 s before
(the "before" run shared CPU with pytest, so the clean effect is the short-batch
measurement: 0.06 s vs ~6.4 s; corpus estimate 2.13x).

Compatibility: per-batch padding changes the tensor width, and quantised
inference is not bit-identical (cosine p50 0.998, p5 0.996, min 0.863 over 7 059
identical chunks; same-index top-1 query match 14/15). ONNX_FIXED_PADDING=512
restores the legacy width for deployments that must match an old index exactly.
No quality panel was run yet - that is the gate before making this the only mode.

Docs: ONNX_FIXED_PADDING in CONFIGURATION en/ru/zh; investigation in
experiments/onnx_dense_2026_11/REPORT.md; diary entry with numbers.

Gates: pytest -m "not integration and not benchmark" 2608 passed / 10 skipped
(was 2607/9), ruff OK, dense smoke_e2e PASSED (4 checks, step 4 = vector,
provider=onnx, dim=384, results=3), stale_detector and docs=config guards OK.
@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e3de5ca2-58b7-4a7d-9585-a6dc8160d276

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Before deciding on #124, run the documented 48-query panel on two dense indexes
of this repository: control = fixed-512 padding (index + query), treatment =
per-batch padding. Same hybrid `quality` path, same text index, only the vector
table and query padding differ.

  control    hit@1=0.562 hit@5=0.688 MRR=0.601 p50=274ms
  treatment  hit@1=0.583 hit@5=0.688 MRR=0.617 p50=258ms
  paired: hit@1 +1/-0, hit@5 0/0 (33/48 both), MRR +2/-0

No query became worse on the panel; the panel is small (n=48), so the claim is
"non-inferior on the frozen panel", not a general quality statement.

Adds the reproducible harness (quality_panel.py), both result JSONs and the
report section. Gates: pytest 3 dynamic-padding tests passed, ruff OK.
@ManSio
ManSio merged commit 801621d into main Nov 12, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant