2015/01/27(火)INTLAB version 9の重要性
(なぜかページが2つあって微妙に中身が違うので両方載せておこう。)
まったく予想してなかったのですが、このINTLABがversion 9にアップデートして、新たにOctaveに対応したそうです。Octaveは、フリーの有名なmatlabクローンです。と言っても、完璧な互換性を目指してるわけでは無さそう。
INTLABはRump先生の大変な努力で作成された素晴らしいソフトウェアで、この分野のデファクトスタンダードと言えるものです。ところが、最近はきちんと動かないことが多く、周囲の研究者に聞くと、matlabのversionを2012aで留めておかないと、いろいろまずいことがおきるのだそうです。
これは、matlabのせいと言うよりは、matlabの中で使っているBLASの問題です。
BLAS(=Basic Linear Algebra Subprograms)は、LAPACKから呼び出される行列積などの基本的なプログラムの集合体です。近年の計算機では計算速度に比較して相対的にメインメモリが遅くなっており、キャッシュメモリを使ってその遅さをカバーしています。そのため、行列積などのプログラムはキャッシュのヒット率を極限まで高めるきわめて技巧的なプログラミングが要求され、高速なプログラムは誰でも書けるものではなくなってしまいました。実際、素人が書いた単なる三重ループとBLASの持つ行列積とでは速度が10倍以上違うのが普通です。また、CPUの性能を極限まで引き出すにはそのCPUに合わせたプログラムが必要になります。そのため、行列積などの基本的なプログラムはインターフェースだけ揃えておいて中身は自由に差し替えられるように分離するようになり、それがBLASです。
最近のmatlabで使っているBLASはIntel Math Kernel Libraryに含まれているもので、これの挙動が精度保証に使っている手法と合わなくなってしまったため、きちんと精度保証が出来なくなってしまったというわけです。
より詳しく説明します。浮動小数点数(double)を係数に持つ行列A,Bがあったとき、行列積ABを精度保証付きで計算する問題を考えます。三重ループで行列積を計算するプログラムを元に、計算過程を全て区間演算に置き換えれば精度保証付きの結果を得ることが出来ます。ところが、このようなことをすると高速なBLASの恩恵にあずかることが出来ません。そこで、
- 丸めの向きを下向きに変える
- C1=ABをBLASで計算する。
- 丸めの向きを上向きに変える。
- C2=ABをBLASで計算する。
- (丸めの向きを元に戻す)
BLASの中でマルチスレッドあるいはマルチコア計算を行っており、他のスレッドまたはコアに丸めの向きの情報が伝わらないことがあって、この場合は精度保証されていない結果が得られてしまうことになります。また、
BLASの中でStrassenアルゴリズムなどの「減算を含む」計算が使われている場合は、「計算式の中の全ての計算が下向き丸めならば最終的な計算結果が下向き丸めになる」という仮定を満たさなくなってしまい、このケースも精度保証が破れてしまいます。Strassenアルゴリズムはある程度大きい行列でないと効果がないことが知られていますが、計算機の発展に伴い大きな問題が解けるようになってきてそろそろStrassenアルゴリズムが有効に機能する段階に入りつつあると考えられています。
上記のような精度保証の破綻は、自分でBLASを書いたならば当然そういうことがありえるのかどうか判断できますが、closed sourceな他人の書いたBLASの場合はそうはいきません。Intel Math Kernel Libraryはまさにclosed sourceであり、2012年に(何があったか窺い知ることは出来ないが)何らかの変更が行われ精度保証を破綻させてしまったというわけです。
さらに考えてみれば、2012aまでのmatlabでも「観察の結果問題が無いように見える」だけであり、実は精度保証が破綻しているケースがあるかも知れません。使っているアルゴリズムも分からずソースコードも見られないプロダクトを使って精度保証が出来ると考えるほうがどうかしてるとさえ思えます。
さて、ここまでお読みいただければ、INTLABがOctaveに移植されたことの重要性がよく分かると思います。Octaveはopen sourceであり、(インストールの仕方にも拠るでしょうが)使われるBLASもatlasやopenblasなどのopen sourceなものです。中身を見れば精度保証に問題があるかどうか判断できるし、問題があるならば修正することも出来ます。
INTLABはversion 7から有償になってしまったので簡単に試せないのが残念ですが、Octaveでどのくらいちゃんと動くものか、近いうちに試してみたいと思います。
※上に書いたような問題点は、ずっと前から感じていたことですが、代替手段が無かったこともあり、あまり明確に否定してしまうと線形計算の精度保証の分野の研究者が困ると思い、あまり口にしないで来ました。INTLABがOctaveで動くようになったことで、書くなら今しかないと思い、この文章を書きました。
2014/12/25(木)kv-0.4.16
今回は特に大きなバグが見つかったわけではありません。
- Smithの定理を使ったDKAの精度保証版を作ってみた。
- gamma関数を少し整備した。
- defint-singular.hppの中にひっそりとある端点特異性を持つ関数の積分に、ステップ幅を自動調節するversionを追加した。
某所でLaguerre関数を含む凶悪な(?)数値積分が話題になり、その計算できちんと精度を出すためにいろいろと試行錯誤した結果が反映されています。
この積分を通じて、kvライブラリの欠点が見えてきました。
- この積分は全体を超高精度で行えばちゃんとまともな精度保証結果が出ることは分かっているが、それは現実的に無理。
- 高精度が必要なのは全体のある一部分だが、kvライブラリはworking precisionを全体で一斉に変えることを想定していて、一部分だけ計算精度を変える混合精度計算が書きづらい。
2014/12/16(火)C99のfma関数の挙動を調査
HaswellとFMA
の記事で、Haswellに新しく実装されたFMA命令について書きました。某Mさんがたまたまfmaという命令がmath.hにあるのを見つけてくれてその存在を知ったので、どういうものなのか調査してみました。かなりややこしいのですがせっかくなので記憶が薄れないうちに記録を残しておきます。
まず、fmaという命令はC99で追加されていて、使うときにはmath.hが必要らしいです。そこで、
#include <stdio.h> #include <math.h> int main() { double u, x, y, z; int i; u = 1; for (i=0; i<52; i++) u /= 2; x = 1+u; y = 1-u; z = -1; printf("%.15g\n", fma(x, y, z)); }というプログラムを作って、その挙動を観察してみました。
まずコンパイルの可否。次の表のようになりました。
オプション無し | -lm | -mfma | |
---|---|---|---|
gcc4.1 | x | x | x |
gcc4.4 | x | o | x |
gcc4.8 | x | o | o |
IvyBridge | Haswell | |
---|---|---|
gcc4.4 -lm | 0 | -4.93038065763132e-32 |
gcc4.8 -lm | -4.93038065763132e-32 | -4.93038065763132e-32 |
gcc4.8 -mfma | 実行できず | -4.93038065763132e-32 |
- FMA命令がちゃんと機能していれば、真の値-2-104が、
- FMAでなくただの乗算+加算だと、1-2-104が1に丸められ、それに(-1)を足すので0が、
これらの実験結果を見ると、次のような推定が出来ます。
- gcc4.1では、C99のfma関数のサポートそのものが無い。
- gcc4.4では、C99のfma関数が実装され、libm内にfma関数が存在する。-mfmaによるHaswellのFMA命令の有効化は実装されていない。libm内のfma関数は、CPUがFMA命令を持っていればそれを使い、持っていなければ単にfma(a,b,c)=a*b+cを実行する。
- gcc4.8では、-mfmaによるfma命令の有効化が実装された。これを付けてコンパイルすると、CPUがFMA命令を持つことが前提のコードが生成され、main関数中のfmaはlibm呼び出しではなく直接HaswellのFMA命令にコンパイルされる。
- gcc4.8で更に注目するべきは、libm呼び出しの場合IvyBridgeでも何故か正しい値が計算されたことである。これは普通ではなかなか出来ないはずで、CPUがFMA命令を持たなかった場合は(例えばmpfrを用いた)正確なFMA命令のエミュレーションを行うような実装が行われたと思われる。
IvyBridge | Haswell | |
---|---|---|
gcc4.4 -lm | 2.46 | 2.236 |
gcc4.8 -lm | 127.42 | 2.828 |
gcc4.8 -mfma | - | 2.214 |
2014/12/08(月)kv-0.4.15、シンプルな区間演算ライブラリ
今回は、ベキ級数演算の乗算の問題を修正しました。ベキ級数演算では次数を上げながら同じ式を何度も計算する場面が多く、そのときに前回の計算結果を流用すると高速化できるため、計算の履歴を保存しておいて再利用する機構を持っています。ベキ級数の乗算においてこの再利用部分にバグがあり、区間幅が狭くなってしまうことがありました。これは、Type-I PSAの直後にType-II PSAを行った場合のみ稀に発生します。精度保証への影響ですが、常微分方程式ではその計算は候補者集合の大きさを決める目安として使うだけなので恐らく影響なし、数値積分の方は精度保証結果に影響していた可能性があります。
もう一つ、区間演算で、"0.1" < xのように左側に文字列、右側に区間が来るような不等号が正しく計算されないバグも見つかり、修正しました。この計算は自分のライブラリ内では一箇所も使っていませんでしたが、interval.hppを既に何かに利用されている方がいたら、そのプログラムに影響します。
また、久しぶりにVisual Studio 2013でコンパイルしてみたら、新規に書いた部分でいくらかコンパイルが通らない箇所があったので修正しておきました。
「お前のプログラムは本当に精度保証できているのか」と不安になるようなアップデート内容ですが、こうして一つ一つ問題点を修正して完成度を高めていくしかないと信じています。
おまけ: kvライブラリの区間演算の部分を切り出して、簡単な区間演算ライブラリを作ってみました。
interval-simple.tar.gz
- テンプレートを排除しました。従って内部型はdoubleのみですが、プログラムが読みやすくなっています。
- テンプレートを排除したおかげでboostに依存しません。単体で使えます。
- 必要なファイルはinterval.hpp一つです。
- 内部型のカスタマイズが出来ないこと以外は、kvライブラリに含まれるinterval.hppと機能は同等です。数学関数も多数あります。
- 丸めの変更は単純にfesetroundを使っています。
- 文字列との方向丸め付き相互変換機能もあります。従って、文字列からその文字列の表す数値を含むように区間を初期化できるし、表示も真の値を含むように外側に丸めるようになっています。
- volatileを使った最適化対策はきちんとやってます。
2014/12/04(木)tanhとkv-0.4.14
なんだかユーザが不安になりそうなアップデートの頻度で心苦しいのですが、問題が見つかったのだから仕方がないと考えています。だんだん実戦投入される場面が増えてきたおかげと喜んでいます。
今回は、tanh(x)をsinh(x)/cosh(x)と単純に実装していたことによる問題です。この実装だと、|x|が大きい領域ではtanhは-1または1ですがsinhやcoshはとても大きくなりオーバーフローしてしまいます。精度保証的に嘘をつくわけではないですが、オーバーフローで∞/∞がNaNになってしまい、計算が続行不可能になっていました。
x>>0な領域では1-2/(1+exp(2x)), x<<0な領域では2/(1+exp(-2x))-1で計算するように直しました。こんな初歩的なミスをするとは情けない。しかし、ググってみると同じようなミスをしている実装が結構見つかりますね。
ついでに、非線形方程式の全解探索関係の卒論生が何人かいるので、書きかけで完成度は低いですが、全解探索の解説のところにアルゴリズムの解説pdfを追加しておきました。