MATLAB’den CUDA Kodu Üretimi: Gömülü Görüntü İşleme ve Derin Öğrenme Algoritmalarının GPU’lar Üzerinde Hızlandırılması

Doğukan Mehmet Kılınç
MATLAB ile GPU Kod Oluşturmayı Tanıtıyoruz
GPU hesaplama, verilerin devasa ölçekte paralel hesaplanmasına olanak tanıdığı için derin öğrenme uygulamalarının büyümesine katkıda bulunmaktadır. GPU hesaplama, algoritmalarınızdaki veri paralelliğinden (data parallelism) yararlanarak uygulamalarınızı hızlandırır. Bu durum; derin öğrenmenin yanı sıra gömülü görüntü işleme ve radar gibi alanlardaki hesaplama açısından yoğun algoritmalar için performans hedeflerine ulaşılmasında kilit rol oynar.
GPU ile hızlandırılmış hesaplama, heterojen bir programlama modelini takip eder; uygulamanızın paralelleştirilebilen kısımları, GPU üzerindeki yüzlerce veya binlerce paralel çekirdekte eşzamanlı olarak yürütülen çekirdeklere (kernels) atanırken, kodun ardışık (sıralı) kısımları CPU üzerinde çalışır.
GPU Coder™, NVIDIA® GPU’larda yürütülmek üzere MATLAB® kodundan otomatik olarak CUDA® kodu üreterek mevcut MATLAB algoritmalarınızı GPU’lar üzerinde hızlandırır. Üretilen CUDA kodu, CPU ve GPU arasındaki veri transferi yükünü (overhead) en aza indirirken, paralellik için optimize edilir. Kod üretimi sırasında GPU Coder, CPU ve GPU bölümleri (partitions) arasındaki veri bağımlılığını (data dependency) analiz eder. Bu analiz, verilerin CPU ve GPU arasında kopyalanması gereken minimum konum setini belirler.
CPU ve GPU arasındaki bellek bant genişliği darboğazı (memory bandwidth bottleneck), algoritmaların GPU’larda performans kaybı yaşamasının temel nedenidir; veri transferini en aza indirme yeteneği, önemli performans kazanımları sağlar. Otomatik bir iş akışı, CUDA’da elle kod yazmaktan kaynaklanabilecek zorlukların ve artan hataların önüne geçer.
GPU Coder; masaüstü, bulut veya NVIDIA Jetson® ve NVIDIA Drive® platformlarını içeren gömülü cihazlar üzerinde çalışan GPU’larda algoritmalarınızı hızlandırır. Şekil 1 ve 2 bu performans iyileştirmelerini vurgulamaktadır.
Uygulamalar; SURF öznitelik çıkarımı (feature extraction), stereo uyumsuzluk (stereo disparity) ve sis giderme (fog removal) gibi yaygın görüntü işleme operasyonlarının yanı sıra FFT (Hızlı Fourier Dönüşümü) gibi yaygın sinyal işleme operasyonlarında iki kata kadar (100 kata kadar/iki büyüklük mertebesi) performans artışı sağlayabilir.

| Testing Platform | MATLAB 2018a |
| CPU | Intel Xeon CPU E5-1650 v3 @ 3.50 GHz |
| GPU | NVIDIA Pascal TITAN V (Volta architecture) |
| CUDA | Version 9.0 |
Şekil 1. GPU’larda çalışan GPU Coder tarafından oluşturulan CUDA kodu ile CPU’larda çalışan C kodu arasında yaygın görüntü işleme ve sinyal işleme performans karşılaştırmaları.
Şekil 2, GPU Coder ile üretilen CUDA kodunun çıkarım (inference) performansını diğer popüler derin öğrenme çerçeveleriyle (frameworks) kıyaslamaktadır. GPU Coder tarafından üretilen kod, TensorFlow™‘dan beş kat, Apache MXNet‘ten ise iki kat daha hızlı çalışmaktadır.

