استرجاع المعلومات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 المتوازية — ليفحصوا كل كتاب في تلك الغرف دفعةً واحدة.
النتيجة: ما كان يحتاج ساعاتٍ من أمين مكتبة واحد أصبح يُنجَز بالمللي ثانية عندما يعمل آلاف القرّاء معاً بالتوازي.
المشكلة: كيف تجد إبرة في كومة من مليار متّجه؟
الفكرة الأساسية في هي تحويل الصور والجمل ومقاطع الفيديو إلى — أي قوائم من الأرقام تلتقط جوهر المحتوى. كلما تشابهت صورتان، اقترب متّجهاهما في هذا الفضاء عالي الأبعاد. وعندما نريد إيجاد أقرب جار لمتّجه استعلام ضمن قاعدة بيانات من المتّجهات ، فإنّنا نحلّ المسألة التالية:
هذه العملية مكلفة حتى مع مليون متّجه فقط. أمّا مع مليار، فإنّ الطريقة المباشرة — أي حساب كل المسافات ثم ترتيبها — تصبح مستحيلة عملياً. وحتى على GPU بقدرة حسابية تُقاس بالتيرافلوبات، تبقى عقبتان أساسيتان: الحجم الهائل لعمليات حساب المسافات، وصعوبة اختيار أفضل نتيجة بكفاءة.
الفكرة الأولى: ضغط المتّجهات بالتكميم الجداءي
لنحسبها: متّجه من 128 بُعداً يشغل 512 بايت عند تخزينه كأرقام عشرية بدقة 32 بت. مليار متّجه من هذا النوع يحتاج 512 غيغابايت — وهذا أكبر بكثير من ذاكرة أي GPU متوفرة. هنا يأتي دور : نقسّم كل متّجه إلى شريحة، ثم نستبدل كل شريحة بفهرس أقرب مركز عنقودي إليها من دفتر رموز صغير تعلّمناه مسبقاً، يضمّ 256 مُدخلاً فقط. بهذه الطريقة، يتقلّص كل متّجه إلى بايت فقط (عادةً 8 إلى 64 بايت) — أي ضغط بعامل يتراوح بين 8 و64 مرة.
الفكرة قريبة من ضغط الألوان في صور GIF: بدلاً من تخزين قيم RGB الدقيقة لكل بكسل، نبني لوحة من 256 لوناً ونخزّن فهرس اللون فقط. التكميم الجداءي يفعل الأمر نفسه، لكنه يطبّقه بشكل مستقل على كل شريحة من شرائح المتّجه.
الفكرة الثانية: الفهرس المقلوب — ابحث فقط في الأماكن المهمّة
حتى بعد ضغط المتّجهات، يبقى فحصها جميعاً عملاً مُهدِراً. هنا يتدخّل الفهرس المقلوب بإضافة مرحلة توجيه ذكية: نبدأ بتجميع قاعدة البيانات كلها في عنقود باستخدام خوارزمية k-means. عند وصول استعلام، نحدّد أقرب عنقود إليه (وهو ما يُسمّى مُعامل الفحص المتعدد)، ثم نفحص المتّجهات الموجودة في تلك العناقيد فقط. إذا اخترنا وأبقينا صغيراً، فسنتخطّى الغالبية العظمى من القاعدة.
تصوَّر نظام بريد يغطي مدينة كاملة. بدلاً من تفقّد كل صندوق بريد في كل حيّ، تحدّد أولاً أيّ منطقة بريدية هي الأقرب لوجهة رسالتك، ثم تبحث فقط في صناديق تلك المناطق. كلما زاد عدد المناطق التي تفحصها () تحسّنت نسبة الاسترجاع، لكنك تدفع ثمن ذلك بمزيد من وقت البحث.
الفكرة الثالثة: WarpSelect — اختيار أصغر k عنصر داخل سجلّات GPU
قوة GPU تكمن في آلاف الخيوط التي تعمل بالتوازي، لكن مسألة انتقاء أصغر مسافة هي بطبيعتها عملية تسلسلية. الأكوام التقليدية لا تعمل جيداً على GPU لأنّ كل عملية إدراج فيها تتّبع مساراً مختلفاً حسب البيانات، فيرتفع ما يُعرف بانحراف الحزمة (warp divergence) ويتباطأ الأداء.
WarpSelect يتعامل مع هذه المشكلة بأسلوب مختلف تماماً: يحتفظ بكامل الحالة الوسيطة داخل ملف السجلّات في GPU — وهي أسرع طبقة ذاكرة على الإطلاق، بعرض نطاق يفوق الذاكرة المشتركة بـ10 إلى 100 مرة. كل حزمة (32 خيطاً) تحافظ على طابور مُرتَّب لأصغر قيمة رأتها حتى الآن، ولكل خيط طابور صغير يعمل كمصفاة مبدئية: أي قيمة أكبر من رأس طابور الخيط تُرفض فوراً دون معالجة. فقط حين تمتلئ طوابير الخيوط تُدمج النتائج في طابور الحزمة عبر شبكة ترتيب ثنائية النغم (bitonic sort).
المحصّلة: خوارزمية بتمريرة واحدة تصل إلى 55% من الحدّ الأقصى النظري لعرض نطاق ذاكرة GPU — يعني أنّها تقترب من سرعة مجرد قراءة البيانات دون فعل أي شيء بها.
جداول البحث: حساب المسافات دون الحاجة لفكّ الضغط
من أجمل خصائص التكميم الجداءي أنّك لست بحاجة لفكّ ضغط المتّجهات لحساب المسافات بينها. الآلية كالتالي: لاستعلام ومركز عنقودي خشن معلوم ، نحسب مسبقاً جدول بحث بحجم 256 مُدخلاً لكل مُكمِّم فرعي من المُكمِّمات الـ. كل مُدخل فيه يخزّن مربّع المسافة بين شريحة الاستعلام المقابلة وأحد المراكز العنقودية. بعد ذلك، لحساب المسافة التقريبية لأي متّجه مضغوط، لا نحتاج سوى قراءة من الجدول وعمليات جمع — بلا عمليات ضرب وبلا فكّ ضغط.
هذا يحوّل المسألة من عملية فاصلة عائمة (وهي مكلفة) إلى قراءة بايت (رخيصة وتستفيد من الذاكرة المؤقتة بكفاءة). على GPU، هذه الجداول تتسع تماماً في الذاكرة المشتركة — وهي ذاكرة سريعة مدمجة في الشريحة يتشاركها كل خيوط الكتلة الواحدة.
تجميع الأفكار: خط أنابيب IVFADC على GPU
عند معالجة دفعة من الاستعلامات، يمرّ البحث الكامل بثلاث مراحل:
-
التكميم الخشن: لكل استعلام، نحدّد أقرب مركز عنقودي عبر بحث شامل دقيق (ضرب مصفوفات باستخدام cuBLAS).
-
فحص القوائم: لكل استعلام، نفحص فقط القوائم المقلوبة الـ التي اخترناها. لكل متّجه مضغوط في القائمة، نحسب مسافته التقريبية من جداول البحث المخزّنة في الذاكرة المشتركة، ثم نمرّر النتيجة إلى WarpSelect الذي يعمل في السجلّات.
-
إنهاء الاختيار: ندمج النتائج الجزئية من جميع القوائم والمقاطع للوصول إلى أفضل نتيجة نهائية لكل استعلام.
تقسيم الاستعلامات إلى شرائح ( في كل مرة) مع تشغيل عدة تيارات (streams) بالتوازي يُبقي استغلال GPU مرتفعاً مع ضبط استهلاك الذاكرة. الأهم من ذلك أنّ خط الأنابيب بأكمله يتجنّب الرحلات غير الضرورية إلى الذاكرة العامة — وهذه هي النقطة الجوهرية التي تجعل FAISS بهذه السرعة.
التوسُّع لأكثر من GPU واحدة: التجزئة والنَّسخ
حين يكون الفهرس أكبر من أن تستوعبه ذاكرة GPU واحدة، يوفّر FAISS استراتيجيتين متكاملتين:
-
النَّسخ: ننسخ الفهرس كاملاً على وحدة GPU. كل وحدة تتولّى استعلاماً مقابل قاعدة البيانات الكاملة، فنحصل على تسارع شبه خطّي مع دفعات الاستعلامات الكبيرة.
-
التجزئة: نقسّم قاعدة البيانات على وحدة GPU، كل منها تحتفظ بـ متّجهاً. كل استعلام يُرسل إلى جميع الأجزاء، ثم تُدمج النتائج الجزئية بجولة إضافية من اختيار أصغر k عنصر. هذا يتيح فهرسة مجموعات بيانات أكبر من ذاكرة أي وحدة منفردة.
الاستراتيجيتان قابلتان للتركيب: جزء مع نسخة لكل منها تستخدم وحدة إجمالاً. عملياً، 4 وحدات GPU تكفي لمعالجة مليار متّجه بأبعاد 128 بسهولة.
نفس الفكرة بالشيفرة البرمجية
مبسَّط لإظهار الفكرة — ليس التنفيذ الحقيقي.
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النتائج: كم بلغت السرعة وكم بلغت الدقة
لماذا غيَّر هذا العمل كلَّ شيء
2011
التكميم الجداءي (جيغو وآخرون)
وضع أسس التكميم الجداءي لبحث الجار الأقرب: تقسيم المتّجهات إلى شرائح وتكميم كل منها بشكل مستقل. حقّق مستويات ضغط غير مسبوقة مع الحفاظ على قدرة حساب المسافات.
2017
FAISS (هذه الورقة)
تنفيذ IVFADC مُحسَّن لـGPU مع خوارزمية WarpSelect. جعل البحث المتّجهي على نطاق المليارات ممكناً عملياً على جهاز واحد. أتاحه Facebook AI Research كمشروع مفتوح المصدر.
2020
الاسترجاع الكثيف للنصوص (DPR)
اعتمد على FAISS كمحرّك بحث متّجهي لاسترجاع الفقرات النصية والإجابة على الأسئلة المفتوحة. أثبت أنّ الاسترجاع الكثيف قادر على التفوّق على البحث التقليدي بالكلمات المفتاحية.
2020
التوليد المعزّز بالاسترجاع (لويس وآخرون)
دمج استرجاع النصوص عبر FAISS مع التوليد اللغوي في بنية واحدة. هذا هو النموذج الذي أسّس لفكرة ربط إجابات النماذج اللغوية بمصادر معرفية حقيقية.
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.
مصطلحات هذه الورقة
- الجار الأقرب التقريبيApproximate Nearest Neighbor
- التكميم الجداءيProduct Quantization
- الملف المقلوبInverted File
- التكميم المتجهيVector Quantization
- اختيار أصغر k عناصرk-Selection
- وحدة معالجة الرسومياتGPU
- فايس (FAISS)FAISS
- تشابه جيب التمامCosine Similarity
- درجة التشابهSimilarity
- التضمينEmbedding
- العنقَدة بـ k-متوسطاتk-means Clustering
- خوارزمية الجيران الأقرب (KNN)k-Nearest Neighbors
- معدل التدفق والإنتاجيةThroughput
- زمن الاستجابة (التأخير البيني)Latency