Yapay zeka görsel pipeline'ının sıkıcı yarısı
3 dk okuma
QuickStaging boş bir odanın fotoğrafını alıp aynı odayı döşenmiş haliyle geri veriyor. Görsel modelini çağırmak tek satır. Bu yazı geri kalan satırlar hakkında. Model fotoğrafı görmeden önce, model çalışırken ve bir şeyi yanlış yaptığında neler olduğunu anlatacağım.
Modelden önce
Emlak fotoğrafları telefondan, fotoğraf makinesinden ve başka araçlardan geliyor, hepsi de garip dosyalar yolluyor. iPhone'lardan HEIC geliyor, düzenleme programlarından AVIF. Kat planları PDF olarak, PNG'ler arka planı şeffaf olarak geliyor. Bir de dik görünen ama yan kaydedilmiş, EXIF'inde "beni döndür" diye bir bayrak taşıyan fotoğraflar var.
Tarayıcı, yüklemeden önce dönüştürebildiğini dönüştürüyor. Sunucu da her görseli dik çeviriyor, yeniden encode ediyor ve boyutuna bakıyor. Bu işlerin değeri ancak biri eksik kalınca anlaşılıyor. Bir keresinde bir AVIF dosyası bütün kontrollerden geçti, sonra ileride sessizce patladı ve ilan sıfır fotoğrafla kaldı. O yüzden artık alışılmadık formatları her şeyden önce istemci tarafında dönüştürüyorum.
Bu katmanda en sevdiğim bug bir görsel kütüphanesinden çıktı. Kalite kontrollerinden biri sonuç üzerinde kenar tespiti yapıyor. Alfa kanalı olan görsellerde kenar haritası tamamen sıfır dönüyordu, çünkü kanal ancak pipeline'ın en sonunda, konvolüsyondan sonra atılıyordu. Yani kontrol, şeffaflığı olan her görseli geçiriyordu. Düzeltmesi doğru yere koyduğum tek bir çağrıydı. Şeffaflığı olmayan 238 görselde hiçbir şeyi değiştirmediğini de kontrol ettim.
Model çalışırken
Üretim birkaç saniye de sürebiliyor, birkaç dakika da. Bir HTTP isteğini bu kadar açık tutmak olmaz. O yüzden her iş asenkron. API kredileri ayırıyor, işi başlatıp hemen dönüyor, iş bitince de sağlayıcı bir webhook çağırıyor. Sayfa sağlayıcıyı değil, veritabanını poll ediyor.
Asenkron işler senkron çağrılardan daha fazla yoldan patlayabiliyor. O yüzden hataları, karşılığında ne yapmam gerektiğine göre ayırdım.
- Kalıcı bir hatada (bozuk girdi, reddedilen istek) işi durdurup krediyi iade ediyorum.
- Throttle ya da geçici bir hata varsa aynı istek içinde backoff ve jitter ile yeniden deniyorum, ama route'un süre sınırını asla aşmadan.
- Kapasite yoksa, yani sağlayıcı meşgulse, iş neredeyse iki saat boyunca yeniden deneyen bir kuyruğa giriyor. Kredi de iş sonuçlanana ya da iade edilene kadar ayrılmış kalıyor.
İkinci bir sağlayıcı olduğunda fallback, webhook'unu birincisiyle birebir aynı şekilde imzalıyor. Tamamlama kodu işi hangi sağlayıcının yaptığını ayırt edemiyor, buna gerek de yok.
Webhook hiç gelmezse
Webhook'lar kaybolur. Deploy sunucuyu yeniden başlatır, istek zaman aşımına uğrar, ağ birini yolda düşürür. İlk zamanlarda bir webhook kaybolunca, parası ödenmiş bir render kimse görmeden öylece kalıyordu.
Bunu iki şeyle çözdüm. Zamanlanmış bir iş, 15 dakikadır "processing" durumunda takılı kalan her şeyi buluyor. Bir şeyi iade etmeden önce sağlayıcıya gerçekte ne olduğunu soruyor ve işi, webhook gelseydi hangi kod çalışacaksa onunla kapatıyor. Sadece sağlayıcıda hiç kaydı olmayan işleri kayıp sayıyor. Ayrıca kredilere dokunmadan önce her kaydı korumalı bir update ile kendine alıyor. Böylece iki çalıştırma aynı görseli iki kez iade edemiyor.
Süreyi kaydın oluşturulduğu andan değil, işlemin başladığı andan itibaren sayıyorum. Bir ara oluşturulma tarihini kullandım ve zamanlanmış iş, eski görsellerin gayet düzgün ilerleyen yeniden üretimlerini biçip götürdü.
Çıktıyı kontrol etmek
Görsel modelleri yanlış yaparken bile kendinden emin. Sonuç fotoğraf gibi görünüyor ama bir pencerenin yeri değişmiş ya da bir kapı boşluğu ortadan kalkmış oluyor. O yüzden her sonuç iki kontrolden geçiyor. Önce ucuz piksel ölçümlerine bakıyorum, sonra bir vision modeli öncesiyle sonrasını karşılaştırıyor.
Burada önemli olan birkaç kural var.
- Piksel kontrolü tek başına bir sonucu en fazla "şüpheli" diye işaretleyebiliyor. Sonucu geri tutmak için vision modelinin de onaylaması gerekiyor.
- Reddedilen sonuç sınırlı sayıda ücretsiz olarak yeniden üretiliyor. Yine geçemezse kullanıcı iadesini alıyor.
- Kontrol katmanı fail open çalışıyor, çünkü kontrolcü bozuldu diye bütün kullanıcıların işi durmamalı. Her karar ayrıca neden geçtiğini de kaydediyor. Bunu, bir kullanım limiti hatası bir süre boyunca sessizce "geçti" diye loglandıktan sonra ekledim.
Kredilerin bütün bunlar sırasında nasıl davrandığı ayrı bir konu, onu ayrı bir yazıda anlattım.
Neleri sildim
Pipeline en çok küçüldükçe iyileşti. Büyük bir prompt builder, kör karşılaştırmada çok daha kısa bir şeye kaybetti. Odayı korusun diye yazdığım "structure lock" adımı, aynı oda setinde düz bir yeniden denemeden daha kötü sonuç verdi. Fotoğrafa "look" veren bir adım hâlâ kodda duruyor ama onu çağıran hiçbir yer yok.
Bunların her biri, yazarken ilerleme gibi gelmişti. Onları söküp atan şey ölçmek oldu. Artık yeni bir adım ancak bir test setini geçerse yayına giriyor.