Anukari開発者からAppleへの技術的アピール
(anukari.com)- Anukariはリアルタイム3D物理シンセサイザーであり、大規模なspring-massモデルをGPU上で計算する必要があるが、Apple silicon macOSでGPUクロックが十分に上がらないとオーディオ遅延の条件を満たしにくい
- DAWがオーディオバッファブロックごとにプラグインを呼び出す構造と、macOSの電力管理ヒューリスティクスが噛み合うことで、GPUがブロック間で休んでいるように見え、低い性能状態に留まる可能性がある
- Xcode InstrumentsのMetal profilerでPerformance StateをMaximumにすると正常に動作し、Minimumでは大きく悪化することから、ボトルネックの核心がGPUクロック速度であることを確認した
- 現在は小さなspin kernelでGPU負荷を人為的に高める「waste makes haste」という回避策を使っているが、一部のPro/Max Appleハードウェアでは依然として問題が残っている
- 開発者はApple Metalチームに対し、Audio WorkgroupのGPU拡張、MTLCommandQueueのリアルタイム感度オプション、あるいは既存の解決策の案内を求めており、Windowsでは同じspin loopは不要だとしている
Anukariが直面しているmacOS GPU性能問題
- Anukari 3D Physics Synthesizerは、オーディオ生成のために大規模なspring-mass物理モデルをリアルタイムでシミュレーションする
- かなりの数の物理オブジェクトを扱うにはGPUが必要であり、物理コードはメモリよりALUボトルネックに近い
- シミュレーションの可変状態はGPUのthreadgroup memoryに保存される
- 手動割り当てのL1キャッシュに近い構造で、非常に高速
- 一般的な利用形態は、Pro ToolsやAbletonのようなDAW内でAUまたはVST3プラグインとして動作するもの
- DAWはオーディオバッファブロックごとにAnukariを呼び出す
- Anukariは各ブロックごとにGPU物理シミュレーションカーネルを実行し、結果を待ってから返す
- オーディオバッファブロックはGPUカーネルのスケジューリング遅延を複数サンプルに分散して吸収できるが、カーネル自体の実行時間は依然として決定的である
macOSの電力管理とリアルタイムオーディオの衝突
- Apple siliconは省電力のためにチップのクロック速度を下げることがあり、macOSは処理需要が低いと判断すると低クロックを維持する
- DAW内でのAnukariの実行方式は、macOSがGPU需要を判断する方法と相性がよくない
- GPUはオーディオバッファブロック間でアイドル状態になるため、平均負荷がたとえば60%程度にしか見えないことがある
- macOSの実際のヒューリスティクスは不明だが、load averageのような方式かもしれないと推測している
- この負荷ではGPUクロック上昇の基準を超えられない可能性がある
- Anukariはリアルタイム制約を満たすために低遅延が必要で、そのためには高いGPUクロックが必要になる
- AppleのGPUクロックがどこまで低下しうるかは分からないが、Anukariを使えなくするほど低くなることがある
Metal profilerで確認したクロック問題
- Xcodeに含まれるApple InstrumentsのMetal profilerにより、AnukariがALU-boundであることを確認した
- Metal profilerでは、プロファイリング中のMetalの「Performance State」を選択できる
- この設定はprofiler外では構成できない
- Maximum performance stateではAnukariは完璧に動作する
- Minimum performance stateでは動作が大きく悪化する
- この2状態の差によって、GPUクロック速度がAnukari性能問題の核心であることが明らかになった
「waste makes haste」回避策と限界
- macOSが必要なタイミングでGPUクロックを上げてくれないため、Anukariは別の回避策を使っている
- オーディオ計算用GPU作業と並列に2つ目のGPU作業を実行し、高い平均負荷を作ってmacOSがクロックを上げるよう誘導する
- この作業は、できるだけ少ないGPU資源でクロックのヒューリスティクスを刺激するよう調整されている
- 実質的にはGPUを温めるspin loopである
- この戦略は「waste makes haste」と呼ばれ、関連devlogに詳しく記録されている
- 開発者のMacBook M1ではこの方法が問題を完全に解決し、Anukariは安定して動作した
- しかしAnukari Beta公開後、一部のmacOSユーザーで問題が発生した
- 特にProまたはMax Appleハードウェアのユーザーで性能問題が多いように見える
- 記事では、GPU chipletごとの独立クロックの可能性や、より強力なGPUではspin workloadが保守的すぎる可能性を仮説として挙げている
Appleに求める解決の方向性
- Appleエンジニアのほうが詳しいだろうという前提で、いくつかの解決策候補を提案している
- Solution 1: Audio Workgroupの概念をGPU処理まで拡張する
- macOSのオーディオ処理は、Audio Workgroupというスレッドまたはスレッドグループで実行される
- OSはこれらのスレッドがリアルタイム制約を持つことを理解し、優先順位を与える
- Audio Workgroupスレッドが管理するMTLCommandQueueをリアルタイム処理と見なし、GPUクロックを調整できる可能性がある
- Solution 2: Metal APIのMTLCommandQueueにリアルタイム感度を示すオプションを提供する
- そのqueueを処理するGPU chipletのクロックをそれに合わせて調整できる可能性がある
- Solution 3: すでに望む機能を得る方法があるなら、Appleがそれを案内してくれるだけでも十分
- 記事上部には、Appleから連絡があり、関連内容は別記事にあると追記されている
Game ModeとWindowsの比較
- AppleのGame ModeはAnukariが必要としているものに近く見えるが、適用は難しい
- Game Modeはプロセス単位である
- Anukariはたいてい別プロセス内のプラグインとして使われ、そのプロセスはGame Modeをサポートしていない
- Anukari自身ではこれを直接制御できない
- Game Modeはfullscreenも要求するが、Anukariは通常fullscreenではない
- Windowsではこの問題は発生しない
- Windowsがユーザーに性能状態の制御をより多く与えているためなのか、NVIDIAドライバが消費電力にあまり慎重でないためなのかは分からない
- Windowsではspin loopは不要
- 性能の低いGPUを持つWindows PCではAnukariが問題なく動作する一方で、高価なMac M4 Maxではstutterが発生しうると比較している
パイプライニングが適さない理由
- GPUコードをパイプライン化してGPUを飽和させる方法は、スループット重視の作業には向いているが、Anukariは遅延感度の高い作業である
- 複数の物理シミュレーションカーネルを事前にスケジュールすると、GPUが現在のオーディオブロックを処理している間にCPUが次のブロックを準備できる
- しかし、パイプライニングはスループットを高める代わりに遅延を増やす
- Anukariの各カーネル実行は、マイク入力のようなリアルタイムオーディオ入力データにアクセスする必要がある
- 次のオーディオブロックを前もって処理するspeculative executionは、必要な入力データがないため使えない
spin kernelを同じMTLCommandQueueに入れる方法の問題
- spin kernelとphysics kernelが異なるGPU chipletで実行されることが本当の原因なら、同じMTLCommandQueueに入れる方法が解決策に見えるかもしれない
- 実際にこの方法は試したが、うまくいかなかった
- 理由は、Anukariが遅延感度の高い処理だからである
- spin kernelがときどき少し長く実行される
- その時間がphysics kernelの実行時間に食い込んでしまう
- 小さなspin kernelとvolatile unified memoryを使い、CPUが「exit kernel early」フラグを書き込む方式も試した
- こうした工夫をしても、spin kernelがphysics kernelの時間を侵食することがあった
GPU kernel hedgingが難しい理由
- 分散システムのrequest hedgingのように、物理カーネルを複数コピー実行し、最初に終わった結果を使う方法も検討した
- この方式はテール遅延と遅延分散を減らし、同時にGPU負荷を作ってOSが性能状態を上げるよう促すこともできる
- しかしAnukariではいくつもの問題がある
- 1つのphysics kernelがオーディオブロック周期1回分より長くかかると、そのカーネルストリームは遅れ始める
- 遅れたカーネルストリームは未来のブロックで追いつく必要があり、別ストリームの内部状態をコピーするfast-forwardが必要になる
- 内部状態のコピーは高コスト
- 最大の内部状態はdelay line用のオーディオバッファ
- マイクごとに1秒分の過去オーディオを保存する
- サイズは
48,000 samples * 50 mics * 2 channels * 16 voices * 4 bytesで、307MBに達する - より高いsample rateではさらに大きくなる
- 効率的に処理するには、各hedged kernel streamのdirty領域を精密に追跡し、その部分だけをコピーする必要がある
- しかしバッファメモリ配置はphysics kernelの読み取りworkloadに合わせて最適化されている
- 最小限だけをコピーしても、バッファ全体に散らばった領域をコピーすることになり遅い
- ユーザーモデルの変更もすべてのhedged kernelに伝播しなければならない
- physics kernelは「waste makes haste」spin kernelよりGPU footprintがはるかに大きい
- hedgingはより多くの不要なGPU負荷を生み、並列実行できるAnukariインスタンス数を減らす可能性がある
- hedge kernel同士が競合して全体が遅くなる可能性もある
すでに行った最適化とGPUが必要な理由
- AnukariシミュレーションはALU-boundであり、メモリアクセスパターン改善のような一般的な最適化余地は大きくない
- 性能向上には演算スループットの最適化が必要
- 可能な箇所ではFP16演算を使い、Apple ALUをよりよく飽和させている
- micro-benchmarkを用いて命令順序を調整している
- すべての物理状態をL1 memoryに置いている
- vectorizationのためにload順序を再配置している
- Apple SIMD-groupのスレッドが概ねinstruction pointerを共有する点も活用している
- 異なる物理オブジェクトはbranch pathが大きく分岐する
- 1つのSIMD-group内で2種類のオブジェクトをシミュレーションすると、instruction maskingのため遅くなる
- これを避けるため、物理オブジェクトのメモリ配置を動的に最適化し、SIMD-group内で実行されるオブジェクト型の数を減らしている
- この最適化はthe new warp alignment optimizerで詳しく説明されている
- 追加の演算最適化の余地はあるが、改善は一桁パーセントポイント程度にとどまると見ている
- 高性能なマシンでは、Anukariは768〜1024個の物理オブジェクトをシミュレーションできる
- 各オブジェクトは他のオブジェクトと任意に接続できる
- オブジェクトは通常、毎秒48,000サンプルのオーディオsample rateでimplicit Euler integrationを行う
- 各オブジェクトは3〜10個の動作パラメータを持つ
- 一部の動作にはvector rotation、
exp(),log()のような高コスト演算が含まれる - polyphonyのため、物理シミュレーション全体を最大16個の並列コピーとして実行する
- CPUではこの方式は実現できず、GPUの多数のALU、L1 cache layoutの制御、
threadgroup_barrierのような並行性機構が必要だった - AnukariはGPU処理なしには成立しない
GPU Audio APIが解決策ではない理由
- GPU AudioのCEO、Alexander Talashovは、AnukariがGPU Audio APIを使えば問題を解決できる可能性があると述べてきた
- 開発者はGPU Audioを優れた製品と評価し、GPUをDSPに利用可能にする製品だと紹介している
- しかしAnukariにとってGPU Audioは有用ではないと判断している
- Anukariは従来型DSPアプリケーションとは異なり、数値微分方程式積分器に近い
- DSP要素も一部あるが、計算の大半はEulerian integrationである
- 物理世界のマイク圧縮のようなDSPは、GPUの物理計算の中でinline処理される
- AnukariはMetalの低レイヤーでGPUを直接プログラミングしている
- 必要なのは、AppleがGPUクロック速度を安定して引き上げてくれることだ
1件のコメント
Hacker Newsのコメント
私の Show HN 投稿で Anukari を見た人もいるかもしれない: https://news.ycombinator.com/item?id=43873074
そのスレッドで macOS の性能 の話が出ていた。Anukari はベースモデルの M1 を含む Apple silicon の大半で問題なく動作し、私のテストもすべてベース M1 で行ったが素晴らしかった。ハードウェアは本当に見事だ
ただし動かすためには、オーディオ処理が十分に速くなるよう macOS に GPU クロック速度 を上げさせる奇妙な回避策を実装しなければならなかった。macOS が GPU の性能状態を決める一般的なヒューリスティックは、Anukari の特殊なワークロードを理解できない
そこで結局、状況全体を必要以上に詳しくまとめ、Apple の適切な担当者、おそらく Metal API 周りの担当者につないでもらえるよう助けを求めた。助けてください :)
「とても長くて非常に技術的な文章」と言っていたが、最後まで読んでみるとそこまで長くもなく、とても明快でよく書かれていて有益だった。いい文章だった
Mac を持ったことがなく、私の PC も古くてまともな GPU がないので Anukari をすぐ試せそうにはないが、本当に魅力的に見えるので残念だ。早く解決することを願う
この entitlement は試してみただろうか: https://developer.apple.com/documentation/bundleresources/en...
com.apple.developer.sustained-executionが逆方向にも働くのか気になる興味深い記事だし、問題も興味深い。同じキューで作業を実行するというアイデアが失敗する理由も、結局は元の問題と同じなのではないかと思う。可変クロック速度 のせいで精密なスケジューリングが不可能で、OS が GPU クロックをどう設定したかによってスピン停止のタイミングが理想的な時点からずれて aliasing が生じる、ということだろう
だとすると、スピン処理が GPU を最高クロックまで引き上げるほど十分に複雑ではないのかもしれない。本当に最高性能で動いているなら、ソフトウェア PLL を追加しなくてもスピン終了のタイミングを安定して合わせられるはずだ。スピンがどう実装されているのか詳しい説明は見ていないが、GPU のより多くの部分に継続的に負荷をかける、より忠実なスピンループのほうが、クロックを最高性能に保つのに効果的だと思う
Show HN は見逃したが、見た瞬間に 創造的な ASMR サウンドスケープ や没入型の多次元オーディオにとても合いそうだと思った。あなたかユーザーの誰かがデモを作ってくれるといい。プロジェクトおめでとう、そして Apple 関連の問題でも助けが得られることを願う
記事が良く、説明も明快で理解しやすかった。他の文脈でも、説明されていたのと同じ問題を経験したことがあるのは確かだ
みなさん、効果がありました。Metal チームのまさに適任の人 と非常に生産的な会話ができました! Apple の関心を引けるよう助けてくれてありがとう。こんなに多くの支援があるとはまったく予想していなかった
https://anukari.com/blog/devlog/productive-conversation-appl...
今は回避策ができたのは良いことだが、その回避策が何なのかすら共有できないという事実は、皮肉にも Apple のコミュニケーションのやり方についての https://news.ycombinator.com/item?id=43904921 の最後の一文をそのまま示している
「この値をこう設定してからああ変えると動く。文書化はされていないが、これで分かったでしょう」という感じだ
回避策を実装するとき、同じように レイテンシに敏感な GPU の制約に悩む他の人たちが、逆アセンブルででも魔法の呪文の手がかりを見つけられるように、露骨な名前の関数に入れておけるといい
HN がまたしても本来の目的を果たした。大企業のカスタマーサポートの前にある 官僚的な障壁 を突破することだ
プロジェクトおめでとう、幸運を祈る
Apple App Store で非常に有名なアプリを持つ、2つの有名企業で働いたことがある
私たちが話していた Apple チームは私たちの問題にはまったく関心がなく、その代わり WWDC で発表する最新機能を議論しようと、たびたびオフィスに招いてその機能への対応を事実上強要してきた。それが彼らとの関係の始まりであり終わりだった。バグの多い Apple ソフトウェアがなぜ動かないのかを突き止めるには、技術サポートチケット を出すしかなかった
Apple のデベロッパーリレーション担当者は真剣な人たちではない
上で元投稿者が示しているように、私の経験が一般的な法則ではないようで幸いだ。だが約10年前、かなり有名なアプリを持つ会社で働いていたとき、あるアップデートがアプリの性能を完全に壊してしまった
まったく同じ時期に、競合が性能問題のないアプリをリリースした。調べてみると、その競合アプリの開発者は少し前に Apple を辞めた人物で、Apple の ビデオドライバ に文書化されていない落とし穴を残しており、それで私たちのアプリが壊れていたのだった。競合バイナリを逆アセンブルしてようやく、その文書化されていない変更を見つけてアプリを修正できた。その開発者は私たちの CEO にメールで嘲笑までしてきた。なんとも素晴らしい世界だ
Metal プロファイラには、アプリケーションをプロファイルしている間に Metal の 性能状態 を選択できる非常に便利な機能がある。プロファイラの外では設定できない
これを見ると非公開 API があるように思える。リバースエンジニアリングに進むほうが簡単なのではないだろうか? もちろん、SIP を無効にしないと回避できない特殊な権限が必要になるのでなければ、だが
これは必ず 非公開 API があるはずだ。記事にもこうある
「Metal プロファイラには、アプリケーションをプロファイリングしている間に Metal の『Performance State』を選択できる非常に便利な機能がある。プロファイラの外では設定できない」
非公開 API でないなら、Metal プロファイラがどうやってそれを実現できるだろう? 何らかのデバッグツールでプロファイラを観察して、内部で何が起きているのか調べられないだろうか?
このAPIを公開する際の問題は、あまりに多くの開発者が常に最高性能状態を強制的にオンにしてしまうだろうという点。APIを提供しつつそれを防ぐ良い方法が本当にあるのかはよく分からない
バッテリー駆動の機器で、1つのアプリが電力を無駄遣いする方法はすでにいくらでもある。結局のところ、開発者が意図的であれミスであれ、エネルギー集約的な処理を不要に回し続けないことを信頼する構造になっている。適切に使わなければ電力を浪費しうるAPIが1つ増えたところで、そこまで大きくは変わらない
記事ではゲームモードにも触れているが、これは最近のApple OSでこうした用途向けに最適化された機能だ。ゲームモードが有効になると通知が出るし、ほとんどのアプリケーションはそれを望まないだろう。これまでこれが乱用されている例は見たことがない
開発者たちはまだ、すべてのスレッドプールでオーディオワークグループを乱用してP-coreスケジューリングと高優先度を得るようなことはしていない。だとすれば、オーディオワークグループがGPUにコマンドを発行する際、最後にワークグループがデータを送った時点を基準に、GPUのダウンクロックに一種のタイムアウトを設けられることを示唆している
GPUオーディオは今のところかなりニッチな分野だが、本文で触れられている企業が最近SDKを公開したので、今後より一般化するかもしれない。それでもあまり納得はしていない。GPUで処理するということは、レイテンシはそれほど気にしないという意味に近いので、単に入出力バッファサイズを大きくすればいいと思う
APIを乱用するとしても、同じことをするために偽のビジー処理を回すよりは効率的だろう。アプリはすでにAPIなしでも、あるいはAPIが要求しうる権限なしでも、そうしたことができる
手動で権限を付与するのはどうだろう。どこかに隠しておくとしても、かなりニッチなアプリには必要になる可能性が高い
そしてOSレベルでZoom、Teams、Webブラウザはデフォルト拒否にしておけばいい :)
これをやる一番いい方法:
WWDCの動画をざっと見て、今直面している問題を最もよく分かっていそうなエンジニアを探す
Michael Thomsonならmthomson@apple.comみたいな形式で直接メールするpthomsonで送る余談だが、AnukariはMick Gordonサウンドパックを出して、その収益を彼と分け合うといいと思う。あの人は本当にとんでもないものを作っていて、デモもすばらしい。これほど強力なツールができたなら、アーティストと協業するのはビジネスとしても良いし、世の中にとっても良い。Mick Gordonが好きならの話だが、私は好きだ
このアプリはまったく必要というわけではないが、本当にクールだ。こういうアプリはコンピューティングに楽しさを取り戻してくれる。今がまったく楽しくないという意味ではなく、もっとグラフィカルで実験的なプログラムがあちこちにあった昔、さらにはデモシーンまで思い出させる
最後から2番目の段落に入っているhttps://x.com/Mick_Gordon/status/1918146487948919222のリンクは見逃してはいけない。Mick Gordonが作ったデモで、@anukarimusic はこう返している
「笑、リリース2日目なのに、私が2年間毎日使って作ってきたデモをもう完全に打ち砕いてしまいました」
1024個のオブジェクトを48kHzで更新するのは、コードの書き方次第ではCPUでも可能に見える。1秒あたり4,800万回の更新ではないか? OpenMPでいくつかのループをコアに並列化して回すのにちょうどよさそうだ
Anukariはポリフォニーのために物理モデル全体を最大16個のコピーで回している。つまり
16 * 1024 * 48Kだ。ブログ記事は更新したほうがよさそうユーザーはオブジェクト同士を任意に接続できるため、各オブジェクトはN個の別エンティティへの接続を読み取り、処理する必要がある
CPU全体を使うには、各物理ステップごとにコア間同期が必要で、これが遅い
オブジェクトあたりの処理量はかなり大きい。超越関数が多く、近似も可能だが、機能自体も多い。すべてのパラメータを変調でき、NaNに対しても安全である必要があるなど、考慮すべき点が多い
ユーザーは複数のトラックやエフェクトなどのために、Anukariを複数同時並行で走らせたいと思っている
別の見方をすると、
4 GHz / (16 voice * 1024 obj * 4 connections * 48,000 sample) = 1.3 cycles per thingだGPUはこのワークロードを一瞬で処理する。まさに理想的な構造だ。
16 voice * 1024 objをすべて完全に並列処理でき、各ステップの同期も単純で、しかもユーザーがL1キャッシュを管理できる計算が合っているなら、サンプル1つを計算するのに83クロックサイクルになる。16コアなら理論上1333サイクルで、それほど多くはない。CPUを常にほぼ100%使えるわけではないことを考えると、なおさらだ