effidevFlutter · Cloudflare 엣지 · 클라우드 비용 최적화
한국어

Flutter 앱에서 온디바이스 LLM(Gemma/Phi-3) 실행하기: MediaPipe LLM Inference API 활용법

Flutter On-Device LLM MediaPipe Gemma Phi-3 Integration

최근 모바일 애플리케이션 개발 분야의 화두는 단연 **온디바이스 AI(On-device AI)**입니다. 클라우드 기반 LLM API(OpenAI GPT-4o, Anthropic Claude)를 호출하는 방식은 높은 품질을 제공하지만, API 호출 요금 폭증, 네트워크 지연 시간(Latency), 및 개인정보(Privacy) 유출 우려라는 한계를 갖고 있습니다.

이러한 한계를 돌파하기 위해 구글이 출시한 MediaPipe LLM Inference API는 온디바이스 환경에서 Gemma-2B, Phi-3-Mini, Falcon-RW-1B와 같은 경량화 언어 모델을 스마트폰 GPU 및 NPU 자원을 활용해 직접 추론(Inference)할 수 있게 해줍니다.

이 글에서는 Flutter 앱에 MediaPipe LLM Inference API 패키지를 세팅하고, 모델 파일양자화(Quantization - int4/int8), 스트리밍 텍스트 생성(Streaming Token Response), 그리고 모바일 메모리 OOM(Out Of Memory) 방지 최적화 기법을 완벽히 다룹니다.

핵심 요약

  • 온디바이스 LLM의 장점: 네트워크 접속이 없는 오프라인 상태에서도 동작하며, 서버 비용 $0, 응답 지연 시간 최적화, 완벽한 사용자 개인정보 보호를 보장합니다.
  • MediaPipe LLM Inference API: Cross-platform(Android/iOS)을 지원하는 구글의 온디바이스 런타임 엔진으로, GPU 가속을 통해 모바일 기기에서 초당 15~25 토큰 생성 속도를 출력합니다.
  • 모델 양자화(Quantization): 4-bit(int4) 양자화된 .bin / .task 모델 파일을 사용하여 Gemma-2B 모델 기준 메모리 점유율을 1.5GB 미만으로 축소합니다.
  • Flutter Native Binding: FFI 또는 MethodChannel을 통해 C++/Kotlin/Swift 파이프라인과 연결하여 텍스트 스트림을 Flutter StreamBuilder에 직접 바인딩합니다.

1. On-Device LLM vs Cloud LLM 아키텍처 비교

항목 Cloud LLM (OpenAI / Claude API) On-Device LLM (MediaPipe + Gemma)
운영 비용 (Cost) API 호출량 따라 지속 증가 ($$$) 서버 비용 $0 (모바일 자원 활용)
네트워크 의존성 필수 (오프라인 시 사용 불가) 100% 오프라인 동작 가능
개인정보 보호 (Privacy) 사용자 데이터가 외부 서버로 전송됨 기기 내부에서만 처리 (외부 유출 Zero)
응답 Latency 네트워크 RTT 포함 1.5s ~ 4s 첫 토큰 200ms 미만 (스트리밍)
앱 번들 / 메모리 번들 추가 비용 없음 (~10MB) 모델 파일 저장 공간 1.2GB~2.0GB 필요

2. MediaPipe LLM Inference 엔진 설치 및 모델 배치

Android와 iOS 기기에서 4-bit 양자화된 Gemma-2B (gemma-2b-it-gpu-int4.bin) 모델 파일을 다운로드하여 앱의 로컬 스토리지에 배치해야 합니다.

2.1 Flutter 패키지 설정 및 Native 바인딩 (pubspec.yaml)

dependencies:
  flutter:
    sdk: flutter
  path_provider: ^2.1.4
  flutter_highlighter: ^0.0.3

# ios/Podfile 및 android/app/build.gradle 에 MediaPipe tasks-genai 라이브러리 추가

3. Flutter에서 MediaPipe LLM Inference 호출 코드

MediaPipe C++ / Native API를 Wrap하는 Service 클래스를 작성합니다.

// lib/services/ondevice_llm_service.dart
import 'dart:async';
import 'package:flutter/services.dart';

