Это может быть простой ответ. Программы CUDA часто используют тип переменной с плавающей точкой, поскольку она может быть значительно быстрее, чем double. Порядок, в котором оцениваются операции, может существенно повлиять на конечное значение вычисления с плавающей запятой; это не уникально для CUDA, но вы можете заметить эффекты особенно остро, так как это такая паралигма с массивным параллелизмом (а с параллелизмом возникает недетерминизм, по крайней мере при выполнении таких вещей, как глобальные сокращения).
РЕДАКТИРОВАТЬ: Просто чтобы прояснить, это является необходимым (хотя и недостаточным) условием, чтобы CUDA не гарантировала, что одно и то же ядро будет выполняться в одном и том же порядке при нескольких выполнениях. Если CUDA действительно гарантирует это, то не должно быть возможным, чтобы порядок выполнения арифметических операций варьировался от прогона к прогону, и поэтому не следует ожидать увидеть разные значения для одного и того же вычисления с плавающей запятой.
Вот простая программа на C, демонстрирующая вышеуказанное утверждение. Попробуйте код
#include <stdio.h>
int main()
{
float a = 100.0f, b = 0.00001f, c = 0.00001f;
printf("a + b + c = %f\n", a + b + c);
printf("b + c + a = %f\n", b + c + a);
printf("a + b + c == b + c + a ? %d\n", (a + b + c) == (b + c + a));
return 0;
}
в Linux и посмотрите, что вы получите (я использую 64-битный RHEL 6 и gcc версии 4.4.4-13). Мой вывод следующий:
[user@host directory]# gcc add.c -o add
[user@host directory]# ./add
a + b + c = 100.000015
b + c + a = 100.000023
a + b + c == b + c + a ? 0
РЕДАКТИРОВАТЬ: обратите внимание, что, хотя эта программа может предположить, что основная проблема заключается в том, что сложение с плавающей точкой некоммутативно, на самом деле сложение с плавающей точкой неассоциативно (поскольку C оценивает операции сложения слева направо верно, так уж получилось, что первое сложение выполняется как (a + b) + c, а второе - как (b + c) + a). Причиной неассоциативности является то, что представления с плавающей точкой могут представлять только конечное число значащих цифр (в базе 2, но обсуждение системы с базой 10 по существу эквивалентно). Например, если можно представить только три значащие цифры, мы получим (100 + 0,5) + 0,5 = 100 + 0,5 = 100, тогда как 100 + (0,5 + 0,5) = 100 + 1 = 101. В первом случае промежуточный результат 100 + 0,5 должен быть усечен (или, возможно, округлен в большую сторону), поскольку невозможно представить промежуточное значение 100,5 только с тремя значащими цифрами.
Существует ряд важных последствий этого явления; например, вы получите более точный ответ, добавив числа в порядке возрастания размера (экспонента). Реальный вывод состоит в том, что вы не должны ожидать, что результаты будут идентичны, если только вычисления не выполняются в том же порядке, что может быть трудно гарантировать при использовании CUDA на реальном GPU.