TornadoVM 7, 기존 Java 앱을 GPU에서 그대로 돌릴 수 있을까?

TornadoVM 7을 의존성에 넣고 기존 Java 앱을 실행했는데, GPU는 놀고 있다. 어떤 메서드를 어떻게 골라야 계산이 GPU로 넘어갈까?

TornadoVM 7, 기존 Java 앱을 GPU에서 그대로 돌릴 수 있을까?
세 줄 요약
  • TornadoVM은 개발자가 고른 Java 계산을 GPU로 보내며, 앱 전체를 자동으로 병렬화하지 않는다.
  • 지원되는 데이터 표현과 병렬화 방식, 입력·출력 전송, 실행 계획을 구성해야 한다.
  • Java로 작성한 계산과 cuBLAS·Apple MLX 호출을 함께 쓸 수 있지만 하드웨어 조건은 다르다.

기존 JVM 안에서 필요한 계산을 GPU로 보낸다

TornadoVM을 추가해도 기존 Java 앱 전체가 GPU에서 실행되지는 않는다. 공식 프로그래밍 가이드는 Java 메서드를 자동으로 병렬화하지 않는다고 명시한다. 개발자가 병렬 계산 방식을 표현하고 GPU에 보낼 메서드를 선택해야 한다.

예를 들어 행렬 곱셈은 서로 다른 결과 원소를 계산하는 바깥쪽 반복문을 병렬화할 수 있다. TornadoVM은 이렇게 지정한 계산을 장치에 맞게 컴파일하고 실행한다. 웹 요청 처리나 다른 업무 코드를 포함한 프로그램 전체를 통째로 바꾸는 방식과는 다르다.

현재 프로젝트가 안내하는 버전은 7.3.1이며 JDK 21~27을 지원한다. 10월 변경 이력에는 Metal에서 Apple MLX 라이브러리 작업을 실행하는 기능 등이 추가됐다. Java에서 GPU를 쓰는 선택지가 늘었지만, 실행할 계산을 구분하는 작업은 여전히 필요하다.

데이터·병렬화·작업 그래프·실행 계획을 구성한다

도입 작업은 크게 네 부분으로 나뉜다. FloatArray 같은 데이터 표현을 고르고, 계산의 병렬성을 지정하고, TaskGraph에 메서드와 전송을 등록한 뒤 TornadoExecutionPlan으로 실행한다.

결정할 것역할
데이터 표현런타임이 메모리 배치를 아는 배열·행렬 등의 형태로 계산 데이터를 준비한다.
병렬화 방식@Parallel로 루프를 표시하거나 KernelContext·TileContext로 작업을 표현한다.
TaskGraph가속할 메서드와 장치에 넣고 꺼낼 데이터를 지정한다.
ExecutionPlan그래프를 실행하고 장치 선택·워밍업·프로파일링을 설정한다.

그래프를 만들었다고 바로 GPU 계산이 시작되지는 않는다. 실행 계획의 execute()를 호출할 때 지정한 작업이 수행된다. 라이브러리 의존성 추가와 실제 가속 경로 연결을 구분해야 한다.

Java 문법을 전부 GPU에 가져갈 수는 없다

7.3.1의 지원 제한 문서에는 재귀 미지원과 동적 메모리 할당 제약이 남아 있다. GPU에서 실행할 계산이 자유롭게 객체를 만들고 탐색하는 일반적인 Java 코드와 같은 조건으로 작동한다고 가정하면 안 된다.

객체를 전혀 못 쓴다는 뜻도 아니다. TornadoVM은 자체 배열·벡터·행렬처럼 메모리 배치를 알고 있는 자료형을 지원한다. 동적 할당에도 런타임에 크기를 알아낼 수 있는 경우의 예외가 있다. 기존 업무 객체를 그대로 넘기기보다 계산에 필요한 데이터를 지원되는 형태로 표현할 수 있는지가 도입 판단의 출발점이다.

계산을 직접 쓰거나, 이미 최적화된 라이브러리를 연결한다

Hybrid API를 쓰면 Java에서 생성한 GPU 커널과 네이티브 라이브러리 호출을 하나의 TaskGraph에 넣을 수 있다. 예를 들어 전처리는 Java 커널로 하고 행렬 연산은 cuBLAS에 맡긴 뒤, 다시 Java 커널로 결과를 후처리하는 구성이 가능하다.

이 작업들은 TornadoVM이 관리하는 장치 버퍼를 공유한다. GPU에서 만든 중간 결과를 라이브러리에 넘길 때마다 CPU로 가져왔다가 다시 보낼 필요가 줄어든다.

라이브러리의 실행 조건은 따라온다. NVIDIA용 제공자는 CUDA 백엔드와 NVIDIA GPU가 필요하고, Apple MLX 제공자는 Apple silicon의 Metal 백엔드가 필요하다. 지원하지 않는 백엔드에서 해당 라이브러리 작업을 실행하면 UNSUPPORTED로 보고된다. Java로 호출한다는 이유만으로 cuBLAS가 모든 GPU에서 실행되지는 않는다.

계산 시간과 데이터 이동 시간을 함께 잰다

입력값이 매번 달라지는 계산은 전송 설정도 그에 맞아야 한다. 공식 데이터 전송 설명의 EVERY_EXECUTION은 실행할 때마다 입력을 보내고, FIRST_EXECUTION은 처음 한 번 보낸다. 반복 실행 중 호스트에서 입력을 고쳤다면 처음 전송한 값만 계속 쓰는 구성으로는 그 변경이 전달되지 않는다.

GPU 내부 계산이 빨라져도 전체 작업의 개선 폭은 작을 수 있다. TornadoVM 팀의 6.0 설명에서도 같은 행렬 계산 사례의 커널 시간과 호스트로 결과를 복사하는 시간을 포함한 소요시간을 구분한다. 프로그램이 결과를 돌려받는 데 걸린 시간까지 봐야 도입 효과를 판단할 수 있다.

도입을 검토하는 팀은 먼저 병목인 계산 하나를 고르고, 원래 JVM 실행과 GPU 실행의 결과 및 전체 시간을 비교할 수 있다. 선택 기준은 Java 코드의 양보다 병렬화할 계산의 비중, 지원되는 데이터 형태, CPU와 GPU 사이에 오갈 데이터의 양이다.