久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

OneFlow框架添加算子實踐:expand和repeat

 IT管委 2021-11-09

1,、expand 算子

用法介紹

oneflow.expand(input, *sizes)

expand 算子的功能簡單來說就是,,能夠?qū)崿F(xiàn)將輸入張量的沿著大小為 1 的維度進(jìn)行復(fù)制,至于復(fù)制多少份由第二個參數(shù)決定(下面將該參數(shù)稱為 expand_size),。

下面介紹 expand_size 設(shè)置的一些約定:

  • expand_size 維度大小大于等于輸入張量,,如果大于輸入維度則相當(dāng)于輸出會增加維度

  • 對于輸入張量為 1 的維度, expand_size 對應(yīng)維度可以設(shè)置為大于等于 1 的值

  • 對于輸入張量不為 1 的維度,, expand_size 對應(yīng)維度只能設(shè)置為等于輸入或者 -1

  • 新添加的維度只能加在開頭且不能設(shè)置 -1,,新增維度也就相當(dāng)于將整個輸入張量進(jìn)行復(fù)制

具體示例

示例1:

input_shape = [4, 3, 1, 2]exand_size  = [4, 3, 5, 2] # 下面這些 expand_size 的設(shè)置都是合法的#             [-1, 3, 5, 2] #             [-1, -1, 5, 2] #             [-1, -1, 5, -1] #             [4, -1, 5, 2]#             [4, -1, 5, -1]#             [4, 3, 5, -1]out_shape   = [4, 3, 5, 2]

示例2:

input_shape =       [1, 4, 3, 5]exand_size  = [2, 1, 2, 4, 3, 5] # 下面這些 expand_size 的設(shè)置都是合法的#             [2, 1, 2, -1, 3, 5] #             [2, 1, 2, -1, -1, 5] #             [2, 1, 2, -1, -1, -1] #             [2, 1, 2, 4, -1, 5] #             [2, 1, 2, 4, -1, -1] #             [2, 1, 2, 4, 3, -1] out_shape   = [2, 1, 2, 4, 3, 5]

單卡視角實現(xiàn)思路

接下來介紹 expand 算子單卡視角下的實現(xiàn)思路,也就是先不用考慮分布式的情況,。

從上一節(jié)的介紹可知 expand  算子的輸出張量的某個位置的值就是從輸入張量的某個位置復(fù)制過來的,,所以問題就轉(zhuǎn)化成了如何把輸出某個位置的索引映射到輸入對應(yīng)位置的索引。

在介紹如何計算索引映射之前,,首先來復(fù)習(xí)一下張量的 stride 屬性這個概念。對于內(nèi)存連續(xù)的 n 維張量,,可以通過其  stride 屬性,,快速定位到該張量任意位置的索引 (x, y, z, k) 對應(yīng)的一維索引。

舉個例子:

input_shape = [6, 3, 4, 5]stride      = [60, 20, 5, 1] # 下面會介紹 stide 的計算方法input[x, y, z, k] == input_flatten[x * 60 + y * 20 + z * 5 + k * 1]

stride 每一維度的數(shù)值表示該維度索引每增加1,,對應(yīng)到內(nèi)存上應(yīng)該移動的步長,,stride 每一維的計算公式如下:

2d31475c3dda1aeb75d8d0ce95ce735e.png

示例代碼:

# 最后一維初始化為1stride = [1]# 從后往前生成 stridefor i in range(len(input_shape) - 2, -1, -1):    # 在 stride 數(shù)組開頭插入元素    stride.insert(0, input_stride[0] * input_shape[i + 1])

接著來看該如何計算 expand 算子的輸出索引到輸入索引的映射。

我們知道如果輸入張量某維度是 1,,而 expanbd_size 對應(yīng)的維度大于 1,,相當(dāng)于是將輸入張量會沿著該維度進(jìn)行復(fù)制。也就是對于復(fù)制的維度來說,,不管該輸出維度的索引是多少,,都對應(yīng)著輸入張量該維度的索引 0。其實就是通過修改輸入張量的 stride 參數(shù)構(gòu)造一個新的 output_stride ,,該 output_stride 的計算方法就是:

  • 如果 expand_size 某維度 i 值為 -1,,或者與輸入張量的對應(yīng)維度一致,則 output_stirde[i] = stride[i]

  • 如果 expand_size 某維度 i 值大于 1,,而輸入張量對應(yīng)維度為 1,,則 output_stride[i] = 0

  • 對于 expand_size 維度大于輸入張量維度的情況,,則對于新添加的維度 ioutput_stride[i] = 0

計算 output_stirde 的示例代碼:

output_stride = []diff = len(expand_size) - len(input_shape)for i in range(len(expand_size) - 1, -1, -1):    if i >= diff:        if expand_size[i] == -1 or expand_size[i] == input_shape[i - diff]:            output_stride.insert(0, input_stride[i - diff])        else:            assert expand_size[i] >= 1 and input_shape[i - diff] == 1            output_stride.insert(0, 0)    else:        assert expand_size[i] >= 1        output_stride.insert(0, 0)

