【问题标题】:RAM blowing up on computationRAM 在计算上爆炸
【发布时间】:2022-01-12 10:06:16
【问题描述】:

下面是使用daskcupy 的可运行代码sn-p,我遇到了问题。我在激活了 GPU 的 Google Colab 上运行它。

基本上我的问题是,AAt 是对 RAM 来说太大的数组,这就是我使用 Dask 的原因。在这些对于 RAM 数组来说太大的情况下,我运行操作,但我想获得 AtW1[:,k] (作为一个cupy数组)而不会耗尽我的 RAM 或 GPU 内存,因为我需要这个值用于进一步的操作。我怎样才能做到这一点?

import dask.array as da
import cupy as cp

from dask_cuda import LocalCUDACluster
from dask.distributed import Client

cluster = LocalCUDACluster()
client = Client(cluster)

n_topics = 10
n_terms = 10000
n_docs = 250000
k = 0

A = da.zeros_like(cp.zeros(1), shape=(n_terms,n_docs), chunks=(1,n_docs))
At = A.transpose().rechunk((10, n_terms))

n_row = A.shape[0]
n_col = A.shape[1]

W1 = cp.random.random((n_row, n_topics))
H = cp.random.random((n_col, n_topics))
W1tW1 = W1.T.dot(W1)

# The problem starts here: i want to get AtW1[:,k] as a cupy array without blowing my RAM
AtW1 = da.dot(At, W1).rechunk((n_docs, 1))
AtW1 = AtW1.persist()
val = AtW1[:,k].compute()
val

编辑:另一个有同样问题的例子。如何在不更改第一部分的情况下实现计算?第一部分的变量就在那里,所以数组的维度是明确的,例子是可运行的。

import cupy as cp
import dask.array as da
from dask.array.linalg import norm
from dask_cuda import LocalCUDACluster
from dask.distributed import Client

cluster = LocalCUDACluster()
client = Client(cluster)

####### These are given and cannot be changed #######
big = 250000
small = 10000
full = da.full_like(cp.full(1, 2.0), fill_value=2.0, shape=(small,big), chunks=(1,big))
a = cp.random.random((small, 10))
b = cp.transpose(cp.random.random((big, 10)))

###### How can i make this work? #######
ab = da.dot(da.from_array(a), da.from_array(b))
subtracted = full - ab
normalized = norm(subtracted, ord='fro')
val = normalized.compute()

【问题讨论】:

  • 频繁重新分块的动机是什么?
  • @SultanOrazbayev 我根据我计划执行的操作和我想要访问的数据重新分块。例如,在点积的第一个参数上,我想要具有形状(短、长)的块,而对于我想要的第二个参数(长、短),因为矩阵的点积的计算方式。跨度>

标签: python numpy memory dask cupy


【解决方案1】:

虽然重新分块的想法在纸面上很有意义,但在实践中重新分块需要非常小心,因为它只能重塑原则上可以被阻止的工作。

例如,比较以下两种方法:

A = da.zeros_like(cp.zeros(1), shape=(n_terms,n_docs), chunks=(1,n_docs))
At = A.transpose().rechunk((1, n_terms))

At_version2 = da.zeros_like(cp.zeros(1), shape=(n_docs, n_terms), chunks=(1,n_terms))

当请求At[0,:] 时,dask 将完成的工作量将远远大于为 At_version2[0,:] 完成的工作量。为什么?因为At 的原始数据将在(1,n_docs) 的块中定义,所以要生成转置的第一行,dask 必须在每一行计算原始数组A

使用此逻辑,最好确保您的数据以确实与获得最终结果所需的块乘法兼容的形状提供。这将导致更少的任务和更低的内存需求,因为现在只需将数据的子集保存在内存中。

这是快速计算的代码的修改版本:

import dask.array as da
import cupy as cp

from dask_cuda import LocalCUDACluster
from dask.distributed import Client

cluster = LocalCUDACluster()
client = Client(cluster)

n_topics = 10
n_terms = 10000
n_docs = 250000
k = 0

At_v2 = da.zeros_like(cp.zeros(1), shape=(n_docs, n_terms), chunks=(1000,n_terms))

W1 = cp.random.random((n_terms, n_topics))

result = da.dot(At_v2, W1)[:,0].compute()

还请注意,运行 .persist 会将数组保留在内存中,因此如果内存是一个约束,您可能不想用 .persist 阻塞它。

【讨论】:

  • 感谢您的深入解答!在我的情况下,矩阵 A 已经给出,并且已经在矩阵上运行了一些操作。这段代码 sn-p 只是更大算法的一小部分。在转置 A 之前,我执行以下操作。 A = da.zeros_like(cp.zeros(1), shape=(n_terms,n_docs), chunks=(1,n_docs)) for k in range(n_docs): for j in docs[k]: A([j, k]) += 1.0 其中 docs 是迭代的文本文档的集合。 (更多上下文:我正在重新实现github.com/tshi04/SeaNMF,因为它无法处理大量文档)
  • 在您提供的 sn-p 中,很容易切换循环并获得转置。更一般地说,如果上游流程存在效率低下/瓶颈,最好支付存储/重塑它的固定成本以优化下游工作。
  • 详细说明一下:如果你用A([j, k]) += 1.0定义A并想要转置,那么A([k, j]) += 1.0应该给出A的转置。
  • 非常感谢!我让它以这种方式工作。但我觉得我并不真正了解 dask 的工作原理。我编辑了我的帖子并添加了另一个我需要解决的案例。我怎么知道,在这种情况下如何拆分我的数据?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-22
  • 2019-11-07
  • 1970-01-01
  • 2020-04-10
  • 2012-06-13
相关资源
最近更新 更多