Seedance API 的限流是公布的,两个数字而不是一个。doubao-seedance-2-5-260628 挂着 最大 RPM: 企业用户: 600 个人用户: 180最大并发: 企业用户: 10 个人用户: 3。两列共用同一个限定词:非刚性保障,受平台负载/调用方式影响,详见文档。离线推理 暂不支持API 总览讲调用形态。

官方公布的限流数字

数字写在方舟模型列表里,列头是 在线推理限流;每格都是官方原文,

模型 ID最大 RPM最大并发离线推理限流
doubao-seedance-2-5-260628企业用户 600 / 个人用户 180企业用户 10 / 个人用户 3暂不支持
doubao-seedance-2-0-260128非 4k:600 / 180;4k:15 / 15非 4k:10 / 3;4k:1 / 1暂不支持
doubao-seedance-2-0-fast-260128企业用户 600 / 个人用户 180企业用户 10 / 个人用户 3暂不支持
doubao-seedance-2-0-mini-260615企业用户 600 / 个人用户 180企业用户 10 / 个人用户 3暂不支持

搜这个关键词,会先撞上四套互相打架的第三方表:30 requests per 60 seconds per accountGeneration 100/min每分鐘請求: 20–60Requests per minute 60。四家都注明是自己的数字,没有一家是方舟的端点参考里是请求侧本身。

curl -X POST https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ARK_API_KEY" \
  -d '{
    "model": "doubao-seedance-2-5-260628",
    "content": [
      { "type": "text", "text": "A workshop at dawn, hands assembling a clock movement." }
    ],
    "ratio": "16:9",
    "duration": 5
  }'

并发与 RPM 是两个独立额度

一张并发预算示意图:一排槽位,其中几个被在飞请求占着,其余空着,每个请求结束后对应槽位被释放。

两个数字的行为不一样,混为一谈是常见的规划错误。

RPM 数每分钟的提交次数。一个视频任务要一次请求创建,之后每轮询一次又是一次请求,紧凑的轮询循环本身就是请求制造机。并发数的是同一时刻在飞的任务数,一条 Seedance 任务却占着槽位数分钟。

把额度换算成在飞任务

这个差别改变你要观测的指标。10 个并发配 600 RPM 意味着限流不是瓶颈,自己任务的耗时才是。一次生成三分钟,10 个槽位每分钟大约只能吐 3 条,剩下 590 次请求都是花出去的轮询。

import json
import os
import threading
from concurrent.futures import ThreadPoolExecutor

import requests

ARK = "https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks"
BUDGET = 10  # 最大并发: 企业用户: 10 个人用户: 3

slots = threading.Semaphore(BUDGET)


def run(payload, api_key):
    with slots:  # one slot held for the whole generation, released on exit
        created = requests.post(
            ARK,
            headers={"Authorization": f"Bearer {api_key}"},
            json=payload,
            timeout=60,
        )
        created.raise_for_status()
        task_id = created.json()["id"]

        while True:
            polled = requests.get(
                f"{ARK}/{task_id}",
                headers={"Authorization": f"Bearer {api_key}"},
                timeout=60,
            )
            polled.raise_for_status()
            body = polled.json()
            if body["status"] in {"succeeded", "failed", "expired", "cancelled"}:
                return task_id, body["status"]


with ThreadPoolExecutor(max_workers=BUDGET) as pool:
    futures = [pool.submit(run, {"model": "doubao-seedance-2-5-260628",
                                 "content": [{"type": "text", "text": "A workshop at dawn."}],
                                 "ratio": "16:9", "duration": 5}, os.environ["ARK_API_KEY"])
               for _ in range(40)]
    for future in futures:
        print(*future.result())
curl -s -X GET \
  "https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks?page_num=1&page_size=100&filter.status=running" \
  -H "Authorization: Bearer $ARK_API_KEY" | jq '.items | length'

额度按账号算,不按 Key 算

官方写得很直白:每个账号(含主账号下的所有子账号,合并计算)。主账号下面每个子账号,都在同一个天花板下取额度。

