DylanCouzon commited on
Commit
c231ef5
·
verified ·
1 Parent(s): 0e9cd89

docs: simplify and align model card

Browse files
Files changed (1) hide show
  1. README.md +115 -131
README.md CHANGED
@@ -16,197 +16,181 @@ pipeline_tag: feature-extraction
16
 
17
  # constella-zero
18
 
19
- **constella-zero** is the smallest query encoder in an asymmetric retrieval family. Documents are
20
- indexed once, in the cloud, with the frozen
21
- [`stella-en-400M-v5-doc-onnx`](https://huggingface.co/DylanCouzon/stella-en-400M-v5-doc-onnx)
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
- > **Research preview.** The registered reserved-four evaluation and broad descriptive BEIR-18
27
- > validation are pending and unspent; no result is claimed for either. The three models are
28
- > registered on the preview branch below, not in an upstream FastEmbed release yet.
 
29
 
30
- ## Usage
 
 
 
 
 
 
 
 
 
 
 
 
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
- doc_model = TextEmbedding(DOC_NAME)
55
- docs = [
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
- ### With Qdrant
67
-
68
- ```python
69
- from qdrant_client import QdrantClient, models
70
 
71
  client = QdrantClient(":memory:")
72
  client.create_collection(
73
- "docs", vectors_config=models.VectorParams(size=1024, distance=models.Distance.COSINE)
 
74
  )
75
  client.upsert(
76
- "docs",
77
  points=[
78
- models.PointStruct(id=i, vector=D[i].tolist(), payload={"text": text})
79
- for i, text in enumerate(docs)
 
 
80
  ],
81
  )
82
- hits = client.query_points("docs", query=q.tolist(), limit=2).points
83
- print(hits[0].payload["text"])
 
 
 
 
 
 
 
84
  ```
85
 
86
- ### Without FastEmbed
 
87
 
88
- `zero_encoder.py` is the NumPy/tokenizers reference path:
 
 
 
89
 
90
  ```python
91
  from huggingface_hub import snapshot_download
92
  import sys
93
 
94
- d = snapshot_download("DylanCouzon/constella-zero")
95
- sys.path.insert(0, d)
 
96
  from zero_encoder import ZeroQueryEncoder
97
 
98
- enc = ZeroQueryEncoder(d, variant="int8")
99
- q_np = enc.encode(["how do mrna vaccines work?"])
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
- Tokenize with WordPiece, special tokens on, no prefix, and truncation at 512 tokens. A token that
109
- appears `c` times carries total weight `sqrt(c)`, so repetition saturates. The graph sums the rows,
110
- divides by the weight sum, and L2-normalizes; an empty or near-zero bag falls back to normalized
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
- The reported model is int8, which was within 0.00013 nDCG@10 of fp16.
 
115
 
116
- ## Files
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
- Both graphs use opset 17 and standard operators. The int8 table is dequantized in-graph with one
125
- fp32 scale per row. Tokenizer metadata enforces the frozen 512-token rule and dynamic padding.
 
126
 
127
- ## Results
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 (int8) | 0.3124 | 0.1677 | 0.6101 | 0.5490 | 0.5916 | 0.3728 |
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 teacher, symmetric | 0.4134 | 0.2395 | 0.7796 | 0.8234 | 0.6369 | 0.5536 |
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
- | Zero + BM25 fusion | prefetch | all-six macro | clean-4 macro |
150
- |---|---:|---:|---:|
151
- | Qdrant DBSF | 100 | 0.4887 | 0.4912 |
152
- | M7 convex0 (`w=0.8`) | 1000 | 0.4911 | 0.4866 |
153
 
154
- Both rows use `bm25s` with Lucene defaults as the lexical side, not Qdrant's own BM25, which has a
155
- fixed `avg_len` and its own tokenizer — DBSF normalises over returned scores, so a different
156
- lexical implementation shifts its inputs. For the DBSF row, each query's own document is excluded
157
- *before* the prefetch is truncated to 100 (`must_not` on the point id); without that filter a
158
- plain `limit: 100` spends a slot on the self-match. That affects only ArguAna (1,298 of 1,406
159
- queries) and FiQA (55), so the clean-4 figures are unchanged either way.
160
 
161
- Use Qdrant DBSF at prefetch 100 in deployments. M7's convex0 is the registered operator of record,
162
- but Qdrant does not implement it. No confidence interval compared these observations, so neither
163
- superiority nor equivalence is established. M7 C3 likewise did not establish fusion superiority
164
- over OpenSearch: +0.0043, raw 95% interval [-0.0063, +0.0151], p=0.219.
165
 
166
- Full per-dataset fusion rows, registered contrasts, and source traces are in the
167
- [M21 benchmark ledger](https://github.com/Dylancouzon/asymmetric-dual-encoders/blob/main/m21/BENCHMARKS.md).
168
 
169
- ## Limits
 
 
170
 
171
- - Reserved-four and BEIR-18 evidence is pending and unspent; the six datasets do not establish
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
- This measures query encoders only, not retrieval, ANN, or end-to-end system latency. The asset
191
- column follows the common protocol; `model.onnx` itself is the 31 MB query graph listed above.
 
 
 
 
 
 
 
 
192
 
193
  ## Training
194
 
195
- The table was trained by L2 regression against Stella query embeddings over 340,850 pairs plus
196
- 220,632 query-only rows from Amazon ESCI, FEVER, HotpotQA, SQuAD, NQ Open, TriviaQA, and Mr. TyDi
197
- (English); MS MARCO was excluded. Wikipedia-derived sources retain CC BY-SA attribution; ESCI and
198
- TriviaQA are Apache-2.0.
199
 
200
- ## Provenance
201
 
202
- ```text
203
- run_id p35w-2m-s2500
204
- table a7007b1a6af120b976f093fd69ddcb5001996ec0b84b5864b4fd25d7af878abf
205
- teacher NovaSearch/stella_en_400M_v5 @ ffeb2b7ee715c226d4ffe5e4619f7dbb48624c20
206
- comparator BAAI/bge-small-en-v1.5 @ 5c38ec7c405ec4b44b94cc5a9bb96e735b38267a
207
- preproc prefix="" · special tokens · max_length=512 · pool_mode=sqrt
208
- fingerprint adb24fb2e8cad66f
209
- ```
 
 
 
 
 
 
 
 
210
 
211
- The weights are MIT licensed. The pinned Stella teacher/document tower and bge-small comparator
212
- are also MIT and are attributed above.
 
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`.