Приложение ArrayFire CUDA очень медленно в первую минуту - PullRequest
0 голосов
/ 08 мая 2018

Я пишу тестовую программу с использованием ArrayFire на Windows 10 + Nvidia Gtx 970. Эта программа предназначена для обучения нейронной сети с помощью решателя SGD. Таким образом, основным вычислением является итерация для обновления сетевых параметров. Итерация в функции под названием step().

Программа делает то, что ожидается, за исключением того, что она работает очень медленно в первую минуту. Ниже приведен вывод программы. Первый столбец - прошедшее время.

ArrayFire v3.5.1 (CUDA, 64-bit Windows, build 0a675e8)
Platform: CUDA Toolkit 8, Driver: CUDA Driver Version: 8000
[0] GeForce GTX 970, 4096 MB, CUDA Compute 5.2
  time epochs training error
     5  0.002 5.6124567
     6  0.007 5.5981609
     7  0.010 5.3560046
     8  0.015 5.2485286
     9  0.020 5.1370633
    10  0.022 5.1081303
     ....
    52  0.148 3.2528560
    53  0.150 3.2425120
    54  0.153 3.2180901
    55  0.155 3.2048657
    56  0.157 3.1949191
    57  0.158 3.1816899
    58  0.160 3.1717312
    59  0.162 3.1597322
    60  0.165 3.1370639
    60  0.498 2.1359600
    61  0.548 2.0685355
    61  0.882 1.7098215
    62  0.943 1.6575973
    62  1.277 1.4156345
    63  1.343 1.3845720
    63  1.677 1.1789854
    64  1.733 1.1549067
    64  2.067 1.0162785
     ....
    71  4.517 0.4732214
    71  4.850 0.4522045
    72  4.910 0.4501807
    72  5.243 0.4355422
    73  5.305 0.4307187

Как видите, в первую минуту он даже не закончил 1/5 эпохи. Но через минуту он внезапно ускорился, чтобы завершить одну эпоху примерно за 4 секунды.

Данные профилирования также говорят о том же: в первую минуту среднее время выполнения функции step() составляет около 500 мс, но после первой минуты оно падает до 6 мс.

Визуальный профилировщик Nvidia показывает, что ядро ​​почти бездействует в течение первой минуты.

Понятия не имею, что может вызвать изменение производительности до | после первой минуты. Любая помощь приветствуется.

1 Ответ

0 голосов
/ 09 мая 2018

ArrayFire использует JIT-компиляцию во время выполнения для объединения нескольких вызовов функций. Поэтому, когда вы выполняете дополнение или любую другую поэлементную операцию, ArrayFire создаст собственное ядро ​​и выполнит это ядро. Это приводит к некоторым издержкам при первой генерации этого ядра, но эти ядра кэшируются, и дополнительные вызовы не нужно компилировать. Обычно для этого не требуется нескольких итераций, пока не потребуются дополнительные компиляции. Странно, что ядра работают медленно даже после примерно 60 итераций.

Ядра JIT оцениваются с использованием внутренней эвристики, основанной на памяти и размере ядер. Возможно, ваше приложение не запускает ядра оптимально и вызывает дополнительные компиляции ядра. Вы можете обойти это, форсируя оценку, вызывая функцию eval для переменной. Вот надуманный пример:

array a = randu(10, 10);
array b = randu(10, 10);
for(int i = 0; i < 100; i++) {
      a += b / 4;
      b *= i;
      eval(a, b);
}

Здесь вы оцениваете дерево JIT для переменных a и b на каждой итерации. Это будет использовать одно и то же ядро ​​на каждой итерации вместо создания ядра для разных кратных повторений.

Следует отметить, что поэлементно и некоторые условные функции, такие как select и shift, являются JITed. Другие функции заставляют оценивать свои параметры перед использованием. Также, если вы оцениваете слишком часто, вы снизите производительность своего приложения.

...