星火X2.6 响应忽快忽慢、端侧排队还是云侧限流?

文章导读
先给判断路径:同一类请求时快时慢时,不要靠感觉猜是端侧排队还是云侧限流,而是在你自己的调用层加时间戳,把一次请求拆成「排队等待—首包到达—内容生成」三段,再与返回码、重试次数一起看。三段耗时分别看,能立刻把问题定位到端侧资源还是云侧服务;把三段混成一个总耗时,就只能看出「慢」,看不出「谁慢」。下面给出可直接套用的打点骨架、分组表头、返回码判读方法,以及用固定输入做对照测试的步骤。
📋 目录
  1. 一 在请求前后各打一个时间戳,拆出排队与生成两段耗时
  2. 二 端侧与云侧分别统计,不要把两边混在一个均值里
  3. 三 看返回码与重试次数,区分限流与本地排队
  4. 四 用固定输入做对照测试,同一时刻分别打端侧和云侧
  5. 五 根据结果决定调小并发还是改走另一侧
A A

先给判断路径:同一类请求时快时慢时,不要靠感觉猜是端侧排队还是云侧限流,而是在你自己的调用层加时间戳,把一次请求拆成「排队等待—首包到达—内容生成」三段,再与返回码、重试次数一起看。三段耗时分别看,能立刻把问题定位到端侧资源还是云侧服务;把三段混成一个总耗时,就只能看出「慢」,看不出「谁慢」。下面给出可直接套用的打点骨架、分组表头、返回码判读方法,以及用固定输入做对照测试的步骤。

端侧排队通常表现为请求还没发出去就已耗掉时间,云侧限流通常表现为首包等待抬升并伴随限流类返回码和重试增加。先用三段打点把两者分开,再用同一输入、同一时刻的对照测试确认差异是否稳定复现,最后才决定调小本地并发还是改走另一侧。返回码含义以所用版本的官方文档为准,不要按经验臆断。

在请求前后各打一个时间戳,拆出排队与生成两段耗时

打点位置建议放在四处在:进入调用层(还没拿本地并发令牌)、真正发出请求前、收到第一个内容分片时、流结束或抛出异常时。前两点之差就是端侧排队时间,后两点之差分别对应首包等待和生成耗时。字段命名固定下来,后面做统计才不会每次都改脚本:req_id、side、wait_ms、ttfb_ms、gen_ms、total_ms、status、retries、concurrency。

import time, uuid, logging

def call_model(client, prompt, side='cloud', concurrency=4):
    req_id = uuid.uuid4().hex[:12]
    t0 = time.monotonic()             # 进入调用层,排队开始计时
    t1 = t2 = t3 = None
    status, retries = 'ok', 0
    try:
        t1 = time.monotonic()         # 真正发出请求前
        stream = client.chat(prompt)  # 替换为实际 SDK 调用
        for chunk in stream:
            if t2 is None and chunk:
                t2 = time.monotonic() # 首个内容分片到达
        t3 = time.monotonic()
    except Exception as exc:
        t3 = time.monotonic()
        status = type(exc).__name__
        retries = getattr(exc, 'retries', 0)
    finally:
        logging.info('spark_timing req_id=%s side=%s wait_ms=%d ttfb_ms=%s gen_ms=%s total_ms=%d status=%s retries=%s',
                     req_id, side,
                     int(((t1 or t0) - t0) * 1000),
                     'NA' if t2 is None else int((t2 - t1) * 1000),
                     'NA' if t2 is None else int((t3 - t2) * 1000),
                     int((t3 - t0) * 1000), status, retries)

单条日志的形态大致如下,建议一行一条、便于后续按字段过滤:spark_timing req_id=a1b2c3 side=cloud wait_ms=12 ttfb_ms=1840 gen_ms=3200 total_ms=5052 status=ok retries=0 concurrency=4。如果业务里没有端侧排队队列,wait_ms 会长期接近 0,那慢就只可能出现在 ttfb_ms 或 gen_ms,这时前面两段打点的价值就是排除端侧。注意 gen_ms 与输出长度强相关,比较时必须先按输出长度分档。

端侧与云侧分别统计,不要把两边混在一个均值里

分组维度建议至少包含:调用侧(端侧自建服务还是云侧接口)、输入长度档(按 token 或字符数分桶,不要用连续值)、并发档(同一时刻的在途请求数)、是否流式、时间粒度(按分钟或按小时)。端侧和云侧的先验行为不同,混在一个均值里会被样本多的那一侧稀释,看上去「都还好」,实际上某一侧已经明显变慢。

星火X2.6 响应忽快忽慢、端侧排队还是云侧限流?

