[LibTorch 입문] 5편: pybind11로 C++ 코드를 Python에 바인딩하기

앞선 글에서 우리는 C++ 환경에서 LibTorch를 이용해 텐서 연산, TorchScript 모델 로딩 및 추론까지 다뤄보았습니다. 이제는 C++에서 구현한 기능을 Python 환경에서도 그대로 불러와 사용할 수 있다면 어떨까요? 이렇게 하면 C++ 코드 기반의 성능과 최적화를 유지하면서도, Python 환경이 제공하는 편리한 스크립팅과 풍부한 생태계를 활용할 수 있습니다.

이때 pybind11 라이브러리를 이용하면 C++ 함수를 Python 모듈로 손쉽게 노출할 수 있습니다. Python 개발자는 마치 파이썬 함수처럼 C++ 함수를 호출할 수 있으며, 이는 C++/Python 혼합 워크플로우를 매우 유연하게 만들어줍니다.

 

이번 글에서는 pybind11을 사용하여 간단한 C++ 함수를 Python에서 호출하는 방법부터, LibTorch 텐서를 Python으로 넘기고 받아오는 기초적인 과정까지 차근차근 살펴보겠습니다.

pybind11이란 무엇인가?

pybind11은 C++11 이상 표준을 이용해 Python 확장 모듈을 쉽게 생성할 수 있도록 지원하는 경량 바인딩 라이브러리입니다. ctypes나 Cython, SWIG와 같은 대안들도 있지만, pybind11은 다음과 같은 장점을 갖습니다.

  • 직관적인 문법: C++ 템플릿 기반의 깔끔한 인터페이스
  • 최소한의 오버헤드: 성능 손실이 적음
  • 광범위한 지원: STL 컨테이너, 스마트 포인터, NumPy 배열, Eigen 텐서 등 다양한 C++ 타입 지원
  • PyTorch/LibTorch 친화적: Torch 텐서와 손쉽게 호환하는 예제가 많음

즉, pybind11은 Python과 C++ 세계를 잇는 가벼운 다리 역할을 합니다.

pybind11 설치하기

pybind11은 Python 패키지로 설치 가능합니다.

pip install pybind11

혹은 conda를 사용하는 경우:

conda install -c conda-forge pybind11

C++에서 pybind11 헤더를 사용하려면, 프로젝트 빌드 시 pybind11의 include 경로를 CMake 등에 설정해줘야 합니다. pybind11은 헤더 온리(header-only) 라이브러리이므로 별도 라이브러리 링크 없이도 동작합니다.

기본 예제: 단순한 C++ 함수를 Python에서 호출하기

먼저 LibTorch와 무관한 간단한 예제로 시작해봅시다. C++ 함수 add를 하나 정의하고, 이를 pybind11로 파이썬 함수로 노출한 뒤, Python에서 호출해보는 과정을 살펴보겠습니다.

프로젝트 구조를 다음과 같이 가정합니다:

my_project/
  ├─ CMakeLists.txt
  ├─ src/
  │   ├─ bind.cpp   # pybind11 바인딩 코드
  │   └─ add.cpp    # C++ 함수 정의
  ├─ test.py        # 파이썬에서 테스트

C++ 코드 (add.cpp)

// add.cpp
int add(int a, int b) {
    return a + b;
}

아주 간단한 정수 덧셈 함수입니다.

pybind11 바인딩 코드 (bind.cpp)

// bind.cpp
#include <pybind11/pybind11.h>
#include "add.cpp" // 혹은 add.h를 만들어 include할 수도 있음

namespace py = pybind11;

PYBIND11_MODULE(my_module, m) {
    m.doc() = "Example pybind11 module";
    m.def("add", &add, "A function that adds two integers");
}
  • PYBIND11_MODULE(my_module, m): my_module라는 이름의 파이썬 모듈을 생성.
  • m.def("add", &add, ...): C++의 add 함수를 my_module.add로 Python에 노출.

이렇게 하면 my_module라는 파이썬 모듈을 컴파일 시 생성할 수 있습니다.

CMakeLists.txt 작성

cmake_minimum_required(VERSION 3.10)
project(MyPybindProject)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# pybind11 패키지 찾기 (설치 경로에 따라 경로 설정 필요)
# 방법1: find_package(pybind11 CONFIG REQUIRED)
# pybind11이 패키지로 설치되어 있는 경우 위 명령을 사용할 수 있음.
# 방법2: 수동으로 include 경로 지정
# set(PYBIND11_INCLUDE_DIR "/path/to/pybind11/include")
# include_directories(${PYBIND11_INCLUDE_DIR})

# 여기서는 find_package를 가정 (pybind11이 CMake 패키지로 설치된 경우)
find_package(pybind11 REQUIRED)

pybind11_add_module(my_module src/bind.cpp)

위와 같이 설정하면 my_module라는 Python 모듈이 빌드됩니다. 빌드 후 my_project/build 디렉토리 안에 my_module.*.so (또는 .pyd on Windows) 파일이 생깁니다.

빌드 및 테스트

cd my_project
mkdir build && cd build
cmake ..
make

빌드가 완료되면 my_module 공유 라이브러리가 생성됩니다. 이를 Python에서 호출해봅시다.

Python 테스트 (test.py)

import my_module

result = my_module.add(3, 5)
print("3 + 5 =", result)  # 3 + 5 = 8

python test.py를 실행하면 결과가 출력됩니다. 이제 C++ 함수를 Python에서 불러올 수 있음을 확인했습니다.

LibTorch 텐서를 pybind11로 넘겨받기

