模型简介
模型特点
模型能力
使用案例
base_model:
- answerdotai/ModernBERT-large datasets:
- BAAI/Infinity-Instruct
- HuggingFaceFW/fineweb-edu language:
- en license: mit pipeline_tag: feature-extraction tags:
- sentence-transformers
- transformers library_name: sentence-transformers
杜威长上下文嵌入模型:技术报告
论文摘要
论文摘要如下:
本技术报告介绍了杜威(Dewey),一种新型的长上下文嵌入模型,旨在提升长文档场景下的检索性能。杜威基于以高效处理长序列著称的ModernBERT架构,并采用基于指令的训练方法使嵌入与特定任务需求对齐。杜威的核心特性包括128k上下文窗口、提升细粒度的多向量表示,以及支持自定义向量组合的灵活分块机制。我们在LongEmbed基准测试中评估杜威,其取得了最先进的结果,超越多个更大规模的模型。此外,我们提供了全面的使用示例和实现细节,以促进杜威在不同应用中的采用和适配。
1 引言
与Richinfo合作,本次发布的模型采用了一种新颖的训练方法。尽管我们尚未完全理解其底层原理,但已取得显著成果。因此,我们决定开源该模型,并希望有人能测试模型并给予反馈!
技术报告链接:https://arxiv.org/abs/2503.20376
该模型的核心训练方法将在NovaSearch团队开源的RAG-Retrieval仓库中实现,欢迎关注!
本模型基于answerdotai/ModernBERT-large,感谢其优秀的工作与分享!
该嵌入模型具有以下特点:
- 最大长度128k,参数量395M,仅支持英文。
- 支持单向量和多向量(类似Colbert,但向量数量更少,仅为token数的0.5%)。
- 在短文本评估(MTEB-eng-v2)中表现优异,未使用MTEB训练集的情况下超越多个7B规模模型。
- 在长文本评估LongEmbed中,单向量超越多个大型商业模型。若使用多向量,平均得分位列第一。当前得分为0.86,而现有第一名得分为0.79。
- 超快编码速度,受益于ModernBert架构优势,长文本编码依然高效。
- 极其灵活的多向量组合方式,多向量可视为span或chunk级别(非token级别),因此可根据场景完全自定义分块策略。
2 使用方法
建议结合模型架构图阅读以下内容。
强烈建议仔细阅读modeling_dewey_v1.py
和custom_st.py
,这些代码易于理解且会提供极大帮助!
2.1 提示词
本模型属于指令式嵌入模型,使用时需在文本前添加提示词。
检索任务必须使用指定提示词:
查询:<|START_INSTRUCTION|>Answer the question<|END_INSTRUCTION|>
段落:<|START_INSTRUCTION|>Candidate document<|END_INSTRUCTION|>
语义文本相似度(STS)任务必须使用:
<|START_INSTRUCTION|>Generate semantically similar text<|END_INSTRUCTION|>
分类与聚类任务需自定义提示词,例如:
<|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 单向量模式
单向量模式兼容SentenceTransformer
:
import os
import torch
from sentence_transformers import SentenceTransformer
RETRIEVE_Q_PROMPT = "<|START_INSTRUCTION|>Answer the question<|END_INSTRUCTION|>"
RETRIEVE_P_PROMPT = "<|START_INSTRUCTION|>Candidate document<|END_INSTRUCTION|>"
model = SentenceTransformer(
"infgrad/dewey_en_beta",
trust_remote_code=True,
model_kwargs={
"torch_dtype": torch.bfloat16,
"attn_implementation": "flash_attention_2"
},
config_kwargs={"single_vector_type": "mean"}
).cuda().bfloat16().eval()
# 单向量类型选择:
## 短文本(<1k): cls_add_mean
## 长文本(>1k): mean
# 模型最大长度128*1024
model.max_seq_length = 32 * 1024
query_vectors = model.encode(
sentences=[f"{RETRIEVE_Q_PROMPT}What is a computer composed of?", f"{RETRIEVE_Q_PROMPT}why the sky is blue"]
)
passage_vectors = model.encode(
sentences=[
f"{RETRIEVE_P_PROMPT}Central processing unit (CPU), memory (RAM), storage (hard drive or SSD), input/output devices (keyboard, mouse, monitor), and a motherboard",
f"{RETRIEVE_P_PROMPT}Shorter wavelengths of light, such as blue and violet, are scattered more by gases and particles in Earth's atmosphere.",
]
)
print(query_vectors @ passage_vectors.T)
# 输出示例:
# [[0.52512825 0.19771025]
# [0.17617573 0.5918883 ]]
2.3 多向量模式
多向量基于文本span(即chunk),每个向量可视为上下文片段向量。获取文档多向量需先确定chunk及其span范围。
具体步骤如下:
步骤1:对文档分块获取chunk及span。可通过encode
函数自动分块,或根据场景自定义分块。
注意:若自定义分块,span范围不应包含提示词!
步骤2:编码文本获取token嵌入
步骤3:根据span(起始/结束位置)获取chunk向量,采用span内token嵌入的均值(即normalize(token_embed[start_position:end_position].mean(axis=0)))
步骤4:对每个span重复步骤3获取所有chunk向量,可额外添加span(0,1)和span(1+prompt_len, text_len-1)获取全局向量
检索任务中,查询向量应为单向量,最终得分取查询向量与各文档向量的最大相似度。
此方式兼容FAISS、MILVUS等工具,只需扩大top-k后对搜索结果去重。
以下是具体代码示例。
2.3.1 使用encode
函数自动分块
可直接通过encode
方法获取多向量。
该方法自动分块,可通过fast_chunk
参数选择分块策略:若为True则直接按输入ID分块,否则使用RecursiveCharacterTextSplitter。
import os
import numpy as np
from pydantic import BaseModel
from typing import Optional, List
from transformers import AutoTokenizer, AutoModel
class TextSpan(BaseModel):
s: int
e: int
text: Optional[str] = None
module_name: str
RETRIEVE_Q_PROMPT = "<|START_INSTRUCTION|>Answer the question<|END_INSTRUCTION|>"
RETRIEVE_P_PROMPT = "<|START_INSTRUCTION|>Candidate document<|END_INSTRUCTION|>"
model = AutoModel.from_pretrained(
"infgrad/dewey_en_beta",
trust_remote_code=True,
attn_implementation="flash_attention_2"
).cuda().bfloat16()
model.tokenizer = AutoTokenizer.from_pretrained("infgrad/dewey_en_beta")
max_seq_length = 32 * 1024
q_list = ["why the sky is blue"]
p_list = [
"""
(长文本示例,此处省略)
"""
]
# 查询应为单向量,设置chunk_size=-1避免分块
# chunk_size=-1时返回形状为(2,2048)的数组,包含cls向量和均值向量
query_vectors = model.encode(
sentences=q_list,
use_cuda=True,
show_progress_bar=True,
chunk_size=-1,
chunk_overlap=32,
convert_to_tensor=False,
max_seq_length=max_seq_length,
batch_size=8,
normalize_embeddings=True,
prompt=RETRIEVE_Q_PROMPT,
fast_chunk=False
)[0]
# 查询向量仅使用均值作为最终单向量
pred = [vecs[1:2, :] for vecs in query_vectors]
# spans_list包含各chunk的span,可用于获取文本
spans_list: List[List[TextSpan]]
passage_vectors_list: List[np.ndarray]
passage_vectors_list, spans_list = model.encode(
sentences=p_list,
use_cuda=True,
show_progress_bar=True,
chunk_size=64,
chunk_overlap=8,
convert_to_tensor=False,
max_seq_length=max_seq_length,
batch_size=8,
normalize_embeddings=True,
prompt=RETRIEVE_P_PROMPT,
fast_chunk=True # True时直接按输入ID分块,否则使用RecursiveCharacterTextSplitter
)
# spans_list存储各段落的span,passage_vectors_list存储各段落的向量
# 对单个段落,每个span对应一个向量(1*2048),故len(spans_list[idx]) == len(passage_vectors_list[idx])
print((query_vectors[0] @ passage_vectors_list[0].T).max())
# 输出示例:0.7331543
# 获取各chunk内容
for spans, passage in zip(spans_list, p_list):
text_ids = model.tokenizer.encode(RETRIEVE_P_PROMPT + passage)
for span in spans:
s, e = span.s, span.e
chunk_text = model.tokenizer.decode(
text_ids[s:e],
skip_special_tokens=True,
clean_up_tokenization_spaces=True
).strip()
请阅读encode
函数注释获取更多信息。
2.3.2 自定义分块
若需自定义分块,应在encode
函数中设置batch_text_spans
参数。
import os
import numpy as np
from pydantic import BaseModel
from typing import Optional, List
from transformers import AutoTokenizer, AutoModel
class TextSpan(BaseModel):
s: int
e: int
text: Optional[str] = None
module_name: str
prompt = "<|START_INSTRUCTION|>Candidate document<|END_INSTRUCTION|>"
# 加载模型
model = AutoModel.from_pretrained(
"infgrad/dewey_en_beta",
trust_remote_code=True,
attn_implementation="flash_attention_2"
)
model.tokenizer = AutoTokenizer.from_pretrained("infgrad/dewey_en_beta")
max_seq_length = 32 * 1024
# 自定义分块
passage = "this sentence 1. this sentence 2. this sentence 3"
chunks = ["this sentence 1. this sentence 2.", "this sentence 2. this sentence 3"]
prompt_length = len(model.tokenizer.tokenize(prompt))
text_spans = [
# s=0, e=1表示该向量为cls向量,module_name为cls_linear,否则为chunk_linear
TextSpan(s=0, e=1, module_name="cls_linear")
]
for chunk in chunks:
s = passage.find(chunk)
e = s + len(chunk)
text_spans.append(
TextSpan(
# 加1因为文本开头有[CLS] token
s=1 + prompt_length + len(model.tokenizer.tokenize(passage[:s])),
e=1 + prompt_length + len(model.tokenizer.tokenize(passage[:e])),
module_name="chunk_linear"
)
)
spans_list: List[List[TextSpan]]
passage_vectors_list: List[np.ndarray]
passage_vectors_list, _ = model.encode(
sentences=[passage],
use_cuda=False,
show_progress_bar=True,
chunk_size=64,
chunk_overlap=12,
convert_to_tensor=False,
max_seq_length=max_seq_length,
batch_size=8,
normalize_embeddings=True,
prompt=prompt,
fast_chunk=True,
batch_text_spans=[text_spans]
)
print(passage_vectors_list[0].shape, passage_vectors_list[0][:, 2])
# 输出示例:(3, 2048) [0.01461297 0.02085092 0.0022509 ]
3 评估结果
3.1 MTEB(英文,v2)
URL:http://mteb-leaderboard.hf.space/?benchmark_name=MTEB%28eng%2C+v2%29
复现脚本:https://huggingface.co/infgrad/dewey_en_beta/blob