舉個例子:

input_shape   =       [4, 1, 3, 5]stride        =     [15, 15, 5, 1]exand_size    = [2, 1, 4, 4, 3, 5] output_stride = [0, 0, 15, 0, 5, 1]# 輸出張量意位置的索引 (x, y, z, k, v, w)output[x, y, z, k, v, w] = input_flatten[x * 0 + y * 0 + z * 15 + k * 0 + v * 5 + w * 1]# 反向的計算邏輯input_grad_flatten[x * 0 + y * 0 + z * 15 + k * 0 + v * 5 + w * 1] += output_grad[x, y, z, k, v, w]

前向代碼鏈接: 

https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/user/kernels/expand_kernel.cu#L30

反向代碼鏈接: 

https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/user/kernels/expand_kernel.cu#L43

多卡一致性視角

接下來介紹 OneFlow 中添加算子與其他框架不一樣的地方,。除了要正確實現(xiàn)單卡視角下的計算邏輯,,還需要考慮多卡一致性視角下的邏輯,包括輸出形狀推理的邏輯,、sbp 簽名的設(shè)置和實際計算的邏輯,。

首先簡單介紹一致性視角的概念:

OneFlow 提出了一致性視角(consistent view)的概念,用于簡化分布式訓(xùn)練,。簡單而言,,在 OneFlow 的一致性視角下,集群被抽象為一臺“超級計算設(shè)備”,。用戶無需關(guān)心集群中計算和通信的細(xì)節(jié),,只需要關(guān)心邏輯上的數(shù)據(jù)與計算??梢韵駟螜C(jī)單卡那樣去思考要和編程,,就能進(jìn)行分布式訓(xùn)練。

然后什么是 sbp:

sbp 是 OneFlow 發(fā)明的概念,,描述了在一致性視角下的 數(shù)據(jù)與集群中真實的物理設(shè)備上的數(shù)據(jù)的映射關(guān)系,。它由 split, broadcast, partial 的首字母組合而成。

  • split

    表示真實物理設(shè)備上的張量,,是將一致性視角的張量切分得到的,。切分時需要指定切分的維度,而真實物理設(shè)備上的張量經(jīng)過拼接之后可以還原得到一致性視角的張量,。

  • broadcast

    表示一致性視角下的張量,,在所有的真實物理設(shè)備上都有一份完整的復(fù)制。

  • partial

    表示一致性視角下的張量與物理設(shè)備上的張量的形狀相同,,但是對于物理設(shè)備上的值,,只是一致性視角下張量的一部分。以 partial_sum 為例,,如果我們將集群中所有設(shè)備的張量按位置相加,,才可以還原得到一致性視角的張量。除了 sum 外,,min,、max 等操作也適用于 partial。

更多詳細(xì)內(nèi)容可以參考:

https://docs./v0.5.0/parallelism/02_sbp.html

所以在 Oneflow 中開發(fā)算子,,開發(fā)者還需要為算子設(shè)置其輸入和輸出支持哪些 sbp 簽名的組合,,這也是需要付出的額外學(xué)習(xí)成本。

而在一致性視角下,算子的實現(xiàn)邏輯有可能需要考慮,,其在真實物理設(shè)備上的計算與邏輯上的計算(也就是一致性視角)不一致的地方,。

比如對于 expand 算子,在真實物理設(shè)備上計算的時候,,就可能需要修改用戶傳入的邏輯上的 expand_size,。主要原因在于 expand 算子的 sbp 簽名支持對輸入進(jìn)行 split

具體代碼鏈接:

https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/user/ops/expand_op.cpp#L62

舉個具體的例子:

logical input_shape =      [4, 3, 1, 2]logical stride =           [6, 2, 2, 1]logical expand_size =   [2, 4, 3, 4, 2]logical output_stride = [0, 6, 2, 0, 1]

假設(shè)用戶設(shè)置了輸入張量的 sbp 為 split(3) ,也就是對最后一維度進(jìn)行切分,。且設(shè)置該邏輯張量放置在兩張卡上,,則每張卡上的真實物理形狀為:

physical input_shape = [4, 3, 1, 1]physical stride      = [3, 1, 1, 1]

則對于真實物理設(shè)備上的 expand_size 和 output_stride 都需要做修改:

physical expand_size   =  [2, 4, 3, 4, 1]physical output_stride =  [0, 3, 1, 0, 1]

為什么 expand_size 需要修改呢?

首先在一致性視角下,,每個物理設(shè)備上進(jìn)行實際計算的時候,,實際上拿到的輸入大小是切分之后的物理形狀。

而對于上面的例子,,輸入的在每個設(shè)備上的物理形狀變?yōu)?nbsp;[4, 3, 1, 1],,而如果 expand_size 這時候仍然保持用戶設(shè)置的邏輯大小 [2, 4, 3, 4, 2],則在每個設(shè)備上的輸出大小是 [2, 4, 3, 4, 2],,則輸出對應(yīng)的邏輯形狀則是 [2, 4, 3, 4, 4],,則輸出結(jié)果最后一維就比原來多了。

