行列演算チュートリアル
MN-Core の行列演算は、MN-Core の演算性能の要を担っています。
これまでに紹介した ALU や MAU の演算は基本的に各 PE で独立した演算を行っていましたが、行列演算では MAB 内の \(4\) つの PE が協力してデータを供給し、\(1\) つの行列演算を行います。
倍精度行列積命令 dmfma(u|d)
では、\(1\) サイクルで MAB あたり、\(2 \times 4\) 行列と \(4\) 次元ベクトルの積が計算でき、合計で \(8\) 回の積和演算が実行されます。要素単位の積和演算 dvfma(u|d)
では、\(2\) PE がそれぞれ \(1\) 要素の積和を行っていたので、それと単純比較すると \(4\) 倍の積和演算が行えることになります。
単精度 fmfma
では \(8 \times 4\) 行列とベクトルの積を行え \(32\) 積和演算(fvfma
では \(8\) 積和演算)が、半精度 hmfma
では \(16 \times 16\) 行列とベクトルの積なので \(256\) 積和演算(hvfma
では \(16\) 積和演算)が可能なので、MN-Core のピーク演算性能を発揮するには行列演算を効率良く使いこなせるかが一つのポイントになります。
\(1\) 回で行える行列積のサイズは比較的小さめですが、大きな行列積も小さな行列積に分解し、行列積和命令 (d|f|g|h)mfma
と組み合わせることで効率的に計算できます。また行列演算器の用途は単なる密行列積計算にとどまらず、畳み込みを行列演算で表現する Winograd's Minimal Filtering Algorithm や、高速フーリエ変換で用いられるバタフライ演算を行列演算として計算する(PFN ブログ「MN-Core 向けの FFT の実装 」)など、計算を行列演算や内積計算で表現することで、幅広い活用方法が考えられます。
さて、前置きが長くなりましたが、最も基礎的である MAB 単位で行う行列積演算の説明をしていきましょう。
4x4 倍精度行列積 (AxB matmul in double)
以下の 2 つの行列 \(A, B\) の行列積を考えます。
# A B
# $lm0 $lm2 $lm4 $lm6 $ln0 $ln2 $ln4 $ln6
# +-----+-----+-----+-----+ +-----+-----+-----+-----+
# PE 0 | -0 | 0 | -1 | -1 | | -0 | 1 | -1 | 1 |
# PE 1 | 0 | 4 | 1 | -1 | mmul | 1 | -1 | 3 | 1 |
# PE 2 | 2 | -1 | -1 | 1 | mfma | -1 | 3 | 1 | 1 |
# PE 3 | -1 | -4 | -1 | -1 | | -4 | 1 | 1 | -3 |
# +-----+-----+-----+-----+ +-----+-----+-----+-----+
# A
d set $lm0n0c0b0m0p0 4 80000000000000000000000000000000BFF0000000000000BFF0000000000000
d set $lm0n0c0b0m0p1 4 000000000000000040100000000000003FF0000000000000BFF0000000000000
d set $lm0n0c0b0m0p2 4 4000000000000000BFF0000000000000BFF00000000000003FF0000000000000
d set $lm0n0c0b0m0p3 4 BFF0000000000000C010000000000000BFF0000000000000BFF0000000000000
# B
d set $ln0n0c0b0m0p0 4 80000000000000003FF00000000000003FF0000000000000BFF0000000000000
d set $ln0n0c0b0m0p1 4 3FF0000000000000BFF00000000000003FF00000000000003FF0000000000000
d set $ln0n0c0b0m0p2 4 BFF000000000000040080000000000003FF00000000000003FF0000000000000
d set $ln0n0c0b0m0p3 4 C0100000000000003FF0000000000000C008000000000000BFF0000000000000
d getd
でダンプすることで、行列 \(A, B\) が \(4\) つの PE の $lm[0:8]
と $ln[0:8]
に分散して格納されていることが確認できます。
d getd $lm0n0c0b0m0 4
d getd $ln0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p0,0):(-0) (0x8000000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p0,2):(0) (0x0000000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p0,4):(-1) (0xbff0000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p0,6):(-1) (0xbff0000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p1,0):(0) (0x0000000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p1,2):(4) (0x4010000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p1,4):(1) (0x3ff0000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p1,6):(-1) (0xbff0000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p2,0):(2) (0x4000000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p2,2):(-1) (0xbff0000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p2,4):(-1) (0xbff0000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p2,6):(1) (0x3ff0000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p3,0):(-1) (0xbff0000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p3,2):(-4) (0xc010000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p3,4):(-1) (0xbff0000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p3,6):(-1) (0xbff0000000000000) #d getd $lm0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p0,0):(-0) (0x8000000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p0,2):(1) (0x3ff0000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p0,4):(1) (0x3ff0000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p0,6):(-1) (0xbff0000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p1,0):(1) (0x3ff0000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p1,2):(-1) (0xbff0000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p1,4):(1) (0x3ff0000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p1,6):(1) (0x3ff0000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p2,0):(-1) (0xbff0000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p2,2):(3) (0x4008000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p2,4):(1) (0x3ff0000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p2,6):(1) (0x3ff0000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p3,0):(-4) (0xc010000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p3,2):(1) (0x3ff0000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p3,4):(-3) (0xc008000000000000) #d getd $ln0n0c0b0m0 4
DEBUG-LM1(n0c0b0m0p3,6):(-1) (0xbff0000000000000) #d getd $ln0n0c0b0m0 4
コードテストで実行している方は「d set を有効にする ( --enable-set )」チェックボックスを有効にするのを忘れないようにしましょう。
行列演算を行うために、行列 \(A\) ($lm0v
) を行列レジスタ $lx
に書き込む必要がありますが、その前にブロックフロート化(以下「BF 化」)をします。
BF 化とは入力の浮動小数点数の指数部の値を揃える操作です。この際、暗黙の最上位ビットの 1
を明示する必要があるため、ケチ表現を使用しないフォーマットに変換されます。
これにより行列演算器の内積回路を単純化でき、また値を使い回す際に毎回指数部の調整を行う手間が省けるため、計算の効率化に貢献しています。更に、指数を揃える仕事を ALU に分けることで、MAU は膨大な積和演算に集中できます。
倍精度の場合は dbfn
命令を使って BF 化します。
dbfn $lm0v $lr0v # BF-ed A
dbfn $ln0v $ls0v # BF-ed B
d getd
でダンプすることで、PE 間 の $lm0v
の指数部が揃えられた非ケチの値になっていることが確認できます。
d getd $lr0n0c0b0m0 4 # BF 化された A を出力
DEBUG-GREG0(n0c0b0m0p0,0):(-2) (0xc000000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p0,2):(4) (0x4010000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p0,4):(-1.5) (0xbff8000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p0,6):(-1.5) (0xbff8000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p1,0):(2) (0x4000000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p1,2):(6) (0x4018000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p1,4):(1.5) (0x3ff8000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p1,6):(-1.5) (0xbff8000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p2,0):(3) (0x4008000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p2,2):(-4.5) (0xc012000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p2,4):(-1.5) (0xbff8000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p2,6):(1.5) (0x3ff8000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p3,0):(-2.5) (0xc004000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p3,2):(-6) (0xc018000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p3,4):(-1.5) (0xbff8000000000000) #d getd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p3,6):(-1.5) (0xbff8000000000000) #d getd $lr0n0c0b0m0 4
各 PE の \(0\) 番目の値に注目すると、元の値が [-0, 0, 2, -1]
だったところ、[-2, 2, 3, -2.5]
になりました。
[-0, 0, 2, -1]
の中で最も指数部が大きなものは \(2\) です。BF 化された非ケチ表現の値を d getd
でケチ表現として解釈したとき、暗黙の最上位ビット 1
が存在するものとして、最大指数部の重み \(m = 2\) が現れます。
そして、非ケチにするために値が 1 つ右シフトされ、絶対値としては元の \(x\) に対して \(m + x/2\) の値に解釈されます。
\(1\) 番目の値 ($lm2
と BF 化後の $lr2
) に注目してみても、元の値 [0, 4 ,-1, -4]
に対して、最大指数部の重み は \(4\) であり、BF 化後の値は [4, 6, -4.5, -6]
と変換されています。
また、 d getbd
のように指定することで、対象が BF 化された値と解釈してダンプすることが可能です。(2024/8/28 追記)
d getbd $lr0n0c0b0m0 4
出力:
DEBUG-GREG0(n0c0b0m0p0,0):(-0) (0xc000000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p0,2):(0) (0x4010000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p0,4):(-1) (0xbff8000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p0,6):(-1) (0xbff8000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p1,0):(0) (0x4000000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p1,2):(4) (0x4018000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p1,4):(1) (0x3ff8000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p1,6):(-1) (0xbff8000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p2,0):(2) (0x4008000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p2,2):(-1) (0xc012000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p2,4):(-1) (0xbff8000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p2,6):(1) (0x3ff8000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p3,0):(-1) (0xc004000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p3,2):(-4) (0xc018000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p3,4):(-1) (0xbff8000000000000) #d getbd $lr0n0c0b0m0 4
DEBUG-GREG0(n0c0b0m0p3,6):(-1) (0xbff8000000000000) #d getbd $lr0n0c0b0m0 4
詳しくは SDM 3.4.3「Debug get 文」を参照してください。
それでは BF 化された行列 \(A\) を行列レジスタに書き込み、行列乗算をしましょう。
# 次にBF-ed Aを行列レジスタへ書き込む
dmwrite $lr0v $lx0
# dmmulu+dmfmadを発行する
dmmulu $lx $ls0v $nowrite
dmfmad $lx $ls0v $mauf $lm8v
d getd $lm8n0c0b0m0 4
倍精度行列積(和)命令は、倍精度乗算命令dvmul(u|d)
と同様に、PE0,1 だけ乗算を動作させる dm(mul|fma)u
と、PE2,3 だけ乗算を動作させる dm(mul|fma)d
に分かれています。乗算が動作しない側も乗算回路が \(0\) を出力するので、\(2\) 命令目を積和演算 fma
にすることで全てのPEで行列乗算結果が揃います。
なお積和演算 mfma
において、加算項に関してはブロックフロート化は必要ありません。
さて、出力結果を見てみましょう。
DEBUG-LM0(n0c0b0m0p0,8):(2) (0x4000000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p0,10):(5) (0x4014000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p0,12):(5) (0x4014000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p0,14):(3) (0x4008000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p1,8):(21) (0x4035000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p1,10):(-11) (0xc026000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p1,12):(15) (0x402e000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p1,14):(7) (0x401c000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p2,8):(6) (0x4018000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p2,10):(-6) (0xc018000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p2,12):(2) (0x4000000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p2,14):(2) (0x4000000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p3,8):(2) (0x4000000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p3,10):(2) (0x4000000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p3,12):(2) (0x4000000000000000) #d getd $lm8n0c0b0m0 4
DEBUG-LM0(n0c0b0m0p3,14):(2) (0x4000000000000000) #d getd $lm8n0c0b0m0 4
結果を簡単に図示するとこのようになります。
# A (on $lx) B
$lx0 $lx1 $lx2 $lx3 $ln0 $ln2 $ln4 $ln6
+-----+-----+-----+-----+ +-----+-----+-----+-----+
PE 0 | -0 | 0 | -1 | -1 | | -0 | 1 | -1 | 1 |
PE 1 | 0 | 4 | 1 | -1 | mmul | 1 | -1 | 3 | 1 |
PE 2 | 2 | -1 | -1 | 1 | mfma | -1 | 3 | 1 | 1 |
PE 3 | -1 | -4 | -1 | -1 | | -4 | 1 | 1 | -3 |
+-----+-----+-----+-----+ +-----+-----+-----+-----+
= Result
$lm8 $lm10 $lm12 $lx14
+-----+-----+-----+-----+
PE 0 | 2 | 5 | 5 | 3 |
PE 1 | 21 | -11 | 15 | 7 |
PE 2 | 6 | -6 | 2 | 2 |
PE 3 | 2 | 2 | 2 | 2 |
+-----+-----+-----+-----+
結果をみると、普通の行列積とは異なる計算結果になりました。
NumPy などで計算してみると、[[5, -4, 2, 0], [7, -2, 8, 6], [-4, 1, -3, -5], [1, -1, -3, -3]]
という結果を得られるはずです。
import numpy as np
a = np.array(
[[0, 0, -1, -1], [0, 4, 1, -1], [2, -1, -1, 1], [-1, -4, -1, -1]]
)
b = np.array(
[[0, 1, 1, -1], [1, -1, 1, 1], [-1, 3, 1, 1], [-4, 1, -3, -1]]
)
print(a @ b)
これは、MN-Core の行列演算は回路をシンプルにするために、PE 方向を行、アドレス方向を列と見たとき \(A^TB\) を計算することになっているからです。print(a.T @ b)
をすると、
[[ 2 5 5 3]
[ 21 -11 15 7]
[ 6 -6 2 2]
[ 2 2 2 2]]
という結果が得られ、値が一致する事が確かめられます。
以下の図は自然な行列積の表示にしたとき、各値がどの PE のどのアドレス(何サイクル目)に相当する値なのかを表しています。
左の行列 \(A\) に関しては dmwrite
したときの各担当 PE とサイクルを表しています。PE ごとに色を分けて表示しており、各 PE で行列あたり \(4\) つの値を入出力しています。
濃い色はどの要素の内積が担当しているかを示しており、出力である青(PE \(0\))のc0
に注目すると、行列 \(A\) の \(0\) サイクル目の \(4\) 値と、行列 \(B\) の \(0\) サイクル目の \(4\) 値の内積であることが分かります。
倍精度の乗算に関して一度に半分の PE しか動作しないため、図の行列演算は \(2\) 命令(\(2\) ステップ)かかります。
行列積の片方が転置されて計算していると聞くと、全く異なる計算を行っていると感じるかもしれません。しかし、MN-Coreはその特殊なメモリ構造から、上流のデータを並べ替えてから PE メモリへ送る必要があり、その際に転置のレイアウトも考慮して転送することで、求めたい計算結果を基本的に追加コストなく得ることができます。
また、mwrite
命令で行列レジスタに書き込まれた内容を、行列レジスタ転置読み出し命令 mread
で読み出すことでも、行列の転置を行うことができます。転置読み出しの出力は行列レジスタ書き込み時の値がビットレベルでそのまま読み出されます。
なお MN-Core 2 には、行列レジスタへ転置書き込みを行ったり、通常の順で読み出しする命令は存在しません。
「mwrite
命令」「mread
命令」「mfma
, vfma
などの MAU 演算」のうち、\(2\) 種までは同時に実行可能です。ただし mwrite
の入力と、vfma
と vmul
で使用される第 \(2\) 入力は共用されているため、そのふたつの PE オペランド・符号・精度拡張と丸めの指定、はそれぞれ同一である必要があります。行列演算 mfma
, mmul
の場合はこの制約は受けません。(SDM 3.6.4「並列実行条件」)。
単精度、疑似単精度の行列積
倍精度の行列積の次は、単精度と疑似単精度の行列積です。
疑似単精度とは MN-Core の行列演算で登場する精度であり、単精度の仮数部長を \(23\) bit から \(18\) bit へ削減したものです。
具体的には指数部は単精度と同じ\(8\) bit、仮数部長は \(18\) bit でMSB側に有効値が配置されており、単精度から LSB 側 \(5\) bit を無効化したものです。符号部も合わせて \(27\) bit の値ですが、メモリ上では単精度と同じく単語(\(32\) bit)単位で配置して使用します。
疑似単精度は MAU の乗算器を最大限有効活用できるような bit 数として設計されており、単精度では \(1\) 命令で \(8 \times 4\) 行列しか計算できなかったところ、\(8 \times 8\) 行列の計算が行えるようになり、精度が問題ないのであればスループットを倍にできます。
単精度と疑似単精度の行列積を図示すると以下のようになります。
\(1\) マスが単語(\(32\) bit)を示し、\(2\) マスを囲む太い黒枠は \(1\) 長語(\(64\) bit)の単位を示しています。
精度に合わせて単精度であれば fbfn
、疑似単精度であれば gbfn
命令で BF 化を行った後、fmwrite
/gmwrite
命令で行列レジスタに書き込みます。
単精度行列積を行うときは、行列レジスタのうち \(0,2,4,6\) 列目を抽出した \(8\) 行 \(4\) 列の行列が使用されます。つまり長語単位で書き込まれた行列レジスタの内容のうち、MSB 側 \(1\) 単語を使用します。
行列レジスタを \(8\) 行を使い、倍精度と比べると倍の領域サイズになるため、行列レジスタ全体を書き換えるには以下のように \(2\) 回に分けて (f|g)mwrite
する必要があります。
gmwrite $lr0v $lx0
gmwrite $lr8v $lx4
ここで、LM, GRF は単語 (\(32\) bit) 基準のアドレッシングですが、行列レジスタは長語 (\(64\) bit) 基準のアドレッシングですのでお気をつけ下さい。
倍精度以外の行列積和命令は \(1\) 命令で \(4\) PE すべて動作するため、上図の行列演算は \(1\) ステップで行えます。
半精度の行列積
半精度の場合はサイクルあたり \(16 \times 16\) 行列とベクトルの積を MAU で計算できます。以下の図は \(1\) ステップで行われる計算を図示したものです。
行列は疑似単精度と比較して \(2\) 倍のデータを書き込む必要があるので、長語単位で書き込むと \(4\) 命令必要になります。
hmwrite $lr0v $lx0
hmwrite $lr8v $lx4
hmwrite $lr16v $lx8
hmwrite $lr24v $lx12
しかし半精度に限り行列レジスタ書き込みを \(2\) 長語で動作可能であり、以下のように書き換えられます。
hmwrite $llr0v $llx0
hmwrite $llr16v $llx8
なお、回路の簡素化のために精度に応じて別の内部行列アドレスを使用しています。そのため例えば gmwrite $lr0v $lx0
と hmwrite $lr0v $lx0
では別の動作をしますし、\(2\) 長語動作の hmwrite
命令で疑似単精度に必要な行列書き込みを \(1\) 回で行うことはできません。
また半精度の行列積和命令 hmfma
は、半精度積和命令 hvfma
と同じく half × half + float = float の精度で行われるため、この精度に合わせて入出力を行う必要があります。
半精度で BF 化を行う命令は、hbfn/9
のように、\(6\) から \(9\) の範囲で変換後の仮数部長を指定する必要があります。有効仮数部長を小さくすると演算精度が低下する代わりに電力消費量の低減が期待されます。BF 化命令も半精度に限り、サイクルあたり \(2\) 長語で動作可能です。
リンク