
Gemma 4를 스마트폰에서 완전 오프라인으로 동작시키는 VLM 앱을 만들어 본 이야기
요약
Google의 Gemma 4를 활용하여 Android 단말기에서 완전히 오프라인으로 동작하는 멀티모달(VLM) 앱 구현 과정을 소개합니다. 프라이버시 보호와 비용 절감을 위해 양자화된 모델을 온디바이스 환경에 최적화하여 구축했습니다.
핵심 포인트
- Gemma 4 E2B 양자화 모델을 통한 온디바이스 추론 구현
- 텍스트, 이미지(OCR), 음성 처리가 가능한 멀티모달 기능 제공
- 네트워크 연결 없이 동작하여 프라이버시 및 레이턴시 문제 해결
- Android 환경에서 Expo 및 Node.js를 이용한 개발 가이드
클라우드 LLM (Large Language Model)은 편리하지만, 통신이 전제 조건입니다. 전파가 닿지 않는 곳이나 데이터를 외부로 유출하고 싶지 않은 상황에서는 사용할 수 없습니다.
그래서 Google의 Gemma 4를 Android 단말기 내부에서만 구동하는, 텍스트·이미지·음성에 대응하는 앱을 만들었습니다.
모델 다운로드를 제외하면, 추론 시의 통신은 일절 없습니다. 완전히 오프라인으로 동작합니다.
본 기사에서는 만든 것과 사용법, 그리고 구현 과정에서 막혔던 점을 중심으로 정리합니다.
본 앱은 개인이 개발한
비공식 프로젝트이며, Google과는 일절 관계가 없습니다 (제휴·후원·승인 없음).
"Gemma"는 Google LLC의 상표입니다. 본 기사에서는 모델을 지칭하는 설명적인 용도로만 사용하고 있습니다.
Gemma 4 모델은 Apache License 2.0으로 배포되고 있습니다.
클라우드 API가 아니라, 굳이 단말기 내부에서 추론하는 이점은 다음과 같습니다.
프라이버시 (Privacy): 입력한 텍스트·이미지·음성이 단말기 외부로 나가지 않음 -
오프라인 동작 (Offline Operation): 네트워크가 없어도 추론 가능 -
API 과금 없음: 이용 횟수에 따른 비용이 발생하지 않음 -
레이턴시 (Latency): 서버와의 왕복이 없음
그 대신, 어떤 모델이든 단말기 내에서 동작하는 것은 아닙니다. 클라우드를 전제로 하는 대규모 모델을 로컬 디바이스 등에서 처리하는 것은 어렵습니다. 따라서 단말기의 리소스로 구동할 수 있도록 경량화·양자화 (Quantization)된 모델이 필요합니다.
이번에는 모바일용으로 양자화된 Gemma 4 E2B (QAT 모바일 양자화 버전·약 2.5GB)를 선택했습니다.
참고로, 모델 전반의 성능 비교 방법이나 리더보드 (Leaderboard) 읽는 법에 대해서는 별도 기사 「AI 리더보드 가이드 — 용도별 추천 사이트 정리」에 정리해 두었습니다 (이것은 클라우드를 포함한 일반적인 모델에 관한 이야기입니다).
온디바이스 LLM (On-device LLM)을 실제로 만져보고 싶었던 점, 그리고 이미지나 음성까지 포함한 멀티모달 (Multimodal) 처리를 단말기 내에서 완결시키고 싶었던 것이 동기입니다.
할 수 있는 기능은 다음 4가지입니다.
| 기능 | 내용 |
|---|---|
| 텍스트 채팅 | 멀티턴 (Multi-turn) 대화. 토큰을 순차적으로 표시하는 스트리밍 출력 |
| ... |
필요한 것은 Android 실기기 (USB 디버깅 활성화), Node.js 22 이상, Android Studio입니다.
git clone https://github.com/kaze-uta/unofficial-gemma-vlm-android.git
cd unofficial-gemma-vlm-android
npm install
npx expo run:android
최초 실행 시, 앱이 Gemma 4 E2B (약 2.5GB)를 앱 전용 영역으로 자동 다운로드합니다. 진행 바가 표시되며, 중단 후 재개에도 대응하고 있습니다 (Wi-Fi 권장).
네트워크 문제 등으로 자동 다운로드에 실패한 경우에는, HuggingFace에서 수동으로 취득하여 adb push로 배치하는 절차도 마련되어 있습니다 (상세 내용은 README를 참조).
채팅: 메시지를 보내면 답변이 스트리밍으로 표시됩니다. "지금 몇 시야?", "3.5와 12와 7을 모두 곱해줘"와 같이 입력하면, 모델이 필요에 따라 내장 함수를 자동 실행하고 그 결과를 바탕으로 대답합니다. -
이미지 해석 / OCR: "카메라" 또는 "갤러리"를 선택하고, 질문을 입력하여 해석합니다. "OCR 문자 추출" 칩을 누르면 텍스트 변환을 위한 지시가 입력됩니다. -
음성 입력: "녹음 시작"으로 말하고, "정지 후 해석"을 누르면 처리됩니다. "요약", "텍text 변환", "영작" 등의 칩으로 지시를 전환할 수 있습니다.
| 항목 | 내용 |
|---|---|
| 프레임워크 | React Native 0.84 + Expo 55 |
| ... |
구성 방식은 다음 3계층입니다.
JS 계층 (React Native UI): 화면과 사용자 조작. App.tsx가 채팅·이미지·음성의 3가지 모드를 가짐 -
네이티브 브릿지 (Native Bridge): JS로부터의 호출을 받아 추론 엔진을 조작. 결과는 이벤트로서 JS로 스트리밍하여 반환하는 GemmaModule.kt -
추론 엔진 (Inference Engine, LiteRT-LM): 모델 로딩과 추론을 담당
처음에는 MediaPipe의 tasks-genai로 추론하는 구성이었으나, 이것이 비권장(deprecated)되었기 때문에 공식적으로 후계 모델로 권장하는 LiteRT-LM 0.13.1로 이행했습니다.
모델 형식도 .task에서 .litertlm으로 변경됩니다. API는 Engine / Conversation을 중심으로 구성되어 있습니다.
GPU 백엔드(Backend)는 빠르다는 장점이 있는 반면, RAM이 적은 단말기에서는 초기화에 실패할 수 있습니다. 그래서 먼저 GPU 구성으로 초기화를 시도하고, 예외가 발생하면 CPU 구성으로 재초기화하는 방식으로 구현했습니다.
음성 처리(Audio processing)는 CPU가 더 안정적이기 때문에, GPU 구성이라 하더라도 음성 백엔드만큼은 CPU를 지정하고 있습니다.
val created = try {
val gpuConfig = EngineConfig(
modelPath = resolvedPath,
...
LiteRT-LM의 음성 디코더(miniaudio)는 WAV / MP3 / FLAC만 지원합니다. 반면, expo-audio의 녹음은 AAC(.m4a)로 출력되기 때문에 그대로 전달하면 읽을 수 없었습니다.
대응책으로, 녹음 파일을 MediaExtractor와 MediaCodec을 사용하여 PCM16으로 디코딩하고, WAV 헤더를 직접 붙여서 내보낸 뒤 추론 단계로 전달하도록 했습니다.
// .m4a(AAC)는 miniaudio가 읽을 수 없으므로, PCM16 WAV로 변환한 뒤 전달
val wavPath = decodeToWavFile(path)
parts.add(Content.AudioFile(wavPath))
채팅에서 "지금 몇 시야?"와 같은 질문에 정확히 답하기 위해 함수 호출 (Function Calling, 도구) 기능을 포함했습니다.
LiteRT-LM에서는 @Tool 어노테이션(Annotation)을 붙인 함수를 엔진이 리플렉션(Reflection)을 통해 자동으로 호출해 줍니다 (automaticToolCalling).
도구를 늘리고 싶을 때는 함수를 하나 추가하기만 하면 됩니다.
class BuiltinToolSet(private val onCall: (String, String) -> Unit) : ToolSet {
@Tool(description = "현재의 날짜와 시간을 가져온다. 지금이 몇 시인지, 오늘 날짜를 물었을 때 사용한다.")
fun getCurrentDateTime(): String {
...
보충: React Native 0.84 환경에서 LiteRT-LM의 Kotlin 의존성을 빌드할 때, -Xskip-metadata-version-check 플래그를 추가해야 했습니다.
- Gemma 4 E2B를 React Native + LiteRT-LM을 사용하여 Android 단말기 상에서 완전 온디바이스(On-device)로 동작시켰습니다.
- 텍스트·이미지·음성의 멀티모달 (Multimodal) 입력과 함수 호출 (Function Calling)에 대응합니다.
- 구현상의 핵심 요점은 LiteRT-LM으로의 이행, GPU/CPU 폴백 (Fallback), 음성의 WAV 변환,
@Tool을 통한 함수 호출(Function Calling)의 4가지였습니다.
약 2.5GB의 모델이 통신 없이 단말기 내부만으로 텍스트·이미지·음성을 처리할 수 있는 단계까지 도달할 수 있었습니다.
소스 코드는 GitHub에 공개되어 있습니다. 스타(Star)나 좋아요를 눌러주시면 큰 힘이 됩니다!
여기까지 읽어주셔서 감사합니다!
- 라이선스: 앱 코드는 MIT License, Gemma 4 모델은 Apache License 2.0
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기