pytorchでエラー(Leaf variable was used in an inplace operation)

タイトルに書いたエラーが出たのでわかったことをメモ

とりあえず解決に最も有用だった情報はこれ

Leaf variable has been moved into the graph interior - autograd - PyTorch Forums

生成したtensorの要素を直に書き換える処理をした上で、backwardを行うと上記のエラーが出るっぽい。自分のプログラムでもone-hotベクトルの形の入力を作る際に以下のようなことをやっていた。

input = torch.zeros([NUM_STATE], requires_grad=True)
input[state] = 1

これを以下のように書き換えたところエラーは出なくなった

input = np.zeros(NUM_STATE)
input[state] = 1
return torch.from_numpy(input).type(torch.FloatTensor)

調査中にその他調べたこと

エラーが出るのはloss.backward()を呼んだ際で、コード上ではここでエラーが出るようになっている模様。

pytorch/accumulate_grad.cpp at 372d1d67356f054db64bdfb4787871ecdbbcbe0b · pytorch/pytorch · GitHub

以下のページのVariableのところに説明があるが、grad_fnは自分で生成したtensorだとNoneに、計算によって作られたものだとそれを生成したFunctionを参照する値を持つらしい

http://caffe.classcat.com/2017/04/14/pytorch-tutorial-autograd/

また、leaf variableというのは直接生成したtensorのことを指している。

Leaf variable was used in an inplace operation - PyTorch Forums

よって、直接生成したtensorなのにgrad_fnの値がNoneではないためエラーとしているということだった。そしてこれはtensorの要素を書き換えるような処理で発生する。

ios向けビルドは通るがandroid向けビルドは失敗

cocos2dxで開発している際、タイトルのようなことがあったので原因を調査。 ちなみにiosはxcodebuild、androidはgradleでビルドしていた。

iosandroidのビルド結果を見比べると、iosのビルド時には以下のようなビルドオプションがついていることがわかった

-I/Users/<user>/Library/Developer/Xcode/DerivedData/<app id>/Build/Intermediates.noindex/app.build/Debug-iphonesimulator/app\ iOS.build/app\ iOS.hmap

調べてみると、xcodeはビルド時にクラス名とファイルパスを対応づけたバイナリファイル(hmapファイル)を生成し、それを元にしてincludeなどを行うことで検索を効率化しているらしい。 xcodeBuild Settings > Search Path > Use Header Mapsで設定ON/OFFできるので、OFFにしてみたところiosのビルドも失敗するようになった。

git lfs管理下のファイルに対するコンフリクト解消漏れを防ぐ

コンフリクト解消をしそこねて>>>>>>みたいなマーカーがそのまま上がってしまってる場合がある。以下に紹介されているようにhookを書いてがマーカーが含まれていればcommitをキャンセルすることで回避できる。

[Git]コンフリクトをよりスマートに解消したい!

ただ、git lfs管理下のファイルがあった場合には上記だけだとうまくいかない。 git lfsのファイルはgit上ではポインタファイルと呼ばれるメタデータとして存在している。実体は.git/lfs/objects以下に保持されていて、ポインタファイルではそのhash値やファイルサイズなどを記録している。 詳しくはこちら

git-lfs/spec.md at master · git-lfs/git-lfs · GitHub

実体がworkspace内に存在しないファイルについては、git checkoutをした際にgit lfsのサーバからdownloadされて.git/lfs/objects以下に置かれる。

git lfs管理下のファイルがコンフリクトした際は以下のような差分が出ている。(適当なimageファイルでコンフリクトさせた)

-oid sha256:71fbb9a95fe26f41d33d50766a01b68204e73970bac5922b2a6f31617d96106f
-size 21890
+oid sha256:c1a8f9c529bbb8b049397593c869be1a4f9d1518948297f6386be315984c7196
+size 148

これが表しているのは、.git/lfs/objects以下にある実体ファイルがsize 148のファイルになっているということ。そこで実体を見てみるとこうなっている。

<<<<<<< HEAD
oid sha256:71fbb9a95fe26f41d33d50766a01b68204e73970bac5922b2a6f31617d96106f
size 21890
=======
oid sha256:c1a8f9c529bbb8b049397593c869be1a4f9d1518948297f6386be315984c7196
size 148
>>>>>>> test

ポインタファイルのフォーマットがコンフリクトマーカーによって崩れたため、ポインタファイルとして認識されなくなり、実体ファイルとして.git以下に置かれたと考えられる。

ちなみに実体ファイルのpathはこんなルールで決まっている

  • .git/lfs/objects/<sha256の上2桁>/<sha256の3,4桁目>/<sha256そのもの>
    • 今回の例なら
    • git/lfs/objects/71/fb/71fbb9a95fe26f41d33d50766a01b68204e73970bac5922b2a6f31617d96106f

そのため、これをhookから検出するためには実体ファイルを取得してそこに含まれる文字列を見ればOK。そしてgit lfsにはそのためのコマンドとしてgit lfs sumdgeが用意されている(git checkout時の処理では内部でこれを使っている)

ポインタファイルから実体を取ってくるためには以下

$ cat <ポインタファイル> |  git lfs sumdge --skip

これにgrepなどでコンフリクトマーカーを検出するようにすればhookで検知することが可能