在CUDA NVRTC代码中包含C标准头文件

6
我是一名有用的助手,可以为您进行翻译。以下是需要翻译的内容:

我正在编写一个CUDA内核,使用NVRTC(CUDA版本9.2与NVRTC版本7.5)在运行时编译,需要包含stdint.h头文件,以便具有int32_t等类型。

如果我在内核源代码中不包含此头文件,则它可以正常工作。例如内核:

extern "C" __global__ void f() { ... }

将f定义为.visible .entry f,编译成PTX代码。

但如果内核源代码是

#include <stdint.h>
extern "C" __global__ void f() { ... }

IT报告:没有执行空间注释(__host__/__device__/__global__)的函数被视为主机函数,主机函数在JIT模式下不允许。(也没有extern "C")。

传递-default-device使PTX代码.visible .func f,因此该函数无法从主机调用。

是否有一种方法在源代码中包含头文件,并仍然具有__global__入口函数?或者另一种方法是知道NVRTC编译器使用哪种整数大小约定,以便可以手动定义int32_t等类型?

编辑: 显示问题的示例程序:

#include <cstdlib>
#include <string>
#include <vector>
#include <memory>
#include <cassert>
#include <iostream>

#include <cuda.h>
#include <cuda_runtime.h>
#include <nvrtc.h>

[[noreturn]] void fail(const std::string& msg, int code) {
    std::cerr << "error: " << msg << " (" << code << ')' << std::endl;
    std::exit(EXIT_FAILURE);
}


std::unique_ptr<char[]> compile_to_ptx(const char* program_source) {
    nvrtcResult rv;

    // create nvrtc program
    nvrtcProgram prog;
    rv = nvrtcCreateProgram(
        &prog,
        program_source,
        "program.cu",
        0,
        nullptr,
        nullptr
    );
    if(rv != NVRTC_SUCCESS) fail("nvrtcCreateProgram", rv);

    // compile nvrtc program
    std::vector<const char*> options = {
        "--gpu-architecture=compute_30"
    };
    //options.push_back("-default-device");
    rv = nvrtcCompileProgram(prog, options.size(), options.data());
    if(rv != NVRTC_SUCCESS) {
        std::size_t log_size;
        rv = nvrtcGetProgramLogSize(prog, &log_size);
        if(rv != NVRTC_SUCCESS) fail("nvrtcGetProgramLogSize", rv);

        auto log = std::make_unique<char[]>(log_size);
        rv = nvrtcGetProgramLog(prog, log.get());
        if(rv != NVRTC_SUCCESS) fail("nvrtcGetProgramLog", rv);
        assert(log[log_size - 1] == '\0');

        std::cerr << "Compile error; log:\n" << log.get() << std::endl;

        fail("nvrtcCompileProgram", rv);
    }

    // get ptx code
    std::size_t ptx_size;
    rv = nvrtcGetPTXSize(prog, &ptx_size);
    if(rv != NVRTC_SUCCESS) fail("nvrtcGetPTXSize", rv);

    auto ptx = std::make_unique<char[]>(ptx_size);
    rv = nvrtcGetPTX(prog, ptx.get());
    if(rv != NVRTC_SUCCESS) fail("nvrtcGetPTX", rv);
    assert(ptx[ptx_size - 1] == '\0');

    nvrtcDestroyProgram(&prog);

    return ptx;
}

const char program_source[] = R"%%%(
//#include <stdint.h>
extern "C" __global__ void f(int* in, int* out) {
    out[threadIdx.x] = in[threadIdx.x];
}
)%%%";

int main() {
    CUresult rv;

    // initialize CUDA
    rv = cuInit(0);
    if(rv != CUDA_SUCCESS) fail("cuInit", rv);

    // compile program to ptx
    auto ptx = compile_to_ptx(program_source);
    std::cout << "PTX code:\n" << ptx.get() << std::endl;
}

如果在内核源代码中取消注释//#include <stdint.h>,则无法编译。如果取消注释//options.push_back("-default-device");,则可以编译,但不会将函数f标记为.entry

CMakeLists.txt用于编译它(需要CUDA驱动程序API + NVRTC)。

cmake_minimum_required(VERSION 3.4)
project(cudabug CXX)

find_package(CUDA REQUIRED)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED 14)

