モデル概要
モデル特徴
モデル能力
使用事例
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長文コンテキスト埋め込みモデル: 技術レポート
論文要約
論文の要約は以下の通りです:
本技術レポートでは、長文ドキュメントシナリオにおける検索性能を向上させるために設計された新しい長文コンテキスト埋め込みモデルDeweyを紹介します。Deweyは、長いシーケンスを効率的に処理できることで知られるModernBERTアーキテクチャを基盤とし、埋め込みを特定のタスク要件に合わせるための命令ベースのトレーニングアプローチを組み込んでいます。Deweyの主な特徴には、128kのコンテキストウィンドウ、粒度を改善するためのマルチベクトル表現、カスタマイズ可能なベクトル組み合わせを可能にする柔軟なチャンキングメカニズムが含まれます。DeweyをLongEmbedベンチマークで評価したところ、いくつかの大規模モデルを上回る最先端の結果を達成しました。さらに、様々なアプリケーションでのDeweyの採用と適応を容易にするために、包括的な使用例と実装詳細を提供します。
1 はじめに
Richinfoと協力して、このリリースモデルは新しいアプローチでトレーニングされました。私たちはまだ根本的な原理を完全に理解していませんが、有望な結果を得たため、モデルをオープンソース化し、誰かがモデルをテストしてフィードバックを提供してくれることを願っています!
技術レポート: https://arxiv.org/abs/2503.20376
このモデルのコアトレーニング方法は、NovaSearchチームによってオープンソース化されたRAG-Retrievalリポジトリで実装される予定です。ぜひスターをお願いします!
このモデルはanswerdotai/ModernBERT-largeを基にしています。素晴らしいモデルを共有してくれたことに感謝します!
この埋め込みモデルには以下の特徴があります:
- 最大長は128k、パラメータサイズは395Mで、英語のみをサポートします。
- シングルベクトルとマルチベクトルの両方をサポート(Colbertに似ていますが、ベクトル数は少なく、トークン数の0.5%のみ)。
- 短いテキスト評価(MTEB-eng-v2)で非常に印象的な結果を達成し、MTEBトレーニングセットを使用せず、いくつかの7Bサイズのモデルを上回りました。
- 長文評価LongEmbedでは、シングルベクトルが多くの大規模および商用モデルを上回ります。マルチベクトルを使用すると、平均スコアが1位になります。現在、私たちのスコアは0.86で、現在の1位のスコアは0.79です。
- 超高速なエンコーディング速度。ModernBertのアーキテクチャの利点により、長文のエンコーディング速度も非常に高速です。
- 超柔軟なマルチベクトル組み合わせ方法。マルチベクトルはspanまたはchunkレベルと理解でき、トークンレベルではないため、チャンクの指定方法は完全にシナリオに応じてカスタマイズ可能で、非常に柔軟です。
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
# os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
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()
# single_vector_typeの選択:
## 短いテキスト(<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 マルチベクトル
私たちのマルチベクトルはテキストスパン(つまりチャンク)に基づいているため、各ベクトルはコンテキストチャンクベクトルと見なすことができます。ドキュメントのマルチベクトルを取得するには、まずチャンクとそのスパンを取得する必要があります。
以下はマルチベクトルを取得する詳細な手順です:
ステップ1: ドキュメントをチャンクしてチャンクとスパンを取得します。これは私たちのencode
関数を使用して行うことも、シナリオに応じて自分でドキュメントをチャンクすることもできます。
注意:自分でチャンクする場合、チャンクとスパンにプロンプトを含めないでください!
ステップ2: テキストをエンコードしてトークン埋め込みを取得します
ステップ3: スパン(つまり開始位置と終了位置)に基づいてチャンクベクトルを取得します。スパントークン埋め込みの平均をチャンクベクトルとして使用します(つまり、normalize(token_embed[start_position:end_position].mean(axis=0)))
ステップ4: 各スパンに対してステップ3を実行し、すべてのチャンクベクトルを取得します。また、span(0,1)とspan(1+prompt_len, text_len-1)を追加してグローバルベクトルを取得することもできます
検索タスクの場合、クエリベクトルはシングルベクトルである必要があるため、クエリとドキュメント間の最終スコアは、クエリと各ドキュメントベクトル間の最大スコアになります。
これはFAISS、MILVUSなどと互換性があります。トップkを拡大し、検索されたドキュメントに対して重複排除を行うだけです。
以下は詳細なコード例です。
2.3.1 encode
関数内でテキストをチャンクする
マルチベクトルを取得するために、直接モデルのencode
メソッドを使用できます。
このメソッドは自動的にテキストをチャンクします。
fast_chunk
パラメータを設定することでチャンク戦略を選択できます。fast_chunk
がtrueの場合、入力ID上で直接チャンクし、それ以外の場合はRecursiveCharacterTextSplitterを使用します。
import os
import numpy as np
# os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
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 = [
"""
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:
I’ve read:
sky is blue because blue light gets scattered the most during the day.
in the evening it turns red because now even more of the blue light gets scattered
So a few questions:
The 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?
Surely some of the other frequencies also get scattered during the day, just in much smaller amounts?
So during the evening blue light gets scattered even more, to the point where even less of it reaches the eyes?
And so it gets red because now we can see the lower frequencies being scattered without blue overshadowing them?\
Trying 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?\
It 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?\
Is it ok to speak about light as a frequency? Or it’s only correct to say “wave length”?
Blue 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.
This is why we see a blue sky most of the time. Closer to the horizon, the sky fades to a lighter blue or white.
"""
]
# クエリはシングルベクトルである必要があるため、チャンクを避けるためにchunk_sizeを-1に設定します。
# chunk_sizeが-1の場合、モデルはcls_vectorとすべてのトークン埋め込みの平均(mean_vector)からなる形状(2,2048)の配列を返します。
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は各チャンクのスパンを含み、スパンを使用してテキストを取得できます
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, # fast_chunkがtrueの場合、入力ID上で直接チャンクし、それ以外の場合はRecursiveCharacterTextSplitterを使用します
)
# spans_listは各パッセージのスパンを保存し、passage_vectors_listは各パッセージのベクトルを保存するため、len(spans_list) == len(p_list)およびlen(spans_list) == len(passage_vectors_list)
# パッセージのスパンとベクトルについて、各スパンはベクトル(1*2048)に対応します。したがって、len(spans_list[idx]) == len(passage_vectors_list[idx])
print((query_vectors[0] @ passage_vectors_list[0].T).max())
# 出力 0.7331543
# 各チャンクの内容を取得
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
# os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
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です。それ以外の場合、module_nameは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(
# テキストの先頭に[CLS]トークンがあるため、1を追加します。
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(eng, v2)
URL: http://mteb-leaderboard.hf.space/?benchmark_name=MTEB%28eng%2C+v2%29
再現スクリプト: https://huggingface.co/infgrad/dewey_en_beta/blob/main/scripts/evaluate/run_evaluate_mteb_dewey_en_beta.py
モデル | ゼロショット | パラメータ数 | 次元数 | 最大トークン数 | 平均(タスク) | 平均(タスクタイプ) | 分類 | クラスタリング | ペア分類 | 再ランキング | 検索 | STS | 要約 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
gemini-embedding-exp-03-07 | 95% | 不明 | 3072 | 8192 | 73.3 | 67.67 | 90.05 | 59.39 | 87.7 | 48.59 | 64.35 | 85.29 | 38.28 |
jasper_en_vision_language_v1 | 56% | 1B | 8960 | 131072 | 71.41 | 66.65 | 90.27 | 60.52 | 88.14 | 50 | 56.05 | 84.37 | 37.19 |
gte-Qwen2-7B-instruct | NA | 7B | 3584 | 32768 | 70.72 | 65.77 | 88.52 | 58.97 | 85.9 | 50.47 | 58.09 | 82.69 | 35.74 |
stella_en_1.5B_v5 | 56% | 1B | 8960 | 131072 | 69.43 | 65.32 | 89.38 | 57.06 | 88.02 | 50.19 | 52.42 | 83.27 | 36.91 |
SFR-Embedding-2_R | 85% | 7B | 4096 | 32768 | 69.82 | 65.31 | 90.54 | 59.39 | 88.09 | 48.99 | 53.75 | 80.86 | 35.54 |
Linq-Embed-Mistral | 95% | 7B | 4096 | 32768 | 69.8 | 65.29 | 83 | 54.07 | 88.44 | 49.44 | 60.14 | 84.69 | 37.26 |
NV-Embed-v2 | 56% | 7B | 4096 | 32768 | 69.81 | 65 | 87.19 | 47.66 | 88.69 | 49.61 | 62.84 | 83.82 | 35.21 |
SFR-Embedding-Mistral | 85% | 7B | 4096 | 32768 | 69.31 | 64.94 | 80.47 | 54.93 | 88.59 | 50.15 | 59.33 | 84.77 | 36.32 |
stella_en_400M_v5 | 56% | 435M | 4096 | 8192 | 69.39 | 64.84 | 88.25 | 57.65 | 87.17 | 49.6 | 52.73 | 83.93 | 34.53 |
text-embedding-004 | 95% | 不明 | 768 | 2048 | 69.53 | 64.82 | 86.03 | 51.52 | 87.65 | 48.48 | 59.06 | 84.84 | 36.12 |
text-embedding-005 | 95% | 不明 | 768 | 2048 | 69.6 | 64.77 | 86.03 | 51.91 | 87.62 | 48.84 | 58.77 | 85.18 | 35.05 |
e5-mistral-7b-instruct | 95% | 7B | 4096 | 32768 | 67.97 | 64 | 79.85 | 51.44 | 88.42 | 49.78 | 57.62 | 84.32 | 36.57 |
text-multilingual-embedding-002 | 95% | 不明 | 768 | 2048 | 67.67 | 63.52 | 84.65 | 50.41 | 86.6 | 47.48 | 54.7 | 83.94 | 36.84 |
NV-Embed-v1 | 56% | 7B | 4096 | 32768 | 68.32 | 63.37 | 84.11 | 49.5 | 87.05 | 49.16 | 60.13 | 82.2 | 31.4 |
infgrad/dewey_en_beta | 95% | 395M | 2048 | 131072 | 0.68 | 63.30 | 81.83 | 51.75 | 86.82 | 46.35 | 56.32 | 84.21 | 35.79 |
gte-Qwen2-1.5B-instruct | NA | 1B | 8960 | 32768 | 67.2 | 63.26 | 85.84 | 53.54 | 87.52 | 49.25 | 50.25 | 82.51 | 33.94 |
GritLM-7B | 95% | 7B | 4096 | 4096 | 67.07 | 63.22 | 81.25 | 50.82 | 87.29 | 49.59 | 54.95 | 83.03 | 35.65 |
GritLM-8x7B | 95% | 57B | 4096 | 4096 | 66.16 | 62.42 | 79.98 | 51.48 | 85.23 | 49.22 | 52.46 | 82.93 | 35.65 |
text-embedding-3-large | NA | 不明 | 3072 | 8191 | 66.43 | 62.15 | 79.15 | 48.9 | 85.81 | 47.45 | 57.98 | 81.44 | 34.31 |
mxbai-embed-large-v1 | 100% | 335M | 1024 | 512 | 66.26 | 62.04 | 79.1 | 47.48 | 87.2 | 48.05 | 55.4 | 84.42 | 32.63 |
GIST-large-Embedding-v0 | 80% | 335M | 1024 | 512 | 66.25 | 61.96 | 78.91 | 48.84 | 86.7 | 48.76 | 54.52 | 84.44 | 31.52 |
bge-large-en-v1.5 | 100% | 335M | 1024 | 512 | 65.89 | 61.87 | 78.34 | 48.01 | 87.13 | 48.26 | 55.44 | 82.79 | 33.13 |
UAE-Large-V1 | 100% | 335M | 1024 | 512 | 66.4 | 61.85 | 79.08 | 47.86 | 87.25 | 48.35 | 55.91 | 84.37 | 30.13 |
3.2 LongEmbed
URL: http://mteb-leaderboard.hf.space/?benchmark_name=LongEmbed
再現スクリプト: https://huggingface.co/infgrad/dewey_en_beta/blob/main/scripts/evaluate/run_evaluate_long_embed.py
モデル | ゼロショット | パラメータ数 | 埋め込み次元数 | 最大トークン数 | 平均(タスク) | 平均(タスクタイプ) | 検索 |
---|---|---|---|---|---|---|---|
infgrad/dewey_en_beta-MultiVectors | 100% | 395M | 2048 | 131072 | 86.59 | 86.59 | 86.59 |
voyage-multilingual-2 | 100% | 不明 | 1024 | 32000 | 79.17 | 79.17 | 79.17 |
voyage-law-2 | 100% | 不明 | 1024 | 16000 | 78.85 | 78.85 | 78.85 |
infgrad/dewey_en_beta-SingleVector | 100% | 395M | 2048 | 131072 | 77.98 | 77.98 | 77.98 |
voyage-3 | 100% | 不明 | 1024 | 32000 | 74.06 | 74.06 | 74.06 |
inf-retriever-v1 | 100% | 7B | 3584 | 32768 | 73.19 | 73.19 | 73.19 |
3.3 LoCoV1
URL: https://huggingface.co/datasets/hazyresearch/LoCoV1-Queries
https://huggingface.co/datasets/hazyresearch/LoCoV1-Documents
再現スクリプト: https://huggingface.co/infgrad/dewey_en_beta/blob/main/scripts/evaluate/run_evaluate_loco.py
メトリック: NDCG@10
結果:
データセット名 | bge-m3-8k | gte-modernbert-base-8k | Linq-Embed-Mistral-4k | Linq-Embed-Mistral-8k | SFR-Embedding-Mistral-8k | e5-mistral-7b-instruct-8k | dewey_en_beta-8k | dewey_en_beta_64k | dewey_en_beta_64k-マルチベクトル |
---|---|---|---|---|---|---|---|---|---|
2wikimqa_test | 0.9271 | 0.8658 | 0.8884 | 0.9067 | 0.8965 | 0.8901 | 0.8953 | 0.9051 | 0.9775 |
courtlistener_HTML_test | 0.1933 | 0.2349 | 0.3551 | 0.3670 | 0.3647 | 0.3543 | 0.3415 | 0.3616 | 0.4775 |
courtlistener_Plain_Text_test | 0.1888 | 0.2478 | 0.3675 | 0.3761 | 0.3679 | 0.3579 | 0.3377 | 0.3485 | 0.4426 |
gov_report_test | 0.9869 | 0.9750 | 0.9832 | 0.9837 | 0.9816 | 0.9823 | 0.9855 | 0.9883 | 0.9853 |
legal_case_reports_test | 0.3702 | 0.4476 | 0.5398 | 0.5432 | 0.5319 | 0.4850 | 0.5474 | 0.5875 |







