backends/default 設計
設計目標
algebra の trait は、実際の密な型がそれを実装していなければ役に立ちません。backends/default は、具体的な @mutable 型と @immut 型をラップすることでその参照バックエンドを提供しつつ、それらの具体的なパッケージが実験的な algebra 層に一切依存しないようにしています。これはエコシステムの中心ではなく、1 つのバックエンドにすぎません。汎用アルゴリズムは trait に依存し、このパッケージはそれを満たす方法の 1 つです。
数学的背景
インスタンスは法則の根拠である
ある型に @algebra.MatMulMatrix を実装することは、algebra の設計 に挙げた法則、つまり定義される範囲での * の結合律と + に対する分配律を主張することです。ラッパーでは、これらの法則はラップされた型から受け継がれます。どの演算子も「アンラップし、内側の演算子を適用し、ラップする」として定義されているからです。
したがって wrap はすべての演算について準同型であり、内側の型で成り立つ等式はラッパーでも成り立ちます。
密行列積の部分性
実行時に形状が決まる密行列では、積は 上で定義されます。trait メソッドは Self を返すので、この集合の外ではラッパーは何らかの処理をしなければなりません。内側の型と同様に中断します。これは、MatMulMatrix の契約が実装に明示を求めている、ドキュメント化された実行時の前提条件です。
内積とその丸め誤差
dot は を漸化式 , で計算します。浮動小数点では、各ステップがそれまでのすべての項の誤差にさらに因子 を掛けるため、標準的な議論から次が得られます。
したがって相対誤差は、項の符号がそろっているときには小さく、 のとき(桁落ち)には大きくなりえます。matvec はこのような内積を 個計算するもので、行ごとに同じ限界を受け継ぎます。
設計上の判断
所有するラッパー型
問題。 MoonBit では impl Trait for Type を書けるのは、その trait か型を所有するパッケージだけです。@mutable.Matrix に @algebra.MatMulMatrix を実装するには、algebra(具体的な型を知ってはならない)か mutable(そうすると実験的な algebra 層に依存してしまう)のどちらかで行う必要があります。
決定。 このパッケージで新しい型 DenseMatrix[T]、DenseVector[T] とその不変版を定義し、それぞれを公開フィールド inner を 1 つ持つ構造体とし、ここで trait を実装します。
理由。 ラッパーは trait を実装するパッケージが所有するので規則を満たし、依存の向きは backends/default → algebra, immut, mutable のままです。代償は型における 1 段の間接参照ですが、inner() と from_backend によってコピーなしで取り除けます。
スカラー値の写像はバックエンドのメソッドにする
dot、scale、axpy、matvec は trait ではなくラッパーのメソッドです。これらはスカラー型を明示的に扱いますが、algebra の trait はスカラー型を名指しできません(algebra の設計を参照)。メソッドにしておくことで、各バックエンドが独自の制約を選べます。たとえば可変ベクトルの dot は AddMonoid + Mul、不変ベクトルでは Zero + Add + Mul です。
axpy は新しいベクトルを返す
BLAS のルーチン axpy は をインプレースで更新します。ここでは x.axpy(a, y) はどちらのラッパーでも を新しいベクトルとして返すため、可変と不変のバックエンドが値を返す同じ契約を共有します。インプレースの更新は内側の @mutable.Vector で引き続き利用できます。
スカラーは右から掛ける
scale は right_scale を使い、 を計算します。可換なスカラーではこれは と同じですが、非可換なスカラーではこの選択が結果に現れるため、隠さずにドキュメント化しています。
正しさと不変条件
- 準同型。 すべての演算子について
inner(a op b) == inner(a) op inner(b)が成り立つので、ラッパーはラップされた型の法則をそのまま満たします。 - ラップ時にコピーしない。
from_backend(x).inner()は物理的にxそのものです。可変な内側の値への書き込みはラッパーを通じても見えます。 - 転置。
transposeは実体化します。ビューを返すことはないので、TransposeMatrixの法則 は値として成り立ちます。 - 計算量。
+、-、scale: 。dot: 回の積和。matvec: 。*: 回の積和。transpose: 回のコピー。
却下した代替案
- 1 つの行列型の内部での実行時バックエンドセレクタ。 すべての演算がバックエンドで分岐することになり、どのカーネルが実行されるかが見えなくなります。バックエンドは型で選びます。
immutとmutableで直接 trait を実装する。 安定した具体的パッケージを実験的なalgebra層に縛り付けることになります。- インプレースの
axpy。 同じ名前に対して 2 つのラッパーで異なる意味論を持たせることになります。
境界
backends/default は新しい trait も新しい数値アルゴリズムも定義しません。mutable の分解、逆行列、統計量には inner() を通じてアクセスします。疎、遅延、静的サイズ、GPU のバックエンドは提供しません。そうしたバックエンドは、これらのラッパーに変換するのではなく、自身の型に algebra の trait を実装すべきです。かつてこのパッケージの隣にあったネイティブ OpenBLAS バックエンドは本リリースで取り下げられ、contrib/openblas_backend に保存されています。