| CPU | Intel Xeon CPU E5-1650 v4 @ 3.60 GHz |
| GPU | NVIDIA Pascal TITAN Xp |
| cuDNN | v7 |
Şekil 2. Derin öğrenme performans karşılaştırmaları: NVIDIA Titan® Xp üzerinde çalışan GPU Coder, TensorFlow ve MXNet’in AlexNet çıkarım performansı karşılaştırması.
Tipik bir derin öğrenme algoritmasının; çıkarım (inference) için bir veya daha fazla eğitilmiş ağı çağıran bir kullanıcı mantığından (user logic) oluştuğunu unutmayın. GPU Coder ile, herhangi bir manuel kodlamaya gerek duymadan tüm uygulamanın kodunu otomatik olarak üretebilirsiniz.
Örneğin, çok basit bir görüntü sınıflandırma görevinde kullanıcı mantığı; görüntünün yeniden boyutlandırılması ve renk uzayının değiştirilmesi gibi ön işleme (pre-processing) adımlarını ve sınırlayıcı kutu (bounding box) çizilmesi gibi son işleme (post-processing) adımlarını içerebilir. GPU Coder, uygulamanın tamamından kod üretir (Şekil 3).

Şekil 3. GPU Coder kullanarak uçtan uca uygulamanın tamamı için kod oluşturma.
Üst Düzey İş Akışı
Şekil 4, algoritma tasarımından kod üretimine ve ardından dağıtıma kadar uzanan tipik iş akışını göstermektedir. Sürece, MATLAB kodunu kod üretimi için hazırlayarak başlarsınız; ardından üretilen kodun orijinal MATLAB koduyla işlevsel olarak aynı şekilde çalıştığından emin olmak için test aşamasına geçersiniz.
Üçüncü adımda kodu; ya bir MEX dosyası olarak (hızlandırma amacıyla orijinal MATLAB algoritmasının yerine geçmesi için) ya da daha büyük bir CUDA projesine entegre edilebilecek bir statik/dinamik kütüphane veya kaynak kod biçimindeki CUDA kodu olarak üretebilirsiniz. İsteğe bağlı olan dördüncü adım ise, üretilen kodun performansını artırmak için MATLAB kodunu daha fazla optimize etmektir.

