[backend:feature] Client-side model-aware tokenization for Nova embeddings MCDB-98680 #closes - #130
Draft
KarishS2 wants to merge 5 commits into
Draft
Conversation
…e tokenization MCDB-98680
…oved tests MCDB-98680
…98680 Prod shared-qwen3-embed-0-6b returned 0.99987 for batched vs single inputs; vLLM padded GEMM noise, not a tokenizer contract miss. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
This PR enhances singlestoredb.ai.embeddings to support model-aware, client-side tokenization for Nova-hosted embedding models by introducing a per-model tokenization policy registry and a token-based chunking path that can send model-native token IDs when supported.
Changes:
- Added a per-model policy registry (
_MODEL_POLICIES) with context-window sizing and token-ID allowlisting, plus tokenizer loading and special-token affix derivation. - Extended
_ChunkedOpenAIEmbeddingsto support token-based chunking (token IDs on the wire) and to weight-reduce chunk embeddings by content-token count (excluding special-token affixes). - Expanded unit coverage and added a new “live server-contract” test suite (pytest
management-marked) to validate token-ID parity against a real deployment.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
singlestoredb/ai/embeddings.py |
Adds model policy registry, tokenizer loading/inspection, token chunker, and token-ID sending path for Nova models. |
singlestoredb/tests/test_embeddings.py |
Adds offline unit tests for registry behavior, affix derivation, chunk sizing, weighting, and fallback warnings via injected fake tokenizers/modules. |
singlestoredb/tests/test_embeddings_live.py |
Introduces live parity tests (management-marked) to validate server interpretation of token IDs and special-token requirements. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+70
to
+74
| f'because {reason}. Embeddings remain correct, but long inputs are split on a ' | ||
| f'coarse character budget rather than on the model context window.', | ||
| TokenizationFallbackWarning, | ||
| stacklevel=3, | ||
| ) |
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
For Nova models the SDK chunks long text itself, because tiktoken is the wrong tokenizer
for them. It had no way to know how a model tokenizes or how big its window is, so it
split on a 24,000-character guess.
This adds a registry of that per-model information: which tokenizer, the real context
window, and which platforms accept token IDs. Registered models are tokenized with their
own tokenizer, split on the real limit, and sent as token IDs. Supporting a new model
later is one entry.
Entries are added by hand after checking against a live deployment, because wrong token
IDs return a normal-looking vector, not an error. Today:
Qwen/Qwen3-Embedding-0.6Bat32,768 tokens, where a 119,000-character document drops from 5 pieces to 1 (Test Plan).
Azure and Bedrock unchanged. Unregistered Nova models keep the 24,000-character split and
now warn.
Test Plan
pytest singlestoredb/tests/test_embeddings.py— 14 unit tests. They cover whichmodel gets which path, the special tokens we detect, the chunk size math, the
platform check, and each way the code falls back.
singlestoredb/tests/test_embeddings_live.py— tests that need a real deployment, sothey are marked
managementand CI skips them. Sending token IDs gives the samevector as sending text (cosine > 0.9999). Two control tests confirm the server really
is reading Qwen token IDs: tiktoken IDs give a different vector, and so does dropping
the special tokens.
shared-qwen3-embed-0-6bdeployment (NovaMultiTenant,Qwen/Qwen3-Embedding-0.6B) from a Nova notebook.Original MCDB-98680 repro, versus a local
sentence_transformersreference:Long input, 118,923 characters with one distinctive fact buried in the middle.
vs truthis cosine against the whole document embedded in a single chunk.query matchis cosine between a query asking for that fact and the document vector,i.e. how well vector search would rank it.
TODO: single-tenant
Novahas no parity run and is allowed by inference fromNovaMultiTenantserving the same image. The token path also requirestransformersand tokenizer files at runtime; if either is unavailable it warns and falls back to
current behavior.
Deployment Plan
Azure and Bedrock paths are untouched.
Subscribers
JIRA Issues
MCDB-98680