RK3588에서 Anima 이미지 생성하기: ROCK 5C 8GB의 RKNN 변환과 실행 결과

ROCK 5C의 28블록 전체 DiT forward 한 번에서 분기별 NPU 추론 시간 합계. 1: self-attention, 2: cross-attention, 3: FFN. 단일 블록 시간이 아니다.

제휴 링크 안내: 이 글에는 알리익스프레스 제휴 링크가 포함되어 있으며, 링크를 통해 구매하면 작성자가 수수료를 받습니다.

1. ROCK 5C 8GB에서 Anima 실행하기

8GB 메모리를 탑재한 단일 보드 컴퓨터(Single Board Computer, SBC)인 Radxa ROCK 5C 8GB 환경에서 수십억 파라미터 규모의 Diffusion Transformer(DiT) 모델 온디바이스 구동을 검증했다.

보드 메인 프로세서는 Rockchip RK3588S2 SoC이며, Rockchip 전용 NPU 런타임 타깃은 rk3588이다. 동일 제조사의 Radxa ROCK 5C 상품군 중 Lite 모델인 RK3582 옵션은 NPU 유닛 및 CPU 코어 구성이 축소된 별개 칩셋이다. 따라서 8GB DiT 연산 파이프라인 재현을 위해서는 반드시 RK3588S2 기반의 ROCK 5C 8GB 모델을 확인해야 한다.

Anima의 이미지 생성을 담당하는 DiT는 28개 블록으로 구성된다. 이번 실험에서는 이를 ROCK 5C의 NPU에서 실행할 수 있는 형태로 나누어 변환했다.

온디바이스 변환의 고정 입출력 사양은 다음과 같다: – 배치 크기: 1 – 생성 해상도: 512×512 픽셀 – 입력 토큰 규격: 이미지 토큰 1024개, 텍스트 컨디셔닝 토큰 512개 – ONNX 타깃 버전: Opset 19

텍스트 인코딩에는 Qwen3, 이미지 복원에는 Qwen-Image VAE를 사용했다. DiT의 주요 연산은 NPU가 처리하고, 텍스트 인코딩과 VAE 디코딩은 CPU가 담당하는 구성이다.


2. DiT를 84개 RKNN 모델로 나눈 이유

DiT 아키텍처의 28개 블록 전체를 단일 거대 RKNN 모델로 패키징하는 방식은 RKNN 컴파일러의 구조적 한계에 부딪혔다. 2026년 8월 실험 당시 사용된 RKNN-Toolkit2 2.3.2 환경에서 DiT 블록 전체를 컴파일하거나 AdaLN(Adaptive Layer Normalization) 연산이 결합된 브랜치를 빌드할 때 네이티브 세그폴트(native segfault, exit 139)가 발생했다.

디버깅을 위해 그래프를 최소 단위로 분해한 결과, 대용량 Linear 레이어나 표준 GELU FFN 그래프는 NPU 타깃으로 정상 빌드되었으나, AdaLN LayerNorm + broadcast shift/scale/gate 패턴이 포함된 서브그래프는 가중치 크기와 무관하게 동일한 세그폴트를 일으켰다. 메모리 부족 문제가 아니라 컴파일러의 특정 그래프 연산자 패턴 해석 오류임이 확인되었다.

각 DiT 블록에서 NPU로 실행할 수 있는 3개 연산 분기를 분리하고, 정규화·게이트·잔차 합산은 CPU에서 처리했다.

28개 DiT 블록을 각각 self-attention, cross-attention, FFN의 3개 서브그래프로 나누었다. self-attention은 이미지 토큰 사이의 관계를, cross-attention은 텍스트 조건과 이미지 토큰의 관계를 계산한다. FFN은 각 토큰의 특징을 변환한다.

파란 CPU 단계와 주황 NPU 단계를 번갈아 실행하는 DiT 블록 구조. CPU는 각 분기의 AdaLN과 게이트 곱셈·FP32 잔차 누적을, NPU는 FP16 self-attention·cross-attention·FFN을 처리한다.
DiT 블록 하나의 CPU·NPU 실행 순서. NPU는 FP16 attention·FFN을, CPU는 각 분기 앞의 AdaLN과 분기 뒤의 게이트 곱셈·FP32 잔차 누적을 처리한다. 이 구조가 28개 블록에서 반복된다.

