日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

如何在 GPU 上优化卷积

發布時間:2023/11/28 生活经验 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何在 GPU 上优化卷积 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

如何在 GPU 上優化卷積
將演示如何在 TVM 中編寫高性能卷積實現。正方形大小的輸入張量和過濾器為例,假設卷積的輸入具有大batch批量。在這個例子中,使用不同的布局存儲數據,實現更好的數據局部性。緩沖區布局是 HWCN,代表高度、寬度、通道、批次。
準備和算法
對具有 256 個通道和 14 x 14 維度的輸入張量使用固定大小。批量大小為 256。卷積過濾器包含 512 個大小為 3 x 3 的過濾器。使用步幅大小 1 和padding大小 1 進行卷積。以下代碼定義了 TVM 中的卷積算法。
import numpy as np
import tvm
from tvm import te

The sizes of inputs and filters

batch = 256
in_channel = 256
out_channel = 512
in_size = 14
kernel = 3
pad = 1
stride = 1

Algorithm

A = te.placeholder((in_size, in_size, in_channel, batch), name=“A”)
W = te.placeholder((kernel, kernel, in_channel, out_channel), name=“W”)
out_size = (in_size - kernel + 2 * pad) // stride + 1

Pad input

Apad = te.compute(
(in_size + 2 * pad, in_size + 2 * pad, in_channel, batch),
lambda yy, xx, cc, nn: tvm.tir.if_then_else(
tvm.tir.all(yy >= pad, yy - pad < in_size, xx >= pad, xx - pad < in_size),
A[yy - pad, xx - pad, cc, nn],
tvm.tir.const(0.0, “float32”),
),
name=“Apad”,
)

Create reduction variables

rc = te.reduce_axis((0, in_channel), name=“rc”)
ry = te.reduce_axis((0, kernel), name=“ry”)
rx = te.reduce_axis((0, kernel), name=“rx”)

Compute the convolution

B = te.compute(
(out_size, out_size, out_channel, batch),
lambda yy, xx, ff, nn: te.sum(
Apad[yy * stride + ry, xx * stride + rx, rc, nn] * W[ry, rx, rc, ff], axis=[ry, rx, rc]
),
name=“B”,
)
內存層次結構
首先指定緩沖區的內存層次結構。下圖顯示了 GPU 內存層次結構。與 CPU 內存層次結構的重要區別是 GPU,提供了共享內存的緩存緩沖區,由程序員管理。因此,如何最大化共享內存中的數據重用,對于在 GPU 內核中,實現高性能至關重要。

在這個例子中,將 Apad 和 W 加載到緩沖區 AA 和 WW 中,存儲在共享內存中。這些緩沖區由同一線程塊內的所有線程共享計算卷積。然后每個線程從共享緩沖區,加載自定義部分到的本地寄存器 AL 和 WL。BL 是輸出 B 的本地緩存,存儲在線程本地寄存器中。

Designate the memory hierarchy

s = te.create_schedule(B.op)
s[Apad].compute_inline() # compute Apad inline
AA = s.cache_read(Apad, “shared”, [B])
WW = s.cache_read(W, “shared”, [B])
AL = s.cache_read(AA, “local”, [B])
WL = s.cache_read(WW, “local”, [B])
BL = s.cache_write(B, “local”)
阻塞
以下代碼將工作負載拆分為線程塊和單個線程。在矩陣乘法中遵循分塊方案。如下圖所示,給定一個像素坐標(y,x),一個線程塊負責計算一個block_factor x block_factor(64 x 64)的區域,用于輸出通道和batch。由于共享內存空間的限制,每次只從 Apad 和 B 加載 step x block_factor (8 x 64) 數據到共享內存中的緩沖區。

tile consts

tile = 8
num_thread = 8
block_factor = tile * num_thread
step = 8
vthread = 2

Get the GPU thread indices

block_x = te.thread_axis(“blockIdx.x”)
block_y = te.thread_axis(“blockIdx.y”)
block_z = te.thread_axis(“blockIdx.z”)
thread_x = te.thread_axis((0, num_thread), “threadIdx.x”)
thread_y = te.thread_axis((0, num_thread), “threadIdx.y”)
thread_xz = te.thread_axis((0, vthread), “vthread”, name=“vx”)
thread_yz = te.thread_axis((0, vthread), “vthread”, name=“vy”)

