为啥我的 Kmeans CuPy 代码中有“OutOfMemoryError”?

Posted

技术标签:

【中文标题】为啥我的 Kmeans CuPy 代码中有“OutOfMemoryError”?【英文标题】:Why do i have "OutOfMemoryError" in my Kmeans CuPy code?为什么我的 Kmeans CuPy 代码中有“OutOfMemoryError”? 【发布时间】:2021-04-23 13:47:04 【问题描述】:

我对 gpu 编码真的很陌生我发现这个 Kmeans Cupy 代码我的提议是使用大型数据库 (n,3) 例如实现关于 gpu 和 cpu 的时间差异,我想要大量的集群但我收到内存管理错误。谁能给我研究和修复它应该采取的路线,我已经研究但我还没有一个明确的开始。

import contextlib
import time

import cupy
import matplotlib.pyplot as plt
import numpy

@contextlib.contextmanager
def timer(message):
    cupy.cuda.Stream.null.synchronize()
    start = time.time()
    yield
    cupy.cuda.Stream.null.synchronize()
    end = time.time()
    print('%s:  %f sec' % (message, end - start))

    
var_kernel = cupy.ElementwiseKernel(
    'T x0, T x1, T c0, T c1', 'T out',
    'out = (x0 - c0) * (x0 - c0) + (x1 - c1) * (x1 - c1)',
    'var_kernel'
)
sum_kernel = cupy.ReductionKernel(
    'T x, S mask', 'T out',
    'mask ? x : 0',
    'a + b', 'out = a', '0',
    'sum_kernel'
)
count_kernel = cupy.ReductionKernel(
    'T mask', 'float32 out',
    'mask ? 1.0 : 0.0',
    'a + b', 'out = a', '0.0',
    'count_kernel'
)
    
    
def fit_xp(X, n_clusters, max_iter):
    assert X.ndim == 2
 
    # Get NumPy or CuPy module from the supplied array.
    xp = cupy.get_array_module(X)

    n_samples = len(X)
    
    # Make an array to store the labels indicating which cluster each sample is
    # contained.
    pred = xp.zeros(n_samples)
    
    # Choose the initial centroid for each cluster.
    initial_indexes = xp.random.choice(n_samples, n_clusters, replace=False)
    centers = X[initial_indexes]
    
    for _ in range(max_iter):
        # Compute the new label for each sample.
        distances = xp.linalg.norm(X[:, None, :] - centers[None, :, :], axis=2)
        new_pred = xp.argmin(distances, axis=1)
    
        # If the label is not changed for each sample, we suppose the
        # algorithm has converged and exit from the loop.
        if xp.all(new_pred == pred):
            break
        pred = new_pred
    
        # Compute the new centroid for each cluster.
        i = xp.arange(n_clusters)
        mask = pred == i[:, None]
        sums = xp.where(mask[:, :, None], X, 0).sum(axis=1)
        counts = xp.count_nonzero(mask, axis=1).reshape((n_clusters, 1))
        centers = sums / counts
    
    return centers, pred
    
    
def fit_custom(X, n_clusters, max_iter):
    assert X.ndim == 2
    
    n_samples = len(X)
    
    pred = cupy.zeros(n_samples,dtype='float32')
    
    initial_indexes = cupy.random.choice(n_samples, n_clusters, replace=False)
    centers = X[initial_indexes]
    
    for _ in range(max_iter):
        distances = var_kernel(X[:, None, 0], X[:, None, 1],
                                   centers[None, :, 1], centers[None, :, 0])
        new_pred = cupy.argmin(distances, axis=1)
        if cupy.all(new_pred == pred):
            break
        pred = new_pred
    
        i = cupy.arange(n_clusters)
        mask = pred == i[:, None]
        sums = sum_kernel(X, mask[:, :, None], axis=1)
        counts = count_kernel(mask, axis=1).reshape((n_clusters, 1))
        centers = sums / counts
    
    return centers, pred
    
    
def draw(X, n_clusters, centers, pred, output):
    # Plot the samples and centroids of the fitted clusters into an image file.
    for i in range(n_clusters):
        labels = X[pred == i]
        plt.scatter(labels[:, 0], labels[:, 1], c=numpy.random.rand(3))
    plt.scatter(
        centers[:, 0], centers[:, 1], s=120, marker='s', facecolors='y',
        edgecolors='k')
    plt.savefig(output)
  
    