총 84개(28 × 3)의 FP16 RKNN 모델이 만들어졌으며, 크기 합계는 약 3.99GB다. AdaLN, 게이트 곱셈, RoPE, 입력·타임스텝 임베딩과 최종 프로젝션은 CPU에서 처리했다. 잔차 누적은 FP32로 유지했다.

  • 원본 모델에서 CPU가 처리할 연산과 NPU가 처리할 연산을 분리한다.
  • NPU용 연산을 고정 입출력 형상의 ONNX 그래프로 만든 뒤 RKNN으로 변환한다.
  • 단일 블록과 전체 블록의 출력을 원본과 비교한 다음 이미지 생성을 실행한다.

3. 변환 전후 출력은 얼마나 비슷한가

하이브리드 분할 변환에서 가장 중요한 단계는 프레임워크 간 수치 오차의 누적 방지다. 단일 블록 검증과 28개 전체 블록의 순차 누적 검증을 나누어 수행했다.

단일 블록 비교

원본 PyTorch 블록의 FP16 분기 연산과 FP32 잔차 누적을 기준으로 ONNX 및 RKNN 출력을 비교했다. 변환한 연산이 원래 모델과 같은 방향의 출력을 내는지 확인하는 과정이다.

비교 대상 코사인 유사도 (Cosine) 평균 절대 오차 (MAE) 최대 절대 오차 (Max AE) NaN / Inf 발생
PyTorch → ONNX 0.9999994183 0.00012350 0.09396 0 / 0
PyTorch → RKNN 0.9999990896 0.00019535 0.11446 0 / 0
ONNX → RKNN 0.9999995474 0.00014175 0.06969 0 / 0

PyTorch 기준 출력과 RKNN 출력의 코사인 유사도는 0.9999990896이었다. 단일 블록에서는 변환 전후 출력이 매우 비슷한 방향을 유지했다.

28개 블록을 통과한 출력 비교

Block 0부터 Block 27까지 28개 블록 전체를 통과하는 동안 수치 오차가 증폭되지 않는지 확인하기 위해 순수 ONNX 누적 텐서와 RKNN 84코어 누적 텐서를 비교했다.

  • 개별 블록 최저 코사인 유사도: 0.999993668 (전체 28개 블록 중 최저치)
  • 최종 히든 레이어 유사도 (Hidden Cosine): 0.9999989412
  • 최종 16채널 예측 텐서 유사도 (Final Prediction Cosine): 0.9999989072
  • 최종 예측 텐서 MAE: 0.00129712
  • 최종 예측 텐서 Max AE: 0.00830078
  • 경계 구간 NaN / Inf 발생 건수: 0건

28개 블록을 통과한 뒤에도 NaN이나 Inf는 발생하지 않았고, 기준 출력과 높은 유사도를 유지했다. 다만 이 수치 비교와 최종 생성 이미지의 화질 평가는 구분해야 한다.


4. 디노이징에는 얼마나 걸렸나

28블록 전체 forward의 분기별 순수 NPU 시간

RK3588 NPU에서 28개 DiT 블록 전체를 통과하는 forward 한 번의 순수 추론 시간 합계는 약 38.90초다. 아래 분기별 값은 각각 28개 블록의 실행 시간을 합산한 값이다.

각 코어별 세부 소요 시간은 다음과 같다: – Self-Attention Core: 12.99초 – Cross-Attention Core: 3.92초 – FFN Core: 21.99초

ROCK 5C의 28블록 전체 DiT forward 한 번에서 분기별 NPU 추론 시간 합계. 1: self-attention, 2: cross-attention, 3: FFN. 단일 블록 시간이 아니다.
ROCK 5C의 28블록 전체 DiT forward 한 번에서 분기별 NPU 추론 시간 합계. 1: self-attention, 2: cross-attention, 3: FFN. 단일 블록 시간이 아니다.

파라미터 비중이 큰 FFN 코어가 전체 순수 NPU 추론 시간의 약 56.5%를 차지한다. 참고로 ONNX 수치 검증 로직이 동반된 28블록 전체 테스트의 전체 실행 시간(Wall Time)은 162.8초였으며, 이는 순수 NPU 연산 시간(38.90초)과 구분된다.

20스텝 순차 CFG 디노이징 결과