而由于用戶怎么設(shè)置 sbp 是運(yùn)行時才能拿到的信息,,所以在物理設(shè)備上進(jìn)行計算之前,,都需要根據(jù)實際的輸入大小,重新計算 expand_size 和 output_stride,。

具體代碼鏈接:

https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/user/kernels/expand_kernel.cu#L129

2,、repeat 算子

用法介紹

oneflow.repeat(input, *sizes)

repeat 算子的功能簡單來說就是,能夠?qū)崿F(xiàn)將輸入張量的任意維度進(jìn)行復(fù)制,,至于復(fù)制多少份由第二個參數(shù)決定(下面將該參數(shù)稱為 repeat_size),。

下面介紹 repeat_size 設(shè)置的一些約定:

  • repeat_size 維度大小大于等于輸入張量,如果大于輸入維度則相當(dāng)于輸出會增加維度

  • repeat_size 任意維度的值需要設(shè)置為大于等于 1 ,,假設(shè)某維度設(shè)為 n, 則先當(dāng)于輸入張量對應(yīng)維度復(fù)制 n-1 份

  • 新添加的維度只能加在開頭且不能設(shè)置為小于1的值,,新增維度也就相當(dāng)于將整個輸入張量進(jìn)行復(fù)制,,假設(shè)新增維度設(shè)為 n, 則先當(dāng)于將輸入張量復(fù)制 n-1 份

  • repeat_size 任意維度的值其實也可以設(shè)置為 0,,但是這里不考慮這種情況

則輸出張量每一維的大小計算方式如下:

對于非新增的維度:

db3e2176e0594880b1371808674bf86a.png

對于新增的維度:

20d01b9599f1b52ac5b8574e4062b131.png

具體示例

input_shape   =       [4, 1, 3, 5]repeat_size   = [2, 1, 2, 4, 1, 1] output_shape  = [2, 1, 8, 4, 3, 5]

與 expand 算子的聯(lián)系

其實仔細(xì)思考一下,,可以感覺到 repeat 算子和 expand 算子其實是有聯(lián)系的,也就是 repeat 算子是可以通過 expand 算子來實現(xiàn),。

舉些例子:

例子1:

input_shape   = [5]
repeat_size   = [3] 
output_shape  = [15]# 等價與以下操作input_shape            =    [5]
reshaped_input_shape   = [1, 5]
expand_size            = [3, 5] 
output_shape           = [3, 5]
reshaped_output_shape  =   [15]

例子2:

input_shape   = [3, 1, 5]repeat_size   = [5, 3, 1] output_shape  = [15, 3, 5]# 等價于以下操作input_shape           =    [3, 1, 5]reshaped_input_shape  = [1, 3, 1, 5]expand_size           = [5, 3, 3, 5] output_shape          = [5, 3, 3, 5]reshaped_output_shape =   [15, 3, 5]

例子3:

input_shape   =    [3, 1, 5]repeat_size   = [2, 5, 3, 1] output_shape  = [2, 15, 3, 5]# 等價與以下操作input_shape            =       [3, 1, 5]reshaped_input_shape   =    [1, 3, 1, 5]expand_size            = [2, 5, 3, 3, 5] output_shape           = [2, 5, 3, 3, 5]reshaped_output_shape  =   [2, 15, 3, 5]

從上面的例子可以知道,, repeat 操作可以用 reshape + expand + reshape 來代替,問題就轉(zhuǎn)化成如何根據(jù) input_shape 和 repeat_size 計算得到輸入的 reshape大小,expand_size 和輸出的 reshape 大小,。

計算示例代碼:

input_reshape = []
output_reshape = []
expand_size = []
diff = len(repeat_size) - len(input_shape)for i in range(len(repeat_size) - 1, -1, -1):
    if i >= diff:
        if repeat_size[i] > 1:
            if input_shape[i - diff] > 1:
                input_reshape.insert(0, input_shape[i - diff])
                input_reshape.insert(0, 1)
                expand_size.insert(0, input_shape[i - diff])
                expand_size.insert(0, repeat_size[i])
                output_reshape.insert(0,
                        input_shape[i - diff] * repeat_size[i])
            else:
                input_reshape.insert(0, input_shape[i - diff])
                expand_size.insert(0, repeat_size[i])
                output_reshape.insert(0, repeat_size[i])
        else:
            input_reshape.insert(0, input_shape[i - diff])
            expand_size.insert(0, input_shape[i - diff])
            output_reshape.insert(0, input_shape[i - diff])
    else: # 新增的維度
        expand_size.insert(0, repeat_size[i])
        output_reshape.insert(0, repeat_size[i])
new_tensor = flow.reshape(input, input_reshape)
tmp_tensor = new_tensor.expand(*expand_size)
out = flow.reshape(tmp_tensor, output_reshape)

不過這算是取巧的實現(xiàn)了 repeat 算子,,因為替換成了reshape 和 expand 算子來實現(xiàn),所以也不用考慮 sbp 的問題了,,不過后續(xù)為了性能還是需要單獨寫一個算子實現(xiàn)的,。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多