AI・機械学習
Apple Neural Engineから50GB/sを取り戻す
Getting 50 GB/S Back from the Apple Neural Engine (eiln.github.io)
要約
Apple M3 Neural Engine (ANE) のDRAMウェイトストリーミングにおいて、総ウェイトサイズが1MiBの整数倍になる場合に、スループットが45-60GB/sから17-19GB/sに低下するパフォーマンスの問題が発見されました。この問題は、カーネルDMAエンジンのプリフェッチリングの特定パスを回避することで、Llama 3.2 1BやQwen3-8Bモデルのトークン処理スループットを大幅に向上させることで解決されました。調査の結果、このスループット低下は、テンソル次元Dが2048の倍数になる場合に発生するメモリコントローラーの特定周波数に起因することが示唆されています。
全文翻訳
ANEから50GB/sを取り戻す
2026年8月10日
(3162語)
はじめに
Apple M3 Neural Engine (ANE) におけるRTLパフォーマンスの不具合により、総ウェイトサイズが1MiBの整数倍になる場合、DRAMウェイトストリーミングのスループットが公称の45-60GB/sから17-19GB/sに低下します。これは現在、ANEMLLの15モデル中7モデルに影響しています。カーネルDMAエンジンのプリフェッチリングにおける問題のあるパスを回避することで、Llama 3.2 1Bのトークン処理スループットは10.0から24.3トークン/秒に(DRAM使用量は24.7から60.0GB/sに)、Qwen3-8Bは1.36から2.97トークン/秒に(DRAM使用量は22.4から48.7GB/sに)向上しました。
発見
単一トークンデコードにおけるニューラルエンジンのDRAMウェイトストリーミングスループット(GB/s)をプロファイリングしていました。
[ X[1,D] × W[D,N] = Y[1,N] ]
N=4096の場合、D=1536はLlama 3.2で使用されるデフォルト値であるD=2048の約3倍高速に動作することに気づきました。
STATIC (純粋なKernelDMA) の中央値 µs/レプリカ、N=4096:
D 576 768 1024 1280 1536 2048
rep a 150.4 196.8 238.1 293.8 310.9 997.6
rep b 157.8 190.2 250.1 288.3 326.8 995.0
rep c 148.7 189.6 249.4 275.2 316.5 995.4
DをD = 2048の近傍でスイープしました。
なんだ?
D=2048では、スループットは16.93GB/sでした。D=2016では、スループットは44.5GB/sでした。これは
44.505062 − 16.930761 = 27.574301 GB/s (61.96% 低い)
ことを意味します。44.5から16.93GB/sへの27.57GB/sの低下です。スイープデータはM3 Airで収集され、40回の実行にわたって同じ熱/負荷条件で単一の実行中に繰り返し行われました。また、ANEレジスタファイルのDMAサイズとアドレスのみが変更される変数であることを確認しました。
D=2044 D=2048 D=2052
TD+0x004 estimated cycles 0x000001ea 0x000001eb 0x000001ec
TD+0x078 core 1 base 0x00ff800 0x00100000 0x00100800
TD+0x07c core 2 base 0x01ff000 0x00200000 0x00201000
...
TD+0x0b0 core 15 base 0x0ef8800 0x00f00000 0x00f07800
TD+0x0b4–0x0f0 core sizes ×16 0x00ff800 0x00100000 0x00100800
TD+0x134 Common.Cin 0x00007fc 0x0000800 0x0000804
TD+0x1f0 L2 source stride 0x00007fc0 0x00008000 0x00008040
TD+0x1f4 unknown stride mirror 0x00007fc0 0x00008000 0x00008040
TD+0x214 L2 result base 0x00008fc0 0x00009000 0x00009050
そこで、Dの全アパーチャをスイープしました。
それは良いアイデアでした。なぜなら、D = 2048で共鳴が見られるからです。テンソル次元(D)に対するスループット(GB/s)のFFTを行うことになるとは思いもしませんでした。これがそれです。
明らかに、メモリコントローラーのスループットは、テンソル次元空間で波長2048の支配的な高調波を持っています。そして残念ながら、それはディップです。
D = 2048のすべての倍数も同様に、固定された帯域幅のフロアである17-19 GB/sに制限されています。
D = 2048の倍数では、スループットは公称の45-60GB/sから17-19GB/sに急激に低下し、約256ライン離れるだけで公称値に回復します。これはRTLの正確性のバグではありません。カーネルDMAは転送を正しく完了します。しかし、2048付近のリクエストは、別個のクレジット不足の問題レジームに強制され、不当に28-43GB/s(最悪ケース60→17)でスループットを圧迫しています。これらの転送サイズは、残念ながら非常に一般的です。
仮説1 - DRAM空間相関
16個のコアは、2のべき乗のストライドで同じDRAMバンクにエイリアシングしていますか?
DRAMは並列データインターフェイスです。DRAM帯域幅は、DQ(データ)ピンの数×ピンあたりのデータレートです。
[ DRAM BW = N × R = 128 ビット × 6.4 GT/s = 102.4 GB/s ]
M3のLPDDR-6400の102.4GB/sは、広告の100GB/sと一致します。持続的なDRAM帯域幅は厳密にはそのDQ利用率であり、102.4GB/sのDRAM上限から1GB/s不足するごとに、DQラインがアイドル状態だったサイクルが追加されます。DRAM TLDR: DRAMメモリコントローラーは、高速DQピンを通じてビットをストリーミングするために並列アクセスを使用します。大きなDRAMアレイはバンクに分割され、帯域幅は(大まかに言って)並列リクエストをバンク全体に分散させることに依存します。
並列性は、リソースが独立している場合に帯域幅を購入します。並列リクエスタが同じリソースを要求する場合、それらのリクエストはバックツーバックに直列化され、実質的に単一レートでスロットルされます。約17-19GB/sのスロットルされたフロア(その直近の隣が45-60GB/sで動作している間)は、2のべき乗の境界で崩壊が発生することによって説明される可能性があります。低レベルから始めるのも良いことです。追加のAXIリクエストは、同じ物理バンクを要求している場合、何もできません。
コアの競合
ニューラルエンジンには、並列性のためのいくつかの経路があります。第一級はコアレベルの並列性です。ANEは16個のコアを並列に持っています。コアは、バッファをN個のコアに均等に分割し、異なるスライスを処理することに相互に合意することによって作業を分割します。コアはウェイトバッファの異なるスライスを取得するように割り当てられていることを知っていますが、ANEは依然として16個のコアすべてが同じサイクルでDRAMからスライスを取得するように要求しています。
各コアがDRAMから独自のスライスを取得する場合、1つのコアまたはすべての16個のコアが有効になっている場合でも、ストリーミングレイテンシは同じ時間かかるはずです。なぜなら、それらのリクエストは並列に処理されるはずだからです。しかし、コアの競合による帯域幅の低下がある場合、コア数を減らすことが皮肉にもスループットを増加させる可能性があります。これは、スロットルされたD=2048の場合です。D=2048およびD=2016のアクティブコア数に対するスイープ:
レイテンシは、D=2016およびD=2048の両方で、1から16個のアクティブコアまで一定です。これは、コア=1でもスロットリングが存在することを意味します。問題はコアレベルで存在し、問題はコア全体に複製されます。
アドレスの競合
コアレベルの競合を除外した後も、2のべき乗の周期によるDRAM競合を疑っていました。2048のような2のべき乗のストライドは、各回転で2^kを追加します。これは、下位ビット[0..k-1]が一定であることを意味します。DRAMは物理アドレスをハッシュして、ストライドアクセスパターンが異なるバンク全体で空間的に非相関になるようにします。したがって、下位ビットを崩壊させるハッシュ、または上位2^kビットのエイリアシングは、2のべき乗の周期性を説明する可能性があります。
DRAM空間相関が問題であるかどうかをテストするために、ウェイトが取得されるアドレスをスクランブルします。アドレスはランダムにスクランブルされ、約64MiBのIOVAアリーナ全体(59.90MiBの範囲)に分散されました。そのため、ページ内およびページ外の両方でスクランブルされました。ファンレスM3 Airでの熱ドリフトを排除するために、ベースラインとスクランブルされたサンプルは実行ごとにインターリーブされました。したがって、熱ランプは両方の条件に均等に影響します。
ベースラインの中央値スループットは31.37GB/sで、ランダムにスクランブルされたアドレスの中央値スループットは32.29GB/sでした。ランダムスクランブルは、この実行では約1GB/sの平均でわずかに高い持続スループットを示唆しており、スクランブルによって空間相関の一部を攻撃できた可能性があります。しかし、(1)これはすべてのケースで証明されていません。(2)スクランブルは、崩壊を説明するために必要な約+200%のスループット低下を回復できません。
仮説2 - RTL整数ラップアラウンド
D = 2048のすべての整数倍で崩壊が繰り返されたことを思い出してください。
Q: 2のべき乗の整数境界で繰り返されるものは何ですか?
A: 固定幅デジタルロジックの整数オーバーフロー。
module line_counter (
input wire clk,
input wire reset,
input wire advance,
output reg [13:0] line_count
);
always @(posedge clk) begin
if (reset)
line_count <= 14'h0000;
else if (advance)
line_count <= line_count + 1'b1; // wrap at 0x3fff + 1 -> 0x0000
end
endmodule
カーネル次元
[ X[1,D] × W[D,N] = Y[1,N] ]
ここで、D (Cin): 各カーネルの長さ: D個のFP16(2バイト)ウェイト、または2Dバイト。
N (Cout): カーネルの数。各コアはN/16個のカーネルを処理します。
元のプロットはNを4096に固定してDをスイープしたため、ノッチがD、またはDとNの積によって引き起こされたのか、各コアがタスク全体で処理する必要がある静的カーネルデータの総バイト数を決定するのかを実際に解決しませんでした。
[ bytes/core = (N/16 カーネル/コア) × (D ウェイト/カーネル) × (2 バイト/ウェイト) ]
DとNが各スライス転送のタイミングに影響する場合、未知の変数を分離するために、コンパイルされたタスクがすべてコアあたり同じ1MiBの静的カーネルデータを持つように、DとNを逆方向にスイープします。実行されたレジスタファイルの16進数差分を表示し、関連フィールド(アドレス、サイズ)のみが変更されたことを示します。