所以「每个 Key 一份额度」是幻觉。两个服务各按 10 并发调优,合起来就是要 20 个。限流得挡在所有服务都能看到的地方:一个队列加一个派发器,或共享存储里的计数器。

为什么还有余额就被限流

方舟在常见问题里给了答案,这是整个话题里最有用的一句:方舟对于模型限流的逻辑使用的是预扣机制,来保障您已提交的请求的完成率。即在收到请求时会根据输入和输出长度预扣除本时间窗口的 TPM 配额

先对窗口预扣。请求在跑之前就按输入和预期输出长度扣掉额度。同一窗口连提几条长任务,就可能在面板还显示余量时被拒,那点余量已被预扣、还没消耗。官方给的理由是稳定性:不预扣,一波长请求会把已提交的任务全部饿死。

被限流时的退避写法

麻烦在于方舟没有公布限流契约:没有文档化的状态码,也没有重试响应头。下面这段脚本记录真实收到的东西,而不是假定某个码。

import random
import time

import requests

ARK = "https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks"


def submit(payload, api_key, attempts=6):
    """Submit one task. Backs off on any non-2xx and keeps the first body seen."""
    for attempt in range(attempts):
        response = requests.post(
            ARK,
            headers={"Authorization": f"Bearer {api_key}"},
            json=payload,
            timeout=60,
        )
        if response.ok:
            return response.json()

        # Ark does not publish which code means throttled, so log it rather
        # than hardcoding a guess.
        print("status", response.status_code, response.text[:200])

        delay = min(2**attempt, 60) * (0.5 + random.random())
        time.sleep(delay)

    raise RuntimeError("gave up after repeated non-2xx responses")

这段里没有 Retry-After,是故意的。那是第三方网关的做法,不是方舟文档。

分辨率会改变额度

2.0 系列那一行按输出分辨率拆成两档:非 4k 档与上表一致,4k 档掉到 最大 RPM 企业用户:15 个人用户:15最大并发 企业用户:1 个人用户:1

2.0 的 4K 那一行

并发为 1 意味着 4k 档完全串行:第一条没跑完,第二条没地方去。拿 10 槽位的模型去排一批 4k,速率差 40 倍,并发差 10 倍。

2.5 只公布一组数字,没有分辨率分档,不要把 2.0 的 4K 数字搬过来。分辨率会不会改变 2.5 的天花板,官方没说。

官方没有公布什么

本次核查中官方没有公布:

  • 请求被限流时返回的 HTTP 状态码。本次读到的官方页面里没有 429,第三方的 429 处理是网关自己的做法。
  • 任何重试响应头,包括 Retry-AfterX-RateLimit-* 这一族。
  • 各接口的 QPS 上限。此前被引用的页面已是无关内容,本次读到的官方页面也没有这些数字。
  • 分辨率是否会改变 2.5 的天花板,以及队列深度、等待时长与成功率保证。
  • 免费额度。

两条已公布的细节容易漏掉。天花板是非刚性目标,官方写的是 非刚性保障,那些数字表达的是意图而不是承诺。提额路径很明确:如需提升,请联系客户经理或者提交 工单。

常见问题

Seedance 每分钟允许多少次请求? 模型列表写的是 最大 RPM: 企业用户: 600 个人用户: 180

同时能跑多少条任务? 最大并发: 企业用户: 10 个人用户: 3;2.0 的 4k 档只有 1

轮询算不算在限额里? 官方给的是请求速率,轮询就是请求。不留意的话,轮询循环会吃掉大部分请求预算。

限额是按 Key 还是按账号? 按账号:每个账号(含主账号下的所有子账号,合并计算)。服务拆到不同 Key 上,仍共用一个天花板。

被限流时返回什么错误? 官方没有公布。别在假定某个 429 上分支,把真实响应记下来;想知道一波突发要花多少钱,看价格公式,不是错误日志。

给队列定容量之前,把模型列表再核一遍。