NNAPI를 넘어: Android AICore와 Gemini Nano가 온디바이스 AI를 혁신하는 방법
요약
Android의 엣지 AI 아키텍처가 NNAPI에서 AICore로 진화하는 과정을 분석합니다. Gemini Nano와 같은 온디바이스 LLM을 다양한 하드웨어 환경에서 효율적으로 실행하기 위한 기술적 해결책을 다룹니다.
핵심 포인트
- 클라우드 AI의 한계(지연 시간, 비용, 개인정보)를 극복하기 위한 온디바이스 AI의 중요성
- Android의 하드웨어 파편화 문제를 해결하기 위한 AICore 시스템 서비스의 역할
- NNAPI에서 AICore로 이어지는 하드웨어 추상화 계층의 진화 과정
- NPU 가속을 활용한 프로덕션 수준의 이미지 분류 파이프라인 구현 방법
모바일 개발의 지형이 거대하고 지각 변동에 가까운 변화를 겪고 있습니다. 수년 동안 "스마트" 모바일 애플리케이션은 단순히 씬 클라이언트 (thin clients)에 불과했습니다. 사용자 입력을 캡처하여 네트워크를 통해 거대한 클라우드 기반 API로 전송하고, 원격 GPU 클러스터가 추론 (inference)을 수행할 때까지 기다린 다음, 그 응답을 표시하는 방식이었습니다.
하지만 클라우드 의존적인 AI는 한계에 도달했습니다. 지연 시간 (latency) 병목 현상, 증가하는 서버 비용, 엄격한 데이터 개인정보 보호 규정 (GDPR 및 CCPA와 같은), 그리고 불안정한 오프라인 연결성이라는 단순한 현실은 다음과 같은 중요한 깨달음을 강요했습니다: AI의 미래는 온디바이스 (on-device)에 있다.
하지만 Android와 같이 매우 파편화된 생태계에서 복잡한 머신러닝 모델, 특히 Gemini Nano와 같은 대규모 언어 모델 (LLMs)을 실행하는 것은 엔지니어링 측면에서 악몽과 같습니다. Qualcomm, MediaTek, Google의 서로 다른 실리콘 칩을 사용하는 수천 개의 서로 다른 기기 전반에 걸쳐 어떻게 번개처럼 빠른 하드웨어 가속 AI 추론을 제공할 수 있을까요?
이 심층 분석에서는 Android의 엣지 AI (Edge AI) 아키텍처의 진화를 탐구할 것입니다. 기존의 **Neural Network API (NNAPI)**에서 현대적인 AICore 시스템 서비스로 이어지는 경로를 추적하고, NPU의 저수준 하드웨어 메커니즘을 해부하며, Kotlin Coroutines, Flow, Jetpack Compose를 사용하여 프로덕션 수준의 하드웨어 가속 이미지 분류 파이프라인을 작성해 보겠습니다.
실리콘 파편화 문제
Android에서의 엣지 AI에 대한 이론적 토대를 이해하려면, 먼저 **하드웨어 이질성 (hardware heterogeneity)**과 소프트웨어 안정성 (software stability) 사이의 근본적인 긴장 상태를 직시해야 합니다.
Android는 믿기지 않을 정도로 다양한 시스템 온 칩 (SoC) 구성 위에서 실행됩니다. 어떤 플래그십 기기는 Hexagon DSP를 탑재한 Qualcomm Snapdragon을 사용할 수 있고, 다른 기기는 커스텀 TPU를 특징으로 하는 Google Tensor 칩에서 실행될 수 있으며, 중급형 기기는 MediaTek Dimensity APU에 의존할 수도 있습니다.
[ Your Android App ]
│
▼ (이 모든 것들과 어떻게 대화할 것인가?)
...
만약 개발자들이 시장에 나와 있는 모든 신경망 처리 장치 (NPU)마다 기기별 어셈블리 (assembly), 드라이버 레벨 코드, 또는 커스텀 C++ 바인딩 (bindings)을 직접 작성해야 했다면, Android 개발 생태계는 그 복잡성을 견디지 못하고 붕괴했을 것입니다. 이것은 실리콘(silicon)에 직접 적용된 전형적인 "파편화 문제 (Fragmentation Problem)"입니다.
기존의 해결책: AI HAL로서의 NNAPI
역사적으로 Android는 **Neural Network API (NNAPI)**를 통해 이러한 파편화 문제를 해결해 왔습니다. Android 8.1에서 도입된 NNAPI는 AI를 위한 하드웨어 추상화 계층 (HAL, Hardware Abstraction Layer)으로 설계되었습니다.
Android 카메라 프레임워크가 물리적 센서가 Sony인지 Samsung 렌즈인지 알 필요 없이 takePicture()를 호출할 수 있게 해주는 것과 마찬가지로, NNAPI는 개발자가 연산 그래프 (computational graph, 컨볼루션 (convolutions), 풀링 (pooling), 활성화 함수 (activations)와 같은 일련의 수학적 연산)를 정의하면 OS가 하위 하드웨어에서 이를 어떻게 실행할지 협상하도록 허용했습니다.
내부적으로 NNAPI는 **델리게이트 모델 (delegate model)**로 작동했습니다. 애플리케이션은 자신의 APK 내에 자체 머신러닝 모델 (일반적으로 .tflite 파일)을 포함합니다. 런타임 (runtime) 시점에 앱은 이 모델을 TensorFlow Lite와 같은 런타임 엔진에 전달하며, 이 엔진은 NNAPI 델리게이트를 사용하여 모델의 연산을 사용 가능한 NPU 또는 GPU에 매핑함으로써 모델을 "가속 (accelerate)"합니다.
당시에는 혁신적이었지만, 이 모델에는 치명적인 결함이 있었습니다. 바로 **폴백 문제 (fallback problem)**입니다.
만약 모델이 기기의 특정 NPU 드라이버가 지원하지 않는 최신 또는 커스텀 연산 (예: 독특한 활성화 함수 또는 복잡한 트랜스포머 어텐션 메커니즘 (transformer attention mechanism))을 사용한다면, NNAPI는 조용히 CPU로 "폴백 (fall back)"하게 됩니다. 신경망의 CPU 실행은 믿을 수 없을 정도로 느리고 리소스 집약적이기 때문에, 이러한 갑작스러운 폴백은 엄청난 성능 저하, 급격한 배터리 소모, 그리고 UI 스레드에서의 심각한 "쟉 (jank, 프레임 드롭)"을 유발했습니다.
패러다임의 전환: 앱 번들 모델에서 AICore로
최신 파운데이션 모델 (Foundation Models)과 대규모 언어 모델 (LLMs)의 출시는 NNAPI를 한계점 너머로 몰아붙였습니다. 이로 인해 Google은 온디바이스 지능 (on-device intelligence)의 구조를 완전히 재설계해야 했으며, 앱 번들 모델 (App-Bundled Models) 방식에서 AICore를 통한 시스템 제공 모델 (System-Provided Models) 방식으로 전환하게 되었습니다.
이러한 변화를 Android 카메라 API의 진화 과정에 비유해 생각해 보십시오. Google은 원래 개발자가 복잡한 하드웨어 상태를 수동으로 관리해야 하는 로우 레벨 (low-level) API인 Camera2를 제공했습니다. 이후, 하드웨어의 복잡성을 추상화하고 개발자를 대신하여 이를 관리하는 라이프사이클 인식 (lifecycle-aware) 라이브러리인 CameraX를 도입했습니다. AICore는 온디바이스 AI의 CameraX입니다.
개발자가 앱의 APK 내부에 수 기가바이트에 달하는 거대한 모델을 포함하여 배포하도록 요구하는 대신, 이제 모델은 시스템 파티션 (system partition)에 직접 상주하며 운영체제에 의해 완전히 관리됩니다.
AICore를 추진한 세 가지 제약 사항
AICore와 Gemini Nano와 같은 시스템 제공 모델로의 전환은 세 가지 엄격한 엔지니어링 제약 사항에 의해 추진되었습니다.
1. 바이너리 크기 (Binary Size)
공격적인 양자화 (Quantization, 모델 가중치의 정밀도를 낮추는 과정)를 적용하더라도, Gemini Nano와 같이 고도로 최적화된 LLM은 종종 수 기가바이트에 달할 정도로 매우 큽니다. 이 정도 규모의 모델을 APK 내부에 번들링하는 것은 실행 불가능한 선택지입니다. 이는 다운로드 크기를 비대하게 만들고, Google Play Store의 제한을 초과하며, 사용자가 앱을 다운로드하는 것을 주저하게 만들 것입니다.
2. 메모리 압박과 저메모리 킬러 (Low Memory Killer, LMK)
만약 사용자의 기기에 있는 세 개의 서로 다른 앱(예: 메시징 앱, 메모 앱, 이메일 클라이언트)이 각각 자체적인 커스텀 LLM을 번들링하고 이를 동시에 메모리에 로드한다면, 시스템의 RAM은 완전히 고갈될 것입니다. 그러면 Android의 저메모리 킬러 (LMK)가 즉시 백그라운드 프로세스들을 종료하기 시작할 것이며, 이는 기기의 멀티태스킹 능력을 파괴할 것입니다.
AICore는 시스템 수준에서 싱글톤 모델 인스턴스 (Singleton Model Instance) 역할을 수행함으로써 이 문제를 해결합니다. OS는 Gemini Nano를 메모리에 단 한 번 로드합니다. 그러면 여러 애플리케이션이 보안 IPC (Inter-Process Communication, 프로세스 간 통신)를 통해 이 단일 공유 인스턴스와 인터페이스할 수 있으며, 이는 기기의 전반적인 메모리 점유율 (Memory Footprint)을 획기적으로 줄여줍니다.
3. 업데이트 속도 (Update Velocity)
인공지능 분야는 눈부신 속도로 움직입니다. 모델은 매주 정교해지고, 재학습되며, 최적화됩니다. 만약 모델이 APK 내부에 번들로 포함되어 있다면, 이를 업데이트하기 위해서는 전체 애플리케이션을 빌드, 테스트하고 Play Store에 새로운 업데이트를 배포해야 합니다.
AICore를 통해 Google은 모델을 애플리케이션 계층으로부터 분리(Decoupling)합니다. 시스템 수준의 Gemini Nano 모델은 Google Play 시스템 업데이트를 통해 백그라운드에서 조용히 업데이트될 수 있습니다. 여러분의 앱은 단 한 줄의 코드도 변경하거나 재배포할 필요 없이, 자동으로 더 스마트하고 빠르며 정확한 모델에 접근할 수 있게 됩니다.
내부 구조: 하드웨어 라우팅 및 메모리 브릿지 (Hardware Routing and Memory Bridges)
에지 AI (Edge AI)를 진정으로 마스터하려면, 고수준 API 너머를 들여다보고 텐서 (Tensor)가 Kotlin 메모리에서 NPU의 실리콘으로 이동할 때 물리적으로 어떤 일이 발생하는지 이해해야 합니다.
메모리 브릿지: 직접 바이트 버퍼 (Direct ByteBuffers)
Kotlin 객체는 JVM 힙 (JVM Heap) 내부에서 안정적으로 존재합니다. 하지만 NPU는 JVM 힙에 접근할 수 없습니다.
그 이유는 무엇일까요? JVM 가비지 컬렉터 (GC, Garbage Collector)는 동적이기 때문입니다. GC는 메모리 단편화를 방지하기 위해 물리적 RAM 내에서 객체들을 끊임없이 이동시킵니다. 만약 NPU가 수백만 개의 부동 소수점 (Float) 값을 포함하는 텐서를 읽는 도중에, JVM GC가 갑자기 앱을 일시 중단시키고 해당 텐서를 다른 메모리 주소로 이동시킨다면, NPU는 손상된 데이터를 읽거나 시스템 수준의 세그멘테이션 폴트 (Segmentation Fault, 충돌)를 일으키게 됩니다.
이러한 제한을 우회하기 위해 Android 개발자는 반드시 **직접 바이트 버퍼 (Direct ByteBuffers)**를 사용해야 합니다.
JVM Heap (GC Active) Native Memory (Pinned)
┌──────────────────────┐ ┌──────────────────────┐
│ Kotlin Objects │ │ Direct ByteBuffer │ ◄─── DMA (Direct Memory Access)
...
Direct ByteBuffer는 네이티브(C/C++) 메모리에 할당되며, JVM 가비지 컬렉터가 접근할 수 없는 영역에 완전히 존재합니다. NNAPI나 AICore로 데이터를 전달할 때, 시스템은 NPU가 **DMA (Direct Memory Access)**를 통해 물리적 RAM에서 데이터를 직접 읽을 수 있도록 메모리 맵(mmap)을 생성합니다. 이는 JVM과 네이티브 메모리 계층 간 데이터 복사 오버헤드를 제거합니다.
양자화(Quantization): 왜 INT8이 엣지 디바이스의 표준인가
대부분의 최신 AI 모델은 클라우드에서 FP32 (32비트 부동 소수점) 또는 FP16 정밀도를 사용하여 학습됩니다. 부동 소수점 연산은 학습 중 극도의 정밀도를 허용하지만, 이를 모바일 장치에서 실행하는 것은 매우 비효율적입니다.
32비트 부동 소수점 두 숫자를 곱하려면 막대한 수의 실리콘 트랜지스터가 필요하고 상당한 전력을 소비합니다. 반면, NPU는 INT8 (8비트 정수) 행렬 곱셈(GEMM 연산)에 특화된 고도로 전문화된 장치입니다.
모델을
현대적인 Kotlin 기능—구체적으로 Coroutines (코루틴), Flows (플로우), Context Receivers (컨텍스트 수신자), 그리고 Serialization (직렬화)—을 활용하여 안전하고 반응형인 AI 래퍼(wrapper)를 구축하는 방법을 살펴보겠습니다.
1. Coroutines와 Flow를 이용한 비동기 추론 (Asynchronous Inference)
Gemini Nano와 같은 생성형 모델 (generative models)을 다룰 때, 사용자에게 보여주기 전에 전체 응답이 생성될 때까지 기다리는 것은 좋지 않은 사용자 경험을 초래합니다. 대신, 우리는 동적인 "타이핑" 효과를 만들기 위해 토큰을 실시간으로 스트리밍(stream)하기를 원합니다.
우리는 Kotlin의 Flow를 사용하여 이러한 토큰을 비동기적으로 스트리밍하며, 연산이 Dispatchers.Default에 바인딩되도록 보장합니다 (Dispatchers.Default는 네트워크/디스크 차단 작업용인 Dispatchers.IO 대신, 무거운 CPU/연산 작업에 최적화된 스레드 풀을 기반으로 합니다).
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
...
2. 컴파일 타임 하드웨어 안전성을 위한 Context Receivers
복잡한 AI 애플리케이션에서는 특정 작업이 유효한 하드웨어 세션(hardware session)이 활성화되었을 때만 실행되도록 보장해야 할 때가 많습니다. 수십 개의 중첩된 함수를 통해 ModelSession 또는 AIContext 파라미터를 전달하는 것은 지루하고 오류가 발생하기 쉽습니다.
Kotlin의 Context Receivers (Kotlin 2.x에서 완전히 지원됨)를 사용하면 함수에 필요한 스코프(scope)를 정의할 수 있습니다. 이를 통해 고급 추론 함수가 활성화된 하드웨어 가속 컨텍스트(hardware-accelerated context) 내에서만 호출될 수 있음을 컴파일 타임에 보장할 수 있습니다.
interface AIContext {
val sessionToken: String
val hardwareAccelerator: AcceleratorType
...
3. Kotlin Serialization을 이용한 타입 안전(Type-Safe) 설정
AI 모델은 엄격한 설정 파라미터(예: temperature, top-k, top-p)를 필요로 합니다. kotlinx.serialization을 사용하면 이러한 설정을 타입 안전한 방식으로 정의할 수 있으며, 이는 Jetpack DataStore에 쉽게 저장하거나, 캐싱하거나, Binder IPC 인터페이스를 통해 AICore로 전달할 수 있습니다.
import kotlinx.serialization.Serializable
@Serializable
...
실습: 하드웨어 가속 분류 파이프라인 구축하기
이러한 개념들을 실제 프로덕션 환경에서 사용할 수 있는 구현에 적용해 보겠습니다. MobileNet V2 모델을 로드하고, NNAPI 델리게이트 (delegate)를 통해 하드웨어 가속을 설정하며, 그 결과를 Jetpack Compose UI에 반응형으로 표시하는 이미지 분류 파이프라인을 구축할 것입니다.
1단계: 의존성 설정
먼저, build.gradle.kts 파일에 필요한 TensorFlow Lite 및 하드웨어 델리게이트 (delegate) 의존성을 추가합니다:
dependencies {
// 핵심 TensorFlow Lite 런타임 (runtime)
implementation("org.tensorflow:tensorflow-lite:2.14.0")
...
2단계: 하드웨어 가속 리포지토리 (Repository) 생성
이 클래스는 RAM 팽창을 방지하기 위해 메모리 매핑 (mmap)을 사용하여 모델 파일을 로드하고, NPU 가속을 위해 NnApiDelegate를 구성하며, 하드웨어 메모리 누수를 방지하기 위해 안전한 정리 (cleanup) 작업을 관리하는 중요한 작업을 수행합니다.
package com.edgeai.performance.data
import android.content.Context
...
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기