QBit تنظيم تخزين المتجهات لتسريع عمليات البحث التقريبي. فبدلاً من تخزين عناصر كل متجه معًا، يجمع مواقع البت نفسها عبر جميع المتجهات.
يتيح ذلك تخزين المتجهات بالدقة الكاملة مع تمكينك من اختيار مستوى التكميم الدقيق وقت البحث: اقرأ عددًا أقل من البتات لتقليل I/O وتسريع العمليات الحسابية، أو عددًا أكبر من البتات للحصول على دقة أعلى. وهكذا تستفيد من مزايا السرعة الناتجة عن تقليل نقل البيانات والحسابات بفضل التكميم، مع بقاء جميع البيانات الأصلية متاحة عند الحاجة.
للتصريح بعمود من النوع QBit، استخدم الصياغة التالية:
element_type– نوع كل عنصر من عناصر المتجه. الأنواع المسموح بها هيInt8وBFloat16وFloat32وFloat64dimension– بُعد كل متجهstride– اختياري. عدد الأبعاد المخزنة معًا في مجموعة واحدة من التدفقات. عند عدم تحديده، تكون قيمته الافتراضيةdimension(مجموعة واحدة). وعند تحديده، يجب أن تكون قيمةdimensionمن مضاعفاتstride، وعندما تكون قيمةstrideأصغر منdimension، يجب أن تكون أيضًا من مضاعفات 8. تُقسَّم أبعادdimensionإلىdimension / strideمجموعات متجاورة، وتُخزَّن مستويات البت الخاصة بكل مجموعة في تدفقات منفصلة. يتيح ذلك لعملية البحث ضمن أولDبُعدًا (حيث تكونDمن مضاعفاتstride) قراءة تدفقات المجموعات التي تغطي تلك الأبعاد فقط، وهو ما يفيد مع تضمينات Matryoshka.
إنشاء QBit
QBit في تعريف عمود الجدول:
تحويل المصفوفات إلى QBit
QBit عندما يتطابق طول المصفوفة مع بُعد QBit. ولا يلزم أن يتطابق نوع عنصر المصفوفة مع نوع عنصر QBit. ويُحوَّل إليه تلقائيًا أي نوع عناصر رقمي. يتيح لك ذلك نقل عمود تضمينات موجود مباشرةً إلى عمود QBit:
CAST، على سبيل المثال CAST(embedding AS QBit(Float32, 8)).
تحويل QBit إلى مصفوفات
QBit إلى Array يعيد القيم المخزنة. وهذا هو عكس تحويل المصفوفات إلى QBit:
QBit، ثم تُحوَّل عناصرها بعد ذلك إلى نوع عنصر المصفوفة المطلوب. لذلك، يعمل أيضًا cast يغيّر نوع العنصر أيضًا، مثل التحويل من QBit(Float32, N) إلى Array(Float64).
تكون دورة التحويل Array -> QBit -> Array بلا فقدان بالنسبة إلى Int8 وFloat32 وFloat64. أما BFloat16، فهو يطابق تحويلًا مباشرًا إلى BFloat16 — والفقد الوحيد في الدقة هو دقة BFloat16 نفسها.
عندما لا تكون قيمة dimension مضاعفًا للعدد 8، تُحذف عناصر الحشو اللاحقة الموجودة في التمثيل الداخلي، لذلك تكون النتيجة دائمًا مكوّنة من dimension عنصرًا بالضبط.
التحويل بين أنواع QBit
QBit إلى QBit آخر ما دامت قيمة dimension (عدد عناصر المتجه) ثابتة. ويمكن أن يتغير كلٌّ من element_type وstride؛ أما التحويل إلى QBit ذي dimension مختلف فيؤدي إلى حدوث استثناء، لأن ذلك سيغيّر المتجه نفسه.
يؤدي تغيير element_type إلى إعادة تكوين المتجه وتحويل كل عنصر إلى النوع الجديد، تمامًا كما في تحويل Array المقابل: يكون التوسيع (على سبيل المثال من QBit(Float32, N) إلى QBit(Float64, N)) دقيقًا، بينما يؤدي التضييق إلى فقدان بعض الدقة بالطريقة نفسها التي يحدث بها ذلك عند تحويل Array إلى نوع أضيق.
stride فقط (مع الإبقاء على element_type نفسه) يعيد تجميع طبقات البتات المخزّنة من دون المساس بالقيم، لذا تكون العملية دائمًا من دون فقدان:
الأعمدة الفرعية لـ QBit
QBit نمط وصول عبر الأعمدة الفرعية يتيح لك الوصول إلى مستويات بت الفردية في المتجهات المخزّنة. ويمكن الوصول إلى كل موضع بِت باستخدام الصيغة .N، حيث إن N هو موضع البِت:
Int8: 8 أعمدة فرعية لكل مجموعة stride (1-8)BFloat16: 16 عمودًا فرعيًا لكل مجموعة stride (1-16)Float32: 32 عمودًا فرعيًا لكل مجموعة stride (1-32)Float64: 64 عمودًا فرعيًا لكل مجموعة stride (1-64)
vec.N مستوى البِت (N-1) % element_size من مجموعة stride ذات الرقم (N-1) / element_size. على سبيل المثال، مع QBit(BFloat16, 4096, 1024)، تُقسَّم الأبعاد الـ4096 إلى 4 مجموعات، تضم كل منها 1024 بُعدًا، لذا يوجد 64 عمودًا فرعيًا: تمثل vec.1 … vec.16 مستويات البِت لمجموعة stride الأولى (الأبعاد 1–1024)، بينما تنتمي vec.17 … vec.32 إلى المجموعة الثانية (الأبعاد 1025–2048)، وهكذا.
stride
QBit كل مستوى بت في single stream واحد يمتد عبر جميع أبعاد dimension، لذا فإن عملية البحث تقرأ دائمًا مستويات البت كاملةً عبر المتجه بأكمله. يقسّم المعلَّم الاختياري stride أبعاد dimension إلى dimension / stride مجموعات متجاورة، ويخزّن مستويات البت لكل مجموعة في تدفقات منفصلة. يتيح ذلك لعملية بحث تقتصر على أول D بُعد فقط (بحيث تكون D من مضاعفات stride) أن تقرأ فقط تدفقات المجموعات التي تغطي تلك الأبعاد — وهذا مفيد مع تضمينات Matryoshka، حيث تشكّل الأبعاد الأولى تضمينًا منخفض الأبعاد صالحًا للاستخدام.
BFloat16 (16 مستوى بت)، تمثل vec.1 … vec.16 مستويات البت الستة عشر لأول مجموعة stride (الأبعاد 1–1024)، بينما تنتمي vec.17 … vec.32 إلى المجموعة الثانية (الأبعاد 1025–2048)، وهكذا. وبوجه عام، يقرأ vec.N مستوى البت (N-1) % element_size من مجموعة stride رقم (N-1) / element_size.
لتشغيل بحث منخفض الأبعاد، مرِّر عدد الأبعاد المطلوب قراءتها بوصفه الوسيط الرابع لدوال المسافة المنقولة (انظر أدناه). ويجب أن يحتوي المتجه المرجعي على هذا العدد من العناصر على الأقل (ويُتجاهل أي عناصر إضافية لاحقة)، كما يجب أن تكون القيمة من مضاعفات stride.
الوصول إلى العناصر والتقطيع
arrayElement (وكذلك العامل vec[n]) العنصر رقم n من المتجه، بعد إعادة بنائه بالدقة الكاملة لنوع العنصر. لا تُقرأ إلا مستويات البت الخاصة بمجموعة stride التي تضم العنصر. تعيد arraySlice قيمة QBit بالأبعاد المحددة (أي إسقاطًا على مجموعة فرعية من الأبعاد)؛ ويجب أن تكون الإزاحة والطول ثابتين، لأن بُعد QBit جزء من نوعه. يحتفظ التقطيع المحاذي لحدود مجموعة stride بقيمة stride ويعيد استخدام تدفقات مستويات البت المخزنة دون نسخ:
دوال البحث المتجهي
QBit:
بالنسبة إلى QBit ذي stride، تقبل هذه الدوال وسيطًا رابعًا اختياريًا هو used_dims — أي عدد الأبعاد الأولى المطلوب قراءتها — بحيث لا تُقرأ إلا مجموعات stride التي تغطي هذه الأبعاد. ويجب أن يحتوي المتجه المرجعي على used_dims عنصرًا على الأقل (ويُتجاهل أي عنصر زائد في النهاية، لذا يمكن إعادة استخدام متجه query كامل الحجم في بحث reduced-dimension دون الحاجة إلى اقتطاعه أولًا)، كما يجب أن تكون قيمة used_dims مضاعفًا لـ stride.