استرجاع المعلومات2017متوسط9 دقيقة قراءة

البحث عن التشابه بين مليارات المتّجهات باستخدام GPU

Billion-Scale Similarity Search with GPUs

Johnson, J. · Douze, M. · Jégou, H. — IEEE Transactions on Big Data

المشكلة

بحلول 2017 أصبح التعلُّم العميق قادراً على تحويل الصور والنصوص والفيديو إلى متّجهات عالية الأبعاد (من 50 إلى أكثر من 1000 بُعد). البحث عن أقرب المتّجهات في مجموعة بحجم مليار متّجه مسألة جوهرية في تطبيقات كالبحث عن الصور وأنظمة التوصية والاسترجاع، لكنّ المقارنة الشاملة بطيئة إلى حدّ يستحيل معه الاستخدام العملي. حتى الحلول التي كانت تعمل على GPU كانت تصطدم بعنقَي زجاجة: خوارزميات ضعيفة لاختيار أصغر k عنصر، وأنماط وصول مُبدِّدة للذاكرة. لم يكن هناك حلّ يعمل على جهاز واحد ويحقّق سرعة عالية ودقة مقبولة في الوقت نفسه.

الإسهام

قدّم الباحثون مكتبة FAISS المُصمَّمة خصّيصاً لـGPU، وتقوم على ثلاث أفكار رئيسية. الأولى: خوارزمية WarpSelect التي تختار أصغر k عنصر وتعمل بالكامل داخل سجلّات المعالج السريعة، فتصل إلى 55% من الأداء النظري الأقصى. الثانية: تنفيذ مُحسَّن لفهرس IVFADC الذي يضغط كل متّجه إلى بضعة بايتات ويفحص جزءاً صغيراً فقط من قاعدة البيانات. الثالثة: توزيع البيانات ونسخها عبر عدة وحدات GPU للتعامل مع مليارات المتّجهات. النتيجة: سرعة تفوق أفضل الحلول السابقة بـ8.5 مرة، وبناء بيان أقرب الجيران لـ95 مليون صورة في 35 دقيقة، ومعالجة مليار متّجه على 4 وحدات GPU في أقل من 12 ساعة.

الأثر

تحوّل FAISS إلى البنية التحتية شبه المعيارية للبحث المتّجهي في الأبحاث والصناعة معاً. أنظمة التوليد المعزّز بالاسترجاع (RAG)، وأنظمة (DPR)، ومعظم محرّكات التوصية المبنية على التضمينات — كلها تعتمد على FAISS أو على أدوات تفرّعت منه. بفضله انتقل البحث عن التشابه المتّجهي من موضوع بحثي متخصّص إلى مكوّن أساسي في المنظومة الحديثة للذكاء الاصطناعي، بدءاً من استرجاع المعرفة في ChatGPT وصولاً إلى تحليل المحتوى على نطاق واسع في Meta.

تخيّل مكتبة ضخمة فيها مليار كتاب بلا أيّ فهرس. لو أردتَ العثور على الكتب المشابهة لكتاب بين يديك، ستضطرّ للمرور على كل رفّ في المكتبة — حتى لو استعنتَ بفريق كامل، فالأمر سيستغرق وقتاً لا يُحتمل.

FAISS يعمل كنظام فهرسة ذكي من ثلاث مراحل: أولاً، يأخذ وصف كل كتاب ويضغطه في رمز مختصر — وهذا ما يسمّى الجداءي. ثانياً، يوزّع الكتب على غرف مُصنَّفة بحيث تبحث فقط في الغرف ذات الصلة بسؤالك — وهذا هو مبدأ . ثالثاً، يُرسل جيشاً من القرّاء فائقي السرعة — وهي خيوط GPU المتوازية — ليفحصوا كل كتاب في تلك الغرف دفعةً واحدة.

النتيجة: ما كان يحتاج ساعاتٍ من أمين مكتبة واحد أصبح يُنجَز بالمللي ثانية عندما يعمل آلاف القرّاء معاً بالتوازي.

