Q4_0
0を中心に対称に分布していることを前提にしているので QAT でもしないとやってられない感じになる $ v=d(q-8)
$ dスケール。f16
$ qゲタ付き u4
ブロックサイズが32なので 4+(16/32)=4.5b
格納方法が若干独特で、[u8; 16]と解釈したときの各要素の下位4ビットが最初の16個を、上位4ビットが後半の16個を表わしている
i4x8 変換
Q4_0 はゲタ付き表現なので、最近の GPU にあるようなパックされた符号付き整数の内積を取る命令にそのまま適用できない
これらの命令は2の補数であることを要求している
8を繰り上がりが起きないように半加算で足すといい(スケールはそのまま)
code:q4_0_to_i4x8.c
y = x ^ 0x88888888
しかし対応しているプロセッサなら i4x8 の内積は1命令で実行できるので、フォーマットの変換が占める割合が相対的にかなり大きくなって少し悲しい
i8x4 変換
i4x8 の内積を実装しているプロセッサは少ないし、i4 だと精度が足りないので中間の値を持つとあんまり良いことにならない。そんな感じで i8x4 の内積のほうが使いやすい。
右シフトには算術シフトと論理シフトの区別があり、パックされた整数表現では論理右シフトしか使いものにならないが、論理右シフトをすると2の補数表現が失われる。そこで左シフトだけを使って各フィールドが16倍された表現を得ることにする
code:q4_0_to_i8x4.c
d /= 16 // スケールを調整しておく
...
y = x ^ 0x88888888
ylo = x & 0x0f0f0f0f
ylo <<= 4
yhi = x & 0xf0f0f0f0
内積は2命令になったが変換は4命令になってる!鬱