GPU kernel'leri elle yazmak sabır, çaba ve aynı zamanda aklını da ister. Modern LLM'lerle (Claude Opus 5, GPT 5.6 Sol) kelimenin tam anlamıyla kernel yazdırmayı deniyorum. Burada bundan öğrendiklerimden bahsedeceğim.
Dürüst olmak gerekirse, GPU kernel'i yazmak utanç verici derecede doğrulanabilir bir iştir.
Önce, kernel'ini yazmak istediğin işlem(ler)i belirlersin. İkinci olarak, ilk sürümü yazarsın ve belirgin hatalar olmadan derlendiğinden emin olursun. Üçüncü olarak, yavaş bir referans uygulamayla doğruluğu kontrol edersin. Eşleşmezse, doğruluk sorununu düzeltmeye çalışırsın. Bu tamamlandığında, kernel'in çalışma süresini kıyaslarsın (benchmark). Sonraki sürümler bunun üzerine inşa edilir ve roofline metriklerine ulaşana ya da tatmin olana kadar optimize etmeye devam edersin.

Doğrulanabilir döngü: GPU kernel geliştirme
Yukarıdaki görsel bunu doğrulanabilir bir döngü olarak gösteriyor:
- Derleme kontrolü, doğruluğu düşünmeden önce sıkı bir yerel döngüdür (B <-> C).
- Doğruluk kontrolü (D <-> E <-> F), doğrulanabilir ödül döngüsünün çekirdeğidir. Kernel yazmayı otomatik doğrulama için iyi bir problem haline getiren kısım budur, çünkü karşılaştırma yapabileceğin kesin bir referansın vardır.
- Optimizasyon (G -> H -> D'ye geri), her yeni sürüm için aynı doğruluk döngüsünü yeniden kullanır; çünkü hızlı ama hatalı bir kernel işe yaramaz, doğruluk her sürüm için doğrulanmalıdır.
- Roofline/tatmin kontrolü (I), optimize etmeye devam edip etmemeye ya da durmaya karar veren dış döngüdür.
İlk sürüm
Yukarıdaki görsel hâlâ üst düzey bir bakış açısı ve asıl zorluk detaylarda gizli. LLM ajanımızın iyi bir ilk sürüm yazmaya başlayabilmesi için gerekli tüm bağlama sahip olduğundan emin olmalıyız.
CUDA DSL'leri burada devreye giriyor. Triton, CuTeDSL ve Tilelang, Python'da başlaması çok kolay olanlardan. Öğrenme eğrisi CUDA C++'a kıyasla daha dik değil, ancak bu DSL'lerdeki soyutlamalar ajanımızın kafasını daha da karıştırabilir. Bu soyutlamaların bağlamını ajana aktarmanın bir yoluna ihtiyacımız var.
Modern LLM'ler zaten "iyi" Triton yazmayı biliyor. Herhangi bir bağlam olmadan bile Triton soyutlamalarıyla iyi çalışabilirler. Ancak, CuTeDSL gibi Triton'dan çok daha fazla kontrol sağlayan diğer DSL'ler için, ajanın DSL soyutlamalarını anlamak için bakabileceği bir bağlam dizininin (context directory) olmasının çok yardımcı olduğunu buldum.
Örneğin,
NVIDIA cutlass
deposunu bağlam dizinine klonlamak, ajanın
Layout Cebiri, Copy/GEMM atomları, bellek hiyerarşisi, örnek kernel'ler
ve benzeri soyutlamaları CuTeDSL'de kernel yazarken araması için iyi bir yoldur.
Deneyimlerime göre, iyi bir ilk kernel sürümü belirgin hatalar olmadan derlenir ve aşağıda bahsedeceğim doğruluk testini geçer.
Test, Benchmark ve Profiling
Ajana yeterli bağlam verildiğinde, asıl darboğaz artık doğrulamaya kayıyor. Referans uygulamanın kendisi ve ona karşı doğrulama yapmak giderek daha önemli hale geliyor. Bu aşamaya doğruluk testi ya da kısaca test diyorum. Referans uygulamanın hızı, niyeti kadar önemli değildir. Ölçmek ve doğrulamak istediğin şey, ajanının optimize edeceği şeydir.
Genellikle, hesaplama düşük hassasiyette (lower precision) yapılmaması gerektiğinde Maksimum Mutlak/Göreli Hata (MAE) , Ortalama Kare Hata (MSE/RMSE) ve PSNR (Tepe Sinyal-Gürültü Oranı) ölçerim. Düşük hassasiyetler söz konusu olduğunda, PSNR ve Kosinüs benzerliği (cossim) ölçme eğilimindeyim.
Kernel sürümlerinin GPU'da gerçekten çalışmasını sağlama yöntemin, GPU'nun yerel olarak mı yoksa bulut üzerinden mi erişilebilir olduğuna bağlıdır. Her durumda, ajanımız çıktılarına şu ya da bu şekilde erişebilme yeteneğine sahip olmalıdır.
Aşağıdaki rung metodolojisini, N adet test fonksiyonuna sahip olmanın iyi bir yolu olarak buluyorum:
1def rung(name):2 def deco(fn):3 try:4 out = fn()5 results[name] = {"ok": True, **(out or {})}6 print(f"[{name}] ok " + " ".join(f"{k}={v}" for k, v in (out or {}).items()))7 except Exception as e:8 results[name] = {"ok": False, "err": f"{type(e).__name__}: {e}"}9 print(f"[{name}] FAILED {type(e).__name__}: {e}")10 traceback.print_exc()11 return fn12 return deco
şu şekilde çağırabilirsin:
1out = {}23@rung("pre-checks")4def _():5 run_pure_checks()6 run_dsl_checks()78@rung("run")9def _():10 out["o"] = custom_kernel(*inputs)11 torch.cuda.synchronize()12 return {"shape": tuple(out["o"].shape),13 "finite": bool(torch.isfinite(out["o"]).all())}
Benchmark rung'u için birden fazla şey yapabilirsin:
- Toplam harcanan süre için uçtan uca kernel çalışma süresini benchmark et
- Bir kernel içindeki bölümleri benchmark etmek için kernel içi izleme (intra-kernel tracing) kullan ve bunları çıktıya dök (özel bir izleyici veya CUPTI kullanarak)
- Üretilen IR, PTX, SASS ve CUBIN dosyalarını bir dumps dizinine dök ve ajanın bunları incelemesine izin ver
Son nokta biraz daha genişletilebilir. Bazen DSL, alt optimum PTX (ve nihayetinde SASS) üretecek şekilde indirgeme yapabilir ve bunun yerine kullanılabilecek daha iyi talimat(lar) veya şekil(ler) bulabilirsin. Ajanımız PTX/SASS metin dosyalarını okuyabilir ve DSL'nin alt optimum kısmı işlemesine izin vermek yerine düşük seviyeli kodu satır içine alabilir. Yine, "aranabilir" PTX dokümantasyonunu bağlam olarak iletmek burada çok yardımcı olur.
Hepsini bir araya getiren son şey Profiling'dir. Ajanın NCU (Nsight Compute Systems) CLI'sine erişebiliyorsa, yukarıdaki doğrulanabilir geri bildirim döngüsünün bir parçası olarak kernel'in için profil çıkarıp bir rapor oluşturmasını isteyebilirsin.
Kapanış düşünceleri
"GPU kernel geliştirme öldü mü o zaman?"
"Evet ama aslında hayır"
Evet, çünkü layout'ların, indekslemenin, soyutlamaların ve genel yapının zor kısımları, yeterli bağlama sahip ajanlar tarafından büyük ölçüde çözülebilir. 2-3 haftalık işi 1-2 güne kolayca indirebilirsin. Hayır, çünkü asıl darboğaz kernel'lerden doğrulamaya kaydı. Artık işleri yapmanın tek bir yolu yok: bağlamın ve test düzeneğin ne kadar iyiyse, süreç o kadar hızlı olur. Özel durumlar daha da fazla fayda sağlayacaktır ve ihtiyacın olan tek şey iyi bir test düzeneğidir.
Son olarak, ajanları otonom varlıklar olarak görmek yerine, yönlendirebileceğin gerçekten akıllı asistanlar olarak görmek hâlâ gerekli. GPU'lar ve kernel'ler hakkındaki temel anlayışının devreye girdiği yer burasıdır. İnsan kısmı (sen) hâlâ burada gerekli.
Acı tatlı bir his, biliyorum :)





