Kubernetes · GPU Zamanlama
2 Mayıs 2026 · Üretim notları
Kubernetes CPU ve belleği akıcı şekilde böler; ama GPU söz konusu olduğunda varsayılan zamanlayıcı oldukça naiftir. Bir GPU ya tamamen bir pod'a aittir ya da değildir. Üretimde GPU faturasını düşürmek, işte bu kaba taneliliği aşmakla başlar. Bunun yolu da yeni kart almaktan değil, zamanlayıcıya doğru ipuçlarını vermekten geçer.
- Varsayılan
- ya hep ya hiç
- Kaldıraç
- MIG · time-slicing
- Yerleştirme
- taint · affinity
- Sonuç
- %18 → %71 kullanım
Bu yazıdaki kavramlar
- device plugin
- MIG
- time-slicing
- taints & tolerations
- node affinity
- NVLink topolojisi
- bin-packing
Bir GPU pahalıdır, ama asıl pahalı olan boşta bekleyen GPU'dur. Küçük bir çıkarım servisi koca bir A100'ü kapar, aylarca %12 kullanımla durur. Kubernetes'in varsayılan davranışı bu israfı görmez bile, çünkü onun gözünde GPU bölünemez bir bütündür.
Aşağıda problemi ve pratikte işe yarayan dört kaldıracı sırayla çizdim: zamanlayıcının GPU'yu nasıl gördüğü, kartı bölmenin iki yolu (MIG ve time-slicing), işleri doğru düğüme yerleştirmek (taint/affinity) ve son olarak topoloji. Bir şeyi gerçekten anlatan animasyonların köşesinde nazik bir ↻ tekrar düğmesi var.
Sorun: ya hep ya hiç
device plugin modeli bir GPU'yu bölünmez bir kaynak olarak sunar: pod ya tam bir kart ister ya da hiç. Küçük bir çıkarım servisi 80 GB'lık bir A100'ün belki 8 GB belleğini ve hesaplama biriminin (SM) küçük bir kısmını kullanır, ama kartın tamamını rezerve eder. Geri kalan her şey 'belki lazım olur' diye boşta bekler.
Bellek · 80 GB
Hesaplama · 108 SM
Birden fazla küçük iş aynı kartı paylaşamadığı için kümeyi büyütmek tek çare gibi görünür, oysa değildir. Çözüm, aynı kartı güvenli biçimde bölmek ve işleri akıllıca yerleştirmektir.
Zamanlayıcı GPU'yu nasıl görür
GPU'lar Kubernetes'e bir device plugin üzerinden tanıtılır. Plugin düğümdeki kartları sayar ve kubelet'e bildirir; kubelet bunu düğümün kapasitesine yazar; API sunucusu allocatable olarak tutar; zamanlayıcı da pod'un istediği kadar tam birim olan bir düğüm arar ve pod'u oraya bağlar.
Düğümdeki 8 fiziksel GPU bu zincirden geçer ve düğümün 'allocatable' sayısı olur: 8.
Kaynak bir tam sayı: pod 1, 2, … tam GPU alır; 0.3 GPU istenemez.
Püf nokta şu: bu kaynak bir tam sayıdır. Yarım GPU diye bir şey yoktur; zamanlayıcı '0.3 GPU' ayıramaz. Kartı bölmek, ya bu tam sayının ne anlama geldiğini değiştirmek (MIG) ya da aynı birimi zaman içinde birden çok pod'a dağıtmaktır (time-slicing).
MIG ile donanımda bölmek
A100/H100'de Multi-Instance GPU (MIG), kartı donanım seviyesinde izole dilimlere ayırır. Her dilimin kendi hesaplama bölmeleri, kendi bellek dilimi ve kendi bellek yol genişliği vardır. Bir A100 yedi adede kadar bağımsız örneğe bölünebilir; en yaygın profil 1g.5gb'tir (kartın 1/7 hesaplaması, 5 GB bellek).
MIG kapalı
1 kart = 1 pod
MIG açık · 7× 1g.5gb
1 kart = 7 izole örnek, hepsi aynı anda
her dilim ≈ kartın 1/7'i: ~14 SM hesaplama + 5 GB bellek
Önemli olan izolasyon: dilimler birbirinin performansını etkilemez, 'komşu gürültüsü' yoktur, gecikme tahmin edilebilir kalır. Her dilim Kubernetes'e ayrı bir kaynak olarak görünür, yani zamanlayıcı tek bir karta yedi farklı pod yerleştirebilir; hepsi de aynı anda çalışır. MIG mekânı böler.
Time-slicing: zamanı paylaşmak
İzolasyon gerekmeyen, ani ve aralıklı işler için daha ucuz bir yol var: time-slicing. Tek bir GPU birden çok pod tarafından paylaşılır, ama mekânda değil zamanda: kart işleri sırayla, küçük zaman dilimleri (quanta) halinde çalıştırır. Aşağıdaki zaman ekseninde imleç ilerledikçe, her an yalnızca tek bir işin GPU'da olduğunu görebilirsin.
Bedeli şudur: gerçek izolasyon yoktur. Bir iş kartı kötü kullanırsa diğerlerini yavaşlatır ve her geçişte küçük bir bağlam değişimi (context switch) maliyeti vardır. Ani yükler için harika, gecikmeye duyarlı üretim trafiği için riskli. MIG mekânı böler, time-slicing zamanı.
“Kubernetes'te GPU verimliliği bir zamanlama problemidir. Zamanlayıcıya doğru ipuçlarını vermek, donanım almaktan çok daha ucuzdur.”
MIG mı, time-slicing mi?
İkisi de tek kartı çok işe açar, ama farklı söz verir. Karar tek soruya iner: bu işler birbirine güvenebilir mi?
İşi doğru düğüme koymak
Kartı böldün; peki pod doğru makineye nasıl gider? İki mekanizma birlikte çalışır. taint, pahalı GPU düğümüne bir 'giriş yasak' etiketi asar; yalnızca o taint'i tolere eden (toleration taşıyan) pod'lar oraya inebilir. node affinity ise pod'u GPU etiketli düğümlere doğru çeker.
Zamanlayıcı, taint ile toleration'ı eşleştirerek her pod'u doğru düğüme koyar:
taint'i tolere eder, affinity çeker → girer
taint'i tolere edemez → giremez, buraya düşer
Yani taint itme, affinity çekme kuvvetidir. Sonuç: GPU pod'u (tolerasyon + affinity ile) GPU düğümüne yerleşir; tolerasyonu olmayan CPU pod'u oraya giremez ve CPU düğümüne düşer. Böylece kaçak CPU işleri pahalı kartların üstüne düşmez.
Topoloji: NVLink mi, PCIe mi?
Çok-GPU'lu işlerde kartların birbirine nasıl bağlandığı throughput'u belirler. NVLink'le bağlı kartlar birbirleriyle çok hızlı konuşur; PCIe üzerinden konuşmak ise kat kat yavaştır. Bir eğitim işinin her adımdaki all-reduce'u bu bağlantının üstünden akar.
NVLink adası A
NVLink adası B
all-reduce NVLink üstünde uçar
NVLink adası A
NVLink adası B
PCIe darboğazı
İşte bu yüzden topology-aware scheduling önemli: çok-GPU işlerini NVLink'le bağlı kartlara (aynı adaya) denk getirirsin. Aşağıda aynı dört-GPU'lu iş iki türlü yerleştiriliyor: doğru yerleştirmede veri tek ada içinde NVLink üzerinden uçuyor; yanlış yerleştirmede adalara bölünüp PCIe'ye mahkûm oluyor.
Toparlarsak
Kartı böl, işi doğru düğüme koy, topolojiye saygı duy. Üç hamle de kod değil, yapılandırmadır. Üçü birden, yeni bir GPU siparişi vermeden kapasiteni katlar.
