нелинейная оптимизация на графическом процессоре (CUDA) без задержки передачи данных - PullRequest
1 голос
/ 08 января 2020

Я пытаюсь выполнить задачу нелинейной оптимизации полностью на графическом процессоре. Вычисление целевой функции и передача данных из GPU в CPU являются узкими местами. Чтобы решить эту проблему, я хочу

  1. сильно распараллелить вычисление цели и
  2. выполнить всю оптимизацию на GPU.

В частности, проблема заключается в следующем в псевдокоде:

x = x0  // initial guess of the vector of unknowns, typically of size ~10,000
for iteration = 1 : max_iter
      D = compute_search_direction(x)
      alpha = compute_step_along_direction(x)
      x = x   +   D * alpha  // update
end for loop

Функции compute_search_direction(x) и compute_step_along_direction(x) обе вызывают целевую функцию f0(x) десятки раз за итерацию. Целевая функция представляет собой сложное ядро ​​CUDA, в основном это прямое моделирование Блоха (= система уравнений, которая описывает динамику ядерных спинов в магнитном поле c). Выходные данные f0(x) - это F (значение целевой функции, скалярное значение) и DF (якобиан, или вектор первых производных, с таким же размером, что и x, то есть ~ 10000). На GPU f0(x) действительно быстрый, но передача x из CPU в GPU, а затем передача обратно F и DF из GPU в CPU занимает некоторое время (всего ~ 1 секунда). Поскольку эта функция вызывается десятки раз за одну итерацию, это приводит к довольно медленной общей оптимизации.

В идеале, я хотел бы иметь весь псевдокод выше на графическом процессоре. Единственное решение, которое я могу придумать сейчас, - это рекурсивные ядра. Приведенный выше псевдокод будет "внешним ядром", запущенным с числом потоков = 1 и числом блоков = 1 (т. Е. Это ядро ​​на самом деле не параллельное ...). Затем это ядро ​​будет вызывать целевую функцию (т. Е. «Внутреннее ядро», это массивно параллельное) каждый раз, когда ему нужно оценить целевую функцию и вектор первых производных. Поскольку запуск ядра выполняется асинхронно, я могу заставить графический процессор ждать, пока внутреннее ядро ​​f0 не будет полностью оценено, чтобы перейти к следующей инструкции внешнего ядра (используя точку синхронизации).

В некотором смысле это действительно то же самое, что и обычное программирование CUDA, когда ЦПУ контролирует запуск ядра для оценки целевой функции f0, за исключением того, что ЦП заменяется внешним ядром, которое не распараллелено (1 нить, 1 блок). Однако, поскольку все находится на графическом процессоре, задержки передачи данных больше нет.

Сейчас я проверяю идею на простом примере для проверки осуществимости. Однако это кажется довольно громоздким ... Мои вопросы:

  1. Имеет ли это какой-либо смысл для кого-либо еще?
  2. Есть ли более прямой способ достичь того же результата без добавлена ​​сложность вложенных ядер?

1 Ответ

1 голос
/ 17 января 2020

Похоже, вы путаете «уменьшение передачи памяти между GPU и CPU» и «запуск всего кода на устройстве (иначе говоря, на gpu)».

Чтобы уменьшить передачу памяти, вы не требуется для запуска всего кода на графическом процессоре.

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

Вот псевдокод правильного подхода к тому, что вы хотите сделать .

// CPU code
cudaMalloc(&x,...) //allocate memory for x on GPU
cudaMemCpy(x, x0, size, cudaMemCpyHostToDevice); //Copy x0 to the freshly allocated array 
cudaMalloc(&D, ....)    //allocate D and alpha before the loop
cudaMalloc(&alpha, ....)
for iteration = 1 : max_iter
      compute_search_direction<<<...>>>(x, D) //Call a kernel that does the computation and stores the result in D
      compute_step_along_direction<<<....>>>(x, alpha)
      combine_result<<<...>>>(x, D, alpha)  // x   +   D * alpha
end for loop
//Eventually copy x on CPU, if need be

Надеюсь, это поможет!

...