Различия между NV CC и NVRT C при компиляции в PTX - PullRequest
0 голосов
/ 01 апреля 2020

Сводка

Я портирую простое приложение трассировки лучей на основе Scratchapixel версии на несколько библиотек графических процессоров. Я успешно перенес его в CUDA, используя API времени выполнения и API драйвера, но он выдает Segmentation fault (core dumped), когда я пытаюсь использовать PTX, скомпилированный во время выполнения с NVRT C. Если я раскомментирую директиву #include <math.h> в начале файла ядра (см. Ниже), она все равно будет работать с использованием NV CC (сгенерированный PTX точно такой же), но при компиляции с использованием NVRT C.

произойдет сбой.

Я хочу знать, как заставить NVRT C вести себя так же, как NV CC (возможно ли это?), Или хотя бы понять причину этой проблемы.

Подробное описание

Файл kernel.cu (источник ядра):

//#include <math.h>

#define MAX_RAY_DEPTH 5

template<typename T>
class Vec3
{
public:
    T x, y, z;
    __device__ Vec3() : x(T(0)), y(T(0)), z(T(0)) {}
    __device__ Vec3(T xx) : x(xx), y(xx), z(xx) {}
    __device__ Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {}
    __device__ Vec3& normalize()
    {
        T nor2 = length2();
        if (nor2 > 0) {
            T invNor = 1 / sqrt(nor2);
            x *= invNor, y *= invNor, z *= invNor;
        }
        return *this;
    }
    __device__ Vec3<T> operator * (const T &f) const { return Vec3<T>(x * f, y * f, z * f); }
    __device__ Vec3<T> operator * (const Vec3<T> &v) const { return Vec3<T>(x * v.x, y * v.y, z * v.z); }
    __device__ T dot(const Vec3<T> &v) const { return x * v.x + y * v.y + z * v.z; }
    __device__ Vec3<T> operator - (const Vec3<T> &v) const { return Vec3<T>(x - v.x, y - v.y, z - v.z); }
    __device__ Vec3<T> operator + (const Vec3<T> &v) const { return Vec3<T>(x + v.x, y + v.y, z + v.z); }
    __device__ Vec3<T>& operator += (const Vec3<T> &v) { x += v.x, y += v.y, z += v.z; return *this; }
    __device__ Vec3<T>& operator *= (const Vec3<T> &v) { x *= v.x, y *= v.y, z *= v.z; return *this; }
    __device__ Vec3<T> operator - () const { return Vec3<T>(-x, -y, -z); }
    __device__ T length2() const { return x * x + y * y + z * z; }
    __device__ T length() const { return sqrt(length2()); }
};

typedef Vec3<float> Vec3f;
typedef Vec3<bool> Vec3b;

class Sphere
{
public:
    const char* id;
    Vec3f center;                           /// position of the sphere
    float radius, radius2;                  /// sphere radius and radius^2
    Vec3f surfaceColor, emissionColor;      /// surface color and emission (light)
    float transparency, reflection;         /// surface transparency and reflectivity
    int animation_frame;
    Vec3b animation_position_rand;
    Vec3f animation_position;
    Sphere(
        const char* id,
        const Vec3f &c,
        const float &r,
        const Vec3f &sc,
        const float &refl = 0,
        const float &transp = 0,
        const Vec3f &ec = 0) :
        id(id), center(c), radius(r), radius2(r * r), surfaceColor(sc),
        emissionColor(ec), transparency(transp), reflection(refl)
    {
        animation_frame = 0;
    }
    //[comment]
    // Compute a ray-sphere intersection using the geometric solution
    //[/comment]
    __device__ bool intersect(const Vec3f &rayorig, const Vec3f &raydir, float &t0, float &t1) const
    {
        Vec3f l = center - rayorig;
        float tca = l.dot(raydir);
        if (tca < 0) return false;
        float d2 = l.dot(l) - tca * tca;
        if (d2 > radius2) return false;
        float thc = sqrt(radius2 - d2);
        t0 = tca - thc;
        t1 = tca + thc;

        return true;
    }
};

__device__ float mix(const float &a, const float &b, const float &mixval)
{
    return b * mixval + a * (1 - mixval);
}

