В документации CUDA упоминается, что если мы используем 2 потока (stream0 и stream1) вот так: мы копируем данные в stream0, затем запускаем первое ядро в stream0, затем восстанавливаем данные с устройства в stream0, а затем те же операции выполняются в потоке1, таким образом, как указано в книге "CUDA на примере 2010", не предлагается параллельное выполнение, но в "примере параллельных ядер" этот метод используется и предлагает параллельное выполнение. Итак, не могли бы вы помочь мне понять разницу между двумя примерами?
Как потоки могут предлагать параллельное выполнение в CUDA?
Ответы (2)
Перекрывающаяся передача данных зависит от многих факторов, включая версию вычислительных возможностей и стили кодирования. Этот блог может предоставить дополнительную информацию.
https://developer.nvidia.com/content/how-overlap-data-transfers-cuda-cc
Я просто расширяю ответ Эрика.
В Руководстве по программированию CUDA C приводится пример использования 2
потоков, скажем, stream0
и stream1
, для выполнения следующих действий.
СЛУЧАЙ А
memcpyHostToDevice --- stream0
kernel execution --- stream0
memcpyDeviceToHost --- stream0
memcpyHostToDevice --- stream1
kernel execution --- stream1
memcpyDeviceToHost --- stream1
Другими словами, сначала выполняются все операции stream0
, а затем операции, относящиеся к stream1
. Тот же самый пример описан в книге "CUDA By Example", раздел 10.5, но "очевидно" сделан вывод (в "очевидном" противоречии с руководством), что параллелизм таким образом не достигается.
В разделе 10.6 «CUDA By Example» предлагается следующее альтернативное использование потоков.
СЛУЧАЙ Б
memcpyHostToDevice --- stream0
memcpyHostToDevice --- stream1
kernel execution --- stream0
kernel execution --- stream1
memcpyDeviceToHost --- stream0
memcpyDeviceToHost --- stream1
Другими словами, операции копирования памяти и выполнения ядра stream0
и stream1
теперь чередуются. В книге показано, как с помощью этого решения достигается параллелизм.
На самом деле, нет никакого противоречия между книгой «CUDA By Example» и руководством по программированию на CUDA C, поскольку обсуждение в книге было проведено с конкретной ссылкой на карту GTX 285, в то время как, как уже указывал Эрик и в цитируемое сообщение в блоге Как перекрыть передачу данных в CUDA C/C++ параллелизм может быть достигнут по-разному на разных архитектурах в результате наличия зависимостей и механизмов копирования.
Например, в блоге рассматриваются две карты: C1060 и C2050. Первый имеет один механизм ядра и один механизм копирования, который может выполнять только одну транзакцию памяти (H2D или D2H) за раз. Последний имеет один механизм ядра и два механизма копирования, которые могут одновременно выполнять две транзакции памяти (H2D и D2H). Что происходит для C1060, имеющего только один механизм копирования, следующее:
СЛУЧАЙ A – C1060 – ОТСУТСТВИЕ ПАРАЛЛЕЛЬНОСТИ
Stream Kernel engine Copy engine Comment
stream0 ---- memcpyHostToDevice ----
stream0 ---- kernel execution ---- Depends on previous memcpy
stream0 ---- memcpyDeviceToHost ---- Depends on previous kernel
stream1 ---- memcpyHostToDevice ----
stream1 ---- kernel execution ---- Depends on previous memcpy
stream1 ---- memcpyDeviceToHost ---- Depends on previous kernel
СЛУЧАЙ B – C1060 – ДОСТИГНУТО СОГЛАСИЕ
Stream Kernel engine Copy engine Comment
stream0 ---- memcpyHostToDevice 0 ----
stream0/1 ---- Kernel execution 0 ---- memcpyHostToDevice 1 ----
stream0/1 ---- Kernel execution 1 ---- memcpyDeviceToHost 0 ----
stream1 ---- memcpyDeviceToHost 1 ----
Что касается C2050 и при рассмотрении случая 3
потоков, в CASE A теперь достигается параллелизм, в отличие от C1060.
СЛУЧАЙ A – C2050 — ДОСТИГНУТО ОДНОРОДНОСТЬ
Stream Kernel engine Copy engine H2D Copy engine D2H
stream0 ---- memcpyHostToDevice 0 ----
stream0/1 ---- kernel execution 0 ---- memcpyHostToDevice 1 ----
stream0/1/2 ---- kernel execution 1 ---- memcpyHostToDevice 2 ---- memcpyDeviceToHost 0
stream0/1/2 ---- kernel execution 2 ---- memcpyDeviceToHost 1
stream2 ---- memcpyDeviceToHost 2