見出し画像

ゼロからのMAMBA:トランスフォーマーよりも高速で優れたニューラルネット

https://www.youtube.com/watch?v=N6Piou4oYx8

マンバはトランスフォーマーよりも優れた言語モデリングを行う新しいニューラルネットアーキテクチャです。そうです、7年間supremeの座にあったトランスフォーマーがついに王座から引きずり下ろされたのです。まあ、そうかもしれません。今のところマンバは数十億パラメータまでの小規模モデルでしか試されていませんが、これまでの結果は有望です!さらに、マンバはトランスフォーマーよりも少ない計算量で済みます。n個の単語からなる入力シーケンスに対して、マンバはO(nlog(n))の計算量しか使いませんが、トランスフォーマーはO(n^2)を使います。つまり、マンバベースの言語モデルでは、はるかに大きなコンテキストサイズを使用できるはずです。
このビデオでは、マンバアーキテクチャを深掘りし、それが何なのか、なぜそれほどうまく機能するのか、そしてどのようにしてそのようなアーキテクチャを自分で設計できたのかを見ていきます。通常、マンバは状態空間モデルと呼ばれるものの拡張として紹介されます。状態空間モデルはここ数年着実に人気を集めている別種のシーケンスモデルですが、正直なところ、状態空間モデルの背後にある理論は非常に複雑で、かなり高度な数学を使用しています。幸いなことに、マンバは再帰型ニューラルネットワーク、略してRNNの拡張としても理解することができ、こちらの方がはるかに理解しやすいです。そこで、このビデオではRNNの道筋を通じてマンバを理解していくことにします。
では、始めましょう。再帰型ニューラルネットワークとは何でしょうか?入力ベクトルのシーケンスが与えられると、畳み込み層は連続したベクトルのグループにニューラルネットを適用します。重要なのは、ニューラルネットが一度に少数のベクトルしか見ないため、モデルの学習が容易になることです。欠点は、離れたベクトルからの情報が、多くの畳み込み層が適用されるまで組み合わせられないことです。これにより、畳み込みニューラルネットが入力の長距離依存関係を理解することが難しくなります。そして、そのような長距離依存関係は自然言語テキストでは常に発生します。
この欠点を解決するために、トランスフォーマーアーキテクチャが発明されました。トランスフォーマーは、単一のレイヤーで、どれだけ離れていてもベクトルからの情報を組み合わせることができます。以前、トランスフォーマーがどのように、そしてなぜ機能するのかを詳しく説明したビデオを作りました。そして、トランスフォーマーは素晴らしく機能しますが、重大な制限があります。それは、使用する計算量が入力の長さの二乗に比例することです。小さな入力に対しては大きな問題ではありませんが、入力に100万個のベクトルを持たせたい場合、100万掛ける100万の演算を行う必要があり、これは膨大な量です。
再帰型ニューラルネットは、畳み込み層を改善するために全く異なるアプローチを取ります。アイデアはとてもシンプルです。2つの連続する入力ベクトルにニューラルネットを適用する代わりに、1つの入力ベクトルと、ニューラルネットの前回の出力に適用します。これは小さな変更のように見えますが、profound(深遠な)結果をもたらします。各出力ベクトルは、以前の1つのベクトルからだけでなく、それ以前のすべての入力ベクトルからの情報を含むようになります。この最後の出力ベクトルには、入力の数に関係なく、入力のすべてのベクトルからの情報が含まれています。そして、畳み込み層よりも多くの計算を使用していません。長距離の情報を無料で取り込むことができたのです。
これはまさに私たちが望んでいたものです。少なくとも、RNNを実際に使用することをほぼ不可能にする2つの小さな問題がなければ、そうでしょう。最初の問題は、再帰層が畳み込み層と同じ量の計算を使用するとはいえ、その計算を複数のプロセッサに並列化できないことです。たとえ多くのプロセッサが利用可能であっても、前のステップからの出力をニューラルネットに供給する必要があるため、前のすべてのステップが完了するまで入力に対するニューラルネットの評価を開始できません。これを、ニューラルネットが元の入力だけを見る必要がある畳み込み層と比較してみてください。十分なプロセッサがあれば、すべての入力に対して同時にニューラルネットを実行できます。そして、現代のハードウェア、例えばGPUは、何千ものプロセッサを持つ並列計算に高度に特化しているため、RNNは実際にはCNNよりもずっと遅いのです。実際、RNNは計算量が少ないにもかかわらず、トランスフォーマーよりも遅いのです。
そして2つ目の問題は、RNNの学習が非常に困難だということです。理論的には、単一の再帰層が任意に多くの入力からの情報を取り込むことができますが、実際にはそうではありません。代わりに、せいぜい数十個前の入力からの情報を取り込むことしか学習しません。RNNのアイデアは1980年代からありましたが、これら2つの問題のため、RNNは敬遠され、畳み込みニューラルネットとトランスフォーマーが実際にはるかに成功を収めています。実際、過去10年間、RNNはほとんど使用されていませんでした。
しかし、今がその時です。昨年、線形RNNがこれら2つの問題を回避でき、したがって線形RNNが非常に効果的な長いシーケンスモデルであることを示す新しい論文が発表されました。では、線形再帰型ニューラルネットワークとは何でしょうか?単純に、ニューラルネットを線形関数に置き換えるのです。これは悪いアイデアのように思えるかもしれません。線形関数は入力に対して比較的単純な変換しか行えないからです。しかし、その後で各出力ベクトルに完全なニューラルネットを適用することで補うことができます。これは、トランスフォーマーで価値ニューラルネットを単純な線形関数に置き換え、そしてセルフアテンション層の間にニューラルネットを追加して、非線形処理能力の欠如を補うのと似ています。つまり、トランスフォーマーと同様に、線形再帰層と要素ごとのニューラルネットワークを交互に配置します。
しかし重要なのは、再帰演算を純粋に線形にすることで、RNNの両方の問題を解決することが可能になるということです。まず、n個のベクトルに適用される線形再帰がどのようにしてO(log(n))時間で並列に計算できるかを説明します。そして、通常のRNNを悩ませる学習の問題が線形再帰でどのように修正できるかを説明します。
線形再帰演算子は次の式で与えられます:i番目の出力ベクトルを得るには、前の(i-1)番目の出力ベクトルを行列W_yと掛け合わせ、i番目の入力ベクトルを別の行列W_xと掛け合わせたものを加えます。W行列のエントリは、モデルによって学習されるパラメータであり、0を中心とする正規分布からのランダムサンプルで開始し、その後勾配降下法で更新されます。そして、W_x行列は各入力に独立して適用されるので、実際にはそれを前の層の一部と考えることができます。したがって、再帰演算子を単純化して、前の層で入力に線形関数が既に適用されていると仮定して、入力xを単に加えるだけにすることができます。
線形再帰は実際には、スキャンと呼ばれるより一般的な操作の特殊なケースです。そこで、スキャンの最も簡単な例である累積和から始めましょう。n個の数字のリストが入力として与えられた場合、目標は各項までの部分和のリストを計算することです。つまり、出力リストのi番目の項目は、入力リストの最初のi項目の合計であるべきです。順番に数字を足していくだけなら簡単ですが、私たちはこれを並列に行いたいのです。そして、次のようにして並列に行うことができることが分かります:まず、連続する各ペアの数字を足し合わせます。次に、結果のリストから、2ステップ離れた数字のペアを足し合わせます。そして4ステップ離れた数字、8ステップ離れた数字...と続けます。各反復でステップサイズを2倍にし、ステップサイズが入力リスト全体と同じくらい大きくなるまで続けます。これはlog(n)ステップ後に起こります。
このアルゴリズムが機能する理由は、各反復で、i番目の出力要素が前のステップサイズの数の合計を含むからです。例えば、最初の反復では、各出力数は前の2項の合計です。次の反復では、各項目は前の2項の合計に加えて、2つ離れた前の2項の合計、つまり前の4項の合計を含みます。そしてこれが続きます。ステップサイズが入力のサイズになると、各出力にはすべての前の項の合計が含まれ、望む結果が得られます。各反復が並列に計算できることは自明ですが、異なる反復は依然として順次計算する必要があり、log(n)回の反復があります。したがって、n個のプロセッサがある場合、このアルゴリズムの全体の実行時間は、素朴な逐次バージョンのO(n)からO(log(n))に減少します。
そして、この同じアルゴリズムは、加算だけでなく、任意の二項演算の累積適用リストの計算にも機能します。ただし、その二項演算が結合的である必要があります。結合的とは、適用の順序を変えても同じ結果になることを意味します。これは加算に当てはまり、だからこそ並列累積和アルゴリズムが機能するのです。そして、他にもたくさんの演算に当てはまります。特に、この二項演算は結合的です:f((W1, x1), (W2, x2)) = (W1W2, W1x1+x2)。この演算子は、加算のような単一の数字ではなく、行列とベクトルのペアを入力と出力として使用することに注意してください。そして驚くべきことに、この演算子でスキャンを実行することは線形再帰と同等です。
まず、入力ベクトルのリストを、最初の要素が再帰重み行列で2番目の要素が入力ベクトルであるペアのリストに置き換える必要がありますが、その後は通常通りスキャンを実行します。この演算子が実際に結合的であることは、別の順序で数項を展開してみると確認できます。要約すると、加算の代わりにこの演算子を使用して並列累積和アルゴリズムを実行するだけで、O(log(n))時間で線形再帰層の結果が得られます。
ただし、1つ小さな問題があります。この演算をよく見ると、その仕組みは、タプルの最初の要素を累積行列として使用し、これにはこれまでに見たすべての行列の積が含まれています。そのため、出力タプルの最初の要素は2つの入力行列の積になっています。しかし、これは毎ステップで[d, d]掛ける[d, d]の行列乗算を行うことを意味し、dはベクトルの次元です。これは非常に遅いです。元の逐次RNNでは、この累積行列を追跡する必要がなく、各ステップで重み行列と長さ[d]の入力ベクトルを掛け合わせるだけで済み、これはO(d^2)の演算です。しかし今や、毎ステップでO(d^3)の演算を行う必要があります。標準的なモデルサイズでは、これは簡単に1000倍の計算量増加になります。そしてそれは悪いことです。
幸いなことに、この問題を回避する方法があります:行列の対角化です。行列の要素が複素数であることを許容すれば、(ほぼ)すべての正方行列は可逆行列P、対角行列D、そしてP^-1の積に分解できます。例を示します。この中央の行列が対角行列で、主対角線以外のすべての要素が0であることに注目してください。この形式で行列を自身と掛け合わせると、内側のP逆行列とP項が相殺され、2つの対角行列の積は単に要素の積を持つ対角行列になります。つまり、D^2を計算するには、Dの主対角線上の要素を二乗するだけでよく、これはO(m^3)ではなくO(m)の演算で行えます。はるかに良いですね。
そこで、再帰重み行列を対角化された形式で表現し、Dの主対角線の要素を含む複素ベクトルだけを使用すればよいのです。つまり、まず複素行列Pを入力ベクトルに適用し、次に複素重みベクトルwを使用して要素ごとの乗算で線形再帰を実行し、最後にP^-1を出力に適用します。この結果は、ある実数値の重み行列Wに対する線形再帰と等価になります。しかし、この方法で計算すると、再帰演算子は累積重みを更新するために行列乗算の代わりに2つのベクトル間の要素ごとの乗算だけを計算すればよくなります。
これを並列スキャンアルゴリズムに組み込むと、全体の計算量は今やO(dnlog(n))となり、並列実行時間はO(log(n))です。はるかに良くなりました。この層のパラメータは、再帰重みベクトルwと行列Pの複素エントリであることに注意してください。実際には、各パラメータの実部と虚部を表すために2つの別々の実数を使用し、これらは0を中心とする正規分布からサンプリングして初期化され、通常通り勾配降下法で更新されます。
最後に、行列の逆数を計算するのは非常に遅いので、実際には面倒くさがって、線形再帰の前後に2つの独立した複素行列を使用するだけにします。これは実際にモデルをより表現力豊かにし、計算も節約します。しかし、これはモデルがもはや実数値の再帰と等価ではなくなり、出力が複素数になる可能性があることを意味するので、次の層に渡す前に出力の実部を取る必要があります。
さて、線形RNNを現代のハードウェアに適した高速なものにする方法を見てきましたが、RNNの学習が非常に困難であるというもう1つの問題についてはどうでしょうか?この問題を解決する前に、まずRNNの学習がそもそもなぜそんなに問題なのかを簡単に復習しましょう:ニューラルネットは、損失関数の勾配をモデル内の各重みから引くことで学習されます。勾配とは何でしょうか?ニューラルネットを評価し、ある重みの値をごくわずかに増やし、再び評価することを想像してください。これらのスコアの差が(比例して)その重みの勾配であり、ニューラルネットを改善するためにその重みをどう変更すべきかを教えてくれます。
では、線形再帰層の勾配を評価してみましょう。実際、これを少し簡単にするために、最初の入力以外はすべて0だと仮定し、それらを無視することにします。再帰層を評価すると、各ステップで前の出力が重みベクトルと掛け合わされるので、nステップ後の出力ベクトルは、再帰重みベクトルのn乗に最初のベクトルx_1を掛けたものに等しくなります。重みを少し増やして再評価すると、こうなります。差を取ると、定数スケーリング係数を除いて、w^(n-1) x_1が得られます。
ここでの問題は、nが大きくなるにつれて、この項w^(n-1)が、wの値が1より小さいか大きいかによって、非常に小さくなるか非常に大きくなるかのどちらかになることです。どちらの場合も問題です:勾配が非常に大きい場合、ニューラルネットの重みが多すぎて変化し、ニューラルネットがすでに学習した既存の機能が破壊されてしまいます。勾配が非常に小さい場合、重みが十分に変化せず、ニューラルネットは何も学習しません。
これがRNNの学習を難しくする理由です。原理的にはRNNは無限に長いコンテキストを使用できますが、実際には、勾配ベースの学習技術では、勾配が学習に適切なサイズを保つステップ数だけコンテキストを使用することしか学習しません。これは消失勾配と爆発勾配の問題として知られています。そして、非ゼロの入力を加えると、追加の入力が勾配をさらに不安定にするため、この問題はさらに悪化します。
そして明確にしておくと、これが通常のニューラルネットで問題にならない理由は、各層で異なる重みを使用するからです。一部の層は1より小さい重みを持ち、一部の層は1より大きい重みを持つことができます。勾配がほぼ同じサイズを保つ限り、ニューラルネットは学習できます。安定した勾配をもたらす重みの構成は非常に多くあり、学習全体を通して安定した構成を維持するのは簡単です。しかしRNNでは、各ステップで同じ重みを使用するため、安定した構成は重みが1である場合の1つだけです。1から少しでも逸脱すると、指数関数的に成長または減衰する勾配になります。重みが複素数の場合も、重みの絶対値を使用して同じ議論が適用されることに注意してください。
では、消失勾配と爆発勾配をどのように修正できるでしょうか?RNNの勾配は、再帰重みが1で入力が0である限り安定していることがわかりました。そこで、線形RNN論文の著者らは、この安定した状態で線形RNNを初期化することを提案しています。具体的には、重みを複素極形式ae^ibでパラメータ化します。aは大きさ、bは角度です。そして、この e^(-e^()) 関数を通してaを実行することで、大きさを1未満に制限します。この関数は常に0と1の間の数を出力します。通常行うように0を中心とする正規分布からaをランダムにサンプリングする代わりに、大きさe^(-e^(a))が0.999から1の間で一様に分布するようにaを初期化します。角度bは0から/10ラジアンの間で一様に初期化します。これにより、初期化時にすべての重みが1に非常に近くなることが保証されます。
最後に、入力に を掛けます。これは別の学習可能なパラメータで、sqrt(1-e^(-e^(a)))で初期化されます。e^(-e^a)が1に近いので、これはある非常に小さな数になります。これにより、初期化時にすべての入力が0に近くなり、再帰に干渉しないことが保証されます。
したがって、初期化時には、このモデルは私が前に示した安定したRNNとほぼ同じです。モデルが学習を開始し重みが変化した後は、安定性が維持される保証はありませんが、実際にはこのようにモデルを初期化するだけで、数万ステップにわたってコンテキストを記憶することを学習するのに十分なようです。
これで、計算が高速で、非常に長いコンテキストを使用することを学習する線形RNNが得られました。線形RNN論文では、このモデルをlong range arenaベンチマークで評価しています。これは、モデルの長距離推論能力を評価する6つの合成タスクのコレクションです。例えば、PathXタスクでは、モデルは2つの円を結ぶ完全な点線のパスが含まれているかどうかで画像を分類しなければなりません。ただし、画像は1万6千ピクセルの長いシーケンスに平坦化されています。線形RNNはlong range arenaで最先端の性能を達成し、タスク全体でトランスフォーマーを平均約33%上回りました。
さて、線形RNNを理解したところで、状態空間モデルについての話は何なのでしょうか?実は、状態空間モデルは単なる線形RNNなのです。状態空間モデルは制御理論にインスパイアされ、連続的な動的システムを離散化しようとする全く異なるアイデアから導き出されましたが、最終的な結果は単なる線形RNNで、初期化方式が少し異なるだけです。
最も一般的な形式の状態空間モデルは、各再帰重みをw=e^((a+bi))としてパラメータ化します。ここでは再び、学習可能なパラメータで、通常0.0001から0.1の間の非常に小さな数で初期化されます。重みに小さな数を掛けると0に近くなり、0に近い何かをeの累乗にすると1に近くなります。これにより、初期化時に再帰重みがすべてほぼ1になることが再び保証されるので、学習が安定します。状態空間モデルはまた、制御理論で規定されているため、入力に((a+bi))^-1(w-1)を掛けますが、経験的には線形RNNのセットアップと同様に入力をで単にスケーリングしても同じパフォーマンスが得られます。
long range arenaにおいて、制御理論に触発された状態空間の初期化は、線形RNNの初期化とほぼ同じパフォーマンスを示します。とにかく、状態空間モデルという言葉を聞いたら、線形RNNを思い浮かべてください。
そして最後に、マンバについて話すことができます。線形RNNがlong range arenaベンチマークで非常に良いパフォーマンスを示すとはいえ、これは言語モデルとして優れていることを意味するわけではありません。言語モデリングにおいて、線形RNNはトランスフォーマーよりもはるかに劣ります。これは様々な最先端の言語モデルのパフォーマンスを示しています。このグラフでは、低いほど良いです。ご覧のように、状態空間モデルを含むすべてのモデルがトランスフォーマーよりも大幅に劣っています。
マンバ論文で指摘されているように、その理由は、線形RNNが出力ベクトルから情報を選択的に忘却することができないからです。重みが0に近い場合、出力ベクトルは毎回の入力後に0にリセットされ、実質的にモデルは常に現在の入力の直前のものを即座に忘れてしまいます。再帰重みが1に近い場合、出力ベクトルは重みと掛け合わされても変化しないので、出力ベクトルは観察されたすべての入力からの情報を蓄積します。望ましいのは、モデルが見る入力に基づいて、情報をいつ保存し、いつ忘却するかを決定できることです。
マンバはエレガントな解決策を提案します:各ステップで同じ重みを使用する代わりに、入力に依存する異なる重みを使用します。マンバは各入力ベクトルに線形関数を適用して、その入力用の別の重みベクトルを生成します。そして、これらの生成された重みを使用して再帰スキャンが実行されます。このようにして、特定の入力は0に近い重みを生成して出力ベクトルから情報を消去し、他の入力は1に近い重みを生成して出力ベクトルを変更せずに残すことができます。
また、各ステップで異なる重みを使用することが、消失勾配と爆発勾配の問題にも役立つと私は推測しています。フィードフォワードネットワークのように、今や多くの異なる安定した構成が存在するはずだからです。ただし、これはマンバ論文では言及されていませんでした。
マンバはもう1つのトリックを使用しています。それは出力ベクトルのサイズを増やすことです。標準的なRNNでは、出力ベクトルは入力ベクトルと同じサイズです。マンバは出力ベクトルのサイズを16倍に拡大します。これにより、以前の入力からより多くの情報を保存できます。出力ベクトルは次の層に渡される前に元のサイズに戻されます。通常、これは計算時間を16倍に増加させますが、現代のGPUでのマンバ層の主なボトルネックは、高性能メモリへのデータの読み書きにかかる時間であることが判明しています。
現代のGPUには実際に2種類のメモリがあります。データはメインメモリに保存されますが、計算を行うためには、データをまず高性能メモリに転送する必要があります。マンバの再帰演算では、データの転送にかかる時間が、実際の計算にかかる時間よりもはるかに長いことが判明しています。したがってマンバは、入力ベクトルとモデルパラメータを高性能メモリに転送し、その後、出力を元の小さなサイズに投影することを含む、マンバ演算全体を単一のブロックで計算してから、結果をメインメモリに書き戻します。
このようにして、元のサイズのベクトルだけを高性能メモリとの間で転送するので、転送時間は変わりません。実際の計算時間は16倍遅くなりますが、計算時間は転送時間に比べて非常に小さいため、全体の所要時間にはほとんど影響しません。本質的に、16倍大きなベクトルを無料で使用できるのです。
これで、いくつかの小さなアーキテクチャの修正とともに、マンバ、つまり動的線形再帰ニューラルネットワークの説明が終わりました。マンバは言語モデリングにおいてトランスフォーマーよりも優れたパフォーマンスを示し、O(n^2)からO(nlog(n))に削減された計算量を使用します。
さて、これらの退屈な技術的詳細をすべて説明したところで、最後に本当に重要なことについて話すことができます:マンバのドラマです。マンバ論文は今年、機械学習コミュニティにかなりの論争を引き起こしました。マンバ論文はICLR 2024に提出されました。これは世界で最も権威ある機械学習会議の1つです。そして1月、ピアレビューアーによって却下されました。
しかし、それがどうしたのでしょうか?トップ会議で論文が却下されるのはよくあることですよね。ここで少し背景を説明しましょう。マンバのプレプリントは昨年から公開されており、この間に複数のグループがマンバを再実装し、マンバ論文で主張されている結果、つまりマンバがトランスフォーマーよりも優れたパフォーマンスを示し、より少ない計算量を使用するという結果を全て成功裏に再現しています。そして、トランスフォーマーが過去5年間誰もが話題にしてきたものであることを考えると、これはかなり大きな話題です。このため、コミュニティの誰もがマンバ論文が採択されるだろうと期待していました。もしかしたら最優秀論文賞を受賞するかもしれないとさえ思われていました。
では、マンバアーキテクチャが本当に機能するなら、どのような明らかな欠陥が論文にあって却下されたのでしょうか? ICLR のピアレビューは誰でも閲覧できるよう公開されています。では、見てみましょう。
メタレビューによると、マンバはlong range arenaベンチマークでテストされていないとのことです。線形RNNがトランスフォーマーよりもはるかに優れたパフォーマンスを示したと私が話したベンチマークを覚えていますか?このレビューアーは、マンバがそのタスクでどれほどうまく機能するかを見たかったようです。
これは論文を却下する本当に愚かな理由です。なぜなら、long range arenaは言語モデリングとは全く異なるタスクであり、マンバは特に言語モデルだからです。トランスフォーマーがlong range arenaで線形RNNよりもはるかに劣るパフォーマンスを示すにもかかわらず、トランスフォーマーはまだはるかに優れた言語モデルであることを心に留めておいてください。したがって、long range arenaでのパフォーマンスは、言語モデリング能力を示すものではありません。マンバは言語モデリングで新しい最高水準を設定しました。他の無関係なタスクも解決しないからといって却下されるべきではありません。
メタレビューの他の主要な批判は、マンバが言語モデリング、つまりテキストの次の単語を予測する精度についてのみ評価されたということです。レビューアーは、この指標が言語モデルの有用性を示すものではないと主張し、代わりにマンバはモデルの推論能力を測定する下流タスクで評価されるべきだと述べています。
ただし...これはマンバ論文で実際に行ったことです。彼らはマンバを言語モデルとして事前学習し、その後、標準的な下流ベンチマークタスクの束でゼロショットプロンプティングを実行しました。そして驚くべきことに、マンバは他のすべての言語モデルを上回るパフォーマンスを示しました。
おまけに、別のレビューアーはこう言いました。引用します。「マンバはトランスフォーマーと同様に、学習中に二次的なメモリ要件を持っています」。これは...単に事実ではありません。マンバもトランスフォーマーも二次的なメモリコストを持っていません。トランスフォーマーは二次的な計算コストを持っていますが、そのメモリコストは線形です。マンバも同様です...マンバの仕組みを少しでも理解していれば、マンバが二次的なメモリコストを持つという結論に至ることがどうして可能なのか、私にはわかりません...
ご想像の通り、この理想的とは言えないピアレビューは、機械学習コミュニティでピアレビューの慣行について、そしてマンバが却下されるべきだったかどうかについて議論を巻き起こしました。私がこの議論のどちら側に立つかはおそらく推測できるでしょうが、学術的なピアレビューがどれほど壊れているかについてのあなたの考えを下のコメント欄で教えてください。あるいは、マンバアーキテクチャ自体についての考えでも構いません。


この記事が気に入ったらサポートをしてみませんか?