__device__ Vec3f trace(
    const Vec3f &rayorig,
    const Vec3f &raydir,
    const Sphere *spheres,
    const unsigned int spheres_size,
    const int &depth)
{
    float tnear = INFINITY;
    const Sphere* sphere = NULL;
    // find intersection of this ray with the sphere in the scene
    for (unsigned i = 0; i < spheres_size; ++i) {
        float t0 = INFINITY, t1 = INFINITY;
        if (spheres[i].intersect(rayorig, raydir, t0, t1)) {
            if (t0 < 0) t0 = t1;
            if (t0 < tnear) {
                tnear = t0;
                sphere = &spheres[i];
            }
        }
    }
    // if there's no intersection return black or background color
    if (!sphere) return Vec3f(2);
    Vec3f surfaceColor = 0; // color of the ray/surfaceof the object intersected by the ray
    Vec3f phit = rayorig + raydir * tnear; // point of intersection
    Vec3f nhit = phit - sphere->center; // normal at the intersection point
    nhit.normalize(); // normalize normal direction
    // If the normal and the view direction are not opposite to each other
    // reverse the normal direction. That also means we are inside the sphere so set
    // the inside bool to true. Finally reverse the sign of IdotN which we want
    // positive.
    float bias = 1e-4; // add some bias to the point from which we will be tracing
    bool inside = false;
    if (raydir.dot(nhit) > 0) nhit = -nhit, inside = true;
    if ((sphere->transparency > 0 || sphere->reflection > 0) && depth < MAX_RAY_DEPTH) {
        float facingratio = -raydir.dot(nhit);
        // change the mix value to tweak the effect
        float fresneleffect = mix(pow(1 - facingratio, 3), 1, 0.1);
        // compute reflection direction (not need to normalize because all vectors
        // are already normalized)
        Vec3f refldir = raydir - nhit * 2 * raydir.dot(nhit);
        refldir.normalize();
        Vec3f reflection = trace(phit + nhit * bias, refldir, spheres, spheres_size, depth + 1);
        Vec3f refraction = 0;
        // if the sphere is also transparent compute refraction ray (transmission)
        if (sphere->transparency) {
            float ior = 1.1, eta = (inside) ? ior : 1 / ior; // are we inside or outside the surface?
            float cosi = -nhit.dot(raydir);
            float k = 1 - eta * eta * (1 - cosi * cosi);
            Vec3f refrdir = raydir * eta + nhit * (eta *  cosi - sqrt(k));
            refrdir.normalize();
            refraction = trace(phit - nhit * bias, refrdir, spheres, spheres_size, depth + 1);
        }
        // the result is a mix of reflection and refraction (if the sphere is transparent)
        surfaceColor = (
            reflection * fresneleffect +
            refraction * (1 - fresneleffect) * sphere->transparency) * sphere->surfaceColor;
    }
    else {
        // it's a diffuse object, no need to raytrace any further
        for (unsigned i = 0; i < spheres_size; ++i) {
            if (spheres[i].emissionColor.x > 0) {
                // this is a light
                Vec3f transmission = 1;
                Vec3f lightDirection = spheres[i].center - phit;
                lightDirection.normalize();
                for (unsigned j = 0; j < spheres_size; ++j) {
                    if (i != j) {
                        float t0, t1;
                        if (spheres[j].intersect(phit + nhit * bias, lightDirection, t0, t1)) {
                            transmission = 0;
                            break;
                        }
                    }
                }
                surfaceColor += sphere->surfaceColor * transmission *
                max(float(0), nhit.dot(lightDirection)) * spheres[i].emissionColor;
            }
        }
    }

    return surfaceColor + sphere->emissionColor;
}

extern "C" __global__
void raytrace_kernel(unsigned int width, unsigned int height, Vec3f *image, Sphere *spheres, unsigned int spheres_size, float invWidth, float invHeight, float aspectratio, float angle) {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;

    if (y < height && x < width) {
        float xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
        float yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
        Vec3f raydir(xx, yy, -1);
        raydir.normalize();
        image[y*width+x] = trace(Vec3f(0), raydir, spheres, spheres_size, 0);
    }
}

