HHLAB は時代を超えたオンラインデータ処理およびデータ計算フレームワークであることは周知の事実#
HHLAB 誕生の背景#
私たちは皆、環境のインストール、環境の設定、サーバーの構築、ドライバーのインストールが非常に苦痛であることを知っています。そして、私たちは確かに、特にデータ処理の原則がほとんどの場合、普通の人とそれほど変わらないコンピュータスキルを持っていることを考慮して、すぐに使えるデータ処理体験を提供する SaaS を望んでいます。彼らの主な利点は、データ処理アルゴリズムと数学の理解にあります。
そのため、多くのデータ処理フレームワークやプログラミング言語が開発されてきました。
現在市場で主流となっているものは:
- python + tensorflow/torch + pandas + numpy
- julia
- MATLAB/OCATAVE
- Wolfram Mathematica
- FORTRAN
- R + すべてのもの
HHLAB の誕生の目的は、ウェブ上にデータ処理プラットフォームを構築することです。私たちはウェブページを開くだけで、全てのデータ処理(開発から結果の出力まで)を行うことができます。このアイデアは非常に素晴らしいものです。
一定の設定で HHLAB の機能を実現できるソフトウェア#
まず、HHLAB の競合相手の多くは、少しの設定で HHLAB の現在の効果を達成できることを認めざるを得ません:
- まず、開発者は GPU を含むクラウドサーバーをレンタルできます。
- 次に、クラウドサーバー上に Jupyter Server を構築します。
- その後、ソフトウェアをインストールします。
- 最後に、ブラウザを使用してログインします。
上記のすべてのソフトウェアはこの効果を達成できますが、私たち HHLAB は上記の煩雑な設定プロセスを必要とせず、ただ使うだけで済みます。
HHLAB の真の競合相手#
私たちが支払うだけで、さらには支払う必要すらなく享受できるサービス:
- Google Codelab (python)
- MMA cloud
したがって、最初のものは Python エコシステムに属し、2 番目のものは MMA 公式のものです。それでは、これらの製品と HHLAB が主張する機能との違いを比較してみましょう。
ユーザーの使いやすさについて#
プログラミング言語#
Python と JS はどちらも簡単に学べる言語ですが、Python の柔軟性は JS よりも強いです。Python はメタプログラミングやデコレーター、さまざまなオブジェクト操作を通じて DSL レベルの開発を行うことができ、使用体験はネイティブプログラミング言語に匹敵します。
import numpy as np
A = np.array([1.,2.,3.,4.,5.])
B = np.ones(5)
print(A + B)
// import math.js
const A = [1,2,3,4,5]
const B = math.ones(1,5)
console.log(math.add(A,B))
この例から、JS の言語レベルでの柔軟性は Python と競争できないことがわかります。
しかし、私たちの HHLAB は Python のような動作を実現できます。
立党先生は、数値計算専用の演算子オーバーロードを追加するために Babel プラグインの形式で書きました(私はこれを組み込み演算子と呼ぶことを好みます)。
// https://github.com/Hedgehog-Computing/hedgehog-lab/blob/dev/packages/hedgehog-core/src/transpiler/operator-overload.ts
import template from 'babel-template';
import * as types from '@babel/types';
function invokedTemplate(op: any) {
return template(`
(function (LEFT_ARG, RIGHT_ARG) {
if (LEFT_ARG !== null && LEFT_ARG !== undefined
&& LEFT_ARG[Symbol.for("${op}")])
return LEFT_ARG[Symbol.for("${op}")](RIGHT_ARG);
else if (RIGHT_ARG instanceof Sym)
return (sym(LEFT_ARG)[Symbol.for("${op}")](RIGHT_ARG));
else if (Array.isArray(LEFT_ARG) && (RIGHT_ARG instanceof Mat))
return (mat(LEFT_ARG)[Symbol.for("${op}")](RIGHT_ARG));
else if (Array.isArray(LEFT_ARG) && (Array.isArray(RIGHT_ARG)))
return (mat(LEFT_ARG)[Symbol.for("${op}")](mat(RIGHT_ARG)));
else if ( (!isNaN(LEFT_ARG)) && (RIGHT_ARG instanceof Mat))
return (scalar(LEFT_ARG)[Symbol.for("${op}")](RIGHT_ARG));
else if ( (!isNaN(LEFT_ARG)) && (Array.isArray(RIGHT_ARG)))
return (scalar(LEFT_ARG)[Symbol.for("${op}")](mat(RIGHT_ARG)));
else if ( Array.isArray(LEFT_ARG) && (!isNaN(RIGHT_ARG)) )
return (mat(LEFT_ARG)[Symbol.for("${op}")]((RIGHT_ARG)));
else
return LEFT_ARG ${op} RIGHT_ARG;
})
`);
}
Python の演算子オーバーロードの実装は OOP のものであるため、ここでは詳述しませんが、これを行うことで何が問題になる可能性があるかを述べます:
- 演算子の左右の変数が
null
またはundefined
の場合、特別な処理が必要です。 - 演算子にエラーが発生した場合、コンパイルエラーが不親切です。
- 立党先生は左右のすべての型を手動で列挙する必要があります。なぜなら、彼はディスパッチメカニズムを利用していないからです。
人は誰でも間違いを犯すものですので、彼が JS の演算子に追加したこのパッチの信頼性は疑問です。
MMA について戻ると、MMA には組み込みのシンボル計算エンジンがあり、したがって中置表現を簡単に処理し、構文糖を記述できます:
g /: f[g[x_]] := fg[x]
(*あなたが入力すると*)
{f[g[2]], f[h[2]]}
(*あなたは得るでしょう*)
{fg[2], f[h[2]]}
これは明らかに Python や JS よりも優れています。シンボル計算エンジンは多くの非数値的なものを処理できますが、もちろん私たち HHLAB が対象とすべきではないユーザー層です。
次に、プログラミング言語の性能部分について言及します:
この点において、Julia は疑いなく王座を獲得しています。なぜなら、彼は優れた JIT 設計と非常に便利な CUDA の相互運用性を持っているからです。
しかし、Julia は HHLAB の競合相手ではないため、彼については議論しません。
私たちは、Scipy のような Python で何年も最適化されたツールの性能が商業用の MATLAB や MMA と競争できないことを見てきました。
したがって、CUDA を使用して異種加速を行わない HHLAB がランキングに載る必要があるとは思いません。
デバッグ#
Python と Jupyter を組み合わせることで、行レベルの実行が可能になり、解決できないエラーに遭遇した場合はブレークポイントを使用して解決できます。
MMA はデバッグのためにいくつかの方法を提供していますが、MMA はプロセス言語ではないため、直接比較するのは難しいです:
語彙 | 意味 |
---|---|
With[{x=value},expr] | x を value に置き換えて expr を計算する |
Echo[expr] | expr の値を表示して返す |
Monitor[expr,obj] | 計算中に obj を継続的に表示する |
Sow[expr] | expr の値を後で収穫するために撒く |
Reap[expr] | expr が計算されている間に撒かれた値を収集する |
今のところ、私は HHLAB がデバッグツールを提供しているのを見たことがなく、行ごとの実行すらできません。
ユーザーの機能について#
CPU ベクトル化#
numpy は大量の SIMD コードを使用して、ユーザーのデータを CPU で十分にベクトル化します。
MMA については言及しませんが、第一にクローズドソースであり、第二に高級言語であり、CPU の機能に基づいてコンパイルされており、大量のベクトル化が行われています。
立党の態度は:
GPU 計算#
HHLAB は GPU を主要な開発バックエンドの 1 つとして使用しており、これは彼が最も誇りに思っている点です。
まず、GPU が加速できるものは非常に密度が高く、大量の数値計算を含み、少量のジャンプを含むものであり、CPU は論理的な計算に優れています。
彼は GPU.js を通じてこの機能を実現しています。このライブラリのコードは非常に多いため、ここでは列挙できませんが、以下に説明を示します。
https://github.com/gpujs/gpu.js/wiki/Quick-Concepts
1. GPU で使用するための JavaScript のトランスパイリング
1. JavaScript を共通フォーマットに読み取る。この場合、Mozilla の抽象構文木です。
2. 解析された JavaScript からの任意の値または値の導出からの型推論
3. Mozilla の抽象構文木から GPU が理解できる言語の文字列値に変換します。一般的には GLSL(C++ のサブセット)ですが、今後もっと増える可能性があります。
4. 言語に翻訳された文字列に必要なユーティリティ関数と環境修正を追加します。
5. 現在おそらく C++ のサブセットにある全体の翻訳された文字列をコンパイルします。
2. カーネルの結果を計算するために必要な値(引数または定数)をアップロードします。
3. カーネル出力から値を計算します。
4. カーネル出力から値をダウンロードします(このステップは、カーネル設定 pipeline: true を使用することでスキップできます)。
- これは一般的に GPU から値を計算する際に最も時間がかかる部分と見なされます。
- ここにいる場合は、自問してください:
1. "本当に必要な値は必要ですか?"
2. "必要な値を GPU にオフセットできますか?または、GPU から必要だと思う値を返す頻度を減らせますか?"
したがって、GPU.js は WebGL のコンピュートシェーダーを使用してこのプロセスを実行しています:
- WebGL2 が利用可能な場合は、それが使用されます。
- WebGL2 が利用できない場合は WebGL1 が使用されます。
- WebGL1 が利用できない場合は CPU が使用されます。
私はグラフィックス API について研究したことがありますが、WebGL のコンピュートシェーダーは OpenGLES1.0-3.0 に基づいて実装されているため、主流の OpenCL や CUDA に比べて多くの機能が欠けています(結局、これは描画用です)。
さらに、ブラウザによる低レベル API のラッピングや、オブジェクトが JS に渡された後の変換などの操作は、より大きなパフォーマンスの損失を引き起こします。
以下は、WebGL の計算性能を理解するのに役立ついくつかのベンチマークデータです。
このベンチマークは TVM チームによって行われ、そのデータは私の経験と一致します。
したがって、WebGL の性能は実際には OpenGL の性能を大きく下回っています。
現在、WebGPU という実験的な API もあり、これはより低レベルのインターフェースで、Vulkan に非常に似ており、より良いマルチコア性能を提供します。以下は WebGPU のベンチマークです:
したがって、WebGL は実際にはゼロからのスタートに過ぎません。
さらに、この技術スタックには重大な欠点があります。つまり、JS コードが非常に複雑な場合、GLSL に変換できない可能性があるため、複雑なカーネル関数を GPU.js に渡して計算させることができません。
GLSL のコードを書くのは CUDA よりもはるかに苦痛です。
したがって、私は立党がすでに WebGPU サポートを搭載した TVM フレームワークを選ばなかった理由に非常に興味があります。
他の問題には、Nvidia が多大な労力をかけて投入した tensorcore がここで完全に無駄になっていることが含まれます。
立党の対象ユーザーが精度を気にしない可能性があるなら、これは彼がフレームワークを最適化するのを助ける絶好の方向性です。
しかし、WebGL 技術を使用することでは、この面での向上は得られません。
それでは、他の 2 つのプラットフォームが問題をどのように処理しているかを見てみましょう。
Python: 知恵を集め、多くのバックエンド#
Python は Tensorflow や Torch を使用して AI を行い、numpy を使用して数値計算を行います。
しかし、numpy は依然として CPU のものであるため、現在cupyという新しいプロジェクトがあり、これにより GPU 計算もサポートされるようになりました。
MMA: 公式サポート#
MMA は非常に高級な言語を使用しており、CUDA や OpenCL 上で直接実行されるため、... これは言うまでもありません。
TargetDevice->"GPU"
さて、あなたは GPU を使用しています。
しかし、GPU は本当にあなたが想像するほど役に立つのでしょうか?#
高精度の要求に直面したとき、GPU はあなたの CPU よりも遅く計算する可能性すらあります。
さらに、実世界では多くの数値計算は、帯域幅の要求が少ないか、論理が複雑な状態機械のようなものであるか、計算の精度が非常に複雑であるか、さらには新しい数値形式の定義に直面する可能性があるため、このような場合、GPU は役に立たないか、混乱を招くことがあります。
行列操作#
まず、HHLAB の行列アルゴリズムのコードを観察しましょう。
これらのソースコードのアドレスはこちら#
外積
function multiply(leftMat: Mat, rightMat: Mat): Mat {
if (leftMat.cols !== rightMat.rows)
throw new Error('次元が操作:muitiplyに対して一致しません');
if (leftMat.mode === 'gpu' || rightMat.mode === 'gpu') return multiply_gpu(leftMat, rightMat);
const m = leftMat.rows,
n = leftMat.cols,
p = rightMat.cols;
const returnMatrix = new Mat().zeros(m, p);
for (let i = 0; i < m; i++) {
for (let j = 0; j < p; j++) {
let val = 0;
for (let it = 0; it < n; it++) val += leftMat.val[i][it] * rightMat.val[it][j];
returnMatrix.val[i][j] = val;
}
}
return returnMatrix;
}
内積
function dotMultiplyInPlace(leftMat: Mat, rightMat: Mat): Mat {
if (leftMat.rows !== rightMat.rows || leftMat.cols !== rightMat.cols)
throw new Error('次元が操作:dot muitiplyに対して一致しません');
for (let i = 0; i < leftMat.rows; i++) {
for (let j = 0; j < leftMat.cols; j++) {
leftMat.val[i][j] *= rightMat.val[i][j];
}
}
return leftMat;
}
逆行列
function (rightOperand: number): Mat {
if (this.rows !== this.cols) throw new Error('この行列は^演算子をサポートしていません');
//右オペランドが-1の場合、逆行列を返します
if (rightOperand === -1) {
// mathjsを使用した行列の逆
return new Mat(mathjs.inv(this.val));
}
if (!Number.isInteger(rightOperand) || rightOperand < 1)
throw new Error('この右オペランドは^演算子をサポートしていません');
const returnMatrix = this.clone();
for (let i = 2; i <= rightOperand; i++) {
multiplyInPlace(returnMatrix, this);
}
return returnMatrix;
}
emmm 逆に固有値がない... まあ、これでいいでしょう。これらのコードは非常に leetcode 風味です。
少なくともあなたはそれを / 2/4 のようにできますが、ああ、すみません、JS にはマルチコアがありません、私の問題です。
私たちは実際の行列演算には 2 つの異なる状況があることを知っています。
密な演算の加速は、コアが多く、帯域幅が高いほど計算が速くなります。
しかし、問題は疎行列(立党が予測していなかったシナリオであり、数値計算が正則化やノイズフィルタリングを行う際に最もよく遭遇するシナリオ)にやってきます。
レイアウトだけでも以下のような形式が存在する可能性があります:
- COO: Coordinate(これは位置を記録するもので、もちろんあまり使われていません、古すぎます)
- CSR: Compressed Sparse Row(これは行が比較的分散しているときに行を圧縮します)
- CSC: Compressed Sparse Column(同様ですが、列です)
- BCSR: Blocked Compressed Sparse Row(ブロックを圧縮します。線形代数を学んだことがある人はすぐに反応できるはずです)
現在の主流の科学計算ライブラリは、計算を行う前にプロファイリングを行う Auto-tuning メカニズムを提供しています。
したがって、これは立党の知識の盲点かもしれません。
ライブラリ開発者の使いやすさについて#
もしツールがどれほど進んでいても、開発者に説明できなければ、開発者は成果を出すことができず、エコシステムを構築することもできません。
パッケージ管理メカニズム#
立党はリンクをコピーし、開発者がブランチを作成することでパッケージ管理を完了しようとしていますが、これには問題があります。
仮に、パッケージ A、パッケージ B、パッケージ C があるとします。
B は A に依存し、C は B に依存しています。
B は A が壊れたことに気づき、B は A のコピーを作成し、パッチを書いて自分が使用しているすべての A の場所に追加する必要があります。
C は B が A が壊れたことに気づきませんが、C は B がすでに修正したことを知らないため、C は B のコピーを作成し、さらに A のコピーを作成します...
したがって、私たちはパッケージ管理の重要性を理解しました。立党もこの点を認識できるでしょう。
Python のパッケージ管理には 2 つのセットがあります:
- pip
- anaconda
これらの 2 つはどちらも非常に便利で、pip はより多くのパッケージを提供し、conda は科学計算関連の依存関係を処理するのに優れ、環境の隔離も構成できます。これについては詳しく述べませんが、皆さんはご存知でしょう。
プラットフォームサービス#
立党はここでどうするつもりなのか、わかりません...
HHLAB はどこへ行くべきか?#
bboczeng/why-you-do-not-need-hedgehog-lab
HHLAB は、装飾的なホームページや大学生の宿題などのニーズを完璧に解決できる可能性があります。
したがって、HHLAB は非常に優れたフレームワークであり、私たちは将来、彼が私たちの科学計算の方法を変え、いつでもどこでも無料で科学計算コードを実行できることを期待しています。