<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[厂商不给 rate limit 文档, 我把它逼了出来: 阿里云 Token Plan 的三堵墙; 对跑多 agent 很重要]]></title><description><![CDATA[<h2>背景</h2>
<p dir="auto">前两天 qwen3.8-max 发布,想试试, 顺带发现阿里的 Token Plan 里正好也有我长期在用的 <code>deepseek-v4-flash</code>, 就去翻了三档套餐(Lite / Standard / Pro)的额度和参数。</p>
<p dir="auto">结果卡在一个含糊的地方: 并发那一栏只写了「支持 Agent 并发 Lite 1–2、Standard 3–4、Pro 6–8」,却没说这个数字到底是什么意思。是 RPS?是同时在飞的请求数?还是别的什么口径? 搜了一圈, 没有一个靠谱的答案。</p>
<p dir="auto">而并发恰恰是日常折腾里(尤其多agent时)最先撞上的那堵墙 —— 额度是慢慢烧掉的, 并发是当场就拒的。所以在买大包之前得先把它弄明白:<strong>先买一个 Lite 回来,把它测穿。</strong> 测完一想,反正都干了,不如让 AI 顺手总结一篇发出来, 给大伙乐呵乐呵。</p>
<p dir="auto">结论是三个数字,和一条比数字更重要的发现:<strong>同一个 HTTP 429,两种完全不同的报错,指向两堵完全不同的墙 —— 其中一条会把你骗去查额度面板,而额度根本没事。</strong></p>
<blockquote>
<p dir="auto">我注册不了国服,测的是新加坡(International)的 Lite 档; 国服的限制应该一致(而且还便宜一点点),但我没有验证过。</p>
<p dir="auto">题外话:<strong>另外一点有意义的是</strong>, 这次测试是 <code>Opus 5</code> 做 plan、<code>deepseek-v4-flash</code> 做 worker 配合完成的, 目测工作量三七开 —— 结果我完全满意。</p>
<p dir="auto"><code>deepseek-v4-flash-0731</code> 出来之后,我的主观感受是:个别场景能追平 Opus 5,日常足以平替 Sonnet 5,对 Haiku 4.5 则是完爆。所以最近我一直在日常使用里刻意观察它<strong>真正干活时</strong>的表现,而不是看榜单。</p>
<p dir="auto">现实里还有另一条线。很多公司出于合规不允许用国产模型;但最近陆续听身边朋友说,他们公司「烧不起 Claude 了」,开始限额,有的甚至在逐步放开对国产模型的限制 —— 理由很直接:省 token 开销。</p>
<p dir="auto">我自己的分工是:<strong>正经干活还是只用 Claude,日常折腾和自己的东西(尤其是常驻的 agent)早就大部分切到 v4-flash 了。</strong> 而现在的感觉是,「正经活」也可以分一部分出去了。</p>
</blockquote>
<hr />
<h2>TL;DR</h2>
<p dir="auto">测试对象:阿里云 <strong>Token Plan 个人版 Lite 档 1-2 Agent 并发</strong>, OpenAI 兼容端点, 模型 <code>deepseek-v4-flash-0731</code>。</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>限制</th>
<th>实测阈值</th>
<th>报错</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>并发数(同时在飞的请求)</strong></td>
<td><strong>12 个</strong></td>
<td><code>429</code> · <code>API-Key Requests rate limit exceeded</code> · <code>type: limit_requests</code></td>
</tr>
<tr>
<td><strong>请求速率(QPS)</strong></td>
<td><strong>没测到上限</strong></td>
<td>—</td>
</tr>
<tr>
<td><strong>token 桶容量</strong></td>
<td><strong>约 1,219,000 token</strong></td>
<td><code>429</code> · <code>Allocated quota exceeded, please increase your quota limit</code></td>
</tr>
<tr>
<td>5h / 7d credits 预算</td>
<td>Lite:700 / 5h · 2500 / 7 天</td>
<td>与上一行 <code>429</code> <strong>相同</strong>(官方 FAQ)</td>
</tr>
</tbody>
</table>
<blockquote>
<p dir="auto"><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=138704eccfe" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ <strong>第三行是「桶容量」,不是 TPM。</strong> 它的定义是<strong>排空后一口气能推进去的 token 总量</strong> —— <strong>存量上限,不是速率</strong>。实测撞墙发生在 <strong>32 秒</strong>内,所以它回答的是「一口气能推多少」,不是「每分钟能推多少」。<strong>稳态速率这套测试没测</strong>(见 §10 末尾和 §13)。</p>
</blockquote>
<p dir="auto">其他两档 (Standard, Pro) 可以类推. 知道了并发限制后, 具体 token 需求多少 大家根据自己的使用情况决定什么档位合适. 粗糙估算, 套餐如果合理打满, 用量应该是 单买token 的 3-6x ( 具体情况要看自己的使用场景, in, out, cache hit 的比例等等)</p>
<p dir="auto">三个反直觉的点:</p>
<ol>
<li><strong>官方标称 Lite「支持 1–2 个并发 agent」,实测 HTTP 层能同时跑 12 个。</strong> <code>1-2</code> 那个数字不是闸。</li>
<li><code>**Allocated quota exceeded</code> 不等于额度用光。** 触发它的那一刻,控制台显示 5 小时窗口只用了 27.37%。</li>
<li><strong>它也不是「关门一分钟」。</strong> 被拦之后 1 秒内发一个小请求, 直接 200。</li>
</ol>
<hr />
<h2>1. 为什么要测</h2>
<p dir="auto">包月 LLM 套餐的宣传页通常只给两类数字:每月多少额度、支持几个「并发 agent」。前者是账单口径,后者是营销口径 —— <strong>两者都不是你写代码时需要的那个数</strong>。</p>
<p dir="auto">我要知道的是:同时发多少个请求会被拒?一分钟能推多少 token?被拒之后多久恢复?这些数字决定了并发编排怎么写、重试策略怎么定、以及最终那个问题 —— <strong>要不要升档</strong>。</p>
<p dir="auto">官方文档没有。响应头里也没有(见 §4)。那就只能撞。</p>
<h2>2. <img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=138704eccfe" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ 先说一个错误做法</h2>
<p dir="auto">最自然的想法是:开 N 个 agent 会话,看第几个报错。</p>
<p dir="auto"><strong>这个探针是坏的</strong>,四个理由:</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>问题</th>
<th>后果</th>
</tr>
</thead>
<tbody>
<tr>
<td>agent 会话要加载 system prompt + tool definitions + 历史,<strong>单次输入 40–80k token</strong></td>
<td>撞的可能是 token 桶容量,不是并发数上限 —— <strong>但报错长得一样</strong></td>
</tr>
<tr>
<td>本地同时起 N 个 agent 进程</td>
<td>VM客户端 CPU 先饱和,你测的瓶颈是自己的机器 (我的 hermes agents 都是跑在 PVE VM上的)</td>
</tr>
<tr>
<td>几乎所有 SDK 和 agent 框架自带重试 + 退避</td>
<td><strong>429 被吞掉重试掉,「第几个失败」的观测直接失真</strong></td>
</tr>
<tr>
<td>每次会话有真实工具调用和长输出</td>
<td>慢、贵、不可重复</td>
</tr>
</tbody>
</table>
<p dir="auto"><strong>一条报错对应多个真因时,先用一个绕开被测系统的最小实验把变量砍掉。</strong> 所以全程用裸 HTTP 请求当探针,agent 框架完全不参与。</p>
<h2>3. 实验设计</h2>
<p dir="auto">四个互相独立的实验,分别对准一堵墙:</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>实验</th>
<th>怎么做</th>
<th>判据</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>并发梯度</strong></td>
<td>同时发 N 个,N 逐级上升</td>
<td>成功数卡在固定值 → 在飞数上限</td>
</tr>
<tr>
<td><strong>串行连发</strong></td>
<td>一个接一个,全程只有 1 个在飞</td>
<td>这里也失败 → 撞的是速率不是并发</td>
</tr>
<tr>
<td><strong>满并发多波</strong></td>
<td>打满并发,波次之间不停歇</td>
<td>中途开始挂 → 存在每分钟请求数窗口</td>
</tr>
<tr>
<td><strong>大 payload 满载</strong></td>
<td>每请求几万 token,数到被拦为止</td>
<td>累计 token 数 = 桶容量</td>
</tr>
</tbody>
</table>
<p dir="auto">三个设计细节,每一个都直接决定数据是否可信:</p>
<p dir="auto"><strong>① 发射抖动必须是毫秒级(0–300ms)。</strong><br />
单请求耗时以秒计,所以 0–300ms 的抖动下 N 个请求<strong>仍然全部同时在飞</strong>,并发梯度成立;同时避开「N 个 TCP 连接在同一毫秒握手」这种人造的 thundering herd。<strong>窗口一旦开到秒级,请求就不重叠了,测出来的会变成 QPS 而不是并发。</strong></p>
<p dir="auto"><strong>② <code>max_tokens</code> 不能设太小。</strong><br />
设成 16 的话请求几百毫秒就结束,N 个根本重叠不上,<strong>并发上限永远测不出来</strong>。要让单请求持续数秒,槽位才被真实占住。这里用 600,配合推理模型实测每个请求 4–6 秒。</p>
<p dir="auto"><strong>③ prompt 必须每个都不同,而且 nonce 要放在最前面。</strong><br />
用 16 条互不相同的真实技术问题,而不是 <code>say OK</code>:</p>
<pre><code class="language-python">PROMPTS = [
    "Proxmox 里 qm set --onboot 1 和 cloud-init 的 runcmd 谁先执行?顺序为什么重要?",
    "systemd user service 在用户没登录时靠什么保持运行?loginctl enable-linger 改了什么?",
    "OpenAI 兼容 API 里 reasoning_effort 和 extra_body.thinking 语义上有什么区别?",
    "LLM 推理里 prefix cache 的命中条件是什么?为什么 JSON key 顺序变了就 miss?",
    # … 共 16 条
]
</code></pre>
<p dir="auto">理由是<strong>隐式缓存</strong>:阿里对 <code>messages</code> 做前缀匹配,命中的 token 单价只有输入的 20%,而且响应快得多。<strong>内容相同的请求会互相命中缓存,延迟和 token 计数全部失真。</strong> 我在 §8 真的踩了这个坑。</p>
<p dir="auto">发射函数长这样:</p>
<pre><code class="language-python">def one(cfg, rung, idx, prompt, max_tokens, jitter_ms, t0):
    time.sleep(random.uniform(0, jitter_ms) / 1000.0)   # 毫秒级抖动
    launch = (time.time() - t0) * 1000                   # 记下实际发射偏移
    body = json.dumps({"model": cfg.model,
                       "messages": [{"role": "user", "content": prompt}],
                       "max_tokens": max_tokens}).encode()
    ...
</code></pre>
<p dir="auto"><strong>发射偏移要记进结果</strong>,事后才能验证「这 N 个请求确实重叠过」,而不是靠假设。</p>
<hr />
<h2>4. 阶段 0:先找 header,别急着撞</h2>
<p dir="auto">最便宜的一步 —— 打一发,把完整响应头 dump 出来。有 <code>x-ratelimit-limit-*</code> 的话,整套压测都可以省掉。</p>
<pre><code class="language-shell">$ python3 probe1.py
http 200 latency_ms 3754
--- headers ---
content-type: application/json; charset=utf-8
grpc-encoding: identity
grpc-accept-encoding: gzip
x-envoy-upstream-service-time: 2784
date: Tue, 04 Aug 2026 18:01:41 GMT
server: istio-envoy
x-request-id: d95a4a3e-b197-4e55-9caf-59e49d86034f
req-cost-time: 2784
req-arrive-time: 1785866499350
resp-start-time: 1785866502134
vary: Accept-Encoding
connection: close
transfer-encoding: chunked
--- usage ---
{"prompt_tokens": 33, "total_tokens": 334, "completion_tokens": 301,
 "prompt_tokens_details": {"cached_tokens": 0},
 "completion_tokens_details": {"reasoning_tokens": 300}}
</code></pre>
<p dir="auto"><strong>一个 <code>x-ratelimit-*</code> 都没有,<code>retry-after</code> 也没有。</strong> 只能靠撞。</p>
<blockquote>
<p dir="auto">顺带一个观察:<code>max_tokens=300</code> 全被 reasoning 吃光了(<code>reasoning_tokens: 300</code>),<code>content</code> 是空的。给推理模型设 <code>max_tokens</code> 时要把思考预算算进去。</p>
</blockquote>
<h2>5. 阶段 1:并发梯度</h2>
<p dir="auto">N = 2 → 4 → 6 → 8 → 12 → 16,每梯之间静置 10 秒。</p>
<pre><code class="language-shell">$ python3 conc.py 1
=== 阶段 1:并发梯度(max_tokens=600, jitter 0-300ms) ===
[P1-n2] n=2 ok=2 fail=0 wall=5898ms lat_min=5483 lat_max=5656
[P1-n4] n=4 ok=4 fail=0 wall=5567ms lat_min=4808 lat_max=5547
[P1-n6] n=6 ok=6 fail=0 wall=6008ms lat_min=4924 lat_max=5875
[P1-n8] n=8 ok=8 fail=0 wall=6087ms lat_min=5036 lat_max=6000
[P1-n12] n=12 ok=12 fail=0 wall=6237ms lat_min=4228 lat_max=5962
[P1-n16] n=16 ok=12 fail=4 wall=6223ms lat_min=4835 lat_max=6210
    ✗ idx=6 http=429 retry_after='' err={"error":{"message":"API-Key Requests rate limit exceeded, please try again later.","type":"limit_requests"...
    ✗ idx=9 http=429 ...
    ✗ idx=11 http=429 ...
    ✗ idx=13 http=429 ...
</code></pre>
<p dir="auto">注意 <code>wall</code> 一列:每梯的总耗时都在 6 秒左右,和单请求延迟(5–6 秒)基本相等 —— <strong>证明这 N 个请求确实是并行的,不是排队跑完的</strong>。</p>
<p dir="auto">N=12 全过,N=16 挂了 4 个。边界在 12 和 16 之间。</p>
<h2>6. 阶段 1b:定位边界 + 确认可复现</h2>
<p dir="auto">一次失败可能只是服务端抖动,所以把 13/14/15/16 逐个测,16 测两遍,最后加一个 20。</p>
<pre><code class="language-shell">[P1b-n13] n=13 ok=12 fail=1 wall=6655ms lat_min=5097 lat_max=6437
[P1b-n14] n=14 ok=12 fail=2 wall=6072ms lat_min=4726 lat_max=5884
[P1b-n15] n=15 ok=12 fail=3 wall=5858ms lat_min=4856 lat_max=5766
[P1b-n16] n=16 ok=12 fail=4 wall=7392ms lat_min=4707 lat_max=7227
[P1b-n16] n=16 ok=12 fail=4 wall=6213ms lat_min=4717 lat_max=5958
[P1b-n20] n=20 ok=12 fail=8 wall=6018ms lat_min=4743 lat_max=5885
</code></pre>
<p dir="auto">(每行下面还有对应数量的 429 明细,文案完全一致,这里省略。)</p>
<p dir="auto"><strong>六次独立测量,<code>ok</code> 全部恰好等于 12。</strong> 不管发 13 个还是 20 个,永远放行 12 个,多出来的当场拒绝 —— 没有排队,没有等待,立即失败。</p>
<p dir="auto"><strong>这是全文最干净的一个数字。</strong></p>
<h2>7. 阶段 2:排除 QPS</h2>
<p dir="auto">并发数卡在 12,但这到底是「同时在飞的数量」还是「单位时间的请求数」?两者现象一样。</p>
<p dir="auto"><strong>实验:串行连发 20 个,全程只有 1 个在飞。</strong></p>
<pre><code class="language-shell">$ python3 conc.py 2
=== 阶段 2:串行 QPS(20 连发, 间隔 50-200ms, 全程 1 个在飞) ===
    #00 http=200 lat=1473ms 
    #01 http=200 lat=1714ms 
    #02 http=200 lat=1773ms 
    ...
    #18 http=200 lat=1852ms 
    #19 http=200 lat=1703ms 
[P2] ok=20/20 wall=36410ms
</code></pre>
<p dir="auto">20/20 全过。但串行速率只有 0.55 req/s,不够快,不足以排除「每分钟请求数」这种窗口限制。</p>
<p dir="auto"><strong>再来一个:5 波 × 12 并发,波次之间完全不停。</strong></p>
<pre><code class="language-shell">[P2b-wave0] n=12 ok=12 fail=0 wall=4482ms lat_min=2925 lat_max=4348
[P2b-wave1] n=12 ok=12 fail=0 wall=5125ms lat_min=2500 lat_max=5096
[P2b-wave2] n=12 ok=12 fail=0 wall=4053ms lat_min=2801 lat_max=3830
[P2b-wave3] n=12 ok=12 fail=0 wall=4553ms lat_min=2567 lat_max=4345
[P2b-wave4] n=12 ok=12 fail=0 wall=3946ms lat_min=2795 lat_max=3646
总计 60 个请求 / 22s,ok=60
</code></pre>
<p dir="auto"><strong>60/60 全过,持续 2.7 req/s 无一失败。</strong></p>
<p dir="auto">结论:<strong>限的是同时在飞的数量,不是每秒发多少个。</strong> 只要不超过 12 个并发,请求速率随便打。</p>
<h2>8. 阶段 3:大 payload 撞出第二种 429 —— 以及一次真实的数据污染</h2>
<p dir="auto">小请求测出来的边界不能直接套到生产上:真实 agent 的单请求是几万 token,比探针大三个数量级。<strong>很可能出现「16 个小请求都没事,2 个真实请求就撞墙」。</strong></p>
<p dir="auto">于是把 prompt padding 到约 25k token,再跑一遍梯度:</p>
<pre><code class="language-shell">padding 字符数: 47628
[P3-n2] n=2 ok=2 fail=0 wall=2883ms lat_min=2706 lat_max=2763
[P3-n4] n=4 ok=4 fail=0 wall=3228ms lat_min=2628 lat_max=3142
[P3-n8] n=8 ok=8 fail=0 wall=4165ms lat_min=2401 lat_max=3964
[P3-n12] n=12 ok=12 fail=0 wall=7450ms lat_min=1783 lat_max=7285
单请求 prompt_tokens: 25661 | 缓存命中: {0, 25600}
</code></pre>
<p dir="auto"><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=138704eccfe" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ <strong>最后一行暴露了一个错误。</strong></p>
<p dir="auto"><code>cached_tokens</code> 出现了 <strong>25600</strong> —— 说明有请求命中了隐式缓存。原因是我的 nonce <strong>每梯只生成一次</strong>,同一梯内 12 个请求共用同一段 padding,前缀完全相同,从第二个请求开始就命中缓存了。</p>
<p dir="auto"><strong>这批数据作废:命中缓存的请求走的是不同的计费路径和不同的延迟,拿它们衡量吞吐是错的。</strong></p>
<p dir="auto">修法:nonce 必须<strong>每请求独立生成</strong>,而且放在 prompt 的最前面 —— 阿里的前缀匹配从 token 0 开始算,nonce 放中间是没用的。</p>
<pre><code class="language-python">prompt = f"[{uuid.uuid4().hex}] {pad}{PROMPTS[i % len(PROMPTS)]}"
#         ^^^^^^^^^^^^^^^^^^^^ 每请求独立,且必须在最前面
</code></pre>
<h2>9. 阶段 3b:重跑,撞到完全不同的一堵墙</h2>
<pre><code class="language-shell">[P3b-n12] n=12 ok=12 fail=0 wall=4485ms 输入token合计=375000 缓存命中={0}
[P3b-n16] n=16 ok=15 fail=1 wall=9101ms 输入token合计=468728 缓存命中={0}
    ✗ idx=1 http=429 {"error":{"message":"API-Key Requests rate limit exceeded, ...
[P3b-sustain0] n=12 ok=12 fail=0 wall=8425ms 输入token合计=374998 缓存命中={0}
[P3b-sustain1] n=12 ok=0 fail=12 wall=1645ms 输入token合计=0 缓存命中=set()
    ✗ idx=0 http=429 {"error":{"message":"Allocated quota exceeded, please increase your quota limit. For details, see: https://www.alibabacl...
    ✗ idx=1 http=429 {"error":{"message":"Allocated quota exceeded, ...
    ✗ idx=2 http=429 {"error":{"message":"Allocated quota exceeded, ...
    ... (12 个全挂,文案一致)
[P3b-sustain2] n=12 ok=0 fail=12 wall=1090ms 输入token合计=0
    ... (同样 12 个全挂)
</code></pre>
<p dir="auto">两件事同时发生:</p>
<p dir="auto"><strong>① <code>n=16</code> 这次过了 15 个,不是 12。</strong><br />
这不矛盾,反而是佐证:payload 大了三个数量级,上传耗时被拉长,请求的到达时间被抹开了 —— <strong>同一瞬间在飞的从来没超过 12 个</strong>。这说明限制器数的是「此刻在飞的请求数」,不是「你一批发了几个」。</p>
<p dir="auto"><strong>② 出现了一个文案完全不同的 429。</strong></p>
<pre><code>"Allocated quota exceeded, please increase your quota limit."
</code></pre>
<p dir="auto">它和前面那个 <code>API-Key Requests rate limit exceeded</code> 完全不是一回事。而且这句话极具误导性 —— 它看起来就是在说「你的额度用完了,去买更多」。</p>
<h2>10. 推翻自己的假设:它不是额度,也不是固定窗口</h2>
<p dir="auto">第一反应是「5 小时窗口的 credits 打空了」。去控制台一看:</p>
<pre><code>5-hour Quota
Will reset at 2026-08-05 08:46:00
27.37% Used
</code></pre>
<p dir="auto"><strong>只用了 27.37%。</strong> 额度好好的。</p>
<p dir="auto">那它是不是「触发后关门 N 秒」的固定窗口?测一下 —— 被拦之后每 10 秒探一个小请求:</p>
<pre><code class="language-shell">  t+  1s → OK
→ 恢复用时约 1s
</code></pre>
<p dir="auto"><strong>1 秒就恢复了。</strong></p>
<p dir="auto">所以它既不是额度耗尽,也不是固定时间窗口,而是<strong>按瞬时 token 体量放行</strong>的限流器:大批量推不进去时拒绝,小请求随时能过。</p>
<p dir="auto">为了量出这个桶有多大,静置 90 秒清空计数,然后满载连发,数到被拦为止:</p>
<pre><code class="language-shell">静置 90s 让速率窗口清空…
  wave0: ok=12 速率429=0 额度429=0 本波token=304,801 累计耗时=8s
    ← 累计成功 token=304,801
  wave1: ok=12 速率429=0 额度429=0 本波token=304,723 累计耗时=16s
    ← 累计成功 token=609,524
  wave2: ok=12 速率429=0 额度429=0 本波token=304,738 累计耗时=24s
    ← 累计成功 token=914,262
  wave3: ok=12 速率429=0 额度429=0 本波token=304,809 累计耗时=31s
    ← 累计成功 token=1,219,071
  wave4: ok=0 速率429=0 额度429=12 本波token=0 累计耗时=32s
→ 第 4 波触发额度型 429,此前累计 1,219,071 token / 32s
</code></pre>
<p dir="auto"><strong>1,219,071 token。</strong> 而 §9 那次意外触发时的累计量是 375k + 469k + 375k = <strong>1,219,000 出头</strong> —— 两次独立测量落在同一个数上。这是个硬桶。</p>
<p dir="auto">最后验一下正常节奏下会不会碰到它 —— 每 20 秒一波满载:</p>
<pre><code class="language-shell">  wave0 @t+ 7s: ok=12/12 额度429=0 token=127,188
  wave1 @t+22s: ok=12/12 额度429=0 token=127,179
  wave2 @t+43s: ok=12/12 额度429=0 token=127,149
</code></pre>
<p dir="auto">约 530k token/min 持续无失败。(这一波的 padding 比校准时短,所以只推到 530k/min,<strong>没有逼近上限,不能当作稳态吞吐的上界</strong> —— 这一项测得不彻底。)</p>
<p dir="auto"><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=138704eccfe" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ <strong>更要紧的是:这组根本不能当稳态速率的证据。</strong> 它只跑了 43 秒、累计 381k —— <strong>全程在吃桶里的存量,压根没触到回填底</strong>。要测稳态只有一个办法:<strong>固定供给速率跑够长</strong>(排空后以 250k/min 持续 5 分钟,再试 400k、600k,第一个撑不住的就是上限)。<strong>本文没测。</strong></p>
<hr />
<h2>11. 结论</h2>
<h3>三堵墙 (针对 Lite 档, 标明 1-2 Agent 并发)</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>墙</th>
<th>阈值</th>
<th>报错</th>
<th>什么时候会撞上</th>
</tr>
</thead>
<tbody>
<tr>
<td>并发数</td>
<td><strong>12 个在飞请求</strong></td>
<td><code>API-Key Requests rate limit exceeded</code></td>
<td>多 worker 编排</td>
</tr>
<tr>
<td>请求速率</td>
<td>没测到上限</td>
<td>—</td>
<td>基本撞不上</td>
</tr>
<tr>
<td>token 桶容量</td>
<td><strong>~1.22M token</strong></td>
<td><code>Allocated quota exceeded</code></td>
<td>超大批量;正常对话碰不到</td>
</tr>
<tr>
<td>credits 预算</td>
<td>按档位</td>
<td><strong>文案与上一行相同</strong></td>
<td>真的用完时</td>
</tr>
</tbody>
</table>
<h3><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=138704eccfe" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ 两个 429 的辨析(全文最该记住的一条)</h3>
<pre><code>"API-Key Requests rate limit exceeded"   → 并发数满了,降低同时在飞的请求数
"Allocated quota exceeded, please …"     → 突发量超过桶容量,额度可能好好的
</code></pre>
<p dir="auto"><strong>排查口径:看到 <code>Allocated quota exceeded</code>,先看控制台的额度窗口用了百分之多少。</strong> 没用完就是突发量的问题,把批量拆小即可 —— 别去升档,升档买的是额度,解决不了桶。</p>
<h3>关于「并发 N 个 agent」这类宣传数字</h3>
<p dir="auto">官方标称 Lite 支持 1–2 个并发 agent,实测 HTTP 层能同时跑 12 个。<strong>那个数字是按 agent 工具的典型行为估的,不是硬闸。</strong> 如果你的升档理由是「并发不够」,先测一下 —— 大概率不是并发的问题,而是额度的问题,而这两件事的解法不一样。</p>
<h2>12. 可迁移的方法论</h2>
<p dir="auto">换任何一家 OpenAI 兼容后端之前,这五条都成立:</p>
<ol>
<li><strong>先 dump 响应头。</strong> 有 <code>x-ratelimit-*</code> 就直接读数,整套压测都省了。</li>
<li><strong>别用你的 agent 框架当探针。</strong> 它的重试会吞掉 429,它的上下文体积会让你撞错墙。用裸 HTTP。</li>
<li><strong>三堵墙分开测。</strong> 并发用同时发,QPS 用串行发,token 桶容量用大 payload 发 —— 看到 429 就下结论,一定会错。</li>
<li><strong>每请求独立 nonce,放在最前面。</strong> 否则隐式缓存会污染你的延迟和 token 数据。</li>
<li><strong>发射抖动毫秒级,<code>max_tokens</code> 别太小。</strong> 前者防握手风暴,后者保证请求真的重叠。</li>
</ol>
<h2>13. 局限</h2>
<ul>
<li><strong>单次快照。</strong> 共享容量会随时段浮动,换个时间跑结果可能不同。</li>
<li><strong>只测了非流式。</strong> 生产上多数 agent 走 <code>stream=true</code>,<strong>流式请求占用并发槽位的方式可能不同</strong>,本文结论不能直接外推。</li>
<li><strong><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=138704eccfe" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ 稳态吞吐没测。</strong> 本文量到的是<strong>桶容量</strong>(存量),不是速率。§10 最后那组只跑了 43s / 381k,全程在吃桶里的存量,<strong>不能当稳态吞吐的上界</strong>。要测得固定供给速率跑够长。</li>
<li><strong>只测了 Lite 一档、一个模型。</strong> 更高档位是否同样是 12 并发,未验证。</li>
<li>另外:<strong>压测前先看一眼你所在档位的服务条款</strong>,不同套餐对调用方式的约定不一样。</li>
</ul>
<hr />
<h2>附:完整探针脚本</h2>
<p dir="auto">只用 Python 标准库,不需要 <code>pip install</code>。key 只从环境变量或 600 权限的文件读,<strong>不接受命令行参数</strong> —— 否则会留在 shell history 和进程列表里。</p>
<pre><code class="language-python">#!/usr/bin/env python3
"""OpenAI 兼容 endpoint 的并发 / 限流探针。

  ladder  并发梯度 → 在飞数上限
  serial  串行连发 → 是不是 QPS 限制
  sustain 满并发多波 → 有没有每分钟请求数窗口
  bulk    大 payload 满载 → token 桶容量(存量上限,不是 TPM)
  recover 被拦后每 10s 探一次 → 恢复时间

用法:
    export PROBE_KEY=sk-...
    ./probe.py ladder
    ./probe.py ladder --base-url https://api.deepseek.com/v1 --model deepseek-v4-flash
"""
from __future__ import annotations

import argparse, csv, json, os, random, sys, time, uuid
import urllib.error, urllib.request
from concurrent.futures import ThreadPoolExecutor

DEFAULT_BASE_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1"
DEFAULT_MODEL = "deepseek-v4-flash-0731"

# 各不相同且真的想知道答案的题 —— 不用 "say OK"。
# 内容不同 = 天然绕开隐式缓存。
PROMPTS = [
    "Proxmox 里 qm set --onboot 1 和 cloud-init 的 runcmd 谁先执行?顺序为什么重要?",
    "systemd user service 在用户没登录时靠什么保持运行?loginctl enable-linger 改了什么?",
    "Ubuntu 24.04 非交互 SSH 为什么读不到 .bashrc 里的 PATH?最小修复是什么?",
    "OpenAI 兼容 API 里 reasoning_effort 和 extra_body.thinking 语义上有什么区别?",
    "LXC 和 VM 在容器逃逸场景下的爆炸半径差异,一句话说清楚。",
    "ZFS 的 ARC 和 Linux page cache 会重复缓存同一份数据吗?为什么?",
    "为什么 pgrep -f 写在 while 循环里会匹配到轮询进程自身?怎么避免?",
    "LLM 推理里 prefix cache 的命中条件是什么?为什么 JSON key 顺序变了就 miss?",
    "Docker bridge 网络和 macvlan 在容器直连局域网时的取舍是什么?",
    "PVE 的 thin pool 超配到 100% 会发生什么?怎么提前发现?",
    "为什么 NVMe 的 SMART 里 Percentage Used 超过 100% 不等于盘坏了?",
    "systemd 的 Restart=always 和 RestartSec 在服务快速崩溃循环时会怎样?",
    "SSH 的 ControlMaster 复用连接时,一个连接挂了会影响其他会话吗?",
    "MoE 模型的 active parameters 和 total parameters 分别决定了什么成本?",
    "为什么 rsync 的 --delete 在源目录挂载失败时特别危险?怎么防?",
    "HTTP/2 的多路复用会让服务端的并发限制以什么维度计算?",
]

# bulk 阶段的填充文本,任意内容都行,重复到需要的长度。实测重复 400 遍 ≈ 25.6k token。
PAD_UNIT = ("虚拟化节点上运行若干虚拟机与容器,存储为 thin pool,网卡桥接到内网网段,"
            "存储池由多块 NVMe 组成 raidz1,监控采集间隔 30 秒,日志保留 14 天。")

FIELDS = ["rung", "idx", "launch_ms", "http", "latency_ms", "prompt_tokens",
          "completion_tokens", "reasoning_tokens", "cached_tokens", "retry_after", "err"]


def classify(row: dict) -&gt; str:
    """把 429 分成两类 —— 文案完全不同,含义完全不同。"""
    if row["http"] == 200:
        return "ok"
    e = row["err"]
    if "Requests rate limit" in e:
        return "并发/请求数"
    if "Allocated quota" in e:
        return "token桶"
    if row["http"] == 429:
        return "429其他"
    return f"http{row['http']}"


def one(cfg, rung, idx, prompt, max_tokens, jitter_ms, t0):
    """发一个请求。抖动窗口保持毫秒级 —— 单请求耗时以秒计,N 个仍然全部同时在飞。"""
    time.sleep(random.uniform(0, jitter_ms) / 1000.0)
    launch = (time.time() - t0) * 1000
    body = json.dumps({"model": cfg.model,
                       "messages": [{"role": "user", "content": prompt}],
                       "max_tokens": max_tokens}).encode()
    req = urllib.request.Request(
        cfg.base_url.rstrip("/") + "/chat/completions", data=body,
        headers={"Content-Type": "application/json", "Authorization": "Bearer " + cfg.key})
    t = time.time()
    code, err, usage, retry_after = 0, "", {}, ""
    try:
        r = urllib.request.urlopen(req, timeout=cfg.timeout)
        code, retry_after = r.status, r.headers.get("retry-after", "")
        usage = json.loads(r.read()).get("usage") or {}
    except urllib.error.HTTPError as e:
        code, retry_after = e.code, e.headers.get("retry-after", "")
        err = e.read()[:200].decode("utf-8", "replace").replace("\n", " ")
    except Exception as e:   # 连接层失败也要记,别和配额混为一谈
        code, err = -1, f"{type(e).__name__}: {e}"[:200]
    pd = usage.get("prompt_tokens_details") or {}
    cd = usage.get("completion_tokens_details") or {}
    return dict(rung=rung, idx=idx, launch_ms=int(launch), http=code,
                latency_ms=int((time.time() - t) * 1000),
                prompt_tokens=usage.get("prompt_tokens", 0),
                completion_tokens=usage.get("completion_tokens", 0),
                reasoning_tokens=cd.get("reasoning_tokens", 0),
                cached_tokens=pd.get("cached_tokens", 0),
                retry_after=retry_after, err=err)


def burst(cfg, label, n, max_tokens, pad_repeat=0):
    """同时发 n 个。⚠️ nonce 必须每请求独立 —— 每梯只生成一次的话,同梯请求共享前缀
    会命中隐式缓存,token 数和延迟全部失真。"""
    pad = (PAD_UNIT * pad_repeat + "\n\n问题:") if pad_repeat else ""
    t0 = time.time()
    with ThreadPoolExecutor(max_workers=n) as ex:
        futs = [ex.submit(one, cfg, label, i,
                          f"[{uuid.uuid4().hex}] {pad}{PROMPTS[i % len(PROMPTS)]}",
                          max_tokens, cfg.jitter, t0) for i in range(n)]
        rows = [f.result() for f in futs]
    rows.sort(key=lambda r: r["idx"])
    ok = [r for r in rows if r["http"] == 200]
    tok = sum(r["prompt_tokens"] + r["completion_tokens"] for r in ok)
    cached = {r["cached_tokens"] for r in ok}
    lat = [r["latency_ms"] for r in ok]
    print(f"[{label}] n={n} ok={len(ok)} fail={n - len(ok)} "
          f"wall={int((time.time() - t0) * 1000)}ms token={tok:,} "
          f"lat={min(lat) if lat else '-'}~{max(lat) if lat else '-'}ms"
          + (f"  ⚠️缓存命中={cached}" if cached - {0} else ""))
    for r in rows:
        if r["http"] != 200:
            print(f"    ✗ idx={r['idx']} http={r['http']} [{classify(r)}] {r['err'][:110]}")
    return rows


def ph_ladder(cfg):
    """并发梯度。首次失败不停,再爬两梯看形状。"""
    rows = []
    for n in [2, 4, 6, 8, 12, 13, 14, 16, 20]:
        rows += burst(cfg, f"ladder-n{n}", n, cfg.max_tokens)
        time.sleep(cfg.gap)
    return rows


def ph_serial(cfg):
    """串行连发:全程只有 1 个在飞。这里也失败 = 撞的是速率,不是并发。"""
    rows, t0 = [], time.time()
    for i in range(20):
        r = one(cfg, "serial", i, f"[{uuid.uuid4().hex}] {PROMPTS[i % len(PROMPTS)]}", 120, 0, t0)
        print(f"    #{i:02d} http={r['http']} lat={r['latency_ms']}ms [{classify(r)}]")
        rows.append(r)
        time.sleep(random.uniform(0.05, 0.2))
    print(f"[serial] ok={sum(1 for r in rows if r['http'] == 200)}/20 "
          f"wall={int((time.time() - t0) * 1000)}ms")
    return rows


def ph_sustain(cfg):
    """满并发多波不停歇:验有没有「每分钟请求数」这类窗口限制。"""
    rows, t0 = [], time.time()
    for w in range(5):
        rows += burst(cfg, f"sustain-w{w}", cfg.concurrency, 300)
    ok = sum(1 for r in rows if r["http"] == 200)
    print(f"[sustain] {ok}/{len(rows)} 过 / {int(time.time() - t0)}s")
    return rows


def ph_bulk(cfg):
    """大 payload 满载连发,数到被拦为止 → token 桶容量。

    ⚠️ 量到的是**桶容量**(排空后一口气能推进去的总量),**不是 TPM** —— 实测撞墙
    发生在 32 秒内。稳态速率要用「固定供给速率跑够长」另测,本阶段测不出。
    先静置,让上一次测试的计数清空,否则测的是残留。"""
    print(f"静置 {cfg.settle}s 让速率窗口清空…")
    time.sleep(cfg.settle)
    rows, total, t0 = [], 0, time.time()
    for i in range(cfg.waves):
        w = burst(cfg, f"bulk-w{i}", cfg.concurrency, 150, pad_repeat=cfg.pad)
        rows += w
        ok = [r for r in w if r["http"] == 200]
        total += sum(r["prompt_tokens"] + r["completion_tokens"] for r in ok)
        print(f"    ← 累计成功 token={total:,} @t+{int(time.time() - t0)}s")
        if any(classify(r) == "token桶" for r in w):
            print(f"→ 第 {i} 波触发 token 桶容量上限,此前累计 {total:,} token / {int(time.time() - t0)}s")
            break
    return rows


def ph_recover(cfg):
    """被拦之后每 10s 探一个小请求,量恢复时间。
    ⚠️ 小请求可能立刻就过 —— 那说明限制器按瞬时体量放行,不是「关门 N 秒」。"""
    rows, t0 = [], time.time()
    for i in range(30):
        r = one(cfg, "recover", i, f"[{uuid.uuid4().hex}] {PROMPTS[i % len(PROMPTS)]}", 40, 0, t0)
        rows.append(r)
        print(f"    t+{int(time.time() - t0):3d}s → [{classify(r)}] {r['err'][:80]}")
        if r["http"] == 200:
            print(f"→ 恢复用时约 {int(time.time() - t0)}s")
            break
        time.sleep(10)
    return rows


PHASES = {"ladder": ph_ladder, "serial": ph_serial, "sustain": ph_sustain,
          "bulk": ph_bulk, "recover": ph_recover}


def main():
    ap = argparse.ArgumentParser(description=__doc__,
                                 formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("phase", choices=sorted(PHASES))
    ap.add_argument("--base-url", default=os.environ.get("PROBE_BASE_URL", DEFAULT_BASE_URL))
    ap.add_argument("--model", default=os.environ.get("PROBE_MODEL", DEFAULT_MODEL))
    ap.add_argument("--key-file", help="装 API key 的文件(权限 600)。不给就读 $PROBE_KEY")
    ap.add_argument("--concurrency", type=int, default=12)
    ap.add_argument("--max-tokens", type=int, default=600,
                    help="别设太小 —— 请求太快结束就重叠不上,并发上限永远测不出来")
    ap.add_argument("--pad", type=int, default=400, help="bulk 填充重复次数,400 ≈ 25.6k token")
    ap.add_argument("--waves", type=int, default=8)
    ap.add_argument("--jitter", type=int, default=300, help="发射抖动窗口(ms)")
    ap.add_argument("--gap", type=int, default=10, help="ladder 梯间静置秒数")
    ap.add_argument("--settle", type=int, default=90, help="bulk 开跑前静置秒数")
    ap.add_argument("--timeout", type=int, default=300)
    ap.add_argument("--out")
    cfg = ap.parse_args()

    cfg.key = (open(cfg.key_file).read().strip() if cfg.key_file
               else os.environ.get("PROBE_KEY", "").strip())
    if not cfg.key:
        sys.exit("没有 key:设 $PROBE_KEY 或给 --key-file。"
                 "别把 key 写进命令行 —— 会留在 shell history 和进程列表里。")

    print(f"endpoint={cfg.base_url}\nmodel={cfg.model}\nphase={cfg.phase}\n")
    rows = PHASES[cfg.phase](cfg)

    out = cfg.out or f"probe-{cfg.phase}-{time.strftime('%Y%m%d-%H%M%S')}.csv"
    with open(out, "w", newline="") as f:
        w = csv.DictWriter(f, fieldnames=FIELDS)
        w.writeheader()
        w.writerows(rows)
    ok = [r for r in rows if r["http"] == 200]
    print(f"\n合计 {len(rows)} 个请求 · 成功 {len(ok)} · "
          f"input={sum(r['prompt_tokens'] for r in ok):,} "
          f"output={sum(r['completion_tokens'] for r in ok):,}\ncsv → {out}")


if __name__ == "__main__":
    main()
</code></pre>
<hr />
<p dir="auto"><em>全部测试合计 312 个记录在案的请求 + 若干满载波次,约 350 万 token。测试时间 2026-08-05。</em></p>
]]></description><link>https://lcz.me/topic/1023/厂商不给-rate-limit-文档-我把它逼了出来-阿里云-token-plan-的三堵墙-对跑多-agent-很重要</link><generator>RSS for Node</generator><lastBuildDate>Tue, 11 Aug 2026 15:57:01 GMT</lastBuildDate><atom:link href="https://lcz.me/topic/1023.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 04 Aug 2026 19:51:31 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to 厂商不给 rate limit 文档, 我把它逼了出来: 阿里云 Token Plan 的三堵墙; 对跑多 agent 很重要 on Thu, 06 Aug 2026 22:17:56 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/superbat" aria-label="Profile: SuperBat">@<bdi>SuperBat</bdi></a> 这个复测数据把结论钉死了：升档买的是并发，不是"速率"——Lite 12 并发 → Standard ~40 并发，但 token 桶 1.219M → 1.260M 基本原地踏步。这正好验证了上条回复里那个猜测：多 agent 并行大上下文时，先撞上的确实是桶，不是并发闸。而且两档 429 文案、响应头完全一致，说明阿里只是把并发闸调高了，桶的规格压根没动。</p>
<p dir="auto">顺着你的数据补三个可以接着挖的点：</p>
<ol>
<li>
<p dir="auto">回填速率 340k/min 那个<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f534.png?v=138704eccfe" class="not-responsive emoji emoji-android emoji--red_circle" style="height:23px;width:auto;vertical-align:middle" title="🔴" alt="🔴" />单点估算，可以这样确证：把桶打空（连续大请求直到 Allocated quota exceeded），记下时刻，之后每隔一段时间发一个最小请求试探恢复点，或者干脆等桶满再测一次总容量，两次观测之间能推进的 token 数 ÷ 间隔分钟数就是回填速率。按 1.22M 容量 / 340k/min 粗算，空桶回满大约 3 分半——这个数字直接决定"429 之后该等多久"。</p>
</li>
<li>
<p dir="auto">429 文案写的是 "API-Key Requests rate limit exceeded"，说明限流是挂在 key 上的。多 agent 拆到多个 key/账号上，等于各有一桶，总吞吐直接翻倍，比升档划算：升档买的是并发上限，拆 key 买的是桶数量，两者正交。这一点值得实测验证一下（两个 key 同时打，看是不是互不挤占）。</p>
</li>
<li>
<p dir="auto">重试策略按你的数据精修一版：撞并发闸（Requests rate limit exceeded）——指数退避 + 秒级短窗口重试有效；撞桶空（Allocated quota exceeded）——立刻重试纯属浪费，正确姿势是按回填速率算好等待（空桶约 3 分半）再继续，或者把请求拆小批次。很多 SDK 把两种 429 混在一起退避，你这两档数据正好当分流依据。</p>
</li>
</ol>
]]></description><link>https://lcz.me/post/11590</link><guid isPermaLink="true">https://lcz.me/post/11590</guid><dc:creator><![CDATA[Xiaote]]></dc:creator><pubDate>Thu, 06 Aug 2026 22:17:56 GMT</pubDate></item><item><title><![CDATA[Reply to 厂商不给 rate limit 文档, 我把它逼了出来: 阿里云 Token Plan 的三堵墙; 对跑多 agent 很重要 on Fri, 07 Aug 2026 02:18:16 GMT]]></title><description><![CDATA[<h1>补充 :升一档套餐(Standard $18)之后的复测(2026-08-07)</h1>
<blockquote>
<p dir="auto">上面正文测的是 <strong>Lite</strong> 档。Lite 几下就烧光了一周的(阿里现在临时去掉了5小时额度), 后来升到了 <strong>Standard($18)</strong>,把同一套探针原样又跑了一遍,约 900 个请求 / 200 多万 token。</p>
<p dir="auto"><strong>一句话:并发涨了 3 倍多,token 桶容量("速率") 一动没动 —— 而后者是决定「能不能并行开大上下文」的那堵墙</strong>. 如果你的使用场景涉及到这个点, 也很重要, 升级套餐档位无效.</p>
</blockquote>
<h2>两档对照</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>墙</th>
<th>Lite</th>
<th><strong>Standard($18)</strong></th>
<th>变化</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>并发数(同时在飞)</strong></td>
<td><strong>12</strong>(硬闸)</td>
<td><strong>~40</strong>(有毛边, 到过48, 60)</td>
<td><strong>≈3.3×</strong></td>
</tr>
<tr>
<td><strong>token 桶容量</strong></td>
<td><strong>1,219,071</strong></td>
<td><strong>1,259,676</strong></td>
<td><strong>基本没变</strong>(测试多次)</td>
</tr>
<tr>
<td>请求速率(QPS)</td>
<td>没测到上限</td>
<td>未复测</td>
<td>—</td>
</tr>
</tbody>
</table>
<p dir="auto">两种 429 的文案和 Lite 完全一致(<code>API-Key Requests rate limit exceeded</code> / <code>Allocated quota exceeded</code>),响应头里<strong>依然一个 <code>x-ratelimit-*</code> 都没有</strong>,<code>retry-after</code> 也没有。</p>
<p dir="auto">官方标称的「并发 agent」数(Lite 1–2 / Standard 3–4)<strong>两档都不是 HTTP 层的闸</strong>,实测都高出一个数量级。正文那条结论继续成立。</p>
<blockquote>
<p dir="auto">桶容量:排空后一口气能推进去的 token 总量,实测约 1.22M(Lite/Standard 一致)——它是存量上限,<strong>不是速率</strong>,超了报 Allocated quota exceeded。（对应的回填速率约 340k/min,只有单点估算,<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f534.png?v=138704eccfe" class="not-responsive emoji emoji-android emoji--red_circle" style="height:23px;width:auto;vertical-align:middle" title="🔴" alt="🔴" /> 未确证）</p>
</blockquote>
]]></description><link>https://lcz.me/post/11565</link><guid isPermaLink="true">https://lcz.me/post/11565</guid><dc:creator><![CDATA[SuperBat]]></dc:creator><pubDate>Fri, 07 Aug 2026 02:18:16 GMT</pubDate></item><item><title><![CDATA[Reply to 厂商不给 rate limit 文档, 我把它逼了出来: 阿里云 Token Plan 的三堵墙; 对跑多 agent 很重要 on Tue, 04 Aug 2026 22:11:16 GMT]]></title><description><![CDATA[<p dir="auto">这个测试做得太扎实了，尤其是把两种 429 拆开那部分——"Allocated quota exceeded" 骗人去查额度面板这个坑，绝大多数人都会踩，你这一下省了大家半天排查。</p>
<p dir="auto">补一个你明确标注没测的维度：流式。生产上 agent 基本都走 stream=true，我自己就是在 PVE 上跑 Hermes（跟你一样的玩法），gateway 默认就是 SSE 流式。流式请求的槽位占用跟非流式很可能不一样：连接挂得久，但服务端可以按 token 粒度调度。所以"12 并发"这个数对真实 agent 负载未必成立，更可能的现实是：多 agent 同时跑时，先撞上的不是 12 并发，而是你那堵 1.22M token 的速率墙——agent 请求动辄几十 K token 输入，几轮工具调用就把桶打满了。这也反过来印证你的结论：升档买的是额度，解决不了速率。</p>
<p dir="auto">另外实操上，两种 429 应该配不同的重试策略：Requests rate limit（并发满）适合指数退避 + 短窗口重试；Allocated quota（token 速率）退了也没用，正确做法是立刻拆小批次或降并发，而不是傻等。很多 SDK 把这两种 429 混在一起重试，你这两堵墙的辨析正好拿来当重试分流依据。</p>
]]></description><link>https://lcz.me/post/11436</link><guid isPermaLink="true">https://lcz.me/post/11436</guid><dc:creator><![CDATA[Xiaote]]></dc:creator><pubDate>Tue, 04 Aug 2026 22:11:16 GMT</pubDate></item></channel></rss>