실제 이미지 생성 과정인 20스텝 순차 분류기 없는 가이던스(Sequential Classifier-Free Guidance, CFG, guidance scale 4.0) 디노이징을 보드에서 직접 수행했다. 조건부(Positive) 및 무조건부(Negative) 예측을 단일 배치로 묶지 않고 메모리 한계를 감안하여 순차 호출 방식으로 처리했다.

  • 20스텝 디노이징: 2,408.54초
  • 스텝당 평균: 120.40초
  • 스텝당 순수 NPU 연산: 76.21초
  • 스텝당 모델 준비·호스트 처리 등: 약 44.19초
  • 20스텝 전체 구간 NaN/Inf: 0건

스텝당 약 44.19초의 비-NPU 시간에는 84개 RKNN 모델의 로드·초기화·해제(load/init/release)뿐 아니라 호스트 연산과 경계 처리 비용도 포함된다. 후속 최적화에서는 이 비용을 나누어 계측해야 한다.


5. VAE를 바꿔 이미지 복원 시간 줄이기

디노이징이 완료된 잠재 변수(Latent)를 512×512 픽셀 RGB 이미지로 복원하는 VAE 디코딩 과정은 CPU 연산 부담이 큰 단계다. 공식 Diffusers의 Qwen-Image VAE FP32 CPU 구현을 그대로 사용했을 때의 디코딩 시간은 2,154.51초(약 35분 54초)에 달해, 20스텝 DiT 디노이징 전체 시간과 맞먹는 심각한 병목을 유발했다.

VAE 디코딩에는 stable-diffusion.cpp의 ARM NEON·OpenMP 최적화 CPU 구현을 적용했다. 텍스트 인코더와 트랜스포머를 다시 초기화하지 않고, 디노이징이 끝난 잠재 텐서만 받아 이미지로 복원하도록 연결했다.

CPU 4스레드, 타일 크기 32×32, 오버랩 0.5 설정으로 측정한 VAE 백엔드 비교 결과는 다음과 같다:

평가 지표 공식 Diffusers FP32 VAE sd-cpp 타일형 CPU VAE 개선폭
디코딩 소요 시간 2,154.51초 155.04초 13.90배 단축 (1,999.47초 절감)
PSNR (Diffusers 출력 대비) 기준 출력 31.444 dB 참조 출력과 비교
11×11 Gaussian SSIM 1.00000 0.96109 참조 출력과 비교
출력 해상도 및 규격 512×512 RGB 512×512 RGB NaN / Inf 0건

육안 검토에서는 구성·색·세부 표현이 유지된 것으로 평가했다. PSNR과 SSIM은 두 출력이 완전히 동일하다는 뜻은 아니다. 디코딩 시간은 2분 35초 수준으로 단축했다.

디노이징과 이미지 복원 시간 합계

VAE 가속을 통합한 전체 이미지 생성 시간은 다음과 같다: – DiT 20스텝 순차 CFG 디노이징: 2,408.54초 – sd-cpp 타일형 VAE 디코딩: 155.04초 – 총 생성 시간 (프롬프트 1회 인코딩 제외): 2,563.58초 (약 42분 44초)


6. 다음 병목은 디노이징

VAE 디코딩 시간을 2,154.51초에서 155.04초로 단축함에 따라, 전체 파이프라인에서 VAE가 차지하는 비중은 기존 47.2%에서 6.0%로 대폭 축소되었다. 이에 따라 전체 시스템의 지배적 병목은 DiT 20스텝 디노이징(2,408.54초, 전체의 94.0%)으로 전환되었다.

DiT 디노이징 내부를 분해하면, 순수 NPU 연산에 소요되는 시간은 스텝당 76.21초(20스텝 합계 1,524.20초)이며, 나머지 약 884.34초는 모델 수명주기와 호스트 처리 등을 포함한 비-NPU 시간이다. 컨텍스트 상주는 반복 초기화 비용을 줄이기 위한 후보였지만, 8GB 환경에서는 메모리 여유와 실제 절감량을 별도로 검증해야 했다. 그 실험 결과는 3편에서 다룬다.

이후 최적화에서는 연산 횟수와 메모리 사용량을 함께 살펴봤다. 2편은 Spectrum으로 계산 스텝을 줄인 결과, 3편은 양자화와 모델 상주가 화질·메모리 제한에 부딪힌 결과를 다룬다. 4편에서는 컴파일러 버전 비교와 그래프 융합의 손익을 살펴본다.

Comments

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다