Ollama: ضبط الأداء
ضبط الأداء هو تغيير السرعات لمحرك الذكاء الاصطناعي المحلي الخاص بك — من الزحف بالسرعة الأولى إلى الطيران بالسرعة الخامسة.
💡 نصيحة:
OLLAMA_NUM_PARALLEL يتحكم في عدد الطلبات المتوازية لكل نموذج — تعيينه إلى 4 يسمح بمعالجة 4 طلبات في وقت واحد، مما يزيد الإنتاجية. لـ 8 جيجابايت VRAM، ابدأ الاختبار من 2-4؛ لـ 16 جيجابايت، جرب 4-8. اجمع مع OLLAMA_KEEP_ALIVE=30m لتجنب التفريغ وإعادة التحميل المتكررين للنموذج.
📋 المتطلبات المسبقة: يجب أن تتقن أولاً ما يلي
- الدرس 9: إعدادات GPU وCUDA
- الدرس 15: النشر الحاوي باستخدام دوكر
1. ما ستتعلمه
- مقاييس الأداء الرئيسية: رموز/ثانية، TTFT، استخدام الذاكرة
- ضبط الطلبات المتزامنة: OLLAMA_NUM_PARALLEL وOLLAMA_MAX_LOADED_MODELS
- إدارة نافذة السياق: num_ctx وKV Cache
- معالجة الطلبات الدفعية وجدولة الصفوف
- أدوات القياس وتأسيس خط أساس الأداء
2. قصة حقيقية من رائدة أعمال SaaS
💡 نصيحة:
OLLAMA_KEEP_ALIVE يحدد المدة التي يبقى فيها النموذج في الذاكرة. لسيناريوهات التزامن العالي، عيّنه إلى 30m أو أطول لتجنب تأخيرات البدء البارد الناتجة عن التفريغ/إعادة التحميل المتكررين للنموذج (5-30 ثانية). للاستخدام غير المتكرر، عيّن 5m لتحرير VRAM.
ℹ️ معلومة:
num_ctx (حجم نافذة السياق) يؤثر مباشرة على استخدام ذاكرة KV Cache. نموذج 8B مع num_ctx=2048 يحتاج حوالي 4 جيجابايت VRAM، وnum_ctx=8192 يحتاج حوالي 6 جيجابايت، وnum_ctx=32768 يحتاج حوالي 12 جيجابايت. عيّنه بناءً على الاحتياجات الفعلية، لا تزيده عميًا.
(1) المشكلة: انتهاء المهلة تحت التزامن العالي
بدأ SupportBot الخاص بـ Alice، وخلال ساعات الذروة، تسبب 10 طلبات متزامنة في ارتفاع زمن الاستجابة من ثانيتين إلى 30 ثانية. اشتكى العملاء من "الانتظار الطويل"، لكن Ollama يعالج طلبًا متزامنًا واحدًا فقط افتراضيًا.
(2) الحل: تحسين التزامن والسياق
ضبط OLLAMA_NUM_PARALLEL وOLLAMA_MAX_LOADED_MODELS، تعافى زمن الاستجابة إلى أقل من 5 ثوانٍ:
BASH
# Enable 4 parallel requests
export OLLAMA_NUM_PARALLEL=4
export OLLAMA_MAX_LOADED_MODELS=2
3. مقاييس الأداء الرئيسية
⚠️ تحذير: زيادة
OLLAMA_NUM_PARALLEL لا تزيد الإنتاجية خطيًا — كل طلب متزامن يشارك GPU VRAM، والكثير من الطلبات المتزامنة قد يسبب OOM أو انخفاضًا حادًا في سرعة كل طلب. ابدأ من 2 واختبر تدريجيًا، مع مراقبة استخدام VRAM وتغيرات زمن الانتقال.
(1) ثلاثة مقاييس أساسية
| المقياس | الاسم الكامل | المعنى | القيمة المستهدفة |
|---|---|---|---|
| رموز/ثانية | رموز في الثانية | سرعة التوليد | GPU: 30+، وحدة المعالجة المركزية: 5+ |
| TTFT | الوقت حتى أول رمز | زمن انتقال الرمز الأول | < 500 مللي ثانية (GPU) |
| الذاكرة | استخدام VRAM/RAM | استهلاك الموارد | < 90% من السعة |
(2) إحصائيات أداء Ollama
بيانات الأداء المضمنة في وضع --verbose أو الاستجابات غير المتدفقة:
| الحقل | المعنى |
|---|---|
| total_duration | الوقت الإجمالي (نانوثانية) |
| load_duration | وقت تحميل النموذج |
| prompt_eval_count | عدد رموز الإدخال |
| prompt_eval_duration | وقت معالجة الإدخال |
| eval_count | عدد رموز الإخراج |
| eval_duration | وقت توليد الإخراج |
(1) ▶ مثال: جمع مقاييس الأداء
BASH
# Collect performance metrics with verbose output
ollama run --verbose qwen2.5 "Explain AI in 50 words"
# Key output:
# total duration: 2500000000 # 2.5s total
# load duration: 500000000 # 0.5s model load
# prompt eval count: 15 token(s)
# prompt eval duration: 200000000 # 0.2s input processing
# prompt eval count: 15 token(s) # = prompt_eval_speed: 75 tok/s
# eval count: 52 token(s)
# eval duration: 1800000000 # = eval_speed: 28.9 tok/s
الإخراج:
TEXT
I'm a helpful AI assistant running locally on your machine...
PYTHON
import ollama
import time
def measure_performance(model: str, prompt: str) -> dict:
start = time.time()
response = ollama.chat(
model=model,
messages=[{"role": "user", "content": prompt}],
stream=False
)
total_time = time.time() - start
content = response["message"]["content"]
token_count = len(content) // 4 # Rough estimate
return {
"model": model,
"total_time_s": round(total_time, 2),
"estimated_tokens": token_count,
"estimated_tok_per_s": round(token_count / total_time, 1) if total_time > 0 else 0
}
result = measure_performance("qwen2.5", "Explain machine learning in 100 words")
print(result)
4. ضبط الطلبات المتزامنة
⚠️ ملاحظة: التزامن المفرط قد يسبب OOM —
OLLAMA_NUM_PARALLEL لا يزيد الإنتاجية خطيًا؛ كل طلب متزامن يشارك GPU VRAM، والكثير من الطلبات المتزامنة يسبب انخفاضًا حادًا في سرعة كل طلب أو حتى تجاوز الذاكرة. ابدأ من 2 واختبر تدريجيًا، مع مراقبة استخدام VRAM وتغيرات زمن الانتقال.
(1) متغيرات البيئة المتعلقة بالتزامن
| المتغير | الافتراضي | الوصف | التأثير |
|---|---|---|---|
| OLLAMA_NUM_PARALLEL | 1 | الطلبات المتوازية لكل نموذج | ↑الإنتاجية ↓سرعة كل طلب |
| OLLAMA_MAX_LOADED_MODELS | 1 | الحد الأقصى للنماذج المحملة في وقت واحد | ↑تزامن النماذج المتعددة ↑VRAM |
| OLLAMA_KEEP_ALIVE | 5m | مدة بقاء النموذج في الذاكرة | ↑يتجنب إعادة التحميل ↓استعادة الذاكرة |
(2) قرار إعدادات التزامن
flowchart LR
A[طلبات متزامنة؟] --> B{ذروة QPS؟}
B -->|1-2| C[افتراضي: NUM_PARALLEL=1]
B -->|3-5| D[NUM_PARALLEL=4<br/>8 جيجابايت VRAM كحد أدنى]
B -->|6-10| E[NUM_PARALLEL=8<br/>16 جيجابايت+ VRAM]
B -->|10+| F[عقد متعددة<br/>موازن الحمل]
| الإعدادات | متطلب VRAM | الإنتاجية | زمن انتقال كل طلب |
|---|---|---|---|
| NUM_PARALLEL=1 | الأدنى | 1 طلب/ثانية | الأقصر |
| NUM_PARALLEL=4 | متوسط | 3-4 طلب/ثانية | زيادة طفيفة |
| NUM_PARALLEL=8 | مرتفع | 6-8 طلب/ثانية | زيادة ملحوظة |
(2) ▶ مثال: إعدادات التزامن والاختبار
BASH
# Configure parallel processing
sudo systemctl edit ollama
# Add:
[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_MAX_LOADED_MODELS=2"
Environment="OLLAMA_KEEP_ALIVE=30m"
sudo systemctl daemon-reload
sudo systemctl restart ollama
الإخراج:
TEXT
# Ollama command executed successfully
PYTHON
import ollama
import asyncio
import time
async def concurrent_test(num_requests: int):
"""Test concurrent request handling."""
client = ollama.AsyncClient()
start = time.time()
async def single_request(i: int):
req_start = time.time()
response = await client.chat(
model="qwen2.5",
messages=[{"role": "user", "content": f"Say hello {i}"}],
stream=False,
options={"temperature": 0.1}
)
return time.time() - req_start
tasks = [single_request(i) for i in range(num_requests)]
latencies = await asyncio.gather(*tasks)
total = time.time() - start
print(f"Concurrent: {num_requests} requests")
print(f"Total time: {total:.2f}s")
print(f"Avg latency: {sum(latencies)/len(latencies):.2f}s")
print(f"Throughput: {num_requests/total:.1f} req/s")
asyncio.run(concurrent_test(4))
5. إدارة نافذة السياق
(1) تأثير num_ctx على الذاكرة
| num_ctx | KV Cache (8B Q4_M) | إجمالي VRAM المطلوب | حالة الاستخدام |
|---|---|---|---|
| 2048 | ~1 جيجابايت | ~6 جيجابايت | محادثات قصيرة (افتراضي) |
| 4096 | ~2 جيجابايت | ~7 جيجابايت | محادثات متوسطة |
| 8192 | ~4 جيجابايت | ~9 جيجابايت | استرجاع RAG |
| 32768 | ~16 جيجابايت | ~21 جيجابايت | مستندات طويلة |
⚠️ ملاحظة: KV Cache ينمو خطيًا مع num_ctx. نموذج 8B مع num_ctx=32768 لا يمكن تشغيله على 8 جيجابايت VRAM؛ يحتاج 21 جيجابايت+ VRAM.
(2) استراتيجيات تحسين num_ctx
| الاستراتيجية | الطريقة | التأثير |
|---|---|---|
| تعيين حسب الطلب | المحادثة تستخدم 2048، RAG يستخدم 8192 | تقليل هدر الذاكرة |
| نافذة منزلقة | الاحتفاظ بآخر N جولات محادثة فقط | التحكم في طول الإدخال |
| ضغط الملخص | ضغط المحادثات المبكرة دوريًا | توفير مساحة السياق |
| تنقية RAG | تقليل top-k من 5 إلى 3 | تقليل رموز الإدخال |
(3) ▶ مثال: اختبار مقارنة num_ctx
PYTHON
import ollama
import time
def test_context_sizes(model: str, prompt: str, context_sizes: list[int]):
"""Test performance with different context window sizes."""
for ctx in context_sizes:
start = time.time()
response = ollama.chat(
model=model,
messages=[{"role": "user", "content": prompt}],
stream=False,
options={"num_ctx": ctx, "temperature": 0.3}
)
elapsed = time.time() - start
tokens = len(response["message"]["content"]) // 4
speed = tokens / elapsed if elapsed > 0 else 0
print(f"num_ctx={ctx}: {elapsed:.2f}s, ~{speed:.1f} tok/s")
test_context_sizes("qwen2.5", "Explain AI briefly", [2048, 4096, 8192])
الإخراج:
TEXT
# Function defined successfully
6. المعالجة الدفعية والقياس
(1) استراتيجيات المعالجة الدفعية
| الاستراتيجية | الوصف | حالة الاستخدام |
|---|---|---|
| دفعة تسلسلية | معالجة الطلبات واحدًا تلو الآخر | بسيطة وموثوقة |
| دفعة متوازية | تزامن asyncio | إنتاجية عالية |
| جدولة الصفوف | FastAPI + صف | بيئة الإنتاج |
(4) ▶ مثال: نص قياس دفعي
PYTHON
import ollama
import time
import asyncio
import json
from datetime import datetime
class BenchmarkRunner:
def __init__(self, model: str = "qwen2.5"):
self.model = model
self.results = []
def warmup(self, runs: int = 2):
for _ in range(runs):
ollama.chat(model=self.model,
messages=[{"role": "user", "content": "warmup"}],
stream=False)
def single_benchmark(self, prompt: str) -> dict:
start = time.time()
response = ollama.chat(
model=self.model,
messages=[{"role": "user", "content": prompt}],
stream=False,
options={"temperature": 0.3}
)
elapsed = time.time() - start
content = response["message"]["content"]
return {
"prompt_length": len(prompt),
"response_length": len(content),
"estimated_tokens": len(content) // 4,
"total_time_s": round(elapsed, 2),
"tok_per_s": round(len(content) / 4 / elapsed, 1) if elapsed > 0 else 0
}
async def concurrent_benchmark(self, prompt: str, concurrency: int) -> dict:
client = ollama.AsyncClient()
start = time.time()
tasks = [
client.chat(model=self.model,
messages=[{"role": "user", "content": prompt}],
stream=False, options={"temperature": 0.3})
for _ in range(concurrency)
]
await asyncio.gather(*tasks)
total = time.time() - start
return {
"concurrency": concurrency,
"total_time_s": round(total, 2),
"throughput_rps": round(concurrency / total, 1)
}
def run_full_benchmark(self):
self.warmup()
prompts = [
"Hello",
"Explain AI in 3 sentences",
"Write a product description for wireless headphones"
]
print("=== Single Request Benchmark ===")
for p in prompts:
result = self.single_benchmark(p)
print(f" {result['estimated_tokens']}tok, {result['tok_per_s']}tok/s, {result['total_time_s']}s")
print("\n=== Concurrent Benchmark ===")
for c in [1, 2, 4]:
result = asyncio.run(self.concurrent_benchmark("Hello", c))
print(f" Concurrency {c}: {result['throughput_rps']} req/s")
# Run
runner = BenchmarkRunner("qwen2.5")
runner.run_full_benchmark()
الإخراج:
TEXT
=== Single Request Benchmark ===
=== Concurrent Benchmark ===
(5) ▶ مثال: تأسيس خط أساس الأداء
BASH
#!/bin/bash
# Establish performance baseline
MODEL="qwen2.5"
DATE=$(date +%Y%m%d)
REPORT="baseline_${DATE}.txt"
echo "=== Performance Baseline ===" > "$REPORT"
echo "Date: $(date)" >> "$REPORT"
echo "Model: $MODEL" >> "$REPORT"
echo "GPU: $(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || echo 'CPU')" >> "$REPORT"
echo "" >> "$REPORT"
# Single request benchmark
echo "## Single Request ##" >> "$REPORT"
for i in {1..5}; do
start=$(date +%s%N)
curl -s http://localhost:11434/api/chat -d "{
\"model\": \"$MODEL\",
\"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}],
\"stream\": false
}" > /dev/null
end=$(date +%s%N)
elapsed=$(( (end - start) / 1000000 ))
echo " Run $i: ${elapsed}ms" >> "$REPORT"
done
echo "" >> "$REPORT"
echo "## GPU Utilization ##" >> "$REPORT"
nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv >> "$REPORT" 2>/dev/null || echo "CPU mode" >> "$REPORT"
echo "Baseline saved: $REPORT"
الإخراج:
TEXT
{"status":"ok","data":{}}
7. مثال شامل: تقرير ضبط أداء SupportBot
PYTHON
# ============================================
# Comprehensive: Performance tuning report
# Full benchmark + optimization recommendations
# ============================================
import ollama
import time
import asyncio
import json
class PerformanceTuner:
def __init__(self, model: str = "qwen2.5"):
self.model = model
def run_diagnostics(self) -> dict:
"""Run full performance diagnostics."""
# Warm up
ollama.chat(model=self.model,
messages=[{"role": "user", "content": "warmup"}],
stream=False)
# Single request test
start = time.time()
resp = ollama.chat(
model=self.model,
messages=[{"role": "user", "content": "Hello"}],
stream=False, options={"temperature": 0.3}
)
single_latency = time.time() - start
# Context size test
ctx_results = {}
for ctx in [2048, 4096, 8192]:
start = time.time()
ollama.chat(
model=self.model,
messages=[{"role": "user", "content": "Hello"}],
stream=False, options={"num_ctx": ctx, "temperature": 0.3}
)
ctx_results[ctx] = round(time.time() - start, 2)
return {
"model": self.model,
"single_latency_s": round(single_latency, 2),
"context_sizes": ctx_results,
"recommendations": self._generate_recommendations(single_latency)
}
def _generate_recommendations(self, latency: float) -> list[str]:
recs = []
if latency > 5:
recs.append("High latency detected. Enable GPU acceleration or use smaller model.")
if latency > 2:
recs.append("Set OLLAMA_NUM_PARALLEL=4 for concurrent requests.")
recs.append("Use num_ctx=2048 for chat, num_ctx=8192 for RAG.")
recs.append("Set OLLAMA_KEEP_ALIVE=30m to avoid model reloading.")
return recs
def generate_report(self) -> str:
diag = self.run_diagnostics()
report = [
f"# Performance Tuning Report",
f"Model: {diag['model']}",
f"Single request latency: {diag['single_latency_s']}s",
f"\n## Context Window Impact:",
]
for ctx, latency in diag["context_sizes"].items():
report.append(f" num_ctx={ctx}: {latency}s")
report.append("\n## Recommendations:")
for r in diag["recommendations"]:
report.append(f" - {r}")
return "\n".join(report)
tuner = PerformanceTuner("qwen2.5")
print(tuner.generate_report())
❓ أسئلة شائعة
س ماذا يجب تعيين OLLAMA_NUM_PARALLEL؟
ج 2-4 لـ 8 جيجابايت VRAM، 4-8 لـ 16 جيجابايت، 8 لـ 24 جيجابايت. الكثير من الطلبات المتوازية يزيد زمن انتقال كل طلب ويخاطر بـ OOM. ابدأ الاختبار من 4.
س ماذا لو كان TTFT مرتفعًا جدًا؟
ج 1) تأكد أن OLLAMA_KEEP_ALIVE طويل بما يكفي (تجنب البدء البارد)؛ 2) قلل num_ctx؛ 3) استخدم نموذجًا أصغر؛ 4) تأكد أن تسريع GPU يعمل.
س كيف أقلل وقت تحميل النموذج؟
ج عيّن OLLAMA_KEEP_ALIVE=30m أو أطول لإبقاء النموذج مقيمًا في الذاكرة. التحميل الأول لا مفر منه؛ الطلبات اللاحقة تستجيب في ثوانٍ.
س ماذا لو حدث OOM أثناء الطلبات المتزامنة؟
ج قلل OLLAMA_NUM_PARALLEL؛ استخدم num_ctx أصغر؛ قلل OLLAMA_MAX_LOADED_MODELS؛ أو أضف المزيد من VRAM.
س ماذا لو كانت نتائج القياس غير مستقرة؟
ج 1) إحماء 2-3 مرات للقضاء على البدء البارد؛ 2) أغلق برامج GPU الأخرى؛ 3) خذ 5+ مرات ووسطها؛ 4) ثبت البذرة العشوائية.
س كيف أراقب أداء بيئة الإنتاج؟
ج الدرس 21 سيتناول بالتفصيل مكدس مراقبة Prometheus + Grafana. حاليًا، استخدم
--verbose ونصوصًا مخصصة لجمع المقاييس.📖 ملخص
- ثلاثة مقاييس أساسية: رموز/ثانية (السرعة)، TTFT (زمن الانتقال)، الذاكرة (الموارد)
- OLLAMA_NUM_PARALLEL يتحكم في التزامن؛ 4 نقطة بداية لـ 8 جيجابايت VRAM
- num_ctx يؤثر خطيًا على ذاكرة KV Cache؛ عيّن حسب الطلب: 2048 للمحادثة، 8192 لـ RAG
- القياسات تحتاج إحماء + مرات متعددة مُوسطَة؛ أسس خطوط أساس أداء لتتبع التغيرات
- OLLAMA_KEEP_ALIVE يتجنب البدء البارد؛ عيّن 30m للخدمات عالية التردد
- ضبط الإنتاج: اختبر طلب واحد → ثم متزامن → أخيرًا سياق طويل
📝 تمارين
- أساسي (⭐): شغّل وضع
--verbose، سجّل مقاييس رموز/ثانية وTTFT وغيرها لـ 3 مرات استنتاج، وأسس خط أساس الأداء الخاص بك. - متوسط (⭐⭐): إعدادات OLLAMA_NUM_PARALLEL=4، شغّل اختبارات متزامنة، وقارن تغيرات الإنتاجية وزمن الانتقال قبل وبعد التزامن.
- متقدم (⭐⭐⭐): اكتب تقرير ضبط أداء كامل — يتضمن قياسات الطلبات الفردية، اختبارات متزامنة، مقارنة num_ctx، وتوصيات التحسين — بتنسيق Markdown.