第97話のGPU routeへ、掛け算の板が届いた。
input matrix Aは8×8。
weight matrix Bも8×8。
作るoutput matrix Cは8×8。
イトはCの左上から、答えを一つずつ一列のlaneへ並べた。
output cells 64
active compute lane 1
multiply-accumulate steps 512
estimated finish tick 64
deadline tick 20
commit 0
壁一面のlaneは空いている。
一列目だけが、64個の答えを抱えて赤くなった。
「同じ形に分けたのに、また一列へ積んでた」
一つのoutput cellは、rowとcolumnのdot product
matrix multiplication C = A × Bをsmall fixtureで追う。
Cの一つのcellを作るには、Aの一rowとBの一columnを同じ位置どうしで掛け、足し合わせる。
8×8なら、一cellにつき8回のmultiply-accumulateを進める。
C[row, col]
= A[row, 0] * B[0, col]
+ A[row, 1] * B[1, col]
+ ...
+ A[row, 7] * B[7, col]
input A / Bが計算中に変わらないなら、Cの別cellは別threadへ割り当てられる。
64 cellを64 threadへ分ける形を作れる。
neural networkのdense layerにも、inputとweightのdot productへbiasやactivationを組み合わせる処理がある。
ただしAI modelの仕事がmatrix multiplicationだけとは限らない。
normalization、activation、memory movement、control、前処理や後処理もある。
「掛け算があるから、どんなAIでもGPUなら速い」とは決めない。
threadを増やしたら、今度はdataが届かなかった
イトは64 laneを開くpreviewへ切り替えた。
各threadがoutput一cellを受け持つ。
compute boardは八tickで終わる表示になった。
でもfar memory shelfから、同じAのrowとBのcolumnをthreadごとに読み直している。
mapping one thread / output cell
output cells 64
global element reads 1024
compute ready tick 8
data ready tick 34
observed finish tick 34
laneは増えた。
材料待ちは増えた。
「計算する手が空いても、同じ数字を遠くから何度も運んでたら待つんだ」
matrixをtileへ分け、近くでreuseする
大きなmatrix全体を、すべて一度に近いmemoryへ置けるとは限らない。
そこでoutput Cをsmall tileへ分ける。
今回のclosed fixtureでは4×4 tileを使う。
Cには四つのoutput tileができる。
各thread blockが一つのoutput tileを受け持つ。
必要なA tileとB tileをglobal memoryから近いshared trayへ協力してloadする。
loadがそろう前に計算を始めないよう、block内でsynchronizeする。
tile内のthreadは、近いtrayの同じvalueをreuseする。
K方向の次tileをloadし、partial sumへ足す。
最後に完成したC cellをglobal memoryへwriteする。
output tiles 4
tile size 4x4
K phases per output tile 2
global element reads 256
output writes 64
これは仕組みを見えるようにしたlab countだ。
real kernelのload数、cache hit、instruction、timeはdevice、library、data type、shape、implementationで変わる。
よくある誤解:VRAMが多いほど、同じ計算は必ず速い
VRAM capacityが大きければ、larger model、input、intermediate stateをdevice側へ置ける余地が増える。
capacity不足でworkloadが載らない、分割や移動が増える、という問題を避けられる場合がある。
でもcapacityは、dataが一秒にどれだけ動くかというbandwidthと同じではない。
access patternも別だ。
近いaddressをgroupでまとめて読むcoalesced accessなら、memory transactionを有効に使いやすい。
ばらばらのaddressを読むと、運んだ帯域の一部を使わずに捨てることがある。
shared memoryを使っても、bank conflictや不要なsynchronizationを増やせば別の待ちが生まれる。
discrete GPUではhost memoryとdevice memory間のtransferも調べる。
integrated GPUなどmemory構成が違うdeviceへ、同じ棚の比喩を固定しない。
見るのはcapacityだけではない。
fits in memory
host-device transfer
requested / effective bandwidth
access pattern
reuse
synchronization
end-to-end time
training preview終了まで残り17秒
output Cをreference CPU resultとexact compareする。
残り十七秒。
input A / Bはread-only。
deadlineはtick 20。
global element read budgetは320以下。
output 64 cellのmissing / duplicate / mismatchは0にする。
tile loadがそろわなければcomputeへ進まない。
イトはmatrix wallの前へ、三つのroute cardを置いた。
イト
A。output 64 cellを、一つのcompute laneで順番に作る
イト
B。64 threadへ分けるけど、AとBを各threadがglobal memoryから毎回読み直す
イト
C。4×4 output tileへ分け、blockでA / B tileをloadして近いtrayからreuseする
Aは正しいがdeadlineを越える。
Bはcompute laneを使えても、data readyが遅れる。
Cにはtile boundaryとsynchronizationの管理が増える。
それでもglobal readをbudget内へ収め、outputごとの対応を保てる。
イトがsingle-cell queueを閉じ、四つのoutput tileを開いた
イトは左手で、64 cellを一列へ流すred queue gateを閉じた。
naive reread leverにもdark lockを掛ける。
右手で、matrix wallを四つに分けるtile guardを開いた。
cyan A tileとamber B tileを、各blockのnear shared trayへそろえて置いた。
tile-ready lampを確認してから、compute gateを開く。

Picoはglobal shelf、shared tray、output tileの境界だけを照らす。
gate、lever、input、tray、thread、outputには触れない。
64 cellがそろい、memory待ちが消えた
イトはfirst output tileだけをsingle-stepする。
output tile top-left 4x4
threads active 16
A tile loaded 16
B tile loaded 16
tile-ready 1
partial output cells 16
K方向のsecond phaseをloadする。
second A tile loaded 16
second B tile loaded 16
block synchronized 1
completed output cells 16
残り三blockも同じmappingで進める。
completed output cells 64 / 64
global element reads 256
output writes 64
observed finish tick 14
reference mismatch 0
input A / B changed 0
read budget 320以下。
deadline tick 20より六tick早い。
イトは赤いqueue lampが消えるのを見届けた。
「laneを増やしただけじゃない。近くへ一度運んで、同じ数字を使い回した」
tileにもcostと境界がある
tileが大きすぎれば、shared memoryやregisterのresourceを使い切る。
小さすぎれば、loadとsynchronizationの回数が増える。
matrix sizeがtileの倍数でなければ、edgeのbounds checkやpaddingが要る。
別thread block間で自由に同じshared trayを使えるわけでもない。
highly tuned libraryやcompilerが適切なkernelを選ぶ場合もある。
だから「4×4が正解」と保存しない。
matrix shape and data type
tile / block mapping
memory layout and transfer
resource usage
correctness
measured end-to-end time
target deviceとreal workloadで測り直す。
同じ音声cardに、二つの行き先が開いた
matrix wallがWAIT_EVENTへ戻る。
ユイの端末から、短いvoice memoが届いた。
同じ音声を文字へ変えるinference requestだ。
raw voice location device
small local model available
larger cloud model available
network unstable
privacy permission undecided
イトは、端末内のsmall compute roomと、networkの先にあるcloud hallを同時に開きかけた。
でもraw voiceを送るかどうかと、どちらのmodelを使うかは、matrixの速さだけでは決められない。
片方はofflineでも動く。
片方はnetworkとremote serviceへ依存する。
イトは二つのsend gateを閉じたまま、voice memoの条件を先に並べた。
次回、ピコルート第99話。
イトは、同じ声を端末と雲へ同時送信しかけた|クラウドAIとオンデバイスAIは何が違う?

