arielshemesh1999@gmail.com · ישראל
← כל המאמרים

TurboVec

vector index ב-Rust עם כריכות Python, בנוי על האלגוריתם TurboQuant של Google Research: מכניס קורפוס של 10 מיליון מסמכים מ-31GB ל-4GB, ומחפש מהר יותר מ-FAISS. איך הדחיסה עובדת, וכיצד מריצים אותו.

מה זה

TurboVec הוא vector index — מבנה נתונים שמאחסן embeddings ומאפשר חיפוש שכן-קרוב (nearest-neighbor) מהיר עליהם. התיאור הרשמי במאגר: "A vector index built on TurboQuant, written in Rust with Python bindings." כלומר הליבה כתובה ב-Rust לביצועים, ויש כריכות Python כדי שתשתמשו בו מקוד רגיל. הרישיון MIT, והמאגר (RyanCodrai/turbovec) עומד על כ-11.3 אלף כוכבים ו-981 forks נכון לכתיבה — פרויקט צעיר יחסית (נוצר במרץ 2026) שצבר תאוצה מהר.

הטענה שמושכת תשומת לב מנוסחת במאגר במספרים: קורפוס של 10 מיליון מסמכים ששוקל 31GB כ-float32 נכנס ל-4GB — ומחפש מהר יותר מ-FAISS, ספריית החיפוש-הווקטורי הסטנדרטית של התעשייה. שני הדברים האלה ביחד הם הלא-טריוויאלי: דחיסה אגרסיבית בדרך כלל פוגעת או ב-recall או במהירות, ו-TurboVec מנסה להראות שאפשר גם וגם.

הסוד הוא לא ב-TurboVec עצמו אלא באלגוריתם שעליו הוא בנוי: TurboQuant, עבודה של Google Research שפורסמה ב-ICLR 2026 (arxiv:2504.19874). TurboVec הוא מימוש production-ready של האלגוריתם הזה כספרייה שאפשר להתקין ב-pip אחד — הגשר בין נייר אקדמי לכלי שמריצים בפועל.

איך זה עובד — הצינור

הרעיון המרכזי הוא quantization: במקום לאחסן כל מספר ב-vector כ-float של 32 ביט, מצמצמים אותו ל-2 או 4 ביט. ב-2 ביט זו דחיסה פי 16 (vector בגודל 1536 ממדים יורד מ-6,144 בתים ל-384 בתים). האתגר הוא לעשות את זה בלי להרוס את הדיוק של החיפוש. TurboQuant עושה זאת בצינור של כמה שלבים:

  • נרמול — מפרידים את אורך ה-vector מ-כיוונו, ועובדים עם כיוון יחידה.
  • סיבוב אקראי (random rotation) — מכפילים במטריצה אורתוגונלית. אחרי הסיבוב הקואורדינטות מתפלגות לפי התפלגות Beta צפויה — וזה מה שמאפשר לכמת אותן ביעילות.
  • כיול לכל קואורדינטה (TQ+) — מתאימים shift/scale לכל קואורדינטה בזמן ה-add הראשון, ומקפיאים אותם מכאן והלאה.
  • קוונטיזציה סקלרית מסוג Lloyd-Max — גבולות דליים אופטימליים מחושבים מראש (4 דליים ל-2 ביט, 16 ל-4 ביט).
  • אריזת ביטים — דוחסים לבתים.
  • ניקוד מנורמל-מחדש — שומרים לכל vector גורם תיקון שמבטל את ההטיה שהקוונטיזציה מכניסה לניקוד.

בזמן חיפוש, מסובבים את ה-query פעם אחת ואז מנקדים אותו ישירות מול ערכי הקוד באמצעות טבלאות חיפוש (nibble-split lookup). השילוב של דחיסה לרמת ביטים + טבלאות חיפוש הוא מה שנותן גם את הזיכרון הקטן וגם את המהירות.

התקנה והגדרה

במסלול ה-Python זו התקנת pip אחת. יש extras לאינטגרציה עם frameworks נפוצים של RAG:

# Core install
pip install turbovec

# With a framework integration (pick what you use)
pip install turbovec[langchain]
pip install turbovec[llama-index]
pip install turbovec[haystack]
pip install turbovec[agno]

למי שרוצה לבנות מהמקור (למשל לפיתוח על ה-Rust), משתמשים ב-maturin כדי לבנות את גלגל ה-Python מקוד ה-Rust:

