docs: simplify and align model card
Browse files
README.md
CHANGED
|
@@ -16,197 +16,181 @@ pipeline_tag: feature-extraction
|
|
| 16 |
|
| 17 |
# constella-zero
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
tower; Zero or the stronger [`constella-nano`](https://huggingface.co/DylanCouzon/constella-nano)
|
| 23 |
-
can then query that same 1024-dimensional index without re-encoding it. Zero is a 30,522 × 1024
|
| 24 |
-
int8 lookup table—not a transformer—so encoding is a gather and weighted sum.
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
| 29 |
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
```console
|
| 33 |
pip install "fastembed @ git+https://github.com/Dylancouzon/fastembed.git@constella-research-preview"
|
| 34 |
pip install qdrant-client
|
| 35 |
```
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
```python
|
| 38 |
-
import numpy as np
|
| 39 |
from fastembed import TextEmbedding
|
|
|
|
| 40 |
|
| 41 |
NAME = "DylanCouzon/constella-zero"
|
| 42 |
-
query_model = TextEmbedding(NAME)
|
| 43 |
-
q = np.asarray(next(iter(query_model.embed(["how do mrna vaccines work?"]))))
|
| 44 |
-
assert q.shape == (1024,) and q.dtype == np.float32
|
| 45 |
-
```
|
| 46 |
-
|
| 47 |
-
FastEmbed fetches `model.onnx` and the tokenizer. Pooling and L2 normalization happen inside the
|
| 48 |
-
graph.
|
| 49 |
-
|
| 50 |
-
### The document side
|
| 51 |
-
|
| 52 |
-
```python
|
| 53 |
DOC_NAME = "DylanCouzon/stella-en-400M-v5-doc-onnx"
|
| 54 |
-
|
| 55 |
-
|
| 56 |
"mRNA vaccines deliver messenger RNA encoding a viral antigen.",
|
| 57 |
"The Treaty of Westphalia ended the Thirty Years' War in 1648.",
|
| 58 |
]
|
| 59 |
-
D = np.stack(list(doc_model.embed(docs)))
|
| 60 |
-
assert D.shape == (2, 1024) and D.dtype == np.float32
|
| 61 |
-
```
|
| 62 |
-
|
| 63 |
-
The document tower runs once per document; Zero runs on every query. Do not use the document
|
| 64 |
-
model's unprompted path as a Stella query encoder.
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
```python
|
| 69 |
-
from qdrant_client import QdrantClient, models
|
| 70 |
|
| 71 |
client = QdrantClient(":memory:")
|
| 72 |
client.create_collection(
|
| 73 |
-
"
|
|
|
|
| 74 |
)
|
| 75 |
client.upsert(
|
| 76 |
-
"
|
| 77 |
points=[
|
| 78 |
-
models.PointStruct(id=i, vector=
|
| 79 |
-
for i, text in enumerate(
|
|
|
|
|
|
|
| 80 |
],
|
| 81 |
)
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
```
|
| 85 |
|
| 86 |
-
|
|
|
|
| 87 |
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
```python
|
| 91 |
from huggingface_hub import snapshot_download
|
| 92 |
import sys
|
| 93 |
|
| 94 |
-
|
| 95 |
-
sys.path.insert(0,
|
|
|
|
| 96 |
from zero_encoder import ZeroQueryEncoder
|
| 97 |
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
assert np.abs(q_np[0] - q).max() < 1e-5
|
| 101 |
```
|
| 102 |
|
| 103 |
-
Zero and Nano share a document space, not a retrieval-quality guarantee: they have different
|
| 104 |
-
measured behavior, and interchangeability does not mean parity, equivalence, or a tie.
|
| 105 |
-
|
| 106 |
## How it works
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
`[CLS]`. Learned token weights are folded into the rows. This is still a bag of tokens: word order,
|
| 112 |
-
negation, and syntax are not represented.
|
| 113 |
|
| 114 |
-
|
|
|
|
| 115 |
|
| 116 |
-
##
|
| 117 |
-
|
| 118 |
-
| file | purpose | size |
|
| 119 |
-
|---|---|---:|
|
| 120 |
-
| `model.onnx` | FastEmbed/ONNX Runtime; pooled normalized `(batch, 1024)` output | 31 MB |
|
| 121 |
-
| `model_tokens.onnx` | token-level `(batch, sequence, 1024)` output | 31 MB |
|
| 122 |
-
| `model.npz` | NumPy reference path | 94 MB |
|
| 123 |
|
| 124 |
-
|
| 125 |
-
|
|
|
|
| 126 |
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
The headline partition is **clean-4**: NFCorpus, SCIDOCS, SciFact, and TREC-COVID. ArguAna and
|
| 130 |
-
FiQA remain beside it with `†` because Stella discloses training/evaluation contact with them.
|
| 131 |
-
All values are exact-search nDCG@10.
|
| 132 |
-
|
| 133 |
-
| system | NFCorpus **(clean-4)** | SCIDOCS **(clean-4)** | SciFact **(clean-4)** | TREC-COVID **(clean-4)** | ArguAna† | FiQA† |
|
| 134 |
|---|---:|---:|---:|---:|---:|---:|
|
| 135 |
-
| constella-zero
|
| 136 |
| constella-nano | 0.363080 | 0.217710 | 0.721097 | 0.787116 | 0.623296 | 0.477765 |
|
| 137 |
| BM25 | 0.3180 | 0.1565 | 0.6791 | 0.6099 | 0.4878 | 0.2532 |
|
| 138 |
-
| Stella
|
| 139 |
-
|
| 140 |
-
† Stella-disclosed training/evaluation contact; excluded from clean-4.
|
| 141 |
-
|
| 142 |
-
Zero did **not** confirmatorily beat BM25. M7 C2 was +0.0165 across all six with raw 95% interval
|
| 143 |
-
[+0.0017, +0.0311], but its sign-flip p=0.0149 failed the Holm threshold of 0.0083. On clean-4,
|
| 144 |
-
the descriptive contrast was -0.0311 [-0.0517, -0.0109], with Zero at 0.4098 versus BM25 at
|
| 145 |
-
0.4409. Superiority is **UNESTABLISHED**.
|
| 146 |
-
|
| 147 |
-
The deployable hybrid recommendation and registered operator of record are distinct:
|
| 148 |
|
| 149 |
-
|
| 150 |
-
|---|---:|---:|---:|
|
| 151 |
-
| Qdrant DBSF | 100 | 0.4887 | 0.4912 |
|
| 152 |
-
| M7 convex0 (`w=0.8`) | 1000 | 0.4911 | 0.4866 |
|
| 153 |
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
queries) and FiQA (55), so the clean-4 figures are unchanged either way.
|
| 160 |
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
over OpenSearch: +0.0043, raw 95% interval [-0.0063, +0.0151], p=0.219.
|
| 165 |
|
| 166 |
-
|
| 167 |
-
[M21 benchmark ledger](https://github.com/Dylancouzon/asymmetric-dual-encoders/blob/main/m21/BENCHMARKS.md).
|
| 168 |
|
| 169 |
-
|
|
|
|
|
|
|
| 170 |
|
| 171 |
-
|
| 172 |
-
broad domain coverage.
|
| 173 |
-
- Stella contact with ArguAna and FiQA makes all-six results secondary to clean-4.
|
| 174 |
-
- Zero is an English-only bag of tokens and truncates beyond 512 wordpieces.
|
| 175 |
-
- The document side is not cheap: the 400M-parameter Stella tower still runs at indexing time.
|
| 176 |
-
|
| 177 |
-
## Costs
|
| 178 |
-
|
| 179 |
-
These are synthetic query latencies, not workload estimates. The common three-model protocol used
|
| 180 |
-
three fresh processes per model, batch one, four CPU threads; hydration includes imports,
|
| 181 |
-
verification, and load but excludes interpreter startup; first inference is separate; warm timing
|
| 182 |
-
uses five warmups and twenty 20-word samples; the OS disk cache was not flushed.
|
| 183 |
-
|
| 184 |
-
| model | hydration | first query | warm 20-word p50 | peak RSS | model assets |
|
| 185 |
|---|---:|---:|---:|---:|---:|
|
| 186 |
| constella-zero | 0.2618 s | 0.3529 ms | 0.1119 ms | 275.4 MiB | 90.1 MiB |
|
| 187 |
| bge-small | 0.6726 s | 8.2401 ms | 6.8400 ms | 291.0 MiB | 127.6 MiB |
|
| 188 |
| constella-nano | 0.6907 s | 7.6685 ms | 7.2511 ms | 280.9 MiB | 132.3 MiB |
|
| 189 |
|
| 190 |
-
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
|
| 193 |
## Training
|
| 194 |
|
| 195 |
-
The table was trained by L2 regression against Stella query embeddings
|
| 196 |
-
220,632 query-only rows from Amazon ESCI, FEVER,
|
| 197 |
-
|
| 198 |
-
TriviaQA are Apache-2.0.
|
| 199 |
|
| 200 |
-
|
| 201 |
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
|
| 211 |
-
The
|
| 212 |
-
|
|
|
|
| 16 |
|
| 17 |
# constella-zero
|
| 18 |
|
| 19 |
+
constella-zero is a small English query encoder for semantic search. It uses an int8 token lookup
|
| 20 |
+
table instead of a transformer, making it useful when query latency matters more than maximum
|
| 21 |
+
retrieval quality.
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
+
It produces normalized 1024-dimensional vectors that search documents encoded by
|
| 24 |
+
[`stella-en-400M-v5-doc-onnx`](https://huggingface.co/DylanCouzon/stella-en-400M-v5-doc-onnx).
|
| 25 |
+
The same document index also works with the stronger
|
| 26 |
+
[`constella-nano`](https://huggingface.co/DylanCouzon/constella-nano) query encoder.
|
| 27 |
|
| 28 |
+
| Property | Value |
|
| 29 |
+
|---|---|
|
| 30 |
+
| Role | Query encoder |
|
| 31 |
+
| Output | 1024-dimensional normalized fp32 vector |
|
| 32 |
+
| Architecture | 30,522 x 1,024 int8 token lookup table |
|
| 33 |
+
| Languages | English |
|
| 34 |
+
| Maximum input length | 512 tokens |
|
| 35 |
+
| Query prefix | None |
|
| 36 |
+
| Document encoder | `DylanCouzon/stella-en-400M-v5-doc-onnx` |
|
| 37 |
+
|
| 38 |
+
## Installation
|
| 39 |
+
|
| 40 |
+
Native FastEmbed support is currently available from the Constella preview branch:
|
| 41 |
|
| 42 |
```console
|
| 43 |
pip install "fastembed @ git+https://github.com/Dylancouzon/fastembed.git@constella-research-preview"
|
| 44 |
pip install qdrant-client
|
| 45 |
```
|
| 46 |
|
| 47 |
+
## Usage
|
| 48 |
+
|
| 49 |
+
Encode documents once with the document model, then encode queries with constella-zero. The
|
| 50 |
+
example below creates an in-memory Qdrant collection, but the vectors can be used with any vector
|
| 51 |
+
database that supports cosine similarity.
|
| 52 |
+
|
| 53 |
```python
|
|
|
|
| 54 |
from fastembed import TextEmbedding
|
| 55 |
+
from qdrant_client import QdrantClient, models
|
| 56 |
|
| 57 |
NAME = "DylanCouzon/constella-zero"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
DOC_NAME = "DylanCouzon/stella-en-400M-v5-doc-onnx"
|
| 59 |
+
|
| 60 |
+
documents = [
|
| 61 |
"mRNA vaccines deliver messenger RNA encoding a viral antigen.",
|
| 62 |
"The Treaty of Westphalia ended the Thirty Years' War in 1648.",
|
| 63 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
+
document_model = TextEmbedding(DOC_NAME)
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
client = QdrantClient(":memory:")
|
| 68 |
client.create_collection(
|
| 69 |
+
"documents",
|
| 70 |
+
vectors_config=models.VectorParams(size=1024, distance=models.Distance.COSINE),
|
| 71 |
)
|
| 72 |
client.upsert(
|
| 73 |
+
"documents",
|
| 74 |
points=[
|
| 75 |
+
models.PointStruct(id=i, vector=embedding.tolist(), payload={"text": text})
|
| 76 |
+
for i, (text, embedding) in enumerate(
|
| 77 |
+
zip(documents, document_model.embed(documents))
|
| 78 |
+
)
|
| 79 |
],
|
| 80 |
)
|
| 81 |
+
|
| 82 |
+
query_model = TextEmbedding(NAME)
|
| 83 |
+
query_embedding = next(iter(query_model.embed(["how do mRNA vaccines work?"])))
|
| 84 |
+
results = client.query_points(
|
| 85 |
+
"documents", query=query_embedding.tolist(), limit=2
|
| 86 |
+
).points
|
| 87 |
+
|
| 88 |
+
for result in results:
|
| 89 |
+
print(result.score, result.payload["text"])
|
| 90 |
```
|
| 91 |
|
| 92 |
+
FastEmbed handles pooling and L2 normalization. Do not use the document model as an unprompted
|
| 93 |
+
query encoder. Use constella-zero, constella-nano, or Stella's prompted query path instead.
|
| 94 |
|
| 95 |
+
### NumPy reference implementation
|
| 96 |
+
|
| 97 |
+
The repository also includes `zero_encoder.py`, a reference implementation that does not require
|
| 98 |
+
FastEmbed or ONNX Runtime:
|
| 99 |
|
| 100 |
```python
|
| 101 |
from huggingface_hub import snapshot_download
|
| 102 |
import sys
|
| 103 |
|
| 104 |
+
model_directory = snapshot_download("DylanCouzon/constella-zero")
|
| 105 |
+
sys.path.insert(0, model_directory)
|
| 106 |
+
|
| 107 |
from zero_encoder import ZeroQueryEncoder
|
| 108 |
|
| 109 |
+
model = ZeroQueryEncoder(model_directory, variant="int8")
|
| 110 |
+
query_embeddings = model.encode(["how do mRNA vaccines work?"])
|
|
|
|
| 111 |
```
|
| 112 |
|
|
|
|
|
|
|
|
|
|
| 113 |
## How it works
|
| 114 |
|
| 115 |
+
The encoder tokenizes each query with WordPiece, looks up a learned vector for every token, and
|
| 116 |
+
combines those vectors into one query embedding. Repeated tokens receive diminishing weight: a
|
| 117 |
+
token that occurs `c` times contributes a total weight of `sqrt(c)`. The result is L2-normalized.
|
|
|
|
|
|
|
| 118 |
|
| 119 |
+
This is a bag-of-tokens model. It does not represent word order, syntax, or negation directly.
|
| 120 |
+
Learned token weights are already included in the table.
|
| 121 |
|
| 122 |
+
## Retrieval results
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
+
The table reports exact-search nDCG@10. ArguAna and FiQA are marked because the Stella teacher
|
| 125 |
+
discloses training or evaluation contact with those datasets. Results on those two datasets should
|
| 126 |
+
therefore be interpreted separately from the other four.
|
| 127 |
|
| 128 |
+
| System | NFCorpus | SCIDOCS | SciFact | TREC-COVID | ArguAna* | FiQA* |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|---|---:|---:|---:|---:|---:|---:|
|
| 130 |
+
| constella-zero | 0.3124 | 0.1677 | 0.6101 | 0.5490 | 0.5916 | 0.3728 |
|
| 131 |
| constella-nano | 0.363080 | 0.217710 | 0.721097 | 0.787116 | 0.623296 | 0.477765 |
|
| 132 |
| BM25 | 0.3180 | 0.1565 | 0.6791 | 0.6099 | 0.4878 | 0.2532 |
|
| 133 |
+
| Stella query encoder | 0.4134 | 0.2395 | 0.7796 | 0.8234 | 0.6369 | 0.5536 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
+
Note: Stella discloses training or evaluation contact with ArguAna and FiQA.
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
+
For hybrid retrieval, combine constella-zero with BM25 using Qdrant's distribution-based score
|
| 138 |
+
fusion (DBSF) and prefetch 100 candidates from each side. This setup scored 0.4887 mean nDCG@10
|
| 139 |
+
across all six datasets and 0.4912 across the four datasets without disclosed Stella contact.
|
| 140 |
+
The evaluated lexical side used `bm25s` with Lucene defaults, so results may differ with another
|
| 141 |
+
BM25 implementation.
|
|
|
|
| 142 |
|
| 143 |
+
Zero did not establish an improvement over BM25 in the six-dataset statistical test. Its measured
|
| 144 |
+
difference was +0.0165 nDCG@10, but the adjusted test threshold was not met. This is not an
|
| 145 |
+
equivalence claim.
|
|
|
|
| 146 |
|
| 147 |
+
## Query encoding cost
|
|
|
|
| 148 |
|
| 149 |
+
These measurements cover the query encoder only. They use batch size 1, four CPU threads, five
|
| 150 |
+
warmups, and twenty synthetic 20-word queries in each of three fresh processes. They do not
|
| 151 |
+
include vector search or end-to-end application latency.
|
| 152 |
|
| 153 |
+
| Model | Load time | First query | Warm query p50 | Peak RSS | Measured assets |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
|---|---:|---:|---:|---:|---:|
|
| 155 |
| constella-zero | 0.2618 s | 0.3529 ms | 0.1119 ms | 275.4 MiB | 90.1 MiB |
|
| 156 |
| bge-small | 0.6726 s | 8.2401 ms | 6.8400 ms | 291.0 MiB | 127.6 MiB |
|
| 157 |
| constella-nano | 0.6907 s | 7.6685 ms | 7.2511 ms | 280.9 MiB | 132.3 MiB |
|
| 158 |
|
| 159 |
+
## Files
|
| 160 |
+
|
| 161 |
+
| File | Purpose | Size |
|
| 162 |
+
|---|---|---:|
|
| 163 |
+
| `model.onnx` | Pooled and normalized FastEmbed graph | 31 MB |
|
| 164 |
+
| `model_tokens.onnx` | Token-level output for custom pooling | 31 MB |
|
| 165 |
+
| `model.npz` | NumPy reference implementation | 94 MB |
|
| 166 |
+
|
| 167 |
+
Both ONNX graphs use opset 17 and standard operators. The int8 table is dequantized inside the
|
| 168 |
+
graph with one fp32 scale per row.
|
| 169 |
|
| 170 |
## Training
|
| 171 |
|
| 172 |
+
The released table was trained by L2 regression against Stella query embeddings. Training used
|
| 173 |
+
338,076 usable query-document pairs plus 220,632 query-only rows from Amazon ESCI, FEVER,
|
| 174 |
+
HotpotQA, SQuAD, NQ Open, TriviaQA, and Mr. TyDi English. MS MARCO was excluded.
|
|
|
|
| 175 |
|
| 176 |
+
Wikipedia-derived data retains CC BY-SA attribution. Amazon ESCI and TriviaQA are Apache-2.0.
|
| 177 |
|
| 178 |
+
## Limitations
|
| 179 |
+
|
| 180 |
+
- The model is English-only and truncates inputs after 512 tokens.
|
| 181 |
+
- As a bag-of-tokens model, it is weak at distinctions that depend on word order, syntax, or
|
| 182 |
+
negation.
|
| 183 |
+
- Retrieval quality is lower than constella-nano and the full Stella query encoder on the six
|
| 184 |
+
reported datasets.
|
| 185 |
+
- Document indexing still requires the 400M-parameter Stella document encoder.
|
| 186 |
+
- The reported retrieval evaluation covers six datasets and does not establish performance in
|
| 187 |
+
other domains or applications.
|
| 188 |
+
|
| 189 |
+
## License and provenance
|
| 190 |
+
|
| 191 |
+
The model is MIT licensed. It was distilled from
|
| 192 |
+
[`NovaSearch/stella_en_400M_v5`](https://huggingface.co/NovaSearch/stella_en_400M_v5) at revision
|
| 193 |
+
`ffeb2b7ee715c226d4ffe5e4619f7dbb48624c20`, which is also MIT licensed.
|
| 194 |
|
| 195 |
+
The released int8 table has SHA-256
|
| 196 |
+
`a7007b1a6af120b976f093fd69ddcb5001996ec0b84b5864b4fd25d7af878abf`.
|