2014-11-05 13 views
0

У меня есть цикл, в котором я запускаю несколько ядер на графический процессор. Ниже приведен фрагмент кода:Использование нескольких графических процессоров OpenCL

for (int idx = start; idx <= end ;idx ++) { 

      ret = clEnqueueNDRangeKernel(command_queue, memset_kernel, 1, NULL, 
              &global_item_size_memset, &local_item_size, 0, NULL, NULL); 
      ASSERT_CL(ret, "Error after launching 1st memset_kernel !"); 


      ret = clEnqueueNDRangeKernel(command_queue, cholesky_kernel, 1, NULL, 
                &global_item_size_cholesky, &local_item_size, 0, NULL, NULL); 
      ASSERT_CL(ret, "Error after launching 1st cholesky_kernel !"); 


      ret = clEnqueueNDRangeKernel(command_queue, ckf_kernel1, 1, NULL, 
              &global_item_size_kernel1, &local_item_size, 0, NULL, NULL); 
      ASSERT_CL(ret, "Error after launching ckf_kernel1[i] !"); 



      clFinish(command_queue); 
      ret = clEnqueueNDRangeKernel(command_queue, memset_kernel, 1, NULL, 
              &global_item_size_memset, &local_item_size, 0, NULL, NULL); 
      ASSERT_CL(ret, "Error after launching 2nd memset_kernel !"); 


      ret = clEnqueueNDRangeKernel(command_queue, cholesky_kernel, 1, NULL, 
                &global_item_size_cholesky, &local_item_size, 0, NULL, NULL); 
      ASSERT_CL(ret, "Error after launching 2nd cholesky_kernel !"); 


      ret = clSetKernelArg(ckf_kernel2, 4, sizeof(idx), (void *)&idx); 

      ret = clEnqueueNDRangeKernel(command_queue, ckf_kernel2, 1, NULL, 
              &global_item_size_kernel2, &local_item_size, 0, NULL, NULL); 
      ASSERT_CL(ret, "Error after launching ckf_kernel2 !"); 

Теперь я хотел использовать этот код для системы, которая имеет несколько графических процессоров. Поэтому я выполнил следующие шаги:

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

    cl_kernel ckf_kernel1[2]; 
    cl_kernel ckf_kernel2[2]; 
    cl_kernel cholesky_kernel[2]; 
    cl_kernel memset_kernel[2]; 
    
    // read get kernel. 
    ckf_kernel1[0] = clCreateKernel(program, "ckf_kernel1", &ret); 
    ASSERT_CL(ret, "Cannot load ckf_kernel1[i]!"); 
    ckf_kernel2[0] = clCreateKernel(program, "ckf_kernel2", &ret); 
    ASSERT_CL(ret, "Cannot load ckf_kernel2!"); 
    memset_kernel[0] = clCreateKernel(program, "memset_zero", &ret); 
    ASSERT_CL(ret, "Cannot load memset_kernel!"); 
    cholesky_kernel[0] = clCreateKernel(program, "cholesky_kernel", &ret); 
    ASSERT_CL(ret, "Cannot load cholesky_kernel!"); 
    
    ckf_kernel1[1] = clCreateKernel(program, "ckf_kernel1", &ret); 
    ASSERT_CL(ret, "Cannot load ckf_kernel1[i]!"); 
    ckf_kernel2[1] = clCreateKernel(program, "ckf_kernel2", &ret); 
    ASSERT_CL(ret, "Cannot load ckf_kernel2!"); 
    memset_kernel[1] = clCreateKernel(program, "memset_zero", &ret); 
    ASSERT_CL(ret, "Cannot load memset_kernel!"); 
    cholesky_kernel[1] = clCreateKernel(program, "cholesky_kernel", &ret); 
    ASSERT_CL(ret, "Cannot load cholesky_kernel!"); 
    

Теперь, я не знаю, как запустить ядра на различные устройства внутри цикла. Как заставить их выполнять параллельно? Обратите внимание, что в этом цикле есть команда clFinish.

Другой вопрос: стандартная практика заключается в использовании нескольких потоков/процессов на хосте, где каждый поток/процесс отвечает за запуск ядер на одном графическом процессоре?

+0

Вы перекрываете связь с вычислениями или совместно используете одно и то же общее? –

+0

Можете ли вы немного уточнить, я не уверен в вашем вопросе. – user1274878

+0

«Стандартная практика заключается в использовании нескольких потоков/процессов на хосте, где каждый thread/process отвечает за запуск ядер на одном GPU? »- не типично. – Dithermaster

ответ

3
  1. Вам не нужно создавать отдельные контексты для всех устройств. Вам нужно только это, если они с разных платформ.
  2. Вам также не нужно создавать отдельные ядра. Вы можете скомпилировать свои ядра для нескольких устройств одновременно (clBuildProgram поддерживает компиляцию нескольких устройств), и если вы запустите ядро ​​на устройстве, среда выполнения будет знать, что объект ядра хранит двоичный файл устройства, действительный для данного устройства, или нет.
  3. Простейшая вещь: создать контекст, получить все необходимые вам устройства, поместить их в массив и использовать этот массив для построения ваших ядер и создать один файл command_queue для каждого устройства в них.
  4. Ядро clEnqueueNDRange не блокирует. Единственная причина, по которой ваш цикл for не проходит, - это из-за членов clFinish(), и, скорее всего, из-за того, что вы используете очередь в очереди, а это значит, что случай с одним устройством будет работать отлично без clFinish.

Общая идея наилучшего использования многопроцессорных графических процессоров в OpenCL - создание очереди-ядер-ядер так, как я упоминал, и сделать очереди не в порядке. Таким образом, команды могут выполняться параллельно, если у них нет неудовлетворенных зависимостей, например. ввод команды2 не является результатом команды 1, тогда он может начать выполнение параллельно с командой1. Однако, если вы используете этот метод, вам нужно использовать последние несколько параметров для clEnqueueNDRangeKernels, потому что вам нужно построить эту цепочку зависимостей, используя cl_events. Каждый clEnqueueWhatever может ждать на множестве событий, которые исходят из других команд. Выполнение команды в очереди запускается только после выполнения всех ее зависимостей.

Существует одна проблема, которую вы не затронули, и это идея буферов. Если вы хотите запускать несколько GPU, вам нужно явно создавать буферы для ваших устройств по отдельности и разбивать свои данные. Недопустимо иметь тот же буфер, что и аргумент, на двух устройствах, в то время как оба они пытаются его записать. В лучшем случае среда выполнения сериализует вашу работу, и два устройства не будут работать параллельно. Это связано с тем, что буферы обрабатывают память, а среда выполнения отвечает за перемещение содержимого буфера на устройства, которые в нем нуждаются. (Это может происходить неявно (ленивое движение памяти) или явно, если вы вызываете clEnqueueMigrateBuffer.) Среда исполнения запрещается предоставлять один и тот же буфер с флажками CL_MEM_READ_WRITE или CL_MEM_WRITE_ONLY на 2 устройства одновременно. Несмотря на то, что вы знаете, как программист, что 2 устройства могут не писать одну и ту же часть буфера, среда выполнения не работает. Вы должны это сказать. Элегантный способ - создать 2 суббуфера, которые являются частью более крупного/оригинального буфера; менее элегантный способ состоит в том, чтобы просто создать 2 буфера. Первый подход лучше, потому что легче собрать данные с нескольких устройств обратно на хост, потому что вам нужно извлечь только большой буфер, а среда выполнения будет знать, какие суббуферы были изменены на каких устройствах, и это займет заботиться о сборе данных.

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

+0

Я не согласен с точкой 2. Это может быть не обязательно, но, как правило, это хорошая практика, чтобы упростить вызовы setArgs(). Я согласен с остальными. – DarkZeros

+0

@Meteorhead: Я показал свои аргументы ядра здесь: http://pastebin.com/TuxraFs4 Если вы можете помочь мне с примером, это будет замечательно – user1274878

+0

Вот что я придумал: http: // pastebin. com/idgJhbed Он не будет компилироваться, но вы получите эту идею. Судя по вашему setKernelArgs, я чувствую, что ваши ядра полностью взаимозависимы, поэтому никто не может взять на себя ядро ​​перед ним. Изучение с использованием событий, хотя будет полезно позже. Кроме того, у меня есть ощущение, что вы слишком свободно используете CL_MEM_READ_WRITE. Если это возможно, ограничьте свои буферы READ_ONLY/WRITE_ONLY. temp_var должен быть буфером, но не видел его объявленным. – Meteorhead

Смежные вопросы