haskell-jp / questions #47

デフォルトなのは外部のCライブラリに依存しないでビルドできるからだと思います。
僕もtls回りが遅くて以前見てみたことがあります。詳細は忘れましたが依存ライブラリ(Haskellコード)を少し弄ったら速度が結構改善しました。全体的にあまり速度を気にして書いているコードではない印象でした。
tlsを改善するコードをつくっていくしかなさそうですかね。
Windows 10 (ghc: 一定, ghci: 一定)
Mac/Linux (ghc: 不定, ghci: 一定)

こういうことで、その値は本来なら範囲外であるデータに由来していることですね。
mizunashi-mana さんのソースコードを実行してみた所、以下のような結果になりました (ghc, ghci どちらでも同じ)

0
MyI# 0#
IBI# 0#
2305843009213693952
MyI# 0#
CB
10
CB
CA
CC
うちのmacだと, GHCi を再度立ち上げながら実行した場合も同様に実行度に値が変わりました.再起動しないままだと常に同じ値ですね.
しかし, unsafeCoerce LD :: CompactDCA にならないのは驚きですね. tagging による分岐が Windows だと違ったりするんですかね?
あ,そういえば上のソースコードは,一度ビルドしたものを実行しています. runghc / ghci だと unsafeCoerce LD :: CompactDCC になりました.ビルドする場合は -O0 でも CA になりましたね. ghci だと pointer tagging は機能してなかったりするんですかね? runghc / ghci の結果は以下のようになりました(と言っても数字の部分は実行度に異なりますが):

4539535848
MyI# 4539535848#
IBI# 4539535848#
-3458764513802808375
MyI# 4539533688#
CB
10
CB
CA
CC
せっかくなので、元々のコードの方について、メモリ表現を覗けるようにしてみました。
以下のgistに貼り付けてあります。
https://gist.github.com/takenobu-hs/751aed055481d3594cf439a40790119b

Coerce2.hsを手元のUbuntuでコンパイルして実行すると、16進数で「0x4000_0000_0000_4645」を出力します。

Execution.txt の方には、ghcコマンドオプションでのstg, cmm, アセンブリのダンプ結果と、最終バイナリの逆アセンブル結果を付けています。

「0x4000_0000_0000_4645」の値は、objdump -D での逆アセンブルの箇所で、Main_boolzuvar1_closure から、+9 byte目からの8byteに一致しています。
(4a4309 番地から、4a4310番地までの値。)

ということで、まさに、True_closure + 9 からの8byteを拾っている挙動ですね。

いずれにしても、GHCi含めて、その時々の後続のメモリを素直に拾っているというとこですね。
そもそもunsafeの非保証動作の場合ですね:slightly_smiling_face:
+9 (アラインメントしていない場所)からの8バイト、というのがすごく気持ち悪いです… intel だから読めるけどそれ以外のアーキテクチャだったらアラインメントエラーになるのでは、という感が
TLSライブラリのメンテナの山本です。
性能は上げたいのですが、個人的にはTLS 1.3とQUICの実装で忙しいので、手が回っていません。
性能を上げるPRは大歓迎で、最優先でマージしますので、ぜひ送ってください。
ただ、もうすぐQUICのために大改造が入るので、桜の咲く頃まで待っていただけると助かります。
とりあえず、hs-tlsのissueに登録していただけると助かります。
@doanobu has joined the channel
@cutsea110 has joined the channel
おしえてー
くだされ
ゼロから作るDeep LearningをRepaでなぞってるんだけど
Performanceが悪いのでどうやるのが良いのか
現在4章の2層ネットでバッチ処理をしたいというところ.
誤差逆伝搬の前で数値微分によるミニバッチ学習をやろうとしている.
パフォーマンスについては書籍の公開しているpythonコードをダウンロードしてきて走らせて比較してみた結果,今の実装が遅すぎると判明.
Shift + Enterで
改行できるので
まとめると良いですよ
pyhonのはどうやら誤差逆伝搬のアルゴリズムの方をenableにしてたからひとまず数値微分の方を有効にしてみたら,確かに遅めだけど待ってられない程じゃない.
それに比べたら今の私の実装は終わるまで待ってられないレベル.
Pythonの実装だとネットワークを1枚用意しておいて代入操作で各ノードを書き換えつつ計算しているのだけど,RepaだとArray DのままだとmmultできずどうもそこでUnboxedな配列として実体化されてしまうのが問題なのかなーというところです.
100*784の2次元配列(Double)に対して順に100*784回もUnboxedな配列が実体化されるはずで,これ回避できないか?(小さい層もあるのでもう少し回数増える)
あるいはRepaでそういことやるときのノウハウとかありましたら是非お願いします.
たぶん,どういうコードを書いてるのかわからないので何も言いようがない雰囲気
今一歩一歩試しつつやってたのでmainの中がごちゃごちゃしているけど,TwoLayerNet.hsの99行目の処理がミニバッチサイズ100に対して1回numerical gradientを走らせただけの処理です.
ここを改善したいのです.
https://github.com/vincenthz/hs-tls/issues/357
こんな感じです。
よろしくお願いします。
おおー
@autotaker has joined the channel
とりあえずSystem.Randomは非常に遅いので`mwc-random`等速い乱数生成ライブラリを使ったほうが良いです。
あとUtil.hs内の型クラス制約をもつ関数にINLINABLEプラグマをつけると改善するかもしれません。
この分野には詳しくないんですけど、 fromFunction で作った行列はインデックスアクセスする度に関数を計算するんですね。だとすると、実体化した方が速くなるポイントとかありそう。メモ化的な。
ミュータブルな代入を使ったアルゴリズムがあるならHaskellでもそれ使った方がいい気がするんですが、repaでミュータブルアルゴリズムの併用ってどうやるんだろう
@tkrs has joined the channel
アドバイスありがとうございます。
INLINABLEプラグマも試してみます。がRepaでもINLINE段数まで指定してるっぽいんでなかなか厳しそう。

