Dewey Long Context Embedding Model: A Technical Report
The model was presented in the paper .
Paper abstract
The abstract of the paper is the following:
In this technical report, we introduce Dewey, a novel long context embedding model designed to enhance retrieval performance in long document scenarios. Dewey builds upon the ModernBERT architecture, known for its efficient handling of extended sequences, and incorporates an instruction-based training approach to align embeddings with specific task requirements. Key features of Dewey include its 128k context window, multi-vector representation for improved granularity, and a flexible chunking mechanism that allows customizable vector combinations. We evaluate Dewey on the LongEmbed benchmark, where it achieves state-of-the-art results, surpassing several larger models. Additionally, we present comprehensive usage examples and implementation details to facilitate the adoption and adaptation of Dewey for various applications.
1 Introduction
Cooperating with Richinfo, this released model was trained using a novel approach,
and while we haven't fully understood
the underlying principles
yet, we have achieved promising results. Therefore, we have decided to open-source the model and hope that
someone will test the model and provide us with feedback!
Max length is 128k, parameter size is 395M, and support only for English.
Supports both single-vector and multi-vector (similar to Colbert, but with fewer vectors, only 0.5% of the number of
tokens).
Achieved quite impressive results on the short text evaluation (MTEB-eng-v2), without using the MTEB training set,
even surpassing several 7B-sized models.
On the long text evaluation LongEmbed, the single-vector surpasses many large and commercial models. If multi-vector
is used, the average score becomes the first place. Currently, our score is 0.86, while the current first place score
is 0.79.
Ultra-fast encoding speed, benefiting from the architectural advantages of ModernBert, the encoding speed for long
texts is still very fast.
Super flexible multi-vector combination method, where the multi-vector can be understood as span or chunk level, not
token level, so how to specify the chunk can be completely customized according to your own scenario, very flexible.
2 Usage
We suggest you read the following contents with the model architecture diagram.
avatar
We do hope you read the modeling_dewey_v1.py and custom_st.py carefully, these codes is easy to read and
will help you a lot!
2.1 Prompts
Our model is a kind of instruct-embedding-model, when using our model, you should add prompt before the text.
For Retrieval task, you MUST use our provided prompt:
query: <|START_INSTRUCTION|>Answer the question<|END_INSTRUCTION|>
passage: <|START_INSTRUCTION|>Candidate document<|END_INSTRUCTION|>
For STS task, you MUST use our provided prompt: <|START_INSTRUCTION|>Generate semantically similar text<|END_INSTRUCTION|>
For Classification and Clustering task, you should design your own prompt, below are some examples: <|START_INSTRUCTION|>Classify text into intents<|END_INSTRUCTION|> <|START_INSTRUCTION|>Classify text into toxic or not toxic<|END_INSTRUCTION|> <|START_INSTRUCTION|>Output main category of Medrxiv papers based on the titles<|END_INSTRUCTION|> <|START_INSTRUCTION|>Output topic or theme of news articles<|END_INSTRUCTION|>
2.2 Single Vector
For using single vector, our model is compatible with the SentenceTransformer.
python
1import os
23# os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"4import torch
5from sentence_transformers import SentenceTransformer
67RETRIEVE_Q_PROMPT ="<|START_INSTRUCTION|>Answer the question<|END_INSTRUCTION|>"8RETRIEVE_P_PROMPT ="<|START_INSTRUCTION|>Candidate document<|END_INSTRUCTION|>"9model = SentenceTransformer(10"infgrad/dewey_en_beta",11 trust_remote_code=True,12 model_kwargs={13"torch_dtype": torch.bfloat16,14"attn_implementation":"flash_attention_2"15},16 config_kwargs={"single_vector_type":"mean"}17).cuda().bfloat16().eval()18# the choice of single_vector_type:19## for short text (<1k): cls_add_mean20## for long text (>1k): mean2122# the max length of model is 128*102423model.max_seq_length =32*10242425query_vectors = model.encode(26 sentences=[f"{RETRIEVE_Q_PROMPT}What is a computer composed of?",f"{RETRIEVE_Q_PROMPT}why the sky is blue"]27)28passage_vectors = model.encode(29 sentences=[30f"{RETRIEVE_P_PROMPT}Central processing unit (CPU), memory (RAM), storage (hard drive or SSD), input/output devices (keyboard, mouse, monitor), and a motherboard",31f"{RETRIEVE_P_PROMPT}Shorter wavelengths of light, such as blue and violet, are scattered more by gases and particles in Earth's atmosphere.",32]33)3435print(query_vectors @ passage_vectors.T)36# the output is:37# [[0.52512825 0.19771025]38# [0.17617573 0.5918883 ]]
2.3 Multi Vectors
Our multi vectors are bsed on text span(i.e. chunk), so each vector can be considered as a contextual chunk vector.
In order to get multi vectors of a document, you should get chunks and their spans first.
Below are detailed steps to get multi vectors:
Step1: Chunk the document to get chunks and spans. This can be done by using our encode function, or you can also
chunk documents by yourself according to your scenario. Note that, if you decide to chunk by yourself, your chunk and span should not contain prompt!!! Step2: encode text to get token embeddings Step3: according to span (i.e. start_position and end_position) to get chunk vector,
we use mean of span token embeddings as chunk vector (i.e. normalize(token_embed[start_position:end_position].mean(
axis=0))) Step4: For each span, do Step3, until get all chunk vectors, you can also add span(0,1) and span(1+prompt_len,
text_len-1) to get global vector
For retrieval tasks, query vector should be single vector, so the final score between query and document is the max
score of query with every document vector.
This is compatible with FAISS, MILVUS and so on. Just enlarge the top-k and do de-duplicate on searched documents.
Below are detailed code examples.
2.3.1 Chunk text in the encode function
You can directly use encode method in our model to get multi vectors.
This method will chunk text automatically.
You can choose the chunk strategy by setting fast_chunk parameter, if fast_chunk is true, directly chunk on input
ids, else using RecursiveCharacterTextSplitter.
python
1import os
2import numpy as np
34# os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"5from pydantic import BaseModel
6from typing import Optional, List
7from transformers import AutoTokenizer, AutoModel
8910classTextSpan(BaseModel):11 s:int12 e:int13 text: Optional[str]=None14 module_name:str151617RETRIEVE_Q_PROMPT ="<|START_INSTRUCTION|>Answer the question<|END_INSTRUCTION|>"18RETRIEVE_P_PROMPT ="<|START_INSTRUCTION|>Candidate document<|END_INSTRUCTION|>"19model = AutoModel.from_pretrained(20"infgrad/dewey_en_beta",21 trust_remote_code=True,22 attn_implementation="flash_attention_2"23).cuda().bfloat16()24model.tokenizer = AutoTokenizer.from_pretrained("infgrad/dewey_en_beta")25max_seq_length =32*10242627q_list =["why the sky is blue"]28p_list =[29"""
30 I’ve been trying to understand why the sky changes colors, and I think I understand most of it, but something in the online explanations doesn’t make it clear for me:
3132I’ve read:
3334sky is blue because blue light gets scattered the most during the day.
3536in the evening it turns red because now even more of the blue light gets scattered
3738So a few questions:
3940The scattering of light during the day: does it mean that blue light gets reflected off air particles and reaches our eyes, while the rest of the frequencies pass through and reach the ground?
4142Surely some of the other frequencies also get scattered during the day, just in much smaller amounts?
4344So during the evening blue light gets scattered even more, to the point where even less of it reaches the eyes?
4546And so it gets red because now we can see the lower frequencies being scattered without blue overshadowing them?\
4748Trying to word it myself: during the day only the highest frequencies get filtered, but during the evening also lower frequencies get filtered, because now the “light strainer” (air) is just catching more of it?\
4950It gets darker in the evening without a good ability to see colors because there’s is no blue and so on light to reflect off of objects?\
5152Is it ok to speak about light as a frequency? Or it’s only correct to say “wave length”?
5354Blue light is scattered in all directions by the tiny molecules of air in Earth's atmosphere. Blue is scattered more than other colors because it travels as shorter, smaller waves.
55This is why we see a blue sky most of the time. Closer to the horizon, the sky fades to a lighter blue or white.
56 """57]5859# query should be a single vector, so we set chunk_size as -1 to avoid chunk.60# If chunk size is -1, the model will return an array with shape of (2,2048) consisting of cls_vector and mean_vector(mean of all token embeddings).61query_vectors = model.encode(62 sentences=q_list,63 use_cuda=True,64 show_progress_bar=True,65 chunk_size=-1,66 chunk_overlap=32,67 convert_to_tensor=False,68 max_seq_length=max_seq_length,69 batch_size=8,70 normalize_embeddings=True,71 prompt=RETRIEVE_Q_PROMPT,72 fast_chunk=False7374)[0]75# query vector do not need multi vector, we only use mean as final single vector76pred =[vecs[1:2,:]for vecs in query_vectors]7778# spans_list contail each chunk's span, you can use span to get text79spans_list: List[List[TextSpan]]80passage_vectors_list: List[np.ndarray]81passage_vectors_list, spans_list = model.encode(82 sentences=p_list,83 use_cuda=True,84 show_progress_bar=True,85 chunk_size=64,86 chunk_overlap=8,87 convert_to_tensor=False,88 max_seq_length=max_seq_length,89 batch_size=8,90 normalize_embeddings=True,91 prompt=RETRIEVE_P_PROMPT,92 fast_chunk=True,# if fast_chunk is true, directly chunk on input ids, else using RecursiveCharacterTextSplitter93)94# spans_list stores each passage's spans, passage_vectors_list stores each passage's vectors so len(spans_list) == len(p_list) and len(spans_list) == len(passage_vectors_list)95# for a passage's spans and vectors, each span corresponds to a vector (1*2048). So, len(spans_list[idx]) == len(passage_vectors_list[idx])96print((query_vectors[0] @ passage_vectors_list[0].T).max())97# output 0.733154398# get each chunk's content99for spans, passage inzip(spans_list, p_list):100 text_ids = model.tokenizer.encode(RETRIEVE_P_PROMPT + passage)101for span in spans:102 s, e = span.s, span.e
103 chunk_text = model.tokenizer.decode(104 text_ids[s:e],105 skip_special_tokens=True,106 clean_up_tokenization_spaces=True107).strip()
Please read annotation of this encode to get more information.
2.3.2 Chunk text by yourself
If you want to chunk text by yourself, you should just set the batch_text_spans parameter in the encode function.
python
1import os
2import numpy as np
34# os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"5from pydantic import BaseModel
6from typing import Optional, List
7from transformers import AutoTokenizer, AutoModel
8910classTextSpan(BaseModel):11 s:int12 e:int13 text: Optional[str]=None14 module_name:str151617prompt ="<|START_INSTRUCTION|>Candidate document<|END_INSTRUCTION|>"1819# load model20model = AutoModel.from_pretrained(21"infgrad/dewey_en_beta",22 trust_remote_code=True,23 attn_implementation="flash_attention_2"24)25model.tokenizer = AutoTokenizer.from_pretrained("infgrad/dewey_en_beta")26max_seq_length =32*10242728# chunk text29passage ="this sentence 1. this sentence 2. this sentence 3"30chunks =["this sentence 1. this sentence 2.","this sentence 2. this sentence 3"]31prompt_length =len(model.tokenizer.tokenize(prompt))32text_spans =[33# s=0, e=1 means that this vector is cls vector, so the module_name is cls_linear, otherwise the module_name is chunk_linear34 TextSpan(s=0, e=1, module_name="cls_linear")35]36for chunk in chunks:37 s = passage.find(chunk)38 e = s +len(chunk)39 text_spans.append(40 TextSpan(41# add 1, as there is a [CLS] token at the beginning of text.42 s=1+ prompt_length +len(model.tokenizer.tokenize(passage[:s])),43 e=1+ prompt_length +len(model.tokenizer.tokenize(passage[:e])),44 module_name="chunk_linear"45)46)4748spans_list: List[List[TextSpan]]49passage_vectors_list: List[np.ndarray]50passage_vectors_list, _ = model.encode(51 sentences=[passage],52 use_cuda=False,53 show_progress_bar=True,54 chunk_size=64,55 chunk_overlap=12,56 convert_to_tensor=False,57 max_seq_length=max_seq_length,58 batch_size=8,59 normalize_embeddings=True,60 prompt=prompt,61 fast_chunk=True,62 batch_text_spans=[text_spans]63)64print(passage_vectors_list[0].shape, passage_vectors_list[0][:,2])65# the output is (3, 2048) [0.01461297 0.02085092 0.0022509 ]
On short text tasks, the performance might not be as good as that of conventional short text embedding models.
As said before, this model is still in alpha or beta stage, the model may have some unexpected behaviour.
5 Cite
@misc{zhang2025deweylongcontextembedding,
title={Dewey Long Context Embedding Model: A Technical Report},
author={Dun Zhang and Panxiang Zou and Yudong Zhou},
year={2025},
eprint={2503.20376},
archivePrefix={arXiv},
primaryClass={cs.IR},
url={https://arxiv.org/abs/2503.20376},
}