BigQuery 的 AI token 配额正式可用:每项目每天 2000 亿输入、200 亿输出
💡 工具推荐: SQL 格式化, CSV 转 JSON, JSON 格式化, 字数统计
BigQuery 里的生成式 AI 函数有一个好描述、难感知的成本问题:一条 SELECT 可以扇出成几百万次 LLM 调用,而写 SQL 的训练里没有任何一环教你去注意这件事。Google 的方案在 2026 年 10 月 15 日正式可用:项目级与单用户级的每日 token 配额,覆盖输入与输出 token,作用于 AI.GENERATE、AI.GENERATE_TABLE、AI.GENERATE_TEXT、AI.CLASSIFY、AI.IF、AI.AGG 与 AI.SCORE。默认值大得让大多数团队永远碰不到——而这恰恰说明,值得看的不是那个数字,而是你真撞上去时会发生什么,以及一份「处理了一半」的结果集,落在一张已经被其他查询依赖的表里是什么样子。
只活在控制台里的配额,是一个没人复核的数字。
这次正式开放的是什么
这套功能是一组每日配额,限制 BigQuery 中生成式 AI 函数消耗的输入与输出 token 数量,在 IAM & Admin 的「配额与系统限制」里管理。工作负载需要更多容量时可以上调,也可以用 override 把用量压得比默认更紧。两个方向都重要:默认值保护你不被一条失控的查询拖走账单,更严格的 override 保护一个共享项目不被某个团队的实验占满。
覆盖范围很具体。AI.GENERATE、AI.GENERATE_TABLE、AI.GENERATE_TEXT、AI.CLASSIFY、AI.IF、AI.AGG、AI.SCORE 受配额约束。向量生成与语义搜索类函数,例如 AI.EMBED、ML.GENERATE_EMBEDDING,不受约束,因为它们跑在不同的、便宜得多的模型上,计费结构也不一样。
-- Estimate before you spend. AI.COUNT_TOKENS prices a dry run instead of a surprise.
SELECT
COUNT(*) AS rows_to_process,
SUM(tok.total_tokens) AS estimated_tokens,
SUM(tok.prompt_tokens) AS estimated_input,
SUM(tok.candidates_tokens) AS estimated_output
FROM (
SELECT
review_id,
AI.COUNT_TOKENS(
'gemini-2.5-flash',
CONCAT('Classify this review as positive, negative or mixed:\n', review_text)
) AS tok
FROM `project.dataset.reviews`
WHERE processed_at IS NULL
LIMIT 1000
)
-- Multiply the per-1000-row average by your real row count, then compare against
-- the quota table: 200,000,000,000 input and 20,000,000,000 output tokens per day per project.
默认值换算成你能推理的单位
输入 token:每项目每天 2000 亿,每项目每用户每天 1500 亿。输出 token:每项目每天 200 亿,每项目每用户每天 150 亿。输出里包含内部推理阶段产生的思考 token,这个细节会让朴素的估算彻底失效——推理型 prompt 的可计费输出,可能远多于你看到的答案本身。
还有两个细节值得记。缓存命中的 token 不计入配额。配额以百万 token 为单位累积统计,所以虽然你可以设很精确的值,低于几百万的量级可能反映得并不完全准确。配额对整个项目的所有区域全局生效(成本控制本来就该这样),每日在太平洋时间午夜重置。
# Budget arithmetic in Python, because the quota units are easy to misread.
QUOTAS = {
"input_per_project_per_day": 200_000_000_000,
"input_per_user_per_day": 150_000_000_000,
"output_per_project_per_day": 20_000_000_000,
"output_per_user_per_day": 15_000_000_000,
}
def plan(rows, avg_input, avg_output, users=10, batch_size=10_000):
in_total = rows * avg_input
out_total = rows * avg_output
batches = -(-rows // batch_size)
return {
"rows": rows,
"input_tokens": f"{in_total:,}",
"output_tokens": f"{out_total:,}",
"input_pct_of_project_quota": f"{in_total / QUOTAS['input_per_project_per_day']:.1%}",
"output_pct_of_project_quota": f"{out_total / QUOTAS['output_per_project_per_day']:.1%}",
"output_pct_of_user_quota": f"{out_total / users / QUOTAS['output_per_user_per_day']:.1%}",
"batches": batches,
}
# 5 million rows, 700 input tokens and 90 output tokens each
print(plan(5_000_000, 700, 90))
# Output is usually the binding constraint: 450M output tokens is 2.25% of the project cap
# but only 15M of headroom per user on a shared service account is a different story.
执行行为才是出意外的地方
BigQuery 在执行前先查配额。如果相关配额已经耗尽,查询会以 QuotaExceeded 被直接拒绝,什么都不跑。这是好情况。
难的是运行中途耗尽。当一条正在跑的查询消耗 token 到把某个配额打满时,这条查询里新的 LLM 调用会被拒绝,其余依赖这些调用的行会遇到配额耗尽错误。对 AI.IF 来说,如果你用了 max_error_ratio,只要错误比例还在允许范围内,仍可能返回部分结果;否则整条查询失败。对 AI.GENERATE,作业可以跑完,但没能处理的行会返回空的生成结果,并在 status 字段里带一条错误信息。
最后这种行为在生产里最危险,因为它「软失败」。一条下游查询去对分类列求和,会顺利地把成功的那些行聚起来,然后静默地少算其余部分。而同一项目里后续的生成式 AI 查询,在每日重置之前都会以 QuotaExceeded 失败——一个糟糕的下午很容易变成一个糟糕的整天。
# Mid-query exhaustion is not an all-or-nothing failure. Handle the partial result.
from google.cloud import bigquery
QUERY = '''
SELECT
review_id,
AI.GENERATE(
('Classify the sentiment', review_text),
connection_id => 'us.conn',
endpoint => 'gemini-2.5-flash',
output_schema => 'sentiment STRING, confidence FLOAT64'
) AS result
FROM `project.dataset.reviews`
WHERE processed_at IS NULL
'''
def run_with_partial_handling(client, query):
job = client.query(query) # rejected up front with QuotaExceeded if exhausted
rows = list(job.result())
ok = [r for r in rows if r["result"] and not r["result"].get("error")]
failed = [r for r in rows if not (r["result"] and not r["result"].get("error"))]
return {"ok": len(ok), "failed": len(failed), "job_id": job.job_id}
# AI.IF can return partial results when max_error_ratio is used; AI.GENERATE completes
# the job but leaves an error message on the rows it could not process.
输出配额比输入配额小两个数量级。猜猜哪个先顶到。
先估再跑,而不是跑完再算
最便宜的习惯是在样本上做一次「干跑」。AI.COUNT_TOKENS 能给出指定模型与 prompt 下的 prompt token 与候选 token 数量,于是你可以先给一千行定价、再乘上去,然后拿去和配额表对照,最后才碰全表。同一个函数还能用来做 prompt 优化:在每行 700 token 的情况下,把 prompt 砍掉 120 token,输入成本就降了 17%。
实践里,输出比输入更常成为瓶颈,因为默认输出配额小了两个数量级。如果你的负载是分类,这没问题,输出很短;但如果涉及推理模型输出带解释的结构化 JSON,200 亿输出 token 消耗起来会比你预期的快。
# Quotas can be raised, lowered or overridden. Do it as code so the value is reviewable.
import subprocess, json
def current_quota(project, quota_id="GenAiInputTokensPerDay"):
out = subprocess.run(
["gcloud", "beta", "quotas", "info", "list",
"--service=bigquery.googleapis.com", "--project", project, "--format=json"],
capture_output=True, text=True, check=True,
).stdout
for q in json.loads(out):
if quota_id and quota_id in q.get("name", ""):
return q
return None
def desired_limits(monthly_rows, avg_input, avg_output, working_days=21):
per_day_in = monthly_rows / working_days * avg_input
per_day_out = monthly_rows / working_days * avg_output
return {
"GenAiInputTokensPerDay": int(per_day_in * 1.5),
"GenAiOutputTokensPerDay": int(per_day_out * 1.5),
}
print(desired_limits(3_000_000, 700, 90)) # headroom of 50% over expected daily usage
# Apply through the Cloud Quotas API, gcloud, or Terraform so the override is in git.
把配额当配置管,而不是控制台里的一个开关
只存在于控制台里的配额值,是一个没人复核的数字。Cloud Quotas API、gcloud 和 Terraform 都能把这些值当代码来管,意味着每次改动都会落进一个带理由的 pull request。这点接线成本值得付,因为配额改动往往发生在最糟的时刻——通常是在一次故障当中。
文档里还有一条提醒:如果你用的是带预置吞吐(provisioned throughput)的 Gemini Enterprise Agent Platform 模型,计费根本不由 token 用量决定。这种情况下应把 BigQuery 的 token 配额设得很高,否则一套为按 token 计费设计的配额,会把已经付过钱的查询挡在门外。
# Fail loudly when a job is about to sit on the quota instead of quietly burning it.
from dataclasses import dataclass
@dataclass
class Guardrail:
name: str
limit: int
used: int
@property
def ratio(self):
return self.used / self.limit if self.limit else 0.0
def preflight(guardrails, warn=0.7, block=0.95):
findings = []
for g in guardrails:
status = "block" if g.ratio >= block else "warn" if g.ratio >= warn else "ok"
findings.append({"guardrail": g.name, "used_pct": round(g.ratio * 100, 1), "status": status})
return findings
print(preflight([
Guardrail("input_per_project", 200_000_000_000, 46_000_000_000),
Guardrail("output_per_project", 20_000_000_000, 19_200_000_000),
Guardrail("output_per_user", 15_000_000_000, 4_100_000_000),
]))
# [(input_per_project, 23.0, ok), (output_per_project, 96.0, block), (output_per_user, 27.3, ok)]
# Output is the one that runs out first in almost every pipeline I have seen.
配额按项目全局统计,每日太平洋时间午夜重置。
我真正会装上的几道护栏
第一,预检:当某个护栏超过 95% 时让作业直接失败,而不是让它去试着跑、然后产出部分结果。第二,按用户或按服务账号设上限,作为爆炸半径控制,这样一个 notebook 吃不掉整个共享项目当天的输出预算。第三,跑完之后加一条断言,数一下未处理的行数,只要不为零就拒绝把结果写进下游。
这些都不新奇。这就是你对待一个有限流的第三方 API 时会有的纪律,只是最常缺失在那些「感觉像 SQL」的工具里。SQL 给人的印象是要么成功要么报错;而 SQL 里的生成式 AI 函数,是这套环境里第一个会「半成功」的东西——所以这笔账必须写明白。
📌 常见问题 FAQ
Google 这次正式开放的到底是什么?
BigQuery 生成式 AI 函数的每日 token 配额,覆盖项目级与单用户级的输入、输出 token,可在 IAM & Admin 的「配额与系统限制」里配置,也可以通过 API 或 IaC 管理。
默认配额值是多少?
每项目每天 2000 亿输入 token、每用户 1500 亿;每项目每天 200 亿输出 token、每用户 150 亿。输出包含思考 token,缓存命中的 token 不计入。
配额覆盖哪些函数?
AI.GENERATE、AI.GENERATE_TABLE、AI.GENERATE_TEXT、AI.CLASSIFY、AI.IF、AI.AGG、AI.SCORE。向量生成与语义搜索类函数如 AI.EMBED、ML.GENERATE_EMBEDDING 不在此列,因为它们的计费结构不同。
查询执行中途配额用尽会怎样?
该查询内新的 LLM 调用会被拒绝,剩下的行报错。AI.IF 在 max_error_ratio 允许范围内仍可能返回部分结果;AI.GENERATE 可能跑完作业但对未处理的行留下错误信息。之后同一项目的相关查询在每日重置前都会以 QuotaExceeded 失败。
大作业开跑之前怎么估成本?
用 AI.COUNT_TOKENS 在样本行上拿到所选模型的 prompt 与候选 token 数,乘以真实行数,再对照配额表。这相当于给那件无法撤销的事情做一次干跑。