ONNX export of nomic-ai/CodeRankEmbed
Browse files- README.md +80 -0
- config.json +108 -0
- export_coderankembed_onnx.py +274 -0
- model.onnx +3 -0
- special_tokens_map.json +37 -0
- tokenizer.json +0 -0
- tokenizer_config.json +64 -0
- vocab.txt +0 -0
README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
base_model: nomic-ai/CodeRankEmbed
|
| 4 |
+
pipeline_tag: feature-extraction
|
| 5 |
+
library_name: onnxruntime
|
| 6 |
+
tags:
|
| 7 |
+
- onnx
|
| 8 |
+
- code
|
| 9 |
+
- code-retrieval
|
| 10 |
+
- sentence-similarity
|
| 11 |
+
- feature-extraction
|
| 12 |
+
language:
|
| 13 |
+
- en
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# CodeRankEmbed (ONNX)
|
| 17 |
+
|
| 18 |
+
A single-file ONNX export of [CodeRankEmbed](https://huggingface.co/nomic-ai/CodeRankEmbed), a 137M parameter bi-encoder
|
| 19 |
+
for natural language to code retrieval (Suresh et al., 2025). The upstream repository ships PyTorch weights
|
| 20 |
+
with custom modeling code. This export runs on ONNX Runtime without that code.
|
| 21 |
+
|
| 22 |
+
It is used by [Zulia](https://github.com/zuliaio/zuliasearch) as `KnownEmbeddingModel.CODE_RANK_EMBED`.
|
| 23 |
+
|
| 24 |
+
## Reproducing
|
| 25 |
+
|
| 26 |
+
`export_coderankembed_onnx.py` in this repository is the exact script that produced `model.onnx`. It loads the
|
| 27 |
+
upstream model, traces the encoder, checks the ONNX output against PyTorch at several sequence lengths and
|
| 28 |
+
writes this folder:
|
| 29 |
+
|
| 30 |
+
```bash
|
| 31 |
+
pip install torch --index-url https://download.pytorch.org/whl/cpu
|
| 32 |
+
pip install "transformers>=4.51,<5" einops onnx onnxruntime huggingface_hub
|
| 33 |
+
python export_coderankembed_onnx.py --out ./coderankembed-onnx
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
## Modifications from the upstream model
|
| 37 |
+
|
| 38 |
+
- The encoder was traced to ONNX (opset 17, fp32). No weights were changed. Outputs match the PyTorch model
|
| 39 |
+
to within floating point tolerance, including inputs of several thousand tokens.
|
| 40 |
+
- The graph takes `input_ids`, `attention_mask` and `token_type_ids` and returns `last_hidden_state`.
|
| 41 |
+
Pooling is not part of the graph. Apply CLS pooling (take position 0) and L2 normalize, as the upstream
|
| 42 |
+
sentence-transformers configuration does.
|
| 43 |
+
|
| 44 |
+
## Usage
|
| 45 |
+
|
| 46 |
+
Queries must start with the task instruction `Represent this query for searching relevant code: `. Code passages take no prefix. Maximum sequence
|
| 47 |
+
length is 8192 tokens. Embedding dimension is 768.
|
| 48 |
+
|
| 49 |
+
```python
|
| 50 |
+
import numpy as np
|
| 51 |
+
import onnxruntime as ort
|
| 52 |
+
from transformers import AutoTokenizer
|
| 53 |
+
|
| 54 |
+
tok = AutoTokenizer.from_pretrained("zuliaio/coderankembed-onnx")
|
| 55 |
+
sess = ort.InferenceSession("model.onnx")
|
| 56 |
+
texts = ["Represent this query for searching relevant code: Calculate the n-th factorial", "def fact(n): return 1 if n == 0 else n * fact(n - 1)"]
|
| 57 |
+
enc = tok(texts, padding=True, truncation=True, max_length=8192, return_tensors="np")
|
| 58 |
+
hidden = sess.run(None, dict(enc))[0]
|
| 59 |
+
emb = hidden[:, 0, :]
|
| 60 |
+
emb = emb / np.linalg.norm(emb, axis=1, keepdims=True)
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
## License and attribution
|
| 64 |
+
|
| 65 |
+
MIT, unchanged from the upstream model released by the CoRNStack authors and Nomic AI. CodeRankEmbed was
|
| 66 |
+
initialized from [Snowflake Arctic Embed M Long](https://huggingface.co/Snowflake/snowflake-arctic-embed-m-long)
|
| 67 |
+
(Apache 2.0). See the [CoRNStack project](https://gangiswag.github.io/cornstack/). If you use this model,
|
| 68 |
+
please cite:
|
| 69 |
+
|
| 70 |
+
```bibtex
|
| 71 |
+
@misc{suresh2025cornstackhighqualitycontrastivedata,
|
| 72 |
+
title={CoRNStack: High-Quality Contrastive Data for Better Code Retrieval and Reranking},
|
| 73 |
+
author={Tarun Suresh and Revanth Gangi Reddy and Yifei Xu and Zach Nussbaum and Andriy Mulyar and Brandon Duderstadt and Heng Ji},
|
| 74 |
+
year={2025},
|
| 75 |
+
eprint={2412.01007},
|
| 76 |
+
archivePrefix={arXiv},
|
| 77 |
+
primaryClass={cs.CL},
|
| 78 |
+
url={https://arxiv.org/abs/2412.01007}
|
| 79 |
+
}
|
| 80 |
+
```
|
config.json
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"prenorm": false,
|
| 3 |
+
"parallel_block": false,
|
| 4 |
+
"parallel_block_tied_norm": false,
|
| 5 |
+
"rotary_emb_fraction": 1.0,
|
| 6 |
+
"tie_word_embeddings": true,
|
| 7 |
+
"fused_dropout_add_ln": true,
|
| 8 |
+
"fused_bias_fc": true,
|
| 9 |
+
"use_flash_attn": true,
|
| 10 |
+
"use_xentropy": true,
|
| 11 |
+
"qkv_proj_bias": false,
|
| 12 |
+
"rotary_emb_base": 1000,
|
| 13 |
+
"rotary_emb_scale_base": null,
|
| 14 |
+
"rotary_emb_interleaved": false,
|
| 15 |
+
"mlp_fc1_bias": false,
|
| 16 |
+
"mlp_fc2_bias": false,
|
| 17 |
+
"use_rms_norm": false,
|
| 18 |
+
"causal": false,
|
| 19 |
+
"type_vocab_size": 2,
|
| 20 |
+
"dense_seq_output": true,
|
| 21 |
+
"pad_vocab_size_multiple": 64,
|
| 22 |
+
"rotary_scaling_factor": null,
|
| 23 |
+
"max_trained_positions": 2048,
|
| 24 |
+
"vocab_size": 30528,
|
| 25 |
+
"n_positions": 8192,
|
| 26 |
+
"n_embd": 768,
|
| 27 |
+
"n_layer": 12,
|
| 28 |
+
"n_head": 12,
|
| 29 |
+
"n_inner": 3072,
|
| 30 |
+
"activation_function": "swiglu",
|
| 31 |
+
"resid_pdrop": 0.1,
|
| 32 |
+
"embd_pdrop": 0.1,
|
| 33 |
+
"attn_pdrop": 0.0,
|
| 34 |
+
"layer_norm_epsilon": 1e-12,
|
| 35 |
+
"initializer_range": 0.02,
|
| 36 |
+
"summary_type": "cls_index",
|
| 37 |
+
"summary_use_proj": true,
|
| 38 |
+
"summary_activation": null,
|
| 39 |
+
"summary_first_dropout": 0.1,
|
| 40 |
+
"summary_proj_to_labels": true,
|
| 41 |
+
"scale_attn_weights": true,
|
| 42 |
+
"use_cache": true,
|
| 43 |
+
"scale_attn_by_inverse_layer_idx": false,
|
| 44 |
+
"reorder_and_upcast_attn": false,
|
| 45 |
+
"bos_token_id": null,
|
| 46 |
+
"eos_token_id": null,
|
| 47 |
+
"return_dict": true,
|
| 48 |
+
"output_hidden_states": false,
|
| 49 |
+
"torchscript": false,
|
| 50 |
+
"dtype": "float32",
|
| 51 |
+
"pruned_heads": {},
|
| 52 |
+
"chunk_size_feed_forward": 0,
|
| 53 |
+
"is_encoder_decoder": false,
|
| 54 |
+
"is_decoder": false,
|
| 55 |
+
"cross_attention_hidden_size": null,
|
| 56 |
+
"add_cross_attention": false,
|
| 57 |
+
"tie_encoder_decoder": false,
|
| 58 |
+
"architectures": [
|
| 59 |
+
"NomicBertModel"
|
| 60 |
+
],
|
| 61 |
+
"finetuning_task": null,
|
| 62 |
+
"id2label": {
|
| 63 |
+
"0": "LABEL_0",
|
| 64 |
+
"1": "LABEL_1"
|
| 65 |
+
},
|
| 66 |
+
"label2id": {
|
| 67 |
+
"LABEL_0": 0,
|
| 68 |
+
"LABEL_1": 1
|
| 69 |
+
},
|
| 70 |
+
"task_specific_params": null,
|
| 71 |
+
"problem_type": null,
|
| 72 |
+
"tokenizer_class": null,
|
| 73 |
+
"prefix": null,
|
| 74 |
+
"pad_token_id": null,
|
| 75 |
+
"sep_token_id": null,
|
| 76 |
+
"decoder_start_token_id": null,
|
| 77 |
+
"max_length": 20,
|
| 78 |
+
"min_length": 0,
|
| 79 |
+
"do_sample": false,
|
| 80 |
+
"early_stopping": false,
|
| 81 |
+
"num_beams": 1,
|
| 82 |
+
"temperature": 1.0,
|
| 83 |
+
"top_k": 50,
|
| 84 |
+
"top_p": 1.0,
|
| 85 |
+
"typical_p": 1.0,
|
| 86 |
+
"repetition_penalty": 1.0,
|
| 87 |
+
"length_penalty": 1.0,
|
| 88 |
+
"no_repeat_ngram_size": 0,
|
| 89 |
+
"encoder_no_repeat_ngram_size": 0,
|
| 90 |
+
"bad_words_ids": null,
|
| 91 |
+
"num_return_sequences": 1,
|
| 92 |
+
"output_scores": false,
|
| 93 |
+
"return_dict_in_generate": false,
|
| 94 |
+
"forced_bos_token_id": null,
|
| 95 |
+
"forced_eos_token_id": null,
|
| 96 |
+
"remove_invalid_values": false,
|
| 97 |
+
"exponential_decay_length_penalty": null,
|
| 98 |
+
"suppress_tokens": null,
|
| 99 |
+
"begin_suppress_tokens": null,
|
| 100 |
+
"num_beam_groups": 1,
|
| 101 |
+
"diversity_penalty": 0.0,
|
| 102 |
+
"_name_or_path": "nomic-ai/CodeRankEmbed",
|
| 103 |
+
"transformers_version": "4.57.6",
|
| 104 |
+
"model_type": "nomic_bert",
|
| 105 |
+
"tf_legacy_loss": false,
|
| 106 |
+
"use_bfloat16": false,
|
| 107 |
+
"output_attentions": false
|
| 108 |
+
}
|
export_coderankembed_onnx.py
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Export CodeRankEmbed to a single ONNX graph for Zulia.
|
| 3 |
+
|
| 4 |
+
nomic-ai/CodeRankEmbed is a NomicBERT encoder (custom modeling code, rotary positions, SwiGLU) fine-tuned for
|
| 5 |
+
natural language to code retrieval. Its Hugging Face repository ships PyTorch weights only. This script traces the
|
| 6 |
+
encoder into one ONNX file that DJL can load through KnownEmbeddingModel with CLS pooling.
|
| 7 |
+
|
| 8 |
+
Requirements (CPU is fine):
|
| 9 |
+
pip install torch --index-url https://download.pytorch.org/whl/cpu
|
| 10 |
+
pip install "transformers>=4.51,<5" einops onnx onnxruntime huggingface_hub
|
| 11 |
+
|
| 12 |
+
Usage:
|
| 13 |
+
python export_coderankembed_onnx.py --out ./coderankembed-onnx
|
| 14 |
+
python export_coderankembed_onnx.py --out ./coderankembed-onnx --push zuliaio/coderankembed-onnx
|
| 15 |
+
|
| 16 |
+
This script is published in the model repo it produces, so every revision of the ONNX file carries the exact
|
| 17 |
+
exporter that made it. The upstream model is MIT licensed (see the generated model card).
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import argparse
|
| 21 |
+
import json
|
| 22 |
+
import shutil
|
| 23 |
+
import sys
|
| 24 |
+
import textwrap
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
|
| 27 |
+
import numpy as np
|
| 28 |
+
import torch
|
| 29 |
+
from transformers import AutoModel, AutoTokenizer
|
| 30 |
+
|
| 31 |
+
DEFAULT_MODEL = "nomic-ai/CodeRankEmbed"
|
| 32 |
+
OPSET = 17
|
| 33 |
+
QUERY_PREFIX = "Represent this query for searching relevant code: "
|
| 34 |
+
MAX_TOKENS = 8192
|
| 35 |
+
|
| 36 |
+
TRACE_SAMPLES = [
|
| 37 |
+
QUERY_PREFIX + "Calculate the n-th factorial",
|
| 38 |
+
"def fact(n):\n if n < 0:\n raise ValueError\n return 1 if n == 0 else n * fact(n - 1)",
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
# Validation covers short inputs, a padded batch of mixed lengths, and inputs far longer than the trace,
|
| 42 |
+
# which is where a rotary table baked in at the trace length would show up
|
| 43 |
+
LONG_CODE = "public int add(int a, int b) {\n\treturn a + b;\n}\n" * 250
|
| 44 |
+
VALIDATION_BATCHES = [
|
| 45 |
+
[QUERY_PREFIX + "Calculate the n-th factorial",
|
| 46 |
+
"def fact(n):\n if n < 0:\n raise ValueError\n return 1 if n == 0 else n * fact(n - 1)",
|
| 47 |
+
"The history of Renaissance painting in Florence."],
|
| 48 |
+
[QUERY_PREFIX + "Add two integers", LONG_CODE[:400], LONG_CODE[:2000]],
|
| 49 |
+
[LONG_CODE],
|
| 50 |
+
]
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class LastHiddenState(torch.nn.Module):
|
| 54 |
+
"""Wrap the encoder so the ONNX graph has one output named last_hidden_state."""
|
| 55 |
+
|
| 56 |
+
def __init__(self, model):
|
| 57 |
+
super().__init__()
|
| 58 |
+
self.model = model
|
| 59 |
+
|
| 60 |
+
def forward(self, input_ids, attention_mask, token_type_ids):
|
| 61 |
+
return self.model(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids).last_hidden_state
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def reset_rotary_caches(model):
|
| 65 |
+
"""The rotary modules cache cos/sin tables from earlier forward passes. Clearing them makes the trace compute the
|
| 66 |
+
tables from the input length instead of capturing a cached tensor as a fixed size constant."""
|
| 67 |
+
for module in model.modules():
|
| 68 |
+
if hasattr(module, "_seq_len_cached"):
|
| 69 |
+
module._seq_len_cached = 0
|
| 70 |
+
module._cos_cached = module._sin_cached = None
|
| 71 |
+
module._cos_k_cached = module._sin_k_cached = None
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def encode(tokenizer, texts, tensors):
|
| 75 |
+
return tokenizer(texts, padding=True, truncation=True, max_length=MAX_TOKENS, return_tensors=tensors)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def reference_outputs(model, tokenizer):
|
| 79 |
+
outputs = []
|
| 80 |
+
with torch.no_grad():
|
| 81 |
+
for batch in VALIDATION_BATCHES:
|
| 82 |
+
outputs.append(model(**encode(tokenizer, batch, "pt")).last_hidden_state.numpy())
|
| 83 |
+
return outputs
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def export(model, tokenizer, onnx_path: Path):
|
| 87 |
+
reset_rotary_caches(model)
|
| 88 |
+
enc = encode(tokenizer, TRACE_SAMPLES, "pt")
|
| 89 |
+
dynamic = {0: "batch", 1: "sequence"}
|
| 90 |
+
with torch.no_grad():
|
| 91 |
+
torch.onnx.export(
|
| 92 |
+
LastHiddenState(model),
|
| 93 |
+
(enc["input_ids"], enc["attention_mask"], enc["token_type_ids"]),
|
| 94 |
+
str(onnx_path),
|
| 95 |
+
input_names=["input_ids", "attention_mask", "token_type_ids"],
|
| 96 |
+
output_names=["last_hidden_state"],
|
| 97 |
+
dynamic_axes={
|
| 98 |
+
"input_ids": dynamic,
|
| 99 |
+
"attention_mask": dynamic,
|
| 100 |
+
"token_type_ids": dynamic,
|
| 101 |
+
"last_hidden_state": dynamic,
|
| 102 |
+
},
|
| 103 |
+
opset_version=OPSET,
|
| 104 |
+
do_constant_folding=True,
|
| 105 |
+
dynamo=False,
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def cls_normalized(hidden: np.ndarray) -> np.ndarray:
|
| 110 |
+
cls = hidden[:, 0, :]
|
| 111 |
+
return cls / np.linalg.norm(cls, axis=1, keepdims=True)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def validate(references, tokenizer, onnx_path: Path):
|
| 115 |
+
import onnx
|
| 116 |
+
import onnxruntime as ort
|
| 117 |
+
|
| 118 |
+
onnx.checker.check_model(str(onnx_path), full_check=False)
|
| 119 |
+
session = ort.InferenceSession(str(onnx_path), providers=["CPUExecutionProvider"])
|
| 120 |
+
print("ONNX inputs:", [(i.name, i.shape) for i in session.get_inputs()])
|
| 121 |
+
print("ONNX outputs:", [(o.name, o.shape) for o in session.get_outputs()])
|
| 122 |
+
|
| 123 |
+
first = None
|
| 124 |
+
for batch, torch_hidden in zip(VALIDATION_BATCHES, references):
|
| 125 |
+
enc = encode(tokenizer, batch, "np")
|
| 126 |
+
ort_hidden = session.run(None, {k: v for k, v in enc.items()})[0]
|
| 127 |
+
mask = enc["attention_mask"][..., None].astype(bool)
|
| 128 |
+
# padded positions are never pooled, so only real tokens are compared
|
| 129 |
+
max_diff = float(np.abs(np.where(mask, torch_hidden - ort_hidden, 0)).max())
|
| 130 |
+
cls_cos = float((cls_normalized(torch_hidden) * cls_normalized(ort_hidden)).sum(1).min())
|
| 131 |
+
print(f"sequence length {enc['input_ids'].shape[1]}: max |torch - onnx| {max_diff:.3e}, min CLS cosine {cls_cos:.6f}")
|
| 132 |
+
if max_diff > 1e-3 or cls_cos < 0.9999:
|
| 133 |
+
raise SystemExit(f"ONNX output diverges from PyTorch at sequence length {enc['input_ids'].shape[1]}")
|
| 134 |
+
if first is None:
|
| 135 |
+
first = cls_normalized(ort_hidden)
|
| 136 |
+
|
| 137 |
+
near = float(first[0] @ first[1])
|
| 138 |
+
far = float(first[0] @ first[2])
|
| 139 |
+
print(f"cosine near={near:.4f} far={far:.4f}")
|
| 140 |
+
if near <= far:
|
| 141 |
+
raise SystemExit("sanity check failed: the factorial query should score its code above the unrelated text")
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def write_model_card(out: Path, model_id: str):
|
| 145 |
+
card = textwrap.dedent(f"""\
|
| 146 |
+
---
|
| 147 |
+
license: mit
|
| 148 |
+
base_model: {model_id}
|
| 149 |
+
pipeline_tag: feature-extraction
|
| 150 |
+
library_name: onnxruntime
|
| 151 |
+
tags:
|
| 152 |
+
- onnx
|
| 153 |
+
- code
|
| 154 |
+
- code-retrieval
|
| 155 |
+
- sentence-similarity
|
| 156 |
+
- feature-extraction
|
| 157 |
+
language:
|
| 158 |
+
- en
|
| 159 |
+
---
|
| 160 |
+
|
| 161 |
+
# CodeRankEmbed (ONNX)
|
| 162 |
+
|
| 163 |
+
A single-file ONNX export of [CodeRankEmbed](https://huggingface.co/{model_id}), a 137M parameter bi-encoder
|
| 164 |
+
for natural language to code retrieval (Suresh et al., 2025). The upstream repository ships PyTorch weights
|
| 165 |
+
with custom modeling code. This export runs on ONNX Runtime without that code.
|
| 166 |
+
|
| 167 |
+
It is used by [Zulia](https://github.com/zuliaio/zuliasearch) as `KnownEmbeddingModel.CODE_RANK_EMBED`.
|
| 168 |
+
|
| 169 |
+
## Reproducing
|
| 170 |
+
|
| 171 |
+
`export_coderankembed_onnx.py` in this repository is the exact script that produced `model.onnx`. It loads the
|
| 172 |
+
upstream model, traces the encoder, checks the ONNX output against PyTorch at several sequence lengths and
|
| 173 |
+
writes this folder:
|
| 174 |
+
|
| 175 |
+
```bash
|
| 176 |
+
pip install torch --index-url https://download.pytorch.org/whl/cpu
|
| 177 |
+
pip install "transformers>=4.51,<5" einops onnx onnxruntime huggingface_hub
|
| 178 |
+
python export_coderankembed_onnx.py --out ./coderankembed-onnx
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
## Modifications from the upstream model
|
| 182 |
+
|
| 183 |
+
- The encoder was traced to ONNX (opset {OPSET}, fp32). No weights were changed. Outputs match the PyTorch model
|
| 184 |
+
to within floating point tolerance, including inputs of several thousand tokens.
|
| 185 |
+
- The graph takes `input_ids`, `attention_mask` and `token_type_ids` and returns `last_hidden_state`.
|
| 186 |
+
Pooling is not part of the graph. Apply CLS pooling (take position 0) and L2 normalize, as the upstream
|
| 187 |
+
sentence-transformers configuration does.
|
| 188 |
+
|
| 189 |
+
## Usage
|
| 190 |
+
|
| 191 |
+
Queries must start with the task instruction `{QUERY_PREFIX}`. Code passages take no prefix. Maximum sequence
|
| 192 |
+
length is {MAX_TOKENS} tokens. Embedding dimension is 768.
|
| 193 |
+
|
| 194 |
+
```python
|
| 195 |
+
import numpy as np
|
| 196 |
+
import onnxruntime as ort
|
| 197 |
+
from transformers import AutoTokenizer
|
| 198 |
+
|
| 199 |
+
tok = AutoTokenizer.from_pretrained("zuliaio/coderankembed-onnx")
|
| 200 |
+
sess = ort.InferenceSession("model.onnx")
|
| 201 |
+
texts = ["{QUERY_PREFIX}Calculate the n-th factorial", "def fact(n): return 1 if n == 0 else n * fact(n - 1)"]
|
| 202 |
+
enc = tok(texts, padding=True, truncation=True, max_length={MAX_TOKENS}, return_tensors="np")
|
| 203 |
+
hidden = sess.run(None, dict(enc))[0]
|
| 204 |
+
emb = hidden[:, 0, :]
|
| 205 |
+
emb = emb / np.linalg.norm(emb, axis=1, keepdims=True)
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
## License and attribution
|
| 209 |
+
|
| 210 |
+
MIT, unchanged from the upstream model released by the CoRNStack authors and Nomic AI. CodeRankEmbed was
|
| 211 |
+
initialized from [Snowflake Arctic Embed M Long](https://huggingface.co/Snowflake/snowflake-arctic-embed-m-long)
|
| 212 |
+
(Apache 2.0). See the [CoRNStack project](https://gangiswag.github.io/cornstack/). If you use this model,
|
| 213 |
+
please cite:
|
| 214 |
+
|
| 215 |
+
```bibtex
|
| 216 |
+
@misc{{suresh2025cornstackhighqualitycontrastivedata,
|
| 217 |
+
title={{CoRNStack: High-Quality Contrastive Data for Better Code Retrieval and Reranking}},
|
| 218 |
+
author={{Tarun Suresh and Revanth Gangi Reddy and Yifei Xu and Zach Nussbaum and Andriy Mulyar and Brandon Duderstadt and Heng Ji}},
|
| 219 |
+
year={{2025}},
|
| 220 |
+
eprint={{2412.01007}},
|
| 221 |
+
archivePrefix={{arXiv}},
|
| 222 |
+
primaryClass={{cs.CL}},
|
| 223 |
+
url={{https://arxiv.org/abs/2412.01007}}
|
| 224 |
+
}}
|
| 225 |
+
```
|
| 226 |
+
""")
|
| 227 |
+
(out / "README.md").write_text(card)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def main():
|
| 231 |
+
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 232 |
+
parser.add_argument("--model", default=DEFAULT_MODEL)
|
| 233 |
+
parser.add_argument("--revision", default=None, help="upstream commit to export, defaults to the latest")
|
| 234 |
+
parser.add_argument("--out", required=True, help="output directory for model.onnx, tokenizer and model card")
|
| 235 |
+
parser.add_argument("--push", metavar="REPO_ID", help="upload the output directory to this Hugging Face repo")
|
| 236 |
+
args = parser.parse_args()
|
| 237 |
+
|
| 238 |
+
out = Path(args.out)
|
| 239 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 240 |
+
|
| 241 |
+
print(f"loading {args.model}")
|
| 242 |
+
tokenizer = AutoTokenizer.from_pretrained(args.model, revision=args.revision)
|
| 243 |
+
model = AutoModel.from_pretrained(args.model, revision=args.revision, trust_remote_code=True,
|
| 244 |
+
add_pooling_layer=False, safe_serialization=True)
|
| 245 |
+
model.eval()
|
| 246 |
+
|
| 247 |
+
references = reference_outputs(model, tokenizer)
|
| 248 |
+
|
| 249 |
+
onnx_path = out / "model.onnx"
|
| 250 |
+
print(f"exporting to {onnx_path}")
|
| 251 |
+
export(model, tokenizer, onnx_path)
|
| 252 |
+
validate(references, tokenizer, onnx_path)
|
| 253 |
+
|
| 254 |
+
tokenizer.save_pretrained(out)
|
| 255 |
+
config = model.config.to_dict()
|
| 256 |
+
config.pop("auto_map", None) # the custom modeling code is not shipped, the ONNX graph replaces it
|
| 257 |
+
(out / "config.json").write_text(json.dumps(config, indent=2))
|
| 258 |
+
write_model_card(out, args.model)
|
| 259 |
+
|
| 260 |
+
shutil.copy(Path(__file__).resolve(), out / Path(__file__).name)
|
| 261 |
+
print("wrote", sorted(p.name for p in out.iterdir()))
|
| 262 |
+
|
| 263 |
+
if args.push:
|
| 264 |
+
from huggingface_hub import HfApi
|
| 265 |
+
|
| 266 |
+
api = HfApi()
|
| 267 |
+
api.create_repo(args.push, repo_type="model", exist_ok=True)
|
| 268 |
+
api.upload_folder(folder_path=str(out), repo_id=args.push, repo_type="model",
|
| 269 |
+
commit_message=f"ONNX export of {args.model}")
|
| 270 |
+
print(f"pushed to https://huggingface.co/{args.push}")
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
if __name__ == "__main__":
|
| 274 |
+
sys.exit(main())
|
model.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:12ba359756210927940f8a9ec569cc7d6d6f091e7fcfae6cbe09bba6aab8830d
|
| 3 |
+
size 547637263
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cls_token": {
|
| 3 |
+
"content": "[CLS]",
|
| 4 |
+
"lstrip": false,
|
| 5 |
+
"normalized": false,
|
| 6 |
+
"rstrip": false,
|
| 7 |
+
"single_word": false
|
| 8 |
+
},
|
| 9 |
+
"mask_token": {
|
| 10 |
+
"content": "[MASK]",
|
| 11 |
+
"lstrip": false,
|
| 12 |
+
"normalized": false,
|
| 13 |
+
"rstrip": false,
|
| 14 |
+
"single_word": false
|
| 15 |
+
},
|
| 16 |
+
"pad_token": {
|
| 17 |
+
"content": "[PAD]",
|
| 18 |
+
"lstrip": false,
|
| 19 |
+
"normalized": false,
|
| 20 |
+
"rstrip": false,
|
| 21 |
+
"single_word": false
|
| 22 |
+
},
|
| 23 |
+
"sep_token": {
|
| 24 |
+
"content": "[SEP]",
|
| 25 |
+
"lstrip": false,
|
| 26 |
+
"normalized": false,
|
| 27 |
+
"rstrip": false,
|
| 28 |
+
"single_word": false
|
| 29 |
+
},
|
| 30 |
+
"unk_token": {
|
| 31 |
+
"content": "[UNK]",
|
| 32 |
+
"lstrip": false,
|
| 33 |
+
"normalized": false,
|
| 34 |
+
"rstrip": false,
|
| 35 |
+
"single_word": false
|
| 36 |
+
}
|
| 37 |
+
}
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"added_tokens_decoder": {
|
| 3 |
+
"0": {
|
| 4 |
+
"content": "[PAD]",
|
| 5 |
+
"lstrip": false,
|
| 6 |
+
"normalized": false,
|
| 7 |
+
"rstrip": false,
|
| 8 |
+
"single_word": false,
|
| 9 |
+
"special": true
|
| 10 |
+
},
|
| 11 |
+
"100": {
|
| 12 |
+
"content": "[UNK]",
|
| 13 |
+
"lstrip": false,
|
| 14 |
+
"normalized": false,
|
| 15 |
+
"rstrip": false,
|
| 16 |
+
"single_word": false,
|
| 17 |
+
"special": true
|
| 18 |
+
},
|
| 19 |
+
"101": {
|
| 20 |
+
"content": "[CLS]",
|
| 21 |
+
"lstrip": false,
|
| 22 |
+
"normalized": false,
|
| 23 |
+
"rstrip": false,
|
| 24 |
+
"single_word": false,
|
| 25 |
+
"special": true
|
| 26 |
+
},
|
| 27 |
+
"102": {
|
| 28 |
+
"content": "[SEP]",
|
| 29 |
+
"lstrip": false,
|
| 30 |
+
"normalized": false,
|
| 31 |
+
"rstrip": false,
|
| 32 |
+
"single_word": false,
|
| 33 |
+
"special": true
|
| 34 |
+
},
|
| 35 |
+
"103": {
|
| 36 |
+
"content": "[MASK]",
|
| 37 |
+
"lstrip": false,
|
| 38 |
+
"normalized": false,
|
| 39 |
+
"rstrip": false,
|
| 40 |
+
"single_word": false,
|
| 41 |
+
"special": true
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"additional_special_tokens": [],
|
| 45 |
+
"clean_up_tokenization_spaces": true,
|
| 46 |
+
"cls_token": "[CLS]",
|
| 47 |
+
"do_lower_case": true,
|
| 48 |
+
"extra_special_tokens": {},
|
| 49 |
+
"mask_token": "[MASK]",
|
| 50 |
+
"max_length": 512,
|
| 51 |
+
"model_max_length": 8192,
|
| 52 |
+
"pad_to_multiple_of": null,
|
| 53 |
+
"pad_token": "[PAD]",
|
| 54 |
+
"pad_token_type_id": 0,
|
| 55 |
+
"padding_side": "right",
|
| 56 |
+
"sep_token": "[SEP]",
|
| 57 |
+
"stride": 0,
|
| 58 |
+
"strip_accents": null,
|
| 59 |
+
"tokenize_chinese_chars": true,
|
| 60 |
+
"tokenizer_class": "BertTokenizer",
|
| 61 |
+
"truncation_side": "right",
|
| 62 |
+
"truncation_strategy": "longest_first",
|
| 63 |
+
"unk_token": "[UNK]"
|
| 64 |
+
}
|
vocab.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|