- يحسب البحث الدقيق عن المتجهات المسافة بين النقطة المعطاة وجميع النقاط في الفضاء المتجهي. وهذا يضمن أعلى دقة ممكنة، أي إن النقاط المُعادة تكون مضمونة بوصفها أقرب الجيران الفعليين. ونظرًا إلى أن الفضاء المتجهي يُفحَص بالكامل، فقد يكون البحث الدقيق عن المتجهات بطيئًا جدًا للاستخدامات العملية.
- يشير البحث التقريبي عن المتجهات إلى مجموعة من التقنيات (مثل بُنى بيانات خاصة كالرُّسوم البيانية والغابات العشوائية) التي تحسب النتائج بسرعة أكبر بكثير من البحث الدقيق عن المتجهات. وعادةً ما تكون دقة النتائج “جيدة بما يكفي” للاستخدام العملي. كما توفّر العديد من التقنيات التقريبية معلمات لضبط المفاضلة بين دقة النتائج ووقت البحث.
vectors من نوع Array، مثل Array(Float64) أو Array(Float32) أو Array(BFloat16).
يكون المتجه المرجعي مصفوفةً ثابتة، ويُعرَّف كتعبير جدول شائع.
تحسب <DistanceFunction> المسافة بين النقطة المرجعية وجميع النقاط المخزَّنة.
ويمكن استخدام أي من دوال المسافة المتاحة لهذا الغرض.
ويحدّد <N> عدد الجيران المطلوب إرجاعهم.
البحث الدقيق عن المتجهات
مثال
البحث التقريبي عن المتجهات
فهارس تشابه المتجهات
تتوفّر فهارس تشابه المتجهات في ClickHouse الإصدار 25.8 وما بعده.
إذا واجهت أي مشكلات، يُرجى فتح بلاغ في مستودع ClickHouse.
إنشاء فهرس لتشابه المتجهات
ALTER TABLE أعلاه لا تؤدي إلا إلى إنشاء الفهرس للبيانات الجديدة التي ستُدرَج في الجدول لاحقًا.
ولإنشاء الفهرس للبيانات الموجودة أيضًا، تحتاج إلى إضفاء الطابع المادي عليه:
<distance_function> إحدى القيم التالية:
L2Distance، وهي المسافة الإقليدية، وتمثل طول القطعة المستقيمة بين نقطتين في الفضاء الإقليدي،cosineDistance، وهي مسافة جيب التمام، وتمثل الزاوية بين متجهين غير صفريين، أوdotProduct، وهو الضرب النقطي (الضرب الداخلي)، ويمثل مجموع نواتج الضرب عنصرًا بعنصر لمتجهين. وهو مكافئ لـcosineDistanceفي البيانات المُطبَّعة.
L2Distance عادةً الخيار الأفضل، وإلا فيُوصى باستخدام cosineDistance للتعويض عن اختلاف المقياس.
بالنسبة إلى دالتي المسافة
L2Distance وcosineDistance، فإن القيمة الأصغر تعني تشابهًا أعلى، بينما بالنسبة إلى dotProduct، فإن القيمة الأكبر تعني تشابهًا أعلى.
ونتيجة لذلك، لا يمكن استخدام فهارس المتجهات مع L2Distance وcosineDistance إلا في استعلامات SELECT [...] ORDER BY [...] ASC (حيث إن ASC هي القيمة الافتراضية لـ ORDER BY)، بينما لا يمكن استخدام فهارس المتجهات المُنشأة لـ dotProduct إلا في استعلامات SELECT [...] ORDER BY [...] DESC.<dimensions> عدد عناصر المصفوفة في العمود الأساسي.
إذا عثر ClickHouse على مصفوفة بعدد عناصر مختلف أثناء إنشاء الفهرس، فسيتم تجاهل الفهرس وإرجاع خطأ.
تشير المعلمة الاختيارية GRANULARITY <N> إلى حجم حبيبات الفهرس (انظر هنا).
وعلى خلاف فهارس التخطي العادية، التي تستخدم قيمة افتراضية لمحببية الفهرس مقدارها 1، تستخدم فهارس تشابه المتجهات 100 مليون بوصفها محببية الفهرس الافتراضية.
تضمن هذه القيمة ألّا يُنشأ داخليًا سوى عدد قليل من الفهارس حتى مع الأجزاء الكبيرة.
نوصي بتغيير محببية الفهرس فقط للمستخدمين المتقدمين الذين يفهمون تبعات ما يفعلونه (انظر أدناه).
تُعد فهارس تشابه المتجهات عامة، بمعنى أنها يمكن أن تدعم طرق بحث تقريبي مختلفة.
ويُحدَّد النوع المستخدم فعليًا بواسطة المعلمة <type>.
حتى الآن، الطريقة الوحيدة المتاحة هي HNSW (ورقة أكاديمية)، وهي تقنية شائعة ومتقدمة للبحث المتجهي التقريبي تعتمد على رسوم بيانية هرمية للتقارب.
إذا استُخدم HNSW بوصفه النوع، فيمكن للمستخدمين اختياريًا تحديد معلمات إضافية خاصة بـ HNSW:
- يتحكم
<quantization>في تكميم المتجهات في رسم بياني القرب. القيم الممكنة هيf64وf32وf16وbf16وi8أوb1. القيمة الافتراضية هيbf16. لاحظ أن هذه المَعلمة لا تؤثر في تمثيل المتجهات في العمود الأساسي. - يتحكم
<hnsw_max_connections_per_layer>في عدد الجيران لكل عقدة في الرسم البياني، ويُعرف أيضًا باسم المَعلمة الفائقةMفي HNSW. القيمة الافتراضية هي32. وتعني القيمة0استخدام القيمة الافتراضية. - يتحكم
<hnsw_candidate_list_size_for_construction>في حجم قائمة المرشحين الديناميكية أثناء إنشاء رسم HNSW البياني، ويُعرف أيضًا باسم المَعلمة الفائقةef_constructionفي HNSW. القيمة الافتراضية هي128. وتعني القيمة0استخدام القيمة الافتراضية.
- لا يمكن إنشاء فهارس تشابه المتجهات إلا على أعمدة من النوع Array(Float32) أو Array(Float64) أو Array(BFloat16). ولا يُسمح بالمصفوفات ذات القيم العائمة القابلة لأن تكون NULL أو منخفضة الكاردينالية، مثل
Array(Nullable(Float32))وArray(LowCardinality(Float32)). - يجب إنشاء فهارس تشابه المتجهات على أعمدة مفردة.
- يمكن إنشاء فهارس تشابه المتجهات على تعبيرات محسوبة (مثل
INDEX index_name arraySort(vectors) TYPE vector_similarity([...]))، ولكن لا يمكن استخدام هذه الفهارس لاحقًا في البحث التقريبي عن الجيران. - تتطلب فهارس تشابه المتجهات أن تحتوي جميع المصفوفات في العمود الأساسي على
<dimension>عنصرًا — ويُتحقق من ذلك أثناء إنشاء الفهرس. ولاكتشاف أي انتهاك لهذا الشرط في أقرب وقت ممكن، يمكن للمستخدمين إضافة قيد على عمود المتجهات، مثلCONSTRAINT same_length CHECK length(vectors) = 256. - وبالمثل، يجب ألا تكون قيم المصفوفة في العمود الأساسي فارغة (
[]) أو ذات قيمة افتراضية (وهي أيضًا[]).
استخدام فهرس تشابه المتجهات
لاستخدام فهارس تشابه المتجهات، يجب أن تكون قيمة الإعداد compatibility هي
'' (القيمة الافتراضية)، أو '25.1' أو إصدارًا أحدث.SELECT [...] SETTINGS hnsw_candidate_list_size_for_search = <value>).
القيمة الافتراضية للإعداد وهي 256 تُحقق أداءً جيداً في غالبية حالات الاستخدام.
القيم الأعلى تعني دقةً أفضل على حساب أداء أبطأ.
إذا كان بإمكان الاستعلام استخدام فهرس تشابه المتجهات، يتحقق ClickHouse من أن قيمة LIMIT <N> المحددة في استعلامات SELECT تقع ضمن حدود معقولة.
وتحديدًا، يُعاد خطأ إذا كانت <N> أكبر من قيمة الإعداد max_limit_for_vector_search_queries التي قيمتها الافتراضية 100.
قد تؤدي قيم LIMIT الكبيرة جدًا إلى إبطاء عمليات البحث، وغالبًا ما تشير إلى خطأ في الاستخدام.
للتحقق مما إذا كان استعلام SELECT يستخدم فهرس تشابه المتجهات، يمكنك إضافة EXPLAIN indexes = 1 في بداية الاستعلام.
كمثال، الاستعلام
Skip واسم فهرس المتجه ونوعه (في المثال، idx وvector_similarity).
في هذه الحالة، تخطّى فهرس تشابه المتجهات اثنتين من أصل أربع حبيبات، أي ما يعادل 50% من البيانات.
وكلما زاد عدد الحبيبات القابلة للتخطي، كان استخدام الفهرس أكثر فاعلية.
التصفية اللاحقة والتصفية المسبقة
يمكن للمستخدمين اختياريًا تحديد جملة WHERE مع شروط تصفية إضافية لاستعلام SELECT.
سيُقيِّم ClickHouse شروط التصفية هذه باستخدام إحدى استراتيجيتَي post-filtering أو pre-filtering.
باختصار، تحدد كلتا الاستراتيجيتين الترتيبَ الذي تُقيَّم به عوامل التصفية:
- تعني التصفية اللاحقة أنه يجري أولًا تقييم فهرس تشابه المتجهات، ثم يقيّم ClickHouse عوامل التصفية الإضافية المحددة في عبارة
WHERE. - وتعني التصفية المسبقة أن ترتيب تقييم عوامل التصفية يكون بالعكس.
- تواجه التصفية اللاحقة مشكلة عامة، وهي أنها قد تُرجِع عددًا أقل من الصفوف المطلوبة في عبارة
LIMIT <N>. ويحدث ذلك عندما لا يستوفي صفّ نتيجة واحد أو أكثر، أعاده فهرس تشابه المتجهات، المرشِّحات الإضافية. - تُعدّ التصفية المسبقة عمومًا مشكلة غير محلولة. توفّر بعض قواعد بيانات المتجهات المتخصصة خوارزميات للتصفية المسبقة، لكن معظم قواعد البيانات العلائقية (بما في ذلك ClickHouse) تعود إلى البحث الدقيق عن أقرب الجيران، أي فحص brute-force من دون فهرس.
year وشُغِّل الاستعلام التالي:
- إذا كان شرط التصفية يستبعد صفًا واحدًا على الأقل داخل جزء، فسيعود ClickHouse إلى التصفية المسبقة للنطاقات “المتبقية” داخل الجزء،
- إذا كان شرط التصفية لا يستبعد أي صفوف داخل جزء، فسينفّذ ClickHouse التصفية اللاحقة لهذا الجزء.
auto التي تطبّق الاستدلالات المذكورة أعلاه) على prefilter.
ويكون ذلك مفيدًا لفرض التصفية المسبقة في الحالات التي تكون فيها شروط التصفية الإضافية شديدة الانتقائية.
على سبيل المثال، قد يستفيد الاستعلام التالي من التصفية المسبقة:
SETTINGS vector_search_filter_strategy = 'prefilter' إلى الاستعلام)، يعثر ClickHouse أولًا على جميع الكتب التي يقل سعرها عن دولارين، ثم يُجري بحثًا متجهيًا بالقوة الغاشمة على الكتب التي عُثر عليها.
وكحل بديل للمشكلة المذكورة أعلاه، يمكن ضبط vector_search_index_fetch_multiplier (القيمة الافتراضية: 1.0، والحد الأقصى: 1000.0) على قيمة > 1.0 (على سبيل المثال، 2.0).
ويُضرَب عدد أقرب الجيران التي يجري جلبها من فهرس المتجهات في قيمة هذا الإعداد، ثم يُطبَّق عامل التصفية الإضافي على تلك الصفوف لإرجاع عدد من الصفوف يساوي قيمة LIMIT.
على سبيل المثال، يمكننا تنفيذ الاستعلام مرة أخرى، ولكن بمعامل 3.0:
vector_search_index_fetch_multiplier قد يخفف من هذه المشكلة، ولكن في الحالات القصوى (عندما يكون شرط WHERE انتقائيًا جدًا)، يظل من الممكن إعادة عدد أقل من N من الصفوف المطلوبة.
إعادة التقييم
تُجري skip indexes في ClickHouse عادةً التصفية على مستوى الـ حبيبة، أي إن عملية lookup في skip index (داخليًا) تُرجع قائمة بالـ حبيبات التي يُحتمل أن تتطابق، مما يقلل حجم read data في الفحص اللاحق.
يعمل هذا جيدًا مع skip indexes عمومًا، لكن في حالة فهارس تشابه المتجهات، فإنه يسبب “عدم تطابق في المحببية”.
وبمزيد من التفصيل، يحدد فهرس تشابه المتجهات أرقام الصفوف الخاصة بـ N من أكثر المتجهات تشابهًا بالنسبة إلى متجه مرجعي معيّن.
مع الإعداد vector_search_with_rescoring = 1، يقرأ ClickHouse المتجهات الأصلية ذات الدقة الكاملة للصفوف المرشحة ويحسب المسافة النهائية ضمن مسار SQL العادي.
وعندما تسمح query plan بذلك، يقيّد ClickHouse الفحص بالصفوف المرشحة التي يُرجعها فهرس المتجهات قبل حساب المسافة النهائية.
وتُسمى هذه الخطوة إعادة التقييم، ويمكن أن تحسّن الدقة، وخاصةً مع فهارس المتجهات المُكمّاة، لأن الترتيب النهائي يستخدم المتجهات المخزنة بدلًا من المسافات القادمة من الفهرس.
إذا كانت عوامل التصفية الإضافية تستبعد عددًا كبيرًا جدًا من المرشحين أو كانت هناك حاجة إلى قدر أكبر من الاسترجاع، فقم بزيادة الإعداد vector_search_index_fetch_multiplier لكي يُرجع فهرس المتجهات مزيدًا من الصفوف المرشحة لإعادة التقييم.
لذلك يوفّر ClickHouse تحسينًا يعطّل إعادة التقييم ويُرجع أكثر المتجهات تشابهًا ومسافاتها مباشرةً من index.
يكون هذا التحسين enabled افتراضيًا، راجع الإعداد vector_search_with_rescoring.
وعلى مستوى عام، تعمل هذه الآلية بأن ClickHouse يوفّر أكثر المتجهات تشابهًا ومسافاتها على هيئة عمود افتراضي _distance.
ولرؤية ذلك، شغّل query بحث متجهي باستخدام EXPLAIN header = 1:
قد يرجع الاستعلام الذي يُشغَّل دون إعادة التقييم (
vector_search_with_rescoring = 0) ومع تفعيل النسخ المتماثلة المتوازية، تلقائيًا إلى إعادة التقييم.ضبط الأداء
CODEC(NONE) لعمود المتجهات كما يلي:
system.text_log) إلى أن فهرس تشابه المتجهات قيد التحميل.
وإذا ظهرت هذه الرسائل بشكل متكرر مع استعلامات بحث متجهي مختلفة، فهذا يشير إلى أن حجم ذاكرة التخزين المؤقت منخفض جدًا.
تخزّن ذاكرة التخزين المؤقت لفهرس تشابه المتجهات حبيبات فهرس المتجهات.
إذا كان حجم حبيبات فهرس المتجهات الفردية أكبر من حجم ذاكرة التخزين المؤقت، فلن تُخزَّن مؤقتًا.
لذلك، يُرجى التأكد من حساب حجم فهرس المتجهات (استنادًا إلى الصيغة الواردة في “تقدير التخزين واستهلاك الذاكرة” أو system.data_skipping_indices) وتحديد حجم ذاكرة التخزين المؤقت بما يتناسب مع ذلك.
يقلل التكميم من دقة البحث المتجهي مقارنةً بالبحث في قيم الفاصلة العائمة الأصلية كاملة الدقة (
f32).
ومع ذلك، في معظم مجموعات البيانات، يؤدي تكميم brain float بنصف الدقة (bf16) إلى فقدان طفيف جدًا في الدقة، لذا تستخدم فهارس تشابه المتجهات أسلوب التكميم هذا افتراضيًا.
أما تكميم ربع الدقة (i8) والتكميم الثنائي (b1) فيؤديان إلى فقدان ملحوظ في دقة البحث المتجهي.
ولا نوصي باستخدام هذين النوعين من التكميم إلا إذا كان حجم فهرس تشابه المتجهات أكبر بكثير من حجم DRAM المتاح.
وفي هذه الحالة، نقترح أيضًا تمكين إعادة التقييم (vector_search_index_fetch_multiplier، vector_search_with_rescoring) لتحسين الدقة.
ولا يُنصح بالتكميم الثنائي إلا في حالتين فقط: 1) عند استخدام تضمينات مُطبَّعة (أي إن طول المتجه = 1، وعادةً ما تكون نماذج OpenAI مُطبَّعة)، و2) عند استخدام مسافة جيب التمام بوصفها دالة المسافة.
ويستخدم التكميم الثنائي داخليًا مسافة Hamming لبناء الرسم البياني للتقارب والبحث فيه.
وتستخدم خطوة إعادة التقييم المتجهات الأصلية كاملة الدقة المخزنة في الجدول لتحديد أقرب الجيران عبر مسافة جيب التمام.
ضبط نقل البيانات
يُقدَّم المتجه المرجعي في استعلام البحث المتجهي من قِبل المستخدم، ويُسترجع عادةً عبر إجراء استدعاء إلى نموذج لغوي كبير (LLM).
وقد يبدو نموذج شيفرة بايثون المعتاد الذي يُجري بحثًا متجهيًا في ClickHouse كما يلي
search_v في المقتطف أعلاه) عدد كبير جدًا من الأبعاد.
فعلى سبيل المثال، توفّر OpenAI نماذج تُنشئ متجهات تضمين تضم 1536 أو حتى 3072 بُعدًا.
في الشيفرة أعلاه، يستبدل برنامج تشغيل ClickHouse لبايثون متجه التضمين بسلسلة نصية مقروءة للبشر، ثم يرسل استعلام SELECT بالكامل كسلسلة نصية.
وبافتراض أن متجه التضمين يتكوّن من 1536 قيمة فاصلة عائمة أحادية الدقة، فإن طول السلسلة المُرسلة يصل إلى 20 كيلوبايت.
ويؤدي ذلك إلى ارتفاع استخدام CPU بسبب تقسيم النص إلى رموز، والتحليل، وإجراء آلاف التحويلات من سلسلة نصية إلى قيمة فاصلة عائمة.
كما يتطلب ذلك مساحة كبيرة في ملف سجل ClickHouse server، مما يسبب أيضًا تضخمًا في system.query_log.
لاحظ أن معظم نماذج LLM تُرجع متجه تضمين على هيئة قائمة أو مصفوفة NumPy من قيم فاصلة عائمة أصلية.
لذلك نوصي تطبيقات بايثون بربط معلمة متجه المرجع بالصيغة الثنائية باستخدام الأسلوب التالي:
system.query_log.
الإدارة والمراقبة
الاختلافات عن فهارس تخطي البيانات العادية
GRANULARITY = [N] حبيبات ([N] = 1 افتراضيًا في فهارس تخطي البيانات العادية).
على سبيل المثال، إذا كانت دقة حبيبات الفهرس الأساسي للجدول 8192 (الإعداد index_granularity = 8192) وكانت GRANULARITY = 2، فستحتوي كل كتلة مفهرسة على 16384 صفًا.
لكن بُنى البيانات والخوارزميات الخاصة ببحث أقرب الجيران التقريبي هي بطبيعتها موجّهة نحو الصفوف.
فهي تخزّن تمثيلًا مضغوطًا لمجموعة من الصفوف، كما تُرجع صفوفًا لاستعلامات البحث المتجهي.
ويؤدي ذلك إلى بعض الاختلافات غير البديهية نوعًا ما في سلوك فهارس تشابه المتجهات مقارنةً بفهارس تخطي البيانات العادية.
عندما يعرّف المستخدم فهرس تشابه متجهات على عمود، ينشئ ClickHouse داخليًا “فهرسًا فرعيًا” لتشابه المتجهات لكل كتلة فهرس.
ويكون الفهرس الفرعي “محليًا” بمعنى أنه لا يعرف إلا صفوف كتلة الفهرس التي ينتمي إليها.
في المثال السابق، وبافتراض أن العمود يحتوي على 65536 صفًا، نحصل على أربع كتل فهرس (تمتد عبر ثماني حبيبات) وفهرس تشابه متجهات فرعي لكل كتلة فهرس.
ويستطيع الفهرس الفرعي نظريًا أن يُرجع مباشرةً الصفوف التي تحتوي على أقرب N نقاط ضمن كتلة الفهرس الخاصة به.
بالنسبة إلى الاستعلامات التي تستخدم vector_search_with_rescoring = 1، يمكن لـ ClickHouse استخدام مواضع الصفوف هذه لتصفية الصفوف قبل حساب المسافة النهائية من المتجهات المخزّنة عندما تسمح خطة الاستعلام بهذا التحسين.
ومن دون إعادة التقييم، يستخدم ClickHouse المسافات من فهرس المتجهات مباشرةً عبر العمود الافتراضي _distance.
ويظل كلا الوضعين يستخدمان نطاقات الحبيبات المحيطة لجدولة عمليات القراءة، وهذا يختلف عن فهارس تخطي البيانات العادية التي تتخطى البيانات على مستوى كتل الفهرس.
تحدّد المعلمة GRANULARITY عدد فهارس تشابه المتجهات الفرعية التي يتم إنشاؤها.
فكلما كانت قيم GRANULARITY أكبر، قلّ عدد فهارس تشابه المتجهات الفرعية ولكن ازداد حجمها، إلى أن نصل إلى حالة لا يكون فيها للعمود (أو لجزء بيانات العمود) سوى فهرس فرعي واحد فقط.
في هذه الحالة، تكون لدى الفهرس الفرعي رؤية “شاملة” لجميع صفوف العمود، ويمكنه أن يُرجع مباشرةً جميع حبيبات العمود (الجزء) التي تحتوي على صفوف ذات صلة (ويكون عدد هذه الحبيبات بحد أقصى LIMIT [N]).
عند استخدام vector_search_with_rescoring = 1، يستطيع ClickHouse بعد ذلك قراءة مواضع الصفوف المطابقة وحساب المسافة الدقيقة لتلك الصفوف.
أما عند استخدام قيمة صغيرة لـ GRANULARITY، فيمكن لكل فهرس فرعي أن يُرجع ما يصل إلى LIMIT N من الصفوف المرشحة.
ونتيجةً لذلك، قد يلزم قراءة عدد أكبر من الصفوف المرشحة وتصفيتها لاحقًا.
لاحظ أن دقة البحث متساوية في كلتا الحالتين، والاختلاف الوحيد هو في أداء المعالجة.
ويُوصى عمومًا باستخدام قيمة GRANULARITY كبيرة لفهارس تشابه المتجهات، واللجوء إلى قيم GRANULARITY أصغر فقط عند ظهور مشكلات مثل الاستهلاك المفرط للذاكرة من قِبل بُنى تشابه المتجهات.
إذا لم يتم تحديد GRANULARITY لفهارس تشابه المتجهات، فإن القيمة الافتراضية هي 100 مليون.
مثال
Query
Response
البحث المتجهي باستخدام مرمّزات الضغط المُكمّاة
مرمّز الضغط
Quantized تجريبي. فعِّله باستخدام SET allow_experimental_codecs = 1.
إذا واجهت أي مشكلات، يُرجى فتح بلاغ في مستودع ClickHouse.مقدمة
- الحجم. يُصبح وقت بناء الرسم البياني والذاكرة اللازمة لتخزينه — إضافةً إلى المتجهات ذاتها — التكلفة الأكثر هيمنةً.
- التصفية. في ظل فلتر
WHEREانتقائي، يفقد اجتياز الرسم البياني فاعليته؛ إذ إما أنه لا يستطيع الوصول إلى المجموعة الصغيرة من الصفوف التي تستوفي الشرط، أو يضطر إلى فحص عدد غير متناسب من المرشحين للعثور عليها.
Float32 الكاملة، لأن عمود المتجهات بأكمله يجب قراءته من القرص (أو وحدة التخزين الكائنية) — وبالنسبة لعمود التضمين الكثيف، فهو أكبر عمود في الجدول، ويُضغط بصورة رديئة.
يُعالج مرمّز العمود Quantized هذا العيب.
يُخزَّن كل متجه مرتين: القيم الأصلية كاملة الدقة دون تغيير، إلى جانب شيفرة مُكثَّفة في مجرى مرافق.
يبدأ استعلام البحث عن المتجهات بفحص الشيفرات باستخدام دالة مسافة خفيفة الحساب وصديقة لـ SIMD لتجميع قائمة مختصرة بأكثر المرشحين واعداً، ثم يُعيد ترتيب تلك القائمة مقارنةً بالمتجهات كاملة الدقة.
ولأن الشيفرة لا تُمثّل سوى جزء صغير من حجم المتجه الخام، يقرأ فحص القائمة المختصرة بايتات أقل بكثير من التخزين — ولا يلمس عمود الدقة الكاملة إلا لعدد محدود من المرشحين المُدرجين في القائمة — في حين يظل الترتيب النهائي دقيقاً.
تعريف المرمّز
Quantized(...) بعمود من النوع Array(Float32) (أو Array(Float64) / Array(BFloat16)).
هذا المرمّز تجريبي، لذا فعِّل allow_experimental_codecs أولًا:
ALTER TABLE.
طرق التكميم
dimensions هو طول المتجه.
Quantized('rabitq', dimensions)— بت إشارة واحد لكل إحداثي، بالإضافة إلى معامل تصحيح غير متحيز لجيب التمام (dimensions/8 + 4بايت). خيار افتراضي صغير وقوي ومنخفض الكلفة حسابيًا معpopcount. لـcosineDistanceفقط.Quantized('turboquant', dimensions)— بتّان لكل إحداثي (شيفرة MSE من 1 بت وشيفرة متبقية من 1 بت) للحصول على مرشحين أعلى دقة (dimensions/4 + 4بايت). لـcosineDistanceفقط.Quantized('int8', dimensions)— شيفرةInt8واحدة لكل إحداثي، بالإضافة إلى معيار المتجه (dimensions + 4بايت)؛ وهو أكبر ترميز مسطّح لكنه الأوفى تمثيلًا. يدعمL2DistanceوcosineDistance.Quantized('prefix', dimensions, leading_dimensions, 'int8'|'bf16')— Matryoshka: يحتفظ فقط بأولleading_dimensionsمن الإحداثيات، بصيغةInt8(مع مقياس لكل متجه) أوBFloat16. ترميزات صغيرة جدًا للتضمينات المدرَّبة باستخدام Matryoshka Representation Learning. يدعمL2DistanceوcosineDistance.Quantized('product', dimensions, nbits, m)— Product Quantization: جدول ترميز لكل جزء مُدرَّب باستخدام k-means؛ ويتحول كل متجه إلىmترميزات منnbitsبتات (لذا يجب أن تكونdimensionsمن مضاعفاتm). وهو الخيار الأكثر اكتنازًا والأعلى استرجاعًا لكل بايت، لكن على حساب خطوة تدريب أثناء الإدراج. يدعمL2DistanceوcosineDistance.
rabitq و turboquant أن تكون dimensions من مضاعفات 8.
البحث بشفافية
k نفسه الذي ستستخدمه في البحث الدقيق:
vector_search_use_quantized_codes = 1، يعيد المُحسِّن كتابة الاستعلام تلقائيًا إلى خطة من مرحلتين: إذ يفحص الشيفرات المكمّاة لتجميع قائمة مختصرة، ثم يعيد تقييم هذه القائمة المختصرة بمقارنتها مع vec كاملة الدقة.
يكون هذا الإعداد غير مفعّل افتراضيًا، لذا من دونه يُنفَّذ الاستعلام نفسه بوصفه فحصًا دقيقًا مباشرًا — ولا يغيّر codec النتائج مطلقًا، بل يوفّر فقط مسارًا أسرع عندما تختار تفعيله.
استخدم دالة مسافة تدعمها الطريقة المختارة: cosineDistance لجميع الطرق، وL2Distance أيضًا مع int8 وprefix وproduct.
الإعدادات
allow_experimental_codecs— يجب تمكينه لتعريف مرمّزQuantized(القيمة الافتراضية:0).vector_search_use_quantized_codes— يفعّل إعادة الكتابة على مرحلتين: إعداد قائمة مختصرة ثم إعادة التقييم (القيمة الافتراضية:0). عند تعطيله، تمسح الاستعلامات المطابقة المتجهات كاملة الدقة بدقة تامة.vector_search_index_fetch_multiplier— يحدد عدد المرشحين في القائمة المختصرة نسبةً إلىLIMITالخاص بالاستعلام: يحتفظ الفحص بأفضلLIMIT × multiplierمن الرموز قبل إعادة التقييم. تحسّن القيم الأكبر الاسترجاع على حساب زيادة إعادة التقييم. القيمة الافتراضية هي1(من دون أخذ عينات إضافية)، لذا يلزم عادةً رفعها — على سبيل المثال إلى10أو أكثر — للحصول على استرجاع جيد.
مصمم للتوسع
- متجهّي. كُتبت نوى الفحص للعمل مع SIMD، مع توجيه وقت التشغيل إلى أوسع التعليمات التي يدعمها
CPU: استخدامpopcountالعتادي لطرائق sign-code (rabitq,turboquant)، واستخدام fused-multiply-add العريض للطرائق الأخرى. - بالتوازي عبر الأنوية والأجزاء. الفحص المسطح قابل للتوازي بسهولة، وClickHouse يتعامل معه على هذا النحو: إذ تُحسب المسافات عبر جميع الخيوط المتاحة وعبر جميع أجزاء الجدول في آنٍ واحد، ولا يُنفَّذ تسلسليًا إلا دمج top-
kالنهائي. - موزّع. في cluster مُجزّأ إلى shards، يتوزع العمل على الأجهزة — إذ يفحص كل shard الجزء الخاص به بالتوازي، ثم يدمج coordinator القوائم المختصرة.
- عمودي وملائم للتصفية. تشغل الرموز المُكمّاة عمودًا خاصًا بها، وتُضغط وتُقرأ عبر مسار
I/Oنفسه المستخدم لكل عمود آخر، لذا فإنWHEREالانتقائي يترك عددًا أقل من الرموز لفحصه. - من دون خطوة بناء منفصلة. تُنتَج الرموز أثناء كتابة المتجهات وتُدمَج بالوصل — فلا يوجد فهرس يلزم إنشاؤه أو ضبطه أو إعادة بنائه، لذا يصبح الجدول جاهزًا للبحث بمجرد وصول بياناته.
البت المُكمَّم (QBit)
Array(BFloat16) بدلًا من Array(Float32)، فسينخفض حجم البيانات إلى النصف، ومن المتوقع أن تنخفض أزمنة تنفيذ الاستعلامات بنسبة مماثلة.
وتُعرف هذه الطريقة باسم التكميم. وعلى الرغم من أنها تُسرّع العمليات الحسابية، فقد تقلل من دقة النتائج رغم إجراء فحص شامل لجميع المتجهات.
في التكميم التقليدي، نفقد الدقة أثناء البحث وعند تخزين البيانات أيضًا. ففي المثال أعلاه، سنخزّن BFloat16 بدلًا من Float32، ما يعني أنه لن يعود بالإمكان إجراء بحث أكثر دقة لاحقًا، حتى لو أردنا ذلك. وأحد البدائل هو تخزين نسختين من البيانات: نسخة مكمّمة ونسخة كاملة الدقة. ورغم أن هذا الأسلوب ينجح، فإنه يتطلب تخزينًا زائدًا. تخيّل سيناريو تكون فيه Float64 هي البيانات الأصلية ونريد تشغيل عمليات بحث بدقات مختلفة (16-بت، أو 32-بت، أو 64-بت كاملة). عندها سنحتاج إلى تخزين ثلاث نسخ منفصلة من البيانات.
يوفّر ClickHouse نوع البيانات Quantized Bit (QBit) لمعالجة هذه القيود من خلال:
- تخزين البيانات الأصلية كاملة الدقة.
- إتاحة تحديد دقة التكميم وقت الاستعلام.
QBit، استخدم البنية التالية:
element_type– نوع كل عنصر في المتجه. والأنواع المدعومة هيInt8وBFloat16وFloat32وFloat64dimension– عدد العناصر في كل متجهstride– اختياري. وهو قاسم لـdimensionيُقسِّم الأبعاد إلىdimension / strideمجموعات متجاورة تُخزَّن في تدفقات منفصلة، بحيث تؤدي عملية البحث التي تقتصر على الأبعاد الأولى فقط إلى قراءة عدد أقل من التدفقات (وهو ما يفيد مع تضمينات Matryoshka). والقيمة الافتراضية هيdimension، وفي هذه الحالة يكون النوع مطابقًا على مستوى البايت لنوعQBitغير المستخدِم لـstride. راجع صفحة نوع البياناتQBitللتفاصيل.
إنشاء جدول QBit وإدراج البيانات فيه
البحث المتجهي باستخدام QBit
QBit هنا.
بحث بالدقة الكاملة (64 بت):
اعتبارات الأداء
QBit من تقليل عمليات الإدخال/الإخراج، إذ يلزم قراءة كمية أقل من البيانات من التخزين عند استخدام دقة أقل. بالإضافة إلى ذلك، عندما يحتوي QBit على بيانات Float32، وإذا كانت معلَمة precision تساوي 16 أو أقل، فستكون هناك فوائد إضافية ناتجة عن تقليل العمليات الحسابية. وتتحكم معلَمة precision مباشرةً في المفاضلة بين الدقة والسرعة:
- دقة أعلى (أقرب إلى عرض البيانات الأصلي): نتائج أكثر دقة، واستعلامات أبطأ
- دقة أقل: استعلامات أسرع مع نتائج تقريبية، وانخفاض في استخدام الذاكرة