fp32 ONNX exports of all four Hypencoder checkpoints
Browse files- 2_layer/passage_encoder.onnx +3 -0
- 2_layer/query_encoder.onnx +3 -0
- 2_layer/tokenizer.json +0 -0
- 4_layer/passage_encoder.onnx +3 -0
- 4_layer/query_encoder.onnx +3 -0
- 4_layer/tokenizer.json +0 -0
- 6_layer/passage_encoder.onnx +3 -0
- 6_layer/query_encoder.onnx +3 -0
- 6_layer/tokenizer.json +0 -0
- 8_layer/passage_encoder.onnx +3 -0
- 8_layer/query_encoder.onnx +3 -0
- 8_layer/tokenizer.json +0 -0
- README.md +172 -0
2_layer/passage_encoder.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5f59e6bfd7d89658d8cfee2133d3a7b5e45a302fd15c042019ed43692ba0df1c
|
| 3 |
+
size 435860222
|
2_layer/query_encoder.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:02b284a3e0a9e24c251bcecf7480c148c8c3a7d3f195a3669e532e56aa439bbf
|
| 3 |
+
size 480834359
|
2_layer/tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
4_layer/passage_encoder.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0ed883b26da4e62aa932fc854826729f1034136023a59a69527c53e0d5d50b1f
|
| 3 |
+
size 435860222
|
4_layer/query_encoder.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ca96639b56c684450e7f23746b0be2cbb4cb2dab9f3ec827748f451210cdd902
|
| 3 |
+
size 518659573
|
4_layer/tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
6_layer/passage_encoder.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0a5c339faf41279f1e9489834d24eebfd3386b58b740a64c7643110d2b4f8d97
|
| 3 |
+
size 435860222
|
6_layer/query_encoder.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b526006e7b7c5298d08d6322cde553fb88fd35cdf10195793ebfd0231f207789
|
| 3 |
+
size 556484953
|
6_layer/tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
8_layer/passage_encoder.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a0325451dcfb622ad8989d8397284d968a5b5488e98061cc74646addcab07129
|
| 3 |
+
size 435860222
|
8_layer/query_encoder.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:076395b8413001bf8c1d87d6724abc901b45c0ff6f932014e7d2ab7b8e8eb794
|
| 3 |
+
size 594310387
|
8_layer/tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
base_model:
|
| 4 |
+
- jfkback/hypencoder.2_layer
|
| 5 |
+
- jfkback/hypencoder.4_layer
|
| 6 |
+
- jfkback/hypencoder.6_layer
|
| 7 |
+
- jfkback/hypencoder.8_layer
|
| 8 |
+
tags:
|
| 9 |
+
- onnx
|
| 10 |
+
- information-retrieval
|
| 11 |
+
- vespa
|
| 12 |
+
- hypencoder
|
| 13 |
+
library_name: onnx
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# Hypencoder β ONNX exports (fp32)
|
| 17 |
+
|
| 18 |
+
Ready-to-run fp32 ONNX artifacts for all four released
|
| 19 |
+
[Hypencoder](https://arxiv.org/abs/2502.05364) checkpoints, so you can run them without
|
| 20 |
+
PyTorch, `transformers`, or the paper's research package.
|
| 21 |
+
|
| 22 |
+
A Hypencoder replaces the fixed inner product of a bi-encoder with a *learned,
|
| 23 |
+
query-specific* neural network: a hypernetwork turns the query into a small MLP (a
|
| 24 |
+
"q-net") whose input is a document vector and whose output is the relevance score.
|
| 25 |
+
|
| 26 |
+
Exported from the upstream checkpoints (all Apache-2.0) with
|
| 27 |
+
[`model2onnx.py`](https://github.com/vespa-engine/sample-apps/blob/master/hypencoder/model2onnx.py)
|
| 28 |
+
from the Vespa sample app.
|
| 29 |
+
|
| 30 |
+
> **Why this exists.** The upstream checkpoints cannot be loaded by `transformers>=5`:
|
| 31 |
+
> `Hypencoder.__init__` calls `AutoModel.from_pretrained()` *inside the constructor*, and
|
| 32 |
+
> transformers 5 wraps `cls(config)` in a meta-device context, so the nested load is
|
| 33 |
+
> rejected by `check_and_set_device_map`. Exporting once and shipping ONNX removes
|
| 34 |
+
> `torch`, `transformers`, and the editable `hypencoder-paper` install requirement for running our sample app.
|
| 35 |
+
|
| 36 |
+
## Contents
|
| 37 |
+
|
| 38 |
+
| directory | q-net blocks | query-encoder outputs | generated params per query | passage_encoder | query_encoder |
|
| 39 |
+
|---|---|---|---|---|---|
|
| 40 |
+
| `2_layer/` | 2 | 5 | 1,181,952 | 435.8 MB | 480.8 MB |
|
| 41 |
+
| `4_layer/` | 4 | 9 | 2,363,136 | 435.8 MB | 518.6 MB |
|
| 42 |
+
| `6_layer/` | 6 | 13 | 3,544,320 | 435.8 MB | 556.4 MB |
|
| 43 |
+
| `8_layer/` | 8 | 17 | 4,725,504 | 435.8 MB | 594.3 MB |
|
| 44 |
+
|
| 45 |
+
Each directory holds `passage_encoder.onnx`, `query_encoder.onnx` and `tokenizer.json`.
|
| 46 |
+
|
| 47 |
+
The four passage encoders are **not** interchangeable β each checkpoint is a separate
|
| 48 |
+
fine-tune, so the document towers differ. Pair a passage encoder only
|
| 49 |
+
with the query encoder from the same directory.
|
| 50 |
+
|
| 51 |
+
## I/O contract
|
| 52 |
+
|
| 53 |
+
Identical across depths except for the number of query-encoder outputs.
|
| 54 |
+
|
| 55 |
+
**`passage_encoder.onnx`** β `input_ids`, `attention_mask` (**int64**, `[batch, seq]`)
|
| 56 |
+
β `last_hidden_state` `[batch, seq, 768]`. **CLS-pool it** (take index 0) and do **not**
|
| 57 |
+
L2-normalise.
|
| 58 |
+
|
| 59 |
+
**`query_encoder.onnx`** β `input_ids`, `attention_mask` (**float32**, `[batch, seq]`)
|
| 60 |
+
β `W0` `[768,768]`, `b0` `[768]`, β¦ , `W{n-1}`, `b{n-1}`, `Wout` `[768]`.
|
| 61 |
+
|
| 62 |
+
Inputs are float32 rather than int64 because Vespa tensors have no int64 cell type; the
|
| 63 |
+
export wrapper casts internally. Weight matrices are pre-transposed to `(out, in)` so
|
| 64 |
+
they arrive in Vespa's alphabetical dimension order.
|
| 65 |
+
|
| 66 |
+
## Scoring
|
| 67 |
+
|
| 68 |
+
Each q-net block is `linear β ReLU β parameter-free LayerNorm`, with a residual
|
| 69 |
+
connection, followed by a final linear projection to a scalar. For the 2-block model, as
|
| 70 |
+
written in the Vespa sample app's rank profile:
|
| 71 |
+
|
| 72 |
+
```python
|
| 73 |
+
import numpy as np
|
| 74 |
+
|
| 75 |
+
def layer_norm(v, eps=1e-5):
|
| 76 |
+
return (v - v.mean(-1, keepdims=True)) / np.sqrt(v.var(-1, keepdims=True) + eps)
|
| 77 |
+
|
| 78 |
+
def score_2layer(x0, W0, b0, W1, b1, Wout):
|
| 79 |
+
relu0 = np.maximum(x0 @ W0.T + b0, 0.0)
|
| 80 |
+
res0 = layer_norm(relu0) + x0 # residual on the first block
|
| 81 |
+
relu1 = np.maximum(res0 @ W1.T + b1, 0.0)
|
| 82 |
+
return layer_norm(relu1) @ Wout # no residual before the final projection
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
For the deeper checkpoints, treat
|
| 86 |
+
[`q_net.py`](https://github.com/jfkback/hypencoder-paper/blob/main/hypencoder_cb/modeling/q_net.py)
|
| 87 |
+
as authoritative for where residuals and layer norms are applied β the snippet above is
|
| 88 |
+
written for the 2-block case only.
|
| 89 |
+
|
| 90 |
+
## Usage with onnxruntime
|
| 91 |
+
|
| 92 |
+
```python
|
| 93 |
+
import numpy as np, onnxruntime as ort
|
| 94 |
+
from tokenizers import Tokenizer
|
| 95 |
+
|
| 96 |
+
D = "2_layer"
|
| 97 |
+
tok = Tokenizer.from_file(f"{D}/tokenizer.json")
|
| 98 |
+
enc = ort.InferenceSession(f"{D}/passage_encoder.onnx")
|
| 99 |
+
qenc = ort.InferenceSession(f"{D}/query_encoder.onnx")
|
| 100 |
+
|
| 101 |
+
d = tok.encode("Mount Everest is Earth's highest mountain, at 8,849 metres.")
|
| 102 |
+
doc_vec = enc.run(None, {"input_ids": np.array([d.ids], dtype=np.int64),
|
| 103 |
+
"attention_mask": np.array([d.attention_mask], dtype=np.int64)}
|
| 104 |
+
)[0][:, 0] # CLS pooling
|
| 105 |
+
|
| 106 |
+
q = tok.encode("tallest mountain in the world")
|
| 107 |
+
names = [o.name for o in qenc.get_outputs()]
|
| 108 |
+
w = dict(zip(names, qenc.run(None, {
|
| 109 |
+
"input_ids": np.array([q.ids], dtype=np.float32), # float, not int64
|
| 110 |
+
"attention_mask": np.array([q.attention_mask], dtype=np.float32)})))
|
| 111 |
+
|
| 112 |
+
print(score_2layer(doc_vec.astype(np.float64),
|
| 113 |
+
w["W0"], w["b0"], w["W1"], w["b1"], w["Wout"]))
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
## Usage with Vespa
|
| 117 |
+
|
| 118 |
+
The passage encoder and tokenizer can be fetched **by URL at deploy time**, so they never
|
| 119 |
+
enter your application package:
|
| 120 |
+
|
| 121 |
+
```xml
|
| 122 |
+
<component id="passage_embedder" type="hugging-face-embedder">
|
| 123 |
+
<transformer-model url="https://huggingface.co/andreer/hypencoder-onnx/resolve/main/2_layer/passage_encoder.onnx"/>
|
| 124 |
+
<tokenizer-model url="https://huggingface.co/andreer/hypencoder-onnx/resolve/main/2_layer/tokenizer.json"/>
|
| 125 |
+
<pooling-strategy>cls</pooling-strategy>
|
| 126 |
+
<normalize>false</normalize>
|
| 127 |
+
</component>
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
The query encoder **must be a local file** β `onnx-model` does not accept a URI
|
| 131 |
+
(`OnnxModel.setUri()` throws "URI for ONNX models are not currently supported"), so
|
| 132 |
+
download it into the package first:
|
| 133 |
+
|
| 134 |
+
```sh
|
| 135 |
+
mkdir -p app/models
|
| 136 |
+
curl -L -o app/models/query_encoder.onnx \
|
| 137 |
+
https://huggingface.co/andreer/hypencoder-onnx/resolve/main/2_layer/query_encoder.onnx
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
```
|
| 141 |
+
onnx-model query_encoder {
|
| 142 |
+
file: models/query_encoder.onnx
|
| 143 |
+
input "input_ids": query(input_ids)
|
| 144 |
+
input "attention_mask": query(attention_mask)
|
| 145 |
+
}
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
**The sample app's rank profile implements 2 blocks only.** Using `4_layer`, `6_layer` or
|
| 149 |
+
`8_layer` means extending that expression with the extra `W{i}`/`b{i}` blocks β the ONNX
|
| 150 |
+
outputs are there, but the ranking expression is not written for them.
|
| 151 |
+
|
| 152 |
+
## Modifications relative to the base models
|
| 153 |
+
|
| 154 |
+
Per Apache-2.0 Β§4(b): no weights were retrained or altered numerically. The export
|
| 155 |
+
wrapper (a) returns the q-net weight tensors directly instead of a callable, (b) casts
|
| 156 |
+
float `input_ids`/`attention_mask` to int64 internally, (c) pre-transposes weight matrices
|
| 157 |
+
to `(out, in)`, and (d) uses static layer-norm shapes so the legacy TorchScript exporter
|
| 158 |
+
inlines weights into a single file rather than an external `.data` sidecar.
|
| 159 |
+
|
| 160 |
+
## Citation
|
| 161 |
+
|
| 162 |
+
```bibtex
|
| 163 |
+
@inproceedings{killingback2025hypencoder,
|
| 164 |
+
title = {Hypencoder: Hypernetworks for Information Retrieval},
|
| 165 |
+
author = {Killingback, Julian and Zeng, Hansi and Zamani, Hamed},
|
| 166 |
+
booktitle = {SIGIR},
|
| 167 |
+
year = {2025}
|
| 168 |
+
}
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
Weights Β© the original authors, Apache-2.0. This repository redistributes them in ONNX
|
| 172 |
+
form under the same licence.
|