
iPhone의 Visual Intelligence(AI 렌즈)로 커스텀 AI 호출하기
요약
iPhone의 Visual Intelligence 기능을 활용하여 사용자가 직접 커스텀 AI 모델을 호출할 수 있는 개발 방법을 소개합니다. MiniCPM-V 4.6 모델을 통합하여 OS의 비주얼 검색 프로세스 내에서 동작하도록 구현하는 과정을 다룹니다.
핵심 포인트
- Visual Intelligence는 커스텀 모델을 호출할 수 있는 확장성을 제공함
- Xcode를 통해 앱 메타데이터에 특정 타입을 정의하여 OS와 연동
- AI 추론은 OS 내부가 아닌 개별 앱 프로세스 내에서 실행됨
- IntentValueQuery와 OpenIntent를 구현하여 검색 및 앱 실행 제어
iPhone의 카메라를 비추면, AI에게 주변 사물에 대해 물어볼 수 있다.
Visual Intelligence(비주얼 인텔리전스)라는 기능이다.
「Google 렌즈」와 「서클 투 서치(かこって検索)」를 합쳐놓은 듯한 AI 렌즈다.
스크린샷으로도 사용할 수 있다.
내용물은 ChatGPT이다.
기능 확장(Function Calling)을 활성화하면, 이미지가 ChatGPT로 전달된다.
이 Visual Intelligence의 내부 AI는 ChatGPT뿐만 아니라 자체적인 커스텀 모델도 사용할 수 있게 되었다(iOS 26).
즉, 사용자가 툭 찍은 사진을 다양한 편리한 모델로 전달할 수 있다.
커스텀 모델을 사용하면,
- 태스크 특화형 모델을 사용할 수 있다
- 인터넷 연결 없이도 동작한다
- LLM뿐만 아니라, 객체 탐지(Object Detection)나 세그멘테이션(Segmentation) 등의 컴퓨터 비전(Computer Vision) 모델도 사용할 수 있다
와 같은 장점이 있다.
그래서 이미지 LLM을 좋아하는 나는,
MiniCPM-V 4.6이라는 경량 VLM(Vision Language Model)을 통합해 보았다.
직접 변환한 모델이, 앱을 열지 않아도 동작한다. 기쁘다.
어떻게 하면 OS가 나의 모델을 호출해 줄까.
AI가 답을 반환하는 코드는, 자신의 앱 안에 작성한다.
정해진 형태의 타입(Type)을 하나 작성하면 끝난다.
앱이 설치되면, OS가 앱의 메타데이터를 읽고 「이 앱은 비주얼 검색에 답할 수 있다」는 것을 알게 된다.
즉, 커스텀 AI를 Visual Intelligence에서 사용하려면, 그 AI를 호출할 수 있는 앱이 설치되어 있어야 한다.
순서대로 정리하면 다음과 같다.
- 앱 빌드 시 Xcode가 앱 내의 정해진 타입을 추출하여 앱 내의 메타데이터로 만든다
- 설치 시 OS가 이를 읽는다
- 카메라를 비추었을 때 OS가 당신의 앱을 백그라운드에서 실행한다
values(for:)를 호출한다 ← 자신이 작성한 코드는 여기다 - 반환된 결과를 OS가 검색 화면에 그린다- 결과를 탭하면 OS가
perform()을 호출한다. 앱이 열린다
핵심은 AI 처리가 동작하는 장소로, OS 내부가 아니라 자신의 앱 프로세스 안에서 동작한다.
따라서 모델 로드와 추론(Inference) 모두 평소처럼 자신의 코드로 작성하기만 하면 된다.
그 대신, 메모리 제약 또한 자신의 앱이 부담하게 된다.
작성해야 하는 타입은 두 가지다.
첫 번째.
사진을 받아 답을 반환한다.
struct VisualSearchValueQuery: IntentValueQuery {
@Dependency var engine: MiniCPMVIEngine
func values(for input: SemanticContentDescriptor) async throws -> [VisualAnswerEntity] {
...
표식은 인자의 타입이며, values(for:)가 SemanticContentDescriptor를 받고 있으면, OS는 이를 비주얼 검색용 쿼리로 판단한다. OS로부터 전달되는 것은 레이블(Label)과 픽셀 버퍼(Pixel Buffer) 두 가지뿐이다.
두 번째. 결과를 탭하여 앱이 열렸을 때의 동작.
struct OpenVisualAnswerIntent: OpenIntent {
static let title: LocalizedStringResource = "Open Answer"
@Parameter(title: "Answer") var target: VisualAnswerEntity
...
그리고 하나 더.
모델 엔진을 앱 실행 시점에 등록해 둔다.
@main
struct MiniCPMVisualIntelApp: App {
init() {
...
이것으로 완료다.
그 외에는 설정도, 사용 권한도 필요 없다.
백그라운드 실행 시에도 init()은 먼저 실행된다.
따라서 쿼리가 호출될 때쯤에는 준비가 되어 있다.
OS와 주고받는 타입에는 모델을 지정하는 인자가 어디에도 없으며, values(for:) 안에서 무엇을 구동하든 시스템에서는 보이지 않는다.
두 번째 OpenIntent
OpenIntent를 쓰는 것을 잊어버리면, 에러나 경고도 발생하지 않은 채 앱이 검색 결과에 나타나지 않게 된다. -
모델 다운로드는 불가능하다. 모델은 미리 앱을 열어 다운로드해 두어야 한다. -
컴파일을 미리 해둔다. 첫 추론(Inference)은 느리기 때문에, 검은색 더미 이미지로 한 번 실행해 두어야 한다. -
쿼리(Query)는 메인 액터(Main Actor) 외부에서 실행된다. 모델 본체는 Metal 버퍼를 가지므로 메인 액터 안에 있다. CGImage는 액터를 넘나들 수 없다. CGImage는 Sendable이 아니므로, 그대로 작성하면 Swift 6 컴파일러에 의해 차단된다. 경계를 넘기 전에 [Float16]으로 변환해 버리면 통과된다. -
종료되어도 괜찮도록 작성한다. 모델 호출은 try?로 처리했다. 메모리 부족으로 프로세스가 종료되거나 예외가 발생하더라도, 단지 "탭하여 듣기"라는 결과가 반환될 뿐이다. 검색 결과가 빈 상태가 되지는 않는다. -
한 앱에는 하나의 쿼리만 둘 수 있다. 즉, 앱 하나당 탭 하나, 모델 하나다. 탐지(Detection)와 VLM을 모두 보여주고 싶다면 앱을 분리해야 한다.
Apple은 백그라운드 실행 시의 메모리 상한을 공개하지 않고 있습니다.
그래서 이전에 Qwen3-VL-2B로 유사한 작업을 했을 때 측정해 보았습니다.
디스크 상에서는 2.3GB였지만, 실제로 집계된 피크(Peak)는 약 440MB였습니다.
엔진이 가중치(Weights)를 mmap(memory map)하고 있기 때문입니다. 파일에서 읽기만 하는 페이지는 프로세스의 메모리로 계산되지 않습니다.
OS가 앱을 종료할지 여부를 판단하는 기준은 이 값이므로, GB 단위가 아니라 수백 MB 단위의 이야기가 됩니다.
MiniCPM-V 4.6은 1.3B로, 이보다 훨씬 더 작습니다.
이번에는 수치를 측정하지 않았지만, 문제없이 작동했습니다.
속도는 iPhone 17 Pro 기준으로 prefill 50.6 tok/s, decode 51.5 tok/s입니다. 상주 메모리는 약 1.5GB입니다.
디코더(Decoder)의 출력층을 int8로 변경한 버전으로 교체하면, decode 속도는 70.0 tok/s까지 올라갑니다 (+36%).
Visual Intelligence에 연결하는 것은 파인튜닝(Fine-tuning)된 VLM이어도 좋고, 탐지기(Detector)여도 좋으며, 자사 카탈로그의 임베딩 검색(Embedding Search)이어도 좋습니다.
그리고 사용자는 앱을 열지 않습니다. 그저 카메라를 향하기만 하면 됩니다.
코드: apps/MiniCPMVisualIntel
모델: mlboydaisuke/MiniCPM-V-4.6-CoreAI (Apache-2.0)
동일한 배선으로 Qwen3-VL-2B를 구동하는 버전도 있습니다: Examples/AskVLM
빌드하려면 coreai-models에 apps/coreai-pipelined-static-inputs.patch와 apps/coreai-pipelined-extra-states.patch를 적용해야 합니다. 이는 이미지 버퍼와 이 모델이 사용하는 추가적인 내부 상태(Internal State)를 위한 것입니다.
참고:
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기