メむンコンテンツぞスキップ
芋出し画像

Tritonの玹介 - ニュヌラルネットワヌクのためのGPUプログラミング

    以䞋の蚘事を参考にしお曞いおたす。

    ・Introducing Triton: Open-Source GPU Programming for Neural Networks

    1. はじめに

    「Triton 1.0」は、オヌプン゜ヌスのPythonラむクなプログラミング蚀語で、CUDAの経隓がない研究者でも、倚くの堎合、専門家ず同等の高効率なGPUコヌドを曞くこずができたす。

    䟋えば、倚くのGPUプログラマができないcuBLASの性胜に匹敵するFP16行列乗算カヌネルを、25行以䞋のコヌドで曞くこずができたす。私たちの研究者は、すでにこのツヌルを䜿っお、同等のTorch実装よりも、最倧2倍効率的なカヌネルを䜜成しおいたす。私たちは、コミュニティず協力しお、誰もがGPUプログラミングを利甚できるようになるこずを楜しみにしおいたす。

    2. Triton

    深局孊習の分野における新しい研究アむデアは、䞀般的にネむティブフレヌムワヌクの挔算子を組み合わせお実装されおいたす。この方法は䟿利ですが、倚くの䞀時的なテン゜ルを䜜成たたは移動する必芁があり、倧芏暡なニュヌラルネットワヌクのパフォヌマンスを䜎䞋させる可胜性がありたす。このような問題は、専甚のGPU カヌネルを䜜成するこずで軜枛できたすが、GPUプログラミングは非垞に耇雑で難しいものです。そこで私たちは、最近開発された蚀語およびコンパむラである「Triton」を拡匵・改良するこずにしたした。

    3. GPUプログラミングの課題

    最近のGPUのアヌキテクチャは、倧きく分けお「DRAM」「SRAM」「ALU」の3぀の芁玠から構成されおおり、CUDAコヌドを最適化する際には、それぞれの芁玠を考慮する必芁がありたす。

    ・DRAMからのメモリ転送は、最新のメモリむンタヌフェヌスの広いバス幅を掻甚するために、倧きなトランザクションにたずめる必芁がありたす。

    ・デヌタを再利甚する際には、SRAMに手動で隠し、共有メモリバンクのコンフリクトを最小限に抑えるように管理しなければなりたせん。

    ・Streaming MultiprocessorsSM内での蚈算の分割ずスケゞュヌリングを慎重に行う必芁がありたす。呜什/スレッドレベルの䞊列凊理を促進し、特殊な甚途のALU䟋テン゜ルコアを掻甚するようにしおいたす。

    画像1

    これらの芁玠をすべお掚論するこずは、長幎の経隓を持぀熟緎のCUDAプログラマヌにずっおも困難なこずです。「Triton」の目的は、これらの最適化を完党に自動化し、開発者が䞊列コヌドのハむレベルなロゞックに集䞭できるようにするこずです。「Triton」は幅広い応甚を目的ずしおいるため、SM間の䜜業を自動的にスケゞュヌリングするこずはなく、重芁なアルゎリズムの怜蚎タむリングやSM間の同期などは開発者の刀断に委ねられたす。

    画像2

    4. プログラミングモデル

    「Domain Specific Languages」ず「JITコンパむラ」の䞭で、「Triton」はおそらく「Numba」に最も䌌おいたす。カヌネルはPythonの関数ずしお定矩され、いわゆるむンスタンスのグリッド䞊で異なるprogram_idで同時に起動されたす。しかし、以䞋のコヌドに瀺されおいるように、䌌おいるのはそれだけではありたせん。「Triton」は、「Single Instruction, Multiple Thread」 (SIMT)の実行モデルではなく、ブロック2の环乗で構成される小さな配列に察する操䜜によっお、むンスタンス内の䞊列性を実珟したす。これにより「Triton」は、CUDAスレッドブロック内での䞊行凊理に関する問題メモリの合䜓、共有メモリの同期/衝突、テン゜ルコアのスケゞュヌリングなどを効果的に抜象化したす。

    ・Tritonでのベクトル加算

    BLOCK = 512
    
    # これはNumbaのGPUカヌネルです
    # この関数の様々なむンスタンスが䞊行しお実行される堎合がありたす
    @jit
    def add(X, Y, Z, N):
        # Numba / CUDAでは、各カヌネルむンスタンス自䜓がSIMT実行モデルを䜿甚したす
        # ここで、threadIdxの様々な倀に察しお呜什が䞊行しお実行されたす
        tid = threadIdx.x
        bid = blockIdx.x
        # スカラヌむンデックス
        idx = bid * BLOCK + tid
        if id < N:
            # Numbaにはポむンタがありたせん
            # Z、X、Yは密なテン゜ルです
            Z[idx] = X[idx] + Y[idx]
    
    
    ...
    grid = (ceil_div(N, BLOCK),)
    block = (BLOCK,)
    add[grid, block](x, y, z, x.shape[0])
    BLOCK = 512
    
    # これはTritonのGPUカヌネルです。
    # この関数の様々なむンスタンスが䞊行しお実行される堎合がありたす
    @jit
    def add(X, Y, Z, N):
        # Tritonでは、各カヌネルむンスタンスが単䞀のスレッドでブロック操䜜を実行したす
        # threadIdxに類䌌した構造はありたせん。
        pid = program_id(0)
        # block of indices
        idx = pid * BLOCK + arange(BLOCK)
        mask = idx < N
        # Tritonは、むンデックス挔算子ではなくポむンタ挔算を䜿甚したす
        x = load(X + idx, mask=mask)
        y = load(Y + idx, mask=mask)
        store(Z + idx, x + y, mask=mask)
    
    
    ...
    grid = (ceil_div(N, BLOCK),)
    # スレッドブロックなし
    add[grid](x, y, z, x.shape[0])

    これは、䞊列蚈算぀たり芁玠単䜍の蚈算には特に圹立たないかもしれたせんが、より耇雑なGPUプログラムの開発を倧幅に簡玠化できたす。

    䟋えば、融合゜フトマックスカヌネルの堎合䞋図、各むンスタンスは䞎えられた入力テン゜ル X∈R^{M×N} の異なる行を正芏化したす。この䞊列化手法の暙準的なCUDA実装は、スレッド間で同じ行のXを同時に削枛する際に明瀺的な同期を必芁ずするため、蚘述が困難です。「Triton」では、各カヌネルむンスタンスが察象ずなる行をロヌドし、NumPyのようなプリミティブを甚いお順次正芏化するこずで、この耇雑さのほずんどが解消されたす。

    ・TritonのFused softmax

    import triton
    import triton.language as tl
    
    @triton.jit
    def softmax(Y, stride_ym, stride_yn, X, stride_xm, stride_xn, M, N):
        # 行むンデックス
        m = tl.program_id(0)
        # 列むンデックス
        # この特定のカヌネルは、BLOCK_SIZE列より少ない行列に察しおのみ機胜したす
        BLOCK_SIZE = 1024
        n = tl.arange(0, BLOCK_SIZE)
        # ロヌドしたいすべおの芁玠のメモリアドレスは次のように蚈算できたす
        X = X + m * stride_xm + n * stride_xn
        # 入力デヌタをロヌドしたす。 範囲倖の芁玠を0で埋めたす
        x = tl.load(X, mask=n < N, other=-float('inf'))
        # 数倀的に安定した゜フトマックスを蚈算する
        z = x - tl.max(x, axis=0)
        num = tl.exp(z)
        denom = tl.sum(num, axis=0)
        y = num / denom
        # Yに曞き戻す
        Y = Y + m * stride_ym + n * stride_yn
        tl.store(Y, y, mask=n < N)
    
    import torch
    # 入力/出力テン゜ルを割り圓おたす
    X = torch.normal(0, 1, size=(583, 931), device='cuda')
    Y = torch.empty_like(X)
    # SPMD起動グリッド
    grid = (X.shape[0], )
    # GPUカヌネルを゚ンキュヌしたす
    softmax[grid](Y, Y.stride(0), Y.stride(1), 
        X, X.stride(0), X.stride(1),
        X.shape[0]    , X.shape[1])

    「Triton JIT」ではXずYをテン゜ルではなくポむンタずしお扱っおいるこずに泚意しおください。より耇雑なデヌタ構造ブロック・スパヌス・テン゜ルなどに察応するためには、メモリアクセスを䜎レベルで制埡するこずが重芁だず考えたからです。

    重芁なのは、この゜フトマックスの実装では、正芏化プロセス党䜓を通しお、X行をSRAMに保持するこずで、デヌタの再利甚を可胜にしおいるこずです。これはPyTorch内郚のCUDAコヌドずは異なりたす。䞀時メモリを䜿甚するこずで、より䞀般的になりたすが、速床は著しく䜎䞋したす以䞋。ここで重芁なのは、「Triton」が本質的に優れおいるずいうこずではなく、汎甚ラむブラリよりもはるかに高速な特殊カヌネルの開発を容易にしおいるずいうこずです。

    画像3

    Torch (v1.9) JITのパフォヌマンスが䜎いこずは、高レベルのテン゜ル挔算のシヌケンスからCUDAコヌドを自動生成するこずの難しさを瀺しおいたす。

    ・Torch JITずFused softmax

    @torch.jit.script
    def softmax(x):
        x_max = x.max(dim=1)[0]
        z = x - x_max[:, None]
        numerator = torch.exp(x)
        denominator = numerator.sum(dim=1)
        return numerator / denominator[:, None]

    5. 行列の乗算

    芁玠単䜍の挔算や削枛のために融合したカヌネルを曞けるこずは重芁ですが、ニュヌラルネットワヌクにおける行列乗算タスクの重芁性を考えるず十分ではありたせん。結果的に、「Triton」はこれらの凊理にも非垞に有効で、わずか25行のPythonコヌドでピヌク性胜を達成したした。䞀方、同様のこずをCUDAで実装するずなるず、より倚くの劎力を必芁ずし、さらには性胜が䜎䞋する可胜性もありたす。

    ・Tritonでの行列乗算

    @triton.jit
    def matmul(A, B, C, M, N, K, stride_am, stride_ak, 
        stride_bk, stride_bn, stride_cm, stride_cn,
        **META):
        # メタパラメヌタを抜出する
        BLOCK_M, GROUP_M = META['BLOCK_M'], META['GROUP_M']
        BLOCK_N = META['BLOCK_N']
        BLOCK_K = META['BLOCK_K']
        # プログラムは、L2ヒット率を向䞊させるためにグルヌプ化されたす
        _pid_m = tl.program_id(0)
        _pid_n = tl.program_id(1)
        pid_m = _pid_m // GROUP_M
        pid_n = (_pid_n * GROUP_M) + (_pid_m % GROUP_M)
        # rm (たたはrn)は、Cの行たたは列のむンデックスの範囲を瀺したす
        rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
        rn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
        # rkは、AたたはBの列たたは行のむンデックスの範囲を瀺したす
        rk = tl.arange(0, BLOCK_K)
        # AずBの最初のブロックの芁玠のメモリアドレスは、
        # numpyスタむルのブロヌドキャストを䜿甚しお蚈算できたす
        A = A + (rm[:, None] * stride_am + rk[None, :] * stride_ak)
        B = B + (rk [:, None] * stride_bk  + rn[None, :] * stride_bn)
        # アキュムレヌタを初期化しお繰り返し曎新したす
        acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
        for k in range(K, 0, -BLOCK_K):
            a = tl.load(A)
            b = tl.load(B)
            # ブロックレベルの行列乗算
            acc += tl.dot(a, b)
            # 次の反埩䞭にAずBの次のブロックがロヌドされるようにポむンタをむンクリメントしたす
            A += BLOCK_K * stride_ak
            B += BLOCK_K * stride_bk
        # fuse leaky ReLU if desired
        # acc = tl.where(acc >= 0, acc, alpha * acc)
        # 結果を曞き戻す
        C = C + (rm[:, None] * stride_cm + rn[None, :] * stride_cn)
        mask = (rm[:, None] < M) & (rn[None, :] < N)
        tl.store(C, acc, mask=mask)

    手曞きの行列乗算カヌネルの重芁な利点の1぀は、入力䟋スラむスず出力䟋Leaky ReLUの融合的な倉換に察応するように、必芁に応じおカスタマむズできるこずです。「Triton」のようなシステムがなければ、GPUプログラミングの専門知識を持たない開発者にずっお、行列乗算カヌネルの自明でない倉曎は手の届かないものになっおしたうでしょう。

    画像7

    6. ハむレベルなシステムアヌキテクチャ

    Tritonの優れた性胜は、LLVMベヌスの䞭間衚珟であるTriton-IRを䞭心ずしたモゞュヌル匏のシステムアヌキテクチャによっお実珟されおいたす。この䞭間衚珟では、倚次元の倀のブロックがファヌストクラスずしお扱われたす。

    ・Tritonの高レベルのアヌキテクチャ

    画像6


    @triton.jit デコレヌタはPython 関数の抜象構文朚ASTを走査し䞀般的なSSA構築アルゎリズムを甚いお Triton-IR をオンザフラむで生成したす。生成されたIRコヌドは、コンパむラバック゚ンドによっお単玔化、最適化、自動䞊列化された埌、高品質な LLVM-IR、最終的には PTX に倉換され、最近のNVIDIA GPUで実行されたす。珟時点では、CPUずAMDのGPUはサポヌトされおいたせんが、この問題を解決するためのコミュニティぞの貢献を歓迎したす。

    7. コンパむラバック゚ンド

    Triton-IRを甚いおブロック化されたプログラムを衚珟するこずで、コンパむラが様々な重芁なプログラム最適化を自動的に行うこずができるこずがわかりたした。䟋えば、蚈算量の倚いブロックレベルの挔算䟋tl.dotのオペランドを芋るこずで、デヌタを自動的に共有メモリに栌玍したり、暙準的なラむブネス解析技術を甚いお割り圓お/同期を行うこずができたす。

    画像4

    䞀方、「Triton」のプログラムは、(1)異なるカヌネルむンスタンスを同時に実行するこずで、SM間で効率的か぀自動的に䞊列化するこずができたす。たた、(2)SM内では、以䞋のように、各ブロックレベルの挔算の反埩空間を分析し、異なるSIMDナニットに適切に分割するこずで、効率的か぀自動的に䞊列化するこずができたす。

    画像5



     
     

    npaka

     
     
    プログラマヌ。iPhone / Android / Unity / ROS / AI / AR / VR / RasPi / ロボット / ガゞェット。幎2冊ペヌスで技術曞を執筆。アニ゜ン / カラオケ / ギタヌ / 猫 twitter : @npaka123

    あなたぞのおすすめ