#gpu #cupy #codegems
How to write CPU/GPU agnostic code
CuPy’s compatibility with NumPy makes it possible to write CPU/GPU agnostic code. For this purpose, CuPy implements the cupy.get_array_module() function that returns a reference to cupy if any of its arguments resides on a GPU and numpy otherwise. Here is an example of a CPU/GPU agnostic function that computes log1p:
How to write CPU/GPU agnostic code
CuPy’s compatibility with NumPy makes it possible to write CPU/GPU agnostic code. For this purpose, CuPy implements the cupy.get_array_module() function that returns a reference to cupy if any of its arguments resides on a GPU and numpy otherwise. Here is an example of a CPU/GPU agnostic function that computes log1p:
# Stable implementation of log(1 + exp(x))https://docs.cupy.dev/en/stable/user_guide/basic.html
def softplus(x):
xp = cp.get_array_module(x) # 'xp' is a standard usage in the community
print("Using:", xp.__name__)
return xp.maximum(0, x) + xp.log1p(xp.exp(-abs(x)))
#featureselection #diogenes #optimisation #gpu #cuda #cupy
Докладываю новости о проекте Диоген. Вчера добавил бюджет времени, переписал функции покрасивее, потестировал все ветки. С указанием допустимого времени работает просто отлично.
Сегодня ещё добавил функциональность random seed. Выяснилось, что нумба не подтягивает его из нампай "обычного" режима, надо в njit-нутой функции его ещё раз выставлять.
Отпрофилировал код на глубинах 1-2 взаимодействий признаков. Оказалось, что 80% времени кушает оценка кандидатов, и 20% времени - расчёт надёжности для лучших кандидатов. Из этих 80% основные опять же ~80% тратятся в прямой оценке MI кандидата и таргета.
Дальше они распадаются так: 20% на слияние многомерного вектора, 70% на перемешивание таргета (np.random.shuffle), 10% на сам расчёт MI из совместного распределения.
Очевидно, выгоднее всего оптимизировать шаффл таргета. Решил попробовать cupy как замену numpy: просто перенести таргет в память gpu и шаффлить прямо там. Улучшения таймингов это не дало, т.к. перемешанный, пусть и быстро, массив 120k элементов всё равно надо было копировать обратно в RAM для подсчёта MI.
К счастью, подумал я, cupy позволяет писать и запускать произвольные ядра Cuda в C++ прямо из питона. Почему бы не выполнить расчёт гистограммы и MI прямо на GPU? Капец я с этим намучался ) Я же совсем забыл, что программирование для GPU - это сущий ад после "обычного", в поточной парадигме: ваша программа выполняется сразу на тысячах потоков, куча ограничений и условий, разделяемая память,синхронизация, атомики, блоки... Плюс я совсем забыл С++ (даже то немногое, что знал). Да ещё cupy иногда радовал перлами типа невозможности подсчитать сумму массива без Visual Studio 2019 (хотя максимум считался нормально).
В итоге всё же добил эту идею, и сейчас у меня есть реализация критической части на Cuda )
Что же по скорости?
%timeit показывает, что массив 120k элементов ноутбучная 1050Ti шаффлит в 4 раза быстрее, чем i7-7700HQ. То есть я могу ожидать, что 0.8*0.8*0.7=40% всего времени исполнения сократится в 4 раза. Если поиск на CPU идёт 15 минут, то можно ожидать, что на GPU это будет 15*0.6+15*0,4/4=11 минут.
Напрямую сравнить CPU и GPU не получается, т.к. у разных библ разные сиды и порядок выполнения операций, а длительность всего поиска зависит от случайных решений. Пара запусков дала новое время 12.5-13.0 минут вместо 15. Возможно, где-то налажал, буду ещё профилировать. Но timeit шаффла массива 1M элементов даёт выигрыш уже не в 4, а в 40 раз, т.е., по идее, GPU будет ещё выгоднее для больших датасетов.
Докладываю новости о проекте Диоген. Вчера добавил бюджет времени, переписал функции покрасивее, потестировал все ветки. С указанием допустимого времени работает просто отлично.
Сегодня ещё добавил функциональность random seed. Выяснилось, что нумба не подтягивает его из нампай "обычного" режима, надо в njit-нутой функции его ещё раз выставлять.
Отпрофилировал код на глубинах 1-2 взаимодействий признаков. Оказалось, что 80% времени кушает оценка кандидатов, и 20% времени - расчёт надёжности для лучших кандидатов. Из этих 80% основные опять же ~80% тратятся в прямой оценке MI кандидата и таргета.
Дальше они распадаются так: 20% на слияние многомерного вектора, 70% на перемешивание таргета (np.random.shuffle), 10% на сам расчёт MI из совместного распределения.
Очевидно, выгоднее всего оптимизировать шаффл таргета. Решил попробовать cupy как замену numpy: просто перенести таргет в память gpu и шаффлить прямо там. Улучшения таймингов это не дало, т.к. перемешанный, пусть и быстро, массив 120k элементов всё равно надо было копировать обратно в RAM для подсчёта MI.
К счастью, подумал я, cupy позволяет писать и запускать произвольные ядра Cuda в C++ прямо из питона. Почему бы не выполнить расчёт гистограммы и MI прямо на GPU? Капец я с этим намучался ) Я же совсем забыл, что программирование для GPU - это сущий ад после "обычного", в поточной парадигме: ваша программа выполняется сразу на тысячах потоков, куча ограничений и условий, разделяемая память,синхронизация, атомики, блоки... Плюс я совсем забыл С++ (даже то немногое, что знал). Да ещё cupy иногда радовал перлами типа невозможности подсчитать сумму массива без Visual Studio 2019 (хотя максимум считался нормально).
В итоге всё же добил эту идею, и сейчас у меня есть реализация критической части на Cuda )
Что же по скорости?
%timeit показывает, что массив 120k элементов ноутбучная 1050Ti шаффлит в 4 раза быстрее, чем i7-7700HQ. То есть я могу ожидать, что 0.8*0.8*0.7=40% всего времени исполнения сократится в 4 раза. Если поиск на CPU идёт 15 минут, то можно ожидать, что на GPU это будет 15*0.6+15*0,4/4=11 минут.
Напрямую сравнить CPU и GPU не получается, т.к. у разных библ разные сиды и порядок выполнения операций, а длительность всего поиска зависит от случайных решений. Пара запусков дала новое время 12.5-13.0 минут вместо 15. Возможно, где-то налажал, буду ещё профилировать. Но timeit шаффла массива 1M элементов даёт выигрыш уже не в 4, а в 40 раз, т.е., по идее, GPU будет ещё выгоднее для больших датасетов.
#sklearn #cupy
Вот пример выполнения LDA на GPU с применением этого экспериментального Array API.
А еще теперь можно напрямую работать с тензорами Pytorch:
Пока далеко не все модули это поддерживают, но сама идея чудесная.
https://scikit-learn.org/stable/modules/array_api.html#array-api
Вот пример выполнения LDA на GPU с применением этого экспериментального Array API.
from sklearn.datasets import make_classification
from sklearn import config_context
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
import cupy
X_np, y_np = make_classification(random_state=0)
X_cu = cupy.asarray(X_np)
y_cu = cupy.asarray(y_np)
X_cu.device
with config_context(array_api_dispatch=True):
lda = LinearDiscriminantAnalysis()
X_trans = lda.fit_transform(X_cu, y_cu)
X_trans.device
А еще теперь можно напрямую работать с тензорами Pytorch:
import torch
X_torch = torch.asarray(X_np, device="cuda", dtype=torch.float32)
y_torch = torch.asarray(y_np, device="cuda", dtype=torch.float32)
with config_context(array_api_dispatch=True):
lda = LinearDiscriminantAnalysis()
X_trans = lda.fit_transform(X_torch, y_torch)
type(X_trans)
X_trans.device.type
Пока далеко не все модули это поддерживают, но сама идея чудесная.
https://scikit-learn.org/stable/modules/array_api.html#array-api
scikit-learn
11.1. Array API support (experimental)
The Array API specification defines a standard API for all array manipulation libraries with a NumPy-like API. Scikit-learn’s Array API support requires array-api-compat to be installed. Some sciki...