mogee.
<< BACK
#Flutter#온디바이스AI#Gemma#flutter_gemma#개발일지#모바일#AI

Flutter 앱에 온디바이스 AI 넣기 — Gemma 4 구현 삽질기

DATE: 2026년 4월 4일TIME: 8분 읽기VIEWS: 18

Flutter 앱에 온디바이스 AI 넣기 — Gemma 4 구현 삽질기

ttapp에 온디바이스 AI 기능을 추가했다. 인터넷 없이 기기 안에서 AI와 대화할 수 있는 기능이다. Google의 Gemma 4 모델을 flutter_gemma 패키지로 연동했고, 구현 과정에서 꽤 많은 삽질을 했다. 이 글은 그 과정의 기록이다.


왜 온디바이스 AI인가?

서버 AI(Claude, GPT 등)는 훌륭하지만 몇 가지 한계가 있다.

  • 인터넷 필수: 오프라인 환경에서는 사용 불가
  • 비용: API 호출마다 요금 발생
  • 개인정보: 입력 데이터가 서버로 전송됨

온디바이스 AI는 이 문제를 전부 해결한다. 기기 안에서만 돌아가니까.


기술 스택

  • Flutter + flutter_gemma 0.13.0
  • Gemma 4 (E2B: ~2.6GB, E4B: ~3.7GB)
  • HuggingFace litert-community 공개 레포 (인증 불필요)
  • ModelFileType.litertlm (LiteRT-LM 포맷)

삽질 1: 모델 다운로드가 0%에서 멈춘다

처음에 다운로드를 구현하고 테스트하니 0%에서 영원히 멈췄다. 에러 메시지도 없었다. 30분을 기다려봤지만 아무 일도 없었다.

원인은 황당하게도 main()에 초기화 한 줄이 빠진 거였다.

// main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // ★ 이게 없으면 다운로드가 0%에서 멈춤
  await FlutterGemma.initialize();

  runApp(const MyApp());
}

공식 문서에는 있는 내용인데 놓쳤다. 패키지 내부에서 이 초기화가 없으면 에러를 던지는 대신 그냥 조용히 멈춰버린다. 디버깅하기 가장 어려운 종류의 버그다.


삽질 2: Play Store 리젝 — FOREGROUND_SERVICE

처음엔 foreground: true로 다운로드를 설정했다. 3GB짜리 파일이니까 포그라운드 서비스를 쓰는 게 맞다고 생각했다.

Play Store 심사에서 리젝이 왔다.

"귀하의 앱이 FOREGROUND_SERVICE 권한을 사용하는지 알려주세요."

flutter_gemma의 foreground 다운로드는 Android FOREGROUND_SERVICE 권한을 요구한다. 그런데 이 권한은 Play Store에서 사용 목적을 명시해야 하고, AI 모델 다운로드는 인정되는 용도가 아니다.

해결책: foreground: false로 설정하고 AndroidManifest에서 FOREGROUND_SERVICE 완전 제거.

await FlutterGemma.installModel(
  modelType: ModelType.gemmaIt,
  fileType: model.fileType,
).fromNetwork(
  url,
  foreground: false, // FOREGROUND_SERVICE 미사용
).install();

Wi-Fi 환경에서는 백그라운드로도 충분히 다운로드된다.


삽질 3: HuggingFace 401 인증 오류

처음엔 Google 공식 레포(google/gemma-4-*)에서 모델을 받으려 했다.

401 Unauthorized

Google의 공식 Gemma 레포는 gated repo다. HuggingFace 계정으로 접근 신청을 하고 승인을 받아야 다운로드할 수 있다. 앱 사용자들이 각자 HuggingFace 계정을 만들고 신청하게 할 수는 없었다.

해결책: litert-community 공개 레포를 사용했다. 인증 없이 누구나 다운로드 가능하다.

