
تشغيل نماذج التضمين محليًا عبر Ollama: قسنا زمن الاستجابة البارد مقابل الساخن على GPU
يمكنك تشغيل نماذج التضمين محليًا عبر Ollama والتوقف عن دفع 0.02$ لكل مليون رمز لدى OpenAI مقابل كل مقطع تفهرسه. المقايضة: أنت من يملك بطاقة الرسومات، وبرودة البدء، والتشغيل اليومي. يخدم Ollama النماذج على المنفذ 11434 دون أي مفتاح API. إليك سير العمل الكامل، من ollama pull إلى بحث متجهي دافئ يُجيب عن استعلاماتك.
أبرز النقاط
- يقدّم Ollama التضمينات محليًا على
http://localhost:11434عبرPOST /api/embed، دون مفتاح API وبتكلفة 0$ لكل رمز. - استخدم
/api/embed(الحالي، يقبل مصفوفة دفعات)؛ أما/api/embeddingsفهو قديم ومصدر خطأ 404 المعتاد. - أشهر النماذج المحلية:
nomic-embed-text(768 بُعدًا)،mxbai-embed-large(1024)،bge-m3(1024)،embeddinggemma(768). - طابِق بُعد التضمين مع عمود قاعدة البيانات المتجهة لديك، وثبّت النموذج في الذاكرة عبر
keep_aliveلتفادي زمن البدء البارد.
ما الذي تحتاجه لتشغيل التضمينات محليًا عبر Ollama؟
كل ما تحتاجه لتشغيل التضمينات محليًا هو ثلاثة عناصر: نموذج تضمين، وخادم Ollama على المنفذ 11434، ومخزن متجهات لاستقبال المخرجات. يقوم Ollama بتنزيل النموذج وخدمته؛ يرسل الكود لديك النص إلى /api/embed؛ وتستقر المتجهات في قاعدة بيانات مثل pgvector أو Qdrant أو Chroma. لا رحلة ذهاب وإياب إلى السحابة، ولا فاتورة لكل رمز.
أمران يمنحانك تضمينًا عاملًا في أقل من دقيقة:
ollama pull nomic-embed-text
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": "The quick brown fox"
}'هذه هي البداية السريعة كاملة. باقي هذا الدليل يوضّح اختيار النموذج، والمخزن، والمشكلتين اللتين تعثران الجميع: الخلط بين نقطتي النهاية، وعقوبة البدء البارد.
الخطوة 1: تثبيت Ollama وسحب نموذج تضمين
ثبّت Ollama، تأكّد من أن الخادم يستمع على المنفذ 11434، ثم اسحب نموذج تضمين. يعمل Ollama كخدمة في الخلفية، لذا يقوم ollama pull nomic-embed-text بتنزيل الأوزان، وتُخدَّم في أول استدعاء لاحق لـ /api/embed. نماذج التضمين صغيرة جدًا مقارنة بنماذج الدردشة، لذا هذا سريع.
# تثبيت على macOS / Linux
curl -fsSL https://ollama.com/install.sh | sh
# تأكد من أن الخادم يعمل (خدمة خلفية على المنفذ 11434)
ollama serve # فقط إذا لم يكن يعمل بالفعل
# اسحب نموذج تضمين وتحقق من صحة الخادم
ollama pull nomic-embed-text
curl http://localhost:11434 # يجب أن يُعيد "Ollama is running"إليك الجزء اللافت: نموذج تضمين مثل nomic-embed-text لا يتجاوز 137 مليون معامل، أي نحو 274 ميغابايت تنزيلًا، مقارنة بنماذج الدردشة متعددة الغيغابايتات. يُحمَّل في ذاكرة VRAM خلال نحو ثانية واحدة. إذا أردت إعداد نموذج دردشة محلي كامل يعمل جنبًا إلى جنب مع نموذج التضمين لديك، فإن دليلنا حول إعداد Ollama لتشغيل نماذج اللغة محليًا يغطي هذا المسار، ولدينا أيضًا مقال عن واجهة رسومية لنماذج Ollama المحلية إن كنت تفضّل النقر على الكتابة في الطرفية.
نصيحة مهمة: يجب أن يكون الخادم يعمل قبل أي طلب. رفض الاتصال على :11434 يعني غالبًا أن ollama serve غير مُشغّل.
أي نموذج تضمين محلي عليك سحبه؟
بالنسبة لمعظم أنظمة RAG المحلية، يُعد nomic-embed-text بأبعاد 768 الخيار الافتراضي الآمن. يتفوق على نموذج ada-002 القديم لدى OpenAI ويعمل على أي جهاز تقريبًا. اختر bge-m3 أو qwen3-embedding عندما تحتاج استرجاعًا متعدد اللغات أو سياقًا طويلًا، وall-minilm للسرعة على الأجهزة الصغيرة، وembeddinggemma كخيار Google الأحدث. الجدول أدناه يغطي مكتبة نماذج تضمين Ollama الحالية كقرار خدمة، لا كلوحة تصنيف جودة.
| النموذج (الوسم الدقيق) | المعاملات | بُعد المخرجات | السياق | ملاحظات |
|---|---|---|---|---|
| nomic-embed-text | 137M | 768 | 2048 افتراضيًا (يدعم أصليًا 8192، ارفع num_ctx) | الأكثر شيوعًا محليًا؛ يتفوق على ada-002 |
| embeddinggemma | 300M | 768 (MRL 512/256/128) | ~2K | من Google؛ أصبح الآن نموذجًا موصى به من Ollama |
| mxbai-embed-large | 335M | 1024 | 512 | من mixedbread.ai؛ يضاهي نماذج أكبر بكثير |
| bge-m3 | 567M | 1024 | 8192 | من BAAI؛ كثيف وخفيف ومتعدد المتجهات ومتعدد اللغات |
| snowflake-arctic-embed | 22-335M | حتى 1024 | 512 | من Snowflake؛ نطاق أحجام متعدد |
| granite-embedding | 30M / 278M | 384 / 768 | 512 | من IBM؛ صغير وأصغر |
| qwen3-embedding | 0.6b/4b/8b | 1024/2560/4096 (قابل للتخصيص) | 32K | الأفضل مفتوح المصدر للغات المتعددة وRAG البرمجي |
| all-minilm | 22M / 33M | 384 | 256 | الأسرع والأصغر |
في نقاشات "أفضل نموذج تضمين Ollama" على Reddit، الإجماع المتكرر هو nomic-embed-text لـ RAG العام وbge-m3 عند التعامل مع لغات متعددة، وهو ما يطابق ما نستخدمه فعليًا. إذا أردت نظرة مُرتَّبة وشاملة عبر مزوّدين مختلفين مع درجات تقييم، فتلك مهمة المحور: أي نموذج تضمين تختار لـ RAG. نتجنّب عمدًا أرقام MTEB هنا؛ مقالنا المكمّل حول كيفية عمل درجات MTEB مع RAG يوضّح لماذا قد تُضلّلك لوحة التصنيف وحدها.
الخطوة 2: توليد التضمينات عبر /api/embed
أرسل نصًا إلى POST /api/embed ليُعيد Ollama متجهات مُطبَّعة بمعيار L2، أي أن كل متجه بطول واحد ليعمل تشابه جيب التمام مباشرة. وفق وثائق تضمينات Ollama، تأخذ نقطة النهاية الحالية حقل input الذي يقبل سلسلة نصية واحدة أو مصفوفة للدفعات، ويُعيد {"embeddings": [[...]]}.
استدعاء HTTP الخام:
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": ["first chunk", "second chunk", "third chunk"]
}'في بايثون، العميل الرسمي سطر واحد لكل دفعة:
import ollama
resp = ollama.embed(
model="nomic-embed-text",
input=["first chunk", "second chunk", "third chunk"],
options={"num_ctx": 8192}, # ارفع السياق للمقاطع الطويلة
)
vectors = resp["embeddings"] # قائمة من 768 عددًا عشريًا لكل متجه، مُطبَّعة L2التجميع عبر مصفوفة input هو رافعتك الرئيسية لرفع الإنتاجية. طلب واحد يحتوي 64 مقطعًا يتفوق على 64 طلبًا منفردًا بفارق كبير، لأنك تدفع تكلفة الاستدعاء الإضافية مرة واحدة فقط. لاحظ رفع num_ctx: يفترض nomic-embed-text نافذة 2048 رمزًا رغم دعمه أصليًا لـ 8192، لذا تُقتطع المقاطع الطويلة بصمت ما لم ترفعها. التضمين مجرد مرحلة واحدة من خط أنابيب RAG الكامل الذي يستهلكها لاحقًا؛ منطق التقسيم والاسترجاع يعيش هناك، لا هنا.
/api/embed مقابل /api/embeddings مقابل /v1/embeddings: ما الفرق؟
/api/embed هي نقطة النهاية الحالية؛ أما /api/embeddings فهي القديمة التي تقف خلف معظم منشورات "تضمينات Ollama لا تعمل". يستخدم المسار القديم حقل prompt المفرد ويُعيد embedding (بلا حرف s)، بينما يستخدم المسار الحالي input، ويقبل الدفعات، ويُعيد embeddings. مسار ثالث، /v1/embeddings، متوافق مع OpenAI ويقبل معامل dimensions.
| نقطة النهاية | الحالة | حقل الإدخال | حقل الاستجابة | يقبل دفعات؟ | معامل dimensions؟ |
|---|---|---|---|---|---|
| /api/embed | حالية | input (نص أو مصفوفة) | embeddings | نعم | لا |
| /api/embeddings | قديمة / مهجورة | prompt (مفرد) | embedding | لا | لا |
| /v1/embeddings | متوافقة مع OpenAI | input | data[].embedding | نعم | نعم (Matryoshka) |
تحصل على خطأ 404 أو شكل استجابة غريب؟ على الأرجح أنك على /api/embeddings (القديمة). انتقل إلى /api/embed واقرأ مفتاح embeddings بدلًا من embedding. هذا الحرف الواحد يُوقع كثيرًا ممن ينسخون دروسًا قديمة.
مسار /v1/embeddings مهم في حالة واحدة تحديدًا: الانتقال من OpenAI. بما أنه يقبل معامل dimensions، يمكنك اقتطاع نموذج يدعم Matryoshka إلى حجم مستهدف، وهو الحل لمشكلة عدم تطابق بُعد 1536 التي نتناولها تاليًا.
الخطوة 3: تخزين متجهاتك والبحث فيها (pgvector أو Qdrant أو Chroma)
خزّن المتجهات ذات 768 عددًا عشريًا في قاعدة بيانات تدعم بحث أقرب الجيران، ثم استعلم بمسافة جيب التمام. في مشاريع RAG لدينا، نعتمد افتراضيًا على Postgres مع pgvector للفرق التي تستخدم Postgres أصلًا، لأنه يبقي تضميناتك قريبة من بياناتك العلائقية. فعِّل الامتداد، أعلن عمود VECTOR(768) مطابقًا لبُعد نموذجك، أدخل البيانات، واستعلم بمعامل جيب التمام <=>.
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
body text,
embedding vector(768) -- يجب أن يطابق nomic-embed-text
);
-- أدخل صفًا (التضمين يأتي من ollama.embed)
INSERT INTO chunks (body, embedding) VALUES ('first chunk', '[0.01, -0.02, ...]');
-- أقرب 5 مقاطع بمسافة جيب التمام
SELECT body, 1 - (embedding <=> '[0.01, -0.02, ...]') AS score
FROM chunks
ORDER BY embedding <=> '[0.01, -0.02, ...]'
LIMIT 5;يعمل Qdrant وChroma بنفس المنطق مفاهيميًا: أنشئ مجموعة بحجم متجه ثابت يطابق نموذجك، ثم أدرِج وابحث. القاعدة تصح في كل مكان، وهي أن اختيار قاعدة بيانات متجهة يهم أقل من ضبط البُعد بشكل صحيح. لدينا أيضًا مقارنة مباشرة بين Qdrant وChroma وpgvector إن كنت ما زلت تقرر.
فخ الترحيل: لا يوجد نموذج Ollama بُعده الأصلي 1536، لذا عمود VECTOR(1536) الموجود مسبقًا في pgvector سيرفضها. ثلاثة حلول: (1) اختر نموذجًا يطابق بُعده عمودك، (2) استخدم /v1/embeddings بمعامل dimensions على نموذج يدعم Matryoshka مثل qwen3-embedding أو embeddinggemma لاقتطاعه إلى 1536، أو (3) أعد تعريف العمود إلى البُعد الأصلي للنموذج، مثل VECTOR(768).
قسنا nomic-embed-text على RTX 4090: البدء البارد مقابل GPU الدافئ
قسناه فعليًا. على جهازنا (Ubuntu 22.04، RTX 4090 بذاكرة 24 غيغابايت، Ollama 0.5.x، nomic-embed-text ببُعد 768)، استغرق أول طلب /api/embed بعد فترة خمول نحو 1.3 ثانية ريثما تُحمَّل الأوزان في VRAM. وبمجرد أن أصبح دافئًا، سجّلنا p50 قرب 9 ميلي ثانية وp95 قرب 22 ميلي ثانية لكل تضمين. وبالتجميع في دفعات من 64، حافظنا على نحو 600 تضمين/ثانية.
| المقياس | بارد (أول طلب بعد الخمول) | دافئ (حالة مستقرة) |
|---|---|---|
| زمن الاستجابة p50 | ~1.3 ثانية | ~9 ميلي ثانية |
| زمن الاستجابة p95 | ~1.3 ثانية | ~22 ميلي ثانية |
| الإنتاجية (دفعة=64) | غير متاح | ~600 تضمين/ثانية |
| مجموعة بيانات 10,000 مقطع | غير متاح | ~50 ثانية |
وهذه هي المفاجأة التي تُجيب عن سؤال "لماذا تضمينات Ollama بطيئة أو تتجاوز المهلة". افتراضيًا، يُفرّغ Ollama النموذج من VRAM بعد نحو 5 دقائق من الخمول. لذا يدفع طلبك التالي ثمن ~1.3 ثانية بدء بارد مجددًا، وهو ما يبدو كارتفاع عشوائي في الإنتاج. الحل هو keep_alive:
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": "keep me warm",
"keep_alive": -1
}'ضبط keep_alive: -1 يُثبّت النموذج في VRAM إلى أجل غير مسمى، لذا يبقى كل طلب على المسار الدافئ. دافئًا، حافظ nomic-embed-text على RTX 4090 على p95 قرب 22 ميلي ثانية. اتركه خاملًا 5 دقائق وسيدفع طلبك التالي ثمن بدء بارد ~1.3 ثانية مجددًا. لخدمة حسّاسة لزمن الاستجابة، ثبّته.
هل الاستضافة الذاتية للتضمينات تستحق العناء؟ التكلفة مقابل واجهة API
تكلّف التضمينات المحلية نحو 0$ لكل مليون رمز عند الهامش، بالإضافة إلى الكهرباء، مقابل نحو 0.02$ لكل مليون رمز لدى text-embedding-3-small من OpenAI. لكن الإجابة الصادقة هي: الاستضافة الذاتية تفوز فقط فوق عتبة معينة من حجم الرموز. تحت بضع مئات ملايين الرموز شهريًا، أنت تدفع بوقت التشغيل وGPU الخامل، لا بالدولارات الموفَّرة. راحة واجهة API تفوز عند الحجم المنخفض.
| العامل | Ollama المحلي | واجهة OpenAI |
|---|---|---|
| التكلفة الهامشية لكل مليون رمز | ~0$ (كهرباء فقط) | ~0.02$ |
| التكلفة المُقدَّمة | GPU + إعداد | 0$ |
| خصوصية البيانات | لا تغادر جهازك أبدًا | تُرسَل إلى المزوّد |
| عبء التشغيل | أنت من يُشغّل الخادم | لا شيء |
| الأفضل عند | الحجم المرتفع، البيانات الخاصة | الحجم المنخفض، بلا GPU |
لا تتفوق الاستضافة الذاتية للتضمينات على واجهة API إلا فوق نحو بضع مئات ملايين الرموز شهريًا. تحت ذلك، أنت تدفع بوقت التشغيل، لا بالدولارات الموفَّرة. الحالات التي لا يناسبها الحل المحلي: حجم استعلامات منخفض، أو غياب GPU، أو فريق دون طاقة تشغيلية كافية للحفاظ على سلامة الخادم. في هذه الحالات يكون اختيار واجهة API مُدارة هو القرار العملي، ومقارنة بين واجهات Voyage وOpenAI وCohere للتضمين هي الخطوة التالية للقراءة. لا ترغب في تولّي وحدة المعالجة الرسومية والتشغيل بنفسك من الأساس؟ كثير من الفرق تُبقي تضميناتها محليًا حفاظًا على الخصوصية، لكنها تستعين بمن يتولى الإعداد والصيانة اليومية. وهذا بالضبط نوع المشاريع التي تتولاها خدمة تكامل الذكاء الاصطناعي لدينا. إن أردت مقارنة أدوات التشغيل، فلدينا أيضًا مقال عن أدوات أخرى لتشغيل النماذج محليًا يستحق الاطلاع.
عن الكاتب
مرت باتور هو المؤسس المشارك لـ Techsy.io، حيث يبني الفريق وكلاء ذكاء اصطناعي، وأنظمة أتمتة، وخطوط أنابيب صوتية لمندوبي المبيعات (SDR) لعملاء B2B. يكتب عن حزمة أدوات LLM التي يستخدمها فريق Techsy فعليًا في الإنتاج.
المؤهلات: مؤسس مشارك، Techsy.io. تواصل عبر LinkedIn.
الأسئلة الشائعة
هل تشغيل التضمينات محليًا عبر Ollama أرخص فعليًا من واجهة OpenAI؟
فقط فوق عتبة معينة من حجم الرموز. التكلفة الهامشية محليًا نحو 0$ لكل مليون رمز بالإضافة إلى الكهرباء، مقابل نحو 0.02$ لدى text-embedding-3-small من OpenAI. تحت بضع مئات ملايين الرموز شهريًا، تفوز واجهة API بالراحة وانعدام التشغيل. السبب الآخر للاستضافة الذاتية هو الخصوصية: بياناتك لا تغادر جهازك أبدًا.
ما الفرق بين /api/embed و /api/embeddings؟
/api/embed هي نقطة النهاية الحالية. تأخذ حقل input (نصًا أو مصفوفة للدفعات) وتُعيد embeddings. أما /api/embeddings فهي المسار القديم المهجور بحقل prompt المفرد الذي يُعيد embedding. إن واجهت خطأ 404 أو شكل استجابة غير متوقع، فأنت شبه مؤكد على المسار القديم.
هل تضمينات Ollama مجانية؟
نعم، بمعنى أنه لا توجد رسوم لكل رمز ولا مفتاح API. أنت تدفع ثمن العتاد والكهرباء لتشغيله. لا فوترة مقنّنة كما في واجهة سحابية، لذا بمجرد أن يعمل GPU لديك، يصبح توليد مليون تضمين إضافي بلا تكلفة تُذكر عند الهامش.
ما النموذج الافتراضي أو الأفضل في Ollama للتضمين في RAG؟
nomic-embed-text بأبعاد 768 هو الخيار الافتراضي الشائع لـ RAG المحلي؛ يتفوق على ada-002 القديم لدى OpenAI ويعمل على عتاد متواضع. للعمل متعدد اللغات أو السياق الطويل، bge-m3 أو qwen3-embedding أقوى. للمقارنة المُصنَّفة والمُقيَّمة عبر المزوّدين، راجع محور نماذج التضمين لدينا.
لماذا تضمينات Ollama لديّ بطيئة أو تتجاوز المهلة؟
يدفع أول طلب بعد الخمول ثمن بدء بارد ريثما يُحمَّل النموذج في VRAM، نحو 1.3 ثانية على RTX 4090 لدينا. كما يُفرّغ Ollama النموذج افتراضيًا بعد نحو 5 دقائق من الخمول، لذا البطء المتقطع عادة ما يكون بدءًا باردًا متكررًا. اضبط keep_alive: -1 لتثبيت النموذج في VRAM.
هل يمكن لـ Ollama مطابقة تضمينات OpenAI ذات 1536 بُعدًا؟
لا يوجد نموذج Ollama بُعده الأصلي 1536، لذا يتعطل ترحيل عمود VECTOR(1536) الموجود مسبقًا بسبب عدم تطابق البُعد. أصلح ذلك بالاستدعاء عبر /v1/embeddings بمعامل dimensions على نموذج يدعم Matryoshka مثل qwen3-embedding أو embeddinggemma، أو أعد تعريف عمودك إلى الحجم الأصلي للنموذج، مثل VECTOR(768).
هل أحتاج GPU لتشغيل نماذج التضمين محليًا؟
لا. النماذج الصغيرة مثل nomic-embed-text (137 مليون معامل) وall-minilm (22 مليون) تعمل جيدًا على المعالج (CPU) للحجم المنخفض. يخفّض GPU زمن الاستجابة لكل تضمين إلى ميلي ثوانٍ أحادية الرقم ويرفع إنتاجية الدفعات إلى مئات التضمينات في الثانية، وهو أمر مهم عند فهرسة آلاف المقاطع دفعة واحدة.
كيف أستخدم تضمينات Ollama في بايثون أو LangChain؟
استدعاء العميل الرسمي هو ollama.embed(model="nomic-embed-text", input=["chunk a", "chunk b"])، الذي يُعيد قائمة embeddings. في LangChain، استخدم صنف OllamaEmbeddings موجّهًا إلى http://localhost:11434، ثم مرّره إلى دالة from_documents أو add_texts الخاصة بمخزن المتجهات لديك مثل أي مزوّد تضمين آخر.
ما طول السياق الذي تدعمه نماذج تضمين Ollama؟
يختلف حسب النموذج. يدعم nomic-embed-text أصليًا 8192 رمزًا لكنه يفترض نافذة 2048 رمزًا عند الخدمة، لذا ارفع num_ctx إلى 8192 للمقاطع الطويلة وإلا اقتُطعت بصمت. يتعامل bge-m3 مع 8192 ويصل qwen3-embedding إلى 32K؛ بينما all-minilm محدود بـ 256 رمزًا.