PyTorch(used GPU) 코드를 독립 실행파일로 패키징

실행 환경 : MAC M2

파이썬 버전 : Python 3.12.0

 

1.  pyinstaller 안써

파이썬 패키징하면 pyinstaller 이 대표적이다.

pytorch로 짧은 학습코드를 패키징하려고하는데 종속성 문제 때문에 약간의 씨름을 했었다.

 

torch error : NameError name 'name' is not defined

I'm currently attempting to create an executable using PyInstaller, but I've encountered an error : NameError: name 'name' is not defined caused by the line of code below. model = lp.

stackoverflow.com

정리하자면 다음과 같다.

 

PyInstaller와 PyTorch의 충돌

- PyTorch는 런타임 시 동적으로 모듈을 로드하는데, PyInstaller는 이러한 동적 로드 종속성을 자동으로 감지하지 못하는 경우가 많다.

- torch._numpy, torch._dynamo, torch._numpy._ufuncs와 같은 서브모듈이 제대로 포함되지 않은 것이 주요 원인입니다.

 

그래서 수동적으로 추가해줘야하는데, 

 

방법은 두 가지다. 

pyinstaller --onefile --hidden-import=torch._numpy --hidden-import=torch._dynamo --hidden-import=torch._numpy._ufuncs main.py

 

이렇게 hidden-import 옵션을 통해 문제의 모듈들을 명시적으로 포함하고 build를 하거나,

 

 

PyInstaller/hooks 디렉토리에 hook-torch.py 파일을 따로 생성하여,

from PyInstaller.utils.hooks import collect_submodules

# PyTorch 서브모듈 포함
hiddenimports = collect_submodules('torch')



// shell
pyinstaller --onefile main.py

 

서브 모듈을 포함시켜서 빌드하는 방법이 있다.

여기서 PyInstaller/hooks 디렉토리라 함은 site-packages 에 설치한 PyInstaller 라이브러리로 들어가서 직접 파이썬 파일을 추가하는것이다.

 

귀찮은 것도 크고, 잘 되지도 않는다.

하나가 해결되면 다른 종속성 문제들이 뒤따라 올라온다.

물론 해결책이 있겠지만, 그 시간에 다른 라이브러리를 찾아보는게 시간 절약될 거라고 생각했다.

 

 

 

2.  cx_Freeze

짧게 라이브러리 설명(더보기 열기)

더보기

cx_Freeze는 Python 스크립트를 독립 실행 가능한 실행 파일로 변환하는 크로스 플랫폼 라이브러리입니다. cx_Freeze는 Python 스크립트, 해당 스크립트의 종속성 및 Python 인터프리터를 하나의 실행 파일로 패키징하여, Python이 설치되어 있지 않은 시스템에서도 Python 프로그램을 실행할 수 있게 해줍니다.

주요 기능

  1. 크로스 플랫폼: Windows, macOS, Linux 등 다양한 운영 체제에서 사용할 수 있습니다.
  2. 패키징: Python 스크립트와 필요한 모든 라이브러리, 파일을 하나의 실행 파일로 패키징합니다.
  3. 종속성 해결: 스크립트가 필요로 하는 모든 모듈과 패키지를 자동으로 감지하고 포함합니다.
  4. 사용자 정의: 스크립트의 실행 옵션, 추가 파일, 라이브러리 등을 사용자 정의할 수 있습니다.

사용하기 심플하고, 이 라이브러리 또한 종속성 문제가 발생하지만 결과적으로 쉽게 해결할 수 있었다.

 

main.py 와 setup.py 이 필요하다.

- main.py :  패키징 하려는 코드

- setup.py : 패키징 코드

 

main.py

from torch.utils.data import DataLoader, TensorDataset
import torch.nn as nn
import torch.optim as optim
import torch
import importlib

if hasattr(importlib, "invalidate_caches"):
	def invalidate_caches(cls=None):
		pass
	importlib.invalidate_caches = invalidate_caches

device = torch.device("mps" if torch.backends.mps.is_available() else "cpu")
print(f"사용 중인 디바이스: {device}")

class SimpleModel(nn.Module):
	def __init__(self):
		super(SimpleModel, self).__init__()
		self.layers = nn.Sequential(
			nn.Linear(10, 50),
			nn.ReLU(),
			nn.Linear(50, 20),
			nn.ReLU(),
			nn.Linear(20, 1)
		)

	def forward(self, x):
		return self.layers(x)

def generate_data(num_samples=1000):
	x = torch.randn(num_samples, 10)
	y = x.sum(dim=1, keepdim=True) + torch.randn(num_samples, 1) * 2.0
	return x, y

def script_method(fn, _rcb=None):
	return fn

def script(obj, optimize=True, _frames_up=0, _rcb=None):
	return obj

import torch.jit
torch.jit.script_method = script_method
torch.jit.script = script

