GPU
DeepLearning
PyTorch

【GPU】メモリについて「cudaMemcpy」

pytorchの並列化のレスポンスの調査のため、gpuメモリについて調べた軌跡をメモ。
この記事では、もしかしたらってこうかなーってのしかわかってない。

これらのサイトを参考にした。非常に勉強になった。
http://d.hatena.ne.jp/interleave/20091103/1257259065
http://news.mynavi.jp/column/architecture/338/

cudaでhello world!!

実装は見なくていいという場合は飛ばしてください。
いや 本当に動くの?と思いながらこの記事を元に動かした。
http://www.gdep.jp/page/view/255

環境:ubuntu14, cuda8.0

main.cu
#include <stdio.h>

__global__ void
    kernel( void ) {
}

int main( void ) {
    kernel<<<1,1>>>();
    printf( "Hello, World!\n" );
    return 0;
}
nvcc main.cu
./a.out
#Hello, World!って出た。

GPUで文字列を形成し、それをホスト側に移して表示する

ホストがcpu側、デバイスはgpu側。

main.cu
#include <stdio.h>

//デバイス側で文字を表示
__device__ void
device_strcpy(char *dst, const char *src)
{
    while (*dst++ = *src++);
}

__global__ void kernel(char *A)
{
    device_strcpy(A, "Hello, World!");
}

int main() {
   char *d_hello;
   char hello[32]; //cpu側メモリを確保

   cudaMalloc((void**)&d_hello, 32);  //gpu側メモリを確保

   kernel<<<1,1>>>(d_hello);

   cudaMemcpy(hello, d_hello, 32, cudaMemcpyDeviceToHost); //gpuからcpuにコピー

   cudaFree(d_hello);

   puts(hello);
}

gdepさんの記事は非常に勉強になる。

こちらの記事も試して見た。
http://d.hatena.ne.jp/interleave/20091103/1257259065

  • 1KB転送
cudaMalloc : 932.438000
cudaMemcpyHostToDevice : 5247.837000
cudaMemcpyDeviceToHost : 7321.464000
cudaFree : 0.365000
  • 1MB転送
cudaMalloc : 938.814000
cudaMemcpyHostToDevice : 359.169000
cudaMemcpyDeviceToHost : 365.630000
cudaFree : 0.141000
  • 512MB転送
cudaMalloc : 924.903000
cudaMemcpyHostToDevice : 150.473000
cudaMemcpyDeviceToHost : 136.740000
cudaFree : 0.274000

やっぱり転送はデータをでっかくして送った方がスピードが出てる。

cudaMallocについて

整理すると
cudaMalloc:gpu(デバイス)メモリ確保
cudaMemcpyHostToDevice:メモリ(ホスト)からgpuメモリに転送
cudaMemcpyDeviceToHost:gpuメモリからメモリに転送

cudaMalloc(&d_tmp, N);
cudaMemcpy(d_tmp, input, N, cudaMemcpyHostToDevice);
cudaMemcpy(output, d_tmp, N, cudaMemcpyDeviceToHost);

で、何となくcudaに慣れてきたところで、pytorchの中身へ。
pytorchはcpuだとcとかc++でgpuはcudaファイルが動いてる。
今回見るのはcuファイル。
今回目をつけたのはcudaMemcpyとcudamemcpyasync。
いかにもって名前でcudamemcpyasyncは非同期だけどcudaMemcpyって同期だよね。
だからcudaMemcpyやってる間はコピーを待っちゃうよね?gpuが大量にあってもcudaMemcpyで処理待ちしちゃうよね?って想定でコードを解析。

PReLU

https://github.com/pytorch/pytorch/blob/6fab62173e842bbf550de1c68cfae507ca35b800/torch/lib/THCUNN/generic/PReLU.cu

preluの実装の中では至る所でTHCTensor_クラスが使われてる。