样本量方面,每组先累计到能看出趋势的量再下判断,几十条以内的中位数同样会被个位数晃得厉害;短时间只有几条记录时,宁可看原始日志也不要做结论。结果表头可以这样定,前后端共用一张表:

side输入长度档并发档样本数wait_ms 中位数/P95ttfb_ms 中位数/P95gen_ms 中位数/P95各返回类型计数
端侧短4—————
云侧短4—————

用中位数配 P95,不要只看平均值:偶发的长尾在平均值里会被摊平,在 P95 里会保留。表格里空格用「—」占位,跑到足够样本再回填,避免拿一两条记录当结论。

看返回码与重试次数,区分限流与本地排队

延迟要和返回类型对着看才有意义。同样的首包变慢,正常返回和限流返回指向的原因不同,处理动作也不同。下面是最常见的几类判读方向,具体错误码含义请回到所用 SDK/API 版本的官方文档核对,不要照搬别人的错误码表:

星火X2.6 响应忽快忽慢、端侧排队还是云侧限流?
返回类型耗时特征判读方向建议动作
正常返回,wait_ms 接近 0,ttfb_ms 抬升首包等待变长,生成段平稳云侧前置排队或服务端处理变慢看是否集中在固定时间段,先降并发观察
正常返回,wait_ms 明显抬高请求还没发出就耗时端侧本地队列或线程池积压核对本地并发上限与线程池配置
限流类返回码(如 429 一类)首包等待抬升,retries 上升云侧限流,重试越多越堵降并发或错峰,关注返回中的重试等待提示
5xx 服务端异常可能快速失败也可能超时服务端不稳定,不等于限流记录次数,配合重试上限
建连失败、读取超时ttfb_ms 直接为 NA网络链路或端侧出口问题查本地连接池、出口带宽与 DNS
认证或参数类 4xx失败很快配置问题重试无意义,先修配置

无法确定时,回到官方文档核对这几个字段:HTTP 状态码与响应体里的业务 code/message、响应头中的限流与重试提示字段、请求 ID 或链路 ID、SDK 异常类型与原始 message、SDK 是否在内部自动重试以及重试了几次。很多「忽快忽慢」其实是 SDK 内部重试造成的,日志里 retries 长期不为 0 就该先降并发再谈优化。

用固定输入做对照测试,同一时刻分别打端侧和云侧

输入长度不同会直接改变生成段耗时,测试必须固定输入。准备三档固定 prompt(短、中、长各一条),同一轮里交替向端侧和云侧各发一次,交替顺序能削弱时间漂移的影响。每轮记录字段保持与打点一致,另加 round、prompt_id、prompt_len、start_ts。

星火X2.6 响应忽快忽慢、端侧排队还是云侧限流?
for r in range(ROUNDS):
    for pid in ('short', 'mid', 'long'):
        for side in ('edge', 'cloud'):
            start = time.time()
            call_model(client_for(side), PROMPTS[pid], side=side, concurrency=CONC)
            record(round=r, prompt_id=pid, side=side, start_ts=start)

重复轮次不要只跑三五次,至少覆盖多个时间点(例如每轮间隔一段固定时间),否则分不清是稳定差异还是瞬时抖动。结果比对时按 prompt_id 配对:同一输入档下,比较端侧与云侧的 wait_ms、ttfb_ms 中位数与最大值,看差异是否在多数轮次里方向一致。方向一致才算稳定差异,忽正忽负说明当前波动还没被压住,先别改并发。

根据结果决定调小并发还是改走另一侧

测量结果要落到具体动作上,两种调整的触发条件不同:

  • 调小端侧并发:wait_ms 随并发档上升而上升、ttfb_ms 相对平稳,错误类型多指向本地队列满或线程池等待。动作是把本地并发上限按档位下调,风险是吞吐随之下降,需要结合业务高峰评估。
  • 改走另一侧或错峰:wait_ms 很低、ttfb_ms 集中在某些时间段抬高,且限流类返回码与 retries 同步上升。动作是降低对云侧的瞬时并发、把批量任务挪到压力较低的时段,或把部分流量分流到另一侧。风险是两侧输出质量可能有差异,分流前要按同一批固定输入比对生成结果。

调整后必须复测,复测方法就是上一节的固定输入对照测试:同样的 prompt、同样的轮次、同样的记录字段,重跑一遍,比较调整前后的 wait_ms、ttfb_ms 中位数与限流类返回的计数是否变化。变化不明显就把改动回滚,避免留下一个说不清作用的配置。整套流程的边界也要说清:三段打点只能定位时间花在哪一侧,不能证明某一侧「性能上限是多少」,容量结论仍然要结合官方配额说明与实际压测环境确认。