if __name__ == "__main__":
	x_data, y_data = generate_data(num_samples=10000)
	dataset = TensorDataset(x_data, y_data)
	dataloader = DataLoader(dataset, batch_size=32, shuffle=True)

	model = SimpleModel().to(device)
	criterion = nn.MSELoss()
	optimizer = optim.Adam(model.parameters(), lr=0.001)

	num_epochs = 100
	for epoch in range(num_epochs):
		model.train()
		epoch_loss = 0

		for batch_x, batch_y in dataloader:
			batch_x, batch_y = batch_x.to(device), batch_y.to(device)

			optimizer.zero_grad()
			predictions = model(batch_x)
			loss = criterion(predictions, batch_y)
			loss.backward()
			optimizer.step()

			epoch_loss += loss.item()

		print(f"에포크 {epoch + 1}/{num_epochs}, 손실: {epoch_loss:.4f}")

	print("학습 완료!")

	test_data = torch.randn(5, 10).to(device)
	model.eval()
	with torch.no_grad():
		test_predictions = model(test_data)

	print("\n테스트 결과:")
	for i, (input_vec, output) in enumerate(zip(test_data, test_predictions), 1):
		print(f"입력 {i}: {input_vec.tolist()} -> 출력: {output.item():.4f}")

 

 

setup.py

from cx_Freeze import setup, Executable  # cx_Freeze의 setup과 Executable 클래스 가져오기

# 빌드 옵션
build_options = {
    "packages": [
        "torch"  # 실행 파일에 포함할 패키지. PyTorch의 핵심 모듈을 포함하도록 지정
    ],
    "include_files": [
        # 빌드 결과물에 포함할 추가 파일 목록
        (
            # libtorch_global_deps.dylib의 실제 경로
            "/Users/sonseongjun/Library/Caches/pypoetry/virtualenvs/pytorch-pyinstaller-UNa3Va_d-py3.12/lib/python3.12/site-packages/torch/lib/libtorch_global_deps.dylib",
            # 빌드된 실행 파일의 내부에서 파일이 위치할 경로
            "lib/torch/lib/libtorch_global_deps.dylib",
        ),
    ],
}

# 실행 파일 정의
executables = [
    Executable(
        "main.py",  # 메인 스크립트 파일 경로
        target_name="pytorch_app",  # 생성될 실행 파일 이름 (확장자는 OS에 따라 자동 결정됨)
    )
]

# setup 함수 정의
setup(
    name="PyTorch App",  # 애플리케이션 이름
    version="1.0",  # 애플리케이션 버전
    description="PyTorch Application Example",  # 애플리케이션 설명
    options={"build_exe": build_options},  # 빌드 옵션 전달
    executables=executables,  # 실행 파일 목록 전달
)

 

여기서 빌드 옵션만 신경을 쓰면되는데, 이 부분이 종속성을 관리하는 영역이다.

 

빌드 옵션에 "include_files"를 추가하고 PyTorch의 네이티브 파일(libtorch_global_deps.dylib)을 명시적으로 포함시키는 이유는

cx_Freeze 가 자동으로 감지하지 못하는 파일을 수동으로 추가해 문제를 해결하기 위해서다.

 

 

이게 왜 문제? : PyTorch 네이티브 파일의 역할

  • libtorch_global_deps.dylib은 PyTorch의 내부 네이티브 라이브러리로, PyTorch가 실행 시 사용하는 종속성을 관리함.
  • 이 파일은 PyTorch의 메모리 관리, GPU(MPS), CUDA, OpenMP 등의 기능을 지원함.
  • Python 스크립트에서 직접 참조되지 않는 네이티브 파일을 자동으로 패키징하지 못할 때가 많아, 이를 명시적으로 포함해야 실행 시 오류를 방지할 수 있음.

 

해결은? : include_files로 해결

include_files를 사용하여 빌드 디렉토리에 libtorch_global_deps.dylib를 포함함으로써 문제를 해결함

 

1. 첫 번째 경로는 파일의 실제 위치

find $(python -c "import torch; print(torch.__path__[0])") -name "libtorch_global_deps.dylib"
이 명령어로 경로 찾았음. GPT가 알려줌.
"/Users/sonseongjun/Library/Caches/pypoetry/virtualenvs/pytorch-pyinstaller-UNa3Va_d-py3.12/lib/python3.12/site-packages/torch/lib/libtorch_global_deps.dylib",

 

2. 두 번째 경로는 빌드된 실행 파일 내에서 이 파일이 위치할 디렉토리를 지정

PyTorch는 실행 시 이 경로를 참조하므로, 동일한 구조로 복사해야한다.

lib/torch/lib/libtorch_global_deps.dylib

 

 

이제 빌드하면된다.

 python setup.py build

 

 

GPU 100%를 사용하는 모습

 

 

코드 공유

https://github.com/X2bee/py-to-exe

 

GitHub - X2bee/py-to-exe: GPU를 사용하는 pytorch로 exe파일 빌드

GPU를 사용하는 pytorch로 exe파일 빌드. Contribute to X2bee/py-to-exe development by creating an account on GitHub.

github.com

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유