我在使用cuda-python
接口时遇到了一个有趣的副作用。
更详细地说,我发现在我的系统上调用nvrtc.nvrtcCompileProgram
会将locale.getpreferredencoding
的返回值从UTF-8
更改为ANSI_X3.4-1968
。这很烦人,因为它对open
函数的默认行为有副作用,在使用其他库时很容易导致UnicodeDecodeError
。
下面的代码应该会重现这个问题,这是一个非常简单的CUDA程序,它将a和B两个数组相加,并将结果保存在C.中
import numpy as np
from cuda import cuda, nvrtc
import locale
# interesting interaction between CUDA and OpenCL
# --- define a simple kernel
kernel_code = """
extern "C" __global__ void array_sum(float *A, float *B, float *C, size_t size, size_t n_threads)
{
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
for(size_t i = tid; i<size; i = i + n_threads){
C[i] = A[i] + B[i];
}
}
"""
def cuda_check(err): # fun to control for errors from CUDA/NVRTC
if isinstance(err, cuda.CUresult):
if err != cuda.CUresult.CUDA_SUCCESS:
raise RuntimeError("Cuda Error: {}".format(cuda.cuGetErrorString(err)))
elif isinstance(err, nvrtc.nvrtcResult):
if err != nvrtc.nvrtcResult.NVRTC_SUCCESS:
raise RuntimeError("Nvrtc Error: {}".format(nvrtc.nvrtcGetErrorString(err)))
else:
raise RuntimeError("Unknown error type: {}".format(cuda.cuGetErrorString(err)))
### --- initialization --- ###
# --- create
err, prog = nvrtc.nvrtcCreateProgram(str.encode(kernel_code, encoding='UTF-8'), b"rendering_kernel.cu", 0, [], [])
cuda_check(err)
# --- compile
print(locale.getpreferredencoding(False)) # < ----------- printing preferred locale before
err, = nvrtc.nvrtcCompileProgram(prog, 0, [])
print(locale.getpreferredencoding(False)) # < ----------- and after compiling
cuda_check(err)
err, ptx_size = nvrtc.nvrtcGetPTXSize(prog)
print(f'compilation of NVRTC successful, ptx size {ptx_size}')
ptx = b" " * ptx_size
err, = nvrtc.nvrtcGetPTX(prog, ptx) # PTX is here
cuda_check(err)
# initialize CUDA (single GPU)
# Initialize CUDA Driver API
err, = cuda.cuInit(0)
cuda_check(err)
# Retrieve handle for device 0
err, cuDevice = cuda.cuDeviceGet(0)
cuda_check(err)
# Create context
err, context = cuda.cuCtxCreate(0, cuDevice)
cuda_check(err)
# # Load PTX as module data and retrieve function
ptx = np.char.array(ptx)
# Note: Incompatible --gpu-architecture would be detected here
err, module = cuda.cuModuleLoadData(ptx.ctypes.data)
cuda_check(err)
err, kernel = cuda.cuModuleGetFunction(module, b"array_sum")
cuda_check(err)
PROGRAM = prog
NUM_THREADS = 2
NUM_BLOCKS = 2
CONTEXT = context
MODULE = module
KERNEL = kernel
err, stream = cuda.cuStreamCreate(0)
cuda_check(err)
### --- END of initialization --- ###
### --- preparing parameters --- ###
# --- PARAMETERS ---
def byte_size(x):
return x.size*x.itemsize
def push_to_stream(np_array, stream):
err, cls = cuda.cuMemAlloc(byte_size(np_array))
cuda_check(err)
err, = cuda.cuMemcpyHtoDAsync(
cls, np_array.ctypes.data, byte_size(np_array), stream
)
cuda_check(err)
return cls
A = np.array([i for i in range(10)], dtype=np.float32)
B = np.array([9-i for i in range(10)], dtype=np.float32)
print(f"A: {A}")
print(f"B: {B}")
A = push_to_stream(A, stream)
B = push_to_stream(B, stream)
C = push_to_stream(np.zeros([10]).astype(np.float32), stream)
size = np.array(10, dtype=np.uint64)
n_threads = np.array(NUM_BLOCKS*NUM_THREADS, dtype=np.uint64)
def get_ptr(x):
return np.array(int(x), dtype=np.uint64)
A_ptr = get_ptr(A)
B_ptr = get_ptr(B)
C_ptr = get_ptr(C)
args = [
A_ptr, B_ptr, C_ptr, size, n_threads
]
# args must be void**
args = np.array([arg.ctypes.data for arg in args], dtype=np.uint64)
err, = cuda.cuLaunchKernel(
KERNEL,
NUM_BLOCKS, # grid x dim
1, # grid y dim
1, # grid z dim
NUM_THREADS, # block x dim
1, # block y dim
1, # block z dim
512, # dynamic shared memory
stream, # stream
args.ctypes.data, # kernel arguments
0, # extra (ignore)
)
cuda_check(err)
### --- DONE preparing parameters --- ###
### --- launching the kernel --- ###
out = np.zeros( [10] ).astype(np.float32)
err, = cuda.cuMemcpyDtoHAsync(
out.ctypes.data, C, byte_size(out), stream
)
cuda_check(err)
err, = cuda.cuStreamSynchronize(stream)
cuda_check(err)
print(f"C (A+B): {out}")
### --- DONE launching the kernel --- ###
### --- cleaning up --- ###
def cleanup(memory_locs, stream):
err, = cuda.cuStreamDestroy(stream)
cuda_check(err)
for m in memory_locs:
err, = cuda.cuMemFree(m)
cuda_check(err)
cleanup([A,B], stream)
err, = nvrtc.nvrtcDestroyProgram(PROGRAM)
cuda_check(err)
err, = cuda.cuModuleUnload(MODULE)
cuda_check(err)
err, = cuda.cuCtxDestroy(CONTEXT)
cuda_check(err)
运行后,我得到以下输出:
UTF-8
ANSI_X3.4-1968
compilation of NVRTC successful, ptx size 1343
A: [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
B: [9. 8. 7. 6. 5. 4. 3. 2. 1. 0.]
C (A+B): [9. 9. 9. 9. 9. 9. 9. 9. 9. 9.]
前两行显示了首选编码的变化情况。
如果我尝试检查当前和默认区域设置,我可以看到它们仍然被报告为UTF-8,并且我没有发现用locale
库中的其他函数更改首选的区域设置的简单方法。
为了参考,请查看以下代码在调用nvrtc:之前和之后的输出
import os
import locale
print(locale.getdefaultlocale())
locale.resetlocale()
print(locale.getpreferredencoding(False))
print(locale.getlocale(category=locale.LC_CTYPE))
! env | grep LC
locale.localeconv()
之前:
('en_US', 'UTF-8')
UTF-8
('en_US', 'UTF-8')
LC_ALL=C.UTF-8
LC_MEASUREMENT=en_US.UTF-8
LC_PAPER=en_US.UTF-8
LC_MONETARY=en_US.UTF-8
LC_NAME=en_US.UTF-8
LC_COLLATE=en_US.UTF-8
LC_CTYPE=C
LC_ADDRESS=en_US.UTF-8
LC_NUMERIC=en_US.UTF-8
LC_MESSAGES=en_US.UTF-8
LC_TELEPHONE=en_US.UTF-8
LC_IDENTIFICATION=en_US.UTF-8
LC_TIME=en_US.UTF-8
{'int_curr_symbol': 'USD ',
'currency_symbol': '$',
'mon_decimal_point': '.',
'mon_thousands_sep': ',',
'mon_grouping': [3, 3, 0],
'positive_sign': '',
'negative_sign': '-',
'int_frac_digits': 2,
'frac_digits': 2,
'p_cs_precedes': 1,
'p_sep_by_space': 0,
'n_cs_precedes': 1,
'n_sep_by_space': 0,
'p_sign_posn': 1,
'n_sign_posn': 1,
'decimal_point': '.',
'thousands_sep': ',',
'grouping': [3, 3, 0]}
编译后:
('en_US', 'UTF-8')
ANSI_X3.4-1968
('en_US', 'UTF-8')
LC_ALL=C.UTF-8
LC_MEASUREMENT=en_US.UTF-8
LC_PAPER=en_US.UTF-8
LC_MONETARY=en_US.UTF-8
LC_NAME=en_US.UTF-8
LC_COLLATE=en_US.UTF-8
LC_CTYPE=C
LC_ADDRESS=en_US.UTF-8
LC_NUMERIC=en_US.UTF-8
LC_MESSAGES=en_US.UTF-8
LC_TELEPHONE=en_US.UTF-8
LC_IDENTIFICATION=en_US.UTF-8
LC_TIME=en_US.UTF-8
{'int_curr_symbol': '',
'currency_symbol': '',
'mon_decimal_point': '',
'mon_thousands_sep': '',
'mon_grouping': [],
'positive_sign': '',
'negative_sign': '',
'int_frac_digits': 127,
'frac_digits': 127,
'p_cs_precedes': 127,
'p_sep_by_space': 127,
'n_cs_precedes': 127,
'n_sep_by_space': 127,
'p_sign_posn': 127,
'n_sign_posn': 127,
'decimal_point': '.',
'thousands_sep': '',
'grouping': []}
您可以看到,环境仍然设置为UTF-8模式,并且只更改了首选编码(及其约定(。
考虑到这一点,我想知道首选和默认区域设置之间有什么区别,以及是否可以以某种方式更改第一个区域设置。
我已经知道locale.getpreferredencoding(False)
在后台从_locale
库调用_locale.nl_langinfo(_locale.CODESET)
,这是nl_langinfo(3)
函数的直接接口。。。如果我在同一环境中从C调用它,我会得到首选编码仍然是UTF-8,所以我猜更改的影响在某种程度上仅限于当前的python实例,除此之外没有任何影响。
无论如何,我认为我不是第一个对如何为open
函数选择默认编码有问题的人,因为幸运的是,它将在python 3.15中更改。
感谢大家的帮助,如果您需要其他信息,请告诉我。
系统详细信息:
NAME="Ubuntu"
VERSION="18.04.6 LTS (Bionic Beaver)"
上面的代码在python 3.10.6和3.9.12 上都进行了测试
这显然是nvrtc中的一个错误。作为一种变通方法:
只是为了提供一个小的更新。通过与英伟达讨论这个问题,我们发现可以导出LC_ ALL="0";POSIX";作为一种变通方法以避免NVCC将编码更改为ASCII。
(从评论中收集答案,并添加为社区wiki条目,以将问题从CUDA标签的未回答队列中删除(