add_executable(cudabug cudabug.cc)
include_directories(SYSTEM ${CUDA_INCLUDE_DIRS})
link_directories(${CUDA_LIBRARY_DIRS})
target_link_libraries(cudabug PUBLIC ${CUDA_LIBRARIES} nvrtc cuda)

1
你是否有一个实际的复现案例可以编辑到你的问题中? - talonmies
如果只是关于int32_t,使用int32_t = int应该就可以解决问题。如果是关于头文件的一般性问题,这个问题比较广泛但非常有趣:) - Regis Portalez
添加了示例程序 - tmlen
2个回答

2

[前言:这是一种非常hacky的答案,仅适用于GNU工具链(尽管我怀疑问题也特定于GNU工具链)]。

这里的问题似乎与GNU标准头文件“features.h”有关,该文件被引入到“stdint.h”中,然后定义了许多默认为__host__编译空间的存根函数。这会导致nvrtc崩溃。另外,似乎-default-device选项将导致解析glibC编译器功能集,从而使整个nvrtc编译器失败。

您可以通过预定义排除所有主机函数的标准库功能集来解决此问题(以非常hacky的方式)。将JIT内核代码更改为:

const char program_source[] = R"%%%(
#define __ASSEMBLER__
#define __extension__
#include <stdint.h>
extern "C" __global__ void f(int32_t* in, int32_t* out) {
    out[threadIdx.x] = in[threadIdx.x];
}
)%%%";

得到了这个:

$ nvcc -std=c++14 -ccbin=g++-7 jit_header.cu -o jitheader -lnvrtc -lcuda
$ ./jitheader 
PTX code:
//
// Generated by NVIDIA NVVM Compiler
//
// Compiler Build ID: CL-24330188
// Cuda compilation tools, release 9.2, V9.2.148
// Based on LLVM 3.4svn
//

.version 6.2
.target sm_30
.address_size 64

    // .globl   f

.visible .entry f(
    .param .u64 f_param_0,
    .param .u64 f_param_1
)
{
    .reg .b32   %r<3>;
    .reg .b64   %rd<8>;


    ld.param.u64    %rd1, [f_param_0];
    ld.param.u64    %rd2, [f_param_1];
    cvta.to.global.u64  %rd3, %rd2;
    cvta.to.global.u64  %rd4, %rd1;
    mov.u32     %r1, %tid.x;
    mul.wide.u32    %rd5, %r1, 4;
    add.s64     %rd6, %rd4, %rd5;
    ld.global.u32   %r2, [%rd6];
    add.s64     %rd7, %rd3, %rd5;
    st.global.u32   [%rd7], %r2;
    ret;
}

重要提示:此方法在我测试的glibC系统上有效。如果使用其他工具链或libC实现(如果它们确实存在此问题),可能无法正常工作。


1

另一种选择是创建可替代品,用于一些标准库头文件。NVRTC的API支持您将头文件内容作为字符串与头文件名称相关联 - 在它为您搜索文件系统之前。这种方法在NVIDIA JITify中被采用,在我自己从事的其他工作中也采用了这种方法,该工作可能已发布或未发布。

这样做的简单方法是,您只需从这里获取JITify头文件存根stdint.hlimits.h,我也附上它们,因为它们不是很长。或者,您可以自己生成此存根,以确保您没有错过来自标准的任何相关内容。以下是该过程的具体操作:

从你的 stdint.h 文件开始(或根据情况使用 cstdint 文件); 对于文件中的每个包含指令(以及递归地处理每个包含指令等): 2.1 弄清楚是否可以完全跳过包含该文件(可能通过制作已知在 GPU 上保持的少量定义来实现)。 2.2 如果不确定是否可以跳过该文件-完全包含它并递归到(2.),或将其保留为自己单独的标头(并对其应用(1.)中的整个过程)。 现在你有一个只包括设备安全的头文件(或根本没有)。 部分预处理文件,在GPU上不会被使用的所有内容都要删除 删除在GPU上可能有问题的行(例如#pragma),并根据需要向每个函数声明添加__device__ __host____host__

重要提示:进行此操作需要注意许可证和版权。您将创建 glibc 和/或 JITify 和/或 StackOverflow 贡献等的“派生作品”。


现在,我承诺从NVIDIA JITify中提供的stdint.hlimits.h。我已经适应它们,使它们没有命名空间:

stdint.h:

#pragma once
#include <limits.h>
typedef signed char      int8_t;
typedef signed short     int16_t;
typedef signed int       int32_t;
typedef signed long long int64_t;
typedef signed char      int_fast8_t;
typedef signed short     int_fast16_t;
typedef signed int       int_fast32_t;
typedef signed long long int_fast64_t;
typedef signed char      int_least8_t;
typedef signed short     int_least16_t;
typedef signed int       int_least32_t;
typedef signed long long int_least64_t;
typedef signed long long intmax_t;
typedef signed long      intptr_t; //optional
typedef unsigned char      uint8_t;
typedef unsigned short     uint16_t;
typedef unsigned int       uint32_t;
typedef unsigned long long uint64_t;
typedef unsigned char      uint_fast8_t;
typedef unsigned short     uint_fast16_t;
typedef unsigned int       uint_fast32_t;
typedef unsigned long long uint_fast64_t;
typedef unsigned char      uint_least8_t;
typedef unsigned short     uint_least16_t;
typedef unsigned int       uint_least32_t;
typedef unsigned long long uint_least64_t;
typedef unsigned long long uintmax_t;
#define INT8_MIN    SCHAR_MIN
#define INT16_MIN   SHRT_MIN
#if defined _WIN32 || defined _WIN64
#define WCHAR_MIN   SHRT_MIN
#define WCHAR_MAX   SHRT_MAX
typedef unsigned long long uintptr_t; //optional
#else
#define WCHAR_MIN   INT_MIN
#define WCHAR_MAX   INT_MAX
typedef unsigned long      uintptr_t; //optional
#endif
#define INT32_MIN   INT_MIN
#define INT64_MIN   LLONG_MIN
#define INT8_MAX    SCHAR_MAX
#define INT16_MAX   SHRT_MAX
#define INT32_MAX   INT_MAX
#define INT64_MAX   LLONG_MAX
#define UINT8_MAX   UCHAR_MAX
#define UINT16_MAX  USHRT_MAX
#define UINT32_MAX  UINT_MAX
#define UINT64_MAX  ULLONG_MAX
#define INTPTR_MIN  LONG_MIN
#define INTMAX_MIN  LLONG_MIN
#define INTPTR_MAX  LONG_MAX
#define INTMAX_MAX  LLONG_MAX
#define UINTPTR_MAX ULONG_MAX
#define UINTMAX_MAX ULLONG_MAX
#define PTRDIFF_MIN INTPTR_MIN
#define PTRDIFF_MAX INTPTR_MAX
#define SIZE_MAX    UINT64_MAX

limits.h:

#pragma once
#if defined _WIN32 || defined _WIN64
 #define __WORDSIZE 32
#else
 #if defined __x86_64__ && !defined __ILP32__
  #define __WORDSIZE 64
 #else
  #define __WORDSIZE 32
 #endif
#endif
#define MB_LEN_MAX  16
#define CHAR_BIT    8
#define SCHAR_MIN   (-128)
#define SCHAR_MAX   127
#define UCHAR_MAX   255
enum {
  _JITIFY_CHAR_IS_UNSIGNED = (char)-1 >= 0,
  CHAR_MIN = _JITIFY_CHAR_IS_UNSIGNED ? 0 : SCHAR_MIN,
  CHAR_MAX = _JITIFY_CHAR_IS_UNSIGNED ? UCHAR_MAX : SCHAR_MAX,
};
#define SHRT_MIN    (-32768)
#define SHRT_MAX    32767
#define USHRT_MAX   65535
#define INT_MIN     (-INT_MAX - 1)
#define INT_MAX     2147483647
#define UINT_MAX    4294967295U
#if __WORDSIZE == 64
 # define LONG_MAX  9223372036854775807L
#else
 # define LONG_MAX  2147483647L
#endif
#define LONG_MIN    (-LONG_MAX - 1L)
#if __WORDSIZE == 64
 #define ULONG_MAX  18446744073709551615UL
#else
 #define ULONG_MAX  4294967295UL
#endif
#define LLONG_MAX  9223372036854775807LL
#define LLONG_MIN  (-LLONG_MAX - 1LL)
#define ULLONG_MAX 18446744073709551615ULL

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接