0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

iPhoneのVisual Intelligence(AIレンズ)で、カスタムAIを呼び出す

0
Posted at

iPhoneのカメラを向けると、AIに周囲のものについて尋ねることができる。
ビジュアルインテリジェンスという機能です。
「Googleレンズ」と「かこって検索」を合わせたようなAIレンズです。
スクリーンショットでも使えます。

中身はChatGPTです。
機能拡張を有効にすると、画像がChatGPTに渡されます。

このVisual Intelligenceの中身のAI、ChatGPTだけではなく自前のカスタムモデルも使えるようになりました(iOS 26)。

つまりユーザーがパッと撮った写真をさまざまな便利なモデルに渡せる。

カスタムモデルを使うと、

  • タスク特化型モデルが使える
  • インターネットなしでも動く
  • LLMだけでなく、物体検出やセグメンテーションなどのコンピューターヴィジョンモデルも使える

といったメリットがあります。

ということで、画像LLM好きの僕は、
MiniCPM-V 4.6という軽量なVLMを組み込んでみました。
自分で変換したモデルが、アプリを開かなくても動く。うれしい。

visual_intelligence_minicpm_demo.gif

やり方

どうすればOSが自分のモデルを呼んでくれるか。

AIが答えを返すコードは、自分のアプリの中に書きます。
決まった形の型を1つ書けば終わります。
アプリがインストールされると、OSがアプリのメタデータを読んで「このアプリはビジュアル検索に答えられる」と知ります。

つまりカスタムAIをVisual Intelligenceで使うには、そのAIを呼べるアプリがインストールされている必要があります。

順番にするとこうです。

  1. アプリビルド時にアプリ内の決まった型をXcodeが抽出してアプリ内のメタデータにする
  2. インストール時にOSが読む
  3. カメラを向けた時にOSがあなたのアプリをバックグラウンドで起動
  4. values(for:) を呼ぶ ← 自分が書いたコードはここ
  5. 返ってきた結果を、OSが検索画面に描画
  6. 結果をタップすると、OSが perform() を呼ぶ。アプリが開く

肝心なのはAIの処理が動く場所で、OSの中ではなく、自分のアプリのプロセスの中で動きます。
だからモデルのロードも推論も、普段どおり自分のコードで書くだけです。

その代わり、メモリの制約も自分のアプリが背負います。

書く型は2つです。

1つ目。
写真を受け取って、答えを返す。

struct VisualSearchValueQuery: IntentValueQuery {
    @Dependency var engine: MiniCPMVIEngine

    func values(for input: SemanticContentDescriptor) async throws -> [VisualAnswerEntity] {
        guard let pixelBuffer = input.pixelBuffer,
              let cgImage = MiniCPMVIEngine.cgImage(from: pixelBuffer) else { return [] }

        let answer = try await engine.caption(cgImage)   // ここでMiniCPM-Vが答える
        return [VisualAnswerEntity(answer: answer, thumbnail: ...)]
    }
}

目印は引数の型で、values(for:)SemanticContentDescriptor を受け取っていると、OSはこれをビジュアル検索用のクエリだと判断します。OSから渡されるのは、ラベルとピクセルバッファの2つだけです。

2つ目。結果をタップしてアプリが開いたときの動き。

struct OpenVisualAnswerIntent: OpenIntent {
    static let title: LocalizedStringResource = "Open Answer"
    @Parameter(title: "Answer") var target: VisualAnswerEntity
}

あと一つだけ。
モデルのエンジンをアプリの起動時に登録しておきます。

@main
struct MiniCPMVisualIntelApp: App {
    init() {
        AppDependencyManager.shared.add(dependency: MiniCPMVIEngine.shared)
    }
    var body: some Scene { WindowGroup { ContentView() } }
}

これで完了です。
他は設定も、使用許可もいりません。
バックグラウンド起動のときも init() は先に走る。
だからクエリが呼ばれる頃には間に合っています。

OSとやり取りする型にはモデルを指定する引数はどこにもなく、values(for:) の中で何を動かしても、システムからは見えません。

僕が落ちた落とし穴を踏まないために

  • 2つ目の OpenIntent を書き忘れると、エラーも警告も出ないまま、アプリが検索結果に出てこなくなる。
  • モデルダウンロードはできない。 モデルは先にアプリを開いてダウンロードしておく必要がある。
  • コンパイルを先にしておく。 初回推論は遅いので、真っ黒なダミー画像で1回走らせておく。
  • CGImage はアクターをまたげない。 クエリはメインアクターの外で走る。モデル本体はMetalバッファを持つのでメインアクターの中にいる。CGImageSendable ではないので、素直に書くとSwift 6のコンパイラに止められる。境界を渡る前に [Float16] に変換してしまえば通る。
  • 落ちてもいいように書く。 モデルの呼び出しは try? にしてある。メモリ不足で殺されても、例外が飛んでも、「タップして聞く」という結果が返るだけ。検索結果が空になることはない。
  • 1アプリに1クエリしか置けない。 つまりアプリ1つにつきタブ1つ、モデル1本。検出とVLMを両方見せたいならアプリを分ける。

2GBのモデルは、メモリをどれだけ使うのか

バックグラウンド起動のメモリ上限を、Appleは公開していません。
なので、前にQwen3-VL-2Bで同様のことをしたときに測りました。

ディスク上は2.3GB。実際に計上されたピークは約440MBでした。

エンジンが重みをmmapしているからです。ファイルから読んでいるだけのページは、プロセスのメモリとして数えられない。
OSがアプリを殺すかどうかを判断するのはこの値なので、GB単位ではなく数百MB単位の話になります。

MiniCPM-V 4.6は1.3Bで、これよりさらに小さい。
今回は数値を測っていませんが、問題なく動きました。

速度は、iPhone 17 Proでprefill 50.6 tok/s、decode 51.5 tok/sです。常駐は約1.5GB。
デコーダの出力層をint8にした版に差し替えると、decodeは70.0 tok/sまで上がります(+36%)。

やってみて

Visual Intelligenceに接続するのは、ファインチューニングしたVLMでも、検出器でも、自社カタログの埋め込み検索でもいい。

そしてユーザーは、アプリを開かない。カメラを向けるだけ。

コード: apps/MiniCPMVisualIntel
モデル: mlboydaisuke/MiniCPM-V-4.6-CoreAI(Apache-2.0)

同じ配線でQwen3-VL-2Bを動かす版もあります: Examples/AskVLM

ビルドするには、coreai-modelsapps/coreai-pipelined-static-inputs.patchapps/coreai-pipelined-extra-states.patch を当てます。画像バッファと、このモデルが使う追加の内部状態のためのものです。

参考:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?