機械学習で使う用にhalf float(16-bit float)の演算ライブラリ作りました。と言ってもfloat型がIEEE 754の形式であることを仮定して、符号bitと指数部、小数部をそのままとってきてuint16_tに変換する、みたいな感じのお手軽実装ですが。
このライブラリの必要性
コンピューター将棋の評価関数の機械学習では、KPPT型の評価関数が主流で、その配列の大きさが1GB(次元数で言うと5億次元)程度になっています。今後、評価関数の次元数を増やそうと思ったときに、省メモリ化がどうしても必須になるので、half floatで計算してくれるライブラリを製作しました。
世の中にはすでにhalf floatのライブラリはいくつかあるのですが、わかりやすくシンプルに実装してあって、お手軽に使えそうなものがなかったので自作しました。
注意点
ちなみにソースコード中に出てくるs16,s32はそれぞれint16_t,int32_tの意味です。typedefなどで置き換えて使ってください。
ソースコードは非常にシンプルに書いてあるので読みやすいと思います。適宜、改造して使ってみてください。
ライセンス
MIT Licenseで配布します。
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
#ifndef __HALF_FLOAT_H__ #define __HALF_FLOAT_H__ // Half Float Library by yaneurao // (16-bit float) namespace HalfFloat { // IEEE 754 float 32 format is : // sign(1bit) + exponent(8bits) + fraction(23bits) = 32bits // // Our float16 format is : // sign(1bit) + exponent(5bits) + fraction(10bits) = 16bits union float32_converter { s32 n; float f; }; // 16-bit float struct float16 { // --- constructors float16() {} float16(s16 n) { from_float((float)n); } float16(s32 n) { from_float((float)n); } float16(float n) { from_float(n); } float16(double n) { from_float((float)n); } // build from a float void from_float(float f) { *this = to_float16(f); } // --- implicit converters operator s32() const { return (s32)to_float(*this); } operator float() const { return to_float(*this); } operator double() const { return double(to_float(*this)); } // --- operators float16 operator += (float16 rhs) { from_float(to_float(*this) + to_float(rhs)); return *this; } float16 operator -= (float16 rhs) { from_float(to_float(*this) - to_float(rhs)); return *this; } float16 operator *= (float16 rhs) { from_float(to_float(*this) * to_float(rhs)); return *this; } float16 operator /= (float16 rhs) { from_float(to_float(*this) / to_float(rhs)); return *this; } float16 operator + (float16 rhs) const { return float16(*this) += rhs; } float16 operator - (float16 rhs) const { return float16(*this) -= rhs; } float16 operator * (float16 rhs) const { return float16(*this) *= rhs; } float16 operator / (float16 rhs) const { return float16(*this) /= rhs; } float16 operator - () const { return float16(-to_float(*this)); } bool operator == (float16 rhs) const { return this->v_ == rhs.v_; } bool operator != (float16 rhs) const { return !(*this == rhs); } private: // --- entity u16 v_; // --- conversion between float and float16 static float16 to_float16(float f) { float32_converter c; c.f = f; u32 n = c.n; // The sign bit is MSB in common. u16 sign_bit = (n >> 16) & 0x8000; // The exponent of IEEE 754's float 32 is biased +127 , so we change this bias into +15 and limited to 5-bit. u16 exponent = (((n >> 23) - 127 + 15) & 0x1f) << 10; // The fraction is limited to 10-bit. u16 fraction = (n >> (23-10)) & 0x3ff; float16 f_; f_.v_ = sign_bit | exponent | fraction; return f_; } static float to_float(float16 v) { u32 sign_bit = (v.v_ & 0x8000) << 16; u32 exponent = ((((v.v_ >> 10) & 0x1f) - 15 + 127) & 0xff) << 23; u32 fraction = (v.v_ & 0x3ff) << (23 - 10); float32_converter c; c.n = sign_bit | exponent | fraction; return c.f; } }; } #endif // __HALF_FLOAT_H__ |
言われてサクッと作っちゃう当たりさすがです。
今後のCPUにサポートされていくといわれていますが、言語側はまだまだですね。
AIにもつかうそうなので、屋根さんの未来も安泰でしょうか??
https://twitter.com/wain_CGP/status/869145414594777089
> 実はx86の拡張命令に半精度・単精度の相互変換命令はあったり。(F16C)
> IntelではIvyBridge以降AMDではPiledriver以降ならば対応している。
> 残念ながら1要素用の命令はないみたいだけど。
へ〜へ〜へ〜。
あら?最近対応始めたもんだと思ってましたが、ivyからあるんですね。
非正規化数がらみの変換で値がおかしくなるとか、絶対値の大きすぎる値が無限大にならない (指数部が飽和しない) とか、float16の無限大・NaNが実数に変換されるとか、色々ありますが……
まあ、機械学習で使う場合、値が範囲内に収まっていることは使う側で守るので、問題にはならないかと。assertぐらいは書いてあったほうが良いかもですが。