web-dev-qa-db-ja.com

Python numpy float16 datatype operations、and float8?

float16 Numpy数値に対して数学演算を実行すると、結果もfloat16型の数値になります。私の質問は、結果がどのように正確に計算されるのですか? 2つのfloat16数を乗算/加算すると、pythonはfloat32で結果を生成し、その結果をfloat16に切り捨て/丸めますか?または、「16ビットマルチプレクサ/加算器ハードウェア」で実行される計算はすべて仕方?

別の質問-float8タイプはありますか?私はこれを見つけることができませんでした...見つからない場合、なぜですか?皆さん、ありがとうございました!

10
JonyK

最初の質問へ:一般的なプロセッサ(少なくとも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サポートに対する需要はそれほど多くはなかったと思います。

11
Mark Dickinson

この回答は、質問の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実装を作成することに煩わされることはほとんどありません。

3
jeteon