Apple AIやローカルAI、フィジカルAIの開発を考えている方の役に立つかもしれない、ノードベースのノーコードAIテスト環境を開発しました。

ノードを結んでプロンプトなどを入れるだけで、Apple IntelligenceのFoundation Models(デバイス内LLM)を試すことができるプレイグラウンドです。チャットや文法判定、固有名詞の抽出なんかを組んでみましたが、数分で動き出しますし、プロンプトや入力データのA/Bテストも簡単に行えます。
ユーザーガイドはまだないんですが、さわれば使い方はわかるかと思いますのでどうぞ遊んでみてください。
以下、Qiitaに投稿した記事を転載します。
先日、macOSとiOS用のローカル翻訳アプリzenバベルをリリースしました。
翻訳エンジンは機械学習モデルのApple Translation Frameworkと、ローカルLLMのApple Intelligence Foundation Models。どちらもシステム標準の機械学習とAIですが、このAI開発に手を焼きました。
Apple IntelligenceのローカルLLM、Foundation Modelsの要素数は3B。これで翻訳、作文、校正から画像生成まで、ローカルモデルでこなしてしまう。蒸留などを含めた最適化が効きまくってるのでしょう。使ってみたくなりますよよね。でも、Web APIがあるわけでもなく、OpenAIのようなプレイグラウンドも用意されていません。試すにはアプリを作るしかないので、実際どんなことができるのかわからない方も多いはず。実際私も、zenバベル(の前身のpre-Babel Lens)を作るまでは、まともに翻訳ができるのかどうかわかりませんでした。
実際にアプリに組み込んでみると、機械学習モデルと比べると圧倒的に遅くて信頼性が低いものの、3B程度の軽量ローカルLLMにしては使えるものでした。多少怪しくても翻訳ぐらいならできてしまうし、ウォームアップさえ終わっていれば軽いタスクなら0.2秒ほどで結果を返してくることもある。この速さは魅力です。セッションの組み方次第ではヒューリスティックな処理をアプリに組み込むことも可能かもしれません。
しかし試すにはハードルが高い。何せアプリを書かないと試せない。実際、今作っているローカル翻訳アプリのzenバベルに、翻訳品質向上のために固有名詞を抽出するApple AIセッションを入れようと考えていますが、そのために、すでに動いているアプリの処理フローに新しいAIセッションを埋め込んで、他のAIセッションと協調させる(後で書きますが、ローカルAIでは処理の直列化がものすごく重要です)ところまで用意してから「やっぱり使い物にならなかった」ではダメージが大きすぎます。だからこそプレイグラウンドで、固有名詞の抽出にはどの程度のコストがかかるのか、そもそも信頼できるアウトプットは得られるのか、前処理があれば使えるのかを試しておきたくなるわけです。
そこで、Apple AI用のプレイグラウンドを作ることにしました。私自身がプログラム書きじゃないので、ノーコードのノードモデラーがいい。セッションはschemaとApple Intelligenceの@Guide/@Generableを切り替え、ノードエッジはSchemaかObjectを受け渡す――みたいな構想です。
FM-Deck
4日ほどCodexを使って作っていたのですが、思ったよりも早くできました。
カードの種類は今のところ以下の7種類。
- input: セッションへの入力を受け取るカード(親のSwiftから呼び出される際の引数にもなる)
- schema: LLMの出力スキーマを定義するモデラー
- session: LLMセッション。message、Schema、promptを受け、schemaで定義されたjsonを出すカード
- constant: 定数のObjectを定義するカード
- output: 最終出力を受け取るカード(親のSwiftプロジェクトにreturnする部分)
- split-router: ObjectやSchemaのパラメータを取り出すことができるカード
- merge-router: 複数のObjectを一つに束ねるカード
ノードを接続して実行ボタンをクリックすると、セッションが実行されてOutputにつながっていれば、結果が出ます。
Schemaモデラーはこんなインターフェイスになっていて、jsonをインポートすることも考えています。inputやconstantの定義部分も同じインターフェイスを流用しています。

セッションを実行すると、Sessionカードは持続時間を計測します(ベンチマーク用ですね)。Sessionカードはtimeoutも指定できます。LLMの処理は決定論的なアルゴリズムと異なり、プロンプトや入力データのわずかな差で処理の時間が突然伸びてしまうことがあります。Apple AIの場合はガードレール違反の時に、10秒、20秒と待たされることが多いのですが、アプリケーションのユーザー体験の上で、どの程度までなら待てるかを設計するためにもFM-Deckは使えます。

重い処理なら分割しよう、プロセッサもたくさんあるんだからスレッドセーフな設計にして並列実行しよう。非同期で結果が帰るのを待とう――という、伝統的なプログラミングの常識が通用しないのもLLMの怖いところです。
実用に供されているローカルLLMの中で最軽量の部類に属する1Apple IntelligenceのFoundation Modelsですが、私のMacBook M2で二つ並行させようとすると、どんなに軽いセッションでもどちらかがタイムアウトに追い込まれます。機械学習モデルのノリで使おうとすると、3Bでも全然軽くない。Instrumentで見てると、セッションが走ると櫛のようにANEやGPUリソースを専有してるのがわかります。隙間はあるけど、間違って重なったら転けるってことでしょう。
たとえば、ニュースから固有名詞の抽出を行なって元の文脈に対する重要性に応じた順番で並べ替えるような処理をする場合(ニュースの情報表示なんかにも使えますね)、まず名前を抽出して配列を作り、その後で並べ替えようと考える方は多いかと思います。直前の処理なら原文がキャッシュに残せるし、名前を抽出しながら順番をつけていくのは煩雑だし――はい、罠です。LLMは、与えた情報と出力の長さに応じて線形に負荷が増えるような、可愛いものではありません。
ここに二つの固有名詞抽出セッションを用意しました。

緑色がニュース、青緑色(Appleの開発ではティールと呼ぶ色です)がスキーマ、水色がAIの処理セッションです。見ての通り、同じニュースを入力して同じスキーマで出力するプログラムです。違いはプロンプトだけ。ウォームアップが終わっていれば処理は1.5秒〜2.2秒ほどで終わります。セッションのロードタイムはほとんどありません。小さなヒューリスティック処理には期待が持てます。しかし予想は裏切ります。
上のセッションのプロンプト:あなたは固有名詞抽出装置です。userMessageの文から固有名詞を抽出して、names:["name","name2"]に出力します。下のセッションのプロンプト:あなたは固有名詞抽出装置です。userMessageの文から固有名詞を抽出して、names:["name","name2"]に出力します。nameは、userMessageで言及された重要さの順に並べます。
見ての通り下のセッションは重要度ソートが入っています。普通に考えれば下の方が重いはず。しかしApple AIは下の方が早く終わるんです。結果も上より概ね正しい。なぜなのか今もってわかりません。何度やっても、並べ替える方が早い。決断に至るコンテクストが充足しているからなのかもしれない――まるで人間のように。
Release 0.1.1にはこのセッションモデルのサンプルも置いておきましたので、ぜひ遊んでみてください。
これからFM-DeckにはApple Intelligenceの特徴である@Guide/@Generable形式のセッションや、Apple Vision Frameworkなどの機械学習モデルも追加して、Swiftで記述したセッションのモデルやSchemaを出力できるようにする予定です。おたのしみに!

