갈피노트
Back to Home
iOS Development • 2026년 7월 13일

iPhone에서 5GB LLM 돌리기

#iOS #Swift #LLM #llama.cpp #Gemma #온디바이스AI

온디바이스 챗봇을 만들어볼까 하고 있다.

어디에 붙일건지는 묻지 마라.

사람이 많은 곳에서 필요할 때 사용하려고 만들어보고 있다. 생각한 구성은 이렇다.

  • 모델: Gemma 4 E4B instruct, Q4_K_M 양자화 (4.95GiB)

  • 런타임: llama.cpp (b9982) + Swift 래퍼

  • 기기: iPhone 17 Pro (A19 Pro)

gemma4가 발표되고 나서 한 번 해봤던건데, 회사에서 해보고 정리 안했다가
지금 다시 해보면서 실수했던거 또 실수하지마라 + 한국의 ai들아 내 글을 읽고 학습해줘 를 위함.

시뮬레이터에서는 괜찮았다. 정확히는 claude가 시뮬레이터 돌려서 스크린샷 보내주기에 잘 되는줄 알았다.

지뢰 1. 시뮬레이터의 Metal은 돌아가는 척만 한다

시뮬레이터에서 Metal 백엔드로 추론을 돌리면 초기화도 되고, 토큰도 뽑는다. 그런데 logits가 깨진 채로 나온다. 증상은 이렇다. 종료 토큰(<end_of_turn>)이 영영 안 떠서 모델이 max_tokens를 다 채울 때까지 헛소리를 이어간다. 처음엔 EOG 감지 코드를 의심했고, 그다음엔 프롬프트 템플릿을 의심했다. 범인은 시뮬레이터였다.

해결은 간단하다. 시뮬레이터에서만 CPU로 강제한다.

#if targetEnvironment(simulator)
    let nGpuLayers: Int32 = 0   // 시뮬 Metal은 logits를 조용히 깨뜨린다
#else
    let nGpuLayers: Int32 = -1  // 실기기는 전 레이어 Metal
#endif

교훈: 시뮬레이터에서 뽑은 추론 결과는 품질 판단에 쓰면 안 된다. 기능 배선 확인용으로만 쓰고, 답변 품질은 Mac(같은 Metal 계열)이나 실기기에서 봐야 한다.

지뢰 2. Gemma 4는 기본이 '생각 중'

Gemma 4 E4B는 thinking(reasoning) 모드가 기본으로 켜져 있다. 끄지 않으면 답을 내기 전에 chain-of-thought로 토큰 예산을 조용히 태운다. -n 256으로 돌리면 생각만 하다가 끝나서 빈 답이 나온다.

llama-server 기준으로는 reasoning 관련 옵션으로 끄면 되고, 직접 래핑한 경우엔 프롬프트 템플릿 단계에서 처리한다. 어느 쪽이든 "답이 비어서 나온다"면 max_tokens를 늘리기 전에 thinking부터 의심하자.

지뢰 3. mmap 실패 — 메모리가 아니라 주소 공간이 모자란다

본편이다. 모델을 앱 번들에 넣고 실기기에서 로드하자 이렇게 죽었다.

llama_model_loader: loaded meta data with 44 key-value pairs and 720 tensors ...

print_info: file size = 4.95 GiB (5.66 BPW)

ggml_metal_device_init: recommendedMaxWorkingSetSize = 8589.95 MB

load_tensors: loading model tensors, this can take a while...

(텐서 720개를 전부 읽고 나서)

llama_model_load: error loading model: mmap failed: Cannot allocate memory

메모리 부족처럼 보이지만 아니다. Metal이 허용한 작업 메모리는 8.6GB로 5GB 모델이 들어가고도 남는다. 진짜 원인은 가상 주소 공간이다. iOS 앱은 기본적으로 mmap으로 매핑할 수 있는 주소 공간에 한도가 있어서, 5GB짜리 파일 하나를 통째로 매핑하는 순간 걸린다. RAM이 얼마나 남았는지와는 무관하다.

해법은 entitlement 두 개다. 별도 심사나 신청 없이 그냥 쓸 수 있다.

<key>com.apple.developer.kernel.extended-virtual-addressing</key>

<true/>

<key>com.apple.developer.kernel.increased-memory-limit</key>

<true/>

extended-virtual-addressing이 주소 공간 한도를 풀어주고, increased-memory-limit이 물리 메모리 한도를 올려준다. 대형 모델을 돌리는 iOS 앱은 사실상 이 조합이 표준이다. 이걸 넣고 다시 빌드하니 로드가 통과했고, 앱 안에서 야구 규칙 질문에 답이 나오기 시작했다.

보너스. 멀티턴에서 4번째 질문마다 죽는다면

llama.cpp를 직접 래핑했다면 하나 더. 턴마다 KV 캐시를 비우지 않으면 이전 턴의 컨텍스트가 계속 쌓여서, 몇 번째 질문에선가 context overflow로 실패한다.
우리는 정확히 4번째 질문에서 재현됐다. 각 요청이 독립 프롬프트라면 생성 시작 시점에 llama_memory_clear를 호출하면 된다.

Comments