Örnek sorular
Aipy Async Concurrency Ml ServingZorluk 1
Standart CPython'da Global Interpreter Lock (GIL), tek bir process içinde çalışan thread'ler hakkında neyi garanti eder?
- aThread'lerin herhangi bir C uzantısını çağırması yasaktır, çünkü GIL tüm native kod yollarını engeller.
- bHer thread kendi ayrılmış CPU çekirdeğini alır, böylece bytecode yürütme çekirdekler arasında tam paralel olur.
- cMakinede birden çok CPU çekirdeği olsa bile, aynı anda yalnızca bir thread Python bytecode'u çalıştırır.✓
- dGIL yalnızca process başlangıcında vardır ve ilk thread modülleri import etmeyi bitirdiğinde kaldırılır.
Açıklama:GIL, standart bir CPython process'inde kaç CPU çekirdeği olursa olsun aynı anda yalnızca bir thread'in Python bytecode'u çalıştırmasına izin veren bir mutex'tir. (b) GIL'in Python bytecode için tam olarak engellediği gerçek çok-çekirdekli paralelliği anlatıyor. (c) yanlıştır — C uzantıları GIL'i sıkça bırakabilir. (d) GIL'in çalışma biçimiyle uyuşmayan, sadece başlangıca özgü bir yaşam süresi uyduruyor.
Aipy Async Concurrency Ml ServingZorluk 1
Bir ekip, CPU-bound bir tokenizasyon/ön-işleme fonksiyonunu 4 threading.Thread üzerinde çalıştırıyor ve 4 çekirdekli bir makinede yaklaşık 4 kat hızlanma bekliyor. Standart CPython'da gerçekte ne olur?
- aNeredeyse hiç hızlanma sağlamaz, çünkü GIL yine aynı anda yalnızca bir thread'in Python bytecode'u çalıştırmasına izin verir.✓
- bNeredeyse tam olarak 4 kat hızlanır, çünkü her thread saf Python hesaplaması için bir çekirdeği kendine alır.
- c1 thread'e göre yaklaşık 4 kat yavaşlar, çünkü GIL 4 thread'i uçtan uca hiçbir çakışma olmadan tamamen serileştirir.
- dThread'ler oluşturulma anında RuntimeError fırlatır, çünkü CPython birden fazla CPU-bound thread'i reddeder.
Açıklama:Saf-Python, CPU-bound iş için GIL, 4 thread'in etkin olarak tek bir çekirdek üzerinde bytecode çalıştırmak için sırayla devralması anlamına gelir; bu yüzden gerçek anlamda neredeyse hiç hızlanma olmaz — CPU-bound Python işleri için multiprocessing'in tercih edilmesinin nedeni tam olarak budur. (a) GIL'in engellediği gerçek paralelliği anlatıyor. (c) yavaşlamayı abartıyor; küçük bir GIL-geçiş maliyeti vardır, 4 kat ceza değil. (d) var olmayan bir kısıtlama uyduruyor — thread'ler sorunsuz oluşturulur ve çalışır, sadece bytecode için paralel değildir.
Aipy Async Concurrency Ml ServingZorluk 2
Bir model-serving endpoint'i, inference çalıştırmadan önce uzak bir feature store'a bir ağ çağrısı yapıyor ve yanıt için ~50ms bekliyor. O ağ çağrısında (bloklayan bir çağrı yerine) await kullanmak, aynı tek thread üzerinde daha fazla eşzamanlı request'e hizmet vermeye neden yardımcı olur?
- aAğ çağrısını CPU-bound bir işleme dönüştürür, böylece GIL artık ona hiç uygulanmaz.
- bFeature store'un daha hızlı yanıt vermesine neden olur, çünkü await edilen çağrılar hat üzerinde bloklayanlara göre önceliklendirilir, tıpkı QoS etiketlemesinin paketleri yeniden sıralaması gibi.
- cAwait edilen her çağrı için arka planda yeni bir OS thread'i başlatır, böylece bekleme otomatik olarak paralel gerçekleşir.
- dI/O beklerken coroutine kontrolü event loop'a geri bırakır, böylece diğer request'ler çalışabilir.✓
Açıklama:Bir I/O işleminde await, o coroutine'i askıya alır ve kontrolü event loop'a döndürür; event loop da ağ yanıtı gelene kadar bekleyen diğer coroutine'leri çalıştırabilir — async'in I/O-bound bekleme için temel faydası tam olarak budur. (a) ağ I/O'sunu yanlışlıkla CPU-bound olarak tanımlıyor. (b) var olmayan bir ağ-seviyesi önceliklendirme uyduruyor. (d) asyncio'nun tek-thread'li event loop'unun çalışma biçimini değil, çağrı-başına-thread modelini anlatıyor.
Aipy Async Concurrency Ml ServingZorluk 2
Bir async def request handler'ı içinde, bir geliştirici çalışması 200ms süren senkron, CPU-ağır bir inference fonksiyonunu doğrudan çağırıyor (ne await var ne de başka bir yere devrediliyor). Bu 200ms boyunca aynı event loop tarafından işlenen diğer eşzamanlı request'ler üzerindeki etki nedir?
- aDiğer coroutine'ler normal şekilde çalışmaya devam eder, çünkü
async def handler'lar tanım gereği her zaman bloklamayandır. - bEvent loop tüm 200ms boyunca bloklanır, bu yüzden ilgisiz request'ler dahil hiçbir başka coroutine ilerleyemez.✓
- cEvent loop yeni bağlantı kabul etmeyi durdurur ama zaten planlanmış coroutine'leri tam hızda işlemeye devam eder.
- dYalnızca başka client IP adreslerinden gelen request'ler gecikir; aynı client'tan gelenler etkilenmeden devam eder.
Açıklama:Bir coroutine içindeki senkron, CPU-bound bir çağrı event loop ile aynı tek thread üzerinde çalışır ve hiç kontrolü bırakmaz, bu yüzden tüm süresi boyunca tüm event loop'u bloklar — bekleyen her başka coroutine de durur. (a) yanlıştır; bir fonksiyonu async def ilan etmek içindeki bloklayan kodu bloklamayan yapmaz. (b) ve (c), tek-thread'li bir event loop'un çalışma biçimini yansıtmayan kısmi-bloklama davranışları uyduruyor.
Aipy Async Concurrency Ml ServingZorluk 1
Bir Python model-serving API bağlamında, 'I/O-bound' iş ile 'CPU-bound' işi en iyi ne ayırt eder?
- aKodun gerçekte ne yaptığından bağımsız olarak, I/O-bound iş her zaman CPU-bound işten daha hızlı çalışır.
- bI/O-bound iş, zamanının çoğunu dış sistemleri beklemekle geçirir; CPU-bound iş ise zamanının çoğunu hesaplama yaparak geçirir.✓
- cCPU-bound iş, bir
async def fonksiyonu içindeki herhangi bir koddur; I/O-bound iş ise sıradan bir def fonksiyonu içindeki herhangi bir koddur. - dI/O-bound iş yalnızca diskten dosya okumaya uygulanır; herhangi bir ağ çağrısı bunun yerine CPU-bound olarak sınıflandırılır, çünkü socket'ler kernel thread'leri tarafından yönetilir.
Açıklama:Ayrım, zamanın gerçekte nerede harcandığıyla ilgilidir: I/O-bound iş (ör. bir ağ yanıtını ya da disk okumasını beklemek) çoğunlukla boşta beklemedir, CPU-bound iş (ör. tensör matematiği, tokenizasyon döngüleri) ise çoğunlukla aktif hesaplamadır — bu ayrım async, thread ya da process'in yardımcı olup olmadığını belirler. (b) yanlış bir evrensel hız iddiası yapıyor. (c) async def/def sözdizimini işin gerçek doğasıyla karıştırıyor. (d) ağ çağrılarını I/O-bound işten yanlışlıkla dışlıyor.
Aipy Async Concurrency Ml ServingZorluk 2
import asyncio
async def handle_request(req):
result = run_cpu_heavy_inference(req) # sync, ~150ms CPU işi
return result
async def main():
await asyncio.gather(*(handle_request(r) for r in requests))
requests içinde 10 request varken,
asyncio.gather yaklaşık ne kadar sürer ve neden?
- aToplamda yaklaşık 150ms, çünkü
asyncio.gather 10 coroutine'in CPU işini gerçekten ayrı çekirdeklerde paralel çalıştırır. - bSüresiz olarak takılır, çünkü
run_cpu_heavy_inference'ın kendisi async def ile tanımlanmamıştır. - cToplamda yaklaşık 15ms, çünkü
asyncio.gather CPU işini tüm request'lere otomatik olarak eşit böler. - dToplamda yaklaşık 1500ms, çünkü her
handle_request çağrısı hiçbir çakışma olmadan event loop'u tam 150ms boyunca bloklar.✓
Açıklama:Bir coroutine içinde senkron, CPU-bound bir fonksiyonu doğrudan çağırmak (ne await ne de bir thread/process'e devretme yok), tek event loop thread'ini her çağrının tüm süresi boyunca bloklar; 10 request birbiri ardına hiç çakışma olmadan çalıştığında toplam süre kabaca 10 x 150ms = 1500ms olur. (a) asyncio.gather'ın tek başına bloklayan senkron çağrılar için sağlamadığı, çekirdekler arası gerçek paralel yürütmeyi anlatıyor. (c) var olmayan otomatik iş bölüşümü uyduruyor. (d) yanlıştır — bir coroutine'den sıradan bir senkron fonksiyon çağırmak sorunsuz çalışır, sadece bloklayıcıdır.