Мінус 67% токенаў у RAG з мультывектарамі Qdrant і ізаляцыяй сказаў
RAG-канвеер для юрыдычнага пошуку, які пераранжуе праз ColBERT унутры Qdrant, вылучае адпаведны сказ і мяняе крыху затрымкі на правераныя 67,1% эканоміі токенаў.
Крыніцы: How Qdrant Reduced RAG Token Costs by 67% with Native ColBERT Reranking
Токенавыя счэты сталі серьзым прытворам для команд, якія выкарыстоўваюць тэхналогію падтрымкі генеравання дакументаў. Вялікія компаніі шукаюць способы зменшыць вітраты, а багатыя каманды выявляюць, што неоптымізаваны працэс работы з LLM можа коштаць больш, чым час, які ён мяў бы зарабіць. Перагляд прававых дакументаў ўтварае хорашы прыклад для тэставання, адтолькі дакументы ў такім разе ўявляюць сабою дужа дзялёныя тэксты, а адпаведзі — короткія, пры чым ўвесь обсяг такіх дакументаў вельмі вялікі.
Старонка, якой ніхто не патрабаваў
Уявіце адвоката, які ведае справу пра спор пра контракт вясьмі дзён, і якому трэба пераглянуць велікія купы угод пра адпрацоўку послуг і корпаратыўных дакументаў. Пытанне ўзначальваецца: якім будзе максімальны ліміт адпрацоўкі збітам, калі трэцяя сторона прызнае нараджэнне прав на інтэлектуальную прыналежнасць? Адпаведзь на гэта пытанне можа знаходзіцца ў адной-едзінай фразе ў соракасторанковым контракце.
Стандартная сістэма RAG вяртае тры наявных фрагменты, кожны з якіх складаецца 500–1 000 токенаў; гэтыя фрагменты здзейчай ўключаюць апісанні, вызначэнні, умовы платэжаў і клauзулы, якія рэгулююць права, і якія не маюць нічынага з прэтэнзіямі ў сферы інтэлектуальной сябеўцы. Яедыная корыстная фраза знаходзіцца ў серэдзіне. Гэта раздражае чытальніка, а ў масштабных задачах гэта ўскладнюе і падымае вартасць: пад час аўдыту тыяў тысяч заяв у судовых спраўах прадаўцу LLM плацяць за апрацоўку велічэзных кантэйнтаваных, зайвых дакументаў.
Выступаюць два пытанні: які частака атрыбутаванага тэксту на самай працоўцы неабходная? І чы можна выдзельніць самую точную фразу без выкарыстоўвання додатньага сервісу для пераранжавання?
Няма нічага ў гэтым аспекце відходзячага, што была бы спецыфічная для закону. Будзь-які набор дугіх, шаблонавых дакументаў, такіх як страховыя полісы, контракты з постачальнікамі чыста інструкцыі з дапамогі, мае аднаковую структуру: короткі адказ, абгорнуты ў старонкі тэксту, якія з кожнай спробы выкарыстоўвання падаюць у запіт. Косцты зростаюць пры збільшэнні розмеру выкарыстоўваных фрагментаў, а не пры збільшэнні розмеру адказу, таму ключовым фактарам являецца колькісць кожнага фрагмента, якая потрапляе да модэлі.
Чаму ведзецца пераранжаванне у векторнай базе дадзенаў
Звычны дваэтапны падход заключаецца у адбыцці швайнаго, інтэнсіўнага пошуку, каб з’явіліся прыблізна 100 лепшых кандыдатаў, пасля чаго адбываецца другі вызов сеціі да зовнішньага прыстрою для пераранжавання, такога як Cohere, SageMaker чыста спецыяльны кантэйнер, каб іх сортаваць. Гэты падход працюе, але мае тры недзеянні.
- Два вызовы сеціі адбываюцца да двух разных постачальнікаў.
- Калі прыстрой для пераранжавання перастае працаваць, пошук таксама не вядома.
Qdrant прастаўляе альтэрнатыўу. З версіі 1.10 ён падтрымлівае натыўныя мнагавектарныя поля з кампаратарам MAX_SIM, які ўтварае апарат для пазнейшай інтэрацыі у стыле ColBERT. Порэванне токэнаў адбываецца ўнутранічна ў базе дадзенаў за аднам запыткам, таму точнасць пазнейшай інтэрацыі, якая даходзіць за дапамогай окранжанага прыстрою для пераранжавання, атрымліваецца без дадатковага крока. У поўнанні з ізоляцыяй запэўненняў гэта дазволіла скасаваць вітраты на токэны на 67%.
Наладкі памяці завершаюць прафіль.
- Бінарная квантызацыя на ўплотненай індэксе. Уплотненыя вектары скарацваюцца да аднаго біта на вымер, і гэтыя малыя вектары застаюцца ў RAM (always_ram=True) для дужа шыракаг прадзвяжвання кандыдатаў.
- Матрыцы ColBERT на дыске. Масівы токэнаў зберагаюцца на дыске (on_disk=True) і чытаюцца толькі для найлепшых кандыдатаў, можа 50–100 фрагментаў, пад час перацэнкі, што дапамагае зменшыць выкарыстоўванне RAM.
Працэс
Дыяграма складаецца з шасці крокаў: адбываецца адна запытка да базы дадзенаў, і не выкарыстоўваецца жадна званчайная API для пераранжавання.
- Раздзеліце дакументы па раздзелах, каб жадны фрагмент не перэрываў правовую кляузу.
- Укладзіце кожны фрагмент два разы: аднойчы як стандартны ўпакоўаны вектар для шыранага аналізу, аднойчы як мнагавекторную матрыцу ColBERT для перацэнкі на рэвэле токэнаў.
- Загадзя запрашыце найлепшыя N кандыдатаў, выкарыстоўваючы бінарна квантаваныя ўпакоўаныя вектары.
- Перацэніце гэтыя кандыдаты за дапамогою ColBERT MAX_SIM у Qdrant.
- Выберыце найлепшыя фрагменты і перацэніце іх аддзельныя рэчыцы локальна на аднойчы з вектарам дапытку ColBERT.
- Адправіце толькі прыдатныя рэчыцы да LLM.
Цена для крэйт-бэнчмарка выкліквалася як $3,00 за мільйон токэнаў вхіднага даных, што адпавядае серэднім комерчыям мадэлям.
Схема збору даных
Наведзены выкладком скрыпт налаштавае стандартныя ўпакоўкі даных і мультывектары ColBERT пашырэй, пры чым мадэлі FastEmbed запускаюцца локальна на CPU.
from qdrant_client import QdrantClient, models
# ADDED: Load FastEmbed models locally on CPU
from fastembed import TextEmbedding, LateInteractionTextEmbedding
COLLECTION_NAME = "legal_discovery"
DENSE_DIM = 384 # BAAI/bge-small-en-v1.5
COLBERT_DIM = 128 # colbert-ir/colbertv2.0
# ADDED: Instantiate the vector models
dense_model = TextEmbedding("BAAI/bge-small-en-v1.5")
colbert_model = LateInteractionTextEmbedding("colbert-ir/colbertv2.0")
client = QdrantClient("<http://localhost:6333>")
client.create_collection(
collection_name=COLLECTION_NAME,
vectors_config={
"dense": models.VectorParams(
size=DENSE_DIM,
distance=models.Distance.COSINE,
quantization_config=models.BinaryQuantization(
binary=models.BinaryQuantizationConfig(always_ram=True),
),
),
"colbert": models.VectorParams(
size=COLBERT_DIM,
distance=models.Distance.COSINE,
multivector_config=models.MultiVectorConfig(
comparator=models.MultiVectorComparator.MAX_SIM
),
on_disk=True,
hnsw_config=models.HnswConfigDiff(m=0),
),
},
)
Сярод параметраў вектара ColBERT выдзеляецца адна настройка: конфігурацыя HNSW, у якой значэнне m становіць 0. Паколькі ColBERT выкарыстоўваецца толькі для перыякоравання прадаўжанага набора даных, яму не патрэбна графіка HNSW. Адмова ад графікі дапамагае заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды
# ADDED: Generate query embeddings (ColBERT uses query_embed to add prefix padding)
dense_query = next(dense_model.query_embed(query)).tolist()
colbert_query = next(colbert_model.query_embed(query)).tolist()
# Run the two-stage query in one network round-trip
results = client.query_points(
collection_name=COLLECTION_NAME,
prefetch=models.Prefetch(
query=dense_query,
using="dense",
limit=prefetch_limit,
params=models.SearchParams(
quantization=models.QuantizationSearchParams(rescore=False),
),
),
query=colbert_query,
using="colbert",
limit=top_k,
with_payload=True,
)
Флаг rescore=False у блаке prefetch заслуговае на увагу. За дапамою бінарнай квантызацыі, Qdrant зазвычай запрашоўвае кандыдатаў з квантаванымі вектарамі, а пасля завантажвае вектары з цэлым дзесяткам прыцэльнасці float32, каб перацяліць ўзнакі ўзнакоў. Адтуды, ўжо таму што стадыя ColBERT выканае болей тачную працу, выключэнне процэса перацяліць узнакоў узнакоў запобегае чытанню вектараў з цэлым дзесяткам прыцэльнасці з дыска і зменшае навантажэння на базу дадзенаў.
З фрагмента да рэчы
MAX_SIM дае адну агрэгатную узнаку на кожны фрагмент і не паказвае, якія токены яе стварылі. Чырабяць толькі рэлевантны тэкст можна за дапамою фінальнага локальнага крока, які дзеліць выграшныя фрагменты на рэчы і вычысляе узнакі ColBERT для кожной з іх. Паколькі гэта выканана толькі на пэўных фінальных фрагментах, дзе тэкст маўмілы, не выступае проблема масштабавання базы дадзенаў, а прыцэльнасць на рэвеніі рэчы можна досягнуць за дапамою CPU за незначныя зусілля.
import re
import numpy as np
# ADDED: Basic sentence splitter regex
SENTENCE_SPLIT = re.compile(r"(?<=[.;])\s+(?=[A-Z])")
def max_sim(query_vecs: np.ndarray, doc_vecs: np.ndarray) -> float:
# Compute token-to-token similarity matrix
sims = query_vecs @ doc_vecs.T # (num_query_tokens, num_doc_tokens)
# Sum the maximum similarity scores along the document axis
return float(sims.max(axis=1).sum())
def isolate_sentences(chunk_text: str, query_vecs: np.ndarray, colbert_model, top_n: int = 1):
# ADDED: Split chunk text into candidate sentences
sentences = [s.strip() for s in SENTENCE_SPLIT.split(chunk_text) if len(s.strip()) > 15]
if not sentences:
return [(chunk_text, 0.0)]
# Embed each sentence locally using ColBERT
sentence_vecs = list(colbert_model.embed(sentences))
scored = [(sentences[i], max_sim(query_vecs, sentence_vecs[i])) for i in range(len(sentences))]
scored.sort(key=lambda pair: pair[1], reverse=True)
return scored[:top_n]
Локальныя вычысленнія ведуцься быстра, адколькі ў iх беруцца удзел толькі тры-пяць верхніх частак. Калі выявляюцца найлепшыя рэчы, рэшта кожной частакі адкладаецца, і ствараецца пакет дадзеных для запыту.
def build_optimized_prompt(query: str, chunk_texts: list[str], colbert_model) -> str:
query_vecs = next(colbert_model.query_embed(query))
context_parts = []
for i, text in enumerate(chunk_texts):
top_sentences = isolate_sentences(text, query_vecs, colbert_model, top_n=1)
isolated_text = " ".join(s for s, _ in top_sentences)
context_parts.append(f"[Source Chunk {i+1}]: {isolated_text}")
context_str = "\n\n".join(context_parts)
return f"Context:\n{context_str}\n\nQuestion: {query}\nAnswer:"
Размах частака і чыстая цыфра
Сама толькі зменшэнне колькасці токэнаў ёсць прываблівым паказнікам. Тэорэтычна, якшо запакаваць дзесяць-дванаццаць прававых раздзелаў у адны частак на 2 000 слоў, а пасля застосаваць ізоляцыю абзацаў, можна досягнуць зменшэння па велічыні 85%. Але ў рэальных прававых сістэмах такой падход паграждае тачнасцю пошуку. Вялікі частак затапляе сігнал конкрэтнага пункта, напрыклад, ліміту абгарантавання прав на інтэлектуальную прыналежнасць трэціх сторон, у абрамляючым тэксце векторнага прастора. Запыты да такіх заплямаваных, багатоклauseных частакаў часта вяртаюць несвязаныя раздзелы, такія як гаранціі, адтуды што алгорытм пошуку не можа ізаліраваць цэльны элемент.
Рашэнь — гэта строгія межа: у кожны фрагмент можа быць толькі адна правовая кляуза, кожны фрагмент мае мячыць роўна адну адзінаковую частку, а кляузы маюць рэалістычную дужыну. За такой схемай тэсты паказалі падтверджанае зменшэння колькасці токенав у запытках на 67,1%. Ручныя перакананні ў пытаннях компенсацыі і праваў на ІП падтвердзілі, што былі знайдзены правыя кляузы, нават які загальны показнік зменіўся з тэорычных 87,3% да рэальных 67,1%. Чыстыя межы на рэвэлі частак маюць большое значэнне, чым вышэйшы процэнт.
Компроміс між затрымкай і карыстнасцю
Бінарная квантызацыя пацелуе, што дваэтапны працэс можа быць быстрэй за звычны ўплотнены пошук за скорасцю. Але гэта не так. У разных працоўках дваэтапны пошук быў усё час два-чатыры разы медленей, залежна ад апаратуры і ступеня запам’ятавання дадзеных: 39,5 мс проты 20,4 мс. Прычына простая. Пошук толькі з уплотненням — цэлае адшукванне эмбеддынга, тады калі ў дваэтапнай версіі запускаюцься як кодэры запыткаў, так і ColBERT, і ў базе дадзеных выконваецца матрыца порэвання токэнаў. Аддзельны працэс пошуку толькі з уплотненням даў усерэдненае значэнне 7,4 мс.
Змена ліміту прадзеўранага запрашэння з 10 кандыдатаў на 500 практычна нічога не змяніла, і затрымка засталася ў тым жа дыяпазоне. Додатковы час вызваны самай дваэтапной структурой, а не велічынай вікна прадзеўранага запрашэння.
У цэлым, часоўнае затрымленне працэса RAG з начала да канца вызвана гэнераваннем LLM, а не запытамі да базы дадзеных. Зберагчы больш чым 400 токенаў за запыт, можна скорачыць празрабатванне і гэнераванне на сотні мілісекунд, таму весь прыемлек стае значна быстрэй.
Які ўцём
Пры цэне 3,00 долераў за мільйон токенаў уводу, 67,1% змена колькасці токенаў у запытах даўа значны уцём. Для працэса аналізу дакументаў у справах пра судовыя споры, дзе выконуецца 500 000 запытоў, аднае пераглядванне справы дазволяе заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёды заўсёд
Тры ўзлёмы вялікага значэння дапамагаюць выявіць, чы рэшэнне падходзіць для данай навантажэннай. Першы — гэта правільнасць адпаведзей на наборы запытанняў, перакантрольваныя вручную, адтак эканомія токенаў мае малая значыць, калі не хапае правільнага клазу. Другі — гэта час адпаведзі на весь запыт, уключаючы працэс стварэння рэшэння, адтак дадатковы час работы з базай дадзеных ёсць мінімальны паўстаўляючыся ля часу вызову модэлю. Трэці — гэта колькісць токенаў у кожным запытанні до і пасля ізоляціі рэчэння, якая вімерваецца за прыменным тарыфам. Рэзультаты, атрыманыя для аднаго тыпу дакументаў, можа не паўтарыцца для іншага, таму тэставанне трэба адрабоўваць на цэльным корпусе дакументаў.
Урокі для практычнага выкарыстоўвання
- Зберагаце працэс пераранкінгу ўнутрь базы дадзеных. Зовнішнія API для пераранкінгу ствараюць додатковыя пункты абяваў пра абыекты і дзвойныя перадачы дадзеных па сецыі, тады калі вбудованы аператор MAX_SIM выконвае работу за адну перадачу.
- Выключыце HNSW для польў, дзе трэба толькі перыякшаць рэйтынг. Установка m=0 у настройках з калькуляцыяй колькіх вектараў запобегае дорогам кроку індэксавання, які не дае жадных выгод для матрыц ColBERT пад час выкарыстоўвання ў пошуку.
- Не давайце вялікім фрагментам тэксту большай значымасці. Занадта вялікія фрагменты можу даць вялікія заўзяткі ў колькасці токэнаў на паперы, але пагіршуюць семантычнае прэдставленне тэксту і зніжуюць точнасць рэзультатаў.
- Ўжывайце справжній токенізатор для рэчэй. Простыя рэгулярныя выразы падыходзяць для дэманстрацый, але у прымэтнай програме трэба выкарыстоўваць бібліятэкі на кшталт NLTK або spaCy, якія можу правільна адобразіць прававыя скраценні, такія як Inc., Sec. і Exhibit A, не паскладваючы рэчэы неправільна.
Натыўнае пораўнанне калькуляцыяй колькіх вектараў плюс локальная калькуляцыя рэйтынга на рэвэле рэчэй можа стварыць точны і эканамічна рэшаючы працэс, які вяртае тую частку тэксту, якая трэба корыстувачу, не паднімаючы месячныя расходы на API.
Спаднёе чытанне
- RAG з LangChain ад нуля: загрузчыкі, фрагменты, эмбеддынгі, ацэнка — Построўка сістэмы запытанняў і адказаў для дакументаў па шагах: загрузка файлаў, разбіўка на фрагменты, стварэнне эмбеддынгаў, этапы індэксавання і выкарыстоўвання інформацыі, а таксама спосабы ацэнкі якосты адказаў.
- Claude Agent SDK проты OpenAI Agents SDK проты Google ADK: адні агенты, трое спосабаў пабудовы — Аднаковы агент для стварэння етыкетак з інформацыяй пра паўжыванне, пабудаваны за дапамогою Claude Agent SDK, OpenAI Agents SDK і Google ADK; чыстая адпаведнасць таго, што робіць кожны з інструментаў, і таго, што трэба стварыць самастаў.