본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 01. 22:55

Android용 NativeLM 구축기 (OEM의 RAM 거짓말을 우회하여 구현하기)

요약

Android 기기에서 Gemma 4를 활용해 완전한 온디바이스 RAG 파이프라인을 구축하는 과정을 다룹니다. LiteRT-LM, MediaPipe, ObjectBox를 사용하여 데이터 유출 없이 로컬에서 문서 기반 질의응답을 구현하는 방법과 OEM의 가상 RAM 확장 기능으로 인한 메모리 충돌 문제를 해결하는 기술적 통찰을 제공합니다.

핵심 포인트

  • LiteRT-LM과 MediaPipe를 활용한 경량 온디바이스 임베딩 구현
  • ObjectBox HNSW를 이용한 모바일 환경 벡터 검색 최적화
  • Gemma 모델을 활용한 로컬 컨텍스트 기반 답변 생성
  • OEM의 가상 RAM 확장 기능이 앱 안정성에 미치는 영향과 주의점

온디바이스(On-device)에서 대규모 언어 모델(Large Language Models, LLM)을 실행하는 것은 개인정보 보호를 위한 궁극적인 해답입니다. 하지만 LLM이 당신의 개인 데이터를 알지 못한다면 무슨 소용이 있을까요?

저는 완전히 오프라인으로 작동하는 AI 어시스턴트를 원했습니다. 즉, 제가 직접 PDF와 노트를 가져오고, 단 1바이트의 데이터도 휴대폰을 떠나지 않은 채 로컬 모델에게 그 내용에 대해 질문할 수 있는 앱 말입니다.

그래서 직접 만들었습니다. litertlm-kmp는 Google의 LiteRT-LM(TensorFlow Lite의 리브랜딩 버전)을 감싸는 Kotlin Multiplatform 래퍼(wrapper)입니다. 이와 함께 제공되는 앱인 NativeLM은 Gemma 4를 실행하며 Android에서 작동하는 완전한 온디바이스 문서 RAG(Retrieval-Augmented Generation) 파이프라인입니다.

제가 로컬 RAG 파이프라인을 어떻게 조립했는지, 그리고 이를 프로덕션에 출시하기 위해 해결해야 했던 거대한 OEM 메모리 버그에 대해 설명하겠습니다.

파이프라인: 완전한 온디바이스 RAG

전통적인 클라우드 RAG 아키텍처는 문서를 서버로 보내 청킹(chunking)하고, OpenAI Embeddings API를 호출하며, Pinecone에 벡터를 저장한 다음, OpenAI 채팅 완성(chat completion)을 위해 이를 검색합니다.

NativeLM의 파이프라인은 이 모든 과정을 휴대폰에서 로컬로 수행합니다.

1. 가져오기 및 임베딩 (USE-Lite)

PDF를 가져올 때, NativeLM은 PDFBox를 사용하여 텍스트를 추출하고 이를 500자 단위의 청크(chunk)로 나눕니다. 저희는 Universal Sentence Encoder Lite (USE-Lite) 모델을 실행하는 MediaPipe의 TextEmbedder를 연결했습니다. 이 모델은 매우 가볍고(~6 MB) 모바일 메모리 제약 조건에 완벽한 100차원 임베딩을 생성합니다.

2. 벡터 검색 (ObjectBox HNSW)

채팅 중에 임베딩을 즉시 쿼리해야 합니다. 저희는 에지 디바이스(edge devices)에서 HNSW (Hierarchical Navigable Small World) 벡터 검색을 기본적으로 지원하는 ObjectBox를 사용했습니다.