pip install maturin
cd turbovec-python
maturin build --release
pip install target/wheels/*.whl
cargo build --release

יכולות מרכזיות — קוד

ה-API מינימלי ומכוון. השימוש הבסיסי: יוצרים index עם מימד וגודל-ביט, מוסיפים vectors, מחפשים, ושומרים לדיסק.

from turbovec import TurboQuantIndex

index = TurboQuantIndex(dim=1536, bit_width=4)
index.add(vectors)
scores, indices = index.search(query, k=10)

# Persist and reload
index.write("my_index.tv")
loaded = TurboQuantIndex.load("my_index.tv")

שתי תכונות שמבדילות אותו מ-index צעצוע ומסמנות שהוא נבנה לשימוש אמיתי:

1. מזהים יציבים (stable IDs). במערכת אמיתית מסמכים נמחקים ומתעדכנים, ואינדקס מבוסס-מיקום נשבר. IdMapIndex מצמיד לכל vector מזהה משלכם, כך שאפשר להסיר פריט בלי לבנות מחדש את כל האינדקס:

from turbovec import IdMapIndex
import numpy as np

index = IdMapIndex(dim=1536, bit_width=4)
index.add_with_ids(vectors, np.array([1001, 1002, 1003], dtype=np.uint64))
scores, ids = index.search(query, k=10)
index.remove(1002)

2. סינון בזמן חיפוש (filtered search). זו אחת מנקודות המכאוב הגדולות של חיפוש וקטורי בעולם רב-דיירים (multi-tenant): איך מחזירים רק תוצאות של דייר מסוים בלי לפגוע ב-recall. TurboVec תומך ב-allowlist של מזהים שמחושב בזמן השאילתה — כך אפשר לחבר אותו ל-SQL לגבול ההרשאות:

allowed = np.array(
    db.execute("SELECT id FROM docs WHERE tenant=?", (t,)).fetchall(),
    dtype=np.uint64,
)
scores, ids = idx.search(query, k=10, allowlist=allowed)

תכונות נוספות שכדאי להכיר: online ingest — אין שלב אימון נפרד ואין פרמטרים לכוונן, פשוט מוסיפים vectors ומחפשים; SIMD kernels ייעודיים (NEON ל-ARM, AVX-512BW ל-x86) שמאיצים את הניקוד; ואינטגרציות מובנות ל-LangChain, LlamaIndex, Haystack ו-Agno כך שהוא נכנס כ-vector store ישירות לתוך pipeline קיים של RAG. יש גם API מקביל ב-Rust עבור מי שעובד בשפה הזו:

use turbovec::TurboQuantIndex;

let mut index = TurboQuantIndex::new(1536, 4).unwrap();
index.add(&vectors);
let results = index.search(&queries, 10);

ביצועים — המספרים

הטענות במאגר מגובות במדידות מול FAISS, ושווה לקרוא אותן בזהירות כי הן מפורטות ולא סיסמתיות:

  • Recall (100 אלף vectors, k=64): מנצח את FAISS IndexPQ ב-0.2 עד 1.9 נקודות ב-R@1 על embeddings של OpenAI במימד 1536/3072; על GloVe d=200 יתרון של 0.9 נקודה ב-4 ביט, תיקו ב-2 ביט.
  • מהירות (100 אלף vectors, 1000 שאילתות, k=64, חציון של 5 הרצות): על ARM (Apple M3 Max) מהיר ב-10 עד 19 אחוז מ-FAISS FastScan בכל התצורות; על x86 (Intel Xeon Platinum 8481C) מנצח ב-4 ביט בכ-5 אחוז, אך מפגר בכ-8 אחוז ב-2 ביט בהרצה חד-חוטית.
  • דחיסה: פי 16 (float32 ל-2 ביט), פי 8 (float32 ל-4 ביט).

התמונה המפוכחת: היתרון הגדול הוא על ARM, והוא עקבי. על x86 התמונה מעורבת ותלוית-תצורה. זה לא "פי 10 מהר יותר" — זה שיפור מדוד באחוזים בודדים עד עשרות, עם יתרון הזיכרון כקלף החזק האמיתי.

מתי להשתמש — ומגבלות

TurboVec רלוונטי כשהזיכרון הוא הצוואר הבקבוק: קורפוס גדול של embeddings שצריך להיכנס ל-RAM של מכונה אחת במקום להתפזר על cluster יקר. דחיסה פי 16 בלי קריסת recall יכולה להפוך פריסה שדרשה כמה שרתים לפריסה על שרת אחד — וזה ההבדל המעשי החשוב יותר מאחוזי המהירות. הוא מתאים במיוחד למי שכבר משתמש ב-FAISS ומרגיש את עלות הזיכרון.

המגבלות מוצהרות בכנות במאגר ושוות זהב כשמודדים אם זה מתאים לכם. ראשית, כיול ה-TQ+ קופא אחרי ה-add הראשון — אין אימון-מחדש על נתונים שמגיעים אחר כך, אז אם התפלגות ה-embeddings שלכם נודדת לאורך זמן, הכיול עלול לא להתאים. שנית, הנחת התפלגות ה-Beta היא אסימפטוטית — היא מתהדקת ככל שהמימד גבוה, אבל במימד נמוך היא רופפת יותר, מה שמסביר את התיקו ב-2 ביט על GloVe d=200. שלישית, הקידוד דורש מכפלה סקלרית נוספת אחת בכל vector — עלות קטנה אך לא אפסית בזמן ה-ingest.

בשימוש יומיומי הערך האמיתי הוא לא רק החיסכון בזיכרון אלא ה-online ingest: בלי שלב אימון ובלי פרמטרים לכוונן, המרחק בין "יש לי embeddings" ל-"יש לי חיפוש שעובד" מתקצר לכמה שורות. הטרייד-אוף הוא שכדאי להחליט מראש על גודל-הביט (2 מול 4) ועל סדר ה-add, כי הכיול הקפוא הופך את הבחירות האלה לקשות-לשינוי בדיעבד.

מקורות

המאגר: github.com/RyanCodrai/turbovec — רישיון MIT, כ-11.3 אלף כוכבים, 981 forks (אומת מול GitHub API, יוני 2026). זמין גם ב-PyPI (turbovec) וב-crates.io.
האלגוריתם: TurboQuant, Google Research, ICLR 2026 — arxiv:2504.19874.