정보기술 · 인공지능 ·
파이썬을 어디서나 실행 가능한 실행파일로 컴파일...AI 활용해 C++ 코드로 변환하는 새로운 컴파일러 등장
Muna 창업자가 파이썬 코드를 수정 없이 크로스 플랫폼 실행파일로 변환하는 컴파일러 개발 과정 공개
[한국정보기술신문] 파이썬 코드를 수정 없이 다양한 플랫폼에서 실행 가능한 기계어 코드로 컴파일하는 새로운 접근법이 공개됐다. Muna의 창업자 유수프는 파이썬의 단순함을 유지하면서도 성능과 이식성 문제를 해결하는 컴파일러 개발 과정을 소개했다.
기존에도 파이썬을 다른 형태로 변환하려는 시도는 있었다. Jython이나 RustPython 같은 런타임, Numba와 PyTorch 같은 도메인 특화 언어, Mojo 같은 새로운 프로그래밍 언어 등이 그 예다. 하지만 이번에 소개된 컴파일러는 몇 가지 독특한 특징을 가지고 있다. 파이썬 코드를 전혀 수정하지 않고 완전히 사전 컴파일하며, 파이썬 인터프리터 없이 실행되고, C나 C++ 프로그램과 비슷한 최소한의 오버헤드로 작동한다. 무엇보다 서버, 데스크톱, 모바일, 웹 등 어디서나 실행 가능하다는 점이 핵심이다.
컨테이너는 AI 배포에 적합하지 않다
개발자는 2018년 AI 연구를 시작하며 부동산 이미지 편집 자동화 프로젝트를 진행했다. 딥러닝 모델을 훈련하고 테스트하는 것은 성공적이었지만, 이를 배포하는 과정에서 문제에 부딪혔다. 컨테이너를 사용하면 추론 코드, 모델 가중치, 파이썬 패키지 의존성, 파이썬 인터프리터 등을 모두 포함한 리눅스 운영체제 전체를 패키징해야 했다.
대신 AI 모델만 실행하는 독립 실행파일을 만들면 여러 이점이 있다. 불필요한 파이썬 패키지와 인터프리터를 제외해 훨씬 작은 크기로 더 빠르게 시작할 수 있다. 더 중요한 것은 리눅스 서버뿐 아니라 어디서든 실행할 수 있다는 점이다.
Unity의 IL2CPP에서 영감을 얻다
개발자는 2013년 Apple이 iPhone 5S에 arm64 아키텍처를 도입했을 때의 사례에서 영감을 받았다. 당시 Unity 엔진은 C# 스크립팅을 위해 Mono 가상머신을 사용했는데, Mono가 arm64를 지원하지 않아 문제가 발생했다.
Unity는 이 문제를 IL2CPP 컴파일러로 해결했다. C# 컴파일러가 생성한 중간 언어 바이트코드를 받아 동등한 C++ 소스 코드를 생성하는 방식이다. C++ 소스 코드가 있으면 Nvidia GPU부터 Apple Silicon까지 거의 모든 곳에서 컴파일할 수 있다. 이와 똑같은 것을 파이썬에 구현하기로 했다.
파이썬 컴파일러의 작동 원리
컴파일러는 크게 네 단계로 작동한다. 수정되지 않은 순수 파이썬 코드를 받아들이고, 추적해서 중간 표현 그래프를 생성한다. 중간 표현을 C++ 소스 코드로 낮추고, C++ 소스 코드를 다양한 플랫폼과 아키텍처에서 실행되도록 컴파일한다.
여기서 왜 중간 표현에서 바로 기계어 코드로 가지 않고 C++을 먼저 생성하는지 의문이 들 수 있다. 그 이유는 CUDA, MLX, TensorRT 같은 다양한 프레임워크와 라이브러리를 활용하기 위해서다. 특정 하드웨어에서 사용 가능한 여러 계산 방법을 최대한 활용할 수 있는 시스템을 설계하고자 했다.
심볼릭 트레이서와 타입 전파
첫 번째 단계는 심볼릭 트레이서를 구축하는 것이다. 트레이서는 파이썬 함수를 받아 함수 내 제어 흐름을 완전히 포착하는 중간 표현 그래프를 생성한다. 초기 프로토타입은 PyTorch FX 심볼릭 트레이서를 기반으로 했지만, 두 가지 문제가 있었다.
첫째, 함수를 추적하려면 실제로 실행해야 했는데, PyTorch와 달리 임의의 파이썬 함수에 대해서는 가짜 입력을 만들 수 없었다. 둘째, PyTorch 연산만 기록할 수 있어서 수백, 수천 개의 파이썬 라이브러리에 걸친 임의의 함수를 지원하려면 대대적인 수정이 필요했다. 결국 추상 구문 트리를 파싱하는 방식의 트레이서를 구축했다.
파이썬은 동적 언어라 변수가 어떤 타입이든 될 수 있지만, C++은 강타입 언어로 변수 선언 시 타입을 알아야 한다. 하지만 핵심 통찰이 있다. 특정 입력으로 파이썬 함수를 호출하면 함수 내 모든 중간 변수의 타입을 고유하게 결정할 수 있다는 것이다. 이를 위해 PEP 484의 타입 어노테이션을 활용했다.
AI를 활용한 C++ 연산자 라이브러리 구축
수만 개의 파이썬 함수에 대한 C++ 구현을 작성해야 한다는 문제가 있다. 하지만 대부분의 파이썬 함수는 더 작은 기본 함수들로 구성된다는 통찰이 있다. 코드의 다양성은 고유한 기본 함수의 수가 아니라 이들의 다양한 배열에서 나온다.
여전히 수천 개의 기본 함수를 다뤄야 하지만, 최신 대형 언어 모델이 이 문제를 쉽게 해결했다. AI 기반 코드 생성을 통해 생성된 코드를 제약하고, 정확성을 테스트하며, 의존성 관리와 조건부 컴파일 같은 부수적 로직을 처리하는 인프라를 구축했다. 지금까지 Numpy, OpenCV, PyTorch 같은 인기 라이브러리의 수백 개 파이썬 함수 구현을 AI로 생성했다.
철저한 검색을 통한 성능 최적화
성능 최적화에는 특별한 접근법을 사용한다. 하나의 C++ 연산자만 사용하는 것이 아니라 가능한 한 많이 작성하거나 생성한다. 시작부터 결과까지의 각 경로는 고유한 프로그램이며, 원래 파이썬 함수와 관련해 정확성이 보장된다.
각 C++ 연산자는 다른 알고리즘, 라이브러리, 심지어 하드웨어 가속기로 구동될 수 있다. 예를 들어 이미지 크기 조정 함수를 Apple Silicon용으로 컴파일할 때 선택할 수 있는 접근 방식과 라이브러리가 다양하다. 가능한 많은 구현을 생성한 다음, 각각을 사용하는 컴파일된 프로그램을 생성한다.
실제 테스트에서는 단일 파이썬 함수가 9개의 컴파일 대상에서 거의 200개의 고유한 프로그램으로 생성되는 것을 확인했다. 여기서 각 컴파일된 함수를 테스트해 특정 하드웨어에서 가장 빠르게 실행되는 것을 찾는다. 세밀한 텔레메트리 데이터를 수집하고 이를 사용해 통계 모델을 구축한다.
사용자 인터페이스 설계
가장 중요한 원칙은 인지 부하가 거의 없도록 설계하는 것이었다. 개발자가 컴파일러를 사용하기 위해 새로운 것을 배울 필요가 없어야 했다. PEP 318의 데코레이터를 선택했다. 개발자는 파이썬 함수에 @compile 데코레이터를 추가하기만 하면 컴파일 진입점을 지정할 수 있다.
데코레이터는 함수를 고유하게 식별하는 태그와 설명 외에도, 로컬 개발 환경을 재현하기 위한 샌드박스 설명과 코드 생성을 돕는 메타데이터를 제공할 수 있다. 컴파일 후에는 누구나 어디서든 컴파일된 함수를 실행할 수 있다.
향후 과제와 전망
컴파일러는 예외, 람다 표현식, 재귀 함수, 클래스 등 표준 파이썬 기능 중 일부가 누락되어 있다. 이들을 연결하는 것은 타입 전파 시스템이다. 타입 전파는 단순한 함수에서는 작동하지만 복합 타입과 고차 타입에는 추가 고려가 필요하다.
또 다른 중요한 과제는 디버깅 경험이다. 개발자의 파이썬 코드가 컴파일되면 예상대로 작동할 것을 보장하지만, 파이썬 코드를 어디서나 실행할 수 있게 하려면 매우 안전한 코드를 작성하는 방법과 예외 발생 시 세밀한 추적 데이터를 수집하는 방법을 찾아야 한다.
진화하는 C++ 표준은 큰 도움이 되고 있다. Muna는 C++20 없이는 존재할 수 없었으며, 코드 생성이 std::span, concepts, coroutines에 크게 의존한다. C++23의 광범위한 지원을 기다리고 있는데, 스트리밍 지원을 위한 std::generator, float16_t와 bfloat16_t 지원을 위한 stdfloat, 파이썬 예외 지원을 위한 stacktrace를 사용하기 때문이다.
한국정보기술신문 정보기술분과 강민규 기자 news@kitpa.org