embeddings: MAX_INPUT_TOKENS cap on the tokens taken from each input text (manticoresearch#4816) - #201
Conversation
741be41 to
efec895
Compare
donhardman
left a comment
There was a problem hiding this comment.
Thanks for PR. It's all good to me. I also rebased your branch to latest Qwen optimization we had in master
efec895 to
2e37406
Compare
2e37406 to
72fcbd3
Compare
|
Rebased onto current master (the branch had drifted into conflict): the FFI version moved to 9 (8 was taken by the chunked-embeddings work meanwhile), and two test call sites added upstream since ( |
…text (manticoresearch#4816) Auto-embedding time grows superlinearly with input length and the only limit was the model's own context window (32,768 tokens for Qwen3-Embedding-0.6B), so a single long document could hold an embedding statement for minutes and, under client retries, stack statements until the daemon ran out of memory (manticoresoftware/manticoresearch#4816). - ModelOptions.max_input_tokens (None = model limit); LocalModel::new applies min(model limit, cap) to every architecture's max_input_len, so all predict paths truncate on it. The cap can only lower the limit; 0 means no cap. - FFI: load_model gains a trailing i32 max_input_tokens; EmbedLib version 4 -> 5. - knn::ModelSettings_t::m_iMaxInputTokens, part of the model cache key, passed to load_model; SUPPORTED_EMBEDDINGS_LIB_VER 4 -> 5. - Unit test test_max_input_tokens_cap (lowers, never raises, truncates).
72fcbd3 to
9fef334
Compare
Embeddings-library half of the fix for manticoresoftware/manticoresearch#4816 (the daemon half — DDL option, ALTER, SHOW CREATE TABLE, docs, CLT test — is a separate PR on manticoresearch that pins this commit in the
mclsubmodule).Problem
Auto-embedding time grows superlinearly with input length, and the only limit is the model's own context window (
max_position_embeddings, 32,768 tokens forQwen/Qwen3-Embedding-0.6B). Measured on 29.0.2 (details in the issue): 500 B → 4.6 s, 2,000 B → 35.6 s, 5,000 B → 196 s per document on CPU. A single long document holds an embedding statement for minutes; clients time out, the statement keeps running, retries stack more statements and the daemon is eventually OOM-killed.Change
A per-model cap on the number of tokens taken from each input text,
max_input_tokens(0 /None= the model's own limit; the cap can only lower it):ModelOptions.max_input_tokens: Option<usize>;LocalModel::newappliesmin(model limit, cap)to every architecture'smax_input_len(BERT / T5 / Causal / Quantized / ONNX), so all predict paths truncate on it — no change to the truncation code itself.load_modelgains a trailingi32 max_input_tokens;EmbedLib.version4 → 5 (header regenerated accordingly).knn::ModelSettings_t::m_iMaxInputTokens, part of the model cache key, passed through inknn/embeddings.cpp;SUPPORTED_EMBEDDINGS_LIB_VER4 → 5.test_max_input_tokens_cap: cap lowers the limit, never raises it, zero = no cap, and a text plus a long tail embeds identically to the text alone under the cap (and differs without it).Remote providers (OpenAI / Voyage / Jina) are not capped by this change — happy to add pre-truncation there in a follow-up if wanted.
Verification
cargo check --tests,cargo fmt --check,cargo test --release --features download-ort test_max_input_tokens_cap(rust 1.98).sentence-transformers/all-MiniLM-L6-v2: with a cap of 6 tokens, "a red apple on the table" and the same text followed by 300 extra words produce byte-identical vectors, a different head differs; uncapped the tail changes the vector. A ~5 KB document: 38 ms capped (64 tokens) vs 409 ms uncapped per REPLACE.