为什么普通请求超时设置不够

模型接口通常使用SSE返回多个data事件。首字之前可能包含排队和预填充,首字之后又可能因计算、网络或代理缓冲出现停顿。把所有请求设置为5秒总超时,会误伤正常长任务;完全没有上限,又可能让客户端和GPU长期被占用。

本文针对本地兼容/v1/chat/completions接口,使用Python requests展示最小文本客户端。前提是已有可用模型服务,本例model为qwen3-8b。不是所有厂商流式字段完全相同,接入新服务前核对其协议。

正确处理增量事件

python3 -m venv .client-env
source .client-env/bin/activate
pip install requests
import json,time,uuid,requests
request_id=str(uuid.uuid4())
payload={'model':'qwen3-8b','stream':True,'max_tokens':600,
 'chat_template_kwargs':{'enable_thinking':False},
 'messages':[{'role':'user','content':'分步骤说明GPU服务器验收。'}]}
start=time.perf_counter()
first=None
finished=False
try:
 with requests.post('http://127.0.0.1:8000/v1/chat/completions',
   json=payload,headers={'X-Request-ID':request_id},
   stream=True,timeout=(5,90)) as response:
  response.raise_for_status()
  response.encoding='utf-8'
  for line in response.iter_lines(decode_unicode=True):
   if time.perf_counter()-start>180:
    raise TimeoutError('客户端总预算180秒已到')
   if not line or not line.startswith('data:'):continue
   event=line[5:].strip()
   if event=='[DONE]':
    finished=True
    break
   data=json.loads(event)
   for choice in data.get('choices',[]):
    part=choice.get('delta',{}).get('content') or ''
    if part:
     if first is None:first=time.perf_counter()-start
     print(part,end='',flush=True)
    reason=choice.get('finish_reason')
    if reason:print('\nfinish_reason=',reason)
except KeyboardInterrupt:
 print('\n用户取消,关闭连接')
except (requests.RequestException,ValueError,TimeoutError) as exc:
 print('\n请求失败:',type(exc).__name__)
finally:
 print('\nrequest_id=',request_id,'ttft=',first,
       'elapsed=',time.perf_counter()-start,'done=',finished)

读取超时90秒表示单次网络读取等待上限,不是整个任务只能90秒。循环里的180秒预算在收到数据后检查;如果刚进入阻塞读取,退出可能还要等待读取超时,因此它不是精确硬截止。需要严格总预算时使用支持取消的异步客户端或独立任务控制,并测试阻塞场景。

空增量不是错误

一些事件只携带角色、结束原因或usage,没有正文。客户端应跳过空文本,而不是将整段JSON显示给读者。思考字段与最终content分开处理;错误事件、连接断开和[DONE]要明确区分。没有[DONE]的中断输出只能标记为不完整,不能自动当成文章正文发布。

finish_reason为length时提示用户输出达到预算。结构化任务必须拿完整内容再验证JSON,不按每个增量尝试入库。数据库只在确认完成后提交最终记录,中间文本可以作为暂存,但应带状态。

代理与浏览器路径也要检查

直接访问模型有流式输出,而经过网站变成一次性出现,优先检查反向代理缓冲和超时。不要只看浏览器Network里HTTP 200,要记录首个有效文本到达时间。压测时从客户端、网关和引擎三个位置关联同一个请求ID。

客户端发X-Request-ID不保证所有网关自动透传或服务端自动记录,必须实际核对日志。敏感提示词不直接写入长期开启的访问日志,可记录长度、哈希和任务类型。

取消并不总能立即释放GPU

关闭HTTP连接是客户端行为,推理服务是否停止计算取决于网关和引擎。测试中先发长任务,等待开始生成后Ctrl+C,再观察活跃请求与显存缓存。显存池保留不一定表示泄漏,应结合请求计数和缓存管理指标,而不是要求nvidia-smi立即回到零。

故障恢复避免盲目重试。一个长任务可能已在服务端完成,客户端仅丢失结果;有写操作的工具调用更可能产生重复执行。业务用运行ID区分尝试,确认幂等后再重试,失败结果保留供查看。

验收与回滚

测试正常完成、长等待、用户取消、代理断开、输出截断五种情形,每种记录状态和资源变化。更新网关或客户端后重跑,保留旧配置方便切回。该脚本是诊断客户端,不是完整生产SDK,未包含认证、持久队列和浏览器渲染。官方接口依据:vLLM兼容服务。

浏览器与后端的不同角色

网页前端通常需要保留当前任务状态,后端代理则负责认证、限流和与模型服务通信。浏览器离开页面时可发起取消,但网络断开不代表后端已经收到取消信息。应设计运行ID和状态查询,用户回来后可以知道任务完成、取消或失败,不因为页面刷新就自动重新提交。

如果业务需要保留生成结果,模型输出先进入暂存,确认完成后再形成最终记录。部分文本可以显示给用户,但下载和发布按钮应识别完整状态。没有结束事件或输出达到上限时,提示不完整,不悄悄把它当成正式方案。对于工具调用,流式参数也要等到完整结构后再验证,不能边接收边执行。

首字和逐字指标的实际定义

第一个网络事件可能只有角色或元数据,不能把它算成用户看到的首字。客户端代码以第一个非空文本作为起点,网关和服务端应使用对应定义。生成过程的停顿统计需记录每次有效文本间隔,不把不同大小的数据块当成相同数量token。只有服务提供可靠token统计时,才计算逐token时间。

一个请求的端到端时间包括排队、预填充、生成和网络。直接接口与网页之间差距大,检查代理缓冲、页面渲染和出口;两者都慢则检查模型队列和输入长度。将长短任务混在一个平均值里,会掩盖用户实际感受到的异常,应按类型分别报告。

重试和取消的业务代价

用户取消后仍在服务器计算,会浪费资源并影响其他请求。验证取消传播时观察引擎活跃请求,不仅看浏览器按钮变灰。服务保留显存池是正常优化,不因此立即断定泄漏;对比多次取消后的请求计数和内存趋势更有意义。

自动重试应只针对明确可重试的错误,设定次数、总预算与退避。网络中断时服务端可能已经执行完工具,必须用幂等键或人工确认防止重复。记录重试链路而不是删除失败日志,这样才能计算真实成本与成功率。正式系统还应为用户提供停止、查看状态和重新提交的明确操作,不能用一个旋转图标覆盖所有状态。

来源、翻译与版权说明

来源:网昱算力学院 · 技术编辑。第三方内容版权归原作者或发布机构所有;本站仅在许可证或明确授权允许时提供本地原文。

原文语言
ZH
原文更新时间
未提供
许可证
原创工程内容