在 bad_alloc 之后无法使用 CUDA 库中的 `cudaDeviceReset()` 恢复我的 GPU
Can't restore my GPU after bad_alloc with `cudaDeviceReset()` from the CUDA library
以下程序对 "test" 函数进行了 3 次连续调用,其中执行了一些推力操作。这 3 个调用中的每一个都为问题提供了不同的大小:
- 第一次来电3,000;
- 第二次调用300,000,000;
- 第三次调用又是 3,000。
第二次调用预计会失败,但如果我正确清理了 GPU 的状态,第三次调用应该会成功(与第一次调用一样)。不幸的是,它也失败了。此外,连续调用也会导致失败,直到我退出进程并重新开始。
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <cuda.h>
#include <thrust/system_error.h>
#include <thrust/device_vector.h>
#include <thrust/sort.h>
#include <thrust/execution_policy.h>
#define CUDA_CALL(x)do { if((x) != cudaSuccess) { return -11;}} while(0)
typedef typename thrust::device_vector<size_t> tDevVecInt;
typedef typename thrust::device_vector<float> tDevVecFlt;
struct modSim : public thrust::unary_function<int, int>
{
int szMat;
int p;
modSim(int in1, int in2)
{
this->p = in1;
this->szMat = in2;
}
__host__ __device__ int operator()(const int &x)
{
return (x/szMat)*p+(x%p);
}
};
int test(size_t szData)
{
modSim moduloCol(3, 33);
CUDA_CALL(cudaSetDevice(0));
try
{
tDevVecFlt devRand(szData);
tDevVecInt devIndices(szData);
tDevVecFlt devData(szData);
thrust::sequence(devRand.begin(), devRand.end());
thrust::tabulate(devIndices.begin(), devIndices.end(), moduloCol);
thrust::sort_by_key(devIndices.begin(), devIndices.end(), devRand.begin());
}
catch(std::bad_alloc &e)
{
std::cout << e.what() << std::endl;
CUDA_CALL(cudaDeviceReset());
CUDA_CALL(cudaSetDevice(0));
return -3;
}
catch(thrust::system_error &e)
{
std::cout << e.what() << std::endl;
CUDA_CALL(cudaDeviceReset());
CUDA_CALL(cudaSetDevice(0));
return -2;
}
CUDA_CALL(cudaDeviceReset());
return 0;
}
int main(void)
{
size_t n;
int retVal;
n = 3000;
retVal = test(n);
std::cout << retVal << std::endl;
n = 300000000;
retVal = test(n);
std::cout << retVal << std::endl;
n = 3000;
retVal = test(n);
std::cout << retVal << std::endl;
return(0);
}
在我的设置中(Windows 8,NVIDIA GeForce 820m 配备 2GB 专用显存,CUDA 7.0 使用 nvcc 编译,命令行为“$nvcc -arch=compute_20 test.cu -运行" ), 我明白了:
- 第一次调用 N = 3,000 成功;
- N = 300,000,000 的第二次调用失败,出现异常
bad allocation: out of memory
;
- N = 3,000 的第三次调用失败并返回
thrust::system error : after cub_::DeviceRadixSort::SortPairs(1): out of memory
。
所以输出看起来像这样:
0
bad allocation: out of memory
-3
after cub_::DeviceRadixSort::SortPairs(1): out of memory
-2
如上所述,第三次调用应该不会失败,因为它与成功的第一次调用相同。
这次失败似乎是前一次调用(发出 bad alloc
的那个)的结果,但我在 bad alloc
之后用 cudaDeviceReset()
和 cudaSetDevice()
。
尽管有清洁说明,但设备没有恢复到功能状态,我不明白为什么。
如果我做错了什么,在第一次失败后如何在不结束我的进程的情况下将 GPU 恢复到功能状态?
有人复制这个吗?
此行为已报告给 NVIDIA 问题列表。 NVIDIA的人重现了这个行为,乍一看无法解释。
然而,他们为我提供了一个解决方法,我想与可能感兴趣的人分享。这个想法只是在检测到异常时添加对 cudaGetLastError()
的调用,而不是(或者在我的情况下,之前)对 cudaDeviceReset()
.
的调用
catch(std::bad_alloc &e)
{
std::cout << e.what() << std::endl;
CUDA_CALL(cudaGetLastError());
CUDA_CALL(cudaDeviceReset());
CUDA_CALL(cudaSetDevice(0));
return -3;
}
然后,经过进一步调查,他们发现这实际上不是 cudaDeviceReset()
函数中的真正问题,并给了我以下解释:
The cudaDeviceReset()
explicitly destroys and cleans up all resources associated with the current device in the current process. It is the caller's responsibility to ensure that the device is not being accessed by any other host threads from the process when this function is called. Furthermore any error by a Cuda runtime call is registered internally and can be viewed using either cudaPeekAtLastError()
or cudaGetLastError()
. The first can be called multiple times to read the same error, while the later is used to READ AND CLEAR the error. It is advisable to clear the earlier error before making subsequent Cuda runtime call, using cudaGetLastError()
.
然后从这一点开始,我发现了一个讨论 here 我以前没有接触过并且处理类似的问题。那里的答案也值得一读。
以下程序对 "test" 函数进行了 3 次连续调用,其中执行了一些推力操作。这 3 个调用中的每一个都为问题提供了不同的大小:
- 第一次来电3,000;
- 第二次调用300,000,000;
- 第三次调用又是 3,000。
第二次调用预计会失败,但如果我正确清理了 GPU 的状态,第三次调用应该会成功(与第一次调用一样)。不幸的是,它也失败了。此外,连续调用也会导致失败,直到我退出进程并重新开始。
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <cuda.h>
#include <thrust/system_error.h>
#include <thrust/device_vector.h>
#include <thrust/sort.h>
#include <thrust/execution_policy.h>
#define CUDA_CALL(x)do { if((x) != cudaSuccess) { return -11;}} while(0)
typedef typename thrust::device_vector<size_t> tDevVecInt;
typedef typename thrust::device_vector<float> tDevVecFlt;
struct modSim : public thrust::unary_function<int, int>
{
int szMat;
int p;
modSim(int in1, int in2)
{
this->p = in1;
this->szMat = in2;
}
__host__ __device__ int operator()(const int &x)
{
return (x/szMat)*p+(x%p);
}
};
int test(size_t szData)
{
modSim moduloCol(3, 33);
CUDA_CALL(cudaSetDevice(0));
try
{
tDevVecFlt devRand(szData);
tDevVecInt devIndices(szData);
tDevVecFlt devData(szData);
thrust::sequence(devRand.begin(), devRand.end());
thrust::tabulate(devIndices.begin(), devIndices.end(), moduloCol);
thrust::sort_by_key(devIndices.begin(), devIndices.end(), devRand.begin());
}
catch(std::bad_alloc &e)
{
std::cout << e.what() << std::endl;
CUDA_CALL(cudaDeviceReset());
CUDA_CALL(cudaSetDevice(0));
return -3;
}
catch(thrust::system_error &e)
{
std::cout << e.what() << std::endl;
CUDA_CALL(cudaDeviceReset());
CUDA_CALL(cudaSetDevice(0));
return -2;
}
CUDA_CALL(cudaDeviceReset());
return 0;
}
int main(void)
{
size_t n;
int retVal;
n = 3000;
retVal = test(n);
std::cout << retVal << std::endl;
n = 300000000;
retVal = test(n);
std::cout << retVal << std::endl;
n = 3000;
retVal = test(n);
std::cout << retVal << std::endl;
return(0);
}
在我的设置中(Windows 8,NVIDIA GeForce 820m 配备 2GB 专用显存,CUDA 7.0 使用 nvcc 编译,命令行为“$nvcc -arch=compute_20 test.cu -运行" ), 我明白了:
- 第一次调用 N = 3,000 成功;
- N = 300,000,000 的第二次调用失败,出现异常
bad allocation: out of memory
; - N = 3,000 的第三次调用失败并返回
thrust::system error : after cub_::DeviceRadixSort::SortPairs(1): out of memory
。
所以输出看起来像这样:
0
bad allocation: out of memory
-3
after cub_::DeviceRadixSort::SortPairs(1): out of memory
-2
如上所述,第三次调用应该不会失败,因为它与成功的第一次调用相同。
这次失败似乎是前一次调用(发出 bad alloc
的那个)的结果,但我在 bad alloc
之后用 cudaDeviceReset()
和 cudaSetDevice()
。
尽管有清洁说明,但设备没有恢复到功能状态,我不明白为什么。
如果我做错了什么,在第一次失败后如何在不结束我的进程的情况下将 GPU 恢复到功能状态?
有人复制这个吗?
此行为已报告给 NVIDIA 问题列表。 NVIDIA的人重现了这个行为,乍一看无法解释。
然而,他们为我提供了一个解决方法,我想与可能感兴趣的人分享。这个想法只是在检测到异常时添加对 cudaGetLastError()
的调用,而不是(或者在我的情况下,之前)对 cudaDeviceReset()
.
catch(std::bad_alloc &e)
{
std::cout << e.what() << std::endl;
CUDA_CALL(cudaGetLastError());
CUDA_CALL(cudaDeviceReset());
CUDA_CALL(cudaSetDevice(0));
return -3;
}
然后,经过进一步调查,他们发现这实际上不是 cudaDeviceReset()
函数中的真正问题,并给了我以下解释:
The
cudaDeviceReset()
explicitly destroys and cleans up all resources associated with the current device in the current process. It is the caller's responsibility to ensure that the device is not being accessed by any other host threads from the process when this function is called. Furthermore any error by a Cuda runtime call is registered internally and can be viewed using eithercudaPeekAtLastError()
orcudaGetLastError()
. The first can be called multiple times to read the same error, while the later is used to READ AND CLEAR the error. It is advisable to clear the earlier error before making subsequent Cuda runtime call, usingcudaGetLastError()
.
然后从这一点开始,我发现了一个讨论 here 我以前没有接触过并且处理类似的问题。那里的答案也值得一读。