def run_cpu(gpuid, n_clusters, num, max_iter, use_custom_kernel):##, output
    samples = numpy.random.randn(num, 3)
    X_train = numpy.r_[samples + 1, samples - 1]
    
    with timer(' CPU '):
        centers, pred = fit_xp(X_train, n_clusters, max_iter)
    
    
    
def run_gpu(gpuid, n_clusters, num, max_iter, use_custom_kernel):##, output
    samples = numpy.random.randn(num, 3)
    X_train = numpy.r_[samples + 1, samples - 1]
    
    with cupy.cuda.Device(gpuid):
        X_train = cupy.asarray(X_train)
    
        with timer(' GPU '):
            if use_custom_kernel:
                centers, pred = fit_custom(X_train, n_clusters, max_iter)
            else:
                centers, pred = fit_xp(X_train, n_clusters, max_iter)

顺便说一句,我在 colab pro 25GB(RAM) 中工作,代码使用 n_clusters=200 和 num= 1000000 但如果我使用更大的数字会出现错误,我正在运行这样的代码:

run_gpu(0,200,1000000,10,True)

This is the error that i have

欢迎提出任何建议,感谢您的宝贵时间。

【问题讨论】:

当您的 GPU 只有 2GB 全局内存时,25GB RAM 对您没有多大帮助。那么你使用的是什么 GPU? 免费一代 RAM:26.3 GB | Proc 大小:185.6 MB GPU RAM 可用:16280MB |已使用:0MB |使用率 0% |总计 16280MB 您能否确定(通过单步执行或添加打印语句)错误出现的确切位置?特别是如果它出现在循环之前或循环中直到max_iter 嗨,抱歉重播晚了,我在最后放了一个链接,上面有我的错误图片,正如你所说的那样,它在“var_kernel”的循环中 【参考方案1】:

假设 CuPy 足够聪明,不会创建 var_kernel 的广播输入的显式副本,则输出 distances 的大小必须为 2 * num * num_clusters,这正是它试图分配的 6,400,000,000 字节。您可以通过从不实际将距离写入内存来减少内存占用,这意味着将var_kernelargmin 融合在一起。请参阅this 部分文档。

如果我正确理解了那里的示例,这应该可以:

@cupy.fuse(kernel_name='argmin_distance')
def argmin_distance(x1, y1, x2, y2):
    return cupy.argmin((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2), axis = 1)

下一个问题是其他 13.7GB 来自哪里。其中很大一部分可能只是早期迭代中distances 的实例。我不是 CuPy 专家,但至少在 Python/Numpy 中,您在循环内使用距离不会重用相同的内存,而是在每次调用 var_kernel 时分配更多内存。在循环之前分配的pred 也存在同样的问题。如果 CuPy 以 Numpy 的方式做事,解决方案就是把 [:] 放在那里

pred[:] = new_pred

distances[:,:,:] = var_kernel(X[:, None, 0], X[:, None, 1],
                              centers[None, :, 1], centers[None, :, 0])

为此,您还需要在循环之前分配distances。在使用内核融合时也不再需要这个,所以仅以它为例。最好事先分配所有内容,然后在循环中的任何地方使用此语法。

我对 CuPy 的了解还不够,无法回答为什么 fit_xp 没有同样的问题(或者有吗?)。但我的猜测是,使用 CuPy 对象的垃圾收集在那里的工作方式不同。如果垃圾回收在fit_custom 中“足够快”,那么即使没有内核融合或重用已分配的数组,它也应该可以工作。

您的代码有其他问题或至少有一些奇怪之处:

为什么要将centers 的第零坐标与X 的第一个坐标进行比较?打电话不是更有意义吗
distances = var_kernel(X[:, None, 0], X[:, None, 1],
                       centers[None, :, 0], centers[None, :, 1])
为什么只使用 2D 平面上的投影来创建 3D 数据?那为什么不呢
samples = numpy.random.randn(num, 2)
为什么要为pred(的初始版本)使用浮点数? argmin 应该给出一个整数类型的结果。

【讨论】:

以上是关于为啥我的 Kmeans CuPy 代码中有“OutOfMemoryError”?的主要内容,如果未能解决你的问题,请参考以下文章

为啥同时使用 numba.cuda 和 CuPy 从 GPU 传输数据这么慢?

如何从 CuPy 数组创建一个 dask 数组?

为啥我系统上的所有 CuPy 函数都比它们的 NumPy 对应函数慢?

为啥同组聚类数据点在 Kmeans 聚类中落得较远或分散?

为啥 Spark Mllib KMeans 算法非常慢?

为啥 KMeans 集群标签并不总是与 set random_state 相同?