Я могу успешно скомпилировать его с: nvcc --ptx kernel.cu -o kernel.ptx ( полный PTX здесь ) и использовать этот PTX в API драйвера с cuModuleLoadDataEx с использованием следующего фрагмента. Он работает, как и ожидалось.

Работает нормально, даже если я раскомментирую строку #include <math.h> (фактически, сгенерированный PTX точно такой же).

CudaSafeCall( cuInit(0) );

CUdevice device;
CudaSafeCall( cuDeviceGet(&device, 0) );

CUcontext context;
CudaSafeCall( cuCtxCreate(&context, 0, device) );

unsigned int error_buffer_size = 1024;
std::vector<CUjit_option> options;
std::vector<void*> values;
char* error_log = new char[error_buffer_size];
options.push_back(CU_JIT_ERROR_LOG_BUFFER); //Pointer to a buffer in which to print any log messages that reflect errors
values.push_back(error_log);
options.push_back(CU_JIT_ERROR_LOG_BUFFER_SIZE_BYTES); //Log buffer size in bytes. Log messages will be capped at this size (including null terminator)
values.push_back(&error_buffer_size);
options.push_back(CU_JIT_TARGET_FROM_CUCONTEXT); //Determines the target based on the current attached context (default)
values.push_back(0); //No option value required for CU_JIT_TARGET_FROM_CUCONTEXT

CUmodule module;
CUresult status = cuModuleLoadDataEx(&module, ptxSource, options.size(), options.data(), values.data());
if (error_log && error_log[0]) { //https://stackoverflow.com/a/7970669/3136474
    std::cout << "Compiler error: " << error_log << std::endl;
}
CudaSafeCall( status );

Однако всякий раз, когда я пытаюсь скомпилируйте это точное ядро, используя NVRT C ( полный PTX здесь ), оно успешно компилируется, но дает мне Segmentation fault (core dumped) при вызове cuModuleLoadDataEx (при попытке использовать полученный PTX).

Если я раскомментирую строку #include <math.h>, она завершится неудачно при вызове nvrtcCompileProgram со следующим выводом:

nvrtcSafeBuild() failed at cuda_raytracer_nvrtc_api.cpp:221 : NVRTC_ERROR_COMPILATION
Build log:
/usr/include/bits/mathcalls.h(177): error: linkage specification is incompatible with previous "isinf"
__nv_nvrtc_builtin_header.h(126689): here

/usr/include/bits/mathcalls.h(211): error: linkage specification is incompatible with previous "isnan"
__nv_nvrtc_builtin_header.h(126686): here

2 errors detected in the compilation of "kernel.cu".

Код, который я использую для компиляции с NVRT C: :

nvrtcProgram prog;
NvrtcSafeCall( nvrtcCreateProgram(&prog, kernelSource, "kernel.cu", 0, NULL, NULL) );

// https://docs.nvidia.com/cuda/nvrtc/index.html#group__options
std::vector<const char*> compilationOpts;
compilationOpts.push_back("--device-as-default-execution-space");
// NvrtcSafeBuild is a macro which automatically prints nvrtcGetProgramLog if the compilation fails
NvrtcSafeBuild( nvrtcCompileProgram(prog, compilationOpts.size(), compilationOpts.data()), prog );

size_t ptxSize;
NvrtcSafeCall( nvrtcGetPTXSize(prog, &ptxSize) );
char* ptxSource = new char[ptxSize];
NvrtcSafeCall( nvrtcGetPTX(prog, ptxSource) );

NvrtcSafeCall( nvrtcDestroyProgram(&prog) );

Затем я просто загружаю ptxSource, используя предыдущий фрагмент (примечание: этот блок кода используется как для версии API драйвера, так и для версии NVRT C).

