
2026/02/18 8:21
Flutter で LLM をローカルに実行し、200 ms 未満のレイテンシを実現
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Edge‑Veda は、クラウドに依存せずテキスト・ビジョン・音声・検索強化生成(RAG)を提供する Flutter 用の完全オフライン・オンデバイス AI ランタイムです。スタックは約 22,700 行のコード、50 の C API 関数、および 32 の Dart SDK ファイルで構成されており、Metal GPU を使用して iOS 上でネイティブに実行され、Android CPU サポートを準備中です。
主な機能は次のとおりです:
- 永続的なテキスト/ビジョン推論ワーカー(ストリーミングトークン生成、多輪チャット(自動要約)、GBNF による構造化 JSON 出力、ツール/関数呼び出し、埋め込み(L2 正規化)および RAG 用の純粋 Dart HNSW ベクトルインデックス)。
- リアルタイム音声→テキスト用 Whisper サポート。
ランタイム監視は計算予算(p95 レイテンシ、バッテリー消費、熱/メモリ上限)を宣言し、優先度ベースの劣化でデバイス制約に適応し、JSONL フライトトレースを記録し、単一デバイスで 12 分以上の長時間セッション安定性を保証します。Smart Model Advisor は iPhone モデル、RAM、およびチップティアを推論し、メモリ適合性を見積もり、モデルを適合度/品質/速度/コンテキストでスコアリングし、デバイスごとに最適な
EdgeVedaConfig を生成します。
A16 Bionic iPhone(6 GB RAM)でのベンチマークでは、テキスト生成が 42–43 トークン/秒、定常メモリが 400–550 MB、RAG エンドツーエンドレイテンシが 305–865 ms、ビジョンソークテストはクラッシュなしで 12.6 分、STT は 3 秒チャンクあたり 670 ms(77 MB Whisper モデル)です。
クイックスタート:
dependencies: edge_veda: ^2.1.0 EdgeVedaConfig cfg = EdgeVedaConfig( modelPath, contextLength, useGpu); EdgeVeda.generate(...); EdgeVeda.sendWithTools(...); WhisperSession.start(...);
サポートされる事前設定済みモデルには、Llama 3.2 1B Instruct、Qwen3 0.6B、Phi 3.5 Mini Instruct、Gemma 2 2B Instruct、TinyLlama 1.1B Chat、SmolVLM2 500M(ビジョン)、MiniLM L6 v2(埋め込み)、Whisper Tiny English/Base があります。
このプロジェクトは iOS 上で Metal GPU を使用して完全に検証されており、シミュレータは CPU スタブ経由で実行されます。Android CPU の骨格が存在し、Vulkan 統合も計画されています。Edge‑Veda は Apache 2.0 ライセンスのオープンソースで、llama.cpp および whisper.cpp をベースにしており、Android 検証、QoS 戦略、トレースツール、モデルサポート、およびサンプルアプリへの貢献を歓迎します。
結果として、プライバシー保護・低遅延の AI スタックが実現し、Flutter 開発者は堅牢な AI 機能を直接アプリに組み込み、企業向けにサーバーコストとレイテンシを削減できます。
本文
Edge‑Veda
Flutter 用のオンデバイス AI ランタイム。テキスト・ビジョン・音声・RAG を実際のスマートフォン上で持続可能に動かすために設計されており、既定ではプライベートです。
- 約 22,700 行のソースコード
- 50 個の C API 関数
- 32 本の Dart SDK ファイル
- クラウドへの依存はゼロ
Edge‑Veda が存在する理由
現行のオンデバイス AI デモは実際に使用するとすぐに落ちます。
| 問題 | 詳細 |
|---|---|
| サーマルスロットリング | スループットが急激に低下 |
| メモリスパイク | 静かにクラッシュ |
| 60 秒以上のセッション | 不安定になる |
| ランタイム挙動の可視化不足 | デバッグがほぼ不可能 |
Edge‑Veda は、オンデバイス AI を「実行できる」だけでなく、予測可能・観測可能・持続可能にすることを目的としています。
Edge‑Veda とは何か
Edge‑Veda は監督型のオンデバイス AI ランタイムです。以下を実現します。
- テキスト・ビジョン・音声モデルを完全にデバイス上で走らせる
- 長時間セッション中もモデルを保持する
- サーマル、メモリ、バッテリーの圧力に自動適応
- クラッシュではなくランタイムポリシーを適用
- デバッグ・分析のために構造化された観測情報を提供
- 構造化出力、関数呼び出し、埋め込み、RAG をサポート
- 既定でプライベート(推論時にネットワーク呼び出しは行わない)
Edge‑Veda の違い
Edge‑Veda は一瞬のベンチマークではなく「時間をかけた挙動」に焦点を当てています。
- 長寿命ランタイムと永続的なワーカー
- 物理デバイス制限下で AI を監督するシステム
- 故障ではなく優雅に劣化するランタイム
- 観測可能・デバッグ可能なオンデバイス AI レイヤー
- 推論、音声、ツール、検索を統合した完全なオンデバイス AI スタック
現在の機能
コア推論
- 永続的テキスト/ビジョン推論ワーカー(モデルは一度ロードしてメモリに保持)
- プルベース構造でトークン生成をストリーミング
- コンテキストオーバーフロー時に自動サマリー付きのマルチターンチャット管理
- Llama 3 Instruct、ChatML、Qwen3/Hermes、汎用テンプレート
音声 → テキスト
- whisper.cpp(Metal GPU 加速)を利用したオンデバイス音声認識
- 3 秒間隔でリアルタイムストリーミング文字起こし
- 48 kHz ネイティブオーディオ取得、16 kHz 自動ダウンサンプリング
- WhisperWorker isolate によりノンブロッキング転写(iPhone Metal GPU で約 670 ms / チャンク)
構造化出力 & 関数呼び出し
- GBNF 文法制約付き JSON 出力生成
・ToolDefinition
・スキーマ検証によるツール/関数呼び出しToolRegistry
で最大ラウンドを設定可能なマルチラウンドツールチェーンsendWithTools()- 文法制約付き生成は
sendStructured()
埋め込み & RAG
によるテキスト埋め込み(L2 正規化)ev_embed()- ソフトマックスエントロピーからのトークン信頼度スコア
- 信頼度が閾値を下回った際にクラウドハンドオフシグナル
- 純 Dart HNSW ベース
(余弦類似・JSON 永続化)VectorIndex
:埋め込み → 検索 → 注入 → 生成RagPipeline
ランタイム監督
- コンピュート予算契約(p95 レイテンシ、バッテリー消費、サーマル・メモリ上限)
- アダプティブ予算プロファイル:デバイス性能に自動調整
- 中央スケジューラが優先度ベースで同時ワークロードを仲裁
- サーマル・メモリ・バッテリー感知ランタイムポリシー(ヒステリシス付き)
- フレーム処理は backpressure 制御(drop‑newest、キュー永続化なし)
- JSONL 形式のパフォーマンストレースでオフライン解析可能
- 長時間セッション安定性検証(12 + min、クラッシュゼロ、モデルリロードゼロ)
スマート モデルアドバイザー
:iPhone 型番・RAM・チップ世代・ティア判定DeviceProfile
:パラメータ数からの正確なメモリ予測(キャリブレーション済)MemoryEstimator
:適合度、品質、速度、コンテキストなど 0–100 点でスコアリングModelAdvisor- 使用ケース別重み付き推奨(チャット・推論・ビジョン・音声・高速)
- モデル/デバイスペアごとに最適
を生成(コンテキスト長、スレッド数、メモリ上限)EdgeVedaConfig - ダウンロード前の即時適合チェック:
、ディスク空き確認:canRun()checkStorageAvailability()
アーキテクチャ
Flutter App (Dart) │ ├─ ChatSession ── チャットテンプレート・コンテキストサマリー・ツール呼び出し ├─ WhisperSession ── 3 秒音声チャンクで STT ストリーミング ├─ RagPipeline ── 埋め込み → 検索 → 注入 → 生成 ├─ VectorIndex ── HNSW ベースベクトル検索+永続化 │ ├─ EdgeVeda ── generate(), generateStream(), embed(), describeImage() │ ├─ StreamingWorker ── 永続 isolate、テキストモデル保持 ├─ VisionWorker ┰─ 永続 isolate、VLM(約 600 MB)保持 ├─ WhisperWorker ── 永続 isolate、whisper モデル保持 │ ├─ Scheduler ── 予算強制・優先度ベース劣化 ├─ EdgeVedaBudget ── p95, バッテリー, サーマル, メモリ制約宣言 ├─ RuntimePolicy ── サーマル/バッテリー/メモリ QoS + ヒステリシス ├─ TelemetryService ── iOS でサーマル・バッテリー・メモリ取得 ├─ FrameQueue ── カメラフレームの drop‑newest backpressure ├─ PerfTrace ── JSONL フライトレコーダー(オフライン解析用) ├─ ModelAdvisor ── デバイス感知モデル推奨+4D スコアリング ├─ DeviceProfile ── sysctl で iPhone 型番・RAM・チップ検出 ├─ MemoryEstimator ── モデルメモリ予測(キャリブレーション済) │ └─ FFI Bindings ── 50 C 関数 via DynamicLibrary.process() │ ├─ XCFramework (libedge_veda_full.a) │ ├─ engine.cpp ── テキスト推論 + 埋め込み + 信頼度(llama.cpp ラッパー) │ ├─ vision_engine.cpp ── ビジョン推論(libmtmd ラッパー) │ ├─ whisper_engine.cpp ── STT(whisper.cpp ラッパー) │ ├─ memory_guard.cpp ── クロスプラットフォーム RSS モニタリング │ ├─ llama.cpp b7952 ── Metal GPU, ARM NEON, GGUF (未改変) │ └─ whisper.cpp v1.8.3 ── Metal GPU, ggml バックエンド(未改変)
主な設計制約
Dart FFI は同期的であるため、
llama.cpp を直接呼ぶと UI がフリーズします。すべての推論はバックグラウンド isolate で実行し、ネイティブポインタを isolate 境界で渡さないようにしています。ワーカーは永続的なコンテキストを保持し、モデルは一度ロードしてセッション全体でメモリに残ります。
クイックスタート
インストール
# pubspec.yaml dependencies: edge_veda: ^2.1.0
テキスト生成
final edgeVeda = EdgeVeda(); await edgeVeda.init(EdgeVedaConfig( modelPath: modelPath, contextLength: 2048, useGpu: true, )); // ストリーミング await for (final chunk in edgeVeda.generateStream('Explain recursion briefly')) { stdout.write(chunk.token); } // ブロッキング final response = await edgeVeda.generate('Hello from on-device AI'); print(response.text);
マルチターン会話
final session = ChatSession( edgeVeda: edgeVeda, preset: SystemPromptPreset.coder, ); await for (final chunk in session.sendStream('Write hello world in Python')) { stdout.write(chunk.token); } await for (final chunk in session.sendStream('Now convert it to Rust')) { stdout.write(chunk.token); } print('Turns: ${session.turnCount}'); print('Context: ${(session.contextUsage * 100).toInt()}%');
関数呼び出し
final tools = ToolRegistry([ ToolDefinition( name: 'get_time', description: 'Get the current time', parameters: { 'type': 'object', 'properties': { 'timezone': {'type': 'string', 'enum': ['UTC', 'EST', 'PST']}, }, 'required': ['timezone'], }, ), ]); final session = ChatSession( edgeVeda: edgeVeda, tools: tools, templateFormat: ChatTemplateFormat.qwen3, ); final response = await session.sendWithTools( 'What time is it in UTC?', onToolCall: (call) async { if (call.name == 'get_time') { return ToolResult.success( toolCallId: call.id, data: {'time': DateTime.now().toIso8601String()}, ); } return ToolResult.failure(toolCallId: call.id, error: 'Unknown tool'); }, );
音声 → テキスト
final session = WhisperSession(modelPath: whisperModelPath); await session.start(); session.onSegment.listen((segment) { print('[${segment.startMs}ms] ${segment.text}'); }); final audioSub = WhisperSession.microphone().listen((samples) { session.feedAudio(samples); }); await session.flush(); await session.stop(); print(session.transcript);
埋め込み & RAG
// 埋め込み生成 final result = await edgeVeda.embed('On-device AI is the future'); print('Dimensions: ${result.embedding.length}'); // ベクトルインデックス構築 final index = VectorIndex(dimensions: result.embedding.length); index.add('doc1', result.embedding, metadata: {'source': 'readme'}); await index.save('/path/to/index.json'); // RAG パイプライン final rag = RagPipeline( edgeVeda: edgeVeda, index: index, config: RagConfig(topK: 3), ); final answer = await rag.query('What is Edge‑Veda?'); print(answer.text);
継続的ビジョン推論
final visionWorker = VisionWorker(); await visionWorker.spawn(); await visionWorker.initVision( modelPath: vlmModelPath, mmprojPath: mmprojPath, numThreads: 4, contextSize: 2048, useGpu: true, ); final result = await visionWorker.describeFrame( rgbBytes, width, height, prompt: 'Describe what you see.', maxTokens: 100, ); print(result.description);
ランタイム監督
Edge‑Veda は以下のシグナルを継続的にモニタリングし、動的に適応します。
| シグナル | 値 |
|---|---|
| デバイスサーマル状態 | nominal / fair / serious / critical |
| 利用可能メモリ | |
| バッテリー残量 & Low Power Mode |
動的適応表
| QoS レベル | FPS | 解像度 | トークン数 | トリガー |
|---|---|---|---|---|
| Full | 2 | 640 px | 100 | 無圧力 |
| Reduced | 1 | 480 px | 75 | サーマル警告 / バッテリー <15% / メモリ <200 MB |
| Minimal | 1 | 320 px | 50 | サーマル serious / バッテリー <5% / メモリ <100 MB |
| Paused | 0 | — | 0 | サーマル critical / メモリ <50 MB |
サーマルスパイクは即時に対処し、回復は 60 秒ごとに 1 レベルずつ行われます。paused → full の完全回復には最大 3 分かかります。
コンピュート予算契約
ランタイム保証を宣言し、スケジューラがそれを強制します。
// オプション 1:アダプティブ(デバイス性能に自動調整) final scheduler = Scheduler(telemetry: TelemetryService()); scheduler.setBudget(EdgeVedaBudget.adaptive(BudgetProfile.balanced)); // オプション 2:静的(明示値) scheduler.setBudget(const EdgeVedaBudget( p95LatencyMs: 3000, batteryDrainPerTenMinutes: 5.0, maxThermalLevel: 2, ));
ワークロードを優先度で登録します。
scheduler.registerWorkload(WorkloadId.vision, priority: WorkloadPriority.high); scheduler.registerWorkload(WorkloadId.text, priority: WorkloadPriority.low); scheduler.registerWorkload(WorkloadId.stt, priority: WorkloadPriority.low); scheduler.start();
予算違反を監視します。
scheduler.onBudgetViolation.listen((v) { print('${v.constraint}: ${v.currentValue} > ${v.budgetValue}'); });
アダプティブプロファイル
| プロファイル | p95 マルチプレイヤー | バッテリー | サーマル | 用途 |
|---|---|---|---|---|
| Conservative | 2.0x | 0.6x(厳格) | Floor 1 | 背景ワークロード |
| Balanced | 1.5x | 1.0x(一致) | Floor 2 | デフォルト |
| Performance | 1.1x | 1.5x(寛容) | — | レイテンシセンシティブ |
すべての数値は A16 Bionic、6 GB RAM、iOS 26.2.1 の物理 iPhone 上で測定。詳細は
BENCHMARKS.md を参照。
ベンチマーク
| カテゴリ | メトリック | 値 |
|---|---|---|
| テキスト生成 | スループット | 42–43 tok/s |
| 定常メモリ | 400–550 MB | |
| マルチターン安定性 | 10+ ターンで劣化なし | |
| RAG | 生成速度 | 42–43 tok/s |
| ベクトル検索 | <1 ms | |
| エンドツーエンド取得 | 305–865 ms | |
| ビジョン(ソークテスト) | 持続時間 | 12.6 min |
| 処理フレーム数 | 254 | |
| p50 / p95 / p99 レイテンシ | 1,412 / 2,283 / 2,597 ms | |
| クラッシュ/モデルリロード | 0 / 0 | |
| 音声 → テキスト | ラグ(p50) | 約 670 ms/3 s チャンク |
| モデルサイズ | 77 MB | |
| ストリーミング | リアルタイム | |
| メモリ最適化 | KV キャッシュ | ~64 MB → ~32 MB(Q8_0) |
| 定常メモリ | ~1,200 MB ピーク → 400–550 MB |
観測性
組み込みパフォーマンスフライトレコーダーはフレーム単位で JSONL を書き出します。
- ステージ別タイミング(画像エンコード / プロンプト評価 / デコード)
- ランタイムポリシー遷移(QoS レベル変更)
- フレームドロップ統計
- メモリ・サーマルテレメトリー
tools/analyze_trace.py でオフライン解析(p50/p95/p99、スループットチャート、サーマルオーバーレイ)。
対応モデル
ModelRegistry に事前設定されたダウンロード URL と SHA‑256 チェックサム。
| モデル | サイズ | タイプ | 用途 |
|---|---|---|---|
| Llama 3.2 1B Instruct | 668 MB | テキスト(Q4_K_M) | 一般チャット、指示追従 |
| Qwen3 0.6B | 397 MB | テキスト(Q4_K_M) | 関数呼び出し |
| Phi 3.5 Mini Instruct | 2.3 GB | テキスト(Q4_K_M) | 推論、長文コンテキスト |
| Gemma 2 2B Instruct | 1.6 GB | テキスト(Q4_K_M) | 汎用目的 |
| TinyLlama 1.1B Chat | 669 MB | テキスト(Q4_K_M) | 軽量高速推論 |
| SmolVLM2 500M | 417 MB + 190 MB | ビジョン(Q8_0 + F16) | 画像説明 |
| All MiniLM L6 v2 | 46 MB | 埋め込み(F16) | RAG 用文書埋め込み |
| Whisper Tiny English | 77 MB | 音声(F16) | STT 軽量 |
| Whisper Base English | 142 MB | 音声(F16) | 高精度 STT |
llama.cpp と互換性のある GGUF モデルはファイルパスで読み込めます。
プラットフォーム状況
| プラットフォーム | GPU | ステータス |
|---|---|---|
| iOS(デバイス) | Metal | 完全検証済み |
| iOS(シミュレータ) | CPU | 動作中(Metal スタブ、マイクなし) |
| Android | CPU | スキャフォールド済み、検証保留 |
| Android (Vulkan) | – | 計画中 |
プロジェクト構成
edge-veda/ ├─ core/ │ ├─ include/edge_veda.h C API(50 関数、858 LOC) │ ├─ src/engine.cpp テキスト推論 + 埋め込み(1,173 LOC) │ ├─ src/vision_engine.cpp ビジョン推論(484 LOC) │ ├─ src/whisper_engine.cpp STT(290 LOC) │ ├─ src/memory_guard.cpp メモリ監視(625 LOC) │ └─ third_party/ │ ├─ llama.cpp/ llama.cpp b7952(git サブモジュール) │ └─ whisper.cpp/ whisper.cpp v1.8.3(git サブモジュール) ├─ flutter/ │ ├─ lib/ Dart SDK(32 ファイル、11,750 LOC) │ ├─ ios/ Podspec + XCFramework │ ├─ android/ Android プラグイン(スキャフォールド済み) │ ├─ example/ デモアプリ(10 ファイル、8,383 LOC) │ └─ test/ ユニットテスト(253 LOC、14 テスト) ├─ scripts/ │ └─ build-ios.sh XCFramework ビルドパイプライン(406 LOC) └─ tools/ └─ analyze_trace.py JSONL 分析スクリプト(1,797 LOC)
ビルド方法
必要環境
- macOS + Xcode 15+(Xcode 26.1 でテスト済み)
- Flutter 3.16+(Flutter 3.38.9 でテスト済み)
- CMake 3.21+
XCFramework のビルド
./scripts/build-ios.sh --clean --release
llama.cpp、whisper.cpp と Edge‑Veda C コードをデバイス(arm64)とシミュレータ(arm64)向けにコンパイルし、静的ライブラリを単一 XCFramework にマージします。
デモアプリの実行
cd flutter/example flutter run
チャット(ツール呼び出し付き)、ビジョン(連続カメラスキャン)、STT(ライブマイク文字起こし)と設定画面(モデル管理、デバイス情報)が含まれます。
ロードマップ(方向性)
- Android 持続ランタイム検証(CPU + Vulkan GPU)
- テキスト→音声統合
- セマンティック認識 API(イベント駆動ビジョン)
- 観測性ダッシュボード(ローカルトレースビューア)
- NPU/CoreML バックエンド対応
対象ユーザー
Edge‑Veda は以下のチーム向けに設計されています。
- オンデバイス AI アシスタント
- 連続的知覚アプリ
- プライバシー重視 AI システム
- 長時間稼働するエッジエージェント
- 音声優先アプリケーション
- 規制対象またはオフラインファーストなアプリ
コントリビューション
貢献歓迎です。始め方:
- リポジトリをフォーク
- フィーチャーブランチ作成(例:
)git checkout -b feature/your-feature - 変更とテスト実行
(SDK) &dart analyze
(デモ)flutter analyzecd flutter && flutter test
- 説明付きコミット
- PR を作成し、何が変わったかを記述
興味のある分野
- プラットフォーム検証 – Android CPU/Vulkan 実機テスト
- ランタイムポリシー – 新 QoS 戦略、サーマル適応改善
- トレース解析 – 可視化ツール、異常検知、回帰追跡
- モデルサポート – 追加 GGUF モデル、量子化プロファイルテスト
- サンプルアプリ – 文書スキャナー、音声アシスタント、ビジュアル QA
コード規約
- Dart:標準フォーマットに従う
- C++:
の既存スタイルを踏襲core/src/ - FFI 呼び出しは isolate 内で実行(メインスレッドでは絶対に呼ばない)
- 新 C API は Podspec シンボルホワイトリストに追加
ライセンス
Apache 2.0
llama.cpp と whisper.cpp(Georgi Gerganov 氏および貢献者)のソースを利用しています。