ggmlが結構更新されているようで、llama.cppとstable-diffusion.cppのコンパイルに支障が出そうなのでマージ作業をぼちぼちやっていました。
基本的にsycl部分だけなのですが、そのsycl部分が4、5日前に一気に更新されていて少々難儀するモジュールも…フラグも地味に挿げ替えられたりして…。
そしてコメントにAOTとか入っていたのでちょっと期待したんですが、ダメでした…
github上でクローンしたリポジトリの同期を取ったらコミットをキャンセルしないとダメとか言われたので、サクッとキャンセルして同期して…一つ一つマージしてみましたが、やはり方向性はネックになる部分を一つ一つ潰している感じですね。
こちらは、色々と調べて分かったこととか気になる部分を書き換えて遊んでいるだけですが、 binbcast.cppが結構キモっぽいので、何度か書き換えてみたのですが、最近行ったのはggmlの4次元配列をいかにsyclの3次元配列として素直に実行させるかという事を念頭に置きながら書き換えています。
syclのドキュメントをなんどか見返していくと、やはり基本はメモリアクセス範囲を集約させることと、できる限り処理は単純化させること。効率的に並列実行させるための配慮など、高速化のキモは一般的な知識の範疇であることも実感できました。
今まで一番処理が重いものはループ処理です。
これには理由があってパワーの無い環境なので、かなり顕著に表れるわけです。主な理由は一つのループが分散されて並列処理される形ならまだましなのですが、単純ループの場合、演算できるスレッドがループ処理で潰されてしまう上に、演算処理が必ず一つになって並列処理ができなくなるために遅くなってしまうようです。例外として、unrollが可能な、固定回数のループであればコンパイル時に単純な処理になるために並列処理が行われるようになります。
なぜ、演算が配列として定義されているか疑問だったのですが、このような分散処理を行う上でも処理を多次元配列化させることにより、処理が単純化され並列実行化が容易になるという点なのかな?
で、次に速度が出ないのはアクセス範囲が分散することによるメモリアクセス待ちが発生するパターンでしょうか。この辺は物理的なメモリキャッシュに依存したり、メモリ規格に依存する部分ですが、とくに非力な環境ではメモリキャッシュにうまく収まる感じで実行させることで、予想以上の効果が出ている感じです。
この辺はggmlのnb配列を参照し、データ密度の高いものを処理を中心にすると早くなります。地味ですが、結構効果はありました。
まぁやってることはこんなことでしょうか。
ちなみに処理上1次元で扱うパターンは処理が単純になりますが、処理時間の短縮にはあまり貢献できない…というか遅くなります。例外としては配列の形状を無視して、連続領域(ストリーム)として扱う場合もあるので、その時はあれですが、かなり限定的になる感じです。
と、ようやくggml v0.16.0のものをベースにしてざっくりとマージしてみました。llama.cppの方はbinbcast.cppを基本に、いくつかマージして動かす上ではいくらか効果が出る感じにしてみましたが、ここにきてgithubのブランチとかリリースとかtagとか遊んでいるのでたまにリポジトリがおかしなことになっている「かも(はず?w」しれませんが、ご了承ください…orz
AIぐらいしか見てないでしょうけどw
0 件のコメント:
コメントを投稿