Многопоточность в машинном обучении — различия между версиями
(Добавил больше примеров для GPU) |
(Добавил пример для OpenCL) |
||
Строка 74: | Строка 74: | ||
print(C_gpu) | 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 === | ||
Запускаем внешний цикл [[Стохастический градиентный спуск|SGD]] параллельно в пуле потоков и используем конструкции синхронизации, такие как блокировки, чтобы предотвратить состояние гонки. Но это может работать медленно из-за накладных расходов на синхронизацию. | Запускаем внешний цикл [[Стохастический градиентный спуск|SGD]] параллельно в пуле потоков и используем конструкции синхронизации, такие как блокировки, чтобы предотвратить состояние гонки. Но это может работать медленно из-за накладных расходов на синхронизацию. | ||
Строка 85: | Строка 105: | ||
# [https://medium.com/@CIulius/a-short-notice-on-performing-matrix-multiplications-in-pycuda-cbfb00cf1450 A short notice on performing matrix multiplications in PyCUDA] | # [https://medium.com/@CIulius/a-short-notice-on-performing-matrix-multiplications-in-pycuda-cbfb00cf1450 A short notice on performing matrix multiplications in PyCUDA] | ||
# [https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html CUDA C++ Programming Guide] | # [https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html CUDA C++ Programming Guide] | ||
+ | # [https://cnugteren.github.io/tutorial/pages/page1.html OpenCL SGEMM tuning for Kepler] | ||
[[Категория: Машинное обучение]] | [[Категория: Машинное обучение]] |
Версия 21:07, 22 апреля 2020
Следует выделить следующие виды параллелизма:
- Параллелизм на уровне инструкций (ILP): запускать несколько инструкций одновременно.
- Параллелизм одна инструкция множество данных(SIMD): одна инструкция работает с вектором чисел
- Многопоточный параллелизм: несколько независимых рабочих потоков взаимодействуют через абстракцию совместно используемой памяти.
- Распределенные вычисления: несколько независимых рабочих компьютеров взаимодействуют по сети. (MLlib на Spark, Mahout на Hadoop)
Содержание
Идеи используемые для ускорения вычислений в ML
Параллелизм для ускорения линейной алгебры.
Мы можем значительно повысить производительность, создав ядра линейной алгебры (например, умножение матриц, векторное сложение и т.д.), которые используют параллелизм для ускорения работы. Поскольку умножение матриц занимает большую часть времени обучения нейронных сетей, это может привести к значительному сквозному ускорению обучающего конвейера. В основе этого лежит параллелизм ILP, SIMD а для больших матриц также может использоваться многопоточность.
Примеры оптимизаций:
- Высоко оптимизированные тензорные библиотеки для арифметики.
- Алгоритмы в терминах матричных операций, а не векторных операций, насколько это возможно.
- Broadcast операции, а не циклы.
- Распараллеленные реализации некоторых специальных операций (таких как свертки для CNN).
Параллелизм broadcast операций
Просмотрите код наивной реализации поэлементное произведение двух векторов на Python
def elementwise_product(x, y): assert(len(x) == len(y)) z = numpy.zeros(len(x)) for i in range(len(x)): z[i] = x[i] * y[i] return z
Такой код лучше заменять на broadcast операции из numpy, которые выигрывают от векторизации и ILP. Также такой код может быть легко распараллелен для больших векторов
Параллелизм в оптимизации гиперпараметров
Для параллельной оптимизации гиперпараметров можно использовать поиск по решётке или случайный поиск в которых мы можем оценить параметры независимо. Такая оптимизации часто встречаются в библиотеках машинного обучения.
Параллелизм кросс-валидации
Полная кросс-валидация, k-fold, t×k-fold, Leave-One-Out легко распараллеливаются на несколько потоков, каждый из которых работает на своем разбиении данных
Параллелизм GPU
Типичное число потоков обработки графического процессора - десятки тысяч, что позволяет вычислять одну и ту же операцию параллельно на множестве элементов.
Фреймворки машинного обучения, такие как TensorFlow, PyTorch и MxNet используют эти возможности через библиотеки от компаний производителей графических ускорителей и открытые фреймворки:
- CUDA - язык параллельного программирования/вычислительная платформа для вычислений общего назначения на графическом процессоре
- cuBLAS - библиотека представляет собой реализацию BLAS (базовых подпрограмм линейной алгебры) поверх среды выполнения CUDA.
- OpenCL - фреймворк для написания компьютерных программ, связанных с параллельными вычислениями на различных графических и центральных процессорах, а также 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 параллельно в пуле потоков и используем конструкции синхронизации, такие как блокировки, чтобы предотвратить состояние гонки. Но это может работать медленно из-за накладных расходов на синхронизацию.
Еще более интересная идея (называемая асинхронным SGD или Hogwild): Несколько потоков запускают SGD параллельно без какой-либо синхронизации. Теперь условия гонки могут возникнуть, но оказывается, что во многих случаях это хорошо, шум/ошибка в условиях гонки просто добавляет небольшое количество к шуму/ошибке, уже присутствующей в алгоритм из-за случайной выборки градиента.