Rork が「AI Cloud」を出してきたとき、正直なところ最初は必要性が湧きませんでした。手元の M3 Ultra でのローカル実行は十分に速く、推論にわざわざ月額を払う理由が見当たらなかったからです。
考えを変えたのは、新幹線の中で依存解析を走らせたまま 40 秒近く MacBook を明け渡し、その間に開いていた Xcode がもたついた瞬間でした。速いか遅いかではなく、待っているあいだ自分のマシンが自分のものでなくなる。そこが問題だったのだと気づきました。
以後 1ヶ月、ローカルと AI Cloud を本気で並走させて測りました。レイテンシの分布、月額の実績、発熱、オフラインでの継続性。どの角度から見ても「片方に寄せると損をする」という結論に静かに落ち着いています。以下はその実測と、手元に残った判断の記録です。
AI Cloud は「生成の置き場所」を変える機能
まず誤解されやすい点から整理します。Rork Max の AI Cloud は、出来上がったアプリをホストするためのクラウドではありません。Rork Max がコードや UI を生成する推論パイプラインそのものを、ローカル M シリーズではなくクラウド側で実行するための機能 です。
私が触っている範囲では、AI Cloud は次の3つを置き換えます。
プロンプトを解釈して構造化されたタスクに分解する推論
SwiftUI / Jetpack Compose のコード断片を生成する推論
既存コードを読み込んでリファクタ提案を返す推論
これらをローカルで走らせると、M3 Ultra ですら CPU/GPU/Neural Engine が並走して数十秒の発熱が続きます。出先の M2 MacBook Air では、長い生成タスクが終わるまでファンが回り続けるのが日常でした。AI Cloud はこの「ローカル機器の物理負荷」を逃がす場所として効きます。
アプリ側の推論をどこで走らせるかという話とは別物です。エンドユーザーの端末でオンデバイス推論を優先し、重い処理だけクラウドへ逃がす設計についてはオンデバイスAIを一次経路にして、重い処理だけクラウドへ逃がす推論ルーターをRorkアプリに組む に分けて書いています。本記事はあくまで「開発中に自分が使う推論」の話です。
レイテンシの実測:3種類のタスクで比較
公式の発表値ではなく、私自身が壁紙アプリ群の保守と新規プロト開発で1ヶ月測った値を共有します。サンプル数は各 30 回。条件は M3 Ultra(128GB)と AI Cloud Pro 相当を比較しています。
タスク種別 ローカル M3 Ultra AI Cloud 比率
SwiftUI ビュー生成(小) 4.2 秒 1.1 秒 3.8倍速
既存コードのリファクタ提案 12.7 秒 3.9 秒 3.3倍速
マルチファイル横断の依存解析 38.5 秒 8.2 秒 4.7倍速
ここで重要なのは「単発の短いタスクではローカルも十分実用」という事実です。SwiftUI のビューを 1.1 秒で返してくれるのは魅力的ですが、4.2 秒のローカル応答も体感では遅すぎません。差が決定的になるのはマルチファイル横断の依存解析のような重いタスクで、ローカルだと 38 秒のあいだ MacBook がほぼ占有されます。
私の場合、この「占有される時間」をどう評価するかが AI Cloud を採用する最大の判断軸になりました。コードを書きながら別タスクで Xcode を回したい、Photoshop を開いてアイコンを直したいという日常を、ローカル推論はじわじわ侵食します。AI Cloud は数値で 3〜5 倍速というより、自分の作業を中断しないという意味の体感価値 が大きいです。
平均値では判断を誤る — p50 / p95 で見た分布
上の表は平均値です。ここまでなら「クラウドの方が速い」で話が終わってしまいますが、30 回分の生の値を並べ直したところ、平均が隠していたものが出てきました。
タスク種別 実行環境 p50 p95 最大
SwiftUI ビュー生成(小) ローカル 4.1 秒 4.9 秒 5.2 秒
AI Cloud 0.9 秒 3.6 秒 7.4 秒
リファクタ提案 ローカル 12.4 秒 14.1 秒 15.0 秒
AI Cloud 3.4 秒 9.8 秒 12.6 秒
依存解析 ローカル 38.0 秒 41.2 秒 43.5 秒
AI Cloud 7.6 秒 18.9 秒 24.1 秒
読み取れることは2つあります。
ひとつは、ローカル実行は遅いかわりに、ばらつきがほとんどない という性質です。p50 と p95 の差はどのタスクでも 1〜3 秒に収まっています。4 秒かかると分かっていれば、人はその 4 秒を織り込んで手を動かせます。
もうひとつは、AI Cloud の p95 は p50 の 3 倍前後まで伸びる という点です。分布を時系列で並べ直すと、伸びている回はほぼ例外なく「しばらく触っていなかった後の1回目」でした。数分以上の間隔が空いた後の初回は、SwiftUI 生成でも 3〜7 秒級まで落ちます。2 回目以降は 1 秒前後に戻ります。
この挙動を知らずに使うと、体感が妙にちぐはぐになります。集中して連打しているときは驚くほど速いのに、コーヒーを淹れて戻ってきた最初の1回だけ引っかかる。速いはずのものが不定期に遅れる方が、常に一定して遅いものより作業のリズムを削ります。
私はこれを踏まえて、「重いタスクはクラウド、軽いタスクの単発はローカル」という振り分けに、間隔という軸を足しました 。しばらく席を外したあとの最初の1回は、たとえ軽いタスクでもローカルで返させます。分布を見るまでは思いつかなかった調整です。
実測に使った計測スクリプト
上の分布は、次のスクリプトを 1日 1回回して集めたものです。Rork Max の CLI をどちらのモードでも同じ引数で叩き、経過時間だけを記録します。特別なことはしていませんが、平均ではなくパーセンタイルを出すところと、実行間隔を明示的に空けられるところが要点です。
// bench-ai-cloud.mjs
// 使い方: node bench-ai-cloud.mjs prompts/refactor.txt cloud 30 0
// 引数: プロンプトファイル / モード(local|cloud) / 試行回数 / 各試行前の待機秒数
import { execFile } from "node:child_process" ;
import { readFileSync, appendFileSync } from "node:fs" ;
import { promisify } from "node:util" ;
const run = promisify (execFile);
const [ promptPath , mode , countArg , idleArg ] = process.argv. slice ( 2 );
const count = Number (countArg ?? 30 );
const idleSec = Number (idleArg ?? 0 );
const prompt = readFileSync (promptPath, "utf8" );
const sleep = ( ms ) => new Promise (( r ) => setTimeout (r, ms));
function percentile ( sorted , p ) {
if (sorted. length === 0 ) return NaN ;
const rank = (p / 100 ) * (sorted. length - 1 );
const low = Math. floor (rank);
const high = Math. ceil (rank);
if (low === high) return sorted[low];
return sorted[low] + (sorted[high] - sorted[low]) * (rank - low);
}
const samples = [];
for ( let i = 0 ; i < count; i ++ ) {
// 「間隔が空いた初回」を再現したいときは idleSec を 300 などに設定する
if (idleSec > 0 && i > 0 ) await sleep (idleSec * 1000 );
const startedAt = process.hrtime. bigint ();
try {
await run ( "rork" , [ "max" , "generate" , "--mode" , mode, "--stdin" ], {
input: prompt,
timeout: 120_000 ,
maxBuffer: 32 * 1024 * 1024 ,
});
} catch (err) {
// 失敗も分布の一部なので、捨てずに記録して次へ進む
appendFileSync ( "bench-errors.log" , `${ mode } \t ${ i } \t ${ err . code ?? err . message } \n ` );
continue ;
}
const elapsedSec = Number (process.hrtime. bigint () - startedAt) / 1e9 ;
samples. push (elapsedSec);
appendFileSync (
"bench-samples.tsv" ,
`${ new Date (). toISOString () } \t ${ promptPath } \t ${ mode } \t ${ i } \t ${ elapsedSec . toFixed ( 3 ) } \n `
);
process.stdout. write ( `${ i + 1 }/${ count } ${ elapsedSec . toFixed ( 2 ) }s \n ` );
}
const sorted = [ ... samples]. sort (( a , b ) => a - b);
const mean = samples. reduce (( a , b ) => a + b, 0 ) / samples. length ;
console. log ( ` \n mode=${ mode } n=${ samples . length } (errors=${ count - samples . length })` );
console. log ( `mean=${ mean . toFixed ( 2 ) }s` );
console. log ( `p50=${ percentile ( sorted , 50 ). toFixed ( 2 ) }s` );
console. log ( `p95=${ percentile ( sorted , 95 ). toFixed ( 2 ) }s` );
console. log ( `max=${ sorted [ sorted . length - 1 ]. toFixed ( 2 ) }s` );
失敗した試行を continue で読み飛ばしつつログには残しているのは、成功だけを集めると分布が実態より綺麗になるからです。ネットワークが揺れた回はクラウド側で確実に出ますし、その頻度自体が判断材料になります。
idleSec を 300 にして 10 回だけ回すと、先ほどの「間隔が空いた初回は遅い」という挙動が明確に再現できます。連打で測ったベンチマークは、実際の開発の使い方とはかなり違う数字を出します。ここは自分の手で確かめておく価値がありました。
コストの実数値:個人開発1人月の試算
ここからが個人開発者として一番気になるところです。私が試した範囲では、AI Cloud は推論回数ベースで課金されます。プランによって含まれる無料枠と従量単価が変わるため、月初に予算を決めてからプランを選ぶのが現実的です。プランごとの差額そのものはRorkの料金プランを正直に比較する:無料・Pro・Maxの違いと個人開発者が損しない選び方 に整理しています。
私自身の 1ヶ月の実利用は、おおむね以下のような分布でした。
短い生成(1〜2 秒級):約 1,800 回
中程度の生成(3〜10 秒級):約 420 回
重い依存解析(10 秒以上):約 65 回
これを AI Cloud に丸投げすると、Pro 相当プランの無料枠内に短い生成と中程度の生成の大半が収まり、超過分の重いタスクで月額約 ¥4,800 ほどでした。1日 ¥160 程度で MacBook の発熱と作業中断が消えるなら、迷わず払う という判断を私はします。
逆に、月のクラウド利用が ¥10,000 を超えた週もありました。Rork Max を「気軽に試す」感覚で 1日中触り続けると、生成回数はあっという間に膨らみます。ここは月額固定費として上限を決めて運用する領域だと感じました。
損益分岐を1本の式にしてみる
感覚で払うかどうかを決めていると、月末に驚くことになります。私は次の式で線を引きました。
月あたりの節約時間(時間) = (ローカルp50 − クラウドp50) × 月間実行回数 ÷ 3600
損益分岐時給(円/時) = 月額クラウド課金 ÷ 月あたりの節約時間
重いタスクだけを対象に自分の実測を入れてみます。依存解析はローカル 38.0 秒 / クラウド 7.6 秒、月 65 回でした。
節約時間 = (38.0 − 7.6) × 65 ÷ 3600 ≒ 0.55 時間
損益分岐時給 = 4,800 ÷ 0.55 ≒ 8,730 円/時
この数字だけを見ると、時給 8,730 円で自分の時間を買っている計算になり、割に合わないように見えます。実際、待ち時間の削減だけを根拠に AI Cloud を正当化することはできません 。
私が払う理由は別のところにあります。ローカルで 38 秒占有されると、その 38 秒が終わったあとも集中が戻るまでにもう数分かかります。中断からの復帰コストを 1 回 3 分と控えめに見積もるだけで、月間の実質的な回復時間は 3.8 時間ほどになり、損益分岐時給は 1,260 円まで下がります。
つまり判断の分かれ目は、待ち時間そのものではなく、その待ち時間に別の作業へ切り替えているかどうか です。40 秒を素直に待てる人にとって AI Cloud は割高で、待てずに Slack を開いてしまう人にとっては安い。私は後者でした。
ハイブリッド設計:何をローカルに残し、何をクラウドに出すか
1ヶ月運用して固まった、私の現在の振り分けルールを共有します。これは絶対解ではありませんが、参考にはなるはずです。
ローカル実行に残したもの
既存ファイル 1〜2 個に閉じた小さな修正(数秒で終わる)
数分以上間隔が空いたあとの最初の1回(クラウドの初回遅延を避けるため)
飛行機・新幹線・Wi-Fi 不安定な場所での全タスク
プライベートな実験コード(思想的に外に出したくないもの)
機密に近いビジネスロジック(収益計算ロジック等)
AI Cloud に逃がすもの
プロジェクト全体を読ませる依存解析・横断リファクタ
SwiftUI と Jetpack Compose を同時に出させるマルチプラットフォーム生成
重い AI 機能(音声→テキスト→構造化)の試作
長時間の連続生成(チュートリアル制作などで30分以上回すケース)
このルールに落ち着いた理由は単純で、「ローカル機器の発熱が無視できなくなる閾値」と「ネットワーク前提でよい時間帯」が私の中で重なるところを採用した だけです。本番運用に近いプロジェクトほど AI Cloud 側に寄せ、研究的・個人的なコードほどローカルに残す、というのが結果的に綺麗な分け方になりました。
なお、クラウドに逃がしても Rork Max のネイティブ生成そのものが強くなるわけではありません。生成の限界がどこにあるかはRork Max で届かない領域はどこか — ネイティブ生成の現実的な線引き で別途扱っています。速さの問題と精度の問題は分けて考えた方が、期待の置き所を間違えずに済みます。
オフライン開発を捨てないための保険
ここはハマる人が多そうな落とし穴です。AI Cloud に慣れると、いつのまにかネットワークが切れた瞬間に Rork Max が事実上止まる開発環境になります。私自身、新幹線の中で全てのタスクが AI Cloud 依存になっていたことに気づいて青ざめた朝がありました。
対処として私は次のような小さな構成ファイルを Rork Max のプロジェクト直下に置いて運用しています。
// rork-max.config.ts
export default {
ai: {
// 既定はハイブリッド。明示的に切り替えられるようにする
mode: process.env. RORK_AI_MODE ?? "hybrid" ,
// 単発タスクの上限秒数。これを超えるならクラウドに逃がす
localTimeoutSec: 8 ,
// 直近のクラウド呼び出しからこの秒数以上空いていたらローカルで返す
coldStartGuardSec: 180 ,
// オフライン判定で自動フォールバックする
fallbackToLocal: true ,
// クラウド利用の月次予算(ガード)
monthlyBudgetJPY: 8000 ,
} ,
routes: {
// 重いタスクは強制的にクラウドへ
forceCloud: [
"dependency-analysis" ,
"multi-file-refactor" ,
"multi-platform-generation" ,
],
// 機密プロジェクトは強制ローカル
forceLocal: [
"wallpaper-revenue-calc" ,
"private-experiments/*" ,
],
} ,
} ;
coldStartGuardSec は p95 の分布を見てから足した項目です。間隔が空いた直後の軽いタスクをローカルへ回すだけで、体感のちぐはぐさがほぼ消えました。
予算ガードを「本当に効く」形にする — 呼び出し台帳の実装
前節の monthlyBudgetJPY は、それ単体では何も止めません。設定ファイルに数字を書いただけで安心してしまい、月末に請求を見て青くなるというのが、最初の月に私がやったことです。
そこで、呼び出しのたびに追記する台帳を挟みました。実行前に当月の累計を読み、上限を超えていればクラウドへ出さずローカルへ落とす。それだけの仕組みですが、これがあるかないかで月末の落ち着きがまるで違います。
// ai-budget-ledger.mjs
import { appendFileSync, existsSync, readFileSync, mkdirSync } from "node:fs" ;
import { dirname } from "node:path" ;
const LEDGER = ".rork/ai-usage.jsonl" ;
// タスク種別ごとの想定単価(円)。実際の請求と月末に突き合わせて補正する
const UNIT_COST_JPY = {
"view-generation" : 0.6 ,
"refactor-proposal" : 2.4 ,
"dependency-analysis" : 11.0 ,
};
function currentMonthKey ( now = new Date ()) {
return `${ now . getUTCFullYear () }-${ String ( now . getUTCMonth () + 1 ). padStart ( 2 , "0" ) }` ;
}
export function monthlySpentJPY ( monthKey = currentMonthKey ()) {
if ( ! existsSync ( LEDGER )) return 0 ;
return readFileSync ( LEDGER , "utf8" )
. split ( " \n " )
. filter (Boolean)
. reduce (( sum , line ) => {
let row;
try {
row = JSON . parse (line);
} catch {
return sum; // 壊れた行があっても集計は止めない
}
if (row.month !== monthKey) return sum;
return sum + ( Number (row.costJPY) || 0 );
}, 0 );
}
export function record ( taskType , { mode }) {
if (mode !== "cloud" ) return 0 ; // ローカル実行は課金されないので記録しない
const costJPY = UNIT_COST_JPY [taskType] ?? 0 ;
mkdirSync ( dirname ( LEDGER ), { recursive: true });
appendFileSync (
LEDGER ,
JSON . stringify ({
at: new Date (). toISOString (),
month: currentMonthKey (),
taskType,
costJPY,
}) + " \n "
);
return costJPY;
}
// 実行直前に呼ぶ。予算を超えていたら "local" に落として返す
export function resolveMode ( taskType , desiredMode , budgetJPY ) {
if (desiredMode !== "cloud" ) return desiredMode;
const spent = monthlySpentJPY ();
const next = spent + ( UNIT_COST_JPY [taskType] ?? 0 );
if (next > budgetJPY) {
console. warn (
`[budget] ${ spent . toFixed ( 0 ) }/${ budgetJPY } JPY 到達。${ taskType } をローカル実行に切り替えます。`
);
return "local" ;
}
if (next > budgetJPY * 0.8 ) {
console. warn ( `[budget] 今月 ${ next . toFixed ( 0 ) } JPY(上限の80%超)です。` );
}
return "cloud" ;
}
運用してみて効いたのは、上限到達時に処理を失敗させるのではなく、ローカル実行へ静かに落とすという挙動 でした。エラーで止めると結局その場で上限を引き上げてしまいます。遅くなるだけなら受け入れられるので、予算が守られます。
UNIT_COST_JPY は請求額から逆算した概算です。月末に実際の請求と台帳の合計を並べて、ずれていれば係数を直す。この突き合わせを月1回やるだけで、翌月の見積もり精度がかなり上がりました。コードでも上限を意識し、Rork の管理画面でも上限を設ける という二重防御を、あらためて強く推奨します。
本番運用での落とし穴
1ヶ月のあいだに私が踏んだ実際の落とし穴を列挙します。これから AI Cloud を試す方の時間を少しでも節約できれば幸いです。
1. 「全部クラウド」は思ったよりオフライン耐性を奪う
便利だからとデフォルトをクラウドに振り切ると、Wi-Fi が不安定な場所で開発がほぼ止まります。ローカルとクラウドの自動切り替えは必ず入れておくべきです。
2. 大きなプロジェクトの初回アップロードが地味に重い
依存解析タスクは AI Cloud に最初のフルプロジェクトを送ります。回線が細い場所では数分かかる場合があり、その間に他タスクを走らせると詰まります。本番運用では大きいプロジェクトの初回送信は固定回線で行うのが安全です。
3. 生成結果のばらつきはローカルとクラウドで完全には一致しない
私の経験では、同じプロンプトでもクラウドの方がやや慎重な出力を返す傾向がありました。これは恐らくモデルの構成差で、悪い意味ではないものの、UI 文言の細部などをチームで揃えたい場合は片側に寄せた方が安定します。
4. 平均レイテンシだけを見ると、体感とずれる
先に書いた通り、間隔が空いた直後の初回は遅くなります。ベンチマークを連打で取ると、この遅れは平均に埋もれて見えません。導入判断の材料にするなら、必ず待機時間を挟んだ計測を混ぜてください。
5. 予算は「書く」だけでは守られない
設定ファイルの数字は宣言であって、実行を止める仕組みではありません。呼び出し台帳を挟むまで、私の月次コストは自分の意志だけで管理されていました。意志は月末に負けます。
1ヶ月走らせて残った判断
AI Cloud の価値は速度そのものではなく、自分のマシンを取り戻すことにある、というのが 1ヶ月後の結論です。数字で見れば損益分岐は微妙な線ですが、待ち時間に別の作業へ逃げてしまう自分の癖を勘定に入れると、はっきり黒字に振れます。
面白かったのは、測る前と測ったあとで使い方が変わったことでした。速いと思っていたクラウドには初回の遅れがあり、遅いと思っていたローカルには安定という美点がある。どちらが優れているかではなく、どちらをどの場面に置くかという問いに変わってから、ようやく道具として手に馴染みました。
導入するかどうか迷っている方には、「必須ではないが、入れると開発の質感が変わるもの」と伝えたいです。個人開発では、こういう静かな改善が数ヶ月後の継続力に効いてきます。
次に試すべきアクション
AI Cloud を1ヶ月走らせてみるなら、最初の1週間は意図的に ローカル実行と AI Cloud の両方で同じタスクを並走させる ことから始めてください。レイテンシの絶対値より、自分の体感における中断時間と、月額予算が許容できる範囲を測ることが先決です。
具体的には、この記事の bench-ai-cloud.mjs を待機なしで 30 回、待機 300 秒で 10 回、それぞれ両モードで回すだけで十分です。得られる p50 と p95 の差が、あなたのプロジェクトにおける振り分けの境界線をそのまま示してくれます。
最後までお付き合いいただき、ありがとうございました。同じく個人開発を続けている方の参考になれば嬉しく思います。