Şekil 4. MATLAB kodundan CUDA kodu oluşturmak için iş akışı.
Trafik İşareti Algılama Uygulaması
Bu bölüm; derin öğrenme tabanlı bir trafik işareti tespiti örneğini kullanarak, kod üretimi iş akışını daha ayrıntılı bir şekilde incelemektedir. Trafik işareti tespiti ve tanıma işlemini gerçekleştirmek için gereken üç temel adım; tespit (detection), maksimum olmayan bastırma (non-maximal suppression- NMS) ve tanımadır (recognition). Aşağıdaki işlemleri yapan bir MATLAB algoritması ile başlayabilirsiniz:
- Bir test görüntüsünü okumak ve ön işlemeden geçirmek,
- Trafik işaretini tespit etmek için nesne tespit ağının (object detection network) tahmin (prediction) yöntemini çağırmak,
- NMS algoritmasını kullanarak üst üste binen tespitleri bastırmak,
- İşareti tanımlamak için tanıma ağının (recognition network) tahmin yöntemini çağırmak,
- Sınırlayıcı kutular (bounding boxes) ve bunlara karşılık gelen tespit etiketlerini içeren bir çıktı görüntüsü görüntülemek.
Bu algoritma için CUDA kodu üretmek amacıyla, işe bu MATLAB betiğini (script) kod üretimine hazırlayarak başlayabilirsiniz.
GPU Kod Oluşturma için MATLAB Kodunu Hazırlama
Bir MATLAB algoritmasını kod üretimine hazırlamak için şu adımları izlersiniz:
- Algoritmayı test düzeneğinden (test bench) ayırın. İşe algoritmayı test düzeneğinden ayırarak ve algoritma kodunu bir fonksiyon haline getirerek başlayın. Ayrıca algoritmanızı; derin öğrenme ağı çıkarım fonksiyonu ile ön işleme ve son işleme fonksiyonları olarak kısımlara ayırmak iyi bir uygulamadır.
- Algoritmanın hesaplama açısından yoğun kısımlarını belirleyin. Algoritmadaki darboğazları (hotspots) belirlemek ve profillemek için MATLAB Profiler’ı kullanın. Bir sonraki adımda kolaylık sağlaması için bu kritik noktaları ayrı fonksiyonlar haline getirin.
- GPU Coder’ı, darboğazları otomatik olarak CUDA çekirdeklerine (kernels) eşlemesi için yönlendirin. Darboğaz fonksiyonlarının başına coder.gpu.kernelfun pragmalarını (komutlarını) yerleştirin. Bu, GPU Coder’ın bu hesaplama açısından maliyetli fonksiyonları analiz etmesini ve onlardan en verimli CUDA çekirdeklerini oluşturmasını sağlar.
- Algoritmanın kod üretimi ile uyumlu olduğundan emin olun. Bu süreci yürütmek için GPU Coder arayüzünü kullanın. GPU Coder, desteklenmeyen fonksiyonlar ve yapılar dahil olmak üzere kod üretimi uyumluluk sorunlarını tespit etmek için bir “kod üretimi hazırlık aracı” çalıştırarak başlar:
- Sınırlandırılmamış değişken boyutlu veriler: GPU Coder, sınırları belirlenmiş (bounded) değişkenlerden kod üretir. Eğer kod üreticisi bir dizinin boyutunu belirleyemezse veya boyutun değiştiğini saptarsa, o boyut “değişken boyutlu” (variable-size) olarak kabul edilir. GPU Coder, sınırlandırılmamış değişkenler veya diziler için uyarı verir. Bu durumda, kod üreticisinin belleği statik olarak ayırabilmesi için bir üst sınır (upper bound) belirtmeniz gerekir:
- Giriş değişkenleri için üst sınır belirtme: Değişken boyutlu girişlerin üst sınırlarını belirtmek için coder.typeof yapısını kullanın.
- Yerel değişkenler için üst sınır belirtme: Değişken boyutlu dizilerin boyutlarını belirleyen değişkenlerin değerlerini kısıtlamak için ilişkisel operatörlerle birlikte assert fonksiyonunu kullanın veya bir fonksiyondaki yerel değişkenin tüm örnekleri için üst sınırları belirtmek üzere coder.varsize fonksiyonunu kullanın.
- Sınırlandırılmamış değişken boyutlu veriler: GPU Coder, sınırları belirlenmiş (bounded) değişkenlerden kod üretir. Eğer kod üreticisi bir dizinin boyutunu belirleyemezse veya boyutun değiştiğini saptarsa, o boyut “değişken boyutlu” (variable-size) olarak kabul edilir. GPU Coder, sınırlandırılmamış değişkenler veya diziler için uyarı verir. Bu durumda, kod üreticisinin belleği statik olarak ayırabilmesi için bir üst sınır (upper bound) belirtmeniz gerekir:
- Potansiyel çalışma zamanı (run-time) hatalarını kontrol edin. Kod üretimi uyumluluğunu kontrol ettikten sonra, çalışma zamanı hatalarını tespit edip düzeltmek için çalışma zamanı kontrolleri yapmanızı öneririz. Bellek bütünlüğünü doğrulamak, dizi sınırlarını kontrol etmek ve boyut kontrolü yapmak için CPU çalışma zamanı kontrollerini çalıştırın. Kayıtçı taşmalarını (register spills) belirlemek ve yığın boyutu (stack size) uyumluluğunu doğrulamak için GPU çalışma zamanı kontrollerini çalıştırın.
Trafik İşareti Algılama MATLAB Komut Dosyasını Kod Oluşturma için Hazırlama
Yukarıdaki adımları izleyerek; ilk aşamada tsdr adını verdiğimiz algoritmik kod, tsdr_test.m adlı test düzeneği betiğinden ayrılır. Fonksiyonu MATLAB üzerinde profillediğinizde (profiling), fonksiyon içinde izole edilecek özel bir darboğaz (hotspot) olmadığını görürsünüz. Bu durum, coder.gpu.kernelfun pragmasını tsdr fonksiyonuna yerleştirerek fonksiyonun tamamını GPU’ya eşleyebileceğiniz anlamına gelir.
Üretilen MEX Dosyasının Test Edilmesi
Kod artık kod üretimi için hazır olduğuna göre, ilk yapmanız gereken şey; üretilen MEX dosyasını MATLAB içerisinde test ederek orijinal kodla işlevsel olarak aynı şekilde çalıştığından emin olmaktır. Bunun için bir MEX fonksiyonu oluşturursunuz. MEX; derlenmiş ikili dosyayı (compiled binary) MATLAB içinde çağırmanıza ve orijinal fonksiyonun yerine kullanmanıza olanak tanıyan bir “sarmalayıcı” (wrapper) arayüzüdür.
GPU Coder uygulamasında, CUDA kodu üretmek için derleme türü (build type) olarak MEX‘i seçin ve bunu NVIDIA’nın nvcc derleyicisini kullanarak bir MEX fonksiyonuna dönüştürün. Kod doğrulama adımında, test düzeneğinizden (test bench) bu MEX fonksiyonunu çağırarak, üretilen kodun orijinal MATLAB fonksiyonuyla aynı işlevselliği sağladığını teyit edebilirsiniz.
Bu örnek uygulamada; üretilen kodun davranışını doğrulamak için MEX hedefi üretir ve doğrulama adımında testi MEX versiyonu ile çalıştırırsınız.
GPU Coder’ı Embedded Coder® ile birlikte kullanarak; üretilen bağımsız (standalone) C++ kodunun nümerik davranışını, gömülü GPU’nuza dağıtıldığı haliyle doğrulamak için Yazılım Döngüde (SIL – Software-in-the-Loop) yürütme yöntemini kullanabilir ve böylece kodu daha ileri düzeyde test edebilirsiniz. Bu yöntem, MEX dosyası ile seri üretim kalitesindeki bağımsız (standalone) kod arasındaki her türlü farkı tespit etmenize olanak tanır.
MEX veya Kaynak Kod Olarak Kod Üretimi: Masaüstü, Bulut veya Gömülü GPU’larda Dağıtım
Üretilen MEX fonksiyonunun beklendiği gibi çalıştığını doğruladıktan sonra, sadece “derleme türü” (build type) seçeneğini değiştirerek bağımsız (standalone) C++ kodu veya bir statik kütüphane üretebilirsiniz. Ardından, üretilen kütüphaneyi geliştirme ortamınızdaki daha büyük bir uygulamaya entegre edebilir ve algoritmanızı masaüstüne veya buluta dağıtabilirsiniz.
Amacınız NVIDIA Jetson veya Drive gibi gömülü platformlara dağıtım yapmaksa, CUDA kaynak kodunu hedef cihaza aktarabilir ve derleme işlemini doğrudan cihaz üzerinde yapabilirsiniz. Jetson TX2, TX1 veya TK1 gibi NVIDIA Tegra® geliştirme kartları için bir diğer seçenek ise; üretilen kodu ana bilgisayarınızda (host machine) çapraz derlemek (cross-compile) ve ardından çalıştırılabilir dosyayı (executable) hedef cihaza kopyalayarak dağıtmaktır.
MEX derleme türü ile kod üretmenin önemli bir kullanım alanı da; yorumlanması gereken yerel MATLAB fonksiyonu yerine, GPU üzerinde çalışan derlenmiş ikili dosyayı (binary) çağırarak MATLAB içindeki hesaplama hızını artırmaktır.
Kod Üretimi ve Trafik İşareti Tespit Uygulamasının Dağıtımı
Trafik işareti tespiti örnek uygulamasında hedef, algoritmayı masaüstüne dağıtmaktır. Bu durumda, derleme türünü (build type) kaynak kod (source code) olarak seçer ve bir ana dosyaya (main file) entegre edilip çalıştırılabilir (executable) bir dosyaya dönüştürülebilen bağımsız C++ kodu üretirsiniz. Hatta bir adım daha ileri giderek, çapraz derlenebilen (cross-compiled) ve bir Jetson kartına dağıtılabilen bir statik kütüphane üretebilirsiniz.
Kodunuzu Daha Fazla Optimize Etmek İçin İpuçları
Eğer üretilen kod performans gereksinimlerinizi karşılamıyorsa, kodun performansını artırmak için ek adımlar ve optimizasyonlar uygulayabilirsiniz:
- MEX Profil Oluşturma (Profile MEX) Kullanımı: Üretilen kodun performansındaki darboğazları belirlemek için MATLAB Profiler’ı kullanarak üretilen MEX fonksiyonunun yürütme sürelerini analiz edebilirsiniz. Üretilen kodun profili, ilgili MATLAB fonksiyonunun her bir satırı için çağrı sayısını ve harcanan süreyi gösterir. Belirli darboğazlar, MATLAB algoritmanızda birkaç küçük değişiklik yaparak veya kod üretimi seçeneklerini ayarlayarak giderilebilir; aşağıda birkaç teknik listelenmiştir.
- coder.gpu.kernel Pragmasını Kullanma: Algoritmanızdaki bazı paralel döngüler üretilen kodun bazı kısımlarında paralelleştirilmemişse, GPU Coder’ı o döngü için bir “kernel” (çekirdek) üretmeye zorlamak amacıyla döngünün hemen önüne coder.gpu.kernel pragmasını yerleştirebilirsiniz. Bunun ileri düzey bir işlem olduğunu ve dikkatli kullanılması gerektiğini unutmayın.
- Tasarım Şablonlarını (Design Patterns) Kullanma: Belirli bir giriş elemanının, birden fazla komşu çıktı elemanını hesaplamak için tekrar tekrar erişildiği algoritmalar için; hesaplama verimliliğini artırmak amacıyla şablon işlemleri (gpucoder.stencilKernel) veya matris-matris işlemleri (gpucoder.matrixMatrixKernel) gibi tasarım şablonlarını kullanabilirsiniz. Bu tasarım şablonları, performansı artırmak için GPU’nun paylaşımlı belleğinden (shared memory) yararlanır. Detaylar için tasarım şablonu dokümantasyonuna bakınız.
- Özel CUDA Kodu Ekleme: Üretilen kodunuza dahil etmek istediğiniz, belirli alt fonksiyonlar için halihazırda elinizde bulunan yüksek derecede optimize edilmiş CUDA kodlarınız varsa; GPU Coder, bu özel kodları üretilen kodla entegre etmenize yardımcı olmak için coder.ceval işlevselliğini genişletir. Ayrıntılar için eski kod entegrasyonu (legacy code integration) dokümantasyonuna bakınız.
- Kod Üretimi Seçeneklerini Özelleştirme: Kod üretimini aşağıdaki gibi seçeneklerle özel gereksinimlerinize göre daha fazla ayarlamak için kod üretimi yapılandırmasını (configuration) özelleştirebilirsiniz:
- Bellek Atama Modu Ayarı: Ayrık (discrete) ve birleşik (unified) bellek atama modları arasında seçim yapma.
- CUDA Kütüphaneleri Desteği: cuBLAS ve cuSOLVER gibi optimize edilmiş CUDA kütüphaneleri için desteği etkinleştirme.
- Hedefin Hesaplama Kapasitesini (Compute Capability) Belirtme.
- GPU Derleyicisine Ek Bayraklar (Flags) Gönderme.
Sonraki Adımlar
- Keşfedin: MATLAB’den GPU Kodu Üretimi (Eğitimler ve dökümantasyonları inceleyin).
- İndirin: GPU Coder Deneme Sürümü.