https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/main/gemma-4-E2B-it.litertlm
https://huggingface.co/litert-community/gemma-4-E4B-it-litert-lm/resolve/main/gemma-4-E4B-it.litertlm

삽질 4: 두 번째 모델 다운로드가 0%에서 멈춘다

E2B 다운로드 → 채팅 테스트 → E4B 다운로드 시도.

E4B가 0%에서 멈췄다. E2B 때와 같은 증상이었지만 원인은 달랐다.

flutter_gemma는 getActiveModel()을 통해 모델을 메모리에 올린다. E2B 채팅을 끝내고 뒤로 가기를 눌러도 모델이 메모리에서 내려오지 않는다. 그 상태에서 E4B 다운로드를 시작하면 네이티브 레벨에서 리소스 충돌이 발생해 다운로드가 진행되지 않는다.

해결책: 다운로드 시작 전에 로드된 모델을 반드시 언로드한다.

Future<void> downloadModel(LocalAIModel model) async {
  // 다운로드 전 로드된 모델 언로드
  if (_inferenceModel != null) {
    await unloadModel();
  }
  // ... 다운로드 시작
}

삽질 5: E2B 대화가 갑자기 안 된다

E2B 다운로드 → E2B 대화 성공 → E4B 다운로드 → E2B 대화 다시 시도 → 실패.

원인은 flutter_gemma의 active model 개념이었다.

flutter_gemma는 마지막으로 설치된 모델을 "active model"로 관리한다. getActiveModel()은 이 active model을 로드한다. E4B를 설치하면 E4B가 active model이 되고, E2B 채팅에 들어가도 E4B를 로드하려 한다.

해결책: 채팅 진입 전, 해당 모델을 fromFile()로 재설치해서 active model로 교체한다. 파일이 이미 있으므로 재다운로드는 없다.

Future<void> _activateModel(LocalAIModel model) async {
  final dir = await getApplicationDocumentsDirectory();
  final modelPath = p.join(dir.path, model.fileName);

  // 이미 다운로드된 파일로 active model 교체
  await FlutterGemma.installModel(
    modelType: ModelType.gemmaIt,
    fileType: model.fileType,
  ).fromFile(modelPath).install();
}

최종 구조

LocalAIService (싱글톤)
├── downloadModel()  — 다운로드 전 unload + 진행률 스트림 + 타임아웃
├── loadModel()      — _activateModel() 후 getActiveModel()
├── generateStream() — 스트리밍 추론 + 취소 지원
└── unloadModel()    — InferenceModel.close() + 경쟁 조건 방어

모델은 enum으로 관리해서 새 모델 추가 시 항목 하나만 추가하면 된다.


핵심 체크리스트

항목내용
FlutterGemma.initialize()main()에서 runApp 전에 반드시 호출
foreground: falseFOREGROUND_SERVICE 권한 선언 불필요
litert-community 레포인증 없이 공개 모델 다운로드 가능
다운로드 전 unload로드된 모델이 있으면 먼저 해제
fromFile()로 재활성화다른 모델 설치 후 active model 교체
타임아웃3분 내 진행 없으면 자동 실패 처리

마치며

온디바이스 AI는 분명히 가능성 있는 기술이다. 오프라인 동작, 개인정보 보호, 비용 절감 등 장점이 많다. 다만 모델 파일 크기(2~4GB)와 기기 RAM 요구사항(8GB+)이 현실적인 제약이다.

Gemma 4는 E2B 기준으로 모바일에서 충분히 사용할 수 있는 수준의 응답을 보여줬다. 앞으로 더 작고 성능 좋은 모델이 나오면 온디바이스 AI의 활용 범위는 더 넓어질 것 같다.

이 구현은 ttapp(모바일 Claude Code 원격 제어 앱)에 적용되어 있다. 관심 있는 분들은 ttapp에서 확인해볼 수 있다.

이 글 공유하기

[X] X에 공유

// SPONSORED

[>]댓글

아직 댓글이 없어요. 첫 댓글을 남겨보세요!