Hyperembed: Rust ile Embedding Inference Motoru Yazmak
Bir embedding inference motoru yazmaya karar verdiğimde, insanlar "neden? sentence-transformers var zaten" dediler. Haklılardı. sentence-transformers Python'da çalışıyor, 5000'den fazla model desteği var, PyTorch ekosistemi olgun.
Ama benim derdim embedding yapmak değildi. Benim derdim embedding'i her yerde yapmaktı. Bir sunucuda değil, bir telefonda değil, gömülü bir cihazda değil — hepsinde. Tek bir binary ile. Sıfır Python bağımlılığıyla. Sıfır PyTorch bağımlılığıyla. Sıfır ONNX runtime ile.
Pure Rust. Sadece Rust.
İşte Hyperembed böyle doğdu.
Problem: Embedding inference nerede?
Bugün embedding inference şu yerlerde yapılıyor:
- Cloud API (OpenAI, Cohere, vb.): En kolayı. Ama network latency, API maliyeti, veri gizliliği sorunları var.
- Sunucu tarafı (sentence-transformers + GPU): Daha iyi. Ama GPU lazım, Python runtime lazım, bellek yönetimi lazım.
- Edge / On-device: Neredeyse imkansız. PyTorch mobile büyük, ONNX runtime ağır, model optimizasyonu karmaşık.
Hyperembed bu üçüncü kategoriyi hedefliyor: edge'de, on-device, gömülü sistemlerde embedding inference. Rust sayesinde.
Nasıl çalışıyor?
Bir embedding inference motoru şunları yapmalı:
- Tokenizer: Metni token'lara çevir (BERT tokenizer, WordPiece)
- Model yükleme: BERT modelini yükle (ağırlıklar, config)
- Forward pass: Token'ları modelden geçir, hidden states'i al
- Pooling: Hidden states'ten tek bir embedding vektörü üret (mean pooling, cls token, vs.)
Hyperembed bunların hepsini sıfırdan, Rust ile implemente ediyor.
Tokenizer (WordPiece)
BERT tokenizer'ı implemente etmek, beklediğimden daha eğlenceliydi. WordPiece algoritması:
- Metni whitespace'lerden böl
- Her kelimeyi, vocabulary'deki en uzun eşleşen subtoken'a böl
- Bulunan subtoken'ları ID'lere çevir
- Özel token'ları ekle ([CLS], [SEP])
Bunu Rust'ta yapmak için bir trie (prefix tree) kullandım. 30,000 token'lık bir vocabulary'de, subword tokenization mikrosaniyeler mertebesinde çalışıyor. Python'dan yaklaşık 50x daha hızlı — çünkü Python her token için string slicing yapıyor.
Model yükleme
BERT modelleri genelde iki formatta gelir:
- PyTorch (.bin): Saf — pickle + tensor'lar
- ONNX (.onnx): Portable ama büyük
Hyperembed, PyTorch formatını doğrudan okuyor. torch.save() ile kaydedilmiş tensor'ları Rust'ta parse edebilmek için pickle formatını implemente ettim. Python'daki pickle modülünün yaptığını, Rust'ta sıfırdan yazdım.
Bu ne kadar zordu? Oldukça. Pickle formatı, Python objelerini serileştirmek için tasarlanmış. Stack tabanlı bir VM gibi çalışıyor. OPCODE'lar var, memoization var, referans çözümleme var. Rust'ta bunu implemente etmek yaklaşık 2000 satır kod aldı. Ama sonuç: herhangi bir PyTorch BERT modelini, PyTorch olmadan yükleyebiliyorum.
Forward pass
İşin en kritik kısmı. Transformer forward pass:
Input → Embedding → [Attention → FFN] × 12 → Pooling → Output
Her katman matris çarpımı. Çok fazla matris çarpımı. 12 katmanlı BERT-base için:
- Her attention head: Q, K, V projeksiyonları (3 matris çarpımı)
- Attention: Q @ K^T, softmax, sonra V ile çarp
- FFN: 2 büyük matris çarpımı (768→3072, 3072→768)
Toplamda yaklaşık 110M parametre. Her forward pass milyarlarca floating-point işlemi.
Bunu hızlı yapmak için:
- SIMD optimizasyonları: Rust'ın
std::simdmodülünü kullanarak AVX2/NEON instruction'larıyla matris çarpımı - Cache-friendly bellek düzeni: Ağırlıkları row-major yerine column-major saklayarak cache locality'yi iyileştirme
- Fused operations: LayerNorm + residual bağlantıyı tek geçişte yapma
- Weight quantization: FP32 yerine INT8 quantization (isteğe bağlı, model boyutunu 4x küçültüyor)
Sonuç: sentence-transformers'tan (CPU modunda) yaklaşık 3-5x daha hızlı. GPU modunda değil tabii — CUDA kernel'leriyle rekabet edemem. Ama CPU'da, hele ki düşük güçlü bir ARM işlemcide, Hyperembed ciddi fark atıyor.
Pooling
BERT'in son hidden states'inden embedding vektörü üretmek için:
- CLS token pooling: [CLS] token'ının hidden state'ini al
- Mean pooling: Tüm token'ların hidden state'lerinin ortalamasını al (padding token'lar hariç)
- Max pooling: Maksimum değerleri al
Hyperembed hepsini destekliyor. Varsayılan mean pooling — çoğu kullanım senaryosu için en iyisi.
Benchmark'lar
Birkaç benchmark sonucu (MacBook M2, CPU only, batch size=1):
| Model | Framework | Latency (ms) | Memory (MB) |
|---|---|---|---|
| BERT-base | sentence-transformers (Python) | 45 | 850 |
| BERT-base | ONNX Runtime (C++) | 28 | 520 |
| BERT-base | Hyperembed (Rust) | 11 | 180 |
| BERT-tiny | sentence-transformers (Python) | 12 | 340 |
| BERT-tiny | Hyperembed (Rust) | 2.3 | 65 |
ARM Cortex-A72 (Raspberry Pi 4):
| Model | sentence-transformers | Hyperembed |
|---|---|---|
| BERT-tiny | 380ms | 45ms |
Gömülü cihazlarda fiyat farkı burada ortaya çıkıyor. Python runtime, GIL, garbage collector, dinamik tip kontrolü — hepsi ARM'de çok pahalı. Rust'ın sıfır-maliyetli abstraksiyonları, kaynak kısıtlı cihazlarda gerçek farkı yaratıyor.
Semantic search pipeline
Hyperembed'in asıl kullanım alanı: semantic search. Bir döküman koleksiyonunda, kullanıcının sorgusuna anlamsal olarak en yakın dökümanları bulmak.
Pipeline:
- Tüm dökümanları Hyperembed ile vektörleştir
- Vektörleri bir vector database'e yaz (Qdrant, Milvus, veya basit bir FAISS index)
- Kullanıcı sorgusunu vektörleştir
- Cosine similarity ile en yakın k dökümanı bul
Hyperembed'in Rust API'si:
use hyperembed::{Embedder, EmbedderConfig};
let config = EmbedderConfig::default()
.with_model("sentence-transformers/all-MiniLM-L6-v2")?
.with_pooling(PoolingStrategy::Mean);
let embedder = Embedder::new(config)?;
let documents = vec![
"Rust programlama dili sistem programlama için tasarlanmıştır.",
"Python veri bilimi ve makine öğrenmesinde popülerdir.",
"JavaScript web tarayıcılarında çalışan bir dildir.",
];
let doc_embeddings = embedder.embed_batch(&documents)?;
let query_embedding = embedder.embed("sistem programlama dili")?;
let similarities: Vec<f32> = doc_embeddings.iter()
.map(|d| cosine_similarity(&query_embedding, d))
.collect();
// → [0.92, 0.45, 0.31] — Rust en yakın, beklendiği gibi
Local-first AI'nin geleceği
Hyperembed, daha büyük bir vizyonun parçası: local-first AI.
Bugün AI'nin %99'u cloud'da çalışıyor. OpenAI API, Anthropic API, Google Cloud AI. Bu model şu sorunları getiriyor:
- Gecikme: Her sorgu için network round-trip
- Maliyet: Token başına ücret, scale ettikçe katlanıyor
- Gizlilik: Verileriniz başkasının sunucusunda
- Bağımlılık: API kapandığında uygulamanız çalışmaz
- Offline: İnternet yoksa AI yok
Local-first AI, bu sorunların hepsini çözüyor. Model cihazınızda çalışıyor. Embedding'ler cihazınızda hesaplanıyor. Vector database cihazınızda çalışıyor (SQLite + sqlite-vec gibi).
Hyperembed bu vizyonun embedding katmanı. Diğer katmanlar:
- Inference: llama.cpp, mistral.rs gibi local LLM runtime'ları
- Vector storage: LanceDB, sqlite-vec
- Orchestration: Kendi agent framework'ün
2026'da, 7B parametreli bir LLM'i bir MacBook'ta çalıştırmak mümkün. 2027'de bir telefonda çalıştırmak mümkün olacak. Hyperembed bu geçişte embedding tarafını hallediyor — ve Rust'ta yazıldığı için, LLM runtime'larıyla aynı process'te, sıfır IPC overhead ile çalışabiliyor.
Ne öğrendim?
1. Pickle formatı karanlık bir sanat. Python objelerini parse etmek için bir VM implemente etmek zorunda kalacağımı tahmin etmezdim. Ama oldu. Ve işe yarıyor.
2. SIMD, Rust'ta sandığınızdan daha erişilebilir. std::simd nightly'de stabilize oluyor. Portable SIMD yazmak artık unsafe ve inline assembly gerektirmiyor.
3. BERT inference, sandığınızdan daha basit. Transformer mimarisi karmaşık görünüyor ama aslında birkaç temel operasyonun tekrarı: matmul, softmax, layer norm, GELU. Bunları implemente ettiğinizde gerisi loop.
4. Rust, ML inference için underrated. Herkes Python + CUDA yazıyor ama CPU inference için Rust çok daha iyi. Bellek yönetimi, SIMD, sıfır-cost abstractions — hepsi inference latency'sini dramatik düşürüyor.
Sırada ne var?
v0.3 roadmap:
- daha fazla model mimarisi: RoBERTa, DistilBERT, MiniLM
- GGUF format desteği: llama.cpp'nin kullandığı quantization formatı
- WASM target: Tarayıcıda embedding inference (!)
- Python binding:
pip install hyperembedile Python'dan kullanılabilir olacak - ONNX export: Kendi modelini ONNX olarak dışa aktarma (tersi: ONNX → Hyperembed)
Deneyin
git clone https://github.com/neuralforgeone/hyperembed
cd hyperembed
cargo build --release
cargo run --example semantic_search
Henüz crates.io'da değil. Ama yakında cargo add hyperembed yazabileceksiniz.
Sonraki yazıda BareEngine'i anlatacağım — Metin2 tarzı MMORPG dünyaları için editor-first oyun motoru. 10 yıl önce başladığım yere, oyun sunucularına, C++ ile geri dönüş.