المشكلة: كيف تجد إبرة في كومة من مليار متّجه؟

الفكرة الأساسية في هي تحويل الصور والجمل ومقاطع الفيديو إلى — أي قوائم من الأرقام تلتقط جوهر المحتوى. كلما تشابهت صورتان، اقترب متّجهاهما في هذا الفضاء عالي الأبعاد. وعندما نريد إيجاد أقرب kk جار لمتّجه استعلام xx ضمن قاعدة بيانات من المتّجهات [yi][y_i]، فإنّنا نحلّ المسألة التالية:

L=k-argminixyi2L = k\text{-}\arg\min_{i} \|x - y_i\|^2

هذه العملية مكلفة حتى مع مليون متّجه فقط. أمّا مع مليار، فإنّ الطريقة المباشرة — أي حساب كل المسافات ثم ترتيبها — تصبح مستحيلة عملياً. وحتى على GPU بقدرة حسابية تُقاس بالتيرافلوبات، تبقى عقبتان أساسيتان: الحجم الهائل لعمليات حساب المسافات، وصعوبة اختيار أفضل kk نتيجة بكفاءة.

افتح في المختبر
لاحظ كيف يتزايد زمن البحث الشامل بشكل خطّي مع حجم قاعدة البيانات. عند مليار متّجه، حتى قدرة GPU الحسابية لا تكفي وحدها بدون خوارزميات أذكى.
تستيقظ التجربة عند وصولك…

الفكرة الأولى: ضغط المتّجهات بالتكميم الجداءي

لنحسبها: متّجه من 128 بُعداً يشغل 512 بايت عند تخزينه كأرقام عشرية بدقة 32 بت. مليار متّجه من هذا النوع يحتاج 512 غيغابايت — وهذا أكبر بكثير من ذاكرة أي GPU متوفرة. هنا يأتي دور : نقسّم كل متّجه إلى bb شريحة، ثم نستبدل كل شريحة بفهرس أقرب مركز عنقودي إليها من دفتر رموز صغير تعلّمناه مسبقاً، يضمّ 256 مُدخلاً فقط. بهذه الطريقة، يتقلّص كل متّجه إلى bb بايت فقط (عادةً 8 إلى 64 بايت) — أي ضغط بعامل يتراوح بين 8 و64 مرة.

الفكرة قريبة من ضغط الألوان في صور GIF: بدلاً من تخزين قيم RGB الدقيقة لكل بكسل، نبني لوحة من 256 لوناً ونخزّن فهرس اللون فقط. التكميم الجداءي يفعل الأمر نفسه، لكنه يطبّقه بشكل مستقل على كل شريحة من شرائح المتّجه.

yq(y)=q1(y)+q2(yq1(y))y \approx q(y) = q_1(y) + q_2(y - q_1(y))
التكميم ثنائي المستوى — تقريب IVFADCq₁ يُسقط المتّجه على مركز عنقودي تقريبي (يحدّد القائمة المقلوبة التي ينتمي إليها) · q₂ يرمّز الفرق المتبقي بمُكمِّم جداءي · الاثنان معاً يمثّلان المتّجه الأصلي ببضعة بايتات
افتح في المختبر
اسحب أيّ متّجه لترى كيف يُقسَّم إلى شرائح، وكيف تُسنَد كل شريحة إلى أقرب مركز عنقودي. الرمز المضغوط هو ببساطة تسلسل فهارس هذه المراكز.
تستيقظ التجربة عند وصولك…

الفكرة الثانية: الفهرس المقلوب — ابحث فقط في الأماكن المهمّة

حتى بعد ضغط المتّجهات، يبقى فحصها جميعاً عملاً مُهدِراً. هنا يتدخّل الفهرس المقلوب بإضافة مرحلة توجيه ذكية: نبدأ بتجميع قاعدة البيانات كلها في C1|C_1| عنقود باستخدام خوارزمية k-means. عند وصول استعلام، نحدّد أقرب τ\tau عنقود إليه (وهو ما يُسمّى مُعامل الفحص المتعدد)، ثم نفحص المتّجهات الموجودة في تلك العناقيد فقط. إذا اخترنا C1=n|C_1| = \sqrt{n} وأبقينا τ\tau صغيراً، فسنتخطّى الغالبية العظمى من القاعدة.

