float16 Numpy数値に対して数学演算を実行すると、結果もfloat16型の数値になります。私の質問は、結果がどのように正確に計算されるのですか? 2つのfloat16数を乗算/加算すると、pythonはfloat32で結果を生成し、その結果をfloat16に切り捨て/丸めますか?または、「16ビットマルチプレクサ/加算器ハードウェア」で実行される計算はすべて仕方?
別の質問-float8タイプはありますか?私はこれを見つけることができませんでした...見つからない場合、なぜですか?皆さん、ありがとうございました!
最初の質問へ:一般的なプロセッサ(少なくともGPUの外側)ではfloat16
のハードウェアサポートはありません。 NumPyは、まさにあなたが提案することを実行します。float16
オペランドをfloat32
に変換し、float32
値に対してスカラー演算を実行してから、float32
の結果をfloat16
に丸めます。結果が依然として正しく丸められていることを証明できます。float32
の精度は(float16
の精度に比べて)十分に大きいため、少なくとも4つの基本的な算術演算と平方根では、二重丸めは問題になりません。
現在のNumPyソースでは、これがfloat16
スカラー演算の4つの基本的な算術演算の定義のようになります。
#define half_ctype_add(a, b, outp) *(outp) = \
npy_float_to_half(npy_half_to_float(a) + npy_half_to_float(b))
#define half_ctype_subtract(a, b, outp) *(outp) = \
npy_float_to_half(npy_half_to_float(a) - npy_half_to_float(b))
#define half_ctype_multiply(a, b, outp) *(outp) = \
npy_float_to_half(npy_half_to_float(a) * npy_half_to_float(b))
#define half_ctype_divide(a, b, outp) *(outp) = \
npy_float_to_half(npy_half_to_float(a) / npy_half_to_float(b))
上記のコードは、NumPyソースの scalarmath.c.src から取得されます。また、配列ufuncに対応するコードについて loops.c.src を確認することもできます。サポートするnpy_half_to_float
関数とnpy_float_to_half
関数は、float16
型の他のさまざまなサポート関数とともに、 halffloat.c で定義されます。
2番目の質問:いいえ、NumPyにはfloat8
タイプはありません。 float16
は標準化されたタイプ(IEEE 754標準で説明)であり、一部のコンテキスト(特にGPU)ですでに広く使用されています。 IEEE 754 float8
タイプはなく、「標準」のfloat8
タイプの明確な候補はないようです。また、NumPyでのfloat8
サポートに対する需要はそれほど多くはなかったと思います。
この回答は、質問のfloat8
の側面に基づいています。受け入れられた答えは残りをかなりうまくカバーします。標準の欠如以外に、広く受け入れられているfloat8
型がない主な理由の1つは、それが実際にはあまり役に立たないことです。
標準表記では、float[n]
データ型はn
ビットを使用してメモリに格納されます。つまり、表現できるのは、最大で2^n
の一意の値のみです。 IEEE 754では、nan
のようなこれらの可能な値の一部は、それ自体が偶数ではありません。つまり、すべての浮動小数点表現(float256
を実行した場合でも)は、表現できる有理数のセットにギャップがあり、このギャップの数値の表現を取得しようとすると、最も近い値に丸められます。一般に、n
が高いほど、これらのギャップは小さくなります。
struct
パッケージを使用して一部のfloat32
数値のバイナリ表現を取得すると、ギャップが発生することがわかります。最初はちょっとびっくりしますが、整数空間には32のギャップがあります。
import struct
billion_as_float32 = struct.pack('f', 1000000000 + i)
for i in range(32):
billion_as_float32 == struct.pack('f', 1000000001 + i) // True
一般に、浮動小数点は、最上位ビットのみを追跡するのに最適であるため、数値のスケールが同じである場合、重要な違いが保持されます。浮動小数点標準は一般に、使用可能なビットを基数と指数の間で分配する方法のみが異なります。たとえば、IEEE 754 float32
は、ベースに24ビット、指数に8ビットを使用します。
float8
に戻る上記のロジックにより、float8
値は、ビットを基数と指数の間で分割するのがどれほど巧妙であっても、256の異なる値のみを取ることができます。ゼロに近いクラスター化された256の任意の数値の1つに数値を丸めることに熱心でない限り、int8
の256の可能性を追跡するだけでおそらくより効率的です。
たとえば、非常に狭い範囲を粗い精度で追跡する場合、必要な範囲を256ポイントに分割し、256ポイントのうちどれが最も近いかを保存できます。本当に凝ったものにしたい場合は、最も重要なことに応じて、中央または端にクラスター化された値の非線形分布を作成できます。
他の誰か(または後で自分でも)がこの正確なスキームを必要とする可能性は非常に小さく、ほとんどの場合、代わりにfloat16
またはfloat32
を使用するためのペナルティとして追加のバイトまたは3を支払うのは小さすぎます意味のある違いを作るために。したがって... float8
実装を作成することに煩わされることはほとんどありません。