Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

バックエンドのクラッシュを報告する

コンパイル失敗後に大量の llvm-ir コードが表示された場合、Enzyme バックエンドがあなたのコードのコンパイルに失敗した可能性が高いです。これらのケースはデバッグが難しいため、あなたの協力は非常にありがたいものです。また、現時点では通常、リリースビルドの方がはるかに動作する可能性が高いことも覚えておいてください。

ここでの最終目標は、Enzyme リポジトリでバグレポートを作成するために、Enzyme の compiler explorer であなたのバグを再現することです。

これを支援するために、rustflags に渡せる autodiff フラグがあります。これは、いくつかの __enzyme_fwddiff または __enzyme_autodiff 呼び出しとともに、llvm-ir モジュール全体を出力します。Linux での想定されるワークフローは次のようになります:

llvm-ir 生成を制御する

llvm-ir を生成する前に、関連する Rust コードをデバッグ用に見える状態にするために役立つ 2 つのテクニックを覚えておいてください。

  • std::hint::black_box: Rust の変数または式を std::hint::black_box() でラップし、Rust と LLVM によって最適化で取り除かれるのを防ぎます。これは、llvm-ir 内の特定の値を調査したり手動で操作したりする必要がある場合に便利です。
  • extern "rust" または extern "c": 特定の関数宣言が llvm-ir にどのように低下されるかを確認したい場合は、それを extern "rust" または extern "c" として宣言できます。例として、生成されたモジュール内にある既存の __enzyme_autodiff または類似の宣言を探すこともできます。

1) llvm-ir 再現用ファイルを生成する

RUSTFLAGS="-Z autodiff=Enable,PrintModBefore" cargo +enzyme build --release &> out.ll 

これにより、モジュールの上と下にあるいくつかの警告と情報メッセージもキャプチャされます。out.ll を開き、; moduleid = <somehash> より上のすべての行を削除してください。次にファイルの末尾を見て、llvm-ir の一部ではないもの、つまりエラーや警告をすべて削除してください。llvm-ir の最後の行は、!<somenumber> = で始まるはずです。つまり、!40831 = !{i32 0, i32 1037508, i32 1037538, i32 1037559} または !43760 = !dilocation(line: 297, column: 5, scope: !43746) のようになります。

実際の数値はあなたのコードによって異なります。

2) llvm-ir 再現用ファイルを確認する

前の手順が成功したことを確認するために、LLVM の opt ツールを使用します。opt バイナリへのパスを探してください。これは <some_dir>/rust/build/<x86/arm/...-target-triple>/ci-llvm/bin/opt のようなパスです。LLVM をソースからビルドする場合は、ci-llvmbuild に置き換える必要がある可能性が高いです。また、/rust/build/target-triple/enzyme/build/enzyme/llvmenzyme-21 のような llvmenzyme-21.<so/dll/dylib> パスも探してください。LLVM は LLVM バックエンドを頻繁に更新するため、バージョン番号はより高い可能性があります(20、21、…)。両方が揃ったら、次のコマンドを実行してください。

<path/to/opt> out.ll -load-pass-plugin=/path/to/build/<target-triple>/stage1/lib/libEnzyme-21.so -passes="enzyme" -enzyme-strict-aliasing=0  -S

このコマンドは、将来のバージョンやあなたのシステムでは失敗する可能性があります。その場合は、libEnzyme-21.so を LLVMEnzyme-21.so に置き換えてください。ビルド方法については Enzyme のドキュメントを参照してください。LLVM バージョンのビルド方法も調整する必要があるかもしれません。

前の手順が成功した場合、cargo で Rust コードをコンパイルしたときに見たものと同じエラーが表示されます。

同じエラーを得られない場合は、Rust リポジトリで issue を開いてください。成功した場合は、おめでとうございます!ファイルはまだ巨大なので、自動的に最小化してみましょう。

3) llvm-ir 再現用ファイルを最小化する

まず llvm-extract バイナリを探してください。これは opt バイナリと同じフォルダーにあります。次に実行します。

<path/to/llvm-extract> -S --func=<name> --recursive --rfunc="enzyme_autodiff*" --rfunc="enzyme_fwddiff*" --rfunc=<fnc_called_by_enzyme> out.ll -o mwe.ll

このコマンドは、最小の動作例である mwe.ll を作成します。

最後の --func フラグで渡す名前を調整してください。微分する関数に #[no_mangle] 属性を適用すれば、Rust の名前に置き換えられます。そうでない場合は、マングルされた関数名を調べる必要があります。そのためには、out.ll を開き、__enzyme_fwddiff または __enzyme_autodiff を検索してください。その関数呼び出し内の最初の文字列が、あなたの関数の名前です。例:

define double @enzyme_opt_helper_0(ptr %0, i64 %1, double %2) {
  %4 = call double (...) @__enzyme_fwddiff(ptr @_zn2ad3_f217h3b3b1800bd39fde3e, metadata !"enzyme_const", ptr %0, metadata !"enzyme_const", i64 %1, metadata !"enzyme_dup", double %2, double %2)
  ret double %4
}

