Многопоточность в машинном обучении — различия между версиями
Skozelko (обсуждение | вклад) (Параграф про линейную регрессию) |
Skozelko (обсуждение | вклад) (Подсветка кода) |
||
Строка 35: | Строка 35: | ||
Пример перемножения матриц на cuBLAS | Пример перемножения матриц на cuBLAS | ||
− | + | <font color="#B00040">void</font> <font color="#0000FF">gpu_blas_mmul</font>(cublasHandle_t &handle, <font color="#008000">'''const'''</font> <font color="#B00040">float</font> *A, <font color="#008000">'''const'''</font> <font color="#B00040">float</font> *B, <font color="#B00040">float</font> *C, <font color="#008000">'''const'''</font> <font color="#B00040">int</font> m, <font color="#008000">'''const'''</font> <font color="#B00040">int</font> k, <font color="#008000">'''const'''</font> <font color="#B00040">int</font> n) { | |
− | int lda = m, ldb = k, ldc = m; | + | <font color="#B00040">int</font> lda = m, ldb = k, ldc = m; |
− | const float alf = 1; | + | <font color="#008000">'''const'''</font> <font color="#B00040">float</font> alf = 1; |
− | const float bet = 0; | + | <font color="#008000">'''const'''</font> <font color="#B00040">float</font> bet = 0; |
− | const float *alpha = &alf; | + | <font color="#008000">'''const'''</font> <font color="#B00040">float</font> *alpha = &alf; |
− | const float *beta = &bet; | + | <font color="#008000">'''const'''</font> <font color="#B00040">float</font> *beta = &bet; |
− | // Do the actual multiplication | + | <font color="#408080">''// Do the actual multiplication''</font> |
cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); | cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); | ||
} | } | ||
Пример перемножения матриц на PyCUDA | Пример перемножения матриц на PyCUDA | ||
− | import pycuda.gpuarray as gpuarray | + | <font color="#008000">'''import'''</font> <font color="#0000FF">'''pycuda.gpuarray'''</font> <font color="#008000">'''as'''</font> <font color="#0000FF">'''gpuarray'''</font> |
− | import numpy as np | + | <font color="#008000">'''import'''</font> <font color="#0000FF">'''numpy'''</font> <font color="#008000">'''as'''</font> <font color="#0000FF">'''np'''</font> |
− | import skcuda.linalg as linalg | + | <font color="#008000">'''import'''</font> <font color="#0000FF">'''skcuda.linalg'''</font> <font color="#008000">'''as'''</font> <font color="#0000FF">'''linalg'''</font> |
− | # --- Initializations | + | <font color="#408080">''# --- Initializations''</font> |
− | import pycuda.autoinit | + | <font color="#008000">'''import'''</font> <font color="#0000FF">'''pycuda.autoinit'''</font> |
linalg.init() | linalg.init() | ||
Строка 61: | Строка 61: | ||
C_gpu = linalg.dot(A_gpu, B_gpu) | C_gpu = linalg.dot(A_gpu, B_gpu) | ||
− | print(np.dot(A, B)) | + | <font color="#008000">print</font>(np.dot(A, B)) |
− | print(C_gpu) | + | <font color="#008000">print</font>(C_gpu) |
Наивная реализация перемножения матриц на OpenCL | Наивная реализация перемножения матриц на OpenCL | ||
− | // First naive implementation | + | <font color="#408080">''// First naive implementation''</font> |
− | __kernel void myGEMM1(const int M, const int N, const int K, | + | __kernel <font color="#B00040">void</font> myGEMM1(<font color="#008000">'''const'''</font> <font color="#B00040">int</font> M, <font color="#008000">'''const'''</font> <font color="#B00040">int</font> N, <font color="#008000">'''const'''</font> <font color="#B00040">int</font> K, |
− | const __global float *A, | + | <font color="#008000">'''const'''</font> __global <font color="#B00040">float</font> *A, |
− | const __global float *B, | + | <font color="#008000">'''const'''</font> __global <font color="#B00040">float</font> *B, |
− | __global float *C) { | + | __global <font color="#B00040">float</font> *C) { |
− | // Thread identifiers | + | <font color="#408080">''// Thread identifiers''</font> |
− | const int globalRow = get_global_id(0); // Row ID of C (0..M) | + | <font color="#008000">'''const'''</font> <font color="#B00040">int</font> globalRow = get_global_id(0); <font color="#408080">''// Row ID of C (0..M)''</font> |
− | const int globalCol = get_global_id(1); // Col ID of C (0..N) | + | <font color="#008000">'''const'''</font> <font color="#B00040">int</font> globalCol = get_global_id(1); <font color="#408080">''// Col ID of C (0..N)''</font> |
− | // Compute a single element (loop over K) | + | <font color="#408080">''// Compute a single element (loop over K)''</font> |
− | float acc = 0.0f; | + | <font color="#B00040">float</font> acc = 0.0f; |
− | for (int k = 0; k < K; k++) { | + | <font color="#008000">'''for'''</font> (<font color="#B00040">int</font> k = 0; k < K; k++) { |
acc += A[k * M + globalRow] * B[globalCol * K + k]; | acc += A[k * M + globalRow] * B[globalCol * K + k]; | ||
} | } | ||
− | // Store the result | + | <font color="#408080">''// Store the result''</font> |
C[globalCol * M + globalRow] = acc; | C[globalCol * M + globalRow] = acc; | ||
} | } |
Версия 04:38, 9 января 2021
Следует выделить следующие виды параллелизма:
- Параллелизм на уровне инструкций (ILP[1]): несколько инструкций исполняются одновременно.
- Параллелизм типа одна инструкция множество данных (SIMD[2]): одна операция применяется к множеству данных
- Многопоточный параллелизм: несколько независимых рабочих потоков взаимодействуют через абстракцию совместно используемой памяти.
- Распределенные вычисления: несколько независимых рабочих компьютеров взаимодействуют по сети. (MLlib[3] на Spark, Mahout[4] на Hadoop)
Содержание
- 1 Идеи используемые для ускорения вычислений в ML
- 1.1 Параллелизм для ускорения линейной алгебры.
- 1.2 Параллелизм в оптимизации гиперпараметров
- 1.3 Параллелизм кросс-валидации
- 1.4 Параллелизм GPU[5]
- 1.5 Параллелизм в стохастическом градиентном спуске
- 1.6 Параллелизм в методе k ближайших соседей
- 1.7 Параллелизм в методе опорных веторов
- 1.8 Параллелизм в линейной регрессии
- 2 См. также
- 3 Примечания
- 4 Источники информации
Идеи используемые для ускорения вычислений в ML
Параллелизм для ускорения линейной алгебры.
Многие операции линейной алгебры, например, векторное сложение, произведение матриц и вычисление нормы состоят из большого количества независимых операций. Поэтому можно сильно повысить их производительность как за счёт ILP и SIMD параллелизма для маленьких данных, так и за счёт многопоточности для больших данных. От ускорения линейной алгебры особенно выигрывают нейронные сети, так как большую часть времени их работы занимает умножение матриц.
Иногда необходимо выполнить операцию с объектам имеющими разнаю размерность, но которые можно привести к одной размерности повторением одного из объектов вдоль одной или нескольких осей. Например, если нужно прибавить к каждой строке матрицы вектор или домножить вектор на число. В таком случае можно не писать цикл в явном виде, а использовать broadcast операции. При этом задача оптимизации переходит к разработчику библиотеки, который может обеспечить лучший параллелизм операций за счет доступа к внутренностям библиотеки.
Примеры оптимизаций:
- Высоко оптимизированные тензорные библиотеки для арифметики.
- Алгоритмы в терминах матричных операций, а не векторных операций, насколько это возможно.
- Broadcast операции вместо циклов.
- Распараллеленные реализации некоторых специальных операций (таких как свертки для CNN).
Параллелизм в оптимизации гиперпараметров
Для параллельной оптимизации гиперпараметров можно использовать поиск по решётке или случайный поиск в которых мы можем оценить параметры независимо. Такая оптимизации часто встречаются в библиотеках машинного обучения.
Параллелизм кросс-валидации
Полная кросс-валидация, k-fold, t×k-fold, Leave-One-Out легко распараллеливаются на несколько потоков, каждый из которых работает на своем разбиении данных
Параллелизм GPU[5]
Графические процессоры позволяют применять одну и ту же операцию параллельно к десяткам тысяч элементов за счет большого числа потоков.
Фреймворки машинного обучения, такие как TensorFlow, PyTorch и MxNet используют эти возможности через библиотеки от компаний производителей графических ускорителей и открытые фреймворки:
- CUDA[6] — язык параллельного программирования/вычислительная платформа для вычислений общего назначения на графическом процессоре
- cuBLAS[7] — библиотека представляет собой реализацию BLAS (базовых подпрограмм линейной алгебры) поверх среды выполнения CUDA.
- OpenCL[8]— фреймворк для написания компьютерных программ, связанных с параллельными вычислениями на различных графических и центральных процессорах, а также FPGA
Пример перемножения матриц на cuBLAS
void gpu_blas_mmul(cublasHandle_t &handle, const float *A, const float *B, float *C, const int m, const int k, const int n) { int lda = m, ldb = k, ldc = m; const float alf = 1; const float bet = 0; const float *alpha = &alf; const float *beta = &bet; // Do the actual multiplication cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc); }
Пример перемножения матриц на PyCUDA
import pycuda.gpuarray as gpuarray import numpy as np import skcuda.linalg as linalg # --- Initializations import pycuda.autoinit linalg.init() A = np.array(([1, 2, 3], [4, 5, 6])).astype(np.float64) B = np.array(([7, 8, 1, 5], [9, 10, 0, 9], [11, 12, 5, 5])).astype(np.float64) A_gpu = gpuarray.to_gpu(A) B_gpu = gpuarray.to_gpu(B) C_gpu = linalg.dot(A_gpu, B_gpu) print(np.dot(A, B)) print(C_gpu)
Наивная реализация перемножения матриц на OpenCL
// First naive implementation __kernel void myGEMM1(const int M, const int N, const int K, const __global float *A, const __global float *B, __global float *C) { // Thread identifiers const int globalRow = get_global_id(0); // Row ID of C (0..M) const int globalCol = get_global_id(1); // Col ID of C (0..N) // Compute a single element (loop over K) float acc = 0.0f; for (int k = 0; k < K; k++) { acc += A[k * M + globalRow] * B[globalCol * K + k]; } // Store the result C[globalCol * M + globalRow] = acc; }
Параллелизм в стохастическом градиентном спуске
Можно запустить внешний цикл стохастического градиентного спуска (SGD) параллельно в пуле потоков и использовать конструкции синхронизации, такие как блокировки, чтобы предотвратить состояние гонки. Однако из-за накладных расходов на синхронизацию ускорение может получиться маленьким.
Еще более интересная идея называется асинхронным SGD или Hogwild A Lock-Free Approach to Parallelizing Stochastic Gradient Descent[9]. SGD запускается параллельно в несколько потоков без какой-либо синхронизации. Теперь состояния гонки могут возникнуть, но во многих случаях это хорошо, потому что они просто немного изменяют шум и ошибки уже присутствующие из-за случайного выбора градиента.
Параллелизм в методе k ближайших соседей
Основное время работы метода k ближайших соседей составляет поиск ближайших соседей. Так как расстояния до разных объектов независимы, то можно разбить объекты на группы, параллельно решить задачу во всех группах, а потом объединить результат Implementation of a Parallel K-Nearest Neighbor Algorithm Using MPI[10]. Альтернативный подход — параллельная сортировка всех объектов, например, с использованием битонной сортировки Parallel Search of k-Nearest Neighbors with Synchronous Operations[11].
Параллелизм в методе опорных веторов
Вычислительная сложность метода опорных векторов заключается в минимизации квадратичной функции. Первый вариант распараллеливания задачи — добавление параллелизма в алгоритм в явном виде, например, параллельная оптимизация большего количества переменных в SMO Parallel Support Vector Machines[12]. Второй подход — запись алгоритма через матричные операции, которые легко параллелизируемы Multiplicative Updates for Nonnegative Quadratic Programming[13].
Параллелизм в линейной регрессии
При использовании метода наименьших квадратов поиск коэффициентов регрессии сводится к нахождению псевдообратной матрицы. Хотя псевдообратную матрицу можно вычислить через обратную и для этого существуют параллельные алгоритмы, такой подход остается непрактичным. Более популярный способ, основанный на сингулярном разложении, можно сделать параллельным, если использовать в процессе метод Якоби для собственных значений и на каждом шаге обрабатывать несколько строк и столбцов[14]. Также можно использовать параллельный алгоритм для QR-разложения как это сделано в ScaLAPACK[15].
См. также
- Стохастический градиентный спуск
- Кросс-валидация
- Настройка гиперпараметров
- Метод опорных векторов (SVM)
- Метрический классификатор и метод ближайших соседей
Примечания
- ↑ ILP
- ↑ SIMD
- ↑ MLlib
- ↑ Mahout
- ↑ GPU
- ↑ CUDA
- ↑ cuBLAS
- ↑ OpenCL
- ↑ HOGWILD!: A Lock-Free Approach to Parallelizing Stochastic Gradient Descent
- ↑ Implementation of a Parallel K-Nearest Neighbor Algorithm Using MPI
- ↑ Parallel Search of k-Nearest Neighbors with Synchronous Operations
- ↑ Parallel Support Vector Machines
- ↑ Multiplicative Updates for Nonnegative Quadratic Programming
- ↑ Parallel Algorithms for the Singular Value Decomposition
- ↑ ScaLAPACK Linear Least Squares Problems