はじめに
はじめまして。
国内のSIer企業でエンジニアをしている Taiki です。
今回、Qiita のキャンペーン「OpenAI・Anthropic互換APIを無料で使おう!「さくらのAI Engine」3,000リクエスト使い切りチャレンジ」に参加してみます。
せっかく「3,000リクエスト使い切り」がお題なので、そのリクエストをいかに効率よく使うかを題材にしました。
というのも、さくらのAI Engine の無償枠はトークン数ではなくリクエストの回数で数えるんです。月3,000回まで無料で、そのなかでトークンをどれだけ使っても同じ扱い。この前提だと、私が普段書いているこのコードが一気に非効率になります。
for item in items: # 100件なら100リクエスト
classify(item)
トークン課金なら、まとめて送っても分けて送っても料金は同じなので、これで何の問題もありませんでした。ところがリクエスト課金では、これだと月3,000件で打ち止めです。
そこでバッチング、つまり複数件を1回のリクエストにまとめて処理すれば、理屈の上では何十倍も稼げます。とはいえ詰め込みすぎたら出力が壊れるはず。じゃあその境界はどこなんだ、というのを合計740リクエスト使ってガチで測ってみました。
結論を先に言うと、これも意外な絵が出てきました。
-
96件を1リクエストに詰めても、まったく壊れませんでした(正解率100%)。1件処理するのに必要なリクエスト数は、8件ずつ送る場合の12分の1
→ つまり、リクエスト課金では「1件ずつ投げる」という常識的な設計が桁で損している - 出力を重くすると崩壊しますが、原因は全部
max_tokens(1回の応答で生成できる上限)の打ち切りでした。JSONの構造が壊れたケースは1件もゼロ
→ つまり、「バッチが大きいとAIが混乱してJSONを壊す」は私の思い込みだった - そして必要な上限値は、バッチサイズにほとんど関係なかった。8件しか送っていないのに11,669トークン使ったケースがあります
→ つまり、量を決めているのは出力する本文ではなくAIが考える長さのばらつき。ここが今回いちばんの発見でした
なので結局、やることは3つだけになりました。max_tokens をケチらず大きく取る → そのうえでバッチを大きくする → 出力は JSONL(1行1件)にする。 これだけです。
図は、出力の重さを3段階に変えて、バッチサイズごとに「送った件数のうち何件を回収できたか」を見たものです。左は無傷、右へいくほど崩壊しています。以降で1つずつ見ていきます。
⚠️ 注意:
検証したのはgpt-oss-120bの1モデル、日本語の感情分類という1タスクだけです。必要なトークン量や崩壊の境界はモデルやタスクで変わるので、そのまま一般化はできません。タイトルの28.8万件は実測値からの外挿です(実測したのは192件分の処理)。
背景: リクエスト課金では設計の常識が反転する
さくらのAI Engineの無償枠は、次のようにリクエスト数で決まっています(2026年7月29日時点の公式表記)。
| API | 無償枠 |
|---|---|
| Chat completions | 月3,000リクエスト |
| Embeddings | 月10,000リクエスト |
| 音声認識 / 音声合成 | 各月50リクエスト |
基盤モデル無償プランでは、超過しても課金されず「レート制御がかかります」と明記されています。
つまり、リクエストが希少資源で、トークンは (無償枠の範囲では) いくら使っても同じです。
ここで、トークン課金の場合と何が変わるのか整理します。
| トークン課金 | リクエスト課金(今回) | |
|---|---|---|
| 1件ずつ投げる | 料金は変わらない | 月3,000件で頭打ち |
| B件まとめる | 料金は変わらない | 理論上 3,000×B 件を処理できる |
| まとめる動機 | ほぼ無い | 極めて大きい |
「1件ずつ」は壊れにくく、失敗時のリトライも1件分で済み、並列化もしやすい。トークン課金ではデメリットが無い安全策でした。それがリクエスト課金では、そのまま処理能力の天井に変わってしまいます。
なお、この構図は無償枠に限った話ではありません。RPM(1分あたりのリクエスト数)制限に張り付いているワークロードや、社内でリクエスト数のクォータが決まっている場合も、リクエストが希少資源という点では同じです。今回の考え方はそのまま当てはまります。
というわけで、実際にさくらのAI Engineを使ってどこに損益分岐点があるのかを実測してみました。
検証環境
| 項目 | 内容 |
|---|---|
| モデル |
gpt-oss-120b(通常提供。2026-07-29 にコントロールパネルで利用可能を確認) |
| エンドポイント |
POST https://api.ai.sakura.ad.jp/v1/chat/completions(OpenAI互換) |
| パラメータ |
temperature=0.0, stream=false
|
| データ | 自作の日本語レビュー96件(感情2値分類、positive/negative 各48件) |
| プラン | 基盤モデル無償プラン |
※ 96件にした理由は、バッチサイズが 1/2/4/8/16/32/48/96 のすべてで割り切れるためです。
評価指標
評価するための 実効コスト C(B) の計算式は下記です。
実効コスト C(B) = 消費リクエスト数(リトライ含む) ÷ 正しく処理できた件数
これは小さいほどよく、「1件処理するのに何リクエスト必要か」を表します。
B=1 なら 1.0 が上限で、C(96)=1/96≒0.0104 が理論限界です。
また今回の検証では、壊れた出力をリトライしない設計にしました。
パース失敗や欠落は「測定対象そのもの」となるので、粘って成功させてしまうと劣化が観測から消えてしまうからです。
検証結果
結果①: 96件まとめても、軽い出力なら無傷だった
まず日本語を positive / negative に分類し、ラベルだけ返す簡単なタスクを実施しました。
つまり、1件につき返すのが約10トークンと極めて軽いタスクです。
実際に指示したプロンプトは下記です。
あなたは日本語レビューの分析器です。
各レビューを positive / negative に分類してください。
ルール:
- 入力に含まれる id をそのまま返すこと
- すべてのアイテムについて必ず1件ずつ出力すること
- 説明文・前置き・コードフェンスを出力しないこと
出力形式: 1行につき1アイテムの JSON オブジェクトだけを出力する。各行は次の形式:
{"id": "<入力のid>", "label": "positive|negative"}
入力(B=96 ならこれが96行。3件だけ抜粋):
{"id": "d-001", "text": "注文から到着まで速く、梱包も丁寧でした。また利用します。"}
{"id": "d-002", "text": "届いた商品に傷があり、がっかりしました。"}
{"id": "d-003", "text": "正直期待していなかったが、想像をはるかに超える出来だった。"}
モデルからの実際の出力は下記 (例) です。
[{"id":"d-065","label":"positive"},{"id":"d-078","label":"negative"},...]
| B | 消費req | 正解 | 回収率 | C(B) | 切断 |
|---|---|---|---|---|---|
| 8 | 24 | 192/192 | 100% | 0.1250 | 0 |
| 16 | 12 | 192/192 | 100% | 0.0625 | 0 |
| 32 | 6 | 192/192 | 100% | 0.0312 | 0 |
| 48 | 4 | 192/192 | 100% | 0.0208 | 0 |
| 96 | 2 | 192/192 | 100% | 0.0104 | 0 |
完全に単調減少して理論限界に到達しました。JSON配列でもJSONLでも、96件を1リクエストで完璧に処理しています。「バッチを大きくすると壊れる」という私の予想は、きれいに外れました。
考察: なぜ壊れなかったのか
私の予想が外れた理由は、出力を測ってみると明快でした。
このタスクが返す本文は{"id":"d-065","label":"positive"}のような短いオブジェクトです。96件まとめた時の本文は3,265~3,551文字、1件当たり34~37文字程度にすぎません。トークンに換算すると約1,000トークン (推定: ASCII主体のJSONを3.5文字/トークンで換算) で、max_tokens=8192に対して余裕がありすぎました。
つまり、壊れる余地がそもそもなかったわけです。
ただ、この内訳をもう一段見ると引っかかる点が出てきます。
それは、B=96 の出力トークンは平均3,868でした。本文が約1,000トークンなら、残りの約2,900トークン、つまり出力の3/4は思考 (reasoning) に使用していることになります。
<まとめ>
「バッチを大きくすると壊れる」という予想が外れたのは、モデルが賢かったからというより、このタスクでは予算に対して出力が軽すぎたからでした。
逆に言えば、出力を重くすれば壊れるはずで、しかもその時効いてくるのは本文の量よりも思考の量かもしれません。実際にこの結果が後の結果②以降につながってきます。
考察: 経済的なインパクト
C(1)=1.0 に対して C(96)=0.0104 なので、同じリクエスト数で処理できる件数が約96倍になります。
無償枠の月3,000リクエストに当てはめると、1件ずつ投げれば3,000件で頭打ちですが、96件ずつまとめれば 28.8万件 (推定: C(96)=0.0104 を線形に外挿した値。実測したのは192件分の処理) となります。
「リクエストの節約」というと数割の改善を思い浮かべますが、リクエスト課金では桁が2つも変わります。1件1リクエストという常識的な設計が、この課金モデルではそれだけ非効率だということです。
もっとも、この96倍は「壊れなかった場合」の数字です。以降では、どこから壊れ始めるのかを見ていきます。
結果②: 壊れたのは「出力トークンが尽きたとき」だけだった
では、何をすれば壊れるのか。入力は同じままで、要求する出力だけを重くしてみました。
指示に「判断根拠を20~40文字で述べてください」とプロンプトを足し、出力形式にフィールドを追加していきます。
実際に返ってきたのはこういう出力でした。
# label_reason(ラベル + 根拠)
{"id":"d-048","label":"negative","reason":"多機能だが操作性が悪く、期待した快適さが得られない。"}
{"id":"d-013","label":"positive","reason":"清潔な部屋と美味しい朝食で滞在に大満足。"}
# structured(ラベル + 根拠 + 確信度 + 観点)
{"id":"d-013","label":"positive","reason":"部屋が清潔で朝食も美味しく、滞在全体に大満足","confidence":0.97,"aspects":["清潔さ","朝食"]}
1件当たりの文字数が34文字から60~100文字程度に膨らみました。その結果こうなりました (アイテム回収率) 。
| タスク | 出力内容 | B=8 | B=32 | B=96 |
|---|---|---|---|---|
label |
ラベルのみ | 100% | 100% | 100% |
label_reason |
ラベル + 根拠20~40字 | 100% | 100% | 0% |
structured |
ラベル + 根拠 + 確信度 + 観点配列 | 100% | 100% | 40.6% |
崩壊しました。ただし原因は予想と違って、すべてfinish_reason = "length"、つまりmax_tokensでの打ち切りでした。
JSON構造が破綻したケースは1件もありませんでした。切断されなかったリクエストでは、パース失敗・ID不一致・欠落がほぼゼロ。「大きいバッチはJSONを壊す」という予想は、このモデル・タスクでは成り立ちませんでした。
<まとめ>
「バッチが大きいとLLMが混乱してJSONを壊す」というのは、少なくともこのクラスのモデルでは私の思い込みでした。実際に起きていたのは予算切れによる打ち切りという、ずっと機械的な現象です。
原因が予算枯渇なら、予算そのものを動かせば壊れ始める位置も動くはずです。次はそれを確かめます。
結果③: 崖は予算を増やせば右に動く
(崖...バッチサイズをこれ以上大きくすると、結果が急に全滅する境界のこと)
原因が予算枯渇なら、予算を変えれば崖も動くはずです。
max_tokensを4水準振りました (384リクエスト) 。
崖の位置を「切断率が50%を超える最小のB」と定義すると、こうなりました。
| max_tokens | 崖の位置 |
|---|---|
| 2,048 | B=8 (すでに58%が切断) |
| 4,096 | B=32 |
| 8,192 | B=48 |
| 16,384 | 範囲内になし (B=96でも切断0件) |
崖の落ち方も見ておきます。max_tokens=4096 を例にとると、境界の前後でこう変わります (JSONL形式) 。
| B | 切断率 | 正解 |
|---|---|---|
| 8 | 0% | 191/192 (99.5%) |
| 16 | 33% | 128/192 (66.7%) |
| 32 | 100% | 0/192 (0%) |
B=8 では99.5%正解していたのに、B=32 では1件も取れなくなります。なだらかに落ちるのではなく、境界を越えた瞬間にゼロになる。だから「崖」と呼んでいます。
考察: 予算が崖を作っている
同じタスク・同じモデルで、変えたのはmax_tokensだけです。それでも挙動は両極端でした。
max_tokens=2048では最小の B=8 ですでに半分以上が切断されたのに、16,384では96件を1リクエストにしても切断ゼロ (96リクエスト中0件、192/192件正解) でした。
<まとめ>
結果②で見た「崖」は、モデルの能力限界ではなく**max_tokensの予算不足が作り出していた**ものでした。
では、その予算はどれだけ必要なのか。素直に考えれば「バッチが大きいほど多く必要」となるはずです。ところが実測してみるとそうではありませんでした。
結果④: 必要な予算はバッチサイズにほとんど依存しない
ここが個人的に一番意外でした。切断されなかったリクエストが実際に使った出力トークン量を見ます。
| B | 平均出力 | 最大出力 | 平均 tok/件 | 最大基準 tok/件 |
|---|---|---|---|---|
| 8 | 2,147 | 11,669 | 268.3 | 1,458.6 |
| 16 | 3,733 | 11,269 | 233.3 | 704.3 |
| 32 | 7,148 | 15,015 | 223.4 | 469.2 |
| 48 | 9,349 | 13,495 | 194.8 | 281.1 |
| 96 | 12,819 | 16,000 | 133.5 | 166.7 |
考察: 支配しているのは「思考のばらつき」
まず、平均 tok/件を見ると、1件当たりのトークンは 268 → 133 と減少しています。思考のコストがバッチ全体でほぼ一定なので、まとめるほど1件あたりのトークン数は薄まりました。
これはバッチングが効く仕組みそのものです。
バッチングとは、複数のアイテムを1回のAPIリクエストにまとめて処理すること。
問題は最大値です。B=8 でも最大11,669トークンも使っています。1件当たりに換算すると1,459トークン、平均の5.4倍です。
返すべき本文は8件しかないのに、これだけ膨らむということは、差はすべて思考の長さということになります。
実際、同じlabel_reasonの B=8 でも、完走したリクエストの出力トークンは1,046から11,669まで11.2倍ばらついていました。返す本文の量はどれも8件分で同じなのに、これだけの差が付きます。
小さいバッチでも「たまたま長く考えた」1回で予算を使い切ることがあるわけです。
逆に、最大基準の tok/件は 1,459 → 167 と、バッチを大きくするほど下がっています。まとめるほど「1件が暴走するリスク」も薄まる、という副産物が得られました。
<まとめ>
必要な予算の上限は、バッチサイズよりも思考のばらつきで決まっていました。今回の全740リクエストで、完走した最大出力は16,000トークンでした。
つまり「Bを小さくすれば予算を節約できる」わけではありません。どのBを選ぶにせよ、予算は最悪ケースに合わせて用意する必要があるということです。
結果⑤: C(B)の最小点は罠だった
max_tokensごとに C(B) が最小になる B を出すと予算とともに右へ動きます。
素直に読めば「予算に応じて最適Bを選べばよい」という話になります。ところがその最良点の中身を見てみると話が変わりました (JSONL形式) 。
| max_tokens | C(B)最良のB | そこでの切断率 | 正解 |
|---|---|---|---|
| 2,048 | B=8 | 54% | 122/192 |
| 4,096 | B=16 | 33% | 128/192 |
| 8,192 | B=48 | 50% | 102/192 |
| 16,384 | B=96 | 0% | 192/192 |
考察: 指標を最適化しに行くと運用が壊れる
予算が足りない条件では、C(B)を最小にする点は「半分のリクエストが切断され、3~5割の件数を失いながら、それでもリクエスト効率だけは最良」という状態でした。
C(B) は「1件処理するのに何リクエスト必要か」しか見ていないので、こうなります。切断されても、生き残ったリクエストが大量の件数を運んでくれれば比率は良く出るのです。
しかし運用としては最悪で、切断されたリクエストは出力上限まで生成しきるため、レイテンシは長いのに成果はゼロという組み合わせになります。
レイテンシ...リクエストを送ってから応答が返ってくるまでの待ち時間
一方、予算が十分な16,384だけが「切断ゼロで最大バッチ」という素直な最適点になりました。
<まとめ>
「C(B) を最小化するように B を調整するのは筋が悪い」というのがここでの学びです。
正しい順序は逆で、まず予算を最悪ケースに合わせて十分とる。そのうえでBを大きくする。この順序なら、崖に落ちず単調な改善だけを受け取れます。崖は予算不足が作る人工物にすぎません。
結果⑥: 崩壊には2つのモードがあり、JSONLが効くのは片方だけ
崩壊したリクエストは救えないのか。切断された119件を中身で分類しました。
| モード | 件数 | JSON配列の回収率 | JSONLの回収率 |
|---|---|---|---|
| A. reasoning枯渇 (本文が0文字) | 105件 (88%) | 0.0% (0/1,712件) | 0.0% (0/1,720件) |
| B. 出力途中で切断 | 14件 (12%) | 0.0% (0/88件) | 37.5% (51/136件) |
考察: 本文が1文字も出ないケースがある
モードAは、予算のすべてを思考が消費して本文を1文字も出力しなかったケースです。
max_tokens=2048では全96リクエスト中64件 (67%) がこの状態でした。モデルは予算に合わせて思考を短くしてくれません。そして本文がゼロなら、行単位の復元も働きようがないので、形式を工夫しても救えません。
モードBは、出力途中で切れたケースで、ここでのみ形式差が出ます。
JSON配列は配列が閉じないためjson.loadsが全体で失敗して1件も救えません。JSONLは行ごとに独立して復元できるので、完成済みの行がそのまま使えます。
そしてモードAの比率は、予算が厳しいほど上がりました (2,048で86%、4,096で97%、8,192で77%、16,384で0%) 。
<まとめ>
JSONLは掛け捨てではないが、万能でもないというのが実測の結論です。効くのは切断ケースの12%だけでした。
より重要なのは、予算をケチると「部分回収すらできない全損」の比率が上がることです。結果⑤の「まず予算を十分にとる」がここでも裏付けられました。
結果⑦: 欠落はバッチ後方に集中する
切断されたとき、バッチ内のどの位置が失われるのか見てみました。
| 位置 | 回収率 |
|---|---|
| 前方 (1~25%) | 19.3% |
| 26~50% | 18.8% |
| 51~75% | 6.2% |
| 後方 (76~100%) | 0.0% |
きれいな単調減少で、後方は完全に失われました。
考察: 当初の仮説とは違う形で位置効果が出た
生成が前から順に進んで打ち切られるので、結果自体は当然です。ただ、当初の仮説は「後方ほど誤答が増える」でした (長文脈で中間・後方の情報利用が弱まるという Lost in the Middle からの類推) 。
実際には誤答はほとんど発生せず、位置効果は誤答ではなく欠落として現れました。切断されなければ後方まで正確に答えるし、切断されれば後方は存在しない。0か1かの現象でした。
<まとめ>
実務的にはこの性質が2つの形で使えます。
1つ目が、重要なアイテムはバッチの前方に置く。
2つ目が、欠落を検出したら失敗分だけ再投入すればよい (全件やり直す必要はない)。
返ってきたIDと送ったIDを突き合わせれば、何が落ちたかは機械的にわかります。
結果⑧: レイテンシもバッチングで改善する
最後に処理時間です。1件当たりの所要時間を見ます (label_reason タスク) 。
| B | 8 | 16 | 32 | 48 | 96 |
|---|---|---|---|---|---|
| 1件当たり | 1,197ms | 1,172ms | 943ms | 704ms | 346ms |
B=96 は B=8 と比べ3.5倍速くなりました。
考察: コストと速度が同じ方向に動く
理由は結果④と同じで、思考のオーバーヘッドが1件あたりに薄まるためです。アイテム当たりの出力トークン数と、ほぼ同じカーブを描いています。
一般に「コストを下げると遅くなる」というトレードオフを覚悟しますが、リクエスト課金でのバッチングにはそれがありません。リクエスト数も時間も同時に減ります。
<まとめ>
ただし1つ注意があります。
この数字には切断されたリクエストのレイテンシも含まれています。切断されると出力上限まで生成し切るので、実際には「長く待たされたのに成果はゼロ」という最悪の組み合わせが起きます。
速度の観点でも、崖に落ちないことが前提だということです。
実務への落とし込み
次は、先ほどの検証結果から導いた最適な設定の手順です。
1. まずmax_tokensを最悪ケースに合わせる
Bを調整する前にこれをやります。小さいバッチで試して、平均ではなく最大の出力トークン数を見ます。
# 実測した usage から最悪ケースを把握する
observed = [r["usage"]["completion_tokens"] for r in responses]
print(f"平均 {sum(observed)/len(observed):.0f} / 最大 {max(observed)}")
# → 今回は B=8 で 平均2,147 / 最大11,669。最大は平均の5.4倍
トークン課金でないなら、max_tokensを大きくとっても損はありません (未使用分は請求されない) 。ここをケチると全損モードに落ちます。
2. そのうえでバッチを大きくする
予算が足りていれば、Bは大きいほど得です。データ量とレイテンシ許容度で決めて構いません。
def estimate_batch_size(max_tokens: int, worst_tokens_per_item: float) -> int:
"""最悪ケースのtok/件から安全なバッチサイズを見積もる。
tok/件はBが大きいほど下がるので、この式は保守的な下限を与える。"""
return max(1, int(max_tokens / worst_tokens_per_item))
3. 出力はJSONL形式にする
コストゼロの保険です。効くのは切断ケースの一部 (今回は12%) だけですが、掛け捨てにはなりません。
def parse_jsonl(content: str) -> tuple[list[dict], int]:
"""行ごとに復元する。壊れた行は捨てて、完成した行だけ回収する。"""
records, broken = [], 0
for line in content.splitlines():
line = line.strip()
if not line:
continue
try:
records.append(json.loads(line))
except json.JSONDecodeError:
broken += 1 # 切断された最後の行はここに落ちる
return records, broken
同じ入力で比べると差は明快です。
入力: '{"id":"d-001","label":"positive"}\n{"id":"d-002","label":"neg'
JSONL → 1件回収, 破損1行
JSON配列 → JSONDecodeError(全損)
4. finish_reasonを必ず監視する
lengthが出たら予算不足です。欠落した分だけ再投入します。返ってきたIDと送ったIDを突き合わせれば、何が落ちたかはわかります。
5. 崖ぎりぎりを狙わない
崖の周辺では切断が確率的に起きるので、C(B)が上下に振れて不安定になります。余裕を持たせる方が運用は安定します。
制約・注意点
この結果をそのまま一般化しないでください。
-
モデルは
gpt-oss-120bの1種類のみ。 reasoning の量はモデルごとに違うので、必要予算も崖の位置も変わります。 - タスクは日本語の感情分類のみ。 入力が長い場合や、より複雑な出力構造では傾向が変わりえます。
- 同一の96アイテムを全条件で再利用しているため、観測は完全に独立ではありません。
- 各セル2試行と少なく、切断は確率的に起きるため、崖の位置には±1水準程度の不確かさがあります。
-
完走した最大出力16,000トークンは、
max_tokens=16384の上限に近い値です。真の最悪ケースはこれより大きい可能性があります(観測できていません)。 -
max_tokens=16384での最適Bは観測範囲の上限(B=96)に張り付いており、真の最適はさらに大きい可能性があります(データが96件しかないため未検証)。 - 28.8万件という数字は外挿です(タイトルにも使っています)。実測したのは192件分の処理で、この効率が月3,000リクエスト分そのまま続く前提の計算です。
- 無償枠の仕様(2026年7月29日確認)は変更されえます。RAG(ドキュメント)機能だけは無償プランでも課金対象なので注意してください。
再現手順
ここまで閲覧ありがとうございます。
実験コード・生ログ・集計スクリプトはすべてgithubリポジトリにあります。
記事中のすべての数値は 40_test/ のログから機械的に集計したもので、外挿値には明示的にラベルを付けています。
ぜひお試しで実践してみてください。
git clone https://github.com/Taiki0629/BatchEconomics
cd BatchEconomics
cp .env.example .env # SAKURA_API_KEY=<UUID>:<シークレット> を記入
uv sync
# 送信計画の確認(APIを叩かない)
uv run python 30_development/run_main.py --run-id p3-002 --max-tokens 8192 --dry-run
# 消費量の確認
uv run python 30_development/tally_budget.py
無償枠を消費する実行には上限をコードで強制してあります(超えると送信が拒否されます)。ログは1リクエストにつき「送信前」「応答後」の2行を書く write-ahead 方式にしており、Ctrl-Cや電源断でも消費の記録が失われません。
今回の消費実績は合計740リクエスト (無償枠3,000の24.7%) でした。
| run | 内容 | リクエスト |
|---|---|---|
| p3-001 | 試走(疎通・reasoning挙動の確認) | 18 |
| p3-000 / p3-000b | 探索(どこで壊れるかの特定) | 50 |
| p3-100 | 本実験(3タスク × 5水準 × 2形式 × 2試行) | 288 |
| p3-200 | 追試(max_tokens 4水準) | 384 |
付録: セットアップでハマったこと
同じ検証をする人向けに、公式ドキュメントを読んだだけでは気づきにくかった点を挙げます(すべて2026年7月29日に実機で確認)。
無償枠だけ使う場合でもクレジットカード登録は必須です。 さくらのクラウドのアカウント要件で、公式マニュアルに「ご利用にはクレジットカードによるお支払いのみ」と明記されています。カード以外はプリペイド(法人・文教分野限定)と銀行振込(例外時)のみで、個人利用では回避できません。ただし登録自体で課金は発生しません。
AI Engine のコントロールパネルは別URLです。 さくらのクラウドの管理画面(secure.sakura.ad.jp/cloud/)からは辿れず、secure.sakura.ad.jp/ai/ を直接開く必要があります。
RAG(ドキュメント)機能だけは無償プランでも課金対象です。 プラン選択画面にこう書かれています。
基盤モデル無償プランにおいても、RAGで使用するドキュメントを登録した際には、ドキュメントを削除するまでチャンク数に応じた課金が継続的に毎月発生しますのでご注意ください
3円/100チャンクで、削除するまで毎月続きます。無償枠で試すだけのつもりなら、左メニューの「ドキュメント (RAG)」には触らないほうが安全です。
アカウントトークンは一度しか表示されません。 「このトークンは再度表示されません」と警告が出ます。画面を閉じると作り直しになります。形式は <UUID>:<シークレット> で、これを丸ごと Authorization: Bearer に載せます。
プレビューモデルのIDには preview/ プレフィックスが必要です。 サービス紹介ページには Kimi-K2.6 と書かれていますが、APIに渡すIDは preview/Kimi-K2.6 でした。コントロールパネルの「利用可能なモデル」画面が正です。
モデルは入れ替わります。 参考にした資料に載っていた Qwen3-Coder-480B-A35B-Instruct-FP8 と Qwen3-Coder-30B-A3B-Instruct は、この時点の一覧に存在しませんでした(コード系は preview/Kimi-K2.7-Code に置き換わっていました)。古い記事のモデルIDをそのままコピーしないほうがいいです。
消費量はコントロールパネルで確認できます。 「利用量」またはアカウントトークンの詳細画面に、モデルごとの日次グラフがあります。手元のログと突き合わせておくと安心です。
おわりに
一番の収穫は、当初の仮説が完全に外れたことでした。「バッチを大きくすると出力が壊れてU字カーブになる」と予想して実験を組んだのに、96件まとめても壊れませんでした。
そこで探索し直して分かったのが「壊れるのは予算が尽きたときだけ」で、さらに掘ると「必要な予算はバッチサイズにほとんど依存しない(思考のばらつきが支配的)」という、当初想定していなかった構造でした。
結果として指針はシンプルになりました。max_tokens を最悪ケースに合わせて大きく取る。そのうえでバッチは大きくする。出力はJSONLにする。 リクエスト課金では、これだけで効率が桁で変わります。
参考文献
公式ドキュメント(すべて2026年7月29日に確認)
- さくらのAI Engine サービスサイト — 無償枠の数値、提供モデル一覧、料金、プランごとの超過時の挙動
- さくらのAI Engine 利用手順 — APIエンドポイント、認証方式、アカウントトークンの発行手順
- さくらのAI Engine サービス基本情報 — RAG利用時の課金、解約条件
- さくらのAI Engine 操作ガイド — コントロールパネルでのモデル確認方法
-
AI Engine Inference API リファレンス — OpenAPI 仕様。
chat/completionsのパラメータとエラーコードはここを一次情報とした - さくらのクラウド ご利用開始手順 — 会員登録から支払い方法登録までの流れ
- さくらのクラウド お支払いについて — 利用可能な支払い方法
論文
- Nelson F. Liu, Kevin Lin, John Hewitt, Ashwin Paranjape, Michele Bevilacqua, Fabio Petroni, Percy Liang.
Lost in the Middle: How Language Models Use Long Contexts. TACL, 2023. (arXiv:2307.03172)
長い文脈では冒頭と末尾の情報がよく使われ、中間の情報の利用が落ちるという報告。結果⑦で「後方ほど誤答が増えるのではないか」と予想した根拠にしましたが、今回の実験では誤答ではなく欠落として現れました(そもそも出力タスクへの転移を検証したものではないので、外れて当然かもしれません)。




