GLM API 撞限流怎么办:并发和配额是两回事
数据截至 2026-07,价格与限额以各官网为准。
GLM API 撞限流时,第一件该做的事不是打开充值页面,而是先分清楚你撞到的到底是”同一时刻发出去的请求太多”,还是”账上可用的量真的没了”。这两件事的解法完全相反:前者只需要在客户端把并发压下来、加一段退避重试,不用多花一分钱;后者才涉及账户和计费。分不清就会出现”余额明明还在,为什么一直被挡”的困惑,然后把时间浪费在翻账单上。
还有一个更容易忽略的分岔:智谱这边除了常规的按量计费,还有一套面向编程场景的订阅套餐,官方是按”每 5 小时 / 每周”的额度窗口来限制的,跟按 token 计费不是同一套账。如果你用的是套餐额度,跑到窗口上限时的表现,跟并发被挡看起来很像,但处理方式又完全不同。这篇就把这几层一次性拆开。
先把这个误解掰过来:被挡不等于”没额度了”
新手最常见的反应链条是这样的:请求失败 → 看到限流相关的提示 → 认定是免费额度用光了 → 去查赠送 token 还剩多少 → 发现还剩很多 → 更困惑。
问题出在把”限流”和”配额”当成了一件事。它们描述的是完全不同的两个维度:
- 并发 / 速率是”单位时间内允许你发多少”,是一个流量阀门。它跟你账上有多少量无关,哪怕你余额充足、甚至用的是完全免费的模型,照样会被挡。
- 配额 / 余额是”你总共还能用多少”,是一个水位。它跟你此刻发得快不快无关,慢慢发一样会用完。
一个直观的类比:并发像高速路的车道数,配额像油箱里的油。车道堵住了,加油没用;油没了,车道再空也开不动。
这里还有一条被写进官方文档、但很多人没注意的事实:智谱的 Flash 系列文本模型是长期免费档,官方文档明确标注免费,不依赖”新用户赠送额度”也能零成本调用——但免费不等于不受限,它仍然受并发与速率限制约束。也就是说,你用免费模型跑批量任务,撞墙的概率反而更高,因为你完全没有”余额”这个概念可以去怀疑,只剩流量阀门这一种可能。
第一步:先确认自己撞的是哪一种
在改代码之前,花两分钟做三个判断,能省掉后面大半的返工。
判断一:降低发送速度能不能缓解。 把你的脚本改成串行、每次请求之间 sleep 一两秒,再跑十几次。如果全部成功,那就是流量阀门问题,跟账户无关,往下看第二步就行。如果串行照样被挡,那才需要怀疑账户层面。
判断二:换一个模型试试。 如果你原本调的是付费型号,换成免费的 glm-4.7-flash 发一条最简单的请求。免费档能通、付费型号不通,那大概率是付费侧的余额或权限问题;两边都不通,问题更可能在你的账户状态或者网络链路上。
判断三:把完整的响应体打出来,别只看状态码。 很多人只 print 了异常类型就开始猜。服务端返回的 body 里通常带有更具体的错误描述,是并发超限、余额不足、还是权限不足,往往一句话就写清楚了。养成把原始响应完整记进日志的习惯,比任何猜测都管用。这一层的通用排查顺序,我在 GLM API 常见报错排查:密钥、模型名与配额 里按”从外往里剥”的顺序写过一遍,这篇不重复。
第二步:用信号量把在途请求数摁住
确认是并发问题之后,客户端要做的事其实只有一件:限制”同一时刻已经发出去但还没回来”的请求数量。
注意这个定义。很多人写并发控制时限制的是”每秒发起多少个”,这在响应时间波动大的场景下会失效——如果服务端某一刻变慢,你每秒仍然按固定节奏往外发,在途请求就会越堆越多。用信号量直接卡住途中请求数,是更稳的做法。
Python 里同步版本用线程池加信号量:
import os
import threading
from concurrent.futures import ThreadPoolExecutor
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("ZAI_API_KEY"),
base_url="https://open.bigmodel.cn/api/paas/v4/",
)
# 这个数字要靠实测确定,不要照抄别人的
MAX_INFLIGHT = 4
sem = threading.Semaphore(MAX_INFLIGHT)
def ask(prompt: str) -> str:
with sem: # 拿不到令牌就在这里阻塞,天然把在途数量卡死
resp = client.chat.completions.create(
model="glm-4.7-flash",
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
with ThreadPoolExecutor(max_workers=16) as pool:
results = list(pool.map(ask, prompts))
异步版本同理,把 threading.Semaphore 换成 asyncio.Semaphore,with 换成 async with 即可。
关键在于 MAX_INFLIGHT 怎么定。这里必须诚实说一句:智谱官方文档页面上并没有直接展示各模型的每分钟请求数、每分钟 token 数这类具体数字,我也不打算凭印象编一个给你。可行的做法是实测标定:
- 从一个保守值起步,比如 2。
- 跑一批固定数量的请求(比如 50 条),记录成功率。
- 全绿就把并发加 1,再跑一轮。
- 一旦出现被挡,退回上一档,再往下减 1 作为安全边界。
标定一次大概十几分钟,得到的数字比任何教程里抄来的都可靠——因为它反映的是你这个账户、这个模型、这个时段的真实情况。账户等级、模型型号、时段负载都会影响结果,别指望有一个放之四海皆准的常数。当前控制台里如果有展示你账户对应的限速信息,以那里显示的为准。
第三步:退避重试怎么写才不添乱
并发压住之后,偶发的被挡还是会有。这时候需要重试,但重试写错了会让情况更糟——固定间隔的重试会让一批同时失败的请求在同一时刻再次同时冲上去,形成”惊群”,反而把阀门撞得更死。
正确的写法有三个要素:指数增长、加随机抖动、设上限。
import random
import time
def ask_with_retry(prompt: str, max_retry: int = 5) -> str:
delay = 1.0
for attempt in range(max_retry):
try:
return ask(prompt)
except Exception as e:
if not is_rate_limited(e) or attempt == max_retry - 1:
raise # 不是限流,或者已经是最后一次,直接抛出去
# 指数退避 + 抖动,避免所有失败请求同时重来
time.sleep(delay + random.uniform(0, delay * 0.5))
delay = min(delay * 2, 30.0)
raise RuntimeError("unreachable")
有几个细节值得展开:
- 只对限流类错误重试。 参数写错、模型名不存在、密钥无效这些错误,重试一百次也还是错。把判断条件写清楚,让其它错误立刻抛出来,否则你会在日志里看到一个错误被安静地重试五轮,浪费三十秒才暴露。
- 抖动不能省。 上面代码里那个
random.uniform看着不起眼,但在几十个并发一起失败时,它是防止二次踩踏的关键。 - 上限要设。 退避间隔无限翻倍会让最后一次重试等上好几分钟,用户侧早就超时了。封顶到几十秒是合理的。
- 如果响应里带了建议等待时间,优先听服务端的。 有些服务会在响应头里给出重试前应当等待多久,能读到就按它来,比你自己猜的间隔准确。读不到再退回本地的指数退避。
顺手能降压的几个做法
除了硬压并发,还有几条路能让你在同样的阀门下办更多事。
用免费 Flash 分流简单任务。 官方文档标为免费的文本模型里,glm-4.7-flash 上下文 200K、最大输出 128K,glm-4-flash-250414 上下文 128K(官方原文称其为智谱首个免费大模型 API)。如果你的流水线里有一半是分类、抽字段、格式化这类不需要强推理的活儿,把它们切到免费档,付费型号的压力自然就下来了,成本也顺带省掉。哪些场景免费档能扛住,我在 GLM 有哪些免费模型?能用到什么程度 里展开过。
顺带提醒一个坑:早先的 glm-4.5-flash 已于 2026-01-30 下线,请求会自动路由到 glm-4.7-flash。所以你如果发现日志里模型名和实际行为对不上,先看看是不是撞上了这个自动路由,而不是自己代码有 bug。
离线任务走批量接口。 官方对 Batch API 的说法是”只需五折费用”,用于大规模数据处理。批量接口通常有独立的处理通道,把不着急要结果的活儿(历史数据清洗、离线打标、批量摘要)挪过去,既避开了实时接口的阀门,又直接省一半钱。判断标准很简单:这个结果是不是必须在几秒内返回给某个人看?不是的话,就适合走批量。
减少请求条数,而不是减少每条的大小。 阀门卡的往往是请求个数这个维度。把十条独立的短请求合并成一条包含十个条目的请求,总 token 量差不多,但请求数只有原来的十分之一。对批量分类、批量抽取这类任务效果特别明显。当然合并有上限,别把一次请求塞到超出模型的上下文窗口或最大输出,那会换来另一类错误。
错峰。 如果任务本身不赶时间,把它挪到夜间或业务低谷跑,实测能通过的并发常常比高峰时段高一截。这条没有官方数字支撑,纯属工程经验,但试一次成本很低。
流式不减压。 有人以为把 stream=True 打开就能缓解限流,这是误解。流式改变的是结果怎么返回给你,首字延迟体感更好,但服务端要处理的请求数和 token 数一点没少。它解决体验问题,不解决阀门问题。
订阅套餐额度用完,长得跟限流很像
这是智谱这边比较特殊、也最容易让人误判的一层。
智谱除了按 token 计费,还有面向编程场景的订阅套餐体系(不同档位分级),官方的额度限制口径是按”每 5 小时 / 每周”这样的时间窗口来算,而不是纯粹按 token 消耗计费。这意味着:
- 你可能在某个 5 小时窗口内把额度用完了,接下来一段时间发什么都不通,但下一个窗口开始时又自动恢复。这个”过一会儿自己好了”的表现,跟并发限流恢复的表现几乎一模一样,光看现象根本分不出来。
- 套餐额度和按量计费余额是两本账。你在按量计费那边充了钱,不会让套餐窗口的额度变多;反过来也一样。
- 具体的套餐档位、价格和额度换算,网上流传的版本很多,我不在这里给具体数字——这类信息调整频率高,以官方套餐页面当次展示的为准。
判断方法:如果你的失败是成片出现、持续一段时间、然后整体恢复,而且降低并发完全没有改善,那更像是窗口额度打满,而不是并发被挡。这时候改代码是没用的,要么等窗口重置,要么去看套餐本身。计费口径的完整拆解可以参考 智谱 GLM API 怎么收费?各模型 token 单价与省钱用法。
什么情况下这些办法不管用
诚实说几条局限,免得你按着上面做完还是不通,反过来怀疑自己。
账户本身有问题时,压并发没有意义。 实名、绑卡、权限这类状态不对,表现出来也可能是请求持续失败。判断依据是:串行发一条最简单的请求也不通。这时候该去控制台看账户状态,不是去调信号量。
客户端限流管不住多个进程。 上面的信号量只在单个进程内生效。如果你同时跑了三个脚本、或者服务部署了多个实例,各自的信号量互不知情,加起来的实际并发是三倍。多实例场景需要在网关层或用共享的计数器(比如 Redis)做全局限流,单机方案会失效。
限速数值会变。 你今天标定出来的并发数,可能过一段时间就不适用了——账户等级变化、平台调整策略、模型换代都会影响。把这个值做成配置项而不是写死在代码里,出问题时改配置比改代码快得多。
别把重试当成万能兜底。 重试能吃掉偶发抖动,吃不掉系统性的容量不足。如果你的日志里显示重试率长期在两三成以上,说明并发设得太激进了,该往下调,而不是把 max_retry 从 5 改成 20。
这篇不替官方给具体数字。 前面反复说了,智谱的模型迭代和策略调整都比较快,官方定价与限额页面是前端渲染的,具体的每分钟请求数、每分钟 token 数、套餐额度换算,以你登录控制台当时看到的页面和官方文档为准。这篇给的是判断方法和工程手段,这两样比数字保值。
小结
第一,被限流和额度用完是两件事,前者是流量阀门、后者是水位,别一被挡就去充值。第二,确认方法很朴素:串行重跑一遍,能通就是并发问题,不能通才去查账户。第三,客户端解法是信号量卡在途请求数加指数退避重试,并发值靠实测标定,不要照抄任何教程里的常数。第四,免费 Flash 分流、批量接口走五折、合并请求减少条数、错峰,这四招能在同样的阀门下多干不少活。第五,智谱这边多一层订阅套餐的窗口额度,它的表现跟限流很像但解法完全不同,判断依据是”降并发有没有用”。