تصوَّر نظام بريد يغطي مدينة كاملة. بدلاً من تفقّد كل صندوق بريد في كل حيّ، تحدّد أولاً أيّ τ\tau منطقة بريدية هي الأقرب لوجهة رسالتك، ثم تبحث فقط في صناديق تلك المناطق. كلما زاد عدد المناطق التي تفحصها (τ\tau) تحسّنت نسبة الاسترجاع، لكنك تدفع ثمن ذلك بمزيد من وقت البحث.

افتح في المختبر
عدّل عدد العناقيد المفحوصة (τ) ولاحظ كيف يتضيّق نطاق البحث من قاعدة البيانات الكاملة إلى مجموعة صغيرة فقط.
تستيقظ التجربة عند وصولك…

الفكرة الثالثة: WarpSelect — اختيار أصغر k عنصر داخل سجلّات GPU

قوة GPU تكمن في آلاف الخيوط التي تعمل بالتوازي، لكن مسألة انتقاء أصغر kk مسافة هي بطبيعتها عملية تسلسلية. الأكوام التقليدية لا تعمل جيداً على GPU لأنّ كل عملية إدراج فيها تتّبع مساراً مختلفاً حسب البيانات، فيرتفع ما يُعرف بانحراف الحزمة (warp divergence) ويتباطأ الأداء.

WarpSelect يتعامل مع هذه المشكلة بأسلوب مختلف تماماً: يحتفظ بكامل الحالة الوسيطة داخل ملف السجلّات في GPU — وهي أسرع طبقة ذاكرة على الإطلاق، بعرض نطاق يفوق الذاكرة المشتركة بـ10 إلى 100 مرة. كل حزمة (32 خيطاً) تحافظ على طابور مُرتَّب لأصغر kk قيمة رأتها حتى الآن، ولكل خيط طابور صغير يعمل كمصفاة مبدئية: أي قيمة أكبر من رأس طابور الخيط تُرفض فوراً دون معالجة. فقط حين تمتلئ طوابير الخيوط تُدمج النتائج في طابور الحزمة عبر شبكة ترتيب ثنائية النغم (bitonic sort).

المحصّلة: خوارزمية بتمريرة واحدة تصل إلى 55% من الحدّ الأقصى النظري لعرض نطاق ذاكرة GPU — يعني أنّها تقترب من سرعة مجرد قراءة البيانات دون فعل أي شيء بها.

افتح في المختبر
تابع كيف ترشّح طوابير الخيوط المسافات الواردة وتدمجها دورياً في طابور الحزمة. لاحظ أنّ الغالبية العظمى من القيم تُستبعد قبل أن تصل إلى الطابور الرئيسي.
تستيقظ التجربة عند وصولك…

جداول البحث: حساب المسافات دون الحاجة لفكّ الضغط

من أجمل خصائص التكميم الجداءي أنّك لست بحاجة لفكّ ضغط المتّجهات لحساب المسافات بينها. الآلية كالتالي: لاستعلام xx ومركز عنقودي خشن معلوم q1(y)q_1(y)، نحسب مسبقاً جدول بحث TjT_j بحجم 256 مُدخلاً لكل مُكمِّم فرعي من المُكمِّمات الـbb. كل مُدخل فيه يخزّن مربّع المسافة بين شريحة الاستعلام المقابلة وأحد المراكز العنقودية. بعد ذلك، لحساب المسافة التقريبية لأي متّجه مضغوط، لا نحتاج سوى bb قراءة من الجدول وعمليات جمع — بلا عمليات ضرب وبلا فكّ ضغط.

