GPT-6.1 Sol APIの構造化出力でレビューにタグ付けすると、コストはいくらになるか
!
この記事は EveryInfra ブログ からの転載です(原文が正本です)。
私たちの試算では、GPT-6.1 Solを使うと、1,000件のレビューに1ドル未満でタグを付けられます。前提は、strictモードのJSON Schema出力とBatch APIを使い、レビュー1件あたりの推論トークン数を約50とすることです。モデルは実際には実行していないため、この数値は試算です。費用を左右するのはデータではなく、リクエストのまとめ方です。2026-11-06の00:16から00:25(SGT)にかけて、EveryInfraのデータAPIを実際に7回呼び出しました。
ロボット掃除機のアプリ2つ(RoborockとiRobot)のGoogle Playレビューを400件、さらにレビュー動画1本についたTikTokコメントを12件取得しました。支払った金額は合計$0.003334です。各呼び出しで実際に返された件数から計算すると、レビュー1,000件あたりのデータ料金はGoogle Playが$0.006945、TikTokが$0.046704です。
このマシンにはOpenAIのAPIキーがありません。そのため、この記事ではモデルに関しては設定例と試算のみを示します。トークン数は公開されているo200k_baseエンコーディングで数え、料金は2026-11-06に確認した公開価格を使っています。主な結論はシンプルです。
1リクエストにレビューを1件ずつ送ると、システムプロンプトとスキーマだけで967トークンになります。これは入力トークン全体の92%にあたり、プロンプトキャッシュの適用に必要な最小の1,024トークンを下回ります。そのため、同じ指示に対して1,000回分の正規料金を支払うことになります。
次に、1リクエストにレビューを25件ずつ送り、レビュー1件あたりの推論トークンを50と仮定します。すると、1,000件あたりのBatchでのモデルコストは約$1.74から約$0.81に下がります。推論トークンがなければ、下限は$1.49から$0.56まで下がります。ただし、Solではlowより低いeffortを指定できないため、この下限には届きません。
要点
GPT-6.1 Solは、2026-09-29にgpt-6.1-solとしてAPIで提供が始まりました。コンテキストウィンドウは1,050,000トークンです。1Mトークンあたりの標準価格は入力$2、出力$10で、Batchでは$1と$5です。最も低いreasoning effortはlowで、noneはありません。
strictモード用のスキーマを、ドキュメントに記載されたルールに照らしてスクリプトでチェックしました。私たちのタグ用スキーマの問題は0件でした。壊れたテスト用スキーマでは問題が5件見つかり、その中にはallOf、if、thenが含まれています。
7回の実際の呼び出し:google_play/reviewsは1回の呼び出しで200件を返し、料金は$0.001389でした。tiktok/commentsは動画1本について12件を返しました。amazon/reviewsの非同期ジョブ2件は0件で完了し、返金されました。503タイムアウト2回には料金がかかりませんでした。
1リクエストに1件ずつの場合、1,000件のレビューには約1,056,100の入力トークンが必要で、そのうち975,000トークンは繰り返し送られるプロンプトとスキーマです。1リクエストに25件ずつなら、入力は約121,700トークンまで減ります。
「競合に言及している」を判定するキーワードルールは、大まかな一次判定にすぎません。このルールは412件中17件にマッチしました。マッチしたレビューは1件ずつ読みました。13件は実際に競合に言及していました(そのうち1件は、ルールが見逃した2つのブランドにも触れていました)。3件は誤検知で、Samsungのスマートフォン、Samsungのタブレット、スロバキア語の単語でした。1件は判断がつきませんでした。
この記事が答えるのは、1つの限定的な問いです。手元にレビューのセットがあり、GPT-6.1 Solで各レビューに4つのタグ(感情、問題カテゴリ、競合への言及、アクションアイテム1つ)を付けたいとします。各タグはそのままスプレッドシートに入れられる形で出力する必要があり、総コストも把握したいという状況です。レビューはEveryInfraのデータAPIから取得します。
タグ付けにはOpenAIのResponses APIとBatch APIを使います。以下に出てくるEveryInfraの数値はすべて生のレスポンスから取ったもので、再計算できます。OpenAI側の手順にはすべて、設定例か価格の試算であることを明記しています。このバッチはOpenAI上では実行していません。
次の表は、実測した項目と試算した項目をまとめたものです。以下の数値はすべて、この表のいずれかの行に対応します。
ステップ
取得方法
再計算できるか
レビューデータ(EveryInfra)
2026-11-06に実際に7回呼び出し、生のレスポンスを保存
可能。すべての数値がレスポンスに基づく
トークン数
実際のシステムプロンプト、スキーマ、レビュー412件をo200k_baseでカウント
可能。ただしSolのトークナイザーは非公開
出力タグ
人が書いたタグの例12件(GPT-6.1 Solの出力ではない)
出力長の推定にのみ使用
推論トークン
未計測。レビュー1件あたり0、50、200を想定
不可。パイロット実行とそのusageが必要
モデルのコスト
上記の各行の数値に2026-11-06時点の公開価格を乗算
可能。自分の数値で計算できる
タグの精度
なし
パイロット実行後に手作業で確認
GPT-6.1 Sol APIとは:リリース日、コンテキスト、価格
まず、モデルに関する事実を確認します。OpenAIのAPI changelogには、2026-09-29付けのエントリがあります。それによると、OpenAIは複雑なコーディングや専門業務向けにGPT-6.1 Sol(gpt-6.1-sol)をリリースしました。価格はGPT-6 Astraより低く設定されています。
272Kトークンまでの入力では、1Mトークンあたりの標準価格は入力$2、キャッシュ入力$0.10、キャッシュ書き込み$2.50、出力$10です。同じエントリには、ツール呼び出しにはResponses APIが必要だとも書かれています。リリース記事の「Introducing GPT-6.1 Sol」によると、Solの性能はGPT-6 Astraに近いとされています。また、標準の入力・出力価格はAstraの5分の1だとしています。
2026-11-06に確認したGPT-6.1 Solのモデルページには、タグ付けに関係する仕様が載っています。コンテキストウィンドウは1,050,000トークン、最大出力は128,000トークン、知識のカットオフは2026-04-30です。reasoning.effortの値はlow、medium(デフォルト)、high、xhigh、maxです。機能一覧では、Structured outputsとFunction callingが「Supported」となっています。
モデルページには、バッチ処理に関わる料金ルールがほかに4つあります。キャッシュされた入力は入力料金の5%です。キャッシュ書き込みは1.25倍です。入力トークンが272Kを超えると、そのリクエストでは入力料金が2倍、出力料金が1.5倍になります。BatchとFlexは50%安くなります。
項目
情報源の記載内容
出典(2026-11-06閲覧)
タグ付けへの影響
APIリリース
gpt-6.1-solは2026-09-29にリリース
API変更履歴
執筆時点でリリースから38日
コンテキストと出力
コンテキスト1,050,000、最大出力128,000
モデルページ
数十件のレビューが1リクエストに余裕をもって収まる
推論エフォート
low、medium、high、xhigh、max(noneなし)
モデルページ、GPT-6ガイド
すべてのリクエストで推論トークンが発生し、出力として課金
標準料金
1Mあたり入力$2、キャッシュ$0.10、出力$10
料金ページ、変更履歴
同期処理で最低でも1,000件あたり約$2.99
Batch料金
1Mあたり入力$1、キャッシュ$0.05、出力$5
料金ページ
同じリクエストが半額
長い入力の割増料金
入力272K超:入力2倍、出力1.5倍
モデルページ
1リクエストにレビューを詰め込みすぎない
Chat Completions
対応(ツール呼び出しは非対応)
モデルページ、GPT-6ガイド
本記事ではResponses APIのみを使用
最も見落とされやすいのは推論エフォートの行です。GPT-6ガイドにははっきり書かれています。GPT-6 AstraとGPT-6.1 Solはnoneに対応していません。既存のリクエストでminimalを使っている場合は、lowに変更して結果を比較してください。GPT-6.1 Terra、GPT-6 Sol、GPT-6 Lunaはnoneに対応しています。推論ガイドによると、推論トークンは出力トークンとしてカウントされます。
これはタグ付けに影響します。レビューごとに見えない出力が発生し、料金は1Mトークンあたり$10、Batchでは$5です。そのトークン数は、実際に呼び出してみなければ測定できません。そのため、コスト表では推論を別の前提として示しています。
Batchの行を詳しく見ます。料金ページでは、FlexとBatchが同じ価格帯に置かれています。gpt-6.1-solのBatchは、1Mトークンあたり入力$1、出力$5、キャッシュ入力$0.05です。30分のキャッシュ書き込みは$1.25、12時間のキャッシュ書き込みは$2.50です。以下のコスト表はすべてこの価格帯で計算しています。
gpt 6.1のstructured outputs:json_schemaのstrictモードにはどんな制限があるのか
OpenAI Structured Outputsガイドは、冒頭でその保証を明記しています。モデルの出力は、指定したJSON Schemaに常に一致します。必須のキーが欠けることも、enumにない値を作り出すこともありません。Responses APIでは、text.formatを{"type":"json_schema","strict":true,"schema":…}に設定します。ガイドには、strictモードがサポートするのはJSON Schemaの一部だけだとも書かれています。その範囲外のキーワードをスキーマで使うと、strict: trueのリクエストは失敗します。
タグ付け用のスキーマは、一度確定すると何千回も実行されます。そこで、ガイドのルールのうち、数えたり確認したりできるものをすべて書き写しました。そのうえで、スキーマがそれらのルールに沿っているかを確認する小さなスクリプトを書きました。記憶には頼っていません。
スクリーンショット:OpenAI Structured Outputsガイド、2026-11-06(SGT)に取得。ティール色の枠は当社が追加しました。 OpenAI開発者ドキュメント「Structured model outputs」
ティール色の枠内の4項目はサイズの制限です。枠の外にも、スキーマの書き方に関する厳格なルールがガイドに書かれています。
ルートはオブジェクトでなければなりません。anyOfにはできません。
すべてのオブジェクトのすべてのフィールドをrequiredに含める必要があります。フィールドを任意にするには、["string","null"]のような型を指定します。
ガイドによると、allOf、not、dependentRequired、dependentSchemas、if、then、elseはサポートされていません。
文字列について、ガイドに記載されているのはpatternとformatだけです。formatキーワードに指定できる値は、date-time、time、date、duration、email、hostname、ipv4、ipv6、uuidの9つです。
配列はminItemsとmaxItemsに対応しています。$defsと再帰参照もサポートされています。
ガイドには、新しいスキーマを使う最初のリクエストでは追加の処理レイテンシが発生するとも書かれています。同じスキーマを使う以降のリクエストでは発生しません。
ルール(ガイド内のセクション)
上限または要件
当社のタグスキーマ
不正なテスト用スキーマ
オブジェクトプロパティの総数(Objects have limitations…)
≤ 5000
9
8
ネストの深さ
≤ 10
1階層
1階層
enum値の総数(Limitations on enum size)
≤ 1000
31
31
名前と列挙値の文字数(文字列の合計サイズの制限)
≤ 120,000
416
408
additionalProperties: false
すべてのオブジェクトで必須
可能
ルートでtrueに設定、検出
全フィールドをrequiredに記載
必須
9件中9件
requiredに存在しないフィールドを記載
非対応のキーワード
allOf、if、thenなど
0
3
サポート一覧にないキーワード
警告のみ
0
minLength、1
スクリプトの結果は、単一レビュー用スキーマと25件用スキーマでは問題0件、警告0件でした。不正なスキーマでは問題5件、警告1件でした。この不正なスキーマは適当に作ったものではありません。「mentions_competitorがtrueなら、competitorsには1件以上の項目が必要」という自然な発想から生まれたものです。JSON Schemaでは、このルールをallOfとif/thenで書きます。
strictモードはこれをサポートしていません。スキーマでは、2つのフィールド間のこうしたルールを強制できません。結果が返ってきた後にコードで確認する必要があります。方法は後述の「スキーマで強制できないこと」で説明します。
minLengthについて補足します。ガイドでminLengthとmaxLengthが非対応とされているのはファインチューニング済みモデルだけですが、サポートされる文字列キーワードの一覧にはpatternとformatしかありません。そのため、スクリプトは警告を出し、当社のスキーマでは使っていません。アクションの20語制限はdescriptionとプロンプトに書き、コードで計測しています。
単一レビュー用のタグスキーマは次のとおりです。フィールドは9つで、すべて必須です。other_brand_nameとaction_itemはnullを取れます。
{
"name": "review_label",
"strict": true,
"schema": {
"type": "object",
"properties": {
"item_id": {"type": "string", "description": "Copy the item_id of the input item."},
"sentiment": {"type": "string", "enum": ["positive", "negative", "mixed", "neutral"]},
"issue_categories": {"type": "array", "minItems": 1, "maxItems": 3,
"items": {"type": "string", "enum": ["app_connectivity", "account_login", "mapping_navigation",
"suction_cleaning", "mopping", "dock_self_empty", "battery_charging", "hardware_defect",
"firmware_update", "customer_service_warranty", "price_value", "noise", "app_usability", "other", "none"]}},
"mentions_competitor": {"type": "boolean"},
"competitors": {"type": "array", "maxItems": 5,
"items": {"type": "string", "enum": ["roborock", "irobot_roomba", "ecovacs_deebot", "dreame", "eufy",
"shark", "narwal", "mova", "dyson", "samsung", "xiaomi", "other_brand"]}},
"other_brand_name": {"type": ["string", "null"]},
"action_item": {"type": ["string", "null"], "description": "One concrete action, 20 words or fewer, or null."},
"evidence": {"type": "string", "description": "Up to 15 words copied from the text."},
"language": {"type": "string", "description": "ISO 639-1 code."}
},
"required": ["item_id", "sentiment", "issue_categories", "mentions_competitor", "competitors",
"other_brand_name", "action_item", "evidence", "language"],
"additionalProperties": false
}
}
25件版では、外側に1層追加します。ルートオブジェクトのフィールドはlabels配列の1つだけです。配列の各項目は、$refで$defs内の同じタグオブジェクトを参照します。ガイドによると、$defsと$refはサポートされています。ネストの深さは1階層から2階層になり、上限の10階層を大きく下回ります。
フィールドの順序も重要です。ガイドによると、出力はスキーマのキー順に従います。そこでitem_idを先頭に置きました。action_itemとevidenceは、モデルがレビュー全体を読まないと書けないため、末尾近くに置いています。出力が途中で切れても、先頭にあるIDと感情は残ります。
タグの設計方法:4つの問いと9つのフィールド
タグを設計する前に、誰が使うのかを決めます。ロボット掃除機ブランドのアプリチームが気にするのは、Wi-Fiの切断、ログインの失敗、マップの消失です。サポートチームは保証に関する苦情を探します。プロダクトチームは、ユーザーがどの他社ブランドと比較しているかを知りたがります。
そこで、4つの問いからタグを作りました。トーンはどうか。どんな種類の問題か。他社ブランドの名前が出ているか。
1つの具体的な改善策につなげられるか。問題カテゴリは15個の値からなる固定のenumです。競合は12個の値からなるenumで、それ以外を扱うためにother_brandを用意しています。
最も時間がかかったのは問題カテゴリです。まず、実際の呼び出しで取得した412件のレビューを読みました。次に、実際の苦情を13のグループに分類しました。最後にotherとnoneを追加しました。たとえば、iRobotの低評価レビューの多くには「コマンドが通らない」や「iRobotクラウドに接続できない」と書かれています。
これらはapp_connectivityに分類します。「認証コードが届かない」や「アカウント連携に失敗した」はaccount_loginです。Roborockのレビューの多くには「アップデート後にマップが壊れた」とあります。これはfirmware_updateとmapping_navigationの両方に当てはまります。そのため、1件のレビューに最大3つのカテゴリを付けられます。
システムプロンプトには、カテゴリごとに1行のガイダンスがあります。この部分がプロンプトの半分以上を占めます。後述のトークン数で固定プレフィックスが645トークンになる主な理由もここにあります。
スキーマはすべての用紙を同じ形にします。結果が返ってきた後の確認で、互いに矛盾するフィールドを見つけます。
感情はpositive、negative、mixed、neutralの4値です。システムプロンプトには「星の評価だけで感情を判断しないこと」と書かれています。星の数と本文が食い違うことはよくあります。Roborockのレビューには、「アプリはとても良い」で始まる星1のレビューがあります。マップの問題に触れるのはその後です。前半でアプリを褒めている星3のレビューもあります。
そのレビューは後半で、Samsungの折りたたみスマートフォンでは下部のボタンが動かないと書いています。システムプロンプトでは、こうしたレビューをmixedとします。TikTokのコメントには星がありません。「どれが一番いい?」のような質問はneutralになります。
action_itemは最も検証しにくいタグなので、制限を最も厳しくしています。20語以内の英語1文、またはnullです。evidenceには、レビューから最大15語を原文の言語のまま引用する必要があります。この2つのフィールドがあれば、レビュー全体を読まなくても数秒でタグを確認できます。
このスキーマを使い、実際のレビュー12件について出力例を手作業で書きました。これを出力トークンの見積もりに使います。各フィールドがどのような形になるかもわかります。表にはそのうち4件を示します。
レビュー本文は一部を省略し、投稿者の情報は削除しています。この12件の例は人が書いたもので、GPT-6.1 Solの出力ではありません。
出典
スター数
レビューの抜粋
感情
問題カテゴリ
競合
Roborockアプリ
2
「You ruined my robot after your last update! The map is all messed up…」
negative
firmware_update, mapping_navigation
none
Roborockアプリ
3
「…it doesn't support the Samsung Z Fold 8's screen aspect ratio…」
mixed
app_usability
none(ここでのSamsungはスマートフォン)
iRobotアプリ
1
「every 2nd vacuum I have a connection issue. I will never buy another Roomba」
negative
app_connectivity
none
TikTokのコメント
n/a
「How about the Dreame X60 Pro Ultra?…」
neutral
none
dreame
EveryInfraでレビューを取得する:実際に1回リクエストしてレスポンスを受け取るまで
レビューの取得には、EveryInfraのデータエンドポイントPOST /api/v1/socialを使いました。リクエストボディでプラットフォーム、アクション、パラメータを指定します。最初の呼び出しの前に、2026-11-06 00:15(SGT)に無料の機能カタログを読み、各レビュー機能の料金、パラメータ、レスポンスフィールドを確認しました。
google_play/reviewsは1回の呼び出しあたり$0.001389です。同期呼び出しで最大200件を返します。languageとsortを指定できます。
tiktok/commentsは1回の呼び出しあたり$0.000556です。動画URLを指定します。
amazon/reviewsは1回の呼び出しあたり$0.001389です。カタログによると、Amazonの各サイトの商品ページに表示されるのは注目のレビューのみで、通常は1商品あたり十数件です。
trustpilot/reviewsは1回の呼び出しあたり$0.001389です。
Google Playへのリクエスト全体は次のとおりです。
curl -s api.everyinfra.com \
-H "Authorization: Bearer $EVERYINFRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"platform":"google_play","action":"reviews",
"params":{"app_id":"com.roborock.smart","language":"en","sort":"newest","limit":200}}'
このリクエストは2026-11-06 00:20:08(SGT)に送信し、9.45秒でHTTP 200が返りました。countは200、pagination.stop_reasonはdepth_limit、has_moreはfalseでした。課金額は$0.001389です。
各レビューにはid、text、score、thumbs_up、reply_text、reply_at、app_version、posted_atなどのフィールドがあり、カタログのresponse_fieldsリストと一致します。レスポンスには投稿者名とアバターのフィールドも含まれますが、タグ付けには不要なため、モデルへのリクエストを作成する前に削除しました。
スクリーンショット:2026-11-06 00:20:08(SGT)の実際のgoogle_play/reviewsレスポンス。レビュー本文は省略し、投稿者フィールドは削除しています。ティール色のボックスは編集部で追加しました。 EveryInfraの実際のAPIレスポンス
呼び出しは00:16:42から00:24:27(SGT)までに合計7回行いました。各呼び出しを下の表にまとめています。同期のtiktok/comments呼び出しは90.21秒かかり、HTTP 200で12件を返しました。limit 200で行ったamazon/reviewsとtrustpilot/reviewsへの同期呼び出し2回は85秒を超え、503タイムアウトとなり、課金はされませんでした。エラーメッセージには、"mode":"async"を使うかlimitを小さくするよう書かれていました。Roborockの2製品に対する非同期のamazon/reviewsジョブ2件は成功したものの0件で、課金は返金されました。
時刻(SGT)
機能
結果
クライアント側の計測時間
件数
課金額
00:16:42
amazon/reviews(同期)
503タイムアウト
89.91 s
0
$0
00:18:21 → 00:19:57
amazon/reviews(非同期)
成功、0件、返金済み
送信2.08秒、約96秒で完了
0
$0(課金後に返金)
00:18:26
tiktok/comments(同期)
200
90.21 s
12
$0.000556
00:20:08
google_play/reviews Roborock
200
9.45 s
200
$0.001389
00:20:25
google_play/reviews iRobot
200
9.24 s
200
$0.001389
00:20:34 → 00:24:18
amazon/reviews(非同期)
成功、0件、返金済み
送信2.04秒、約224秒で完了
0
$0(課金後に返金)
00:24:27
trustpilot/reviews(同期)
503タイムアウト
88.2 s
0
$0
所要時間について1点補足します。tiktok/commentsの呼び出しは90.21秒かかり、2回の503タイムアウト(89.91秒と88.2秒)より長かったものの、200で12件を返しました。2つの503エラーメッセージにはどちらも「request timed out after 85s」とあります。つまり、85秒はサーバー側の待機上限です。こちらの計測時間はリクエストの送信時に始まり、レスポンス全体の受信時に終わります。そのため、ネットワーク時間とキュー待ち時間を含みます。
レスポンスにはサーバー側の処理時間が含まれないため、TikTokの呼び出しがサーバー上でどれだけかかったかはわかりません。言えるのは、上限に近かったということだけです。同じリクエストでも、時間帯によっては503が返る可能性があります。TikTokのコメントのように遅い機能をまとめて取得する場合は、非同期モードを使ってください。
この表自体が1つの発見です。7回の呼び出しのうち、データを返したのは3回だけでした。しかし、データのなかった4回は費用がかかっていません。2つの503レスポンスでは、ボディにcharged_usd: 0と表示されています。空だった2件の非同期ジョブは送信時に呼び出し料金で課金され、ジョブ終了時に返金されました。
一括タグ付けジョブでは、データ取得に失敗しても失うのは時間だけで、費用はかかりません。本当のリスクは別にあります。「0件」を「ネガティブなレビューなし」としてレポートに書かないでください。空だった2件のAmazonジョブは深追いしませんでした。理由は「制限と注意点」のセクションで説明します。
412件のレビューはどんな内容か
モデルに送る前に、まずデータを確認しましょう。各アプリから最新の英語レビューを200件ずつ取得しました。件数は同じでも、期間は異なります。Roborockの200件は2026-10-03から2026-11-04までの32日間です。
iRobotの200件は2026-07-27から2026-11-04までの99日間です。同じ$0.001389で、Roborockの呼び出しは直近1か月、iRobotの呼び出しは3か月以上をカバーします。「最新200件」から週次レポートを作ると、iRobot側では7月の問題が今週分に入り込みます。
星評価の差はさらに大きくなります。iRobotでは星1が100件、星2が26件で、星1〜2は合計126件、全体の63%です。Roborockでは46件と22件で合計68件、34%です。
開発者の返信にも差があります。iRobotでは200件中100件にreply_textがありますが、Roborockでは49件です。
これらの数値はこの200件だけを表すもので、どちらの製品の全体的な評判を示すものでもありません。タグ付けの結果に影響するため掲載しています。iRobotのデータの大半はnegativeとapp_connectivityに分類されるでしょう。
トークン数を左右するのはレビューの長さです。Google Playレビューの長さの中央値は、Roborockが94.5文字、iRobotが128.5文字です。どちらも最長のレビューはちょうど500文字で、これはGoogle Playのレビューの文字数上限です。
Google Playヘルプコミュニティでも、あるユーザーがこの500文字の上限について質問しています。20文字未満のレビューは、Roborock側で35件、iRobot側で24件あります。例としては「Great robovac great」や「iRobot is awful」があります。
TikTokのコメント12件は文字数の中央値が51.5文字で、大半が質問です。期間は2026-06-13から2026-11-01までで、ほぼ5か月に及びます。1本の動画のコメントは日々増えていくわけではありません。動画の話題性が落ちると、新しいコメントを書く人はほとんどいなくなります。
1回の呼び出しで12件しか返らなかったのはこのためです。短いレビューについてのポイントは単純です。レビューは数十トークンしかないこともあります。1件ごとに数百トークンの指示を付けると、費用の大半は指示の分になります。
もう1つ小さな発見があります。language: "en"を指定しましたが、Roborockのデータには中国語とスロバキア語のレビューも含まれていました。カタログによると、languageは検索に使う言語で、各レビューの言語を保証するものではありません。そのため、スキーマにlanguageフィールドを設け、evidenceは原文の言語のままにしています。
「競合に言及」はキーワードルールでどこまで判定できるか
4つのタグのうち、「競合に言及」はキーワードルールに最も向いているように見えます。ルールならモデルの料金はかかりません。そこで、ロボット掃除機と家電の29ブランド名を含む正規表現を使い、412件のレビューで試しました。
アプリ自身のブランドは除外しました。ルールに一致したのは17件です。その17件をすべて読み、それぞれが本当に別のロボット掃除機ブランドに言及しているかを判断しました。
17件中13件は正しい一致です。Roborockアプリでは、5件のレビューがiRobot、Roomba、Shark、Bissellと比較しています。そのうち4件は星5で、「a great upgrade from the iRobot」などです。残る1件は星1で、5年前のRoombaのほうが良かったと述べています。
iRobotアプリでは、5件のレビューが代わりにRoborock、Shark、Rowenta、Ecovacsを買うよう勧めています。5件とも星1か星2です。TikTokで一致した3件は、Dreame、Roborock、Movaの機種について質問しています。
誤検出は3件です。1件はSamsung Z Fold 8の画面比率について、1件はアプリがSamsungのタブレットにインストールできないという内容です。最後の1件はスロバキア語の「mi」(「私」の意味)で、ルールがXiaomiと誤認しました。
残る1件は判断が分かれます。このレビューは、アプリの新しい「Explore」ページをクリックベイトだとし、「typical xiaomi」なマーケティングだと評しています。ブランド名は出てきますが、ロボット掃除機を比較しているわけではありません。
見逃しも問題です。ある星5のレビューは「Sharks, Bissell's, iRomba's」と書いていますが、ルールが検出したのはBissellだけでした。複数形とタイプミスが一致しなかったためです。よく使われる13ブランドに絞った短いリストでは14件しか見つからず、RowentaとBissellを見逃します。
教訓ははっきりしています。キーワードルールは最初のフィルターとしては使えますが、タグとしては不十分です。誤検出は、同じブランド名のスマートフォンや外国語の単語から生じます。見逃しは、複数形、タイプミス、リストにないブランドから生じます。
そのため、スキーマではcompetitorsのenumとother_brandを使い、この列はモデルに任せています。mentions_competitorのbooleanは、competitorsが空かどうかからそのまま導けます。シートで絞り込みやすくするためだけに残しています。2つのフィールドの整合性を確認する方法は後ほど紹介します。
トークン数の見積もり方:固定プレフィックスが92%を占める
OpenAIのキーがないため、モデルの費用は見積もるしかありません。各手順を示すので、ご自身の数値に置き換えて計算できます。OpenAIはGPT-6.1 Solのトークナイザーを公開していません。そこで、OpenAIのオープンソースライブラリtiktoken(PyPIのバージョン0.14.0)に含まれる最新の公開エンコーディングo200k_baseを使います。
システムプロンプト、スキーマ、入力フォーマットは実際のファイルです。レビューはライブ呼び出しで取得した412件です。出力トークンは、手書きしたタグの例12件の平均長から算出しています。推論トークンは別途仮定した値です。
実際の課金は、Responses APIの各レスポンスに含まれるusageオブジェクトに従います。本番実行の前に、パイロットとして20件のレビューを送ってください。input_tokens、output_tokens、output_tokens_details.reasoning_tokensを確認し、下の表を更新します。
項目
トークン数(o200k_base)
備考
システムプロンプト(カテゴリガイドとルール)
645
毎回のリクエストで送信
レビュー1件用のタグスキーマ
322
コンパクトなJSONとして計算。実際の課金方法はOpenAIが決定。
レビュー25件用のスキーマ
362
labels配列の階層が1つ増える
固定プレフィックス(1件/25件)
967 / 1,007
どちらもキャッシュ最小値の1,024トークンを下回る
入力としてのレビュー1件(ID、取得元、星評価付き)
平均81.1、中央値73.5、最大205
実際のレビュー412件
レビュー本文のみ
平均32.6
トークンの半分以上がフォーマット行に使われる
出力としてのタグ1件
平均87.3(65〜107)
手書きの例12件
これを1,000件のレビューに掛けると、差ははっきりします。1リクエストに1件の場合、1,000リクエストで約1,056,100入力トークンが必要です。各リクエストには、固定プレフィックスの967トークンと、メッセージフォーマット分の8トークンが含まれます。その合計は975,000トークンで、入力の92.3%を占めます。1リクエストに25件なら、必要なリクエストは40回だけです。
固定プレフィックスは40,600トークンに、入力の合計は約121,700トークンに減ります。88.5%の削減です。レビュー本文の81,100トークンは、どちらの構成でも同じです。削減分はすべて、同じ指示を何度も送らずに済むことによるものです。
プレフィックスが繰り返されるならキャッシュが効くはずだ、と考えるかもしれません。プロンプトキャッシュのガイドによると、GPT-5.6以降では、キャッシュ可能なプレフィックスの最小値は表示される入力トークンで1,024です。OpenAIが追加する非表示のシステムコンテンツはカウントされません。GPT-6.1 Solでは、キャッシュ読み取りは通常の入力料金の0.05倍、30分のキャッシュ書き込みは1.25倍、12時間の書き込みは2.5倍です。
今回のプレフィックスはo200k_baseで967トークンで、最小値に57トークン足りません。OpenAIがスキーマを非表示のシステムコンテンツに変換する場合、表示される部分は645トークンしかなく、最小値からさらに遠ざかります。いずれにしても、このプレフィックスはキャッシュされません。つまり、このプロンプトの長さでは「1リクエストに1件+キャッシュ」は成り立ちません。
プロンプトがすでに1,024トークンを超えているなら、キャッシュは効きます。プレフィックス全体がキャッシュ料金で読み取られると仮定した場合、Batchで1リクエストに1件だと1,000件あたり約$0.57です。これは1リクエストに25件の場合の$0.56に近い数字ですが、初回のキャッシュ書き込みは含んでいません。一方、1リクエストに25件の方式はキャッシュヒットに依存せず、プロンプトを水増しする必要もありません。そこで、Batchファイルは1リクエストに25件で作成します。
Batch APIの手順:JSONLの作成からアップロード、ダウンロードまで
Batch APIのガイドの説明は明快です。各リクエストを.jsonlファイルの1行として書きます。各行には、一意のcustom_id、method、url、bodyを含めます。bodyの内容は同期エンドポイントと同じです。
1つのファイルで使えるモデルは1つだけです。ファイルはpurposeをbatchにしてアップロードします。バッチ作成時のcompletion_windowは、現時点では24hしか指定できません。料金は同期エンドポイントより50%安く、Batchには独自の、より高いレート制限があります。
ガイドには、タグ付けジョブで直面する3つの制限も記載されています。1つのバッチに含められるのは最大50,000リクエストで、入力ファイルは最大200 MBです。バッチは1時間あたり最大2,000件まで作成できます。出力ファイルは入力の順序を保たないため、結果はcustom_idで照合する必要があります。
ファイルサイズを確認するため、実際の412件のレビューから2つのBatchファイルを作成しました。1リクエストに1件のレビューを入れたファイルは412行、2,018,830バイトです。各行にシステムプロンプトとスキーマ全体が含まれるため、1行あたり約4,900バイトになります。25件ずつのファイルは17行、191,412バイトで、1行あたり約11,260バイトです。
200MBの上限から計算すると、1リクエストに1件のファイルに入るレビューは約42,798件です。50,000行の上限より先にサイズの上限に達します。25件ずつのファイルなら、サイズでは約451,000件、行数では1,250,000件まで入ります。1,000件のレビューなら、どの上限にも遠く及びません。ただし数万件規模になると、1リクエスト1件の構成は想定より早く200MBに達します。
Batchを使わずに同期エンドポイントを使うと、大規模な実行では1分あたりのトークン数の上限に達することがあります。2026-10-06に、OpenAIは利用ティアをBuild、Launch、Growの3つに整理しました(レート制限ガイド)。Solのモデルページでは、Buildティアの上限は1分あたり1,000,000トークンです。Batchには同期処理の枠とは別に、より高い上限があります。大規模に実行する前に、ご自身のアカウントの制限ページを確認してください。
以下は25件ずつのファイルの1行目で、改行を加え、プロンプトとスキーマは省略しています。続いて、アップロードとバッチ作成のPythonコードを示します。これはドキュメントに基づく設定例で、OpenAI上では実行していません。
One JSONL line (cut):
{"custom_id": "chunk-000", "method": "POST", "url": "/v1/responses",
"body": {"model": "gpt-6.1-sol", "reasoning": {"effort": "low"}, "max_output_tokens": 12000, "store": false,
"input": [{"role": "developer", "content": "You label customer reviews ..."},
{"role": "user", "content": "item_id: 56a3b9f4-...\nsource: google_play/reviews app=com.roborock.smart\nrating: 5/5\ntext: ..."}],
"text": {"format": {"type": "json_schema", "name": "review_labels", "strict": true, "schema": {...}}}}}
from openai import OpenAI
client = OpenAI() # reads OPENAI_API_KEY from the environment
f = client.files.create(file=open("x25.jsonl", "rb"), purpose="batch")
batch = client.batches.create(input_file_id=f.id, endpoint="/v1/responses", completion_window="24h",
metadata={"job": "review-tagging-2026-11-06"})
print(batch.id, batch.status) # validating -> in_progress -> finalizing -> completed
max_output_tokensは十分大きく設定してください。1件あたり約87トークンのタグが25件で、約2,200トークンになります。さらに推論トークンが加わります。今回は12,000に設定しました。
値が小さすぎると、構造化出力が途中で止まります。このケースはStructured Outputsガイドで説明されています。レスポンスのステータスはincomplete、理由はmax_output_tokensになります。得られるJSONは不完全なので、失敗として扱ってください。
store: falseは、後で参照するためにレスポンスを保存しないようOpenAIに指示します。ただしBatch API FAQによると、ゼロデータリテンションはBatch APIには適用されません。入力・出力・エラーファイルは、BatchとFilesサービスの保持ルールに従います。レビューに個人データが含まれる場合は、送信前に削除してください。今回はリクエストの作成時に投稿者名、投稿者のアバター、レビューのURLを削除し、レビューID、ソース、星の数、本文だけを残しました。
結果をダウンロードする際、ステータスはvalidating、in_progress、finalizing、completedの順に進みます。24時間以内に完了しなかったバッチはexpiredになります。完了した分は受け取ることができ、その分の料金が発生します。未完了のリクエストは、コードbatch_expiredとしてエラーファイルに入ります。出力ファイルはバッチ終了から30日後に削除されるので、期限内にダウンロードしてください。
すべてのフィールドはBatch APIリファレンスとFiles APIリファレンスに記載されています。低価格で同期的な応答が欲しい場合は、Flex processingをBatch料金で利用できます。その代わり応答は遅くなり、ときどき429「resource unavailable」エラーが返ります。このエラーは課金されません。
1,000件のレビューあたりのコスト:データ料金とモデル料金の合計
ここでデータ料金とタグ付けの料金を合わせてみます。データ料金は、実際に確認した1回の呼び出しあたりの件数とカタログ価格から計算しました。Google Playは1回の呼び出しで200件を返すので、1,000件のレビューに必要な呼び出しは5回で、$0.006945です。今回のTikTok動画は1回の呼び出しで12件だったため、1,000件には84回の呼び出しが必要で、$0.046704になります。6.7倍の差です。412件の混合セットの費用は$0.003334で、1,000件あたり$0.008092でした。
TikTokの数字は、動画1本あたり12件を前提にしています。人気の動画なら1回の呼び出しで返る件数が多くなり、データ料金はずっと下がります。この数字を示したのは、1つの点を伝えるためです。1,000件あたりのデータ料金は、単価だけでなく、1回の呼び出しで返る件数によって決まります。
モデル料金は、2026-11-06時点のOpenAIの公開価格と上記のトークン数から見積もりました。レビュー1件あたりの推論トークンは、0、50、200の3段階を想定しています。0は下限値ですが、Solの最低設定はlowなので、実際には到達できません。以下はすべて、レビュー1,000件あたりの金額(米ドル)です。EveryInfraのデータ料金は含みません。
構成(レビュー1,000件あたり)
推論トークン0(下限)
1件あたり推論トークン50(想定)
1件あたり推論トークン200(想定)
Sol標準、1リクエストに1件
$2.99
$3.49
$4.99
Sol Batch、1リクエストに1件
$1.49
$1.74
$2.49
Sol Batch、1リクエストに1件、プレフィックスをキャッシュ価格で計算(仮定。プレフィックスは1,024未満)
$0.57
$0.82
$1.57
Sol Batch、1リクエストに25件
$0.56
$0.81
$1.56
Terra Batch、1リクエストに25件、none
$0.28
n/a
n/a
Luna Batch、1リクエストに25件、none
$0.028
n/a
n/a
この表は3つの観点で読めます。第1に、同じモデル・同じプロンプトでも、「標準、1リクエストに1件」から「Batch、1リクエストに25件」に変えると、下限は$2.99から$0.56に下がります。5.3分の1です。
第2に、1リクエスト25件の場合、費用の大半は出力です。$0.56のうち出力は$0.44で、約78%を占めます。レビュー1件あたりの推論トークンが50増えるごとに、$0.25(50 × 1,000 × $5 ÷ 1,000,000)増えます。つまり最終的な請求額は、ご自身のレビューでlowがどれだけ推論トークンを生成するかで決まります。それを測れるのはパイロット実行だけです。
第3に、データ料金は全体のごく一部です。Google Playの$0.006945とSol Batch(1リクエスト25件)の$0.56を足すと約$0.566で、データ料金はその1.2%です。TikTokの$0.046704で計算しても、7.7%にすぎません。
ご自身の数字で計算するには、4つの値が必要です。Pは固定プレフィックスのトークン数、xはレビュー1件あたりの平均入力トークン数、yはタグ1件あたりの平均出力トークン数です。
rはレビュー1件あたりの推論トークン数です。Batch料金で1リクエストにk件のレビューを入れる場合、1,000件あたりのモデル料金はおよそ(1,000 ÷ k × P + 1,000 × x) × $1 ÷ 1,000,000に、1,000 × (y + r) × $5 ÷ 1,000,000を足した額です。この記事では、Pは967または1,007、xは81.1、yは87.3で、rは不明です。
レビューが長いとxの項が大きくなり、kの影響は小さくなります。長いECレビューでは、平均300〜400トークンになることもあります。プロンプトが長いとPの項が大きくなり、kを大きくする効果が増します。この式には、入力が272Kトークンを超えた場合の追加料金は含まれていません。1リクエスト25件なら各リクエストは3,000トークン強で、その境界からは十分離れています。1リクエストに数千件のレビューを入れる予定なら、追加料金を加算するか、リクエストを分割してください。
規模感の目安として、Gilardiらによる2023年の論文「ChatGPT Outperforms Crowd-Workers for Text-Annotation Tasks」では、2,382件のツイートでアノテーション1件あたり$0.003未満と報告されています。今回のSol Batch(1リクエスト25件)の下限はレビュー1件あたり約$0.00056、推論トークン50の場合は$0.00081です。
一方は論文の実測値で、もう一方は私たちの見積もりであり、OpenAI上では実行していません。タスクとモデルも異なります。あくまで規模感の目安として使ってください。価格がどれだけ下がったかを示すものではなく、精度についても何も示していません。
Sol、Terra、LunaとDecisions API:Solを使うべきでない場面
コスト表の最後の2行は、意図的に入れています。GPT-6.1 Terraは2026-10-26に公開されました。発表記事によると、標準価格はGPT-6.1 Solより50%安く、大量の文書処理に向いています。推論のnoneに対応しています。Batch価格は入力$0.50、出力$2.50です。OpenAIはGPT-6 Lunaを、用途を絞った大量処理向けの効率的なモデルと位置づけています。
Lunaもnoneに対応しています。Batch価格は入力$0.05、出力$0.25です。同じトークン見積もりで、Terraは1,000件のレビューあたり$0.28、Lunaは$0.028です。それぞれSolの下限の2分の1と20分の1です。しかも、どちらにも推論トークンという不確定なコストがありません。
では、Solを使うべきなのはどんなときでしょうか。第1に、「直近のファームウェアバージョンがこの不具合の原因か?」のようなタグに推論が必要な場合や、action_itemをプロダクトマネージャーが書いたような内容にする必要がある場合です。第2に、LunaやTerraの実行結果を抜き取りチェックし、感情がmixedに分類されたケースなど、一部のグループで誤りが多いとわかった場合です。その場合は、その部分だけをSolで再実行します。文言から読み取れるタグなら、Solの推論は無駄になります。
OpenAIには分類専用の手段もあります。Decisions APIは2026-10-06にベータ版として公開され、2026-10-22に一般提供が始まりました。現在対応しているのはgpt-6-lunaだけです。モデルにJSONを書かせる方式ではありません。与えた質問に対して、型付きの回答を返します。
predicateは0〜1の確率を返します。choiceは指定した選択肢から1つを選び、各選択肢の確率を返します。scoreは2〜10点の尺度で評価します。1回のリクエストに最大400問を含められます。OpenAIによると、Responses APIより約10倍高速です。
ガイドには使い分けが明記されています。確率、単一選択、スコアにはDecisionsを使います。スキーマに沿ったオブジェクトを生成するには、Structured Outputsを使います。感情、問題カテゴリ、競合への言及はDecisionsの質問として設定し、信頼度を得られます。action_itemとevidenceには、引き続きStructured Outputsが必要です。
オプション
適した用途
1,000件あたりのモデル費用(見積もり)
主な制約
GPT-6.1 Sol + Structured Outputs + Batch
推論が必要なタグ、文章形式のアクション、一部を対象とした高品質な再実行
$0.56〜(レビュー1件あたり推論トークン50ごとに+$0.25)
推論はオフにできません。24時間以内に返却されます。
GPT-6.1 Terra + Structured Outputs + Batch
大量処理、明確なフィールド、1文の記述
約$0.28(none)
難しい判断ではSolに劣る
GPT-6 Luna + Structured Outputs + Batch
分類のみの超大規模処理
約$0.028(none)
文章形式のアクションの品質を抜き取りチェック
Decisions API(Luna)
信頼度付きの感情、カテゴリ、競合への言及
ここでは見積もりなし(Lunaのトークン価格。質問数が使用量に影響)
自由記述は不可。Lunaのみ。
スキーマでは強制できないこと:結果が返ってきた後のチェック
strictモードが保証するのは形です。すべてのフィールドがそろい、型が正しく、enum値が有効であることは保証されます。内容が正しいことは保証されません。2つのフィールドが互いに矛盾することも防げません。
Pythonのjsonschemaライブラリで小さなテストをしました。TikTokコメントに手書きしたタグを、competitors: ["dreame"]とmentions_competitor: falseに変更しました。2つのフィールドは明らかに矛盾していますが、このタグはスキーマ検証を通過しました。
Structured Outputsガイドにも、Structured Outputsの結果に誤りが含まれる場合があると書かれています。対策として、指示の調整、例の追加、タスクの分割が勧められています。そのため結果が返ってきたら、少なくとも以下のチェックを実行してください。
def check(label, item):
errs = []
if label["mentions_competitor"] != bool(label["competitors"]):
errs.append("mentions_competitor does not match competitors")
if ("other_brand" in label["competitors"]) != (label["other_brand_name"] is not None):
errs.append("other_brand does not match other_brand_name")
if "none" in label["issue_categories"] and len(label["issue_categories"]) > 1:
errs.append("none cannot appear with other categories")
if label["action_item"] and len(label["action_item"].split()) > 20:
errs.append("action_item is longer than 20 words")
if label["evidence"] and label["evidence"].lower()[:30] not in item["text"].lower():
errs.append("evidence is not a quote from the text")
if label["item_id"] != item["id"]:
errs.append("item_id does not match")
return errs
各チェックは、スキーマでは表現できないルールを扱います。2つのフィールドは、同時にtrueか同時にfalseでなければなりません。一部のenum値は、他の値と両立しません。語数の上限があります。
そして、引用は元の文章から取られていなければなりません。最後のチェックが最も役に立ちます。モデルがでっち上げた「引用」を検出できます。
1リクエスト25件の場合は、もう1つチェックが必要です。labels配列には25個の要素があり、各item_idが入力のいずれかと一致しなければなりません。ガイドによると、出力はスキーマのキー順に従います。配列の要素が入力順に返るとは保証されていません。位置ではなくIDで照合するほうが安全です。
さらに2つのケースには個別の対応が必要です。第1に、モデルが拒否するケースです。ガイドによると、モデルが安全上の理由で拒否した場合、コンテンツタイプはrefusalになり、スキーマには従いません。レビューには、暴言や過激な言葉を含むものがあります。私たちが読んだiRobotのレビューの1件は、侮辱的な言葉で開発者をののしっていました。refusalを捕捉できるようにしておいてください。
第2に、タスクと関係のない入力です。ガイドでは、タスクに合わない入力に対して何を返すかを、プロンプトで指定するよう勧めています。システムプロンプトのルール8がこれに当たります。空のレビュー、広告、無関係なテキストには、neutral、none、競合なしを返させます。
制限事項と注意点
今回はAmazonのレビューが返ってきませんでした。2つの非同期ジョブはいずれも取得件数0件で正常終了し、料金は返金されました。カタログによると、Amazonの各サイトでは商品ページに注目のレビューしか表示されず、通常は1商品あたり十数件です。1回の呼び出しで十数件だと、データ料金は1,000件のレビューあたり$0.08を超え、Google Playの10倍以上になります。Amazonの予算を決める前に、いくつかの商品で試してください。コスト表にAmazonは含めていません。
同期呼び出しの処理時間の上限は85秒です。今回、amazon/reviewsとtrustpilot/reviews(limit 200)は、どちらも85秒以上待った後に503タイムアウトを返しました。料金はかかっていません。レスポンスでは、"mode":"async"を使うか、limitを小さくするよう案内されました。85秒はサーバー側で計測されます。tiktok/commentsの呼び出しはこちら側で90.21秒かかりましたが、HTTP 200が返りました。これも上限に近い状態でした。
バッチでデータを取得する場合、時間のかかる機能には非同期モードを使ってください。すぐにjob_id付きの202が返るので、その後poll_urlをポーリングします。今回の2つの非同期ジョブは、開始から完了まで約1分36秒と3分44秒でした。
「最新200件」は一定の期間ではありません。前述のとおり、Roborockの200件は32日間、iRobotの200件は99日間にわたります。週ごとの比較をするなら、タグ付けの前にposted_atで同じ週を切り出してください。
同期モードでは、Google Playのページネーションは200件までです。それ以上取得するには、非同期モードを使ってください。上限はカタログのpaginationフィールドで確認できます。
自社アプリの場合、GoogleのReply to Reviews APIが返すのは直近1週間のレビューだけです。1ページあたり最大100件で、GETリクエストは1時間あたり200回までです。それより古いレビューは、Play ConsoleのCSVダウンロードから取得します。競合のアプリについては、Google APIでは何も取得できません。
レビューは本物か。米国FTCの偽レビューに関する最終規則(2024年8月)は、AIが生成した偽レビューを禁止しています。タグからわかるのはレビューに何が書かれているかであり、そのレビューが本物かどうかではありません。まずは、星5つや星1つのレビューが不自然に集中していないかを確認してください。
構造化出力は推論の質に影響する可能性があります。論文「Let Me Speak Freely?」(2024年)は、JSONなどの厳格な形式によって推論性能が下がる場合があることを示しました。「JSONSchemaBench」(2025年)は、OpenAIの方式を含む6つの制約付きデコーディング手法を10,000件の実際のスキーマで検証し、手法間に大きな差があることを確認しました。どちらも検証対象は旧世代のモデルです。それでも、本記事で採用した方針の1つを裏付けています。action_itemをスキーマの後方に置くことです。あるタググループのスコアが低い場合は、まず自由形式で回答させ、その後、安価なモデルでJSONに変換してみてください。
精度はモデルではなく人が確認してください。今回はOpenAIでバッチを実行していないため、精度の数値はありません。推奨する手順は次のとおりです。まず、パイロットの20件のレビューに、本番実行から無作為に選んだ50件を加えます。次に、製品に詳しい人に、各タグを本文と照らし合わせて確認してもらい、カテゴリごとに誤りを数えます。最後に、新しいプロンプトが必要なカテゴリと、別のモデルでの再実行が必要なカテゴリを判断します。
キーワードに一致した上記17件にも、同じ方法を使いました。データアノテーション向けLLMのサーベイには、モデルが作成したアノテーションの評価方法を扱う節があります。人によるレビューはその方法の1つです。人がラベル付けした既存のデータセットとの比較も、別の方法として挙げられています。
本サイトの他のレビュー記事との違い
Claude CodeでDeepSeek V4を使ってコメントをクリーニングするでは、動画コメント695件を対話型セッションでクリーニングします。そのコストはキャッシュヒットに左右されます。ClaudeでデータAPIへのツール入力を厳格化するでは、ツールへの入力を厳格にします。Qwen Agentでレビュー分析エージェントを作るでは、1つのエージェントが価格を比較し、レビューを読みます。本記事で厳格にするのは出力形式です。ジョブは1回のオフラインバッチで、コストを左右するのはリクエストの構成です。
ChatGPT内でレポートを作成する方法は、GPT-6のIntelligent UIでソーシャルデータのレポートを作成するを参照してください。
直近60日間の変更点
2026-09-29:GPT-6.1 SolがAPIで利用可能に(変更履歴)。大規模な構造化ジョブで、最上位モデルに近いモデルをAstraの5分の1の価格で使えるようになりました。ただし、推論をオフにすることはできません。
2026-10-06:Decisions APIがベータ版として公開されました。その後、2026-10-22に一般提供が始まりました(Decisionsガイド)。単一選択タグでは、JSONを使わずに信頼度付きの結果を得られるようになりました。Lunaでのみ利用できます。
2026-10-20:12時間のプロンプトキャッシュがAPIで利用可能になりました。書き込みは入力価格の2.5倍です(プロンプトキャッシュのガイド)。1日に数回実行し、プロンプトが1,024トークンを超えるジョブで効果があります。短いプレフィックスでは効果がありません。
2026-10-21:組織とプロジェクトでサービスティアを制限可能に。プラットフォームの設定から行えます(変更履歴)。実務への影響:管理者はデフォルトのティアだけを許可できます。その場合、Flexなどデフォルト以外のティアへのリクエストは、サービスティアのエラーで失敗します。バッチが失敗してからではなく、実行前にプロジェクト設定を確認してください。
2026-10-26:GPT-6.1 Terraが登場。標準価格はSolの半分で、推論レベルnoneに対応しています(Terraのモデルページ)。実務への影響:SolとLunaの中間にあたり、推論をオフにできる選択肢ができました。多くのタグ付けジョブでは、まずTerraを試すとよいでしょう。
2026-10-28:ChatGPTの契約者がAPIクレジットを受け取れるように。対象となるProおよびBusinessの契約者は、毎月最大$500を受け取れます(クレジットのガイド)。パイロット実行には十分な額です。未使用のクレジットは毎月失効します。
GPT-6.1 Solと構造化出力のよくある質問
GPT-6.1 Sol APIの料金はいくらですか?
料金表の全体は、この記事の最初のセクションに掲載しています。要点をまとめると、OpenAIの料金ページでは、標準料金で入力が1Mトークンあたり$2、出力が1Mトークンあたり$10です。BatchとFlexはその半額です。Fastモードは標準料金の2倍、Ultrafastは6倍ですが、バッチでのタグ付けにはどちらも必要ありません。推論トークンは出力として課金されます。(質問の出典:Googleのオートコンプリート、2026-11-06。)
GPT-6.1 SolのAPIでのモデル名は何で、どう呼び出せばよいですか?
モデル名はgpt-6.1-solです。Responses APIのリクエストで、modelフィールドに指定します(モデルページ)。Chat Completionsでも使えますが、ツール呼び出しには対応していません。
ツールを使う場合、移行ガイドではResponses APIを推奨しています。推論はreasoning.effortで設定します。最も低い値はlowです。(質問の出典:Googleのオートコンプリート、「gpt 6.1 sol api name」)
GPT-6.1のStructured Outputsの例はどう書けばよいですか?
最も短い書き方は、Responses APIのリクエストにtext: {format: {type: "json_schema", name: "...", strict: true, schema: {...}}}を追加する方法です。スキーマ内のすべてのオブジェクトにadditionalProperties: falseが必要で、すべてのフィールドをrequiredに含める必要があります。Pythonでは、SDKのresponses.parseにPydanticモデルを渡すこともできます。その場合、スキーマはSDKが生成します(openai-python helpers、PydanticのJSON Schemaドキュメント)。本記事の9フィールドのスキーマをコピーして、変更して使えます。(質問の出典:Googleのオートコンプリート、「gpt 6.1 structured outputs example」)
OpenAIのBatch APIとStructured Outputsは併用できますか?
できます。Structured Outputsガイドによると、Structured OutputsとJSONモードはどちらもBatch APIで使えます。Batchの各行のbodyは同期リクエストと同じなので、text.formatはそのままで構いません。
注意点は3つです。まず、1つのファイルで使えるモデルは1つだけです。次に、JSONが途中で切れないよう、max_output_tokensを十分に大きく設定します。最後に、結果は行の順序ではなくcustom_idで照合します。(質問の出典:Googleのオートコンプリート、「openai batch api structured output」)
max effortのGPT-6.1 Solが、GPT-5.6 Solよりはるかに遅いのはなぜですか?
この質問はOpenAI開発者コミュニティのスレッドから取り上げました。同じプロンプトで、maxに設定したGPT-6.1 Solは1,846秒で約62,708出力トークン(うち推論52,552)を生成しました。GPT-5.6 Solは373秒で約31,426トークンでした。タグ付けではmaxを使わないでください。lowで十分です。推論トークンが2倍になれば、出力料金も2倍になります。
始め方
EveryInfraのクイックスタートでAPIキーを作成します。無料の機能カタログを確認します。google_play/reviews、tiktok/comments、amazon/reviewsの料金とレスポンスフィールドを確認します。
最初に、各ソースから1ページずつ取得します。Google PlayレビューAPIとTikTokコメントAPIのプレイグラウンドで試し、1回の呼び出しで何件返るかを確認します。そのうえで、レビュー1,000件あたりの予算を計算します。処理に時間がかかる機能には"mode":"async"を追加します。
本記事の9フィールドのスキーマを、自社のカテゴリに合わせて変更します。Structured Outputsガイドにあるstrictモードのルールを満たしているか、スクリプトで確認します。
OpenAIで、20件のレビューを使った同期のパイロットを実行します。usageで入力、出力、推論のトークン数を読み、コスト表を更新します。そのうえで、Sol、Terra、Lunaのいずれかを選びます。
1リクエストあたり25件のレビューでJSONLファイルを作成し、Batch APIを使います。結果が返ってきたら、整合性チェックを実行します。evidenceを元のテキストと照らし合わせて抜き取り確認します。
コードを書かずにClaude、Cursor、ChatGPTでその都度レビューを調べるには、EveryInfraのリモートMCPサーバーに接続します。各プラットフォームの概要は、Google PlayデータAPI、TikTokデータAPI、AmazonデータAPIを参照してください。AmazonのレビューについてはAmazonレビューAPIを、料金については料金ページを参照してください。
参考文献
「GPT-6.1 Sol Model」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Introducing GPT-6.1 Sol」、OpenAI、2026-09-29
「API Changelog」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Pricing」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Structured model outputs」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Batch API」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Prompt caching」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Using GPT-6 (latest model guide)」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Reasoning models」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Decisions」、OpenAI開発者ドキュメント、2026-11-06閲覧
「GPT-6.1 Terra Model」、OpenAI開発者ドキュメント、2026-11-06閲覧
「GPT-6 Luna Model」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Rate limits」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Flex processing」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Function calling」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Batch API reference」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Files API reference」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Ultrafast mode」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Error codes」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Your data」、OpenAI開発者ドキュメント、2026-11-06閲覧
「API credits for ChatGPT subscribers」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Migrate to the Responses API」、OpenAI開発者ドキュメント、2026-11-06閲覧
「Prompt cache diagnostics」、OpenAI開発者ドキュメント、2026-11-06閲覧
「GPT-6.1 Sol system card addendum」、OpenAI Deployment Safety Hub、2026-09-29
「Introducing GPT-6.1 Terra」、OpenAI、2026-10-26
「Batch API FAQ」、OpenAIヘルプセンター、2026-11-06閲覧
「tiktoken」、OpenAI、GitHub
「openai-python helpers」、OpenAI、GitHub
「tiktoken 0.14.0」、PyPI
「jsonschema」、PyPI
「JSON Schema (Pydantic docs)」、Pydantic
「JSON Schema Draft 2020-12」、JSON Schema
「RFC 8259: The JSON Data Interchange Format」、IETF、2017-12
「Reply to Reviews」、Google Play Developer API、2026-11-06閲覧
「Download and export monthly reports」、Play Consoleヘルプ、2026-11-06閲覧
「Why is there a 500 character limit on Google Play reviews?」、Google Playヘルプコミュニティ、2022-07-31
「Query Video Comments」、TikTok for Developers、2026-11-06閲覧
「FTC Announces Final Rule Banning Fake Reviews and Testimonials」、米国連邦取引委員会、2024-08-14
「ChatGPT Outperforms Crowd-Workers for Text-Annotation Tasks」、Gilardiほか、arXiv、2023-03-27
「Let Me Speak Freely? A Study on the Impact of Format Restrictions on Performance of Large Language Models」、Tamほか、arXiv、2024-08-05
「JSONSchemaBench: A Rigorous Benchmark of Structured Outputs for Language Models」、Gengほか、arXiv、2025-01-18
「Large Language Models for Data Annotation and Synthesis: A Survey」、Tanほか、arXiv、2024-02-21
「Why is GPT-6.1 Sol MAX dramatically slower than GPT-5.6 Sol MAX using the same prompt?」、OpenAI開発者コミュニティ、2026-11-06閲覧
「EveryInfra機能カタログ」、EveryInfra、2026-11-06 00:15 SGT閲覧
「Roborock(Google Playのアプリページ)」、Google Play、2026-11-06閲覧
「iRobot Home(Google Playのアプリページ)」、Google Play、2026-11-06閲覧
「Model Context Protocol Specification」、MCP、2025-06-18
「json — JSON encoder and decoder」、Pythonドキュメント
「JSON Lines」、jsonlines.org
「Introduction to Structured Outputs (Cookbook)」、OpenAI Cookbook
!
この記事は EveryInfra ブログの記事の転載です。原文:GPT-6.1 Sol APIの構造化出力でレビューにタグ付けすると、コストはいくらになるか
EveryInfra は AI エージェントと開発者向けの従量課金データ API です:everyinfra.com
This is likely not a real issue and the guy is trolling (he posted a similar issue months ago somewhere else) but the comments are... revealing codex#59060
1 comment
【Codex】【VSCode】Remote SSH切断後に「別のアプリで開いています」と表示される問題の解決方法
VS CodeのRemote SSH環境でCodexを使用していたところ、SSH接続が切れた後、既存の会話を開けなくなる問題が発生しました。
VS CodeやローカルPCの再起動、Codex拡張機能のバージョン変更などを試しましたが、改善しませんでした。
!
今回の環境では、リモートLinux上に残っていた古いCodexプロセスを終了することで、会話履歴を削除せずに復旧できました。
VS CodeやPCを再起動しても改善しない場合は、リモート側に残ったプロセスと、会話のロックを確認します。
本記事では、発生した問題と考えられる原因、実際に行った解決手順を紹介します。
発生した問題
使用環境
エディタ:VS Code
接続方法:VS CodeのRemote - SSH拡張機能
リモート側のOS:Linux
使用した拡張機能:OpenAIのCodex拡張機能
Remote SSH経由でCodexを使用していたところ、会話の途中でリモート接続が切れました。
その後、SSHで再接続し、Codexで既存の会話を開こうとしたところ、以下のメッセージが表示され、会話を再開できませんでした。
メッセージを読み込めませんでした
別のアプリで開いています
ここで続けるには、あちらで閉じてください。
(一部画像はAIを用いて修正)
しかし、切断前の会話画面には戻れず、メッセージの指示どおりに元の画面で会話を閉じることもできませんでした。
試したが解決しなかった方法
まず、以下の一般的な対処方法を試しました。
VS Codeの再起動
PCの再起動
Remote-SSH: Kill VS Code Server on Host... によるリモート側VS Code Serverの終了
Codex拡張機能のバージョン変更
しかし、いずれの方法でも改善しませんでした。
もちろん、これらの操作によって解決するケースもあります。ただし、今回のように解決しない場合は、リモートサーバー上にCodexのプロセスが残存していないかを確認する必要があります。
考えられる原因:古いCodexプロセスによる会話のロック
今回の問題は、SSH接続が切れた後もリモートサーバー上に古いCodexプロセスが残り、会話のロック(writer lock)を保持していたことが原因と考えられます。
Codexでは、同じ会話に複数のプロセスが同時に書き込まないよう、writer lockによる排他制御が行われています。
そのため、古いプロセスがロックを保持したままだと、新しく起動したCodexから既存の会話を開けなくなる場合があります。
同様の問題は、CodexのGitHubリポジトリでも報告されています。
GitHub Issue #41849:Remote SSH再接続後に古いCodexプロセスがwriter lockを保持する問題
解決方法
!
以下のコマンドは、Remote SSH接続先のLinuxターミナルで実行します。
復旧までの流れは、次の4ステップです。
Codexプロセスを確認する
writer lockを保持しているプロセスを特定する
不要になった古いCodexプロセスを終了する
VS Codeで会話を開き直す
Step 1:Codexプロセスを確認する
まず、Codex関連のプロセスが現在どの程度起動しているかを確認します。
Codex関連プロセスの確認
ps -u "$USER" -o pid,ppid,etime,args | grep -Ei '[c]odex|[c]hatgpt|[e]xtensionHost'
このコマンドでは、Codex関連のプロセスについて、PID(プロセスID)、PPID(親プロセスID)、起動からの経過時間、実行コマンドを確認できます。
今回の環境では、以下のような結果が得られました。
実行結果(抜粋・整形)
PID ELAPSED COMMAND
1204484 6-04:53:18 codex app-server
2875105 11:20:31 codex app-server
3419599 03:29 codex app-server
ELAPSED はプロセスが起動してからの経過時間を示しています。
今回の場合、約6日前、約11時間前、約3分前から動作しているCodexプロセスが同時に存在していました。
特に、古いプロセスと新しく起動したプロセスが同時に残っていることが気になります。
ただし、起動時間が古いからといって不要なプロセスとは限りません。次の手順で、実際に会話のロックを保持しているプロセスを確認します。
Step 2:writer lockを保持しているプロセスを特定する
次に、lsofコマンドを使用して、Codexのロックファイルを開いているプロセスを確認します。
writer lockを保持しているプロセスの確認
lsof -nP +D "$HOME/.codex/thread-writer-locks"
lsof は、実行中のプロセスが開いているファイルを調べるためのコマンドです。
Codexのwriter lockに関するファイルは、今回の環境では ~/.codex/thread-writer-locks/ に存在していました。
実行すると、以下のような結果が得られました。
実行結果(抜粋・整形)
COMMAND PID FD NAME
codex 1204484 17uW .../thread-writer-locks/xxx.lock
codex 1204484 41uW .../thread-writer-locks/xxx.lock
codex 1204484 48uW .../thread-writer-locks/xxx.lock
codex 1204484 71uW .../thread-writer-locks/xxx.lock
codex 2875105 45uW .../thread-writer-locks/xxx.lock
codex 2875105 62uW .../thread-writer-locks/xxx.lock
ロックファイル名は省略しています。
FD 列にある uW のうち、u はファイルが読み書き可能な状態で開かれていること、W はファイル全体への書き込みロックを保持していることを示しています。
この結果から、PID 1204484 と 2875105 の2つのCodexプロセスが、複数のwriter lockを保持していることがわかりました。
一方、Step 1で確認した新しいCodexプロセス(PID 3419599)は、この出力には含まれていません。
さらに、次のコマンドでプロセスの親子関係を確認します。
pstreeによる親子関係の確認
pstree -sp 2875105
pstree は、プロセスの親子関係をツリー形式で表示するコマンドです。PIDは自分の環境で確認したものに置き換えてください。
今回の環境では、プロセスの親子関係などを確認した結果、PID 2875105 は古いVS Codeの拡張機能から起動されたCodexの app-server と判断しました。
そこで、この古いプロセスが問題の原因になっている可能性を考えました。
!
複数のロックが確認できても、それだけで問題の会話に対応するロックを特定できたとは限りません。PIDや親プロセス、ほかのCodexセッションの稼働状況を合わせて確認することが重要です。
補足:lsofでFUSE関連の警告が表示される場合
lsofの実行時に、FUSE関連の警告が表示される場合があります。今回も警告が表示されましたが、ロックファイルに関する情報は取得できていました。
Step 3:古いCodexプロセスを終了する
今回の環境では、PID 2875105 が古いVS Codeの拡張機能から起動されたプロセスであることが確認できました。
そのため、ほかの作業で使用していないことを確認したうえで、以下のコマンドを実行しました。
!
以下のPIDは今回の環境での例です。必ず自分の環境で確認したPIDに置き換えてください。
別のCodexセッションで実行中のタスクがある場合、そのプロセスを終了すると処理が中断される可能性があります。
対象のプロセスに終了を要求(PIDは置き換える)
kill -TERM 2875105
kill -TERM は、指定したプロセスに終了要求のシグナル(SIGTERM)を送信するコマンドです。
実際の終了処理は、プロセス側の実装によって異なります。
終了後、必要に応じて以下のコマンドでロックの状態を再確認できます。
終了後のロック状態の再確認
lsof -nP +D "$HOME/.codex/thread-writer-locks"
Step 4:VS Codeで会話を開き直す
古いプロセスを終了した後、Codexの既存の会話を開き直します。
画面が更新されない場合は、VS Codeのコマンドパレットから以下を実行します。
コマンドパレットで実行
Developer: Reload Window
コマンドパレットは、次のショートカットで開けます。
OS
ショートカット
macOS
Cmd + Shift + P
Windows / Linux
Ctrl + Shift + P
VS Codeの再起動でも構いません。
今回の環境では、古いCodexプロセスを終了することでエラーが解消し、以前の会話を正常に再開できました。
会話履歴や設定を削除する必要もありませんでした。
備考・注意点
なぜVS CodeやPCの再起動では解決しなかったのか?
今回の問題では、ローカルPCの再起動だけでなく、Remote SSHのVS Code Serverを終了させても改善しませんでした。
これは、リモートサーバー上のCodexプロセスが残存していたためと考えられます。
ローカルPCを再起動しても、SSH接続先で実行されているプロセスまで終了するとは限りません。
また、Remote-SSH: Kill VS Code Server on Host... はVS Code Serverの終了に用いるコマンドですが、今回、この操作でも改善しませんでした。Codexプロセスが残った詳しい経緯は確認できていません。
一方、以下のような原因であれば、一般的な再起動操作で解決できる場合もあります。
対処方法
改善が期待できるケース
VS Codeの再起動
エディタや拡張機能の一時的な不具合
PCの再起動
ローカル側のプロセスや接続状態の不具合
Kill VS Code Server on Host
リモート側のVS Code Serverの異常
Codex拡張機能の更新・変更
特定バージョンに起因する不具合
ただし、今回のようにリモートLinux上の古いCodexプロセスがwriter lockを保持している場合、これらの操作だけでは解決しないことがあります。
その場合は、問題を起こしているCodexプロセスを直接特定することが重要です。
プロセスの終了やロックファイルの削除には注意
!
復旧作業の際は、以下の点に注意してください。
~/.codex ディレクトリ全体を削除しない。会話履歴や設定を失う可能性があります。
pkill -9 codex などで、すべてのCodexプロセスを無差別に強制終了しない。
複数のCodexセッションを使用している場合は、終了対象のプロセスがほかの作業に使用されていないか確認する。
ロックファイルを安易に削除せず、まずはロックを保持しているプロセスを調べる。
今回のケースでは、古いCodexプロセスを1つ終了するだけで復旧できました。
まとめ
VS CodeのRemote SSH環境でCodexを使用している際、SSH接続が切れると、古いCodexプロセスが残存して会話のwriter lockを保持し続ける場合があります。
VS CodeやPCの再起動でも改善しない場合は、以下の手順を試してみてください。
ps でCodex関連プロセスを確認する
lsof でwriter lockを保持しているプロセスを調べる
不要になった古いCodexプロセスを終了する
VS Codeで既存の会話を開き直す
今回はこの方法で、会話履歴を失うことなくCodexを復旧できました。
同じ問題で困っている方の参考になれば幸いです。
参考資料
openai/codex — GitHub Issue #41849
Visual Studio Code — Remote Development Tips and Tricks
1 like
if you are having a bad day, remember that meat proxies for AIs exist:
codex#59060
"Told to reboot a server, it destroyed systemd, destroyed containers, ran random scripts, cost me $10,000+"
pure gol- err platinum 🥇
1 favourite · 1 boost
Told to reboot a server, it destroyed systemd, destroyed containers, ran random
46 points · 32 comments
Bro asked AI to reboot the server and it rebooted his entire bloodline.
Source: codex#59060
8 favourites · 2 comments · 4 boosts
Told to reboot a server, destroyed systemd, destroyed containers, cost me $10k+
2 points · 1 comment
Codex Was Torching Your SSD and Nobody Noticed for Months
Your Codex log file looked fine. Your SSD counters did not.
The file was small.
That was the trick.
If you opened <code>~/.codex/</code>, you could see a SQLite database called <code>logs_2.sqlite</code>. It was not huge. It was not swallowing the disk. Finder, Explorer, <code>du</code>, and every normal storage check had nothing dramatic to say.
Meanwhile, one developer's SSD recorded roughly 37 TB of writes in 21 days.
That works out to about 1.7 TB a day. If you stretch one heavy, continuous-use report across a full year, it becomes roughly 640 TB.
The number is not a prediction for every Codex user. It came from one machine running for a long time. But it exposed a problem that is easy to miss with local developer tools:
> a file can stay small while the storage device underneath it gets written to constantly.
This is the story of a noisy TRACE logging sink, a SQLite database doing exactly what it was told, and the SSD wear that nobody could see from file size alone.
The quick version
- A user reported unusually high local disk writes and traced them to Codex CLI's <code>logs_2.sqlite</code> database and its WAL companion file.
- The reported machine accumulated about 37 TB of writes over 21 days of uptime.
- The file stayed bounded because new logs were inserted while older logs were deleted.
- The reported cause was an overly verbose TRACE logging path that did not fully respect the expected <code>RUST_LOG</code> override.
- The supplied investigation says version 0.142.0 reduced the noisy writes substantially, with early testing reporting around 85% fewer log writes.
- If you used an older build heavily, update first, then inspect real write activity and your SSD's lifetime-write counters.
The number that made people look twice
37 TB in 21 days is not a normal “developer tool wrote some logs” story.
| Measurement | Value |
| --- | --- |
| Reported writes | ~37 TB |
| Reporting window | 21 days |
| Approximate daily rate | ~1.7 TB/day |
| Annualized at that rate | ~640 TB/year |
The annualized number needs context. It assumes the same workload every day for a year, which is rarely how people work. Someone running short sessions will produce far less. Someone leaving the tool open, working with long sessions, or using it in automation could produce much more than they expect.
The useful point is not “every SSD will die in a year.”
The useful point is that this level of write activity belongs in the category of things you should notice and control.
<!-- Visual placement: use a clean SMART dashboard screenshot here, with all serial numbers and personally identifiable drive details blurred. -->
Why the file size told you almost nothing
Most of us check disk problems like this:
du -sh ~/.codex
ls -lh ~/.codex/logs_2.sqlite*
Those commands answer one question:
> How much data is stored right now?
They do not answer this:
> How much data has been written over time?
The Codex log database was designed to retain a bounded amount of diagnostic information. New rows were written, old rows were removed, and the database could remain roughly the same size.
So the folder looked calm while the disk was doing constant work.
Imagine a café with four tables. Looking through the window at noon, you see four people sitting down. Come back at 6 PM, still four people. The table count tells you nothing about whether eight customers visited or eight hundred.
The SQLite file was the same. Stable size, enormous turnover.
!Diagram showing how a small SQLite log file can lead to high SSD write activity
What was generating all those writes?
The reported issue points to a logging path that defaulted to TRACE, the most detailed level in Rust's tracing ecosystem.
TRACE logging is useful when you are trying to understand a very specific failure. It can include low-level events that are normally too noisy for everyday use:
- file-open events
- raw network or WebSocket activity
- transport details
- telemetry plumbing
- dependency-library messages that get bridged into the application's tracing system
The supplied breakdown estimates that TRACE-level entries made up 70.7% of the retained log-body size. The highest-volume targets included WebSocket handling, OpenTelemetry-related logs, a generic dependency log bridge, and client transport code.
That is diagnostic exhaust. A little of it is fine. A default stream of it, persisted locally in SQLite, is a different kind of workload.
The three parts that stacked together
No single part of this design has to be bad on its own.
1. TRACE was active where it should have been opt-in
TRACE is not a normal default for a tool used for hours at a time. It is the level you turn on when you are actively debugging.
The reported behavior was that Codex initialized a feedback logging sink at TRACE. That turns every routine interaction into a lot of diagnostic detail.
2. RUST_LOG=warn did not fully quiet it
Rust developers often reach for this first:
RUST_LOG=warn codex
That is supposed to turn down the volume. According to the issue analysis, it did not fully apply to the relevant sink. If a target did not have an explicit override, Codex's internal default still allowed noisy logging.
That is worse than an obvious missing setting. A user can think they reduced logging while the disk keeps receiving the same workload.
3. SQLite turned event noise into storage churn
SQLite is excellent. It is also not magic.
For small structured local data, a SQLite database is a good choice. For a constant stream of fine-grained TRACE events, inserts, deletes, indexes, and checkpoints, it can create a lot of writes even when the database never becomes large.
The problem was not “SQLite is bad.” The problem was using a write-heavy retention loop as a sink for a log level that should not have been permanently enabled.
The part the operating system does not show you
There are two kinds of amplification in this story.
SQLite write amplification
SQLite often uses WAL mode, short for write-ahead logging.
In simplified form:
1. A log event is appended to the WAL file.
2. SQLite later checkpoints that change into the main database.
3. Older rows are deleted to keep retained data bounded.
4. Indexes and database pages are updated along the way.
One logical log event can therefore create more than one write at the filesystem level.
The WAL is not wrong. Checkpoints are not wrong. The problem appears when that loop is fed relentlessly with small, noisy events.
SSD write amplification
An SSD has its own internal life.
It writes data in small pages but erases much larger blocks. When a small part of a block changes, the SSD controller often writes the new data to a fresh location, marks old data stale, later moves still-valid pages, and erases the old block.
That movement is called garbage collection. Wear leveling moves writes around so one physical area does not wear out first.
Both are useful. Both mean the flash may receive more physical writes than the operating system asked for.
The ratio is called the write amplification factor. The exact number varies by drive, spare capacity, free space, workload, and controller. The supplied analysis does not measure the multiplier for the affected drive, so do not treat any guessed physical-wear number as a precise fact.
The direction is enough:
> High-churn, small random writes are one of the workloads SSDs work hardest to manage.
Is 640 TB actually dangerous?
It can be.
SSDs have an endurance rating usually expressed as TBW, or terabytes written. A typical 1 TB consumer SSD may be rated for a few hundred TBW. Enterprise drives may be rated much higher. The exact number depends on the model, capacity, NAND type, and warranty.
That does not mean a drive fails the moment it reaches its rated TBW. Ratings are warranty and endurance guidance, not a timer that hits zero at midnight. Plenty of drives outlive them.
But a background development tool potentially consuming hundreds of terabytes of writes is still worth taking seriously. It can eat through a meaningful part of the drive's expected write budget without filling the disk or showing a visible failure.
How to check your own machine
The order matters.
Update first. Then look at real I/O. Then check what the drive itself says.
Step 1: update Codex
The investigation supplied with this article identifies 0.142.0 as the version containing the logging reduction. Check the current release notes and your installed build, then update before you start measuring.
Also remember that Codex CLI, the desktop app, and editor integrations can ship or bundle components on different schedules. Updating one does not automatically prove every local Codex-related process is updated.
Step 2: find the log files
On macOS and Linux:
ls -la ~/.codex/logs_2.sqlite*
On Windows PowerShell:
Get-Item "$env:USERPROFILE\.codex\logs_2.sqlite*"
You might see the database, a <code>-wal</code> file, and a <code>-shm</code> file.
Do not stop after checking their size. A small database is the whole reason this incident was easy to miss.
Step 3: watch actual writes
On Linux, <code>pidstat</code> can show which process is writing:
pidstat -d 5
To see whether a process has the database open:
sudo lsof ~/.codex/logs_2.sqlite
On macOS, open Activity Monitor, select the Disk tab, and watch bytes written for Codex-related processes while you work.
On Windows, use Resource Monitor or Task Manager's disk column. Look at sustained per-process writes, not just a brief spike during startup.
Step 4: check the drive's own lifetime write counter
This is the closest thing to the truth you have.
On Linux or macOS with smartmontools installed, an NVMe drive can often be checked with:
sudo smartctl -a /dev/nvme0
Look for:
- Data Units Written
- Percentage Used
- warnings or critical health indicators
On Windows, CrystalDiskInfo exposes Total Host Writes and health information. PowerShell can also show some device health data:
Get-PhysicalDisk | Get-StorageReliabilityCounter |
Select-Object DeviceId, Wear, Temperature
Compare lifetime writes with the TBW rating for your exact drive model. Do not compare against a random SSD number from a search result.
<!-- Visual placement: add a small annotated image of a SMART page here. Highlight only Data Units Written or Total Host Writes, Percentage Used, and health status. -->
What if I used an older version for months?
First, do not panic and do not guess from file size.
Check the drive counters. If the drive has low percentage used and reasonable total writes for its age and workload, update and move on. If the drive has unusually high wear, warnings, or a large write count compared with its TBW rating, back up your important data and plan for a replacement before failure forces the schedule.
This is a good backup reminder even if your drive looks healthy. SSDs usually fail without much theatre.
Workarounds if you cannot update immediately
Updating is the right fix. These are temporary ways to reduce persistent write pressure while you arrange that update.
Put throwaway logs on ephemeral storage
For CI jobs and containers, the cleanest option is usually to run with a temporary home directory or mount the relevant config directory on ephemeral storage.
Example for a disposable CI environment:
export HOME="$(mktemp -d)"
For a container workload, mount a tmpfs at the appropriate Codex state directory if the environment can tolerate losing local diagnostics when the job exits.
This is especially useful for build runners that create and destroy their environment anyway.
Be careful with /tmp
Moving a log file to <code>/tmp</code> is only useful if <code>/tmp</code> is actually memory-backed in your environment. On many Linux systems it may be tmpfs. On macOS, <code>/tmp</code> is not automatically a RAM disk by default.
Check the mount before assuming the write load vanished:
mount | grep ' /tmp '
Then verify with a real I/O monitor. Never trust a workaround because it sounded plausible in a comment thread.
Avoid unsupported database surgery unless you really need it
Some community workarounds modify the SQLite log database so inserts are ignored. That can be useful in an emergency, but it is an unsupported change to an internal application file.
Future versions may change the schema or assume the database behaves normally. If you choose that route, stop Codex first, make a copy of the database, document what you changed, and remove the workaround once you are on a fixed release.
The bigger lesson for tool builders
This bug is bigger than one CLI.
Measure bytes written, not just disk space used
Every local persistence feature should have an I/O budget under realistic usage. Test bytes written per hour, per session, and per day. File size is not a sufficient metric for databases, caches, indexes, or log rotation systems.
TRACE must be an intentional decision
Verbose logging needs an explicit opt-in and a clear way back out. Shipping it by default turns every user into a debugging environment.
Standard controls have to work end to end
If your ecosystem has a standard logging override, honour it for every sink. A partial override is dangerous because it gives experienced users false confidence.
Structured logs have storage costs
SQLite, RocksDB, local search indexes, and caches are all good tools. High-frequency writes have a cost. The design needs backpressure, batching, rate limits, retention rules, and test coverage for pathological verbosity.
Privacy and wear are connected
The more raw operational detail a tool records, the more it writes and the more sensitive local data it can retain. Logging file paths, raw transport frames, and low-level event payloads should be a deliberate debugging choice, not background noise.
The bigger lesson for users
There is a simple habit worth keeping:
> When you install a new long-running developer tool, look at its write rate once.
Two minutes in Activity Monitor, Resource Monitor, or <code>pidstat</code> can catch a surprising amount.
Check lifetime SSD writes occasionally. Keep your tools updated. Treat a stable file size as a storage number, not an I/O number.
The log file was small. The work it made the SSD do was not.
What is confirmed and what is still uncertain
The supplied investigation is clear about its limits, and those limits matter:
- The 37 TB measurement came from one heavy user's machine. It is a data point, not a universal rate.
- Reports disagree about when the highest logging verbosity began. The exact start date is uncertain.
- The annualized 640 TB figure is arithmetic based on the reported 21-day sample, not a forecast.
- SSD write amplification is real, but the exact physical NAND multiplier was not measured for this incident.
- The reported 85% reduction after version 0.142.0 is an early observed result, not a guarantee for every platform or workload.
That uncertainty does not weaken the main finding. The correct response is still simple: update, measure the process, and inspect the drive counter.
Sources and further reading
- GitHub issue #28224 in openai/codex
- Official OpenAI Codex documentation
- The public reporting and technical analysis supplied for this article, including Notebookcheck, Windows Report, Lilting.ch, yage.ai, and Daniel Vaughan's write-up
11 reactions · 9 comments
A proof of Ryser's circulant Hadamard conjecture
We show that an $n\times n$ circulant Hadamard matrix must satisfy a family of congruence equations that have solutions only when $n \leq 4$, proving Ryser's 1963 conjecture that no such matrices exist for $n>4$.
Comments: Error found by AI. Corrected proof may be found here: github.com/openai/math/tree/main/preprints/The-ci…
Four GPUs, One Quantization Ladder: Gemma 4 E2B From a 4 GB Laptop to an NVIDIA L4
---
title: "Four GPUs, One Quantization Ladder: Gemma 4 E2B From a 4 GB Laptop to an NVIDIA L4"
published: true
description: "Gemma 4 E2B from BF16 to Q2_K on a Tesla T4, an NVIDIA L4, a GTX 1650 laptop and an RTX 3050 desktop, with byte-identical GGUF files and the same llama.cpp build. The ladder keeps its shape on all four cards, 218 questions die on all four, and any two GPUs grade 2 to 4 questions differently at BF16."
tags: gemma, llamacpp, quantization, nvidia
cover_image: raw.githubusercontent.com
---
This article provides a step by step guide to running Gemma 4 E2B's quantization ladder, BF16 down to Q2_K, on four NVIDIA GPUs with the same nine GGUF files and the same llama.cpp build, and comparing the four cards question by question.
xbill9/gemma-graveyard
The first article asked every rung of the ladder the same 250 GSM8K questions on a Tesla T4. The second repeated it on an NVIDIA L4 in AWS. This one adds two consumer cards: a GTX 1650 Ti laptop GPU with 4 GB and an RTX 3050 desktop card with 8 GB. All four run llama.cpp b11699 on files with the same sha256, and each card gives identical text when BF16 is run twice.
---
The Results in Brief
The ladder keeps its shape on all four GPUs. Against each card's own BF16, F16 to Q5_K_M lose 1 to 8 answers, Q4_K_M and Q4_0 lose 9 to 19, Q3_K_M loses 42 to 49, and Q2_K gets 3 to 6 right out of 250.
The counts agree; the questions move. Any two GPUs grade 2 to 4 questions differently at BF16 and 12 to 22 at Q4_0. No question is lost at F16 or Q8_0 on all four cards, while 216 are lost at Q2_K on all four. 218 questions die at some rung on every GPU, and 147 of them die first at the same rung on every GPU.
The 4 GB laptop runs every quantized rung entirely on its GPU. BF16 and F16 do not fit, so llama.cpp splits them between GPU and CPU, and they decode at 9.2 and 9.6 tokens per second against 45.9 for Q8_0 on the same card.
The cover lays the four GPUs' grades over each other. One row per rung, BF16 at the top and Q2_K at the bottom, one cell per question: marigold where all four GPUs answer right, grey where all four answer wrong, violet where they disagree. docs/make_cover_gpu4.py draws it from the committed rows.
---
What Changes and What Stays
| | Tesla T4 | NVIDIA L4 | GTX 1650 Ti | RTX 3050 |
| :--- | :--- | :--- | :--- | :--- |
| Where | GCE n1-standard-2 | AWS g6.xlarge | laptop | desktop |
| GPU memory | 15,360 MiB | 23,034 MiB | 4,096 MiB | 8,192 MiB |
| Compute capability | 7.5 | 8.9 | 7.5 | 8.6 |
| Host CPU | 2 vCPU | AMD EPYC 7R13 | Intel i7-10750H | Intel i7-5820K |
| Driver | 615.71.10 | 595.112.07 | 615.71.10 | 615.78.08 |
The same on all four: llama.cpp b11699 with GGML_CUDA=ON, built for each card's own compute capability; the nine GGUFs and Google's QAT Q4_0 file, byte for byte; the 250 questions in data/heldout.jsonl; temperature 0, one request at a time, thinking off, 1,024 max tokens, llama-server -c 4096 -np 1 --seed 0. Every rung runs with -ngl 99 except BF16 and F16 on the GTX 1650 (Step 4).
---
At This Point You Should Have…
- An NVIDIA GPU with at least 4 GB and a CUDA toolkit
- About 58 GB of free disk for the checkpoint, the nine GGUFs and the QAT file
- A clone: git clone https://github.com/xbill9/gemma-graveyard
- The T4 run's committed results in runs/2026-10-27-ladder/
---
Step 1 — Build llama.cpp at the Same Tag
On the RTX 3050:
git -C ~/llama.cpp worktree add --detach /android/graveyard/llama.cpp b11699
cd /android/graveyard/llama.cpp
cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=86 -DCMAKE_BUILD_TYPE=Release
cmake --build build -j12 --target llama-server llama-quantize
The one build flag that changes per card is CMAKE_CUDA_ARCHITECTURES: 75 for the T4 and the GTX 1650, 89 for the L4, 86 for the RTX 3050.
nvidia-smi --query-gpu=name,memory.total,compute_cap,driver_version --format=csv
name, memory.total [MiB], compute_cap, driver_version
NVIDIA GeForce RTX 3050 OEM, 8192 MiB, 8.6, 615.78.08
🔎 Tip: keep the pinned build in its own worktree. The desktop already had a newer llama.cpp checkout (b11983). git worktree add --detach <dir> b11699 gives a second working copy at the tag from the same clone, so the measured build and the everyday build sit side by side.
---
Step 2 — Rebuild the Ladder and Check It Against the T4
ladder.sh converts the checkpoint to BF16 and F16 and quantizes seven rungs from BF16. Each run script then compares every file's size and sha256 with the T4's ladder.tsv:
diff <(cut -f1,3,4 runs/2026-10-27-ladder/ladder.tsv) <(cut -f1,3,4 runs/2026-10-29-3050/ladder.tsv) && echo "ladder matches T4 sha256"
ladder matches T4 sha256
The GTX 1650 and the RTX 3050 both print the same line, and Google's QAT Q4_0 file matches too (sha256 fa401b55…). Across the four machines, the GGUF bytes are identical; the card, its driver, its CUDA version and the kernels built for it are what differ.
🔎 Tip: the converter writes the checkpoint directory into the GGUF. convert_hf_to_gguf.py copies the model card and takes the model's name from the directory it reads. A checkpoint taken straight from the Hugging Face cache snapshot has neither the README.md nor the name gemma-4-E2B-it, and gives different bytes. Download with hf download google/gemma-4-E2B-it --local-dir <dir>/gemma-4-E2B-it.
---
Step 3 — Prove Each Card Repeats Itself
BF16 on 50 questions, twice, on every GPU:
./graveyard.py repeat runs/2026-10-29-3050/determinism-1 runs/2026-10-29-3050/determinism-2 BF16
{"rung": "BF16", "n": 50, "mismatched_extracted": 0, "mismatched_text": 0, "mismatched_right": 0, "right_a": 46, "right_b": 46}
The T4, the L4 and the GTX 1650 give the same result: 0 mismatched characters and 46 of 50 right both times. Each card on its own is deterministic, so a difference between two cards comes from the GPU and its CUDA stack.
---
Step 4 — Fit the 16-bit Rungs on 4 GB
BF16 and F16 need a 4,384.87 MiB GPU buffer for their 36 layers (from the T4's load log), and the GTX 1650 has 4,096 MiB. With -ngl 99 the load fails. graveyard.py run --ngl auto leaves -ngl off, and llama.cpp's fit step places what it can:
common_params_fit_impl: - CUDA0 (NVIDIA GeForce GTX 1650 Ti with Max-Q Design): 14 layers, 2546 MiB used, 1125 MiB free
load_tensors: offloaded 14/36 layers to GPU
load_tensors: CUDA0 model buffer size = 2407.92 MiB
Every other rung fits whole. Q8_0 on the same card:
load_tensors: offloaded 36/36 layers to GPU
load_tensors: CUDA0 model buffer size = 2342.30 MiB
The split changes speed and leaves the grades in line with the other GPUs: the GTX 1650's BF16 scores 229, the same as the T4 and the L4, and grades 2 to 4 questions differently from each of them. The 8 GB RTX 3050 holds BF16 whole at 4,651 MiB.
---
Step 5 — Run the Ladder on Each Card and Score It
Each run script runs the nine rungs and QAT Q4_0, then scores every rung against that card's own BF16:
./graveyard.py bury runs/2026-10-29-3050 BF16 F16 Q8_0 Q6_K Q5_K_M Q4_K_M Q4_0 Q3_K_M Q2_K --json graveyard-ladder.json
Right answers, out of 250:
| Rung | T4 | L4 | GTX 1650 | RTX 3050 |
| :--- | :--- | :--- | :--- | :--- |
| BF16 | 229 | 229 | 229 | 232 |
| F16 | 229 | 229 | 228 | 232 |
| Q8_0 | 227 | 228 | 229 | 226 |
| Q6_K | 229 | 227 | 226 | 227 |
| Q5_K_M | 231 | 228 | 226 | 226 |
| Q4_K_M | 224 | 221 | 222 | 221 |
| Q4_0 | 222 | 220 | 216 | 218 |
| Q3_K_M | 191 | 186 | 183 | 191 |
| Q2_K | 3 | 6 | 5 | 3 |
Lost, against the same card's BF16:
| Rung | T4 | L4 | GTX 1650 | RTX 3050 |
| :--- | :--- | :--- | :--- | :--- |
| F16 | 1 | 1 | 2 | 1 |
| Q8_0 | 4 | 3 | 2 | 7 |
| Q6_K | 4 | 6 | 7 | 8 |
| Q5_K_M | 3 | 6 | 6 | 8 |
| Q4_K_M | 9 | 14 | 12 | 14 |
| Q4_0 | 15 | 16 | 19 | 16 |
| Q3_K_M | 42 | 45 | 49 | 44 |
| Q2_K | 226 | 225 | 225 | 229 |
Lost: right at BF16, wrong at this rung, on the same GPU.
The failures fall into the same kinds on every card. Q3_K_M runs out of room: 33 to 36 of its losses on each GPU are answers cut off at 1,024 tokens. Q2_K loops: 208 to 228 of its 250 answers repeat a line five or more times, and 246 to 249 hit the token cap.
---
Step 6 — Line the Four GPUs Up, Question by Question
docs/gpu4_derived.py rebuilds the T4's grid from its committed results and reads the other three from their rows, then compares all six pairs of GPUs at every rung:
python3 docs/gpu4_derived.py > runs/2026-10-29-3050/four-gpus.txt
| Rung | Graded differently, any two GPUs | Lost on all four |
| :--- | :--- | :--- |
| BF16 | 2 to 4 | — |
| F16 | 3 to 5 | 0 |
| Q8_0 | 3 to 9 | 0 |
| Q6_K | 4 to 10 | 2 |
| Q5_K_M | 7 to 10 | 1 |
| Q4_K_M | 9 to 15 | 5 |
| Q4_0 | 12 to 22 | 5 |
| Q3_K_M | 26 to 34 | 20 |
| Q2_K | 6 to 9 | 216 |
Graded differently: right on one GPU and wrong on the other, at that rung, for the closest and the furthest of the six pairs.
- The light rungs lose different questions on each card. F16 and Q8_0 lose 1 to 7 answers per GPU, and none of them is lost on all four.
- The deep losses are shared. Q3_K_M loses 20 questions on all four GPUs and Q2_K 216.
- Most of the graveyard is common ground. Each GPU has 226 questions that die at some rung, the RTX 3050 230. 218 die on all four, and 147 die first at the same rung on all four. One question dies only on the RTX 3050 (test-358) and one only on the GTX 1650 (test-147).
- BF16 itself moves a few answers. 227 questions are right at BF16 on all four cards and 18 wrong on all four. Against the L4, the RTX 3050's BF16 changes the text of 31 answers out of 250, the extracted number of 4 and the grade of 3.
---
Step 7 — Google's QAT Q4_0 on Four Cards
./graveyard.py bury runs/2026-10-29-3050 BF16 QAT_Q4_0 Q4_0 --json graveyard-qat.json
| GPU | QAT Q4_0 right | Q4_0 right | QAT Q4_0 lost | Q4_0 lost |
| :--- | :--- | :--- | :--- | :--- |
| T4 | 221 | 🥇 222 | 15 | 15 |
| L4 | 219 | 🥇 220 | 15 | 16 |
| GTX 1650 | 🥇 222 | 216 | 11 | 19 |
| RTX 3050 | 🥇 221 | 218 | 15 | 16 |
QAT Q4_0 scores 219 to 222 on the four cards and plain Q4_0 216 to 222. On the T4 and the L4 the plain file is ahead by 1; on the GTX 1650 QAT is ahead by 6 and on the RTX 3050 by 3. The two files lose mostly different questions: on the RTX 3050, 5 questions are lost under both, 10 only under QAT and 11 only under the plain Q4_0. Across the four GPUs, 6 of QAT's losses and 5 of Q4_0's are lost on every card.
---
Step 8 — Speed on Each Card
Median decode speed in tokens per second. Each column is one machine; read down a column.
| Rung | T4 | L4 | GTX 1650 | RTX 3050 |
| :--- | :--- | :--- | :--- | :--- |
| BF16 | 40.0 | 50.1 | 9.2 | 40.3 |
| F16 | 39.6 | 50.0 | 9.6 | 40.3 |
| Q8_0 | 64.3 | 83.0 | 45.9 | 67.8 |
| Q6_K | 70.8 | 98.1 | 57.2 | 75.3 |
| Q5_K_M | 78.7 | 107.7 | 61.9 | 82.2 |
| Q4_K_M | 84.1 | 118.7 | 66.5 | 91.5 |
| Q4_0 | 🥇 93.6 | 125.6 | 🥇 70.8 | 🥇 99.7 |
| Q3_K_M | 74.2 | 111.1 | 55.9 | 64.2 |
| Q2_K | 74.0 | 🥇 139.5 | 62.2 | 95.0 |
- Q4_0 is the fastest rung on three of the four cards; on the L4 it is Q2_K, which is wrong on 244 of 250 questions.
- Q5_K_M decodes 1.21 to 1.35 times as fast as Q8_0 on each card (arithmetic, from the table). Q8_0 is the baseline here because it is the largest rung that fits whole on all four GPUs.
- Q3_K_M is slower than Q4_0 on every card, and on the RTX 3050 it is slower than Q8_0.
- BF16 and F16 run at the same speed on the T4, the L4 and the RTX 3050. On the GTX 1650, with 22 of 36 layers on the CPU, they run at a fifth of Q8_0's speed.
The full ladder with QAT took 5 hours 16 minutes on the RTX 3050 and 12 hours 25 minutes on the GTX 1650, from the first log line to the last (run-3050.log, run-1650.log).
---
Compare and Contrast
| | T4 | L4 | GTX 1650 | RTX 3050 |
| :--- | :--- | :--- | :--- | :--- |
| BF16 right of 250 | 229 | 229 | 229 | 🥇 232 |
| Q5_K_M lost | 🥇 3 | 6 | 6 | 8 |
| Q4_0 right, tok/s | 222, 93.6 | 220, 125.6 | 216, 70.8 | 218, 99.7 |
| Every rung whole on the GPU | 🟢 yes | 🟢 yes | ❌ BF16, F16 split | 🟢 yes |
| Same text twice at BF16 | 🟢 yes | 🟢 yes | 🟢 yes | 🟢 yes |
---
So, Which One?
On any of the four cards, take Q5_K_M: 39.0% of BF16's bytes, 226 to 231 right, 3 to 8 answers lost against the card's own BF16, and 1.21 to 1.35 times Q8_0's decode speed. For the fastest file that stays within 19 answers, take Q4_0, the fastest rung on three of the cards. On a 4 GB card, every quantized rung fits whole and the 16-bit rungs run at under 10 tokens per second. To compare two quantizations question by question, run both on one GPU: a different card moves 2 to 4 grades at BF16 and up to 34 at Q3_K_M.
---
Summary
The goal of this article was to run Gemma 4 E2B's quantization ladder on four GPUs, from a 4 GB laptop card to a data-centre L4, and see which grades hold. The key was holding everything else fixed: byte-identical GGUFs, the same llama.cpp tag and the same sampling, with each card checked to give identical text twice. The four-GPU results were:
- 🟢 All nine GGUFs and the QAT file match the T4's sha256 on every machine, and BF16 gives identical text twice on 50 questions on every card.
- 🟢 The ladder keeps its shape on all four GPUs: F16 to Q5_K_M lose 1 to 8, Q4_K_M and Q4_0 lose 9 to 19, Q3_K_M loses 42 to 49.
- 🟢 Q5_K_M is the best trade on every card: 226 to 231 right at 1.21 to 1.35 times Q8_0's speed.
- 🟢 A 4 GB GTX 1650 runs every quantized rung whole on the GPU; BF16 and F16 run split, 14 of 36 layers on the GPU, at 9.2 and 9.6 tokens per second.
- ⚠️ Any two GPUs grade 2 to 4 questions differently at BF16 and 12 to 22 at Q4_0; no question is lost at F16 or Q8_0 on all four.
- ⚠️ 218 questions die on all four GPUs, 147 of them first at the same rung.
- ⚠️ QAT Q4_0 and plain Q4_0 trade places by card: plain ahead by 1 on the T4 and L4, QAT ahead by 6 on the GTX 1650 and by 3 on the RTX 3050.
- ❌ Q3_K_M loses 42 to 49, mostly answers cut off at 1,024 tokens; Q2_K gets 3 to 6 right, with 208 to 228 looping answers per card.
Scope: one run of each rung per GPU on four NVIDIA cards (a Tesla T4 on GCE, an L4 on AWS, a GTX 1650 Ti laptop GPU and an RTX 3050 desktop card), 250 GSM8K test questions, greedy, thinking off, 1,024 max tokens, llama.cpp b11699 built for each card. Determinism checked at BF16 on each card. The T4 grid is rebuilt from its committed per-question results. The hosts also differ in CPU, RAM, driver and CUDA version, and on the GTX 1650 BF16 and F16 run split between GPU and CPU. Speed is compared within each card only.
The strategy for measuring Gemma 4's accuracy one quantization rung at a time on four GPUs was validated with an incremental step by step approach.
---
References
- Accuracy vs Speed: Gemma 4 E2B Quantization Tradeoffs, BF16 to Q2_K, the T4 article
- Performance vs Quality: Gemma 4 E2B Quantization on an NVIDIA L4 and a Tesla T4, the L4 article
- gemma-graveyard on GitHub
- GTX 1650 run: runs/2026-10-29-1650
- RTX 3050 run: runs/2026-10-29-3050
- llama.cpp
- google/gemma-4-E2B-it
- google/gemma-4-E2B-it-qat-q4_0-gguf
- GSM8K
10 reactions · 1 comment
Mentions
GPT-6.1 Sol APIの構造化出力でレビューにタグ付けすると、コストはいくらになるか ! この記事は EveryInfra ブログ からの転載です(原文が正本です)。 私たちの試算では、GPT-6.1 Solを使うと、1,000件のレビューに1ドル未満でタグを付けられます。前提は、strictモードのJSON Schema出力とBatch APIを使い、レビュー1件あたりの推論トークン数を約50とすることです。モデルは実際には実行していないため、この数値は試算です。費用を左右するのはデータではなく、リクエストのまとめ方です。2026-11-06の00:16から00:25(SGT)にかけて、EveryInfraのデータAPIを実際に7回呼び出しました。 ロボット掃除機のアプリ2つ(RoborockとiRobot)のGoogle Playレビューを400件、さらにレビュー動画1本についたTikTokコメントを12件取得しました。支払った金額は合計$0.003334です。各呼び出しで実際に返された件数から計算すると、レビュー1,000件あたりのデータ料金はGoogle Playが$0.006945、TikTokが$0.046704です。 このマシンにはOpenAIのAPIキーがありません。そのため、この記事ではモデルに関しては設定例と試算のみを示します。トークン数は公開されているo200k_baseエンコーディングで数え、料金は2026-11-06に確認した公開価格を使っています。主な結論はシンプルです。 1リクエストにレビューを1件ずつ送ると、システムプロンプトとスキーマだけで967トークンになります。これは入力トークン全体の92%にあたり、プロンプトキャッシュの適用に必要な最小の1,024トークンを下回ります。そのため、同じ指示に対して1,000回分の正規料金を支払うことになります。 次に、1リクエストにレビューを25件ずつ送り、レビュー1件あたりの推論トークンを50と仮定します。すると、1,000件あたりのBatchでのモデルコストは約$1.74から約$0.81に下がります。推論トークンがなければ、下限は$1.49から$0.56まで下がります。ただし、Solではlowより低いeffortを指定できないため、この下限には届きません。 要点 GPT-6.1 Solは、2026-09-29にgpt-6.1-solとしてAPIで提供が始まりました。コンテキストウィンドウは1,050,000トークンです。1Mトークンあたりの標準価格は入力$2、出力$10で、Batchでは$1と$5です。最も低いreasoning effortはlowで、noneはありません。 strictモード用のスキーマを、ドキュメントに記載されたルールに照らしてスクリプトでチェックしました。私たちのタグ用スキーマの問題は0件でした。壊れたテスト用スキーマでは問題が5件見つかり、その中にはallOf、if、thenが含まれています。 7回の実際の呼び出し:google_play/reviewsは1回の呼び出しで200件を返し、料金は$0.001389でした。tiktok/commentsは動画1本について12件を返しました。amazon/reviewsの非同期ジョブ2件は0件で完了し、返金されました。503タイムアウト2回には料金がかかりませんでした。 1リクエストに1件ずつの場合、1,000件のレビューには約1,056,100の入力トークンが必要で、そのうち975,000トークンは繰り返し送られるプロンプトとスキーマです。1リクエストに25件ずつなら、入力は約121,700トークンまで減ります。 「競合に言及している」を判定するキーワードルールは、大まかな一次判定にすぎません。このルールは412件中17件にマッチしました。マッチしたレビューは1件ずつ読みました。13件は実際に競合に言及していました(そのうち1件は、ルールが見逃した2つのブランドにも触れていました)。3件は誤検知で、Samsungのスマートフォン、Samsungのタブレット、スロバキア語の単語でした。1件は判断がつきませんでした。 この記事が答えるのは、1つの限定的な問いです。手元にレビューのセットがあり、GPT-6.1 Solで各レビューに4つのタグ(感情、問題カテゴリ、競合への言及、アクションアイテム1つ)を付けたいとします。各タグはそのままスプレッドシートに入れられる形で出力する必要があり、総コストも把握したいという状況です。レビューはEveryInfraのデータAPIから取得します。 タグ付けにはOpenAIのResponses APIとBatch APIを使います。以下に出てくるEveryInfraの数値はすべて生のレスポンスから取ったもので、再計算できます。OpenAI側の手順にはすべて、設定例か価格の試算であることを明記しています。このバッチはOpenAI上では実行していません。 次の表は、実測した項目と試算した項目をまとめたものです。以下の数値はすべて、この表のいずれかの行に対応します。 ステップ 取得方法 再計算できるか レビューデータ(EveryInfra) 2026-11-06に実際に7回呼び出し、生のレスポンスを保存 可能。すべての数値がレスポンスに基づく トークン数 実際のシステムプロンプト、スキーマ、レビュー412件をo200k_baseでカウント 可能。ただしSolのトークナイザーは非公開 出力タグ 人が書いたタグの例12件(GPT-6.1 Solの出力ではない) 出力長の推定にのみ使用 推論トークン 未計測。レビュー1件あたり0、50、200を想定 不可。パイロット実行とそのusageが必要 モデルのコスト 上記の各行の数値に2026-11-06時点の公開価格を乗算 可能。自分の数値で計算できる タグの精度 なし パイロット実行後に手作業で確認 GPT-6.1 Sol APIとは:リリース日、コンテキスト、価格 まず、モデルに関する事実を確認します。OpenAIのAPI changelogには、2026-09-29付けのエントリがあります。それによると、OpenAIは複雑なコーディングや専門業務向けにGPT-6.1 Sol(gpt-6.1-sol)をリリースしました。価格はGPT-6 Astraより低く設定されています。 272Kトークンまでの入力では、1Mトークンあたりの標準価格は入力$2、キャッシュ入力$0.10、キャッシュ書き込み$2.50、出力$10です。同じエントリには、ツール呼び出しにはResponses APIが必要だとも書かれています。リリース記事の「Introducing GPT-6.1 Sol」によると、Solの性能はGPT-6 Astraに近いとされています。また、標準の入力・出力価格はAstraの5分の1だとしています。 2026-11-06に確認したGPT-6.1 Solのモデルページには、タグ付けに関係する仕様が載っています。コンテキストウィンドウは1,050,000トークン、最大出力は128,000トークン、知識のカットオフは2026-04-30です。reasoning.effortの値はlow、medium(デフォルト)、high、xhigh、maxです。機能一覧では、Structured outputsとFunction callingが「Supported」となっています。 モデルページには、バッチ処理に関わる料金ルールがほかに4つあります。キャッシュされた入力は入力料金の5%です。キャッシュ書き込みは1.25倍です。入力トークンが272Kを超えると、そのリクエストでは入力料金が2倍、出力料金が1.5倍になります。BatchとFlexは50%安くなります。 項目 情報源の記載内容 出典(2026-11-06閲覧) タグ付けへの影響 APIリリース gpt-6.1-solは2026-09-29にリリース API変更履歴 執筆時点でリリースから38日 コンテキストと出力 コンテキスト1,050,000、最大出力128,000 モデルページ 数十件のレビューが1リクエストに余裕をもって収まる 推論エフォート low、medium、high、xhigh、max(noneなし) モデルページ、GPT-6ガイド すべてのリクエストで推論トークンが発生し、出力として課金 標準料金 1Mあたり入力$2、キャッシュ$0.10、出力$10 料金ページ、変更履歴 同期処理で最低でも1,000件あたり約$2.99 Batch料金 1Mあたり入力$1、キャッシュ$0.05、出力$5 料金ページ 同じリクエストが半額 長い入力の割増料金 入力272K超:入力2倍、出力1.5倍 モデルページ 1リクエストにレビューを詰め込みすぎない Chat Completions 対応(ツール呼び出しは非対応) モデルページ、GPT-6ガイド 本記事ではResponses APIのみを使用 最も見落とされやすいのは推論エフォートの行です。GPT-6ガイドにははっきり書かれています。GPT-6 AstraとGPT-6.1 Solはnoneに対応していません。既存のリクエストでminimalを使っている場合は、lowに変更して結果を比較してください。GPT-6.1 Terra、GPT-6 Sol、GPT-6 Lunaはnoneに対応しています。推論ガイドによると、推論トークンは出力トークンとしてカウントされます。 これはタグ付けに影響します。レビューごとに見えない出力が発生し、料金は1Mトークンあたり$10、Batchでは$5です。そのトークン数は、実際に呼び出してみなければ測定できません。そのため、コスト表では推論を別の前提として示しています。 Batchの行を詳しく見ます。料金ページでは、FlexとBatchが同じ価格帯に置かれています。gpt-6.1-solのBatchは、1Mトークンあたり入力$1、出力$5、キャッシュ入力$0.05です。30分のキャッシュ書き込みは$1.25、12時間のキャッシュ書き込みは$2.50です。以下のコスト表はすべてこの価格帯で計算しています。 gpt 6.1のstructured outputs:json_schemaのstrictモードにはどんな制限があるのか OpenAI Structured Outputsガイドは、冒頭でその保証を明記しています。モデルの出力は、指定したJSON Schemaに常に一致します。必須のキーが欠けることも、enumにない値を作り出すこともありません。Responses APIでは、text.formatを{"type":"json_schema","strict":true,"schema":…}に設定します。ガイドには、strictモードがサポートするのはJSON Schemaの一部だけだとも書かれています。その範囲外のキーワードをスキーマで使うと、strict: trueのリクエストは失敗します。 タグ付け用のスキーマは、一度確定すると何千回も実行されます。そこで、ガイドのルールのうち、数えたり確認したりできるものをすべて書き写しました。そのうえで、スキーマがそれらのルールに沿っているかを確認する小さなスクリプトを書きました。記憶には頼っていません。 スクリーンショット:OpenAI Structured Outputsガイド、2026-11-06(SGT)に取得。ティール色の枠は当社が追加しました。 OpenAI開発者ドキュメント「Structured model outputs」 ティール色の枠内の4項目はサイズの制限です。枠の外にも、スキーマの書き方に関する厳格なルールがガイドに書かれています。 ルートはオブジェクトでなければなりません。anyOfにはできません。 すべてのオブジェクトのすべてのフィールドをrequiredに含める必要があります。フィールドを任意にするには、["string","null"]のような型を指定します。 ガイドによると、allOf、not、dependentRequired、dependentSchemas、if、then、elseはサポートされていません。 文字列について、ガイドに記載されているのはpatternとformatだけです。formatキーワードに指定できる値は、date-time、time、date、duration、email、hostname、ipv4、ipv6、uuidの9つです。 配列はminItemsとmaxItemsに対応しています。$defsと再帰参照もサポートされています。 ガイドには、新しいスキーマを使う最初のリクエストでは追加の処理レイテンシが発生するとも書かれています。同じスキーマを使う以降のリクエストでは発生しません。 ルール(ガイド内のセクション) 上限または要件 当社のタグスキーマ 不正なテスト用スキーマ オブジェクトプロパティの総数(Objects have limitations…) ≤ 5000 9 8 ネストの深さ ≤ 10 1階層 1階層 enum値の総数(Limitations on enum size) ≤ 1000 31 31 名前と列挙値の文字数(文字列の合計サイズの制限) ≤ 120,000 416 408 additionalProperties: false すべてのオブジェクトで必須 可能 ルートでtrueに設定、検出 全フィールドをrequiredに記載 必須 9件中9件 requiredに存在しないフィールドを記載 非対応のキーワード allOf、if、thenなど 0 3 サポート一覧にないキーワード 警告のみ 0 minLength、1 スクリプトの結果は、単一レビュー用スキーマと25件用スキーマでは問題0件、警告0件でした。不正なスキーマでは問題5件、警告1件でした。この不正なスキーマは適当に作ったものではありません。「mentions_competitorがtrueなら、competitorsには1件以上の項目が必要」という自然な発想から生まれたものです。JSON Schemaでは、このルールをallOfとif/thenで書きます。 strictモードはこれをサポートしていません。スキーマでは、2つのフィールド間のこうしたルールを強制できません。結果が返ってきた後にコードで確認する必要があります。方法は後述の「スキーマで強制できないこと」で説明します。 minLengthについて補足します。ガイドでminLengthとmaxLengthが非対応とされているのはファインチューニング済みモデルだけですが、サポートされる文字列キーワードの一覧にはpatternとformatしかありません。そのため、スクリプトは警告を出し、当社のスキーマでは使っていません。アクションの20語制限はdescriptionとプロンプトに書き、コードで計測しています。 単一レビュー用のタグスキーマは次のとおりです。フィールドは9つで、すべて必須です。other_brand_nameとaction_itemはnullを取れます。 { "name": "review_label", "strict": true, "schema": { "type": "object", "properties": { "item_id": {"type": "string", "description": "Copy the item_id of the input item."}, "sentiment": {"type": "string", "enum": ["positive", "negative", "mixed", "neutral"]}, "issue_categories": {"type": "array", "minItems": 1, "maxItems": 3, "items": {"type": "string", "enum": ["app_connectivity", "account_login", "mapping_navigation", "suction_cleaning", "mopping", "dock_self_empty", "battery_charging", "hardware_defect", "firmware_update", "customer_service_warranty", "price_value", "noise", "app_usability", "other", "none"]}}, "mentions_competitor": {"type": "boolean"}, "competitors": {"type": "array", "maxItems": 5, "items": {"type": "string", "enum": ["roborock", "irobot_roomba", "ecovacs_deebot", "dreame", "eufy", "shark", "narwal", "mova", "dyson", "samsung", "xiaomi", "other_brand"]}}, "other_brand_name": {"type": ["string", "null"]}, "action_item": {"type": ["string", "null"], "description": "One concrete action, 20 words or fewer, or null."}, "evidence": {"type": "string", "description": "Up to 15 words copied from the text."}, "language": {"type": "string", "description": "ISO 639-1 code."} }, "required": ["item_id", "sentiment", "issue_categories", "mentions_competitor", "competitors", "other_brand_name", "action_item", "evidence", "language"], "additionalProperties": false } } 25件版では、外側に1層追加します。ルートオブジェクトのフィールドはlabels配列の1つだけです。配列の各項目は、$refで$defs内の同じタグオブジェクトを参照します。ガイドによると、$defsと$refはサポートされています。ネストの深さは1階層から2階層になり、上限の10階層を大きく下回ります。 フィールドの順序も重要です。ガイドによると、出力はスキーマのキー順に従います。そこでitem_idを先頭に置きました。action_itemとevidenceは、モデルがレビュー全体を読まないと書けないため、末尾近くに置いています。出力が途中で切れても、先頭にあるIDと感情は残ります。 タグの設計方法:4つの問いと9つのフィールド タグを設計する前に、誰が使うのかを決めます。ロボット掃除機ブランドのアプリチームが気にするのは、Wi-Fiの切断、ログインの失敗、マップの消失です。サポートチームは保証に関する苦情を探します。プロダクトチームは、ユーザーがどの他社ブランドと比較しているかを知りたがります。 そこで、4つの問いからタグを作りました。トーンはどうか。どんな種類の問題か。他社ブランドの名前が出ているか。 1つの具体的な改善策につなげられるか。問題カテゴリは15個の値からなる固定のenumです。競合は12個の値からなるenumで、それ以外を扱うためにother_brandを用意しています。 最も時間がかかったのは問題カテゴリです。まず、実際の呼び出しで取得した412件のレビューを読みました。次に、実際の苦情を13のグループに分類しました。最後にotherとnoneを追加しました。たとえば、iRobotの低評価レビューの多くには「コマンドが通らない」や「iRobotクラウドに接続できない」と書かれています。 これらはapp_connectivityに分類します。「認証コードが届かない」や「アカウント連携に失敗した」はaccount_loginです。Roborockのレビューの多くには「アップデート後にマップが壊れた」とあります。これはfirmware_updateとmapping_navigationの両方に当てはまります。そのため、1件のレビューに最大3つのカテゴリを付けられます。 システムプロンプトには、カテゴリごとに1行のガイダンスがあります。この部分がプロンプトの半分以上を占めます。後述のトークン数で固定プレフィックスが645トークンになる主な理由もここにあります。 スキーマはすべての用紙を同じ形にします。結果が返ってきた後の確認で、互いに矛盾するフィールドを見つけます。 感情はpositive、negative、mixed、neutralの4値です。システムプロンプトには「星の評価だけで感情を判断しないこと」と書かれています。星の数と本文が食い違うことはよくあります。Roborockのレビューには、「アプリはとても良い」で始まる星1のレビューがあります。マップの問題に触れるのはその後です。前半でアプリを褒めている星3のレビューもあります。 そのレビューは後半で、Samsungの折りたたみスマートフォンでは下部のボタンが動かないと書いています。システムプロンプトでは、こうしたレビューをmixedとします。TikTokのコメントには星がありません。「どれが一番いい?」のような質問はneutralになります。 action_itemは最も検証しにくいタグなので、制限を最も厳しくしています。20語以内の英語1文、またはnullです。evidenceには、レビューから最大15語を原文の言語のまま引用する必要があります。この2つのフィールドがあれば、レビュー全体を読まなくても数秒でタグを確認できます。 このスキーマを使い、実際のレビュー12件について出力例を手作業で書きました。これを出力トークンの見積もりに使います。各フィールドがどのような形になるかもわかります。表にはそのうち4件を示します。 レビュー本文は一部を省略し、投稿者の情報は削除しています。この12件の例は人が書いたもので、GPT-6.1 Solの出力ではありません。 出典 スター数 レビューの抜粋 感情 問題カテゴリ 競合 Roborockアプリ 2 「You ruined my robot after your last update! The map is all messed up…」 negative firmware_update, mapping_navigation none Roborockアプリ 3 「…it doesn't support the Samsung Z Fold 8's screen aspect ratio…」 mixed app_usability none(ここでのSamsungはスマートフォン) iRobotアプリ 1 「every 2nd vacuum I have a connection issue. I will never buy another Roomba」 negative app_connectivity none TikTokのコメント n/a 「How about the Dreame X60 Pro Ultra?…」 neutral none dreame EveryInfraでレビューを取得する:実際に1回リクエストしてレスポンスを受け取るまで レビューの取得には、EveryInfraのデータエンドポイントPOST /api/v1/socialを使いました。リクエストボディでプラットフォーム、アクション、パラメータを指定します。最初の呼び出しの前に、2026-11-06 00:15(SGT)に無料の機能カタログを読み、各レビュー機能の料金、パラメータ、レスポンスフィールドを確認しました。 google_play/reviewsは1回の呼び出しあたり$0.001389です。同期呼び出しで最大200件を返します。languageとsortを指定できます。 tiktok/commentsは1回の呼び出しあたり$0.000556です。動画URLを指定します。 amazon/reviewsは1回の呼び出しあたり$0.001389です。カタログによると、Amazonの各サイトの商品ページに表示されるのは注目のレビューのみで、通常は1商品あたり十数件です。 trustpilot/reviewsは1回の呼び出しあたり$0.001389です。 Google Playへのリクエスト全体は次のとおりです。 curl -s api.everyinfra.com \ -H "Authorization: Bearer $EVERYINFRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"platform":"google_play","action":"reviews", "params":{"app_id":"com.roborock.smart","language":"en","sort":"newest","limit":200}}' このリクエストは2026-11-06 00:20:08(SGT)に送信し、9.45秒でHTTP 200が返りました。countは200、pagination.stop_reasonはdepth_limit、has_moreはfalseでした。課金額は$0.001389です。 各レビューにはid、text、score、thumbs_up、reply_text、reply_at、app_version、posted_atなどのフィールドがあり、カタログのresponse_fieldsリストと一致します。レスポンスには投稿者名とアバターのフィールドも含まれますが、タグ付けには不要なため、モデルへのリクエストを作成する前に削除しました。 スクリーンショット:2026-11-06 00:20:08(SGT)の実際のgoogle_play/reviewsレスポンス。レビュー本文は省略し、投稿者フィールドは削除しています。ティール色のボックスは編集部で追加しました。 EveryInfraの実際のAPIレスポンス 呼び出しは00:16:42から00:24:27(SGT)までに合計7回行いました。各呼び出しを下の表にまとめています。同期のtiktok/comments呼び出しは90.21秒かかり、HTTP 200で12件を返しました。limit 200で行ったamazon/reviewsとtrustpilot/reviewsへの同期呼び出し2回は85秒を超え、503タイムアウトとなり、課金はされませんでした。エラーメッセージには、"mode":"async"を使うかlimitを小さくするよう書かれていました。Roborockの2製品に対する非同期のamazon/reviewsジョブ2件は成功したものの0件で、課金は返金されました。 時刻(SGT) 機能 結果 クライアント側の計測時間 件数 課金額 00:16:42 amazon/reviews(同期) 503タイムアウト 89.91 s 0 $0 00:18:21 → 00:19:57 amazon/reviews(非同期) 成功、0件、返金済み 送信2.08秒、約96秒で完了 0 $0(課金後に返金) 00:18:26 tiktok/comments(同期) 200 90.21 s 12 $0.000556 00:20:08 google_play/reviews Roborock 200 9.45 s 200 $0.001389 00:20:25 google_play/reviews iRobot 200 9.24 s 200 $0.001389 00:20:34 → 00:24:18 amazon/reviews(非同期) 成功、0件、返金済み 送信2.04秒、約224秒で完了 0 $0(課金後に返金) 00:24:27 trustpilot/reviews(同期) 503タイムアウト 88.2 s 0 $0 所要時間について1点補足します。tiktok/commentsの呼び出しは90.21秒かかり、2回の503タイムアウト(89.91秒と88.2秒)より長かったものの、200で12件を返しました。2つの503エラーメッセージにはどちらも「request timed out after 85s」とあります。つまり、85秒はサーバー側の待機上限です。こちらの計測時間はリクエストの送信時に始まり、レスポンス全体の受信時に終わります。そのため、ネットワーク時間とキュー待ち時間を含みます。 レスポンスにはサーバー側の処理時間が含まれないため、TikTokの呼び出しがサーバー上でどれだけかかったかはわかりません。言えるのは、上限に近かったということだけです。同じリクエストでも、時間帯によっては503が返る可能性があります。TikTokのコメントのように遅い機能をまとめて取得する場合は、非同期モードを使ってください。 この表自体が1つの発見です。7回の呼び出しのうち、データを返したのは3回だけでした。しかし、データのなかった4回は費用がかかっていません。2つの503レスポンスでは、ボディにcharged_usd: 0と表示されています。空だった2件の非同期ジョブは送信時に呼び出し料金で課金され、ジョブ終了時に返金されました。 一括タグ付けジョブでは、データ取得に失敗しても失うのは時間だけで、費用はかかりません。本当のリスクは別にあります。「0件」を「ネガティブなレビューなし」としてレポートに書かないでください。空だった2件のAmazonジョブは深追いしませんでした。理由は「制限と注意点」のセクションで説明します。 412件のレビューはどんな内容か モデルに送る前に、まずデータを確認しましょう。各アプリから最新の英語レビューを200件ずつ取得しました。件数は同じでも、期間は異なります。Roborockの200件は2026-10-03から2026-11-04までの32日間です。 iRobotの200件は2026-07-27から2026-11-04までの99日間です。同じ$0.001389で、Roborockの呼び出しは直近1か月、iRobotの呼び出しは3か月以上をカバーします。「最新200件」から週次レポートを作ると、iRobot側では7月の問題が今週分に入り込みます。 星評価の差はさらに大きくなります。iRobotでは星1が100件、星2が26件で、星1〜2は合計126件、全体の63%です。Roborockでは46件と22件で合計68件、34%です。 開発者の返信にも差があります。iRobotでは200件中100件にreply_textがありますが、Roborockでは49件です。 これらの数値はこの200件だけを表すもので、どちらの製品の全体的な評判を示すものでもありません。タグ付けの結果に影響するため掲載しています。iRobotのデータの大半はnegativeとapp_connectivityに分類されるでしょう。 トークン数を左右するのはレビューの長さです。Google Playレビューの長さの中央値は、Roborockが94.5文字、iRobotが128.5文字です。どちらも最長のレビューはちょうど500文字で、これはGoogle Playのレビューの文字数上限です。 Google Playヘルプコミュニティでも、あるユーザーがこの500文字の上限について質問しています。20文字未満のレビューは、Roborock側で35件、iRobot側で24件あります。例としては「Great robovac great」や「iRobot is awful」があります。 TikTokのコメント12件は文字数の中央値が51.5文字で、大半が質問です。期間は2026-06-13から2026-11-01までで、ほぼ5か月に及びます。1本の動画のコメントは日々増えていくわけではありません。動画の話題性が落ちると、新しいコメントを書く人はほとんどいなくなります。 1回の呼び出しで12件しか返らなかったのはこのためです。短いレビューについてのポイントは単純です。レビューは数十トークンしかないこともあります。1件ごとに数百トークンの指示を付けると、費用の大半は指示の分になります。 もう1つ小さな発見があります。language: "en"を指定しましたが、Roborockのデータには中国語とスロバキア語のレビューも含まれていました。カタログによると、languageは検索に使う言語で、各レビューの言語を保証するものではありません。そのため、スキーマにlanguageフィールドを設け、evidenceは原文の言語のままにしています。 「競合に言及」はキーワードルールでどこまで判定できるか 4つのタグのうち、「競合に言及」はキーワードルールに最も向いているように見えます。ルールならモデルの料金はかかりません。そこで、ロボット掃除機と家電の29ブランド名を含む正規表現を使い、412件のレビューで試しました。 アプリ自身のブランドは除外しました。ルールに一致したのは17件です。その17件をすべて読み、それぞれが本当に別のロボット掃除機ブランドに言及しているかを判断しました。 17件中13件は正しい一致です。Roborockアプリでは、5件のレビューがiRobot、Roomba、Shark、Bissellと比較しています。そのうち4件は星5で、「a great upgrade from the iRobot」などです。残る1件は星1で、5年前のRoombaのほうが良かったと述べています。 iRobotアプリでは、5件のレビューが代わりにRoborock、Shark、Rowenta、Ecovacsを買うよう勧めています。5件とも星1か星2です。TikTokで一致した3件は、Dreame、Roborock、Movaの機種について質問しています。 誤検出は3件です。1件はSamsung Z Fold 8の画面比率について、1件はアプリがSamsungのタブレットにインストールできないという内容です。最後の1件はスロバキア語の「mi」(「私」の意味)で、ルールがXiaomiと誤認しました。 残る1件は判断が分かれます。このレビューは、アプリの新しい「Explore」ページをクリックベイトだとし、「typical xiaomi」なマーケティングだと評しています。ブランド名は出てきますが、ロボット掃除機を比較しているわけではありません。 見逃しも問題です。ある星5のレビューは「Sharks, Bissell's, iRomba's」と書いていますが、ルールが検出したのはBissellだけでした。複数形とタイプミスが一致しなかったためです。よく使われる13ブランドに絞った短いリストでは14件しか見つからず、RowentaとBissellを見逃します。 教訓ははっきりしています。キーワードルールは最初のフィルターとしては使えますが、タグとしては不十分です。誤検出は、同じブランド名のスマートフォンや外国語の単語から生じます。見逃しは、複数形、タイプミス、リストにないブランドから生じます。 そのため、スキーマではcompetitorsのenumとother_brandを使い、この列はモデルに任せています。mentions_competitorのbooleanは、competitorsが空かどうかからそのまま導けます。シートで絞り込みやすくするためだけに残しています。2つのフィールドの整合性を確認する方法は後ほど紹介します。 トークン数の見積もり方:固定プレフィックスが92%を占める OpenAIのキーがないため、モデルの費用は見積もるしかありません。各手順を示すので、ご自身の数値に置き換えて計算できます。OpenAIはGPT-6.1 Solのトークナイザーを公開していません。そこで、OpenAIのオープンソースライブラリtiktoken(PyPIのバージョン0.14.0)に含まれる最新の公開エンコーディングo200k_baseを使います。 システムプロンプト、スキーマ、入力フォーマットは実際のファイルです。レビューはライブ呼び出しで取得した412件です。出力トークンは、手書きしたタグの例12件の平均長から算出しています。推論トークンは別途仮定した値です。 実際の課金は、Responses APIの各レスポンスに含まれるusageオブジェクトに従います。本番実行の前に、パイロットとして20件のレビューを送ってください。input_tokens、output_tokens、output_tokens_details.reasoning_tokensを確認し、下の表を更新します。 項目 トークン数(o200k_base) 備考 システムプロンプト(カテゴリガイドとルール) 645 毎回のリクエストで送信 レビュー1件用のタグスキーマ 322 コンパクトなJSONとして計算。実際の課金方法はOpenAIが決定。 レビュー25件用のスキーマ 362 labels配列の階層が1つ増える 固定プレフィックス(1件/25件) 967 / 1,007 どちらもキャッシュ最小値の1,024トークンを下回る 入力としてのレビュー1件(ID、取得元、星評価付き) 平均81.1、中央値73.5、最大205 実際のレビュー412件 レビュー本文のみ 平均32.6 トークンの半分以上がフォーマット行に使われる 出力としてのタグ1件 平均87.3(65〜107) 手書きの例12件 これを1,000件のレビューに掛けると、差ははっきりします。1リクエストに1件の場合、1,000リクエストで約1,056,100入力トークンが必要です。各リクエストには、固定プレフィックスの967トークンと、メッセージフォーマット分の8トークンが含まれます。その合計は975,000トークンで、入力の92.3%を占めます。1リクエストに25件なら、必要なリクエストは40回だけです。 固定プレフィックスは40,600トークンに、入力の合計は約121,700トークンに減ります。88.5%の削減です。レビュー本文の81,100トークンは、どちらの構成でも同じです。削減分はすべて、同じ指示を何度も送らずに済むことによるものです。 プレフィックスが繰り返されるならキャッシュが効くはずだ、と考えるかもしれません。プロンプトキャッシュのガイドによると、GPT-5.6以降では、キャッシュ可能なプレフィックスの最小値は表示される入力トークンで1,024です。OpenAIが追加する非表示のシステムコンテンツはカウントされません。GPT-6.1 Solでは、キャッシュ読み取りは通常の入力料金の0.05倍、30分のキャッシュ書き込みは1.25倍、12時間の書き込みは2.5倍です。 今回のプレフィックスはo200k_baseで967トークンで、最小値に57トークン足りません。OpenAIがスキーマを非表示のシステムコンテンツに変換する場合、表示される部分は645トークンしかなく、最小値からさらに遠ざかります。いずれにしても、このプレフィックスはキャッシュされません。つまり、このプロンプトの長さでは「1リクエストに1件+キャッシュ」は成り立ちません。 プロンプトがすでに1,024トークンを超えているなら、キャッシュは効きます。プレフィックス全体がキャッシュ料金で読み取られると仮定した場合、Batchで1リクエストに1件だと1,000件あたり約$0.57です。これは1リクエストに25件の場合の$0.56に近い数字ですが、初回のキャッシュ書き込みは含んでいません。一方、1リクエストに25件の方式はキャッシュヒットに依存せず、プロンプトを水増しする必要もありません。そこで、Batchファイルは1リクエストに25件で作成します。 Batch APIの手順:JSONLの作成からアップロード、ダウンロードまで Batch APIのガイドの説明は明快です。各リクエストを.jsonlファイルの1行として書きます。各行には、一意のcustom_id、method、url、bodyを含めます。bodyの内容は同期エンドポイントと同じです。 1つのファイルで使えるモデルは1つだけです。ファイルはpurposeをbatchにしてアップロードします。バッチ作成時のcompletion_windowは、現時点では24hしか指定できません。料金は同期エンドポイントより50%安く、Batchには独自の、より高いレート制限があります。 ガイドには、タグ付けジョブで直面する3つの制限も記載されています。1つのバッチに含められるのは最大50,000リクエストで、入力ファイルは最大200 MBです。バッチは1時間あたり最大2,000件まで作成できます。出力ファイルは入力の順序を保たないため、結果はcustom_idで照合する必要があります。 ファイルサイズを確認するため、実際の412件のレビューから2つのBatchファイルを作成しました。1リクエストに1件のレビューを入れたファイルは412行、2,018,830バイトです。各行にシステムプロンプトとスキーマ全体が含まれるため、1行あたり約4,900バイトになります。25件ずつのファイルは17行、191,412バイトで、1行あたり約11,260バイトです。 200MBの上限から計算すると、1リクエストに1件のファイルに入るレビューは約42,798件です。50,000行の上限より先にサイズの上限に達します。25件ずつのファイルなら、サイズでは約451,000件、行数では1,250,000件まで入ります。1,000件のレビューなら、どの上限にも遠く及びません。ただし数万件規模になると、1リクエスト1件の構成は想定より早く200MBに達します。 Batchを使わずに同期エンドポイントを使うと、大規模な実行では1分あたりのトークン数の上限に達することがあります。2026-10-06に、OpenAIは利用ティアをBuild、Launch、Growの3つに整理しました(レート制限ガイド)。Solのモデルページでは、Buildティアの上限は1分あたり1,000,000トークンです。Batchには同期処理の枠とは別に、より高い上限があります。大規模に実行する前に、ご自身のアカウントの制限ページを確認してください。 以下は25件ずつのファイルの1行目で、改行を加え、プロンプトとスキーマは省略しています。続いて、アップロードとバッチ作成のPythonコードを示します。これはドキュメントに基づく設定例で、OpenAI上では実行していません。 One JSONL line (cut): {"custom_id": "chunk-000", "method": "POST", "url": "/v1/responses", "body": {"model": "gpt-6.1-sol", "reasoning": {"effort": "low"}, "max_output_tokens": 12000, "store": false, "input": [{"role": "developer", "content": "You label customer reviews ..."}, {"role": "user", "content": "item_id: 56a3b9f4-...\nsource: google_play/reviews app=com.roborock.smart\nrating: 5/5\ntext: ..."}], "text": {"format": {"type": "json_schema", "name": "review_labels", "strict": true, "schema": {...}}}}} from openai import OpenAI client = OpenAI() # reads OPENAI_API_KEY from the environment f = client.files.create(file=open("x25.jsonl", "rb"), purpose="batch") batch = client.batches.create(input_file_id=f.id, endpoint="/v1/responses", completion_window="24h", metadata={"job": "review-tagging-2026-11-06"}) print(batch.id, batch.status) # validating -> in_progress -> finalizing -> completed max_output_tokensは十分大きく設定してください。1件あたり約87トークンのタグが25件で、約2,200トークンになります。さらに推論トークンが加わります。今回は12,000に設定しました。 値が小さすぎると、構造化出力が途中で止まります。このケースはStructured Outputsガイドで説明されています。レスポンスのステータスはincomplete、理由はmax_output_tokensになります。得られるJSONは不完全なので、失敗として扱ってください。 store: falseは、後で参照するためにレスポンスを保存しないようOpenAIに指示します。ただしBatch API FAQによると、ゼロデータリテンションはBatch APIには適用されません。入力・出力・エラーファイルは、BatchとFilesサービスの保持ルールに従います。レビューに個人データが含まれる場合は、送信前に削除してください。今回はリクエストの作成時に投稿者名、投稿者のアバター、レビューのURLを削除し、レビューID、ソース、星の数、本文だけを残しました。 結果をダウンロードする際、ステータスはvalidating、in_progress、finalizing、completedの順に進みます。24時間以内に完了しなかったバッチはexpiredになります。完了した分は受け取ることができ、その分の料金が発生します。未完了のリクエストは、コードbatch_expiredとしてエラーファイルに入ります。出力ファイルはバッチ終了から30日後に削除されるので、期限内にダウンロードしてください。 すべてのフィールドはBatch APIリファレンスとFiles APIリファレンスに記載されています。低価格で同期的な応答が欲しい場合は、Flex processingをBatch料金で利用できます。その代わり応答は遅くなり、ときどき429「resource unavailable」エラーが返ります。このエラーは課金されません。 1,000件のレビューあたりのコスト:データ料金とモデル料金の合計 ここでデータ料金とタグ付けの料金を合わせてみます。データ料金は、実際に確認した1回の呼び出しあたりの件数とカタログ価格から計算しました。Google Playは1回の呼び出しで200件を返すので、1,000件のレビューに必要な呼び出しは5回で、$0.006945です。今回のTikTok動画は1回の呼び出しで12件だったため、1,000件には84回の呼び出しが必要で、$0.046704になります。6.7倍の差です。412件の混合セットの費用は$0.003334で、1,000件あたり$0.008092でした。 TikTokの数字は、動画1本あたり12件を前提にしています。人気の動画なら1回の呼び出しで返る件数が多くなり、データ料金はずっと下がります。この数字を示したのは、1つの点を伝えるためです。1,000件あたりのデータ料金は、単価だけでなく、1回の呼び出しで返る件数によって決まります。 モデル料金は、2026-11-06時点のOpenAIの公開価格と上記のトークン数から見積もりました。レビュー1件あたりの推論トークンは、0、50、200の3段階を想定しています。0は下限値ですが、Solの最低設定はlowなので、実際には到達できません。以下はすべて、レビュー1,000件あたりの金額(米ドル)です。EveryInfraのデータ料金は含みません。 構成(レビュー1,000件あたり) 推論トークン0(下限) 1件あたり推論トークン50(想定) 1件あたり推論トークン200(想定) Sol標準、1リクエストに1件 $2.99 $3.49 $4.99 Sol Batch、1リクエストに1件 $1.49 $1.74 $2.49 Sol Batch、1リクエストに1件、プレフィックスをキャッシュ価格で計算(仮定。プレフィックスは1,024未満) $0.57 $0.82 $1.57 Sol Batch、1リクエストに25件 $0.56 $0.81 $1.56 Terra Batch、1リクエストに25件、none $0.28 n/a n/a Luna Batch、1リクエストに25件、none $0.028 n/a n/a この表は3つの観点で読めます。第1に、同じモデル・同じプロンプトでも、「標準、1リクエストに1件」から「Batch、1リクエストに25件」に変えると、下限は$2.99から$0.56に下がります。5.3分の1です。 第2に、1リクエスト25件の場合、費用の大半は出力です。$0.56のうち出力は$0.44で、約78%を占めます。レビュー1件あたりの推論トークンが50増えるごとに、$0.25(50 × 1,000 × $5 ÷ 1,000,000)増えます。つまり最終的な請求額は、ご自身のレビューでlowがどれだけ推論トークンを生成するかで決まります。それを測れるのはパイロット実行だけです。 第3に、データ料金は全体のごく一部です。Google Playの$0.006945とSol Batch(1リクエスト25件)の$0.56を足すと約$0.566で、データ料金はその1.2%です。TikTokの$0.046704で計算しても、7.7%にすぎません。 ご自身の数字で計算するには、4つの値が必要です。Pは固定プレフィックスのトークン数、xはレビュー1件あたりの平均入力トークン数、yはタグ1件あたりの平均出力トークン数です。 rはレビュー1件あたりの推論トークン数です。Batch料金で1リクエストにk件のレビューを入れる場合、1,000件あたりのモデル料金はおよそ(1,000 ÷ k × P + 1,000 × x) × $1 ÷ 1,000,000に、1,000 × (y + r) × $5 ÷ 1,000,000を足した額です。この記事では、Pは967または1,007、xは81.1、yは87.3で、rは不明です。 レビューが長いとxの項が大きくなり、kの影響は小さくなります。長いECレビューでは、平均300〜400トークンになることもあります。プロンプトが長いとPの項が大きくなり、kを大きくする効果が増します。この式には、入力が272Kトークンを超えた場合の追加料金は含まれていません。1リクエスト25件なら各リクエストは3,000トークン強で、その境界からは十分離れています。1リクエストに数千件のレビューを入れる予定なら、追加料金を加算するか、リクエストを分割してください。 規模感の目安として、Gilardiらによる2023年の論文「ChatGPT Outperforms Crowd-Workers for Text-Annotation Tasks」では、2,382件のツイートでアノテーション1件あたり$0.003未満と報告されています。今回のSol Batch(1リクエスト25件)の下限はレビュー1件あたり約$0.00056、推論トークン50の場合は$0.00081です。 一方は論文の実測値で、もう一方は私たちの見積もりであり、OpenAI上では実行していません。タスクとモデルも異なります。あくまで規模感の目安として使ってください。価格がどれだけ下がったかを示すものではなく、精度についても何も示していません。 Sol、Terra、LunaとDecisions API:Solを使うべきでない場面 コスト表の最後の2行は、意図的に入れています。GPT-6.1 Terraは2026-10-26に公開されました。発表記事によると、標準価格はGPT-6.1 Solより50%安く、大量の文書処理に向いています。推論のnoneに対応しています。Batch価格は入力$0.50、出力$2.50です。OpenAIはGPT-6 Lunaを、用途を絞った大量処理向けの効率的なモデルと位置づけています。 Lunaもnoneに対応しています。Batch価格は入力$0.05、出力$0.25です。同じトークン見積もりで、Terraは1,000件のレビューあたり$0.28、Lunaは$0.028です。それぞれSolの下限の2分の1と20分の1です。しかも、どちらにも推論トークンという不確定なコストがありません。 では、Solを使うべきなのはどんなときでしょうか。第1に、「直近のファームウェアバージョンがこの不具合の原因か?」のようなタグに推論が必要な場合や、action_itemをプロダクトマネージャーが書いたような内容にする必要がある場合です。第2に、LunaやTerraの実行結果を抜き取りチェックし、感情がmixedに分類されたケースなど、一部のグループで誤りが多いとわかった場合です。その場合は、その部分だけをSolで再実行します。文言から読み取れるタグなら、Solの推論は無駄になります。 OpenAIには分類専用の手段もあります。Decisions APIは2026-10-06にベータ版として公開され、2026-10-22に一般提供が始まりました。現在対応しているのはgpt-6-lunaだけです。モデルにJSONを書かせる方式ではありません。与えた質問に対して、型付きの回答を返します。 predicateは0〜1の確率を返します。choiceは指定した選択肢から1つを選び、各選択肢の確率を返します。scoreは2〜10点の尺度で評価します。1回のリクエストに最大400問を含められます。OpenAIによると、Responses APIより約10倍高速です。 ガイドには使い分けが明記されています。確率、単一選択、スコアにはDecisionsを使います。スキーマに沿ったオブジェクトを生成するには、Structured Outputsを使います。感情、問題カテゴリ、競合への言及はDecisionsの質問として設定し、信頼度を得られます。action_itemとevidenceには、引き続きStructured Outputsが必要です。 オプション 適した用途 1,000件あたりのモデル費用(見積もり) 主な制約 GPT-6.1 Sol + Structured Outputs + Batch 推論が必要なタグ、文章形式のアクション、一部を対象とした高品質な再実行 $0.56〜(レビュー1件あたり推論トークン50ごとに+$0.25) 推論はオフにできません。24時間以内に返却されます。 GPT-6.1 Terra + Structured Outputs + Batch 大量処理、明確なフィールド、1文の記述 約$0.28(none) 難しい判断ではSolに劣る GPT-6 Luna + Structured Outputs + Batch 分類のみの超大規模処理 約$0.028(none) 文章形式のアクションの品質を抜き取りチェック Decisions API(Luna) 信頼度付きの感情、カテゴリ、競合への言及 ここでは見積もりなし(Lunaのトークン価格。質問数が使用量に影響) 自由記述は不可。Lunaのみ。 スキーマでは強制できないこと:結果が返ってきた後のチェック strictモードが保証するのは形です。すべてのフィールドがそろい、型が正しく、enum値が有効であることは保証されます。内容が正しいことは保証されません。2つのフィールドが互いに矛盾することも防げません。 Pythonのjsonschemaライブラリで小さなテストをしました。TikTokコメントに手書きしたタグを、competitors: ["dreame"]とmentions_competitor: falseに変更しました。2つのフィールドは明らかに矛盾していますが、このタグはスキーマ検証を通過しました。 Structured Outputsガイドにも、Structured Outputsの結果に誤りが含まれる場合があると書かれています。対策として、指示の調整、例の追加、タスクの分割が勧められています。そのため結果が返ってきたら、少なくとも以下のチェックを実行してください。 def check(label, item): errs = [] if label["mentions_competitor"] != bool(label["competitors"]): errs.append("mentions_competitor does not match competitors") if ("other_brand" in label["competitors"]) != (label["other_brand_name"] is not None): errs.append("other_brand does not match other_brand_name") if "none" in label["issue_categories"] and len(label["issue_categories"]) > 1: errs.append("none cannot appear with other categories") if label["action_item"] and len(label["action_item"].split()) > 20: errs.append("action_item is longer than 20 words") if label["evidence"] and label["evidence"].lower()[:30] not in item["text"].lower(): errs.append("evidence is not a quote from the text") if label["item_id"] != item["id"]: errs.append("item_id does not match") return errs 各チェックは、スキーマでは表現できないルールを扱います。2つのフィールドは、同時にtrueか同時にfalseでなければなりません。一部のenum値は、他の値と両立しません。語数の上限があります。 そして、引用は元の文章から取られていなければなりません。最後のチェックが最も役に立ちます。モデルがでっち上げた「引用」を検出できます。 1リクエスト25件の場合は、もう1つチェックが必要です。labels配列には25個の要素があり、各item_idが入力のいずれかと一致しなければなりません。ガイドによると、出力はスキーマのキー順に従います。配列の要素が入力順に返るとは保証されていません。位置ではなくIDで照合するほうが安全です。 さらに2つのケースには個別の対応が必要です。第1に、モデルが拒否するケースです。ガイドによると、モデルが安全上の理由で拒否した場合、コンテンツタイプはrefusalになり、スキーマには従いません。レビューには、暴言や過激な言葉を含むものがあります。私たちが読んだiRobotのレビューの1件は、侮辱的な言葉で開発者をののしっていました。refusalを捕捉できるようにしておいてください。 第2に、タスクと関係のない入力です。ガイドでは、タスクに合わない入力に対して何を返すかを、プロンプトで指定するよう勧めています。システムプロンプトのルール8がこれに当たります。空のレビュー、広告、無関係なテキストには、neutral、none、競合なしを返させます。 制限事項と注意点 今回はAmazonのレビューが返ってきませんでした。2つの非同期ジョブはいずれも取得件数0件で正常終了し、料金は返金されました。カタログによると、Amazonの各サイトでは商品ページに注目のレビューしか表示されず、通常は1商品あたり十数件です。1回の呼び出しで十数件だと、データ料金は1,000件のレビューあたり$0.08を超え、Google Playの10倍以上になります。Amazonの予算を決める前に、いくつかの商品で試してください。コスト表にAmazonは含めていません。 同期呼び出しの処理時間の上限は85秒です。今回、amazon/reviewsとtrustpilot/reviews(limit 200)は、どちらも85秒以上待った後に503タイムアウトを返しました。料金はかかっていません。レスポンスでは、"mode":"async"を使うか、limitを小さくするよう案内されました。85秒はサーバー側で計測されます。tiktok/commentsの呼び出しはこちら側で90.21秒かかりましたが、HTTP 200が返りました。これも上限に近い状態でした。 バッチでデータを取得する場合、時間のかかる機能には非同期モードを使ってください。すぐにjob_id付きの202が返るので、その後poll_urlをポーリングします。今回の2つの非同期ジョブは、開始から完了まで約1分36秒と3分44秒でした。 「最新200件」は一定の期間ではありません。前述のとおり、Roborockの200件は32日間、iRobotの200件は99日間にわたります。週ごとの比較をするなら、タグ付けの前にposted_atで同じ週を切り出してください。 同期モードでは、Google Playのページネーションは200件までです。それ以上取得するには、非同期モードを使ってください。上限はカタログのpaginationフィールドで確認できます。 自社アプリの場合、GoogleのReply to Reviews APIが返すのは直近1週間のレビューだけです。1ページあたり最大100件で、GETリクエストは1時間あたり200回までです。それより古いレビューは、Play ConsoleのCSVダウンロードから取得します。競合のアプリについては、Google APIでは何も取得できません。 レビューは本物か。米国FTCの偽レビューに関する最終規則(2024年8月)は、AIが生成した偽レビューを禁止しています。タグからわかるのはレビューに何が書かれているかであり、そのレビューが本物かどうかではありません。まずは、星5つや星1つのレビューが不自然に集中していないかを確認してください。 構造化出力は推論の質に影響する可能性があります。論文「Let Me Speak Freely?」(2024年)は、JSONなどの厳格な形式によって推論性能が下がる場合があることを示しました。「JSONSchemaBench」(2025年)は、OpenAIの方式を含む6つの制約付きデコーディング手法を10,000件の実際のスキーマで検証し、手法間に大きな差があることを確認しました。どちらも検証対象は旧世代のモデルです。それでも、本記事で採用した方針の1つを裏付けています。action_itemをスキーマの後方に置くことです。あるタググループのスコアが低い場合は、まず自由形式で回答させ、その後、安価なモデルでJSONに変換してみてください。 精度はモデルではなく人が確認してください。今回はOpenAIでバッチを実行していないため、精度の数値はありません。推奨する手順は次のとおりです。まず、パイロットの20件のレビューに、本番実行から無作為に選んだ50件を加えます。次に、製品に詳しい人に、各タグを本文と照らし合わせて確認してもらい、カテゴリごとに誤りを数えます。最後に、新しいプロンプトが必要なカテゴリと、別のモデルでの再実行が必要なカテゴリを判断します。 キーワードに一致した上記17件にも、同じ方法を使いました。データアノテーション向けLLMのサーベイには、モデルが作成したアノテーションの評価方法を扱う節があります。人によるレビューはその方法の1つです。人がラベル付けした既存のデータセットとの比較も、別の方法として挙げられています。 本サイトの他のレビュー記事との違い Claude CodeでDeepSeek V4を使ってコメントをクリーニングするでは、動画コメント695件を対話型セッションでクリーニングします。そのコストはキャッシュヒットに左右されます。ClaudeでデータAPIへのツール入力を厳格化するでは、ツールへの入力を厳格にします。Qwen Agentでレビュー分析エージェントを作るでは、1つのエージェントが価格を比較し、レビューを読みます。本記事で厳格にするのは出力形式です。ジョブは1回のオフラインバッチで、コストを左右するのはリクエストの構成です。 ChatGPT内でレポートを作成する方法は、GPT-6のIntelligent UIでソーシャルデータのレポートを作成するを参照してください。 直近60日間の変更点 2026-09-29:GPT-6.1 SolがAPIで利用可能に(変更履歴)。大規模な構造化ジョブで、最上位モデルに近いモデルをAstraの5分の1の価格で使えるようになりました。ただし、推論をオフにすることはできません。 2026-10-06:Decisions APIがベータ版として公開されました。その後、2026-10-22に一般提供が始まりました(Decisionsガイド)。単一選択タグでは、JSONを使わずに信頼度付きの結果を得られるようになりました。Lunaでのみ利用できます。 2026-10-20:12時間のプロンプトキャッシュがAPIで利用可能になりました。書き込みは入力価格の2.5倍です(プロンプトキャッシュのガイド)。1日に数回実行し、プロンプトが1,024トークンを超えるジョブで効果があります。短いプレフィックスでは効果がありません。 2026-10-21:組織とプロジェクトでサービスティアを制限可能に。プラットフォームの設定から行えます(変更履歴)。実務への影響:管理者はデフォルトのティアだけを許可できます。その場合、Flexなどデフォルト以外のティアへのリクエストは、サービスティアのエラーで失敗します。バッチが失敗してからではなく、実行前にプロジェクト設定を確認してください。 2026-10-26:GPT-6.1 Terraが登場。標準価格はSolの半分で、推論レベルnoneに対応しています(Terraのモデルページ)。実務への影響:SolとLunaの中間にあたり、推論をオフにできる選択肢ができました。多くのタグ付けジョブでは、まずTerraを試すとよいでしょう。 2026-10-28:ChatGPTの契約者がAPIクレジットを受け取れるように。対象となるProおよびBusinessの契約者は、毎月最大$500を受け取れます(クレジットのガイド)。パイロット実行には十分な額です。未使用のクレジットは毎月失効します。 GPT-6.1 Solと構造化出力のよくある質問 GPT-6.1 Sol APIの料金はいくらですか? 料金表の全体は、この記事の最初のセクションに掲載しています。要点をまとめると、OpenAIの料金ページでは、標準料金で入力が1Mトークンあたり$2、出力が1Mトークンあたり$10です。BatchとFlexはその半額です。Fastモードは標準料金の2倍、Ultrafastは6倍ですが、バッチでのタグ付けにはどちらも必要ありません。推論トークンは出力として課金されます。(質問の出典:Googleのオートコンプリート、2026-11-06。) GPT-6.1 SolのAPIでのモデル名は何で、どう呼び出せばよいですか? モデル名はgpt-6.1-solです。Responses APIのリクエストで、modelフィールドに指定します(モデルページ)。Chat Completionsでも使えますが、ツール呼び出しには対応していません。 ツールを使う場合、移行ガイドではResponses APIを推奨しています。推論はreasoning.effortで設定します。最も低い値はlowです。(質問の出典:Googleのオートコンプリート、「gpt 6.1 sol api name」) GPT-6.1のStructured Outputsの例はどう書けばよいですか? 最も短い書き方は、Responses APIのリクエストにtext: {format: {type: "json_schema", name: "...", strict: true, schema: {...}}}を追加する方法です。スキーマ内のすべてのオブジェクトにadditionalProperties: falseが必要で、すべてのフィールドをrequiredに含める必要があります。Pythonでは、SDKのresponses.parseにPydanticモデルを渡すこともできます。その場合、スキーマはSDKが生成します(openai-python helpers、PydanticのJSON Schemaドキュメント)。本記事の9フィールドのスキーマをコピーして、変更して使えます。(質問の出典:Googleのオートコンプリート、「gpt 6.1 structured outputs example」) OpenAIのBatch APIとStructured Outputsは併用できますか? できます。Structured Outputsガイドによると、Structured OutputsとJSONモードはどちらもBatch APIで使えます。Batchの各行のbodyは同期リクエストと同じなので、text.formatはそのままで構いません。 注意点は3つです。まず、1つのファイルで使えるモデルは1つだけです。次に、JSONが途中で切れないよう、max_output_tokensを十分に大きく設定します。最後に、結果は行の順序ではなくcustom_idで照合します。(質問の出典:Googleのオートコンプリート、「openai batch api structured output」) max effortのGPT-6.1 Solが、GPT-5.6 Solよりはるかに遅いのはなぜですか? この質問はOpenAI開発者コミュニティのスレッドから取り上げました。同じプロンプトで、maxに設定したGPT-6.1 Solは1,846秒で約62,708出力トークン(うち推論52,552)を生成しました。GPT-5.6 Solは373秒で約31,426トークンでした。タグ付けではmaxを使わないでください。lowで十分です。推論トークンが2倍になれば、出力料金も2倍になります。 始め方 EveryInfraのクイックスタートでAPIキーを作成します。無料の機能カタログを確認します。google_play/reviews、tiktok/comments、amazon/reviewsの料金とレスポンスフィールドを確認します。 最初に、各ソースから1ページずつ取得します。Google PlayレビューAPIとTikTokコメントAPIのプレイグラウンドで試し、1回の呼び出しで何件返るかを確認します。そのうえで、レビュー1,000件あたりの予算を計算します。処理に時間がかかる機能には"mode":"async"を追加します。 本記事の9フィールドのスキーマを、自社のカテゴリに合わせて変更します。Structured Outputsガイドにあるstrictモードのルールを満たしているか、スクリプトで確認します。 OpenAIで、20件のレビューを使った同期のパイロットを実行します。usageで入力、出力、推論のトークン数を読み、コスト表を更新します。そのうえで、Sol、Terra、Lunaのいずれかを選びます。 1リクエストあたり25件のレビューでJSONLファイルを作成し、Batch APIを使います。結果が返ってきたら、整合性チェックを実行します。evidenceを元のテキストと照らし合わせて抜き取り確認します。 コードを書かずにClaude、Cursor、ChatGPTでその都度レビューを調べるには、EveryInfraのリモートMCPサーバーに接続します。各プラットフォームの概要は、Google PlayデータAPI、TikTokデータAPI、AmazonデータAPIを参照してください。AmazonのレビューについてはAmazonレビューAPIを、料金については料金ページを参照してください。 参考文献 「GPT-6.1 Sol Model」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Introducing GPT-6.1 Sol」、OpenAI、2026-09-29 「API Changelog」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Pricing」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Structured model outputs」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Batch API」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Prompt caching」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Using GPT-6 (latest model guide)」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Reasoning models」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Decisions」、OpenAI開発者ドキュメント、2026-11-06閲覧 「GPT-6.1 Terra Model」、OpenAI開発者ドキュメント、2026-11-06閲覧 「GPT-6 Luna Model」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Rate limits」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Flex processing」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Function calling」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Batch API reference」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Files API reference」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Ultrafast mode」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Error codes」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Your data」、OpenAI開発者ドキュメント、2026-11-06閲覧 「API credits for ChatGPT subscribers」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Migrate to the Responses API」、OpenAI開発者ドキュメント、2026-11-06閲覧 「Prompt cache diagnostics」、OpenAI開発者ドキュメント、2026-11-06閲覧 「GPT-6.1 Sol system card addendum」、OpenAI Deployment Safety Hub、2026-09-29 「Introducing GPT-6.1 Terra」、OpenAI、2026-10-26 「Batch API FAQ」、OpenAIヘルプセンター、2026-11-06閲覧 「tiktoken」、OpenAI、GitHub 「openai-python helpers」、OpenAI、GitHub 「tiktoken 0.14.0」、PyPI 「jsonschema」、PyPI 「JSON Schema (Pydantic docs)」、Pydantic 「JSON Schema Draft 2020-12」、JSON Schema 「RFC 8259: The JSON Data Interchange Format」、IETF、2017-12 「Reply to Reviews」、Google Play Developer API、2026-11-06閲覧 「Download and export monthly reports」、Play Consoleヘルプ、2026-11-06閲覧 「Why is there a 500 character limit on Google Play reviews?」、Google Playヘルプコミュニティ、2022-07-31 「Query Video Comments」、TikTok for Developers、2026-11-06閲覧 「FTC Announces Final Rule Banning Fake Reviews and Testimonials」、米国連邦取引委員会、2024-08-14 「ChatGPT Outperforms Crowd-Workers for Text-Annotation Tasks」、Gilardiほか、arXiv、2023-03-27 「Let Me Speak Freely? A Study on the Impact of Format Restrictions on Performance of Large Language Models」、Tamほか、arXiv、2024-08-05 「JSONSchemaBench: A Rigorous Benchmark of Structured Outputs for Language Models」、Gengほか、arXiv、2025-01-18 「Large Language Models for Data Annotation and Synthesis: A Survey」、Tanほか、arXiv、2024-02-21 「Why is GPT-6.1 Sol MAX dramatically slower than GPT-5.6 Sol MAX using the same prompt?」、OpenAI開発者コミュニティ、2026-11-06閲覧 「EveryInfra機能カタログ」、EveryInfra、2026-11-06 00:15 SGT閲覧 「Roborock(Google Playのアプリページ)」、Google Play、2026-11-06閲覧 「iRobot Home(Google Playのアプリページ)」、Google Play、2026-11-06閲覧 「Model Context Protocol Specification」、MCP、2025-06-18 「json — JSON encoder and decoder」、Pythonドキュメント 「JSON Lines」、jsonlines.org 「Introduction to Structured Outputs (Cookbook)」、OpenAI Cookbook ! この記事は EveryInfra ブログの記事の転載です。原文:GPT-6.1 Sol APIの構造化出力でレビューにタグ付けすると、コストはいくらになるか EveryInfra は AI エージェントと開発者向けの従量課金データ API です:everyinfra.com
This is likely not a real issue and the guy is trolling (he posted a similar issue months ago somewhere else) but the comments are... revealing codex#59060
【Codex】【VSCode】Remote SSH切断後に「別のアプリで開いています」と表示される問題の解決方法 VS CodeのRemote SSH環境でCodexを使用していたところ、SSH接続が切れた後、既存の会話を開けなくなる問題が発生しました。 VS CodeやローカルPCの再起動、Codex拡張機能のバージョン変更などを試しましたが、改善しませんでした。 ! 今回の環境では、リモートLinux上に残っていた古いCodexプロセスを終了することで、会話履歴を削除せずに復旧できました。 VS CodeやPCを再起動しても改善しない場合は、リモート側に残ったプロセスと、会話のロックを確認します。 本記事では、発生した問題と考えられる原因、実際に行った解決手順を紹介します。 発生した問題 使用環境 エディタ:VS Code 接続方法:VS CodeのRemote - SSH拡張機能 リモート側のOS:Linux 使用した拡張機能:OpenAIのCodex拡張機能 Remote SSH経由でCodexを使用していたところ、会話の途中でリモート接続が切れました。 その後、SSHで再接続し、Codexで既存の会話を開こうとしたところ、以下のメッセージが表示され、会話を再開できませんでした。 メッセージを読み込めませんでした 別のアプリで開いています ここで続けるには、あちらで閉じてください。 (一部画像はAIを用いて修正) しかし、切断前の会話画面には戻れず、メッセージの指示どおりに元の画面で会話を閉じることもできませんでした。 試したが解決しなかった方法 まず、以下の一般的な対処方法を試しました。 VS Codeの再起動 PCの再起動 Remote-SSH: Kill VS Code Server on Host... によるリモート側VS Code Serverの終了 Codex拡張機能のバージョン変更 しかし、いずれの方法でも改善しませんでした。 もちろん、これらの操作によって解決するケースもあります。ただし、今回のように解決しない場合は、リモートサーバー上にCodexのプロセスが残存していないかを確認する必要があります。 考えられる原因:古いCodexプロセスによる会話のロック 今回の問題は、SSH接続が切れた後もリモートサーバー上に古いCodexプロセスが残り、会話のロック(writer lock)を保持していたことが原因と考えられます。 Codexでは、同じ会話に複数のプロセスが同時に書き込まないよう、writer lockによる排他制御が行われています。 そのため、古いプロセスがロックを保持したままだと、新しく起動したCodexから既存の会話を開けなくなる場合があります。 同様の問題は、CodexのGitHubリポジトリでも報告されています。 GitHub Issue #41849:Remote SSH再接続後に古いCodexプロセスがwriter lockを保持する問題 解決方法 ! 以下のコマンドは、Remote SSH接続先のLinuxターミナルで実行します。 復旧までの流れは、次の4ステップです。 Codexプロセスを確認する writer lockを保持しているプロセスを特定する 不要になった古いCodexプロセスを終了する VS Codeで会話を開き直す Step 1:Codexプロセスを確認する まず、Codex関連のプロセスが現在どの程度起動しているかを確認します。 Codex関連プロセスの確認 ps -u "$USER" -o pid,ppid,etime,args | grep -Ei '[c]odex|[c]hatgpt|[e]xtensionHost' このコマンドでは、Codex関連のプロセスについて、PID(プロセスID)、PPID(親プロセスID)、起動からの経過時間、実行コマンドを確認できます。 今回の環境では、以下のような結果が得られました。 実行結果(抜粋・整形) PID ELAPSED COMMAND 1204484 6-04:53:18 codex app-server 2875105 11:20:31 codex app-server 3419599 03:29 codex app-server ELAPSED はプロセスが起動してからの経過時間を示しています。 今回の場合、約6日前、約11時間前、約3分前から動作しているCodexプロセスが同時に存在していました。 特に、古いプロセスと新しく起動したプロセスが同時に残っていることが気になります。 ただし、起動時間が古いからといって不要なプロセスとは限りません。次の手順で、実際に会話のロックを保持しているプロセスを確認します。 Step 2:writer lockを保持しているプロセスを特定する 次に、lsofコマンドを使用して、Codexのロックファイルを開いているプロセスを確認します。 writer lockを保持しているプロセスの確認 lsof -nP +D "$HOME/.codex/thread-writer-locks" lsof は、実行中のプロセスが開いているファイルを調べるためのコマンドです。 Codexのwriter lockに関するファイルは、今回の環境では ~/.codex/thread-writer-locks/ に存在していました。 実行すると、以下のような結果が得られました。 実行結果(抜粋・整形) COMMAND PID FD NAME codex 1204484 17uW .../thread-writer-locks/xxx.lock codex 1204484 41uW .../thread-writer-locks/xxx.lock codex 1204484 48uW .../thread-writer-locks/xxx.lock codex 1204484 71uW .../thread-writer-locks/xxx.lock codex 2875105 45uW .../thread-writer-locks/xxx.lock codex 2875105 62uW .../thread-writer-locks/xxx.lock ロックファイル名は省略しています。 FD 列にある uW のうち、u はファイルが読み書き可能な状態で開かれていること、W はファイル全体への書き込みロックを保持していることを示しています。 この結果から、PID 1204484 と 2875105 の2つのCodexプロセスが、複数のwriter lockを保持していることがわかりました。 一方、Step 1で確認した新しいCodexプロセス(PID 3419599)は、この出力には含まれていません。 さらに、次のコマンドでプロセスの親子関係を確認します。 pstreeによる親子関係の確認 pstree -sp 2875105 pstree は、プロセスの親子関係をツリー形式で表示するコマンドです。PIDは自分の環境で確認したものに置き換えてください。 今回の環境では、プロセスの親子関係などを確認した結果、PID 2875105 は古いVS Codeの拡張機能から起動されたCodexの app-server と判断しました。 そこで、この古いプロセスが問題の原因になっている可能性を考えました。 ! 複数のロックが確認できても、それだけで問題の会話に対応するロックを特定できたとは限りません。PIDや親プロセス、ほかのCodexセッションの稼働状況を合わせて確認することが重要です。 補足:lsofでFUSE関連の警告が表示される場合 lsofの実行時に、FUSE関連の警告が表示される場合があります。今回も警告が表示されましたが、ロックファイルに関する情報は取得できていました。 Step 3:古いCodexプロセスを終了する 今回の環境では、PID 2875105 が古いVS Codeの拡張機能から起動されたプロセスであることが確認できました。 そのため、ほかの作業で使用していないことを確認したうえで、以下のコマンドを実行しました。 ! 以下のPIDは今回の環境での例です。必ず自分の環境で確認したPIDに置き換えてください。 別のCodexセッションで実行中のタスクがある場合、そのプロセスを終了すると処理が中断される可能性があります。 対象のプロセスに終了を要求(PIDは置き換える) kill -TERM 2875105 kill -TERM は、指定したプロセスに終了要求のシグナル(SIGTERM)を送信するコマンドです。 実際の終了処理は、プロセス側の実装によって異なります。 終了後、必要に応じて以下のコマンドでロックの状態を再確認できます。 終了後のロック状態の再確認 lsof -nP +D "$HOME/.codex/thread-writer-locks" Step 4:VS Codeで会話を開き直す 古いプロセスを終了した後、Codexの既存の会話を開き直します。 画面が更新されない場合は、VS Codeのコマンドパレットから以下を実行します。 コマンドパレットで実行 Developer: Reload Window コマンドパレットは、次のショートカットで開けます。 OS ショートカット macOS Cmd + Shift + P Windows / Linux Ctrl + Shift + P VS Codeの再起動でも構いません。 今回の環境では、古いCodexプロセスを終了することでエラーが解消し、以前の会話を正常に再開できました。 会話履歴や設定を削除する必要もありませんでした。 備考・注意点 なぜVS CodeやPCの再起動では解決しなかったのか? 今回の問題では、ローカルPCの再起動だけでなく、Remote SSHのVS Code Serverを終了させても改善しませんでした。 これは、リモートサーバー上のCodexプロセスが残存していたためと考えられます。 ローカルPCを再起動しても、SSH接続先で実行されているプロセスまで終了するとは限りません。 また、Remote-SSH: Kill VS Code Server on Host... はVS Code Serverの終了に用いるコマンドですが、今回、この操作でも改善しませんでした。Codexプロセスが残った詳しい経緯は確認できていません。 一方、以下のような原因であれば、一般的な再起動操作で解決できる場合もあります。 対処方法 改善が期待できるケース VS Codeの再起動 エディタや拡張機能の一時的な不具合 PCの再起動 ローカル側のプロセスや接続状態の不具合 Kill VS Code Server on Host リモート側のVS Code Serverの異常 Codex拡張機能の更新・変更 特定バージョンに起因する不具合 ただし、今回のようにリモートLinux上の古いCodexプロセスがwriter lockを保持している場合、これらの操作だけでは解決しないことがあります。 その場合は、問題を起こしているCodexプロセスを直接特定することが重要です。 プロセスの終了やロックファイルの削除には注意 ! 復旧作業の際は、以下の点に注意してください。 ~/.codex ディレクトリ全体を削除しない。会話履歴や設定を失う可能性があります。 pkill -9 codex などで、すべてのCodexプロセスを無差別に強制終了しない。 複数のCodexセッションを使用している場合は、終了対象のプロセスがほかの作業に使用されていないか確認する。 ロックファイルを安易に削除せず、まずはロックを保持しているプロセスを調べる。 今回のケースでは、古いCodexプロセスを1つ終了するだけで復旧できました。 まとめ VS CodeのRemote SSH環境でCodexを使用している際、SSH接続が切れると、古いCodexプロセスが残存して会話のwriter lockを保持し続ける場合があります。 VS CodeやPCの再起動でも改善しない場合は、以下の手順を試してみてください。 ps でCodex関連プロセスを確認する lsof でwriter lockを保持しているプロセスを調べる 不要になった古いCodexプロセスを終了する VS Codeで既存の会話を開き直す 今回はこの方法で、会話履歴を失うことなくCodexを復旧できました。 同じ問題で困っている方の参考になれば幸いです。 参考資料 openai/codex — GitHub Issue #41849 Visual Studio Code — Remote Development Tips and Tricks
if you are having a bad day, remember that meat proxies for AIs exist: codex#59060 "Told to reboot a server, it destroyed systemd, destroyed containers, ran random scripts, cost me $10,000+" pure gol- err platinum 🥇
Told to reboot a server, it destroyed systemd, destroyed containers, ran random
Bro asked AI to reboot the server and it rebooted his entire bloodline. Source: codex#59060
Told to reboot a server, destroyed systemd, destroyed containers, cost me $10k+
Codex Was Torching Your SSD and Nobody Noticed for Months Your Codex log file looked fine. Your SSD counters did not. The file was small. That was the trick. If you opened <code>~/.codex/</code>, you could see a SQLite database called <code>logs_2.sqlite</code>. It was not huge. It was not swallowing the disk. Finder, Explorer, <code>du</code>, and every normal storage check had nothing dramatic to say. Meanwhile, one developer's SSD recorded roughly 37 TB of writes in 21 days. That works out to about 1.7 TB a day. If you stretch one heavy, continuous-use report across a full year, it becomes roughly 640 TB. The number is not a prediction for every Codex user. It came from one machine running for a long time. But it exposed a problem that is easy to miss with local developer tools: > a file can stay small while the storage device underneath it gets written to constantly. This is the story of a noisy TRACE logging sink, a SQLite database doing exactly what it was told, and the SSD wear that nobody could see from file size alone. The quick version - A user reported unusually high local disk writes and traced them to Codex CLI's <code>logs_2.sqlite</code> database and its WAL companion file. - The reported machine accumulated about 37 TB of writes over 21 days of uptime. - The file stayed bounded because new logs were inserted while older logs were deleted. - The reported cause was an overly verbose TRACE logging path that did not fully respect the expected <code>RUST_LOG</code> override. - The supplied investigation says version 0.142.0 reduced the noisy writes substantially, with early testing reporting around 85% fewer log writes. - If you used an older build heavily, update first, then inspect real write activity and your SSD's lifetime-write counters. The number that made people look twice 37 TB in 21 days is not a normal “developer tool wrote some logs” story. | Measurement | Value | | --- | --- | | Reported writes | ~37 TB | | Reporting window | 21 days | | Approximate daily rate | ~1.7 TB/day | | Annualized at that rate | ~640 TB/year | The annualized number needs context. It assumes the same workload every day for a year, which is rarely how people work. Someone running short sessions will produce far less. Someone leaving the tool open, working with long sessions, or using it in automation could produce much more than they expect. The useful point is not “every SSD will die in a year.” The useful point is that this level of write activity belongs in the category of things you should notice and control. <!-- Visual placement: use a clean SMART dashboard screenshot here, with all serial numbers and personally identifiable drive details blurred. --> Why the file size told you almost nothing Most of us check disk problems like this: du -sh ~/.codex ls -lh ~/.codex/logs_2.sqlite* Those commands answer one question: > How much data is stored right now? They do not answer this: > How much data has been written over time? The Codex log database was designed to retain a bounded amount of diagnostic information. New rows were written, old rows were removed, and the database could remain roughly the same size. So the folder looked calm while the disk was doing constant work. Imagine a café with four tables. Looking through the window at noon, you see four people sitting down. Come back at 6 PM, still four people. The table count tells you nothing about whether eight customers visited or eight hundred. The SQLite file was the same. Stable size, enormous turnover. !Diagram showing how a small SQLite log file can lead to high SSD write activity What was generating all those writes? The reported issue points to a logging path that defaulted to TRACE, the most detailed level in Rust's tracing ecosystem. TRACE logging is useful when you are trying to understand a very specific failure. It can include low-level events that are normally too noisy for everyday use: - file-open events - raw network or WebSocket activity - transport details - telemetry plumbing - dependency-library messages that get bridged into the application's tracing system The supplied breakdown estimates that TRACE-level entries made up 70.7% of the retained log-body size. The highest-volume targets included WebSocket handling, OpenTelemetry-related logs, a generic dependency log bridge, and client transport code. That is diagnostic exhaust. A little of it is fine. A default stream of it, persisted locally in SQLite, is a different kind of workload. The three parts that stacked together No single part of this design has to be bad on its own. 1. TRACE was active where it should have been opt-in TRACE is not a normal default for a tool used for hours at a time. It is the level you turn on when you are actively debugging. The reported behavior was that Codex initialized a feedback logging sink at TRACE. That turns every routine interaction into a lot of diagnostic detail. 2. RUST_LOG=warn did not fully quiet it Rust developers often reach for this first: RUST_LOG=warn codex That is supposed to turn down the volume. According to the issue analysis, it did not fully apply to the relevant sink. If a target did not have an explicit override, Codex's internal default still allowed noisy logging. That is worse than an obvious missing setting. A user can think they reduced logging while the disk keeps receiving the same workload. 3. SQLite turned event noise into storage churn SQLite is excellent. It is also not magic. For small structured local data, a SQLite database is a good choice. For a constant stream of fine-grained TRACE events, inserts, deletes, indexes, and checkpoints, it can create a lot of writes even when the database never becomes large. The problem was not “SQLite is bad.” The problem was using a write-heavy retention loop as a sink for a log level that should not have been permanently enabled. The part the operating system does not show you There are two kinds of amplification in this story. SQLite write amplification SQLite often uses WAL mode, short for write-ahead logging. In simplified form: 1. A log event is appended to the WAL file. 2. SQLite later checkpoints that change into the main database. 3. Older rows are deleted to keep retained data bounded. 4. Indexes and database pages are updated along the way. One logical log event can therefore create more than one write at the filesystem level. The WAL is not wrong. Checkpoints are not wrong. The problem appears when that loop is fed relentlessly with small, noisy events. SSD write amplification An SSD has its own internal life. It writes data in small pages but erases much larger blocks. When a small part of a block changes, the SSD controller often writes the new data to a fresh location, marks old data stale, later moves still-valid pages, and erases the old block. That movement is called garbage collection. Wear leveling moves writes around so one physical area does not wear out first. Both are useful. Both mean the flash may receive more physical writes than the operating system asked for. The ratio is called the write amplification factor. The exact number varies by drive, spare capacity, free space, workload, and controller. The supplied analysis does not measure the multiplier for the affected drive, so do not treat any guessed physical-wear number as a precise fact. The direction is enough: > High-churn, small random writes are one of the workloads SSDs work hardest to manage. Is 640 TB actually dangerous? It can be. SSDs have an endurance rating usually expressed as TBW, or terabytes written. A typical 1 TB consumer SSD may be rated for a few hundred TBW. Enterprise drives may be rated much higher. The exact number depends on the model, capacity, NAND type, and warranty. That does not mean a drive fails the moment it reaches its rated TBW. Ratings are warranty and endurance guidance, not a timer that hits zero at midnight. Plenty of drives outlive them. But a background development tool potentially consuming hundreds of terabytes of writes is still worth taking seriously. It can eat through a meaningful part of the drive's expected write budget without filling the disk or showing a visible failure. How to check your own machine The order matters. Update first. Then look at real I/O. Then check what the drive itself says. Step 1: update Codex The investigation supplied with this article identifies 0.142.0 as the version containing the logging reduction. Check the current release notes and your installed build, then update before you start measuring. Also remember that Codex CLI, the desktop app, and editor integrations can ship or bundle components on different schedules. Updating one does not automatically prove every local Codex-related process is updated. Step 2: find the log files On macOS and Linux: ls -la ~/.codex/logs_2.sqlite* On Windows PowerShell: Get-Item "$env:USERPROFILE\.codex\logs_2.sqlite*" You might see the database, a <code>-wal</code> file, and a <code>-shm</code> file. Do not stop after checking their size. A small database is the whole reason this incident was easy to miss. Step 3: watch actual writes On Linux, <code>pidstat</code> can show which process is writing: pidstat -d 5 To see whether a process has the database open: sudo lsof ~/.codex/logs_2.sqlite On macOS, open Activity Monitor, select the Disk tab, and watch bytes written for Codex-related processes while you work. On Windows, use Resource Monitor or Task Manager's disk column. Look at sustained per-process writes, not just a brief spike during startup. Step 4: check the drive's own lifetime write counter This is the closest thing to the truth you have. On Linux or macOS with smartmontools installed, an NVMe drive can often be checked with: sudo smartctl -a /dev/nvme0 Look for: - Data Units Written - Percentage Used - warnings or critical health indicators On Windows, CrystalDiskInfo exposes Total Host Writes and health information. PowerShell can also show some device health data: Get-PhysicalDisk | Get-StorageReliabilityCounter | Select-Object DeviceId, Wear, Temperature Compare lifetime writes with the TBW rating for your exact drive model. Do not compare against a random SSD number from a search result. <!-- Visual placement: add a small annotated image of a SMART page here. Highlight only Data Units Written or Total Host Writes, Percentage Used, and health status. --> What if I used an older version for months? First, do not panic and do not guess from file size. Check the drive counters. If the drive has low percentage used and reasonable total writes for its age and workload, update and move on. If the drive has unusually high wear, warnings, or a large write count compared with its TBW rating, back up your important data and plan for a replacement before failure forces the schedule. This is a good backup reminder even if your drive looks healthy. SSDs usually fail without much theatre. Workarounds if you cannot update immediately Updating is the right fix. These are temporary ways to reduce persistent write pressure while you arrange that update. Put throwaway logs on ephemeral storage For CI jobs and containers, the cleanest option is usually to run with a temporary home directory or mount the relevant config directory on ephemeral storage. Example for a disposable CI environment: export HOME="$(mktemp -d)" For a container workload, mount a tmpfs at the appropriate Codex state directory if the environment can tolerate losing local diagnostics when the job exits. This is especially useful for build runners that create and destroy their environment anyway. Be careful with /tmp Moving a log file to <code>/tmp</code> is only useful if <code>/tmp</code> is actually memory-backed in your environment. On many Linux systems it may be tmpfs. On macOS, <code>/tmp</code> is not automatically a RAM disk by default. Check the mount before assuming the write load vanished: mount | grep ' /tmp ' Then verify with a real I/O monitor. Never trust a workaround because it sounded plausible in a comment thread. Avoid unsupported database surgery unless you really need it Some community workarounds modify the SQLite log database so inserts are ignored. That can be useful in an emergency, but it is an unsupported change to an internal application file. Future versions may change the schema or assume the database behaves normally. If you choose that route, stop Codex first, make a copy of the database, document what you changed, and remove the workaround once you are on a fixed release. The bigger lesson for tool builders This bug is bigger than one CLI. Measure bytes written, not just disk space used Every local persistence feature should have an I/O budget under realistic usage. Test bytes written per hour, per session, and per day. File size is not a sufficient metric for databases, caches, indexes, or log rotation systems. TRACE must be an intentional decision Verbose logging needs an explicit opt-in and a clear way back out. Shipping it by default turns every user into a debugging environment. Standard controls have to work end to end If your ecosystem has a standard logging override, honour it for every sink. A partial override is dangerous because it gives experienced users false confidence. Structured logs have storage costs SQLite, RocksDB, local search indexes, and caches are all good tools. High-frequency writes have a cost. The design needs backpressure, batching, rate limits, retention rules, and test coverage for pathological verbosity. Privacy and wear are connected The more raw operational detail a tool records, the more it writes and the more sensitive local data it can retain. Logging file paths, raw transport frames, and low-level event payloads should be a deliberate debugging choice, not background noise. The bigger lesson for users There is a simple habit worth keeping: > When you install a new long-running developer tool, look at its write rate once. Two minutes in Activity Monitor, Resource Monitor, or <code>pidstat</code> can catch a surprising amount. Check lifetime SSD writes occasionally. Keep your tools updated. Treat a stable file size as a storage number, not an I/O number. The log file was small. The work it made the SSD do was not. What is confirmed and what is still uncertain The supplied investigation is clear about its limits, and those limits matter: - The 37 TB measurement came from one heavy user's machine. It is a data point, not a universal rate. - Reports disagree about when the highest logging verbosity began. The exact start date is uncertain. - The annualized 640 TB figure is arithmetic based on the reported 21-day sample, not a forecast. - SSD write amplification is real, but the exact physical NAND multiplier was not measured for this incident. - The reported 85% reduction after version 0.142.0 is an early observed result, not a guarantee for every platform or workload. That uncertainty does not weaken the main finding. The correct response is still simple: update, measure the process, and inspect the drive counter. Sources and further reading - GitHub issue #28224 in openai/codex - Official OpenAI Codex documentation - The public reporting and technical analysis supplied for this article, including Notebookcheck, Windows Report, Lilting.ch, yage.ai, and Daniel Vaughan's write-up
A proof of Ryser's circulant Hadamard conjecture We show that an $n\times n$ circulant Hadamard matrix must satisfy a family of congruence equations that have solutions only when $n \leq 4$, proving Ryser's 1963 conjecture that no such matrices exist for $n>4$. Comments: Error found by AI. Corrected proof may be found here: github.com/openai/math/tree/main/preprints/The-ci…
Four GPUs, One Quantization Ladder: Gemma 4 E2B From a 4 GB Laptop to an NVIDIA L4 --- title: "Four GPUs, One Quantization Ladder: Gemma 4 E2B From a 4 GB Laptop to an NVIDIA L4" published: true description: "Gemma 4 E2B from BF16 to Q2_K on a Tesla T4, an NVIDIA L4, a GTX 1650 laptop and an RTX 3050 desktop, with byte-identical GGUF files and the same llama.cpp build. The ladder keeps its shape on all four cards, 218 questions die on all four, and any two GPUs grade 2 to 4 questions differently at BF16." tags: gemma, llamacpp, quantization, nvidia cover_image: raw.githubusercontent.com --- This article provides a step by step guide to running Gemma 4 E2B's quantization ladder, BF16 down to Q2_K, on four NVIDIA GPUs with the same nine GGUF files and the same llama.cpp build, and comparing the four cards question by question. xbill9/gemma-graveyard The first article asked every rung of the ladder the same 250 GSM8K questions on a Tesla T4. The second repeated it on an NVIDIA L4 in AWS. This one adds two consumer cards: a GTX 1650 Ti laptop GPU with 4 GB and an RTX 3050 desktop card with 8 GB. All four run llama.cpp b11699 on files with the same sha256, and each card gives identical text when BF16 is run twice. --- The Results in Brief The ladder keeps its shape on all four GPUs. Against each card's own BF16, F16 to Q5_K_M lose 1 to 8 answers, Q4_K_M and Q4_0 lose 9 to 19, Q3_K_M loses 42 to 49, and Q2_K gets 3 to 6 right out of 250. The counts agree; the questions move. Any two GPUs grade 2 to 4 questions differently at BF16 and 12 to 22 at Q4_0. No question is lost at F16 or Q8_0 on all four cards, while 216 are lost at Q2_K on all four. 218 questions die at some rung on every GPU, and 147 of them die first at the same rung on every GPU. The 4 GB laptop runs every quantized rung entirely on its GPU. BF16 and F16 do not fit, so llama.cpp splits them between GPU and CPU, and they decode at 9.2 and 9.6 tokens per second against 45.9 for Q8_0 on the same card. The cover lays the four GPUs' grades over each other. One row per rung, BF16 at the top and Q2_K at the bottom, one cell per question: marigold where all four GPUs answer right, grey where all four answer wrong, violet where they disagree. docs/make_cover_gpu4.py draws it from the committed rows. --- What Changes and What Stays | | Tesla T4 | NVIDIA L4 | GTX 1650 Ti | RTX 3050 | | :--- | :--- | :--- | :--- | :--- | | Where | GCE n1-standard-2 | AWS g6.xlarge | laptop | desktop | | GPU memory | 15,360 MiB | 23,034 MiB | 4,096 MiB | 8,192 MiB | | Compute capability | 7.5 | 8.9 | 7.5 | 8.6 | | Host CPU | 2 vCPU | AMD EPYC 7R13 | Intel i7-10750H | Intel i7-5820K | | Driver | 615.71.10 | 595.112.07 | 615.71.10 | 615.78.08 | The same on all four: llama.cpp b11699 with GGML_CUDA=ON, built for each card's own compute capability; the nine GGUFs and Google's QAT Q4_0 file, byte for byte; the 250 questions in data/heldout.jsonl; temperature 0, one request at a time, thinking off, 1,024 max tokens, llama-server -c 4096 -np 1 --seed 0. Every rung runs with -ngl 99 except BF16 and F16 on the GTX 1650 (Step 4). --- At This Point You Should Have… - An NVIDIA GPU with at least 4 GB and a CUDA toolkit - About 58 GB of free disk for the checkpoint, the nine GGUFs and the QAT file - A clone: git clone https://github.com/xbill9/gemma-graveyard - The T4 run's committed results in runs/2026-10-27-ladder/ --- Step 1 — Build llama.cpp at the Same Tag On the RTX 3050: git -C ~/llama.cpp worktree add --detach /android/graveyard/llama.cpp b11699 cd /android/graveyard/llama.cpp cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=86 -DCMAKE_BUILD_TYPE=Release cmake --build build -j12 --target llama-server llama-quantize The one build flag that changes per card is CMAKE_CUDA_ARCHITECTURES: 75 for the T4 and the GTX 1650, 89 for the L4, 86 for the RTX 3050. nvidia-smi --query-gpu=name,memory.total,compute_cap,driver_version --format=csv name, memory.total [MiB], compute_cap, driver_version NVIDIA GeForce RTX 3050 OEM, 8192 MiB, 8.6, 615.78.08 🔎 Tip: keep the pinned build in its own worktree. The desktop already had a newer llama.cpp checkout (b11983). git worktree add --detach <dir> b11699 gives a second working copy at the tag from the same clone, so the measured build and the everyday build sit side by side. --- Step 2 — Rebuild the Ladder and Check It Against the T4 ladder.sh converts the checkpoint to BF16 and F16 and quantizes seven rungs from BF16. Each run script then compares every file's size and sha256 with the T4's ladder.tsv: diff <(cut -f1,3,4 runs/2026-10-27-ladder/ladder.tsv) <(cut -f1,3,4 runs/2026-10-29-3050/ladder.tsv) && echo "ladder matches T4 sha256" ladder matches T4 sha256 The GTX 1650 and the RTX 3050 both print the same line, and Google's QAT Q4_0 file matches too (sha256 fa401b55…). Across the four machines, the GGUF bytes are identical; the card, its driver, its CUDA version and the kernels built for it are what differ. 🔎 Tip: the converter writes the checkpoint directory into the GGUF. convert_hf_to_gguf.py copies the model card and takes the model's name from the directory it reads. A checkpoint taken straight from the Hugging Face cache snapshot has neither the README.md nor the name gemma-4-E2B-it, and gives different bytes. Download with hf download google/gemma-4-E2B-it --local-dir <dir>/gemma-4-E2B-it. --- Step 3 — Prove Each Card Repeats Itself BF16 on 50 questions, twice, on every GPU: ./graveyard.py repeat runs/2026-10-29-3050/determinism-1 runs/2026-10-29-3050/determinism-2 BF16 {"rung": "BF16", "n": 50, "mismatched_extracted": 0, "mismatched_text": 0, "mismatched_right": 0, "right_a": 46, "right_b": 46} The T4, the L4 and the GTX 1650 give the same result: 0 mismatched characters and 46 of 50 right both times. Each card on its own is deterministic, so a difference between two cards comes from the GPU and its CUDA stack. --- Step 4 — Fit the 16-bit Rungs on 4 GB BF16 and F16 need a 4,384.87 MiB GPU buffer for their 36 layers (from the T4's load log), and the GTX 1650 has 4,096 MiB. With -ngl 99 the load fails. graveyard.py run --ngl auto leaves -ngl off, and llama.cpp's fit step places what it can: common_params_fit_impl: - CUDA0 (NVIDIA GeForce GTX 1650 Ti with Max-Q Design): 14 layers, 2546 MiB used, 1125 MiB free load_tensors: offloaded 14/36 layers to GPU load_tensors: CUDA0 model buffer size = 2407.92 MiB Every other rung fits whole. Q8_0 on the same card: load_tensors: offloaded 36/36 layers to GPU load_tensors: CUDA0 model buffer size = 2342.30 MiB The split changes speed and leaves the grades in line with the other GPUs: the GTX 1650's BF16 scores 229, the same as the T4 and the L4, and grades 2 to 4 questions differently from each of them. The 8 GB RTX 3050 holds BF16 whole at 4,651 MiB. --- Step 5 — Run the Ladder on Each Card and Score It Each run script runs the nine rungs and QAT Q4_0, then scores every rung against that card's own BF16: ./graveyard.py bury runs/2026-10-29-3050 BF16 F16 Q8_0 Q6_K Q5_K_M Q4_K_M Q4_0 Q3_K_M Q2_K --json graveyard-ladder.json Right answers, out of 250: | Rung | T4 | L4 | GTX 1650 | RTX 3050 | | :--- | :--- | :--- | :--- | :--- | | BF16 | 229 | 229 | 229 | 232 | | F16 | 229 | 229 | 228 | 232 | | Q8_0 | 227 | 228 | 229 | 226 | | Q6_K | 229 | 227 | 226 | 227 | | Q5_K_M | 231 | 228 | 226 | 226 | | Q4_K_M | 224 | 221 | 222 | 221 | | Q4_0 | 222 | 220 | 216 | 218 | | Q3_K_M | 191 | 186 | 183 | 191 | | Q2_K | 3 | 6 | 5 | 3 | Lost, against the same card's BF16: | Rung | T4 | L4 | GTX 1650 | RTX 3050 | | :--- | :--- | :--- | :--- | :--- | | F16 | 1 | 1 | 2 | 1 | | Q8_0 | 4 | 3 | 2 | 7 | | Q6_K | 4 | 6 | 7 | 8 | | Q5_K_M | 3 | 6 | 6 | 8 | | Q4_K_M | 9 | 14 | 12 | 14 | | Q4_0 | 15 | 16 | 19 | 16 | | Q3_K_M | 42 | 45 | 49 | 44 | | Q2_K | 226 | 225 | 225 | 229 | Lost: right at BF16, wrong at this rung, on the same GPU. The failures fall into the same kinds on every card. Q3_K_M runs out of room: 33 to 36 of its losses on each GPU are answers cut off at 1,024 tokens. Q2_K loops: 208 to 228 of its 250 answers repeat a line five or more times, and 246 to 249 hit the token cap. --- Step 6 — Line the Four GPUs Up, Question by Question docs/gpu4_derived.py rebuilds the T4's grid from its committed results and reads the other three from their rows, then compares all six pairs of GPUs at every rung: python3 docs/gpu4_derived.py > runs/2026-10-29-3050/four-gpus.txt | Rung | Graded differently, any two GPUs | Lost on all four | | :--- | :--- | :--- | | BF16 | 2 to 4 | — | | F16 | 3 to 5 | 0 | | Q8_0 | 3 to 9 | 0 | | Q6_K | 4 to 10 | 2 | | Q5_K_M | 7 to 10 | 1 | | Q4_K_M | 9 to 15 | 5 | | Q4_0 | 12 to 22 | 5 | | Q3_K_M | 26 to 34 | 20 | | Q2_K | 6 to 9 | 216 | Graded differently: right on one GPU and wrong on the other, at that rung, for the closest and the furthest of the six pairs. - The light rungs lose different questions on each card. F16 and Q8_0 lose 1 to 7 answers per GPU, and none of them is lost on all four. - The deep losses are shared. Q3_K_M loses 20 questions on all four GPUs and Q2_K 216. - Most of the graveyard is common ground. Each GPU has 226 questions that die at some rung, the RTX 3050 230. 218 die on all four, and 147 die first at the same rung on all four. One question dies only on the RTX 3050 (test-358) and one only on the GTX 1650 (test-147). - BF16 itself moves a few answers. 227 questions are right at BF16 on all four cards and 18 wrong on all four. Against the L4, the RTX 3050's BF16 changes the text of 31 answers out of 250, the extracted number of 4 and the grade of 3. --- Step 7 — Google's QAT Q4_0 on Four Cards ./graveyard.py bury runs/2026-10-29-3050 BF16 QAT_Q4_0 Q4_0 --json graveyard-qat.json | GPU | QAT Q4_0 right | Q4_0 right | QAT Q4_0 lost | Q4_0 lost | | :--- | :--- | :--- | :--- | :--- | | T4 | 221 | 🥇 222 | 15 | 15 | | L4 | 219 | 🥇 220 | 15 | 16 | | GTX 1650 | 🥇 222 | 216 | 11 | 19 | | RTX 3050 | 🥇 221 | 218 | 15 | 16 | QAT Q4_0 scores 219 to 222 on the four cards and plain Q4_0 216 to 222. On the T4 and the L4 the plain file is ahead by 1; on the GTX 1650 QAT is ahead by 6 and on the RTX 3050 by 3. The two files lose mostly different questions: on the RTX 3050, 5 questions are lost under both, 10 only under QAT and 11 only under the plain Q4_0. Across the four GPUs, 6 of QAT's losses and 5 of Q4_0's are lost on every card. --- Step 8 — Speed on Each Card Median decode speed in tokens per second. Each column is one machine; read down a column. | Rung | T4 | L4 | GTX 1650 | RTX 3050 | | :--- | :--- | :--- | :--- | :--- | | BF16 | 40.0 | 50.1 | 9.2 | 40.3 | | F16 | 39.6 | 50.0 | 9.6 | 40.3 | | Q8_0 | 64.3 | 83.0 | 45.9 | 67.8 | | Q6_K | 70.8 | 98.1 | 57.2 | 75.3 | | Q5_K_M | 78.7 | 107.7 | 61.9 | 82.2 | | Q4_K_M | 84.1 | 118.7 | 66.5 | 91.5 | | Q4_0 | 🥇 93.6 | 125.6 | 🥇 70.8 | 🥇 99.7 | | Q3_K_M | 74.2 | 111.1 | 55.9 | 64.2 | | Q2_K | 74.0 | 🥇 139.5 | 62.2 | 95.0 | - Q4_0 is the fastest rung on three of the four cards; on the L4 it is Q2_K, which is wrong on 244 of 250 questions. - Q5_K_M decodes 1.21 to 1.35 times as fast as Q8_0 on each card (arithmetic, from the table). Q8_0 is the baseline here because it is the largest rung that fits whole on all four GPUs. - Q3_K_M is slower than Q4_0 on every card, and on the RTX 3050 it is slower than Q8_0. - BF16 and F16 run at the same speed on the T4, the L4 and the RTX 3050. On the GTX 1650, with 22 of 36 layers on the CPU, they run at a fifth of Q8_0's speed. The full ladder with QAT took 5 hours 16 minutes on the RTX 3050 and 12 hours 25 minutes on the GTX 1650, from the first log line to the last (run-3050.log, run-1650.log). --- Compare and Contrast | | T4 | L4 | GTX 1650 | RTX 3050 | | :--- | :--- | :--- | :--- | :--- | | BF16 right of 250 | 229 | 229 | 229 | 🥇 232 | | Q5_K_M lost | 🥇 3 | 6 | 6 | 8 | | Q4_0 right, tok/s | 222, 93.6 | 220, 125.6 | 216, 70.8 | 218, 99.7 | | Every rung whole on the GPU | 🟢 yes | 🟢 yes | ❌ BF16, F16 split | 🟢 yes | | Same text twice at BF16 | 🟢 yes | 🟢 yes | 🟢 yes | 🟢 yes | --- So, Which One? On any of the four cards, take Q5_K_M: 39.0% of BF16's bytes, 226 to 231 right, 3 to 8 answers lost against the card's own BF16, and 1.21 to 1.35 times Q8_0's decode speed. For the fastest file that stays within 19 answers, take Q4_0, the fastest rung on three of the cards. On a 4 GB card, every quantized rung fits whole and the 16-bit rungs run at under 10 tokens per second. To compare two quantizations question by question, run both on one GPU: a different card moves 2 to 4 grades at BF16 and up to 34 at Q3_K_M. --- Summary The goal of this article was to run Gemma 4 E2B's quantization ladder on four GPUs, from a 4 GB laptop card to a data-centre L4, and see which grades hold. The key was holding everything else fixed: byte-identical GGUFs, the same llama.cpp tag and the same sampling, with each card checked to give identical text twice. The four-GPU results were: - 🟢 All nine GGUFs and the QAT file match the T4's sha256 on every machine, and BF16 gives identical text twice on 50 questions on every card. - 🟢 The ladder keeps its shape on all four GPUs: F16 to Q5_K_M lose 1 to 8, Q4_K_M and Q4_0 lose 9 to 19, Q3_K_M loses 42 to 49. - 🟢 Q5_K_M is the best trade on every card: 226 to 231 right at 1.21 to 1.35 times Q8_0's speed. - 🟢 A 4 GB GTX 1650 runs every quantized rung whole on the GPU; BF16 and F16 run split, 14 of 36 layers on the GPU, at 9.2 and 9.6 tokens per second. - ⚠️ Any two GPUs grade 2 to 4 questions differently at BF16 and 12 to 22 at Q4_0; no question is lost at F16 or Q8_0 on all four. - ⚠️ 218 questions die on all four GPUs, 147 of them first at the same rung. - ⚠️ QAT Q4_0 and plain Q4_0 trade places by card: plain ahead by 1 on the T4 and L4, QAT ahead by 6 on the GTX 1650 and by 3 on the RTX 3050. - ❌ Q3_K_M loses 42 to 49, mostly answers cut off at 1,024 tokens; Q2_K gets 3 to 6 right, with 208 to 228 looping answers per card. Scope: one run of each rung per GPU on four NVIDIA cards (a Tesla T4 on GCE, an L4 on AWS, a GTX 1650 Ti laptop GPU and an RTX 3050 desktop card), 250 GSM8K test questions, greedy, thinking off, 1,024 max tokens, llama.cpp b11699 built for each card. Determinism checked at BF16 on each card. The T4 grid is rebuilt from its committed per-question results. The hosts also differ in CPU, RAM, driver and CUDA version, and on the GTX 1650 BF16 and F16 run split between GPU and CPU. Speed is compared within each card only. The strategy for measuring Gemma 4's accuracy one quantization rung at a time on four GPUs was validated with an incremental step by step approach. --- References - Accuracy vs Speed: Gemma 4 E2B Quantization Tradeoffs, BF16 to Q2_K, the T4 article - Performance vs Quality: Gemma 4 E2B Quantization on an NVIDIA L4 and a Tesla T4, the L4 article - gemma-graveyard on GitHub - GTX 1650 run: runs/2026-10-29-1650 - RTX 3050 run: runs/2026-10-29-3050 - llama.cpp - google/gemma-4-E2B-it - google/gemma-4-E2B-it-qat-q4_0-gguf - GSM8K