ここでは、_zn2ad3_f217h3b3b1800bd39fde3e が正しい名前です。先頭の @ をコピーしないようにしてください。opt コマンドをもう一度実行して 2) をやり直しますが、今回は入力ファイルとして out.ll ではなく mwe.ll を渡します。この最小化された例でもクラッシュを再現するか確認してください。

4) (任意)llvm-ir 再現用ファイルをさらに最小化する。

前の手順の後、約 5k loc の mwe.ll ファイルができているはずです。これを 50 まで減らしてみましょう。optllvm-extract の隣にある llvm-reduce バイナリを探してください。エラーメッセージの最初の行をコピーします。例は次のようになります。

opt: /home/manuel/prog/rust/src/llvm-project/llvm/lib/ir/instructions.cpp:686: void llvm::callinst::init(llvm::functiontype*, llvm::value*, llvm::arrayref<llvm::value*>, llvm::arrayref<llvm::operandbundledeft<llvm::value*> >, const llvm::twine&): assertion `(args.size() == fty->getnumparams() || (fty->isvararg() && args.size() > fty->getnumparams())) && "calling a function with bad signature!"' failed.

単に segfault しか出ない場合は、意味のあるエラーメッセージがなく、自動的にできることはあまりないため、5) に進んでください。
そうでない場合は、次を含む script.sh ファイルを作成します。

#!/bin/bash
<path/to/your/opt> $1 -load-pass-plugin=/path/to/llvmenzyme-19.so -passes="enzyme" \
    |& grep "/some/path.cpp:686: void llvm::callinst::init"

grep に渡すエラーメッセージを少し試してみてください。エラーが一意であることを確実にするのに十分な長さである必要があります。ただし、() を含む長いエラーでは、それらを正しくエスケープする必要があり、面倒になることがあります。実行します。

<path/to/llvm-reduce> --test=script.sh mwe.ll 

input isn't interesting! verify interesting-ness test が表示された場合は、script.sh 内のエラーメッセージが間違っています。grep が実際のエラーに一致することを確認する必要があります。すべてうまくいけば、多数の反復が表示され、最後に新しい reduced.ll ファイルが生成されます。opt で、まだ同じエラーが発生することを確認してください。

高度なデバッグ: 手動での llvm-ir 調査

最小化された再現用ファイル(mwe.ll または reduced.ll)ができたら、さらに深く掘り下げることができます。

  • 手動編集: llvm-ir を手動で書き換えてみてください。間接呼び出しに関するものなど、特定の問題では、__enzyme_virtualreverse のような Enzyme 固有の intrinsic を調査するとよいかもしれません。これらの使い方を理解するには、Enzyme のドキュメントやソースコードを参照する必要がある場合があります。
  • Enzyme のテストケース: Enzyme リポジトリ 内で、あなたの問題に関連する機能や intrinsic の正しい使い方を示している可能性のある関連テストケースを探してください。

5) バグを報告する。

その後、mwe.ll(または reduced.ll)の例をコピーして、私たちの compiler explorer に貼り付けられるはずです。

  • 言語として llvm ir を選択し、コンパイラとして opt 20 を選択します。
  • コンパイラの右側にあるフィールドがまだ設定されていない場合は、-passes="enzyme" に置き換えます。
  • うまくいけば、もはや見慣れたエラーが再び表示されるはずです。
  • 共有ボタンを使用して、それらへのリンクをコピーしてください。
  • https://github.com/enzymead/enzyme/issues で issue を作成し、mwe.ll と(もしあれば)reduced.ll、および compiler explorer へのリンクを共有してください。rust コードやそれへのリンクも自由に追加してください。

調査結果の文書化

"attempting to call an indirect active function whose runtime value is inactive" のような enzyme エラーは、これまで混乱を招いてきました。このような問題を調査した場合、完全な解決策が見つからなくても、調査結果の文書化を検討してください。その知見が enzyme 一般に関するものであり、rust での使用に固有でない場合は、メインの enzyme documentation に貢献することが、多くの場合最初の最善策です。関連する enzyme の GitHub issue で調査結果に言及したり、適切であればこれらのドキュメントへの更新を提案したりすることもできます。これにより、他の人がゼロから始めるのを防ぐのに役立ちます。

明確な再現例とドキュメントがあれば、enzyme 開発者があなたのバグを修正できる可能性が高まります。それが実現すると、rust コンパイラ内の enzyme サブモジュールが更新され、rust コードを微分できるようになるはずです。rust-ad の改善にご協力いただきありがとうございます。

rust コードを最小化する

最小の llvm-ir 再現例があることに加えて、依存関係のない最小の rust 再現例があると有用です。これにより、修正後にそれを CI のテストケースとして追加でき、将来のリグレッションを回避できます。

rust 再現例の最小化に役立つ解決策はいくつかあります。おそらく最も単純な自動化されたアプローチはこれです: cargo-minimize

それ以外にも、treereducehalfemptypicireny など、さまざまな代替手段があります。場合によっては creduce も使えます。