В тази статия ще разгледаме как да постигнем високопроизводителни изчисления с библиотеката NumPy, използвайки силата на BLAS (Basic Linear Algebra Subprograms). Ще обясним какво представлява BLAS, как NumPy се интегрира с него и ще демонстрираме на практика ефекта от оптимизацията върху някои често срещани операции от линейната алгебра. Накрая ще дадем съвети как сами да конфигурирате NumPy да използва оптимална имплементация на BLAS.
Какво е BLAS?
BLAS е спецификация на набор от нискониво рутини за извършване на основни операции от линейната алгебра, като векторно-векторни, матрично-векторни и матрично-матрични операции. Тези рутини са оптимизирани за максимална производителност на различни хардуерни архитектури и са в основата на много софтуерни библиотеки за научни изчисления и машинно обучение.
Има различни имплементации на BLAS, всяка оптимизирана за конкретна платформа, например:
- Intel MKL (Math Kernel Library) за Intel процесори;
- OpenBLAS: оптимизирана open-source имплементация за различни архитектури;
- ATLAS (Automatically Tuned Linear Algebra Software): имплементация, която се самонастройва спрямо конкретния хардуер.
NumPy и BLAS
NumPy е фундаментална библиотека за научни изчисления в Python, предоставяща мощни структури от данни и функции за работа с многомерни масиви и матрици. Голяма част от функционалностите в NumPy, особено тези свързани с линейна алгебра, са имплементирани с използване на рутини от BLAS под капака.
По подразбиране NumPy се компилира с вградена минималистична имплементация на BLAS. Въпреки че е преносима и стабилна, тази имплементация не е оптимизирана за производителност. За да отключим пълния потенциал на NumPy, е препоръчително да го конфигурираме да използва оптимизирана BLAS библиотека, подходяща за нашия хардуер.
Производителност на линейно-алгебричните операции
Нека сега да видим на практика какъв ефект може да има използването на оптимизиран BLAS върху производителността на NumPy. Ще сравним времената за изпълнение на няколко често срещани операции с и без linked BLAS библиотека.
Първо, нека инсталираме NumPy и библиотеката за тестване на производителността:
# Install dependencies
pip install numpy perfplot
След това ще дефинираме функция, която умножава две квадратни матрици, използвайки вградения dot
метод на NumPy масивите:
import numpy as np
def matmul(n):
A = np.random.rand(n, n)
B = np.random.rand(n, n)
return A.dot(B)
Сега ще използваме perfplot
, за да сравним производителността на тази функция при различни размери на матриците и с различни BLAS конфигурации:
import perfplot
perfplot.show(
setup=lambda n: n,
kernels=[
lambda n: matmul(n)
],
n_range=[2**k for k in range(2, 13)],
labels=['NumPy'],
xlabel='Matrix size',
ylabel='Execution time (s)',
logx=True,
logy=True,
)
На моята машина с OpenBLAS получавам няколко пъти по-добра производителност в сравнение с default имплементацията на BLAS в NumPy. Разликата е особено драстична при по-големи размери на матриците, където оптимизираният BLAS блести.
Подобни (а понякога и още по-големи) ускорения могат да се наблюдават при много други NumPy операции, които се възползват от BLAS, като numpy.dot
, numpy.linalg.inv
, numpy.linalg.svd
и т.н.
Конфигуриране на NumPy за използване на оптимизиран BLAS
За да конфигурираме NumPy да използва специфична BLAS библиотека, най-лесният начин е да презаредим (rebuild) NumPy от сорс кода с желаната опция за BLAS. За това първо ни трябват необходимите инструменти и зависимости:
sudo apt-get install build-essential gfortran
pip install cython
След това изтегляме и разархивираме сорс кода на NumPy:
wget https://github.com/numpy/numpy/archive/master.zip
unzip master.zip
cd numpy-master
Сега можем да конфигурираме сайт .npmrc
файл, за да укажем коя BLAS библиотека искаме да използваме. Например, за OpenBLAS:
[openblas]
libraries = openblas
library_dirs = /usr/local/lib
include_dirs = /usr/local/include
Инсталираме OpenBLAS, ако вече не е наличен на системата:
sudo apt-get install libopenblas-dev
Най-накрая, инсталираме NumPy от сорс кода с pip:
pip install .
След като инсталацията приключи, можем да проверим дали NumPy използва правилната BLAS библиотека чрез:
import numpy as np
np.__config__.show()
Би трябвало да видим нещо като:
blas_opt_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/usr/local/lib']
language = c
define_macros = [('HAVE_CBLAS', None)]
Заключение
В тази статия научихме какво е BLAS и как NumPy използва BLAS рутини за ускоряване на линейно-алгебричните операции. Видяхме практическа демонстрация на ползите от използване на оптимизирана BLAS библиотека и как сами да конфигурираме NumPy да използва такава.
Ако вашите NumPy изчисления включват тежки линейно-алгебрични операции, особено върху големи масиви и матрици, инвестицията от време за инсталиране и конфигуриране на оптимизиран BLAS определено си заслужава. Можете да очаквате значително подобрение на производителността, понякога от порядъци.
Разбира се, има и други начини за ускоряване на NumPy изчисленията, като използване на разпределени масиви (distributed arrays) с библиотеки като Dask и Joblib, или изпращане на изчисленията към GPU с библиотеки като CuPy и Pynq. Изборът на правилния инструмент зависи от специфичните нужди и мащаб на вашия проект.
Надявам се тази статия да е подсилила разбирането ви за вътрешната работа на NumPy и да ви е показала един практичен начин за подобряване на производителността. NumPy и екосистемата от научни библиотеки в Python са изключително мощни и с правилните оптимизации могат да ви отведат далеч!