تقديم Gemma 4 بلغة Rust على vLLM: تجربة نهاية أسبوع نجحت ببراعة 🦀

تقديم Gemma 4 بلغة Rust على vLLM: تجربة نهاية أسبوع نجحت ببراعة 🦀

تقديم Gemma 4 بلغة Rust على vLLM: تجربة نهاية أسبوع نجحت ببراعة 🦀

السبت الماضي، وقفت مذهولاً أمام فاتورة AWS جعلت قهوتي تبرد. ثلاثة instances من نوع GPU تشغّل endpoints للـ PyTorch لمشروع جانبي — 47 دولار في يومين. لا بد من طريقة أفضل.

هنا دخل `vllm-rs` (واجهة vLLM بلغة Rust)، و Gemma 4، و instance من نوع Graviton2 مع GPU من نوع T4G. ما بدأ كـ "خليني أشوف لو هيدمّج" عصر السبت، تحوّل ل-stack تقديم production-grade يكلف قرشين ويتحمل الطلبات المتزامنة بطلاقة.

خليني أفصّل لك كل حاجة — العيوب، المكاسب، وتلك الـ config flag الوحيدة اللي وفرت عليا 6 ساعات debugging.

---

ليه الـ Stack ده؟ (وليه دلوقتي؟)

لو قدمت LLMs في production، عارف نقاط الألم: Python's GIL بقتّل الـ throughput تحت الحمل، تجزئة ذاكرة CUDA تاكل الـ VRAM، وكل framework عايز docker image خاص بيه.

vLLM حل مشكلة الذاكرة بـ PagedAttention. لكن الواجهة الأمامية بلغة Python؟ ما تزال bottleneck عند التوسع.

Rust بتغيّر المعادلة:
- **مفيش GIL** — معالجة طلبات متوازية حقيقية
- **Latency متوقعة** — مفيش GC pauses وسط التوليد
- **نشر بbinary واحد** — انسخ، شغّل، خلص
- **Graviton2 + T4G** — حوسبة ARM64 + GPU من NVIDIA بـ $0.75/hr على spot

Gemma 4 (النوع 9B) بتلائم 16GB VRAM براحة وبتبقى مساحة لـ KV cache. مثالية لـ T4G.

---

فحص الواقع للـ Hardware

قبل ما تدوّر instances، اعرف بتدخل في إيه:

| المكون | المواصفات | الواقع |
|-----------|------|---------|
| Instance | `g5g.xlarge` | 4 vCPU، 16GB RAM، 1× T4G (16GB VRAM) |
| OS | Ubuntu 22.04 ARM64 | تشتغل out of the box |
| التخزين | 100GB gp3 | الموديل + cache + logs |
| الشبكة | حتى 10 Gbps | أكتر من كافي |

**التكلفة**: تقريباً $0.75/hr spot، $2.03/hr on-demand. شغلتي الاختبارية (200 req/min، 512 token متوسط) كلفت $12/يوم مقابل $47 على الـ stack القديم.

---

الخطوة 1: Rust Toolchain — متتخطّاهاش

```bash
نصب rustup (ARM64 native)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"

حاسم: استعمل الـ target الصح
rustup target add aarch64-unknown-linux-gnu

تحقق
rustc --version
rustc 1.82.0 (f6e511eec 2024-10-15) — تمام
cargo --version
cargo 1.82.0 — تمام
```

**فخ**: لو على x86_64 وبتبني لـ ARM، محتاج cross-compilation. ابنِ *على* instance الـ Graviton2. أسرع من محاربة `cross` وأخطاء الـ linker.

---

الخطوة 2: واجهة vLLM بلغة Rust — بناء `vllm-rs`

الريبو في `github.com/vllm-project/vllm-rs`. استنسخ وابنِ:

```bash
git clone https://github.com/vllm-project/vllm-rs.git
cd vllm-rs

يسحب vLLM core ك submodule — ياخد 5-10 دقايق
git submodule update --init --recursive

بناء release (اشرب قهوة، ~15 دقيقة على g5g.xlarge)
cargo build --release --features cuda
```

**الـ feature flag اللي عضني**: `--features cuda` إجباري. من غيره، هتحصل على build للـ CPU فقط *يبدو* إنه يشتغل لكن بيرجع لسرعات `llama.cpp`. تحقق من الـ binary:

```bash
./target/release/vllm --help | grep -i cuda
المفروض يظهر: CUDA support: true
```

---

الخطوة 3: Gemma 4 — تجيب الـ Weights صح

Gemma 4 لسه مش على Hugging Face Hub كملف `safetensors` واحد (وقت الكتابة). عندك مسارين:

المسار أ: تحويل من HF (موصى بيه)
```bash
نصب أدوات التحويل
pip install huggingface_hub safetensors torch --index-url https://download.pytorch.org/whl/cu121

حمل وحوّل
python -c "
from huggingface_hub import snapshot_download
from safetensors.torch import save_file
import torch

path = snapshot_download('google/gemma-4-9b', allow_patterns='*.safetensors')
منطق التحويل هنا — شوف docs الـ vLLM للـ script بالظبط
"
```

المسار ب: استخدم AWW كمّي مسبقاً (اللي عملته أنا)
```bash
4-bit AWQ بيلم في ~6GB VRAM، يسيب 10GB لـ KV cache
wget https://huggingface.co/TheBloke/gemma-4-9b-AWQ/resolve/main/gemma-4-9b-awq.safetensors
```

**رأيي**: AWQ 4-bit لا يُميّز عن FP16 في معظم حالات الشات. مكسب الـ throughput (2.3x tokens/sec) حقيقي. لو محتاج logits دقيقة لـ distillation أو eval، حوّل FP16 بنفسك.

---

الخطوة 4: الكونفيق اللي بيشتغل فعلياً

الـ `config.toml` بتاعي — انسخ، عدل، انشر:

```toml
config.toml
[model]
path = "/models/gemma-4-9b-awq.safetensors"
dtype = "float16" # AWQ بيحمّل كـ FP16 داخلياً
max_model_len = 8192

[server]
host = "0.0.0.0"
port = 8000
workers = 4 # يطابق عدد الـ vCPU
max_concurrent_requests = 128

[engine]
gpu_memory_utilization = 0.90
swap_space = 4 # GiB، buffer للـ CPU offload
block_size = 16
max_num_batched_tokens = 4096
max_num_seqs = 256

[logging]
level = "info"
access_log = true
```

**الفلاجات المهمة مفسّرة**:
- `gpu_memory_utilization = 0.90` — سيب 10% headroom لـ CUDA context + التجزئة. ادفع لـ 0.95 وهتاخد OOM تحت الحمل المفاجئ.
- `swap_space = 4` — حاسم للسياقات الطويلة. ينقل أقل كتل KV استخداماً للـ RAM الـ CPU. نجّاني لما مستخدم لصق doc فيه 12k token.
- `max_num_batched_tokens` — ضبّطه. قليل = GPU مستغلة أقل. كتير = OOM. 4096 هي الـ sweet spot لـ T4G + Gemma 4 9B.

---

الخطوة 5: Systemd — تخليه يعيش بعد الريبوت

```ini
/etc/systemd/system/vllm-gemma.service
[Unit]
Description=vLLM Gemma 4 Rust Server
After=network.target nvidia-persistenced.service
Requires=nvidia-persistenced.service

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/vllm-rs
ExecStart=/opt/vllm-rs/target/release/vllm --config /opt/vllm-rs/config.toml
Restart=on-failure
RestartSec=10
Environment=RUST_LOG=info
Environment=CUDA_VISIBLE_DEVICES=0

حدود الموارد
LimitNOFILE=65536
LimitNPROC=32768

[Install]
WantedBy=multi-user.target
```

فعّل وابدأ:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now vllm-gemma
journalctl -u vllm-gemma -f # راقب اللوجز
```

---

اختبار واقعي: 3 سيناريوهات

السيناريو 1: Chat API لـ Docs Bot (حالتي الفعلية)
**الحمل**: 50 مستخدم متزامن، 200 req/min، متوسط 512 output token
**النتيجة**:
- P50 latency: 340ms (أول token)
- P99 latency: 1.2s
- Throughput: 1,850 tokens/sec sustained
- VRAM: 11.2GB / 16GB
- صفر OOMs في 72 ساعة

السيناريو 2: إكمال كود بأسلوب Burst
**الحمل**: 20 طلب متوازي، 2k token prompts، 512 completion
**النتيجة**:
- أول token: 410ms (معالجة prompt أطول)
- Throughput: 2,100 tokens/sec
- `swap_space` اشتغل عند 14GB VRAM — بسلاسة

السيناريو 3: Stress Test (Locust، 500 مستخدم)
**الحمل**: تصعيد لـ 500 متزامن على 5 دقايق
**النتيجة**:
- وقت الطابور سيطر عند 300+ متزامن
- معدل خطأ: 0.3% (timeouts، مش crashes)
- التعافي: فوري لما الحمل قل

**الخلاصة**: الواجهة الأمامية بلغة Rust بتتعامل مع الـ backpressure برشاقة. Python vLLM كان هيكسر worker processes.

---

المراقبة: متطيرش أعمى

أضف مقاييس Prometheus (مدمجة في `vllm-rs`):

```bash
اعرض endpoint المقاييس
أضف لـ config.toml:
[metrics]
enabled = true
port = 9090
```

داشبوردز مفتاحية للمراقبة:
- `vllm_requests_active` — التزامن الحالي
- `vllm_gpu_memory_usage_bytes` — ضغط الـ VRAM
- `vllm_iteration_tokens_total` — صحة الـ throughput
- `vllm_request_duration_seconds` — توزيع الـ latency

JSON داشبورد Grafana: `github.com/vllm-project/vllm-rs/tree/main/monitoring/grafana`

---

لحظات "ليه عملتش كده من بدري"

1. **نشر بbinary واحد** — `scp` للـ binary، `systemctl start`، خلص. مفيش docker، مفيش python env drift.
2. **البداية الباردة** — 2.3 ثانية من `systemctl start` لخدمة الطلبات. Python vLLM: 18-25s (تحميل الموديل + spawn workers).
3. **استقرار الذاكرة** — 72 ساعة، مفيش ريستارت. Python workers محتاجين ريستارت يومي لـ memory leaks.
4. **ARM64 native** — مفيش ضريبة محاكاة. Graviton2 يشغّل ده *أحسن* من x86_64 على price/performance.

---

حاجات لسه بتزعجني

- **تقلّب صيغ الموديلات** — Gemma 4 AWQ مش رسمي. الإصدار الجاي ممكن يكسر سكريبت التحويل بتاعي.
- **Sampling params محدودة** — `vllm-rs` كاشفة temperature، top_p، top_k، لكن مفيش `min_p` أو `typical_p` لسه. PR مرحب بيه.
- **مفيش multi-GPU** — GPU واحد بس للآن. الـ roadmap يقول tensor parallelism جاية Q1 2025.
- **تفصيل اللوجز** — `RUST_LOG=debug` بيسكب 50MB/دقيقة. استعمل `info` في الإنتاج.

---

الأسئلة الشائعة

س: أقدر أشغّل ده على instance x86_64 مع A10G؟
**ج**: تماماً. غيّر نوع الـ instance لـ `g5.xlarge` (A10G، 24GB VRAM)، ابنِ على x86_64، وزود `gpu_memory_utilization` لـ 0.93. هتاخد تقريباً 3,200 tokens/sec على Gemma 4 9B. التكلفة تطلع لـ ~$1.00/hr spot.

س: كيف يقارن بـ TGI (Text Generation Inference)؟
**ج**: TGI أكتر اكتمالاً في الميزات (quantization، sharding، streaming دقيق). `vllm-rs` كاسبة في الـ raw throughput والتكامل الـ Rust-native. لو محتاج LoRA adapters أو speculative decoding النهاردة — TGI. لو عايز ماكس tokens/dollar على GPU واحد — `vllm-rs`.

س: إيه رأيك في Gemma 4 27B؟
**ج**: مش هتلائم على T4G (16GB VRAM). محتاج `g5g.2xlarge` (2× T4G، 32GB مجتمعين) مع tensor parallelism — مش مدعومة لسه في `vllm-rs`. لـ 27B، تمسّك بـ TGI أو vLLM Python على A100/H100.

س: الـ OpenAI-compatible API مطبقة بالكامل؟
**ج**: `/v1/chat/completions` و `/v1/completions` بيشتغلوا. `/v1/embeddings` بترجع 501 (مش مطبقة). الـ streaming (`stream: true`) بيشتغل عبر SSE. Function calling — لسه.

---

باختصار

**الـ Stack**: Gemma 4 9B AWQ + vLLM Rust (`vllm-rs`) + Graviton2 + T4G
**التكلفة**: ~$12/يوم لـ 200 req/min sustained
**الأداء**: 1,800-2,100 tokens/sec، P99 < 1.2s
**التشغيل**: binary واحد، systemd، 2.3s cold start، صفر ريستارت في 72س

**الريبو**: `github.com/vllm-project/vllm-rs`
**الموديل**: `huggingface.co/TheBloke/gemma-4-9b-AWQ`
**الـ Instance**: AWS `g5g.xlarge` spot

هشغّل ده في production لمنتج مدفوع؟ **أيوة** — مع التنبيه إنك على حافة `vllm-rs`. ثبّت commit hash، راقب الريبو، وخلي Python vLLM fallback جاهز.

لكن للأدوات الداخلية، المشاريع الجانبية، والأحمال الحساسة للتكلفة؟ الـ stack ده *هو ده*. الواجهة الأمامية بلغة Rust حولت vLLM من "محرك عظيم، تقديم مؤلم" لـ "محرك عظيم، تقديم عظيم".

خلاص، عندي فاتورة AWS أروح أقللها. ☕

Comments (0)

No comments yet. Be the first to comment!

Leave a Comment