Split the workloads

hi, wi, fi, ni = s[B].op.axis
bz = s[B].fuse(hi, wi)
by, fi = s[B].split(fi, factor=block_factor)
bx, ni = s[B].split(ni, factor=block_factor)

Bind the iteration variables to GPU thread indices

s[B].bind(bz, block_z)
s[B].bind(by, block_y)
s[B].bind(bx, block_x)
虛擬線程拆分
將工作負載從線程塊拆分為單個線程。為避免內存庫沖突,使用虛擬線程,將區域分成 4 部分,平鋪成 8x8 的網格。如下圖所示,每個線程計算 4 個 strided 網格,每個網格的大小為 4 x 4。

tyz, fi = s[B].split(fi, nparts=vthread) # virtual thread split
txz, ni = s[B].split(ni, nparts=vthread) # virtual thread split
ty, fi = s[B].split(fi, nparts=num_thread)
tx, ni = s[B].split(ni, nparts=num_thread)
s[B].reorder(bz, by, bx, tyz, txz, ty, tx, fi, ni)

s[B].bind(tyz, thread_yz)
s[B].bind(txz, thread_xz)
s[B].bind(ty, thread_y)
s[B].bind(tx, thread_x)
Cooperative Fetching
每個時間步都需要將步 x block_factor 數據,從 GPU 全局內存傳輸到共享內存。為了減少每個線程的內存傳輸,以下代碼讓同一線程塊中的線程協同,從全局內存中獲取相關數據。

Schedule BL local write

s[BL].compute_at(s[B], tx)
yi, xi, fi, ni = s[BL].op.axis
ry, rx, rc = s[BL].op.reduce_axis
rco, rci = s[BL].split(rc, factor=step)
s[BL].reorder(rco, ry, rx, rci, fi, ni)

Attach computation to iteration variables

s[AA].compute_at(s[BL], rx)
s[WW].compute_at(s[BL], rx)
s[AL].compute_at(s[BL], rci)
s[WL].compute_at(s[BL], rci)

Schedule for A’s shared memory load

yi, xi, ci, ni = s[AA].op.axis
ty, ci = s[AA].split(ci, nparts=num_thread)
tx, ni = s[AA].split(ni, nparts=num_thread)
_, ni = s[AA].split(ni, factor=4)
s[AA].reorder(ty, tx, yi, xi, ci, ni)
s[AA].bind(ty, thread_y)
s[AA].bind(tx, thread_x)
s[AA].vectorize(ni) # vectorize memory load

Schedule for W’s shared memory load

yi, xi, ci, fi = s[WW].op.axis
ty, ci = s[WW].split(ci, nparts=num_thread)
tx, fi = s[WW].split(fi, nparts=num_thread)
_, fi = s[WW].split(fi, factor=4)
s[WW].reorder(ty, tx, yi, xi, ci, fi)
s[WW].bind(ty, thread_y)
s[WW].bind(tx, thread_x)
s[WW].vectorize(fi) # vectorize memory load
生成CUDA內核
最后,使用 TVM 生成和編譯 CUDA 內核,評估卷積的延遲。
func = tvm.build(s, [A, W, B], “cuda”)
dev = tvm.cuda(0)
a_np = np.random.uniform(size=(in_size, in_size, in_channel, batch)).astype(A.dtype)
w_np = np.random.uniform(size=(kernel, kernel, in_channel, out_channel)).astype(W.dtype)
a = tvm.nd.array(a_np, dev)
w = tvm.nd.array(w_np, dev)
b = tvm.nd.array(np.zeros((out_size, out_size, out_channel, batch), dtype=B.dtype), dev)
func(a, w, b)
evaluator = func.time_evaluator(func.entry_name, dev, number=1)
print(“Convolution: %f ms” % (evaluator(a, w, b).mean * 1e3))
輸去:
Convolution: 41.937872 ms

參考鏈接路徑:
http://tvm.apache.org/docs/how_to/optimize_operators/opt_conv_cuda.html

總結

以上是生活随笔為你收集整理的如何在 GPU 上优化卷积的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。