Задание 5. Программирование тензорных ядер
Используя тензорные ядра вашей видеокарты запрограммируйте тренировку нейронной сети заданной конфигурации. Сравните производительность вашей программы с фреймворком pytorch на разных архитектурах (время выполнения прямого и обратного хода для достаточно больших размерностей входных данных и слоев). Запишите в отчет 10 замеренных времен выполнения для одного и того же размера слоев:
а) низкоуровневая реализация на pyton (forward и backward)
б) pytorch для CPU (forward и backward)
в) pytorch для GPU (forward и backward)
г) C++ для CPU без использования библиотек (forward и backward)
д) CUDA с тензорными ядрами (forward и backward)
В python требуется аккуратность при замерах времени. Указывайте 10 итераций в цикле по батчам и выбирайте что-то более или менее близкое к среднему.
Объясните обнаруженные парадоксы.
Чтобы не допустить ошибок и быть уверенным в правильности работы программы, удобно придерживаться следующей последовательности:
1. Запрограммировать низкоуровневый код нейронной сети на python. Сравнить вычисленные градиенты с результатом автоматического дифференцирования pytorch (см. пример в файле nn.ipynb)
2. Запрограммировать код на C++ для CPU, используя CPU-аналог функции sgemm и сравнить результаты с работой кода на python (см. класс CpuModel в файле nn.cu)
3. Запрограммировать код на CUDA для GPU и сравнить его результаты для разных (больших и маленьких) размерностей с CPU-версией (см. класс GpuModel в файле nn.cu)
Компилируйте программу для GPU, линкуя cublas: nvcc -O2 nn_tensor.cu -lcublas
Варианты (в квадратных скобках указан выходная размерность, параметры n,m можно варьировать):
1. Сеть-трансформер x[n] → FC1[n/16] → ReLU → FC2[n]. Функция ошибок - квадрат L2 нормы разности: выход - вход.
2. x[n] → Dropout 10% → FC1[n/4] → Dropout 10% → tanh → FC2[1] → tanh
3. x[n] → FC1[n/8] → sigmoid → FC2[1], последний слой FC2[1] также на вход принимает дополнительно mean(x[n])
4. x[n] → FC1[n/8] → sigmoid → FC2[n/32]. В данном задании предполагается, что целевая функция многомерна dim=n/32 (т.е. в матрице y n/32 столбцов)
5. x[n] → FC1[n/8] → sigmoid → FC3[1]. Последний слой дополнительно на вход принимает еще выход сети x[n] → sigmoid → FC2[n/8]
6. x[n] делится на m равных групп и к каждой применяется ОДНА и ТА ЖЕ функция FC1[1] → sigmoid. Полученные m результатов подаются на вход FC2[1].
7. x[n] → FC1[n/8] → sigmoid → FC2[1]. Последний слой дополнительно на вход принимает еще выход сети x[n] → sigmoid → FC1[n/8]. Веса обоих слоев FC1 в первой ветви и FC1 во второй - совпадают!
8. x[n] делится на m равных групп и к каждой применяются РАЗНЫЕ функции FC1,2,3...m[1] → sigmoid. Полученные m результатов подаются на вход FC0[1].
9. x[n] → FC1[n/8] → sigmoid → FC2[1]. Последний слой дополнительно на вход принимает еще выход сети "первые n/8+1 координат x" → FC2[1] → sigmoid. Веса обоих слоев FC2 в конце первой ветви и FC2 в середине второй - совпадают!
10. "первая половина x[n]" → FC1[n/16] → sigmoid → FC2[1]. Последний слой дополнительно на вход принимает еще выход сети "вторая половина x[n]" → FC1[n/16] → ReLU. Веса обоих слоев FC1 в первой ветви и FC1 во второй - совпадают!
11. Возьмем слой FC1[1] и циклическим сдвигом его вектора весов вправо на 1,2,3,... позиции получим слои FC1+1[1], FC1+2[1], FC1+3[1], ...Всего m штук слоев. На вход всех этих слоев подадим вектор x[n], а к полученным m выходам применим сигмоиду и подадим на вход FC2[1], который вернет результат.
12. К результату сети x[n] → FC1[n/16] → sigmoid → FC2[1] прибавим среднее значение промежуточного вектора FC1[n/16].
13. x[n] → FC1[n/16] → sigmoid → FC2[1]. Последний слой FC2[1] дополнительно на вход принимает еще выход сети FC1'[n/16] → sigmoid, где слой FC1' имеет те же веса, что и FC1, только отраженные вдоль измерения x (w1127-j,i - в терминах обозначений ноутбука nn.ipynb)
14. Координаты x[n] умножаются на n весов (xi умножается на wi), затем расположенные рядом координаты делятся на m групп и для каждой группы считается сумма. К полученным суммам применяется сигмоида и результаты подаются на вход FC[1].
15. x[n] → FC1[n/16] → sigmoid → FC2[1]. К обычной функции ошибок нужно добавить квадрат L2 нормы выхода слоя sigmoid.
16. x[n] → FC1[n/16] → sigmoid → FC2[1]. Последний слой дополнительно на вход принимает еще выход сети x[n] → smooth → FC3[n/16] → sigmoid → FC2[1]. Сглаживание smooth действует по правилу smoothi=(xi+xi+1+xi+2).
17. Выходы двух сетей: "x[n] с четными индексами" → FC1[n/16] → sigmoid и "x[n] с нечетными индексами" → FC2[n/16] → ReLU подаются на вход слою FC3[1]
18. Выходы четырех сетей: x[n] → FC1[1] → ReLU, x[n] → FC2[1] → sigmoid, x[n] → FC3[1] → tanh, x[n] → FC4[1] → softsign подаются на вход слою FC5[1]