class OnDeviceLlmService {
  static const MethodChannel _channel = MethodChannel('dev.effidev.mediapipe_llm');
  static const EventChannel _streamChannel = EventChannel('dev.effidev.mediapipe_llm/stream');

  bool _isInitialized = false;

  /// 온디바이스 LLM 모델 초기화 (Gemma 2B int4)
  Future<void> initializeModel(String modelPath) async {
    try {
      await _channel.invokeMethod('initModel', {
        'modelPath': modelPath,
        'maxTokens': 512,
        'topK': 40,
        'temperature': 0.7,
      });
      _isInitialized = true;
    } on PlatformException catch (e) {
      throw Exception("LLM 초기화 실패: ${e.message}");
    }
  }

  /// 프롬프트 입력 및 스트리밍 토큰 수신
  Stream<String> generateResponseStream(String prompt) {
    if (!_isInitialized) {
      throw StateError("모델이 초기화되지 않았습니다.");
    }

    // Native 영역에서 토큰 생성시마다 Stream 전달
    _channel.invokeMethod('generateResponse', {'prompt': prompt});
    
    return _streamChannel
        .receiveBroadcastStream()
        .map((event) => event.toString());
  }

  /// 메모리 해제
  Future<void> dispose() async {
    await _channel.invokeMethod('releaseModel');
    _isInitialized = false;
  }
}

4. UI 구현: StreamBuilder 기반 스트리밍 챗 인터페이스

// lib/views/chat_page.dart
import 'package:flutter/material.dart';
import '../services/ondevice_llm_service.dart';

class OnDeviceChatPage extends StatefulWidget {
  const OnDeviceChatPage({super.key});

  @override
  State<OnDeviceChatPage> createState() => _OnDeviceChatPageState();
}

class _OnDeviceChatPageState extends State<OnDeviceChatPage> {
  final _llmService = OnDeviceLlmService();
  final _textController = TextEditingController();
  String _currentResponse = "";
  bool _isLoading = false;

  void _sendMessage() {
    final prompt = _textController.text.trim();
    if (prompt.isEmpty) return;

    setState(() {
      _currentResponse = "";
      _isLoading = true;
      _textController.clear();
    });

    _llmService.generateResponseStream(prompt).listen(
      (token) {
        setState(() {
          _currentResponse += token;
          _isLoading = false;
        });
      },
      onError: (err) {
        setState(() {
          _currentResponse = "에러 발생: $err";
          _isLoading = false;
        });
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("MediaPipe Gemma On-Device AI")),
      body: Column(
        children: [
          Expanded(
            child: SingleChildScrollView(
              padding: const EdgeInsets.all(16),
              child: Text(
                _currentResponse.isEmpty && _isLoading
                    ? "생각 중..."
                    : _currentResponse,
                style: const TextStyle(fontSize: 16),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _textController,
                    decoration: const InputDecoration(
                      hintText: "온디바이스 AI에게 질문하세요...",
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),
                const SizedBox(width: 8),
                IconButton(
                  icon: const Icon(Icons.send),
                  onPressed: _sendMessage,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

5. 모바일 메모리(OOM) 방지 및 GPU 가속 설정 팁

  1. Int4 양자화 모델 필수 사용: 16-bit float 모델은 4GB 이상의 VRAM을 요구하여 모바일 기기에서 앱이 강제 종료(OOM)됩니다. 반드신 MediaPipe 지원 4-bit int4 양자화 .task 모델을 사용하세요.
  2. GPU Delegate 튜닝: iOS Metal 및 Android Vulkan GPU delegate를 활성화하여 CPU 추론 대비 속도를 4배 이상 향상시킵니다.
  3. Background Lifecycle 해제: 앱이 백그라운드로 전환될 때 모델 메모리를 즉시 해제하거나 swap 처리하여 OS에 의한 앱 킬(App Kill) 현상을 방지합니다.

MediaPipe LLM Inference API를 활용하면 모바일 Flutter 앱 개발자는 백엔드 서버 인프라 부담 없이 강력한 AI 챗봇 및 자동 완성 기능을 기기 내부에서 오프라인으로 구현할 수 있습니다.