#Rust#GPT-2#Derin Öğrenme#Sistem Programlama#Yapay Zeka Mühendisliği

Sistem Programlama ve Yapay Zeka: Rust ile Özel GPT-2 Decoder Geliştirmek

Rust ve tch-rs ile GPT tarzı decoder blokları ve eğitim döngüleri kurmaya yönelik sistem odaklı bir rehber.

MTMurat Tut
5 dk okuma

Python çalışma zamanı yükünden uzaklaşarak tip güvenli, derlenmiş hızda makine öğrenmesi modelleri ve eğitim döngüleri geliştirmeye dair teknik bir rehber.

Python uzun yıllardır yapay zeka geliştirme için varsayılan dil oldu. PyTorch, TensorFlow, JAX, Jupyter ve Google Colab gibi araçlar hızlı araştırma ve prototipleme için çok güçlüdür.

Ancak yapay zeka iş yükleri araştırmadan üretim ortamlarına geçtikçe Python'ın bazı sınırları görünür hale gelir:

  • Global Interpreter Lock (GIL): Gerçek çok iş parçacıklı paralel yürütmeyi sınırlar.
  • Yüksek bellek yükü: Dinamik tip sistemi ve garbage collection ek bellek tüketir.
  • Derleme zamanı kontrol eksikliği: Tensor şekli veya tip hataları çoğu zaman ancak çalışma sırasında ortaya çıkar.

Sistem seviyesinde kontrol isteyen geliştiriciler için Rust güçlü bir alternatiftir. tch-rs, PyTorch'un C++ motoru olan libtorch üzerine Rust bağlayıcıları sağlar. Böylece PyTorch performansını Rust'ın bellek güvenliği ve tip sistemiyle birlikte kullanmak mümkün olur.


Rust AI Stack: Neden tch-rs?

Python'da import torch yazdığınızda aslında yüksek performanslı C++ motoru olan libtorch için bir sarmalayıcı kullanırsınız.

tch-rs, bu C++ kütüphanesine doğrudan Rust bağlayıcıları sağlar. Bu, sıfırdan CUDA kernel yazmadan veya derin öğrenme matematiğini baştan kurmadan PyTorch'un tensor performansını, GPU hızlandırmasını ve backpropagation altyapısını kullanmak anlamına gelir.

       ┌──────────────────────────────────────────────────┐
       │             Rust Application Code                │
       └───────────┬──────────────────────────────────────┘
                   │ FFI (Foreign Function Interface)
                   ▼
          tch-rs Rust Bindings
                   │
                   ▼
         PyTorch C++ (libtorch)
                   │
           ┌───────┴───────┐
           ▼               ▼
       CUDA Kernels   MPS Kernels

1. Causal Self-Attention Matematiği

GPT-2 gibi üretici transformer modellerinde attention katmanı query (QQ), key (KK) ve value (VV) matrislerini kullanarak token bağımlılıklarını hesaplar. Causal decoder yapısında modelin gelecekteki tokenlara bakması engellenmelidir. Bunun için gelecekteki pozisyonları -\infty yapan alt üçgensel maske matrisi (MM) kullanılır:

Attention(Q,K,V)=softmax(QKTdk+M)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}} + M\right)V

