-
Stefy Lanza (nextime / spora ) authored
Only the GGUF path ever chunked. Every other backend passed `truncation=True` and returned a confident UNIT vector of roughly the first page of a long document, with nothing in the response to say so: truncation=True, max_length=1800 # GME-Qwen2-VL, a hardcoded ceiling truncation=True, max_length=max_length # bge-m3 native model.encode(texts, ...) # sentence-transformers, its own cap Not an error -- a wrong answer that looks right, which is the failure mode this codebase keeps paying for. A caller had no way to tell an embedding of a document from an embedding of its first page. Now a long input is windowed to the model's real limit, every window of every input goes to the backend in ONE batch (a 40-page document must not become 40 round trips), and the chunk vectors are mean-pooled AND RE-NORMALIZED: mean pooling unit vectors does not give a unit vector, and cosine similarity on a non-unit vector is quietly wrong, so the renormalization is part of the contract rather than a tidy-up. Tunable per-model -> env -> default, the convention the admission gate already uses: embed_long_input chunk | truncate | error (default chunk) embed_chunk_pool mean | first (default mean) `truncate` stays reachable and is now DELIBERATE: for a short-text encoder a 40-page input is usually a caller bug, and an operator may prefer the old behaviour to a vector pooled from forty windows. `error` says so out loud. AND IT IS REPORTED: resp.chunked = {"windows": [...], "pool": ...} whenever any input needed more than one. Replacing a silent truncation with a silent pooling would have been the same bug with a better answer. The GGUF path's hard-won guard now covers every backend: each window is RE-ENCODED and shrunk until it verifiably fits, because a detokenize->retokenize round trip is not token-count-stable (specials and BOS are re-added) and a window cut at exactly the limit can re-inflate past it -- which is how that path first tripped llama.cpp's GGML_ASSERT. Wired: sentence-transformers, and GME-Qwen2-VL (windowed BEFORE the chat prompt is built around the text, since the processor's own truncation would cut the template's closing tokens as readily as the body). GGUF already chunked. NOT wired, on purpose: the CLIP family (clip, geoclip, vpr) takes 77 tokens BY DESIGN -- those encoders embed captions, not documents, and pooling forty windows of a contract into a CLIP vector would be meaningless. A test asserts this so "chunk everywhere" is not later read as "chunk where it makes no sense". NOT wired, needs a decision: bge-m3's native multi-vector path returns dense PLUS sparse and colbert. Dense mean-pools fine, but sparse would need max-merging per token id across windows and colbert would concatenate per-token vectors. Those are retrieval semantics, not a mechanical change, and improvising them would degrade search in a way that looks like a model problem. 26 tests. Suite at the 25 pre-existing failures. Co-Authored-By:Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXRMe9ZPUQCCCtBai9uqt3
7f598fdb