@Entity
data class DocumentChunkEntity(
    @Id var id: Long = 0,
...

3. Gemma 그라운딩 (Grounding Gemma)

사용자가 질문을 하면, USE-Lite 모델을 사용하여 쿼리를 임베딩(embedding)하고, ObjectBox를 대상으로 kNN 검색을 수행하여 가장 잘 일치하는 상위 청크(chunks)를 검색한 뒤, 이를 Gemma의 프롬프트(prompt)에 주입합니다. Gemma는 제공된 컨텍스트(context)만을 사용하여 사용자의 질문에 답변하며, UI는 검색된 청크들을 인용(citations)으로 렌더링합니다.

함정: OEM의 RAM 확장 거짓말

RAG 파이프라인은 완벽하게 작동하고 있었지만, Xiaomi, Realme, OPPO 기기에서 모델을 로드하는 동안 앱이 계속 충돌(crash)했습니다.

이러한 OEM들은 "메모리 확장(Memory Extension)" 또는 "동적 RAM 확장(Dynamic RAM Expansion)"이라고 불리는 기능을 가지고 있는데, 이는 스왑 투 플래시(swap-to-flash)를 사용하여 기기에 보고되는 RAM 용량을 인위적으로 부풀립니다. 물리적 RAM이 6GB인 휴대폰은 운영체제(OS)에 8GB 또는 심지어 10GB로 보고됩니다.

표준 Android API를 사용하여 사용 가능한 메모리를 확인하면 다음과 같습니다:

val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
val totalRam = memInfo.totalMem // Xiaomi/Realme/OPPO에서는 거짓 정보임

...이렇게 하면 부풀려진 숫자를 얻게 됩니다. 모델 로딩 코드는 "8GB 사용 가능"을 확인하고 4GB 모델을 로드해도 안전하다고 판단하지만, 실제 물리적 RAM이 이를 감당할 수 없기 때문에 커널(kernel)이 프로세스를 OOM-kill(Out-Of-Memory Kill) 해버립니다.

해결책: OS 우회하기

저는 /proc/meminfo를 직접 읽는 하드웨어 티어링(hardware tiering) 시스템을 작성했습니다:

private fun detectVirtualRamExpansion(): Boolean {
    val memInfo = File("/proc/meminfo").readText()
    val swapTotal = memInfo.lines()
...

스왑(swap)이 1GB 이상으로 감지되면, 라이브러리는 강제로 하드웨어 티어를 다운그레이드합니다. 3GB의 스왑을 포함하여 8GB를 보고하는 기기는 5GB 기기로 분류되며, 모델 카탈로그는 대신 더 작은 Gemma 변체(variant)를 제공합니다.

이 단 한 번의 수정으로 Xiaomi 및 Realme 테스트 기기에서의 OOM 충돌을 100% 제거했습니다.

상태 유지 KV-캐시 세션 (Stateful KV-Cache Sessions)

로컬 AI의 또 다른 주요 문제는 대화가 진행될수록 느려진다는 점입니다. 대부분의 앱은 매 턴마다 전체 대화 기록을 모델에 다시 전송합니다. 이로 인해 첫 번째 토큰 생성 시간(TTFT, Time-to-first-token)이 선형적으로 저하됩니다.

LiteRT-LM은 openChatSession()을 통해 턴(turn) 사이에도 KV-캐시 (KV-cache)를 유지할 수 있도록 지원합니다. 이 캐시는 이전의 모든 턴에서 발생한 키-값 어텐션 상태 (key-value attention states)를 저장하므로, 모델은 오직 새로운 토큰들만 처리하면 됩니다.

// 지속적인 세션 열기 — 턴 사이에도 KV-캐시가 유지됨
val session = engine.openChatSession()

...

저는 로컬 KV-캐싱 (local KV caching)의 엄격한 제약 사항을 투명하게 처리하는 세션 관리자 (session manager)를 구축했습니다. 그 결과: 대화 길이에 상관없이 Snapdragon 8 Gen 2에서 TTFT (Time-to-first-token)가 약 20 tok/s로 일정하게 유지됩니다.

결과물: NativeLM v0.4.0

NativeLM의 최신 릴리스는 온보딩 (onboarding), 모델 관리 (model management), 상태 유지형 KV-캐시 세션 (stateful KV-cache sessions), 그리고 완전히 오프라인으로 작동하는 새로운 Document RAG 기능을 포함하여 전체 라이브러리를 실행합니다.

모든 것은 오픈 소스입니다: github.com/sagar-develop/litertlm-kmp

사용자의 기기에서 직접 테스트해보고 싶다면: v0.4.0 APK 다운로드

온디바이스 AI (on-device AI) 배포에 대한 더 심도 있는 기술적 분석은 Urja Labs 블로그를 확인하세요.

모바일에서 로컬 AI를 실행하며 어떤 문제에 부딪혔는지 듣고 싶습니다. 댓글을 남기거나 리포지토리 (repo)에 이슈 (issue)를 생성해 주세요.

Built at Urja Labs. 이중 라이선스: 오픈 소스를 위한 AGPL-3.0, 독점 배포를 위한 상업용 라이선스 제공.

여정을 팔로우하세요: LinkedIn · X / Twitter

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0