最近集中学习pytorch,前两天做了个小实例突然想打包给别人耍耍,结果发现打包失败,搜索网上的方法也难以解决。整了两整天总算搞出来了,现在将整个踩坑与解决方法记录一下。当然这里同样的问题由于环境问题未必同样解法就能用,请自行决定取用。注:下述对我失败的方法网上也存在成功的情况。


目录

  • 首先
  • 坑1. ImportError: DLL load failed
  • 1.1法一(失败)
  • 1.2法二(失败)
  • 1.3法三(成功)
  • 坑2. file already exists but should not: XXX/torch_C.cp36-win_amd64.pyd
  • 2.1法一(失败)
  • 2.2法二(成功)
  • 2.3法三(不知道行不行)
  • 坑3. OSError: Can't get source for ... TorchScript requires source access...
  • 3.1法一(失败)
  • 3.2法二(失败)
  • 3.3法三(成功,但没有完全成功)
  • 坑4. AttributeError: 'function' object has no attribute 'def_'
  • 4.1法一(成功)


首先

本人在pycharm下terminal运行pyinstaller
环境为conda虚拟环境
python == 3.7.10
pyinstaller == 4.3
torch == 1.3.1
torchvision == 0.2.2.post3

推荐使用命令:

pyinstaller XXX.py

完成后应该会生成一个spec文件,一个dist文件夹(dist中有个文件夹XXX,下面是一系列文件),一个build文件夹,生成的可运行同名exe就在dist/XXX中。
(有人会在pyinstaller后跟-F, -w这两个命令,一个是将dist/XXX中一堆文件变为只生成一个稍大的exe,一个是去掉运行时的命令框,这两个建议先不加方便调试,不加貌似生成的快一点,搞定打包成功的话再加上重打一次包)

打包完成后注意,先将原来项目目录下要读取的文件啥的复制一份到dist/XXX中(比如txt啥的,否则可能找不到你要读取的目录)然后建议在cmd下执行exe文件,这样报错会显示(否则报错命令行一闪即逝,要靠手速截图)

坑1. ImportError: DLL load failed

前面最后会提示在site-packages\torch_init_.py出问题

1.1法一(失败)

我发现我没缺文件,复制了也没啥用。。。绝望。。。

1.2法二(失败)

在生成的同名spec文件中找binaries_files,修改:

binaries_files = [ ('C://tools//Anaconda3//envs//3DP//lib//site-packages//torch//lib', '.') ]

注意,上述文件目录请改为自己的环境下的torch/lib目录(其中Anaconda后部分除3DP为环境名,其它大致相同),注意目录为双斜杠避免歧义!
然后在终端运行

pyinstaller XXX.spec

(注意是spec)

1.3法三(成功)

重装torch与torchvision,更换版本,建议新建一个虚拟环境搞,在新环境瞎搞,总好过搞坏原有脆弱的环境。当然torchvision可以试试直接装0.2.2,我也是按教程来的怕再出问题。

pip3 install torch===1.3.1 torchvision===0.4.2 -f https://download.pytorch.org/whl/torch_stable.html

此后产生错误“OSError: could not get source code”,
解决方法(失败):降低torchvision版本

pip uninstall torchvision
pip install torchvision==0.2.2.post3

没有解决问题,但因为已经用了不影响啥,我用了这个方法之后也没还原版本,所以降不降自己选择。如果按部就班建议操作一下。
成功的方法见坑3

坑2. file already exists but should not: XXX/torch_C.cp36-win_amd64.pyd

2.1法一(失败)

在打包后生成的同名spec文件中a = Analysis(…)下方加入:

for d in a.datas:
	if '_C.cp36-win_amd64.pyd' in d[0]:
		a.datas.remove(d)
		break

然后运行pyinstaller XXX.spec(注意是spec)

2.2法二(成功)

按照1.3来。。。虽然还会报其它几个错,这个错不会报了,这我是没想到的。。。

2.3法三(不知道行不行)

在我踩完所有坑成功后,发现添加了-F的打包命令

pyinstaller -F XXX.py

后生成的exe运行时仍会报这个,但程序会正常运行

坑3. OSError: Can’t get source for … TorchScript requires source access…

3.1法一(失败)

在打包后生成的同名spec文件中修改:

block_cipher = None
excluded_modules = ['torch.distributions'] # 加入这一行

a = Analysis(['xxx.py'],
             pathex=['path'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=excluded_modules, # 把=None改成=excluded_modules
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)

然后运行pyinstaller XXX.spec(注意是spec)
对我没用,还是报这个错。

3.2法二(失败)

降低torchvision版本
pip uninstall torchvision
pip install torchvision==0.2.2.post3
虽然还是失败了,但我还是干了,之后没还原版本因为也能用,所以搞不搞自己看。

3.3法三(成功,但没有完全成功)

在python文件引入时,在import torch下方写:

def script_method(fn, _rcb=None):
    return fn
def script(obj, optimize=True, _frames_up=0, _rcb=None):
    return obj
import torch.jit
script_method1 = torch.jit.script_method
script1 = torch.jit.script
torch.jit.script_method = script_method
torch.jit.script = script

然后删除之前的spec,build,dist文件,重新应用

pyinstaller XXX.py

注意运行后依旧将项目读取文件复制一份进dist/XXX中。
然后发现,跑了一部分,有点输出!然后又报错。。。
此外,直接在pycharm运行原py代码,发现也报这个错。
报错内容为:

AttributeError: 'function' object has no attribute 'def_'

所以说没有完全成功,不过有些人就直接解决了没有报错。
追踪显示貌似是torch/jit某文件出了问题,并与自身python代码中的torch.jit.trace应用有关,解决办法见坑4

坑4. AttributeError: ‘function’ object has no attribute ‘def_’

追踪显示貌似是torch/jit某文件出了问题,并与自身python代码中的torch.jit.trace应用有关。

4.1法一(成功)

能遇见这个问题,一般来说是因为坑3的解决方法不完善,那咋整呢,我发现用了3.3方法后,仅在torch.jit.trace上出问题,灵机一动,在这代码之前把函数变回去不就成了吗?别说还真有用,于是这就成了解决办法。
具体如下:
法3.3处方法中将修改的两个函数改名暂存为script_method1,script1

def script_method(fn, _rcb=None):
    return fn
def script(obj, optimize=True, _frames_up=0, _rcb=None):
    return obj
import torch.jit
script_method1 = torch.jit.script_method # 暂存函数
script1 = torch.jit.script # 暂存函数
torch.jit.script_method = script_method
torch.jit.script = script

然后在代码中使用torch.jit.trace(…)代码的前面将方法换回去

torch.jit.script_method = script_method1
torch.jit.script = script1

最后删除之前的spec,build,dist文件,重新打包

pyinstaller XXX.py

记得运行后依旧将项目下用到的读写文件复制一份进dist/XXX中。

至此解决,运行成功!!!