Burada:

  • QQ, KK, VV giriş embeddinglerinden üretilen Query, Key ve Value matrisleridir: Q=XWQ,K=XWK,V=XWVQ = XW_Q, \quad K = XW_K, \quad V = XW_V
  • dkd_k, key boyutudur ve softmax sırasında gradyanların kaybolmasını azaltmak için ölçekleme sağlar.
  • MM causal mask matrisidir: Mi,j={0if ijif i<jM_{i,j} = \begin{cases} 0 & \text{if } i \geq j \\ -\infty & \text{if } i < j \end{cases}

Rust tarafında bu maske tch-rs ile oluşturulabilir:

// src/model.rs
impl Module for CausalSelfAttention {
    fn forward(&self, x: &Tensor) -> Tensor {
        let kind = x.kind();
        let (_b, t, _c) = x.size3().unwrap();

        let k = self.key_linear.forward(&x);
        let q = self.query_linear.forward(&x);
        let v = self.value_linear.forward(&x);

        let mask = Tensor::ones([t, t], (kind, self.device))
            .tril(0)
            .reshape([1, t, t]);

        Tensor::scaled_dot_product_attention(&q, &k, &v, Option::Some(mask), 0.1, false, Option::None)
    }
}

2. Rust'ta Bellek Sürekliliği

Python/PyTorch'ta view gibi işlemler, tensor belleği uygun değilse çoğu zaman çalışma sırasında hata üretir. Rust tch-rs tarafında ise transpoze edilmiş bir tensörü .contiguous() çağırmadan yeniden şekillendirmek alttaki libtorch motorunda paniğe yol açabilir.

Çünkü transpoze işlemleri veriyi fiziksel olarak yeniden dizmek yerine layout metadata'sını değiştirir. Feed-forward ve attention çıktıları birleştirilirken bellek sürekliliği açıkça sağlanmalıdır:

// Rust içinde bellek düzenleme bloğu
let transposed = attention_output.transpose(1, 2);
let contiguous_tensor = transposed.contiguous();
let reshaped = contiguous_tensor.view([batch_size, seq_len, embed_dim]);

3. Derlenmiş Eğitim Döngüsü Yazmak

Python'da model parametreleri çoğu zaman arka planda daha örtük şekilde güncellenir. Rust'ta optimizer ve backpropagation adımları açık biçimde kurulur:

// src/main.rs
fn main() -> anyhow::Result<()> {
    let block_size = 128;
    let vocab_size = 968;
    let device = find_device();

    let vs = nn::VarStore::new(device);
    let mut opt = nn::AdamW::default().build(&vs, 1e-4)?;

    let model = Gpt::new(vs.root(), vocab_size, 128, block_size, 4, 4);

    for epoch in 0..100 {
        let (xs, ys, _) = dataset::get_batch_train();

        let logits = model.forward(&xs.to_kind(Kind::Int64).to_device(device));

        let (b, t, c) = logits.size3().unwrap();
        let logits = logits.view([b * t, c]);
        let targets = ys.to_kind(Kind::Int64).to_device(device).view([b * t]);

        let loss = logits.cross_entropy_for_logits(&targets);

        opt.backward_step(&loss);

        if epoch % 10 == 0 {
            println!("Epoch: {}, Loss: {:?}", epoch, loss);
        }
    }
    Ok(())
}

PyTorch ve tch-rs Arasındaki Temel Farklar

  1. Atomik gradyan güncellemeleri: Python'da optimizer.zero_grad(), loss.backward() ve optimizer.step() ayrı ayrı çağrılır. Rust tarafında opt.backward_step(&loss) boilerplate kodu azaltır.
  2. Açık reshape işlemleri: Python dinamik slice sözdizimi sunar. Rust tarafında .view() ve .reshape() gibi dönüşümler daha açık yazılır.
  3. Variable Store (VarStore): Rust değişkenleri isimlendirilmiş bir ağaç altında toplar. Bu, thread paylaşımı ve checkpoint serileştirme için daha güvenli bir yapı sağlar.

Mühendislik Çıkarımları

  1. Sessiz hataları erken yakalayın: Derin öğrenme modellerinde tensor boyutu hataları uzun eğitimlerden sonra ortaya çıkabilir. Rust daha açık tip ve yapı disiplini sağlar.
  2. Bellek sürekliliğine dikkat edin: Transpose işlemleri belleği fiziksel olarak yeniden dizmez. view öncesinde contiguity kontrol edilmelidir.
  3. Düşük çalışma zamanı yükü: cargo build --release ile native binary üretmek, edge sunucularda daha küçük bellek iziyle inference veya eğitim çalıştırmayı kolaylaştırır.