実体化した方が速いと思ってたんですが回数が多いのと、ほとんどは同じエリアの値にアクセスするので流石に作りすぎだと思うんですよね。
今はmmultSとかがArray Uを要求するから実体化してるという認識です。
常に100*784のうちの1箇所だけ値を変えて計算するんだけどそれっきりなんで、使い捨てる感じなんです。
そうすると実体化するより計算で使い捨ての方が速いと読んでるんだけどその方法がよく分からない。
ほとんどmmultSに時間かかってるみたいですけどこれ計算量どのくらいなんですか?
mmultSは行列の積を求めてるけどそれ自体はそんなに時間食ってないと思ってたんですが。
基本的には転置してからzipWithなんで転置はやはりfromFunctionでアクセスを変換するだけだからそれ自体はO(1)かな。zipWithするけどl*mとm*nであればO(l*n)にはなりそうかなぁ。
それと同程度のオーダーでNNも作り直し(実体化)てるはずなんですよね。
各重み(784 * 100 + 100 * 10 変数)の微小変化に対して(100,784) * (784, 100)の行列乗算(naiveには100*100*784回の演算が必要)を行なっているので計算量的に絶望的だと思います。Pythonの実装の方は何か工夫がされているのではないでしょうか?
行列の積自体はNumPyの中なので多分FFI通してC/C++なのかな。
でもnumerical gradientは代入操作でスキャンしてるけど都度lossを計算つまり初期値を投入して計算してるので全体の計算量自体は同じなんじゃないかなぁ。
これが遅いからbackpropergationするぞーという流れのようです。
Python実装って
ですか?これ network.numerical_gradientの呼び出しがコメントアウトされてて代わりに   network.gradient を呼び出しているんですが、コメントアウトを逆にして実行すると全く進まないので`numerical_gradient`が遅いのは仕様なのではないでしょうか?
pyhonのはどうやら誤差逆伝搬のアルゴリズムの方をenableにしてたからひとまず数値微分の方を有効にしてみたら,確かに遅めだけど待ってられない程じゃない.
これはそのことでしたか。失礼しました。
全く進まないですかね.
私の手元のnoteだと1min20sec~1min30sec程度で1回のnumerical_gradientは返るんですよ.
私のコードだとその1回すら40分待っても返ってこないのでさすがにこれは無いだろうと思っているんだけど.
私の手元でも確かに数分に一回くらいは返ってきましたね。numpyの行列乗算が速すぎるのでpureなHaskellで匹敵するのは難しいと思います。
手元で高速化して見たコードをプルリクで送りましたのでよろしければ参考にしてください。多分10分以内には一回のnumerical_gradientが終わると思います。
https://github.com/cutsea110/deep-learning-from-scratch/pull/1
おお、ありがとうございます
おお、これで解決できそうです! あとで Windows でも試します
丁寧にコメントを付けてくれているので順に辿ってみたいのですが,プロファイルを取るのはSCC付与して-profでみるって感じですか?
@Guvalif has joined the channel
コンパイルオプションについては、Repaのドキュメントに推奨設定の記述がありますね。ちょっと昔のGHCっぽい感じもしますが。今だと指定しなくてもあまり変わらないかもしれません http://hackage.haskell.org/package/repa-3.4.1.4/docs/Data-Array-Repa.html
コードを見てないので高速化についてはコメントできませんが、repaがボトルネックであればmassivを使ってみると良いかもしれません。ここに比較があります
https://github.com/lehins/massiv/blob/master/README.md#other-libraries