هذا يحوّل المسألة من n×dn \times d عملية فاصلة عائمة (وهي مكلفة) إلى n×bn \times b قراءة بايت (رخيصة وتستفيد من الذاكرة المؤقتة بكفاءة). على GPU، هذه الجداول تتسع تماماً في الذاكرة المشتركة — وهي ذاكرة سريعة مدمجة في الشريحة يتشاركها كل خيوط الكتلة الواحدة.

xq(y)2=j=1bTj[codej(y)]\|x - q(y)\|^2 = \sum_{j=1}^{b} T_j\bigl[\text{code}_j(y)\bigr]
المسافة عبر جداول البحث — الحيلة المحورية في التكميم الجداءيكل TjT_j جدول محسوب مسبقاً يحوي 256 مسافة بين المتّجهات الفرعية · codej(y)\text{code}_j(y) هو البايت رقم j من المتّجه المضغوط · جمع bb قراءة يُعطي تقريباً لمربّع المسافة

تجميع الأفكار: خط أنابيب IVFADC على GPU

عند معالجة دفعة من الاستعلامات، يمرّ البحث الكامل بثلاث مراحل:

  • التكميم الخشن: لكل استعلام، نحدّد أقرب τ\tau مركز عنقودي عبر بحث شامل دقيق (ضرب مصفوفات باستخدام cuBLAS).

  • فحص القوائم: لكل استعلام، نفحص فقط القوائم المقلوبة الـτ\tau التي اخترناها. لكل متّجه مضغوط في القائمة، نحسب مسافته التقريبية من جداول البحث المخزّنة في الذاكرة المشتركة، ثم نمرّر النتيجة إلى WarpSelect الذي يعمل في السجلّات.

  • إنهاء الاختيار: ندمج النتائج الجزئية من جميع القوائم والمقاطع للوصول إلى أفضل kk نتيجة نهائية لكل استعلام.

تقسيم الاستعلامات إلى شرائح (tqt_q في كل مرة) مع تشغيل عدة تيارات (streams) بالتوازي يُبقي استغلال GPU مرتفعاً مع ضبط استهلاك الذاكرة. الأهم من ذلك أنّ خط الأنابيب بأكمله يتجنّب الرحلات غير الضرورية إلى الذاكرة العامة — وهذه هي النقطة الجوهرية التي تجعل FAISS بهذه السرعة.

افتح في المختبر
تابع المراحل الثلاث لخط أنابيب IVFADC خطوة بخطوة. شاهد كيف ينتقل الاستعلام من التكميم الخشن إلى فحص القوائم ثم إلى الاختيار النهائي.
تستيقظ التجربة عند وصولك…

التوسُّع لأكثر من GPU واحدة: التجزئة والنَّسخ

حين يكون الفهرس أكبر من أن تستوعبه ذاكرة GPU واحدة، يوفّر FAISS استراتيجيتين متكاملتين:

  • النَّسخ: ننسخ الفهرس كاملاً على RR وحدة GPU. كل وحدة تتولّى nq/Rn_q/R استعلاماً مقابل قاعدة البيانات الكاملة، فنحصل على تسارع شبه خطّي مع دفعات الاستعلامات الكبيرة.

  • التجزئة: نقسّم قاعدة البيانات على SS وحدة GPU، كل منها تحتفظ بـ/S\ell/S متّجهاً. كل استعلام يُرسل إلى جميع الأجزاء، ثم تُدمج النتائج الجزئية بجولة إضافية من اختيار أصغر k عنصر. هذا يتيح فهرسة مجموعات بيانات أكبر من ذاكرة أي وحدة منفردة.

الاستراتيجيتان قابلتان للتركيب: SS جزء مع RR نسخة لكل منها تستخدم S×RS \times R وحدة إجمالاً. عملياً، 4 وحدات GPU تكفي لمعالجة مليار متّجه بأبعاد 128 بسهولة.

افتح في المختبر
بدّل بين النَّسخ والتجزئة لترى كيف تُوزَّع الاستعلامات والبيانات على وحدات GPU المختلفة.
تستيقظ التجربة عند وصولك…

نفس الفكرة بالشيفرة البرمجية

فهرس FAISS IVFPQ — البناء والبحثpython

