실행 환경 : 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 프로그램을 실행할 수 있게 해줍니다.
주요 기능
- 크로스 플랫폼: Windows, macOS, Linux 등 다양한 운영 체제에서 사용할 수 있습니다.
- 패키징: Python 스크립트와 필요한 모든 라이브러리, 파일을 하나의 실행 파일로 패키징합니다.
- 종속성 해결: 스크립트가 필요로 하는 모든 모듈과 패키지를 자동으로 감지하고 포함합니다.
- 사용자 정의: 스크립트의 실행 옵션, 추가 파일, 라이브러리 등을 사용자 정의할 수 있습니다.
사용하기 심플하고, 이 라이브러리 또한 종속성 문제가 발생하지만 결과적으로 쉽게 해결할 수 있었다.
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
'A.I.(인공지능) & M.L.(머신러닝) > 딥러닝' 카테고리의 다른 글
Neural Prophet 삼성전자 주가 예측 (2) | 2024.12.25 |
---|---|
[이론] Hugging Face Trainer.TrainingArguments (0) | 2023.12.18 |
[실습] LLaMA-2 Model, LoRA Fine-Tuning (0) | 2023.12.12 |