Дополнительные вещи, которые я заметил / попробовал до сих пор

  1. PTX , сгенерированный NV CC и , сгенерированный NVRT C довольно разные, но я не могу понять их, чтобы определить возможные проблемы.
  2. Попытался указать т Он указывает c архитектуру графического процессора (в моем случае CC 6.1) для компилятора, без разницы.
  3. Попытка отключить любые оптимизации компилятора (параметры --ftz=false --prec-sqrt=true --prec-div=true --fmad=false в nvrtcCompileProgram). Файл PTX стал больше, но все равно Segfaulting .
  4. Попытался добавить --std=c++11 или --std=c++14 в опции компилятора NVRT C. С любым из них NVRT C генерирует почти пустой (4 строки) PTX, но не выдает ни предупреждений, ни ошибок, пока я не попытаюсь его использовать.

Environment

  • SO : Ubuntu 18.04.4 LTS 64-bit
  • nvcc --version: инструменты для компиляции Cuda, выпуск 10.1, V10.1.168. Построен на Wed_Apr_24_19: 10: 27_PDT_2019
  • gcc --version: g cc (Ubuntu 7.5.0-3ubuntu1 ~ 18.04) 7.5.0
  • Оборудование: Intel I7-7700HQ, GeForce GTX 1050 Ti

Редактировать при OP + 1 день

Я забыл добавить свою среду. Смотрите предыдущий раздел.

Также вы можете скомпилировать вывод nvrt c с помощью ptxas? - комментарий @talonmies

PTX, сгенерированный nvcc, компилируется с предупреждением:

$ ptxas -o /tmp/temp_ptxas_output.o kernel.ptx
ptxas warning : Stack size for entry function 'raytrace_kernel' cannot be statically determined

, что связано с рекурсивной функцией ядра ( подробнее об этом ). Его можно безопасно игнорировать.

Сгенерированный nvrtc PTX не компилируется и выдает ошибку:

$ ptxas -o /tmp/temp_ptxas_output.o nvrtc_kernel.ptx
ptxas fatal   : Unresolved extern function '_Z5powiffi'

На основании этого вопроса Я добавил __device__ в Sphere конструктор класса и удалил --device-as-default-execution-space опцию компилятора. Теперь он генерирует немного другой PTX, но все равно выдает ту же ошибку.

Компиляция с #include <math.h> теперь генерирует много «Функция без аннотаций пространства выполнения считается функцией хоста, а функции хоста не являются разрешено в режиме JIT. " Предупреждения, кроме предыдущих ошибок.

Если я попытаюсь использовать принятое решение вопроса , оно выдаст мне кучу синтаксических ошибок и не скомпилируется. NV CC по-прежнему работает без нареканий.

1 Ответ

1 голос
/ 02 апреля 2020

Только что нашли виновника с помощью древнего метода комментирования и проверки : ошибка исчезнет, ​​если я уберу вызов pow, используемый для вычисления эффекта Френеля внутри метода trace.

Сейчас я только что заменил pow(var, 3) на var*var*var.

Я создал MVCE и заполнил отчет об ошибках в NVIDIA: https://developer.nvidia.com/nvidia_bug/2917596 .

На что Лиам Чжан ответил и указал мне на проблему:

Проблема в вашем коде заключается в том, что в cuModuleLoadDataEx передается неверное значение параметра. В строках:

options.push_back(CU_JIT_ERROR_LOG_BUFFER_SIZE_BYTES); //Log buffer size in bytes. Log messages will be capped at this size (including null terminator)
values.push_back(&error_buffer_size);

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

Во время компиляции NVRT C произошла ошибка "Unresolved extern function", потому что сигнатура функции pow, как Вы можете найти в документации следующее:
__device__​ double pow ( double x, double y )
Когда драйвер пытался обнулить буфер, помещая в него сообщение об ошибке, произошла ошибка.
Без вызова pow компиляции не было ошибка, поэтому буфер ошибок не использовался и не было сегфоута.

Чтобы код устройства был правильным, значения, используемые для вызова функции pow, а также выходной указатель, должны быть двойным числом или Можно использовать эквивалентную функцию с плавающей точкой, powf.

Если я изменю вызов на values.push_back((void*)error_buffer_size);, он сообщит о той же ошибке, что и ptxas компиляция сгенерированного PTX:

Compiler error: ptxas fatal   : Unresolved extern function '_Z5powiffi'
cudaSafeCall() failed at file.cpp:74 : CUDA_ERROR_INVALID_PTX - a PTX JIT compilation failed
...