مبسَّط لإظهار الفكرة — ليس التنفيذ الحقيقي.

import numpy as np
import faiss

d = 128           # بُعد المتّجهات
n = 1_000_000     # حجم قاعدة البيانات
nq = 1000         # عدد الاستعلامات
k = 10            # عدد الجيران المطلوبين

# بيانات عشوائية للتوضيح
xb = np.random.random((n, d)).astype('float32')
xq = np.random.random((nq, d)).astype('float32')

# بناء فهرس IVFPQ
nlist = 1024                    # عدد القوائم المقلوبة (العناقيد)
m = 8                           # عدد المُكمِّمات الفرعية (بايت لكل متّجه)
quantizer = faiss.IndexFlatL2(d)  # المُكمِّم الخشن: مسافة L2 دقيقة
index = faiss.IndexIVFPQ(quantizer, d, nlist, m, 8)

index.train(xb)      # تعلُّم دفاتر الرموز عبر k-means
index.add(xb)        # إضافة كل المتّجهات إلى الفهرس

index.nprobe = 10    # فحص أقرب 10 عناقيد (τ)
D, I = index.search(xq, k)  # D = المسافات، I = الفهارس

# D[i] = أقرب k مسافة للاستعلام i
# I[i] = مُعرّفات أقرب k متّجه للاستعلام i

النتائج: كم بلغت السرعة وكم بلغت الدقة

افتح في المختبر
حرّك عدد العناقيد المفحوصة لاستكشاف التوازن بين السرعة والدقة. زيادة العدد تُحسّن الاسترجاع لكنها تزيد زمن البحث.
تستيقظ التجربة عند وصولك…

لماذا غيَّر هذا العمل كلَّ شيء

  1. 2011

    التكميم الجداءي (جيغو وآخرون)

    وضع أسس التكميم الجداءي لبحث الجار الأقرب: تقسيم المتّجهات إلى شرائح وتكميم كل منها بشكل مستقل. حقّق مستويات ضغط غير مسبوقة مع الحفاظ على قدرة حساب المسافات.

  2. 2017

    FAISS (هذه الورقة)

    تنفيذ IVFADC مُحسَّن لـGPU مع خوارزمية WarpSelect. جعل البحث المتّجهي على نطاق المليارات ممكناً عملياً على جهاز واحد. أتاحه Facebook AI Research كمشروع مفتوح المصدر.

  3. 2020

    الاسترجاع الكثيف للنصوص (DPR)

    اعتمد على FAISS كمحرّك بحث متّجهي لاسترجاع الفقرات النصية والإجابة على الأسئلة المفتوحة. أثبت أنّ الاسترجاع الكثيف قادر على التفوّق على البحث التقليدي بالكلمات المفتاحية.

  4. 2020

    التوليد المعزّز بالاسترجاع (لويس وآخرون)

    دمج استرجاع النصوص عبر FAISS مع التوليد اللغوي في بنية واحدة. هذا هو النموذج الذي أسّس لفكرة ربط إجابات النماذج اللغوية بمصادر معرفية حقيقية.

  5. 2023

    عصر قواعد البيانات المتّجهية

    Pinecone وWeaviate وMilvus وغيرها أطلقت قواعد بيانات متّجهية تجارية. معظمها يعتمد داخلياً على خوارزميات مشتقة من FAISS. البحث المتّجهي أصبح بنية تحتية أساسية في منظومة الذكاء الاصطناعي.

يمكن القول إنّ FAISS بالنسبة للبحث المتّجهي كـcuBLAS بالنسبة لضرب المصفوفات: اللبنة المُحسَّنة التي يُبنى فوقها كل نظام أعلى مستوى. الاسترجاع الكثيف للنصوص ما كان ليظهر بدونه، ولا أنظمة الاسترجاع التي تشغّل مساعدات النماذج اللغوية اليوم.

المرجعJohnson, J., Douze, M. & Jégou, H.. Billion-Scale Similarity Search with GPUs. IEEE Transactions on Big Data, 2017.

مصطلحات هذه الورقة