Skip to content

RKNN Whisper 구현 실전 가이드

RKNN으로 Whisper를 구동할 때 가장 중요한 핵심은 "Encoder는 NPU로, Decoder는 CPU(혹은 최적화된 NPU)로 처리한다"는 전략입니다. Whisper의 구조적 특성상 전체를 NPU에 올리는 것은 비효율적이기 때문입니다.

기초 이론: Whisper 구조와 NPU 가속 전략

Whisper의 구조

Whisper는 Transformer 기반의 Encoder-Decoder 구조입니다.

  • Encoder (무거움): 오디오(Mel Spectrogram)를 입력받아 압축된 특징(Feature)을 추출합니다. 연산량이 많지만 구조가 고정적이라 NPU 가속에 매우 적합합니다.
  • Decoder (가벼움/복잡함): 텍스트를 생성합니다. 이전 토큰을 기반으로 다음 토큰을 예측하는 반복(Loop) 구조이며, KV Cache를 사용합니다. 동적 크기 때문에 CPU에서 처리하는 것이 일반적인 구현 방식입니다.

RKNN 전략

  • Audio Encoder: .rknn 모델로 변환하여 NPU에서 실행.
  • Text Decoder: whisper.cpp 같은 경량화된 C++ 라이브러리를 사용하여 CPU(Arm Core)에서 실행하거나, ONNX Runtime 등으로 CPU 구동.

환경 설정

PC (x86_64, Linux 추천) - 모델 변환용

RKNN 모델 변환은 보드가 아닌 성능 좋은 PC에서 수행해야 합니다.

  1. Anaconda/Miniconda 설치
  2. RKNN-Toolkit2 설치:
    bash
    git clone https://github.com/airockchip/rknn-toolkit2
    cd rknn-toolkit2/rknn-toolkit2
    pip install packages/rknn_toolkit2-*.whl
  3. PyTorch & OpenAI Whisper 설치:
    bash
    pip install torch openai-whisper onnx

보드 (RK3588 등) - 실행용

  1. RKNPU2 드라이버 확인: 최신 펌웨어 설치.
  2. rknn-toolkit2-lite (Python) 혹은 rknn-api (C++) 라이브러리 설치.

모델 변환 (Step-by-Step)

이 과정이 가장 중요합니다. PyTorch 모델에서 Encoder만 떼어내어 RKNN으로 만듭니다.

PyTorch ONNX (Encoder Export)

python
import torch
import whisper
import onnx

# 1. 모델 로드 (예: tiny, base, small)
model = whisper.load_model("base")
encoder = model.encoder
encoder.eval()

# 2. 더미 입력 생성 (Batch, Mel_bins, Time)
# Whisper base는 80 mel bins, 3000 frames (30초)
dummy_input = torch.randn(1, 80, 3000)

# 3. ONNX로 내보내기
torch.onnx.export(
    encoder,
    dummy_input,
    "whisper_encoder.onnx",
    opset_version=12,
    input_names=["mel"],
    output_names=["last_hidden_state"],
    dynamic_axes=None # NPU 고정 입력을 위해 dynamic axes 비활성화 권장
)
print("ONNX Export 완료!")

ONNX RKNN 변환

python
from rknn.api import RKNN

# 1. RKNN 객체 생성
rknn = RKNN()

# 2. 설정 (RK3588 기준)
rknn.config(mean_values=[[0]], std_values=[[1]], target_platform='rk3588')

# 3. ONNX 로드
print('--> Loading ONNX model')
ret = rknn.load_onnx(model='./whisper_encoder.onnx')
if ret != 0:
    print('Load model failed!')
    exit(ret)

# 4. 모델 빌드 (FP16이 NPU 성능/정확도 밸런스가 좋음)
print('--> Building model')
ret = rknn.build(do_quantization=False) # 혹은 True로 설정하여 i8 양자화 시도 가능
if ret != 0:
    print('Build model failed!')
    exit(ret)

# 5. RKNN 저장
print('--> Export rknn model')
ret = rknn.export_rknn('./whisper_encoder.rknn')
if ret != 0:
    print('Export rknn model failed!')
    exit(ret)

print("RKNN 변환 완료!")

추론 구현 (Board Side)

보드에서는 Python 또는 C++로 추론을 수행합니다. 여기서는 이해하기 쉬운 Python 예시를 듭니다.

Python 추론 코드 (간소화 버전)

python
import numpy as np
from rknn.api import RKNNLite # 보드에서는 Lite 버전 사용
import whisper
import torch

# 1. 오디오 전처리 (Whisper 라이브러리 활용)
audio = whisper.load_audio("test.wav")
audio = whisper.pad_or_trim(audio)
mel = whisper.log_mel_spectrogram(audio).numpy()
mel = np.expand_dims(mel, axis=0) # (1, 80, 3000)

# 2. RKNN 초기화 및 Encoder 실행
rknn_lite = RKNNLite()
rknn_lite.load_rknn('./whisper_encoder.rknn')
rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_0)

# NPU 추론 (Encoder)
outputs = rknn_lite.inference(inputs=[mel])
npu_features = torch.from_numpy(outputs[0]) # 결과: (1, 1500, 512) 등

# 3. Decoder 실행 (CPU)
# 원본 Whisper 모델의 디코더에 NPU가 계산한 feature를 주입
model = whisper.load_model("base")
options = whisper.DecodingOptions()
result = model.decode(npu_features, options)

print("결과:", result.text)

# 리소스 해제
rknn_lite.release()

추천 오픈소스 및 학습 자료

처음부터 모두 구현하기 어렵다면, 이미 잘 만들어진 프로젝트를 분석하는 것이 가장 빠른 학습법입니다.

rockchip-linux/rknn-toolkit2 (공식)

  • Rockchip 공식 예제 폴더(examples)에 Whisper가 포함되어 있는 경우가 있습니다. (버전에 따라 다름)

useful-rknn / whisper-rknn (추천)

Github에서 useful-transformers 혹은 rknn-whisper를 검색하면 나오는 프로젝트들은 대부분 Encoder(NPU) + Decoder(CPU) 방식을 사용합니다.

  • aishell: 중국 개발자 커뮤니티에서 Rockchip용 Whisper 포팅이 활발합니다.
  • monitor-lizard/rknn-whisper: 이와 유사한 프로젝트를 찾아보시면 C++ 기반의 고성능 구현체를 볼 수 있습니다.

whisper.cpp 활용

가장 현실적인 고성능 솔루션은 whisper.cpp 프로젝트가 RK3588의 NPU를 지원하는지 확인하는 것입니다. 최근 whisper.cpp는 다양한 백엔드를 지원하려 노력 중이며, 일부 포크(Fork) 버전에서 RKNN 백엔드를 실험적으로 지원합니다.

요약 (학습 포인트)

  1. 변환 대상 선정: Whisper 전체를 변환하려 하지 말고 Encoder만 변환하십시오.
  2. 데이터 타입: FP16 모드로 빌드하는 것이 정확도에 좋습니다. INT8 양자화는 소리 인식률을 크게 떨어뜨릴 수 있습니다.
  3. 하이브리드 파이프라인: Python에서 RKNNLite(Encoder)와 PyTorch(Decoder)를 섞어 쓰는 방식으로 프로토타입을 먼저 만드세요. 그 후 C++로 이식하여 속도를 최적화하는 순서를 추천합니다.