void THNN_(PReLU_updateOutput)(
           THCState *state,
           THCTensor *input,
           THCTensor *output,
           THCTensor *weight,
           long nOutputPlane)
{
  THCTensor_(resizeAs)(state, output, input);

  weight = THCTensor_(newContiguous)(state, weight);
  real *w = THCTensor_(data)(state, weight);
...
略

THCTensor

https://github.com/pytorch/pytorch/blob/dfd1dff383d2d9f03031edc24380fc7ecb6f107f/torch/lib/THC/generic/THCTensor.cu

thcって頭についてるのはtorchから使ってるluaの名残の名前?な気がする。
THCStorage_を呼んでる。

略
...
THC_API int THCTensor_(getDevice)(THCState* state, const THCTensor* tensor) {
  if (!tensor->storage) return -1;
  return THCStorage_(getDevice)(state, tensor->storage);
}

THCStorageCopy

https://github.com/pytorch/pytorch/blob/0f65c9267d5ec55584b0ec65acb5374c95af9c16/torch/lib/THC/generic/THCStorageCopy.c

ここでcudaMemcpy使ってるんですね。いやでもなぁ。
活性化関数のとこでメモリ転送って、いやぁー。ここレスポンスに関係ないようなぁ。
そもそもTHCudaCheckがわからん。調べるとみんなここでエラー出たーとか言ってて、解決策はcudaの環境下 実装上の失敗。だからなんかチェックしてくれる機能である。中身が知りたい。

void THCStorage_(copyCPU)(THCState *state, THCStorage *self, struct THStorage *src)
{
  THArgCheck(self->size == src->size, 2, "size does not match");
  THCudaCheck(cudaMemcpy(self->data, src->data, self->size * sizeof(real), cudaMemcpyHostToDevice));
}

その他メモ

GPUは超低速プロセサ
...人を運ぶタスクにおいてgpuはバスでcpuはスポーツカー
http://news.mynavi.jp/column/architecture/338/
【コラム】コンピュータアーキテクチャの話 (336) CPUと異なるGPUにおけるコアと2次キャッシュの関係性
...cpuとgpuではl2キャッシュのついてる場所が違う。
...cpuはl2スイッチで矛盾が起こる共有の仕方をしているので、コヒーレンシプロトコルで矛盾を無くしている。gpuはメモリ側にl2がついてるので、l2の目的が違う。
http://news.mynavi.jp/photo/column/architecture/336/images/011l.jpg
骨まで理解するPCアーキテクチャ(GPU編) 第4回
...gpuメモリのデータ転送時間かかるので、でっかいデータをまとめてやれよ
http://pc.watch.impress.co.jp/docs/column/1month-kouza/646073.html

  • 確かに動くが理解はしてない。

gcc -Wall -c cuda_main.c
nvcc -c cuda_kernel.cu
gcc -I/usr/local/cuda/include -L/usr/local/cuda/lib64 cuda_main.o cuda_kernel.o -lcudart
./a.out
http://qiita.com/gunn/items/34075251ec6687e06800

  • 使ってないgpuを使用するスクリプト

https://gist.github.com/noisychannel/cdf57e2f177e98ae653230323a093d1e

#!/usr/bin/env bash

# Number of free GPUs on a machine
export n_gpus=`lspci | grep -i "nvidia" | wc -l`

# Return -1 if there are no GPUs on the machine
if [ $n_gpus -eq 0 ]; then
  echo "-1"
  exit -1
fi

f_gpu=`nvidia-smi | sed -e '1,/Processes/d' \
  | tail -n+3 | head -n-1 | awk '{print $2}'\
  | awk -v ng=$n_gpus 'BEGIN{for (n=0;n<ng;++n){g[n] = 1}} {delete g[$1];} END{for (i in g) print i}' \
  | tail -n 1`

# return -1 if no free GPU was found
if [ `echo $f_gpu | grep -v '^$' | wc -l` -eq 0 ]; then
  echo "-1"
  exit -1
else
  echo $f_gpu
fi
CUDA_VISIBLE_DEVICES=`scripts/free-gpu` python

あとtensorflowには2017/9/1時点では並列gpuを使用するときに、
エラーなっちゃうバグがまだ残ってるぽい
https://github.com/tensorflow/tensorflow/issues/152

cuda toolkitの全バージョン

https://developer.nvidia.com/cuda-toolkit-archive

cudaのソース

http://developer.download.nvidia.com/compute/cuda/opensource/