이제 LibTorch와 pybind11을 결합해봅시다. 목표는 C++에서 torch::Tensor를 반환하거나 인자로 받아, Python의 torch.Tensor와 주고받는 것입니다.

PyTorch Python 바인딩은 기본적으로 PyTorch가 제공하는 C++ <-> Python 변환 로직을 통해 작동합니다. pybind11로 LibTorch 텐서를 직접 넘기는 건 조금 복잡할 수 있지만, 다행히도 PyTorch는 pybind11 기반으로 만든 확장에 자연스럽게 텐서를 교환할 수 있게 해주는 기능을 갖추고 있습니다.

예제: 텐서 더하기 함수

다음 예제에서는 C++에서 두 텐서를 받아 element-wise 더한 뒤, 결과 텐서를 반환하는 함수를 만들어 파이썬에서 호출해보겠습니다.

// tensor_add.cpp
#include <torch/torch.h>

// 두 텐서를 더하는 함수
torch::Tensor tensor_add(const torch::Tensor& a, const torch::Tensor& b) {
    return a + b; 
}

바인딩 코드 (bind_tensor.cpp)

// bind_tensor.cpp
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <torch/torch.h>

namespace py = pybind11;

extern torch::Tensor tensor_add(const torch::Tensor& a, const torch::Tensor& b);

PYBIND11_MODULE(tensor_ops, m) {
    m.doc() = "Tensor operations with pybind11 and LibTorch";
    m.def("tensor_add", &tensor_add, "Add two tensors");
}

여기서 핵심은 pybind11이 torch::Tensor 타입을 자동으로 파이썬의 torch.Tensor와 매핑할 수 있다는 것입니다. PyTorch의 Python 확장 메커니즘 덕분에 별도의 변환 코드를 작성하지 않아도 됩니다.

CMakeLists.txt 수정

이제 torch 라이브러리를 링크해야 합니다.

cmake_minimum_required(VERSION 3.10)
project(TensorOps)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Torch REQUIRED)        # LibTorch 찾기
find_package(pybind11 REQUIRED)     # pybind11 찾기

pybind11_add_module(tensor_ops src/bind_tensor.cpp src/tensor_add.cpp)
target_link_libraries(tensor_ops "${TORCH_LIBRARIES}")

CMAKE_PREFIX_PATH를 통해 LibTorch 경로를 지정해줘야 할 수도 있습니다. (이전에 했던 것처럼 cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch ..)

빌드 후 tensor_ops.*.so 모듈이 생성됩니다.

Python 테스트 (test_tensor.py)

import torch
import tensor_ops

a = torch.randn(3, 3)
b = torch.randn(3, 3)
c = tensor_ops.tensor_add(a, b)

print("a:", a)
print("b:", b)
print("a+b:", c)

실행해보면 a+b 결과 텐서가 파이썬 콘솔에 출력됩니다. 이는 C++ 함수 tensor_add가 호출된 결과입니다.

응용: C++에서 TorchScript 모델을 불러와 Python 함수로 노출

더 나아가, C++에서 TorchScript 모델 로딩 및 추론 로직을 작성한 뒤, 이를 pybind11을 통해 Python 함수로 감싸면, Python 코드 내에서 TorchScript 모델 추론 함수를 재사용할 수 있습니다.

// model_infer.cpp (C++ 추론 로직)
#include <torch/script.h>
#include <torch/torch.h>

class ModelWrapper {
public:
    ModelWrapper(const std::string& model_path) {
        module_ = torch::jit::load(model_path);
    }

    torch::Tensor infer(const torch::Tensor& input) {
        std::vector<torch::jit::IValue> inputs;
        inputs.push_back(input);
        return module_.forward(inputs).toTensor();
    }

private:
    torch::jit::script::Module module_;
};
// bind_model.cpp
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <torch/torch.h>
#include <torch/script.h>
#include "model_infer.cpp"

namespace py = pybind11;

PYBIND11_MODULE(model_module, m) {
    py::class_<ModelWrapper>(m, "ModelWrapper")
        .def(py::init<const std::string&>())
        .def("infer", &ModelWrapper::infer, "Run inference on input tensor");
}

이렇게 하면 Python에서:

import torch
import model_module

wrapper = model_module.ModelWrapper("model.pt")
input = torch.randn(1,4)
output = wrapper.infer(input)
print("Model output:", output)

이제 Python에서 C++ 로직으로 구현된 TorchScript 모델 추론 기능을 호출할 수 있습니다.

정리

이번 글에서는 pybind11을 통해 C++ 함수를 Python에 바인딩하는 방법을 소개했습니다.

  • 기본 바인딩: 간단한 C++ 함수를 Python 함수로 노출하는 예제
  • LibTorch 텐서와 연계: torch::Tensor를 인자와 반환값으로 하는 C++ 함수를 Python에서 호출
  • 실전 예제: C++에서 TorchScript 모델 추론 로직을 Python에서 호출 가능하게 만드는 방법

이를 통해 C++로 구현한 고성능 로직을 Python 에코시스템에서 바로 활용할 수 있습니다. 추후에는 C++과 Python을 혼합한 아키텍처에서 핵심 부분은 C++로, 부가적인 스크립팅과 툴링은 Python으로 처리하는 유연한 워크플로우를 구축할 수 있게 됩니다.

 

다음 글에서는 C++과 Python 사이에서 텐서를 더 효율적으로 교환하는 방법, 그리고 양쪽 환경에서 동일한 모델 추론 파이프라인을 매끄럽게 운영하는 예시를 다룰 수 있습니다.

참고 자료

반응형