WWDC 2026 のあと、私自身が手元の小さなアプリで、試しに要約機能をオンデバイス推論に寄せてみました。無償の枠が広がったので、これまでクラウドに払っていたぶんを端末内に移せないかと考えたのです。
短い文章の要約は、驚くほど自然に端末内で完結しました。ところが、専門用語の混じった長文や、複数の話題が入り組んだ文章を渡すと、要約が表層をなぞるだけで芯を外すことが増えました。
「無料だから全部オンデバイスに」という最初の期待は、ここで一度崩れます。かといって「品質が不安だから全部クラウド」に戻すと、せっかく広がった無償枠を使わずに、短い要約にまでネットワークの往復を払うことになります。
ここでも答えは、どこまで端末内で粘り、どこからクラウドへ渡すかの線引きです。そしてこの線は、感覚で引くと必ずどちらかに寄りすぎます。計測して引くべき線です。
三つの物差しで分かれ目を見る
オンデバイスとクラウドのどちらを使うかは、ひとつの基準では決められません。私は三つの軸を分けて見るようにしました。
ひとつめは品質です。端末内モデルの出力が、そのタスクで実用に足りているか。ここは数字にしづらい部分ですが、後で触れる比較で代理の指標を作れます。
ふたつめはレイテンシです。意外に思われるかもしれませんが、短いタスクではオンデバイスのほうが速いことがよくあります。ネットワークの往復がない分、最初の一文字が返るまでが速い。逆に長い生成では、端末の処理能力が頭打ちになり、クラウドのほうが速くなる場合があります。タスクの長さで逆転する、という感覚が大事です。
みっつめはコストです。オンデバイスは無償枠の範囲なら実質ゼロ。クラウドは呼ぶたびに積み上がります。ただし「無料」を最優先にして品質を犠牲にすると、ユーザーが離れて結局高くつきます。コストは単独で見ず、品質とセットで見るべき軸です。
この三つを、自分のアプリの代表的なリクエストに対して一度きちんと測ると、「この種類のタスクは端末内で十分」「この種類はクラウドに渡したほうがいい」という分かれ目が、推測ではなく根拠を持って見えてきます。
一度だけ、自分のアプリで測る
三つの軸は、頭で考えるより一度測ったほうが早く腑に落ちます。難しい計測は要りません。代表的なリクエストを10件ほど用意し、端末内とクラウドの両方に同じ入力を渡して、処理時間と出力を並べて記録するだけです。
// 同じ入力を両方に渡し、レイテンシと出力を記録する一回限りの計測
func benchmark ( _ inputs: [ String ]) async {
for text in inputs {
let t0 = Date ()
let local = try? await onDevice. summarize (text)
let localMs = Date (). timeIntervalSince (t0) * 1000
let t1 = Date ()
let cloud = try? await cloudFallback. summarize (text)
let cloudMs = Date (). timeIntervalSince (t1) * 1000
let faster = localMs < cloudMs ? "端末内" : "クラウド"
print ( String ( format : "len=%4d 端末内=%.0fms クラウド=%.0fms 速い=%@" ,
text. count , localMs, cloudMs, faster))
}
}
私のアプリでこれを回したとき、入力が短いうちは端末内が一貫して速く、目安として 800 文字を超えたあたりからクラウドが追い越しました。品質のほうは、出力を10件並べて読み、端末内で芯を外したものに印を付けるだけで、どの長さから危うくなるかの感覚がつかめます。
この計測は、線を引く前に一度やればよく、毎回は要りません。数字を手元に持っておくと、後の閾値調整が「なんとなく」ではなくなります。
信頼度でフォールバックを切り替える
線引きが見えたら、それを実装に落とします。ここで避けたいのは、タスクの種類で固定的にどちらかへ振り分けてしまうことです。同じ「要約」でも、短く平易な入力なら端末内で十分、長く複雑な入力ならクラウドが要る。種類ではなく、一件ごとの難しさで切り替えたいのです。
そこで、まず端末内で試し、その結果に自信が持てないときだけクラウドへ回す、という二段構えにします。
import FoundationModels
// オンデバイスで試し、信頼度が低いときだけ Gemini へ回す
struct AdaptiveSummarizer {
let onDevice: SystemLanguageModel
let cloudFallback: GeminiClient
func summarize ( _ text: String ) async throws -> Summary {
// まず端末内で試す
let local = try await onDevice. summarize (text)
// 信頼度が十分高ければ、そのまま採用(クラウドを呼ばない)
if local.confidence >= 0.7 && ! needsCloud (text) {
return Summary ( text : local. text , source : .onDevice)
}
// 自信が持てない、または明らかに端末向きでない入力だけクラウドへ
let cloud = try await cloudFallback. summarize (text)
return Summary ( text : cloud. text , source : .gemini)
}
// 入口で「明らかに端末では厳しい」入力を弾く軽い判定
private func needsCloud ( _ text: String ) -> Bool {
let longEnough = text. count > 1_200
let manyTopics = text. components ( separatedBy : " \n\n " ). count > 6
return longEnough && manyTopics
}
}
enum SummarySource { case onDevice , gemini }
struct Summary { let text: String ; let source: SummarySource }
ここで二つの判定を組み合わせています。needsCloud は入口での粗いふるい分けで、長くて話題が多い入力を、端末内で試す前にクラウドへ回します。明らかに端末では厳しい入力に、わざわざ一度オンデバイス推論を走らせてから捨てるのは無駄だからです。confidence はその後の細かい判定で、端末内で試した結果が頼りないときにクラウドへ落とします。
この二段構えのおかげで、クラウドを呼ぶのは「本当に端末内では足りなかったリクエスト」だけに絞られます。短く平易なリクエストはネットワークに触れずに完結し、難しいリクエストだけが無償枠の外側へ出ていきます。
どれだけ無償枠で賄えたかを振り返る
フォールバックを入れたら、それが意図どおりに効いているかを後から確かめたくなります。クラウドへ回った割合が高すぎれば、端末内の信頼度の閾値が厳しすぎるのかもしれませんし、逆に低すぎて品質の苦情が来るなら、閾値が甘いのかもしれません。
そこで、要約のたびにどちらで処理したかを記録し、定期的に集計します。
// フォールバックの実績を記録し、無償枠でどこまで賄えたか振り返る
actor FallbackMeter {
private var onDevice = 0
private var cloud = 0
func record ( _ source: SummarySource) {
switch source {
case .onDevice : onDevice += 1
case .gemini : cloud += 1
}
}
func report () -> String {
let total = onDevice + cloud
guard total > 0 else { return "まだ実績がありません" }
let onDeviceRate = Double (onDevice) / Double (total) * 100
return String (
format : "端末内 %.1f%%(%d件) / クラウド %.1f%%(%d件)" ,
onDeviceRate, onDevice, 100 - onDeviceRate, cloud
)
}
}
私の小さな要約機能では、この値を見ながら閾値を一度だけ調整しました。最初は confidence >= 0.85 と厳しめにしていて、クラウド側が約40%まで膨らんでいました。出力を読み比べると、端末内の 0.7 台の要約でも実用には十分なものが多く、閾値を 0.7 に下げたところ、クラウド呼び出しが体感で約50%まで減りました。品質の苦情も増えませんでした。
大事なのは、この閾値を一度決めたら固定でよいわけではない、という点です。端末内モデルは OS の更新で性能が変わりますし、扱うコンテンツの傾向も時間とともにずれます。集計を手元に残しておけば、フォールバック率が静かに上がってきたときに気づけます。これは本番運用で効いてくる注意点で、気づいた時点で閾値を調整すれば回避できます。
線引きは固定ではなく、計測で更新する
オンデバイス推論の無償枠が広がったことは、個人開発にとって素直にありがたい変化です。ただ、その恩恵を最大限に受け取れるかは、「どこまで端末内で粘るか」をきちんと測れているかにかかっています。
全部を端末内に寄せれば品質で取りこぼし、全部をクラウドに戻せば無償枠を捨てる。私は、次の順序で進めることを推奨します。
代表的なリクエストで品質・レイテンシ・コストの三点を測ります。
信頼度ベースのフォールバックとして実装します。
フォールバック率を集計し、閾値を更新します。
この一巡を持っておけば、端末内モデルが進化しても、クラウド料金が変わっても、線を引き直せます。個人的にも、測って切り替える仕組みがあるだけで、判断がずいぶん楽になりました。
無料の枠を活かしきることと、品質を妥協しないことは、対立しません。一件ごとに「これは端末で足りるか」を測って切り替える仕組みがあれば、両方を同時に手にできると考えています。