Açılımı Basic Linear Algebra Subprograms, yani Temel Lineer Cebir Altprogramları diyebiliriz. Bununla ilgili detaylara biraz sonra değineceğim, öncelikle bu konseptin ne olduğunu ve nerede karşımıza çıktığına bakalım.
Bir kez bile olsa Python gibi bir dilde matematik işlemleri yapmışsanız NumPy kütüphanesini duymuş veya kullanmışsınızdır. Örnek olarak bu kütüphanenin belirli bir fonksiyonu üzerinden ilerleyeceğim.
Importlarla başlayalım:
Arka arkaya 1 milyon tane çarpma işlemi yaptığımızı düşünelim. Öncelikle akla ilk gelen yöntemle yapacak olursak:
Bu kod 1'den 1 milyona kadar olan tüm sayıları birbirine çarpacaktır. Bunun ne kadar sürdüğünü de bilsek iyi olur tabii. Bu durumda kodu şu şekilde günceleyip çalıştırıyoruz:
Ben böyle bir çıktı aldım:
Şimdi bir de demin bahsini açtığım fonksiyonu kullanalım, ki kendisi
Aldığım çıktı:
Aradaki fark inanılmaz! Devam edelim. Bu bir scaler çarpımıydı. Şimdi daha zorlayıcı bir şey yapalım, iki vektörün çarpımına bakalım. Bunun için öncelikle iki deneyde de farklı sayılarla işlem yapmamış olmak için baştan bir vektör tanımlıyoruz:
0'dan 10'a değişiklik gösteren 100 milyon integer barındıran bir vektör var elimizde şu an. Şimdi ikinci vektörü oluşturup ikisinin çarpımını akla gelen ilk yöntemle hesaplayıp süresini ölçelim:
Aldığım çıktı:
Şimdi bir de
Çıktı:
Arada yine inanılmaz bir fark var! Peki ama neden? Bunu anlamak için NumPy dokümantasyonuna bakmamız yeterli:
numpy.org
Yani diyor ki, bu fonksiyon mümkün oldukça optimize edilmiş bir BLAS kütüphanesi kullanıyor. Bunun yanında bir de referans link vermiş, bakıyoruz.
Çevirecek olursak:
Mesela OpenBLAS adından ve linkten de anlayabileceğiniz üzere açık kaynak kodlu bir kütüphane. Kütüphaneyi incelerseniz Fortran gibi diller kullanılmış olduğunu görürsünüz. Mesela biz demin dot product işlemi yapmıştık, bunun Fortran kodlarından birisi de ddot.f olarak görebileceğiniz bu dosyadır:
github.com
1979'da yazılmış bir Fortran kütüphanesine dayanmaktadır. Bu kütüphane bir referans uygulaması olup sanılanın aksine şu anda bahsettiğimiz BLAS kütüphanesi olmamakla beraber hız için optimize edilmiş değildir ve kamu malına aittir. Lineer cebir fonksiyonları sunan çoğu kütüphane BLAS arayüzüne uyumludur, bu sayede geliştiriciler kullanılan BLAS kütüphanesinden bağımsız olarak yazılım geliştirebilmektedir.
BLAS fonksiyonların karmaşıklıklarına göre 3 seviyeye ayrılır. Bunlar:
Bununla beraber aşağıdaki lineer eşitlikte üçgensel bir T için x'i bulacak bir fonksiyon da barındırmaktadır.
Bu seviyenin tasarımı 1984'te başlayıp sonuçları 1988'de yayımlanmıştır. Bu seviyedeki fonksiyonlar özellikle Level 1'in yetersiz kaldığı, vector işlemciler kullanan yazılımlarda performansı artırmak için geliştirilmiştir.
Burada A ve B transposed veya hermitian-conjugated gibi farklı türlerde matrixler olabilir. Standart matrix çarpımı da a'yı 1'e, C'yi de gerekli boyutta 0-matrixe eşitleyerek yapılabilir. Ayrıca T'nin yine üçgensel bir matrix olduğu şekildeki gibi fonksiyonları da barındırmaktadır:
Bu bahsettiğim
Diğer BLAS uygulamalarına buradan erişip inceleyebilirsiniz:
en.wikipedia.org
Bir kez bile olsa Python gibi bir dilde matematik işlemleri yapmışsanız NumPy kütüphanesini duymuş veya kullanmışsınızdır. Örnek olarak bu kütüphanenin belirli bir fonksiyonu üzerinden ilerleyeceğim.
Importlarla başlayalım:
Python:
import numpy as np
import time
Arka arkaya 1 milyon tane çarpma işlemi yaptığımızı düşünelim. Öncelikle akla ilk gelen yöntemle yapacak olursak:
Python:
c = 1
for i in range(1, 1_000_000+1):
c *= i
Bu kod 1'den 1 milyona kadar olan tüm sayıları birbirine çarpacaktır. Bunun ne kadar sürdüğünü de bilsek iyi olur tabii. Bu durumda kodu şu şekilde günceleyip çalıştırıyoruz:
Python:
c = 1
st = time.time()
for i in range(1, 1_000_000+1):
c *= i
et = time.time()
print(f"Tüm işlem {(et - st):.2f} saniye sürdü.")
Ben böyle bir çıktı aldım:
Kod:
Tüm işlem 493.20 saniye sürdü.
Şimdi bir de demin bahsini açtığım fonksiyonu kullanalım, ki kendisi
np.dot olur:
Python:
c = 1
st = time.time()
for i in range(1, 1_000_000+1):
c = np.dot(c, i)
et = time.time()
print(f"Tüm işlem {(et - st):.2f} saniye sürdü.")
Aldığım çıktı:
Kod:
Tüm işlem 1.42 saniye sürdü.
Aradaki fark inanılmaz! Devam edelim. Bu bir scaler çarpımıydı. Şimdi daha zorlayıcı bir şey yapalım, iki vektörün çarpımına bakalım. Bunun için öncelikle iki deneyde de farklı sayılarla işlem yapmamış olmak için baştan bir vektör tanımlıyoruz:
Python:
v = np.random.randint(low=0, high=11, size=10**8)
0'dan 10'a değişiklik gösteren 100 milyon integer barındıran bir vektör var elimizde şu an. Şimdi ikinci vektörü oluşturup ikisinin çarpımını akla gelen ilk yöntemle hesaplayıp süresini ölçelim:
Python:
# np.ones ile aynı boyutta ve sadece 1'lerden oluşan bir vektör oluşturuyoruz
w = np.ones(v.size)
sum = 0
st = time.time()
for i in range(len(v)):
sum += v[i] * w[i]
et = time.time()
print(f"Tüm işlem {(et - st):.2f} saniye sürdü.")
Aldığım çıktı:
Kod:
Tüm işlem 33.18 saniye sürdü.
Şimdi bir de
np.dot ile deneyelim, kod diğerine nazaran daha basit görünüyor:
Python:
# np.ones ile aynı boyutta ve sadece 1'lerden oluşan bir vektör oluşturuyoruz
w = np.ones(v.size)
sum = 0
st = time.time()
np.dot(v, w)
et = time.time()
print(f"Tüm işlem {(et - st):.2f} saniye sürdü.")
Çıktı:
Kod:
Tüm işlem 0.33 saniye sürdü.
Arada yine inanılmaz bir fark var! Peki ama neden? Bunu anlamak için NumPy dokümantasyonuna bakmamız yeterli:
numpy.dot — NumPy v1.25 Manual
It uses an optimized BLAS library when possible (see numpy.linalg).
Yani diyor ki, bu fonksiyon mümkün oldukça optimize edilmiş bir BLAS kütüphanesi kullanıyor. Bunun yanında bir de referans link vermiş, bakıyoruz.
The NumPy linear algebra functions rely on BLAS and LAPACK to provide efficient low level implementations of standard linear algebra algorithms. Those libraries may be provided by NumPy itself using C versions of a subset of their reference implementations but, when possible, highly optimized libraries that take advantage of specialized processor functionality are preferred. Examples of such libraries are OpenBLAS, MKL (TM), and ATLAS...
Çevirecek olursak:
NumPy lineer cebir fonksiyonları, standart lineer cebir algoritmalarının verimli birer düşük seviye uygulamalarını sağlamak için BLAS ve LAPACK (Linear Algebra PACKage) kütüphanelerine dayanmaktadır. Bu kütüphaneler NumPy tarafından C versiyonlarınca da sağlanabilir ancak mümkün oldukça özelleştirilmiş işlemci fonksiyonlarını kullanan ve iyi optimize edilmiş kütüphaneler tercih edilmektedir. Bu kütüphanelere örnek olarak OpenBLAS, MKL (Intel Math Kernel Library) ve ATLAS (Automatically Tuned Linear Algebra Software) verilebilir.
Mesela OpenBLAS adından ve linkten de anlayabileceğiniz üzere açık kaynak kodlu bir kütüphane. Kütüphaneyi incelerseniz Fortran gibi diller kullanılmış olduğunu görürsünüz. Mesela biz demin dot product işlemi yapmıştık, bunun Fortran kodlarından birisi de ddot.f olarak görebileceğiniz bu dosyadır:
OpenBLAS/lapack-netlib/BLAS/SRC/ddot.f at develop · OpenMathLib/OpenBLAS
OpenBLAS is an optimized BLAS library based on GotoBLAS2 1.13 BSD version. - OpenMathLib/OpenBLAS
Peki, nedir BLAS?
Açılımını daha önceden de yapmış olduğum Basic Linear Algebra Subprograms; vektör toplamı, skaler çarpımı, matrix çarpımı gibi işlemleri barındıran bir teknik standarttır. BLAS bir standardı belirtiyor olsa da farklı uygulamaları belirli ortamlarda hızlı çalışacak şekilde optimize edildiği için arkadaki örneklerde de görmüş olduğumuz gibi performansta epey bir iyileşme sağlayabilir. Bunu sadece yazıldığı dilin halihazırda performanslı olmasına değil, vector register ve SIMD gibi özel floating point donanımlarına borçludur. Donanım seviyesinde bir uygulamadan bahsediyoruz yani, bunun getireceği avantaj kaçınılmaz.1979'da yazılmış bir Fortran kütüphanesine dayanmaktadır. Bu kütüphane bir referans uygulaması olup sanılanın aksine şu anda bahsettiğimiz BLAS kütüphanesi olmamakla beraber hız için optimize edilmiş değildir ve kamu malına aittir. Lineer cebir fonksiyonları sunan çoğu kütüphane BLAS arayüzüne uyumludur, bu sayede geliştiriciler kullanılan BLAS kütüphanesinden bağımsız olarak yazılım geliştirebilmektedir.
BLAS fonksiyonların karmaşıklıklarına göre 3 seviyeye ayrılır. Bunlar:
- Level 1: Linear time.
- Level 2: Quadratic time.
- Level 3: Cubic time.
Level 1
Bu seviye 1979'daki BLAS uygulamasındaki genelleştirilmiş vektör toplamı gibi fonksiyonları barındırmaktadır. Bu fonksiyon aşağıdaki şekilde tanımlanmış olupaxpy (a x plus y) şeklinde isimlendiriliyor.Level 2
Bu seviye genelleştirilmiş matrix-vector çarpımı (gemv (generalized matrix-vector multiplication)) gibi matrix-vector işlemleri barındırmaktadır.Bununla beraber aşağıdaki lineer eşitlikte üçgensel bir T için x'i bulacak bir fonksiyon da barındırmaktadır.
Bu seviyenin tasarımı 1984'te başlayıp sonuçları 1988'de yayımlanmıştır. Bu seviyedeki fonksiyonlar özellikle Level 1'in yetersiz kaldığı, vector işlemciler kullanan yazılımlarda performansı artırmak için geliştirilmiştir.
Level 3
Bu seviye resmi olarak 1990'da yayımlanmış olup genel matrix çarpımı (gemm (generalized matrix multiplication)) gibi matrix-matrix işlemlerini barındırmaktadır.Burada A ve B transposed veya hermitian-conjugated gibi farklı türlerde matrixler olabilir. Standart matrix çarpımı da a'yı 1'e, C'yi de gerekli boyutta 0-matrixe eşitleyerek yapılabilir. Ayrıca T'nin yine üçgensel bir matrix olduğu şekildeki gibi fonksiyonları da barındırmaktadır:
Bu bahsettiğim
gemm fonksiyonu optimizasyon için hedef haline gelmiş bir fonksiyondur. Çünkü matrix çarpımının pek çok farklı uygulaması var ve pek çok alanda kullabılabildiği için en çok optimize edilmeye çalışılan fonksiyonlardan birisidir. Matrix çarpımı algoritmalarıyla ilgilenmiş olanınız varsa Strassen algoritmasını duymuşsunuzdur. Bu seviyede de gemm3m adında bu fonksiyona benzer bir uygulama kullanılmakta.Diğer BLAS uygulamalarına buradan erişip inceleyebilirsiniz: