From e90ce99588c926854781528c52baacf8fc12db74 Mon Sep 17 00:00:00 2001
From: Zhang Yulong <35552275+ZhangYulongg@users.noreply.github.com>
Date: Mon, 3 Aug 2026 11:31:55 +0800
Subject: [PATCH 1/2] Correct input_tokens accumulation in backend_request_func
Fix token accumulation by replacing '+=' with assignment.
---
benchmarks/backend_request_func.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/benchmarks/backend_request_func.py b/benchmarks/backend_request_func.py
index 8155b363a44..066d18d794d 100644
--- a/benchmarks/backend_request_func.py
+++ b/benchmarks/backend_request_func.py
@@ -857,7 +857,7 @@ async def async_request_eb_openai_chat_completions_multi_turn(
return outputs, metrics
# llm_cost = s1 - s0
- input_tokens += output.prompt_tokens
+ input_tokens = output.prompt_tokens
output_tokens += output.output_tokens
# 更新output_ids
@@ -971,7 +971,7 @@ async def async_request_eb_openai_chat_completions_multi_turn(
)
return outputs, metrics
- input_tokens += output.prompt_tokens
+ input_tokens = output.prompt_tokens
output_tokens += output.output_tokens
# 若session输入长度超过max_prompt_len,则停止session
if max_prompt_len and input_tokens >= max_prompt_len:
From c592278e221ebadf713d8733a8a5ab08f14b66f4 Mon Sep 17 00:00:00 2001
From: Zhang Yulong <35552275+ZhangYulongg@users.noreply.github.com>
Date: Mon, 3 Aug 2026 11:52:37 +0800
Subject: [PATCH 2/2] Delete benchmarks/backend_request_func_swe.py
---
benchmarks/backend_request_func_swe.py | 1530 ------------------------
1 file changed, 1530 deletions(-)
delete mode 100644 benchmarks/backend_request_func_swe.py
diff --git a/benchmarks/backend_request_func_swe.py b/benchmarks/backend_request_func_swe.py
deleted file mode 100644
index 3a70c7e6235..00000000000
--- a/benchmarks/backend_request_func_swe.py
+++ /dev/null
@@ -1,1530 +0,0 @@
-"""
-# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-
-# This file is modified from https://github.com/vllm-project/vllm/blob/main/benchmarks/backend_request_func.py
-
-
-import copy
-import io
-import json
-import logging
-import os
-import sys
-import time
-import traceback
-import uuid
-from dataclasses import dataclass, field
-from typing import Optional
-
-import aiohttp
-from tqdm.asyncio import tqdm
-
-AIOHTTP_TIMEOUT = aiohttp.ClientTimeout(total=6 * 60 * 60)
-
-
-@dataclass
-class RequestFuncInput:
- """Input for requesting LLMs via API"""
-
- no: int
- prompt: str
- history_QA: Optional[dict]
- hyper_parameters: dict
- api_url: str
- prompt_len: int
- output_len: int
- model: str
- model_name: Optional[str] = None
- logprobs: Optional[int] = None
- extra_body: Optional[dict] = None
- multi_modal_content: Optional[dict] = None
- ignore_eos: bool = False
- language: Optional[str] = None
- debug: bool = False
- pd_metrics: bool = False
- response_format: Optional[dict] = None
- random_flag: bool = False
- json_data: Optional[dict] = None
- prompt_token_ids: Optional[list] = None
- tokenizer_model: str = None
- tokenizer_path: str = None
- stream: bool = True
- session_id: Optional[str] = None
- turn_idx: Optional[int] = None
-
-
-@dataclass
-class RequestFuncOutput:
- """Output for requesting LLMs via API"""
-
- no: int = 0
- request_id: str = ""
- generated_text: str = ""
- reasoning_content: str = ""
- success: bool = False
- has_arrival_time: bool = False
- latency: float = 0.0
- end_timestamp: float = 0.0 # 模型完全返回的时间戳(秒, perf_counter基准)
- output_tokens: int = 0
- ttft: float = 0.0 # Time to first token
- arrival_time: list = field(default_factory=list) # arrival_time
- itl: list = field(default_factory=list) # list of inter-token latencies
- tpot: float = 0.0 # avg next-token latencies
- prompt_len: int = 0
- prompt_tokens: int = 0 # 推理侧返回输入token数
- reasoning_tokens: int = 0 # 思考长度
- res_ttft: int = 0 # 包含思考首token时延
- error: str = ""
- metrics: dict = field(default_factory=dict)
- tool_calls: list = field(default_factory=list)
- output_ids: list = field(default_factory=list)
-
-
-@dataclass
-class SessionMetrics:
- """多轮对话指标"""
-
- session_no: int
- session_e2e_time: float
- pure_llm_time: float
- input_tokens: int
- output_tokens: int
- tool_calls: int
-
-
-def safe_cost(a, b):
- """时间差计算"""
- if a is None or b is None:
- return None
- return a - b
-
-
-def metrics_summary(metrics, token_timestamps):
- """Summarize metrics"""
- if not metrics or len(token_timestamps) < 2:
- return {}
-
- m0 = metrics[0]
- m_last = metrics[-1]
-
- summary = {}
-
- arrival_time = m0.get("arrival_time")
- inference_start_time = m0.get("inference_start_time")
-
- # prefill 总耗时
- summary["prefill_cost_time"] = safe_cost(m0.get("send_request_output_to_decode_time"), arrival_time)
- # prefill准备总耗时
- summary["prefill_prepare_cost_time"] = safe_cost(inference_start_time, arrival_time)
- # 预处理耗时
- summary["preprocess_cost_time"] = safe_cost(m0.get("scheduler_recv_req_time"), arrival_time)
- # 请求缓存耗时
- summary["cache_in_scheduler_cost_time"] = safe_cost(
- m0.get("engine_get_req_time"), m0.get("scheduler_recv_req_time")
- )
- # 申请 decode资源耗时
- summary["ask_decode_resource_cost_time"] = safe_cost(
- m0.get("ask_decode_resource_finish_time"), m0.get("ask_decode_resource_start_time")
- )
- # scheduler调度耗时
- summary["schedule_cost_time"] = safe_cost(
- m0.get("inference_start_time"), m0.get("ask_decode_resource_finish_time")
- )
- # prefill 的首 token 推理耗时
- summary["prefill_first_token_infer_cost_time"] = safe_cost(
- m0.get("engine_recv_first_token_time"), inference_start_time
- )
- # prefill 等待 cache 传输耗时
- summary["wait_sending_cache_cost_time"] = safe_cost(
- m0.get("send_request_output_to_decode_time"), m0.get("wait_for_sending_cache_time")
- )
- # decode分配资源耗时
- summary["decode_preallocate_cost_time"] = safe_cost(
- m_last.get("decode_preallocate_req_time"), m_last.get("decode_recv_req_time")
- )
- # decode准备推理耗时
- summary["decode_prepare_cost_time"] = safe_cost(
- m_last.get("decode_inference_start_time"), m_last.get("decode_recv_first_token_time")
- )
- # decode次token推理耗时
- summary["decode_second_token_infer_cost_time"] = safe_cost(
- m_last.get("decode_recv_second_token_time"), m_last.get("decode_inference_start_time")
- )
- # 返回首 token 链路耗时
- summary["first_token_transmission_cost_time"] = safe_cost(
- token_timestamps[0], m_last.get("decode_recv_first_token_time")
- )
- # 返回次 token 链路耗时
- summary["second_token_transmission_cost_time"] = safe_cost(
- token_timestamps[1], m_last.get("decode_recv_second_token_time")
- )
-
- # MIX 模式下,scheduler调度耗时
- summary["mixed_schedule_cost_time"] = safe_cost(m0.get("inference_start_time"), m0.get("engine_get_req_time"))
- # MIX 模式下,返回首 token 链路耗时
- summary["mixed_first_token_transmission_cost_time"] = safe_cost(
- token_timestamps[0], m0.get("engine_recv_first_token_time")
- )
-
- summary["gpu_cache_token_num"] = m0.get("gpu_cache_token_num")
- summary["cpu_cache_token_num"] = m0.get("cpu_cache_token_num")
- summary["storage_cache_token_num"] = m0.get("storage_cache_token_num")
- summary["cpu_cache_prepare_time"] = m0.get("cpu_cache_prepare_time")
- summary["storage_cache_prepare_time"] = m0.get("storage_cache_prepare_time")
-
- return summary
-
-
-def load_tokenizer(model, actor_tokenizer_path):
- """加载tokenizer"""
- from ernie_tokenizer import Ernie5Tokenizer, ErnieBotTokenizer
- from paddleformers.transformers import AutoTokenizer
-
- from fastdeploy.input.ernie4_5_tokenizer import Ernie4_5Tokenizer
-
- vocab_file_names = ["tokenizer.model", "spm.model", "ernie_token_100k.model"]
-
- try:
- if model == "eb":
- for i in range(len(vocab_file_names)):
- if os.path.exists(os.path.join(actor_tokenizer_path, vocab_file_names[i])):
- ErnieBotTokenizer.resource_files_names["vocab_file"] = vocab_file_names[i]
- break
- tokenizer = ErnieBotTokenizer.from_pretrained(actor_tokenizer_path)
- elif model == "eb_mm":
- for vocab_file in vocab_file_names:
- full_path = os.path.join(actor_tokenizer_path, vocab_file)
- if os.path.exists(full_path):
- Ernie4_5Tokenizer.resource_files_names["vocab_file"] = vocab_file
- # for i in range(len(vocab_file_names)):
- # if os.path.exists(os.path.join(actor_tokenizer_path, vocab_file_names[i])):
- # Ernie45Tokenizer.resource_files_names["vocab_file"] = vocab_file_names[i]
- # break
- tokenizer = Ernie4_5Tokenizer.from_pretrained(actor_tokenizer_path)
- # tokenizer.ignored_index = -100
- elif model == "eb5":
- for i in range(len(vocab_file_names)):
- if os.path.exists(os.path.join(actor_tokenizer_path, vocab_file_names[i])):
- Ernie5Tokenizer.resource_files_names["vocab_file"] = vocab_file_names[i]
- break
- tokenizer = Ernie5Tokenizer.from_pretrained(actor_tokenizer_path)
- else:
- print("tokenizer: AUTO")
- tokenizer = AutoTokenizer.from_pretrained(actor_tokenizer_path, padding_side="left", use_fast=True)
- except Exception as e:
- tokenizer = None
- logging.warning(f"Load tokenizer error: {e}")
-
- return tokenizer
-
-
-async def handle_non_stream_response(
- response,
- output,
- st,
-):
- """
- 处理非流式返回
- """
- text = await response.text()
-
- timestamp = time.perf_counter()
- data = json.loads(text)
- # print("data:", data)
-
- request_id = data.get("id", "None")
-
- usage = data.get("usage", {})
-
- output.output_tokens = usage.get("completion_tokens", 0)
- output.prompt_tokens = usage.get("prompt_tokens", 0)
-
- if output.prompt_len == 0:
- if usage.get("prompt_tokens_details", {}):
- output.prompt_len = usage.get("prompt_tokens_details", {}).get("cached_tokens", 0)
-
- choices = data.get("choices", [])
-
- if choices:
- message = choices[0].get("message", {})
-
- output.generated_text = message.get("content", "") or ""
- output.reasoning_content = message.get("reasoning_content", "") or ""
-
- completion_token_ids = message.get("completion_token_ids", [])
- if completion_token_ids:
- output.output_ids.extend(completion_token_ids)
-
- # tool calls
- tool_calls = message.get("tool_calls") or []
-
- for tc in tool_calls:
- func = tc.get("function", {})
-
- try:
- args = json.loads(func.get("arguments", "{}"))
- except Exception:
- args = {}
-
- output.tool_calls.append(
- {
- "id": tc.get("id"),
- "name": func.get("name"),
- "arguments": args,
- }
- )
-
- latency = timestamp - st
-
- # 非流式没有ttft
- output.ttft = latency
- output.res_ttft = latency
-
- output.end_timestamp = timestamp
- output.latency = latency
- # 非流式没有stream chunk
- # 非流式兼容stream benchmark逻辑
- # arrival_time:
- output.arrival_time = []
-
- has_text = bool(output.generated_text) or bool(output.reasoning_content)
-
- has_tool = bool(output.tool_calls)
-
- if not has_text and not has_tool:
- output.success = False
- output.error = "No generated text found!"
- else:
- output.success = True
-
- return data, request_id
-
-
-async def async_request_eb_openai_chat_completions(
- request_func_input: RequestFuncInput,
- pbar: Optional[tqdm] = None,
- session: aiohttp.ClientSession | None = None,
-) -> RequestFuncOutput:
- """Request an LLM using EB OpenAI"""
- api_url = request_func_input.api_url
- assert api_url.endswith(("completions", "profile")), "OpenAI Chat Completions API URL must end with 'completions'."
-
- own_session = session is None
- if own_session:
- session = aiohttp.ClientSession(
- trust_env=True,
- read_bufsize=10 * 1024 * 1024,
- timeout=AIOHTTP_TIMEOUT,
- )
-
- content = [{"type": "text", "text": request_func_input.prompt}]
- if request_func_input.multi_modal_content:
- content.append(request_func_input.multi_modal_content)
- # print("######json_data:", request_func_input.json_data)
- payload = {
- "model": request_func_input.model,
- "messages": request_func_input.history_QA,
- "stream": request_func_input.stream,
- "collect_metrics": request_func_input.pd_metrics,
- }
-
- if request_func_input.output_len is not None:
- payload["max_tokens"] = request_func_input.output_len
-
- # 流式模式返回usage
- if request_func_input.stream:
- payload["stream_options"] = {
- "include_usage": True,
- "continuous_usage_stats": True,
- }
- if request_func_input.json_data:
- json_data = request_func_input.json_data
-
- if json_data.get("max_tokens"):
- payload["max_tokens"] = json_data["max_tokens"]
-
- if json_data.get("min_tokens"):
- payload["min_tokens"] = json_data["min_tokens"]
- if request_func_input.response_format:
- payload["response_format"] = request_func_input.response_format
-
- # 随机输入开关
- if request_func_input.random_flag:
- payload["max_tokens"] = request_func_input.output_len
- payload["min_tokens"] = request_func_input.output_len
- # 随机token_ids场景
- if isinstance(request_func_input.prompt, list):
- request_func_input.prompt_token_ids = request_func_input.prompt
- request_func_input.prompt = ""
-
- # 支持传入prompt_token_ids
- if request_func_input.prompt_token_ids:
- # 不走messages
- payload["messages"] = [{"role": "user", "content": [{"type": "text", "text": ""}]}]
- payload["prompt_token_ids"] = request_func_input.prompt_token_ids
- payload["return_token_ids"] = True
- # print("use_token_ids:", payload)
-
- # 超参由yaml传入
- payload.update(request_func_input.hyper_parameters)
-
- # tools信息,yaml优先级最高
- json_data = request_func_input.json_data or {}
- hyper = request_func_input.hyper_parameters or {}
-
- tools = None
- tool_choice = None
-
- if hyper.get("tools"):
- tools = hyper.get("tools")
- tool_choice = hyper.get("tool_choice", "auto")
- elif json_data.get("tools"):
- tools = json_data.get("tools")
- tool_choice = json_data.get("tool_choice", "auto")
-
- if tools:
- payload["tools"] = tools
- payload["tool_choice"] = tool_choice
-
- # 随机输入开关
- if request_func_input.random_flag:
- payload["max_tokens"] = request_func_input.output_len
- metadata = payload.get("metadata", {})
- metadata["min_tokens"] = request_func_input.output_len
- payload["metadata"] = metadata
-
- if request_func_input.ignore_eos:
- payload["ignore_eos"] = request_func_input.ignore_eos
-
- headers = {
- "Content-Type": "application/json",
- "Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}",
- }
-
- if request_func_input.session_id is not None:
- headers["X-SMG-Routing-Key"] = f"{request_func_input.session_id}"
- if request_func_input.session_id is not None and request_func_input.turn_idx is not None:
- headers["X-Request-Id"] = f"{request_func_input.session_id}:{request_func_input.turn_idx}"
-
- output = RequestFuncOutput()
- output.prompt_len = 0
- output.no = request_func_input.no
- payload["no"] = request_func_input.no
- if request_func_input.debug:
- print(f"payload:{json.dumps(payload, ensure_ascii=False)}")
- metrics_list = []
- request_id = "None"
-
- ttft = 0.0
- res_ttft = 0.0
- st = time.perf_counter()
- most_recent_timestamp = st
- last_chunk_timestamp = st
- token_timestamps = []
- tool_call_buffer = {}
- # 用于 buffer burst 修正:累计上一次"真增量 chunk"结束时服务端已输出的 token 数。
- last_output_len = 0
- try:
- async with session.post(url=api_url, json=payload, headers=headers, read_bufsize=10 * 1024 * 1024) as response:
- data = {}
- if response.status == 200:
- # 默认流式模式
- if request_func_input.stream:
- # Reader loop 保持极简:只记录收包时间和原始 chunk,避免解析/拼接影响 ITL。
- stream_chunks = []
- async for chunk_bytes in response.content:
- timestamp = time.perf_counter()
- wall_timestamp = time.time()
- chunk_bytes = chunk_bytes.strip()
- if not chunk_bytes:
- continue
- if chunk_bytes in (b"data: [DONE]", b"[DONE]"):
- break
- stream_chunks.append((chunk_bytes, timestamp, wall_timestamp))
-
- generated_text_parts = []
- reasoning_content_parts = []
- for chunk_bytes, timestamp, wall_timestamp in stream_chunks:
- chunk = chunk_bytes.decode("utf-8").removeprefix("data: ")
- if chunk == "[DONE]":
- continue
- # print("####chunk:", chunk, type(chunk))
- data = json.loads(chunk)
-
- # 新增:捕获服务端流式 error
- if "error" in data:
- err = data["error"]
-
- output.success = False
- output.error = err.get("message", str(err))
-
- # 可选:保存更多信息
- output.error_type = err.get("type")
- output.error_code = err.get("code")
-
- print("####server error:", json.dumps(err, ensure_ascii=False))
-
- break
- # print("####data:", json.dumps(data, indent=2, ensure_ascii=False))
-
- if "metrics" in data:
- metrics_list.append(data["metrics"])
-
- if request_id == "None" and "id" in data:
- request_id = data["id"]
-
- if choices := data.get("choices"):
- content = choices[0]["delta"].get("content")
- reason_content = choices[0]["delta"].get("reasoning_content")
- tool_calls = choices[0]["delta"].get("tool_calls")
- completion_token_ids = choices[0]["delta"].get("completion_token_ids", [])
- has_token_chunk = bool(content or reason_content or tool_calls or completion_token_ids)
- if tool_calls:
- for tc in tool_calls:
- idx = tc.get("index", 0)
-
- if idx not in tool_call_buffer:
- tool_call_buffer[idx] = {
- "id": tc.get("id"),
- "name": "",
- "arguments": "",
- }
-
- if tc.get("id"):
- tool_call_buffer[idx]["id"] = tc["id"]
-
- func = tc.get("function", {})
-
- if func.get("name"):
- tool_call_buffer[idx]["name"] = func["name"]
-
- if func.get("arguments"):
- tool_call_buffer[idx]["arguments"] += func["arguments"]
-
- # 过滤 role / finish / usage 等空包,只用真正 token 包统计 TTFT/ITL。
- if has_token_chunk:
- # First token
- if ttft == 0.0:
- ttft = timestamp - st
- output.ttft = ttft
- # cached_tokens
- usage = data.get("usage") or {}
-
- if usage.get("prompt_tokens_details"):
- output.prompt_len = usage.get("prompt_tokens_details", {}).get(
- "cached_tokens", 0
- )
- else:
- output.prompt_len = 0
-
- # 首 token 也要更新 last_output_len,用于后续 burst 摊分
- cur_completion_tokens = (data.get("usage") or {}).get("completion_tokens")
- if cur_completion_tokens is None and completion_token_ids:
- # 没有 usage 时退化为按 token_ids 长度推断
- cur_completion_tokens = len(output.output_ids) + len(completion_token_ids)
- if cur_completion_tokens is not None:
- last_output_len = cur_completion_tokens
- else:
- last_output_len = 1
-
- # Decoding phase
- else:
- # buffer burst 修正:如果服务端把多个 token 合并到同一个流式 chunk 里,
- # 直接把整段间隔记成单个 ITL 会高估解码间隔。这里参考 sglang 官方
- # bench_serving 的做法:用真实 token 增量摊分该 chunk 的等待时间。
- cur_completion_tokens = (data.get("usage") or {}).get("completion_tokens")
- if cur_completion_tokens is None and completion_token_ids:
- cur_completion_tokens = len(output.output_ids) + len(completion_token_ids)
-
- chunk_gap = timestamp - most_recent_timestamp
- if cur_completion_tokens is not None:
- num_new_tokens = cur_completion_tokens - last_output_len
- if num_new_tokens <= 0:
- # 没有真实新 token(罕见的 usage/状态包),跳过 ITL 记录
- most_recent_timestamp = timestamp
- continue
- adjust_itl = chunk_gap / num_new_tokens
- output.itl.extend([adjust_itl] * num_new_tokens)
- last_output_len = cur_completion_tokens
- else:
- # 拿不到 completion_tokens 时退化为旧逻辑
- output.itl.append(chunk_gap)
-
- most_recent_timestamp = timestamp
- token_timestamps.append(wall_timestamp)
-
- # response首token
- if res_ttft == 0.0:
- if content:
- res_ttft = choices[0].get("arrival_time", timestamp - st)
- output.res_ttft = res_ttft
- usage = data.get("usage") or {}
- output.reasoning_tokens = max(usage.get("completion_tokens", 0) - 1, 0)
-
- if content:
- generated_text_parts.append(content)
- if reason_content:
- reasoning_content_parts.append(reason_content)
- if completion_token_ids:
- output.output_ids.extend(completion_token_ids)
- # print(f"####content:{data}")
- arrival = choices[0].get("arrival_time")
- if arrival is not None and has_token_chunk:
- output.has_arrival_time = True
- output.arrival_time.append(arrival)
- elif usage := data.get("usage", {}):
- output.output_tokens = usage.get("completion_tokens", 0)
- output.prompt_tokens = usage.get("prompt_tokens", 0)
- prompt_tokens_details = usage.get("prompt_tokens_details") or {}
- if output.prompt_len == 0:
- output.prompt_len = prompt_tokens_details.get("cached_tokens", 0)
-
- last_chunk_timestamp = timestamp
-
- output.generated_text = "".join(generated_text_parts)
- output.reasoning_content = "".join(reasoning_content_parts)
- # 在流式结束时,记录最后一个非 DONE chunk 收到的时间戳
- output.end_timestamp = last_chunk_timestamp
- # 截断case
- usage = data.get("usage", {})
- output.output_tokens = usage.get("completion_tokens", 0)
- output.prompt_tokens = usage.get("prompt_tokens", 0)
- if output.prompt_len == 0:
- prompt_details = usage.get("prompt_tokens_details") or {}
- output.prompt_len = prompt_details.get("cached_tokens", 0)
-
- if tool_call_buffer:
- for _, tc in tool_call_buffer.items():
- try:
- args = json.loads(tc["arguments"]) if tc["arguments"] else {}
- except:
- args = {}
-
- output.tool_calls.append({"id": tc["id"], "name": tc["name"], "arguments": args})
-
- # 如果没有thinking内容,则response首token等于ttft
- if not output.reasoning_content:
- output.res_ttft = output.ttft
- # 新增metrics统计,计算首token过滤空包
- output.metrics = metrics_summary(metrics_list, token_timestamps[1:])
-
- has_text = bool(output.generated_text) or bool(output.reasoning_content)
- has_tool = getattr(output, "tool_calls", None)
-
- # 如果前面已经有服务端错误,保留原错误
- if output.error:
- output.success = False
- # 兼容思考内容超长截断的情况,此时回复内容为空
- elif not has_text and not has_tool:
- output.success = False
- output.reasoning_tokens = output.output_tokens
- output.error = "No generated text found!"
- else:
- output.success = True
- output.latency = most_recent_timestamp - st
- else:
- # 非流式模式
- data, request_id = await handle_non_stream_response(
- response=response,
- output=output,
- st=st,
- )
- else:
- error_text = await response.text()
- print(
- "####error response:",
- error_text,
- "####payload:",
- payload,
- )
- output.error = error_text or ""
- output.success = False
- except Exception:
- output.success = False
- exc_info = sys.exc_info()
- output.error = "".join(traceback.format_exception(*exc_info))
- finally:
- if own_session:
- await session.close()
-
- output.request_id = request_id
-
- # 保存失败请求结果
- if not output.success or output.output_tokens == 0:
- with open("error_output.txt", "a") as f:
- f.write(str(output) + "\n")
- if pbar:
- pbar.update(1)
- if request_func_input.debug:
- print("#####final_output:", output)
- return output
-
-
-async def simple_tool_call(model_output, tool_url: str, timeout=60):
- """调用工具函数"""
-
- import httpx
-
- tool_id = None
-
- if getattr(model_output, "tool_calls", None):
- tc = model_output.tool_calls[0]
- tool_name = tc["name"]
- args = tc.get("arguments", {})
- tool_id = tc.get("id")
- else:
- # 取消正则逻辑
- return "", False, "", tool_id
- # match = re.search(r"(.*?)", model_output.generated_text, re.S)
- # if not match:
- # return "", False, "", tool_id
- #
- # block = match.group(1).strip()
- # lines = block.splitlines()
- # tool_name = lines[0].strip()
- #
- # key = re.search(r"(.*?)", block)
- # val = re.search(r"(.*?)", block)
- #
- # args = {key.group(1): val.group(1)} if key and val else {}
-
- if not tool_name:
- return "", False, "", tool_id
-
- headers = {"Content-Type": "application/json"}
-
- try:
- async with httpx.AsyncClient(timeout=timeout) as client:
- resp = await client.post(
- tool_url,
- headers=headers,
- json={"tool_name": tool_name, "arguments": args},
- )
-
- resp.raise_for_status()
- obj = resp.json()
-
- return obj.get("result", resp.text), "result" in obj, tool_name, tool_id
-
- except Exception as e:
- print(f"[TOOL ERROR] {tool_name}: {repr(e)}")
- return str(e), False, tool_name, tool_id
-
-
-async def async_request_eb_openai_chat_completions_multi_turn(
- request_func_input: RequestFuncInput,
- pbar: Optional[tqdm] = None,
-):
- # yaml中或数据集中带tools才走工具调用逻辑
- json_data = request_func_input.json_data or {}
- hyper = request_func_input.hyper_parameters or {}
- # enable_tools = bool(json_data.get("tools") or hyper.get("tools"))
- # SWE数据集无工具可调用
- enable_tools = False
-
- outputs = []
-
- tool_call_count = 0
- llm_time = 0.0
- tool_time = 0.0
- input_tokens = 0
- output_tokens = 0
-
- ori_history = request_func_input.history_QA
- user_count = sum(msg.get("role") == "user" for msg in ori_history)
- print("START", request_func_input.no, "user对话轮数:", user_count, flush=True)
- history = []
- prompt_no = 0
- max_prompt_len = (
- hyper.get("max_prompt_len") if hyper.get("max_prompt_len") is not None else json_data.get("max_prompt_len")
- )
- print("max_prompt_len:", max_prompt_len)
- input_ids_all = []
- # FD每轮 completion_token_ids
- output_ids = []
- use_token_ids = bool(request_func_input.tokenizer_model and request_func_input.tokenizer_path)
- tokenizer = None
-
- if use_token_ids:
- print("token ids 拼接模式")
- enable_tools = False
- print("tokenizer_model:", request_func_input.tokenizer_model)
- print("tokenizer_path:", request_func_input.tokenizer_path)
- tokenizer = load_tokenizer(
- request_func_input.tokenizer_model,
- request_func_input.tokenizer_path,
- )
- else:
- print("messages 明文拼接模式")
-
- # 只创建一次 session
- session_start = time.perf_counter()
- session_uuid = uuid.uuid4().hex
- connector = aiohttp.TCPConnector(
- limit=0,
- limit_per_host=0,
- keepalive_timeout=60,
- )
-
- async with aiohttp.ClientSession(
- connector=connector,
- trust_env=True,
- read_bufsize=10 * 1024 * 1024,
- timeout=AIOHTTP_TIMEOUT,
- ) as session:
- for i, message in enumerate(ori_history):
- if message["role"] == "user" or message["role"] == "tool":
- history.append(message)
- round_input = copy.deepcopy(request_func_input)
- round_input.history_QA = history
- round_input.no = f"{round_input.no}_{prompt_no}"
- round_input.session_id = f"{session_uuid}:{request_func_input.no}"
- round_input.turn_idx = prompt_no
- if use_token_ids:
- if len(input_ids_all) == 0:
- # 拼接token_ids模式,首轮token_ids
- spliced_text = tokenizer.apply_chat_template(
- history,
- tokenize=False,
- split_special_tokens=False,
- add_special_tokens=False,
- )
- # 转换为token ids
- tokens = tokenizer.tokenize(spliced_text)
- prompt_token_ids = tokenizer.convert_tokens_to_ids(tokens)
- input_ids_all.extend(prompt_token_ids)
- round_input.prompt_token_ids = input_ids_all
- else:
- prompt_length = len(input_ids_all) + len(output_ids)
- if max_prompt_len and prompt_length >= max_prompt_len:
- # 超长截断
- print(
- f"[SESSION STOP] {round_input.no} reach max_prompt_len={max_prompt_len}, stop session"
- )
- break
- # 拼接token_ids模式,后续轮
- input_ids_all.extend(output_ids)
- user_prompt = message["content"]
- # 拼接user_prompt
- if round_input.tokenizer_model == "eb5":
- # EB5模型
- user_prompt = (
- f"\n\n<|im_start|>user\n{user_prompt}<|im_end|>\n\n<|im_start|>assistant\n\n"
- )
- else:
- # 0.3B模型,2 ,拼接时会被替换成100272 <|end_of_sentence|>
- input_ids_all[-1] = 100272
- user_prompt = f"User: {user_prompt}\nAssistant: "
- prompt_token_ids = tokenizer.convert_tokens_to_ids(tokenizer.tokenize(user_prompt))
- input_ids_all.extend(prompt_token_ids)
- round_input.prompt_token_ids = input_ids_all
- # 复用 session
- s0 = time.perf_counter()
- output = await async_request_eb_openai_chat_completions(
- round_input,
- pbar=None,
- session=session,
- )
- s1 = time.perf_counter()
- llm_time += s1 - s0
-
- outputs.append(output)
-
- if not output.success:
- if enable_tools:
- # 有工具调用时,请求失败直接中断整个session
- print(f"[SESSION STOP] {round_input.no} request failed with tools, stop session")
- break
- # SWE无工具调用时,跳过当前轮但继续session
- print(f"[SESSION WARN] {round_input.no} request failed, continue session")
- prompt_no += 1
- continue
-
- # llm_cost = s1 - s0
- input_tokens += output.prompt_tokens
- output_tokens += output.output_tokens
-
- # 更新output_ids
- output_ids = output.output_ids
-
- if max_prompt_len and input_tokens >= max_prompt_len:
- # 后验超长截断
- print(f"[SESSION STOP] {round_input.no} reach max_prompt_len={max_prompt_len}, stop session")
- break
-
- if enable_tools:
- # 循环调用工具
- max_loop = json_data.get("max_loop", 10)
- tool_url = json_data.get("tool_url", "")
- max_prompt_len = json_data.get("max_prompt_len")
- if not tool_url:
- raise ValueError("tool_url is empty.")
- session_stopped = False
- for _ in range(max_loop):
- t0 = time.perf_counter()
- tool_result, is_tool_result, tool_name, tool_id = await simple_tool_call(
- output,
- tool_url,
- )
- t1 = time.perf_counter()
- tool_time += t1 - t0
- # print(f"#### tool_time: {t1 - t0:.3f}")
- # print(f"#### tool_result: {tool_result}")
- # print(f"#### is_tool_result: {is_tool_result}")
-
- # 工具调用失败,中断整个session
- if tool_name and not is_tool_result:
- print(f"[SESSION STOP] tool call failed: {tool_name}, stop session")
-
- output.success = False
- tool_call_count += 1
- session_stopped = True
- break
-
- if not is_tool_result:
- history.append(
- {
- "role": "assistant",
- "content": output.generated_text,
- }
- )
- break
-
- assistant_msg = {
- "role": "assistant",
- "content": output.generated_text,
- }
-
- if getattr(output, "tool_calls", None):
- assistant_msg["tool_calls"] = [
- {
- "id": tc["id"],
- "type": "function",
- "function": {
- "name": tc["name"],
- "arguments": json.dumps(tc["arguments"], ensure_ascii=False),
- },
- }
- for tc in output.tool_calls
- ]
-
- history.append(assistant_msg)
-
- history.append(
- {
- "role": "tool",
- "content": json.dumps(tool_result, ensure_ascii=False),
- "tool_call_id": tool_id or tool_name,
- }
- )
- tool_call_count += 1
-
- round_input.history_QA = history
-
- s0 = time.perf_counter()
- output = await async_request_eb_openai_chat_completions(
- round_input,
- pbar=None,
- session=session,
- )
- s1 = time.perf_counter()
- llm_time += s1 - s0
-
- outputs.append(output)
-
- if not output.success:
- print(f"[SESSION STOP] {round_input.no} tool loop request failed, stop session")
- session_stopped = True
- break
-
- input_tokens += output.prompt_tokens
- output_tokens += output.output_tokens
- # 若session输入长度超过max_prompt_len,则停止session
- if max_prompt_len and input_tokens >= max_prompt_len:
- print(
- f"[SESSION STOP] {round_input.no} reach max_prompt_len={max_prompt_len}, stop session"
- )
- session_end = time.perf_counter()
- metrics = SessionMetrics(
- session_no=request_func_input.no,
- session_e2e_time=session_end - session_start,
- pure_llm_time=llm_time,
- input_tokens=input_tokens,
- output_tokens=output_tokens,
- tool_calls=tool_call_count,
- )
- return outputs, metrics
- else:
- print(f"Warning {prompt_no} exceed max_loop={max_loop}, force stop tool loop")
-
- if session_stopped:
- # 工具调用失败,中断整个session
- break
-
- else:
- # 无tools
- # history.append(
- # {
- # "role": "assistant",
- # "content": output.generated_text,
- # }
- # )
- # SWE数据集拒绝交互,直接用数据集里的模型返回
- pass
-
- prompt_no += 1
- elif message["role"] == "assistant":
- # continue
- # SWE数据集拒绝交互,直接用数据集里的模型返回
- history.append(message)
- else:
- history.append(message)
-
- session_end = time.perf_counter()
- session_e2e_time = session_end - session_start
-
- if pbar:
- pbar.update(1)
-
- metrics = SessionMetrics(
- session_no=request_func_input.no,
- session_e2e_time=session_e2e_time,
- pure_llm_time=llm_time,
- input_tokens=input_tokens,
- output_tokens=output_tokens,
- tool_calls=tool_call_count,
- )
-
- return outputs, metrics
-
-
-async def async_request_eb_openai_completions(
- request_func_input: RequestFuncInput,
- pbar: Optional[tqdm] = None,
-) -> RequestFuncOutput:
- """Request an LLM using EB OpenAI"""
- api_url = request_func_input.api_url
- assert api_url.endswith(
- ("completions", "profile")
- ), "OpenAI Completions API URL must end with 'completions' or 'profile'."
-
- async with aiohttp.ClientSession(
- trust_env=True, read_bufsize=10 * 1024 * 1024, timeout=AIOHTTP_TIMEOUT
- ) as session:
- payload = {
- "model": request_func_input.model,
- "prompt": request_func_input.prompt,
- "stream": True,
- "stream_options": {
- "include_usage": True,
- "continuous_usage_stats": True,
- },
- }
- # 超参由yaml传入
- payload.update(request_func_input.hyper_parameters)
-
- if request_func_input.ignore_eos:
- payload["ignore_eos"] = request_func_input.ignore_eos
-
- if request_func_input.debug:
- print("payload:", json.dumps(payload, ensure_ascii=False))
-
- headers = {
- "Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}",
- "Content-Type": "application/json",
- }
-
- output = RequestFuncOutput()
- output.prompt_len = request_func_input.prompt_len
- output.no = request_func_input.no
-
- generated_text = ""
- ttft = 0.0
- st = time.perf_counter()
- most_recent_timestamp = st
- try:
- async with session.post(url=api_url, json=payload, headers=headers) as response:
- if response.status == 200:
- first_chunk_received = False
- async for chunk_bytes in response.content:
- chunk_bytes = chunk_bytes.strip()
- if not chunk_bytes:
- continue
-
- chunk = chunk_bytes.decode("utf-8").removeprefix("data: ")
- if chunk != "[DONE]":
- # print("####chunk:", chunk, chunk.usage)
- timestamp = time.perf_counter()
- data = json.loads(chunk)
-
- # NOTE: Some completion API might have a last
- # usage summary response without a token so we
- # want to check a token was generated
- if choices := data.get("choices"):
- # Note that text could be empty here
- # e.g. for special tokens
- text = choices[0].get("text")
-
- # First token
- if not first_chunk_received:
- first_chunk_received = True
- ttft = timestamp - st
- output.ttft = ttft
-
- # Decoding phase
- else:
- output.itl.append(timestamp - most_recent_timestamp)
-
- generated_text += text or ""
-
- most_recent_timestamp = timestamp
- output.arrival_time.append(choices[0].get("arrival_time", timestamp))
- elif usage := data.get("usage"):
- output.prompt_tokens = usage.get("prompt_tokens")
- output.output_tokens = usage.get("completion_tokens")
- if first_chunk_received:
- output.success = True
- else:
- output.success = False
- output.error = (
- "Never received a valid chunk to calculate TTFT." "This response will be marked as failed!"
- )
-
- output.generated_text = generated_text
- output.latency = most_recent_timestamp - st
-
- if output.generated_text == "":
- output.success = False
- output.error = "No generated text found!"
- else:
- output.success = True
- else:
- output.error = response.reason or ""
- output.success = False
- except Exception:
- output.success = False
- exc_info = sys.exc_info()
- output.error = "".join(traceback.format_exception(*exc_info))
-
- if request_func_input.debug:
- print(f"final_output:{output}")
-
- if pbar:
- pbar.update(1)
- return output
-
-
-async def async_request_tgi(
- request_func_input: RequestFuncInput,
- pbar: Optional[tqdm] = None,
-) -> RequestFuncOutput:
- """Request an LLM using the TGI API"""
- api_url = request_func_input.api_url
- assert api_url.endswith("generate_stream")
-
- async with aiohttp.ClientSession(trust_env=True, timeout=AIOHTTP_TIMEOUT) as session:
- params = {
- "max_new_tokens": request_func_input.output_len,
- "do_sample": True,
- "temperature": 0.01, # TGI does not accept 0.0 temperature.
- "top_p": 0.99, # TGI does not accept 1.0 top_p.
- "truncate": request_func_input.prompt_len,
- "ignore_eos_token": request_func_input.ignore_eos,
- }
- payload = {
- "inputs": request_func_input.prompt,
- "parameters": params,
- }
- output = RequestFuncOutput()
- output.prompt_len = request_func_input.prompt_len
- if request_func_input.ignore_eos:
- output.output_tokens = request_func_input.output_len
- else:
- output.output_tokens = None
-
- ttft = 0.0
- st = time.perf_counter()
- most_recent_timestamp = st
- try:
- async with session.post(url=api_url, json=payload) as response:
- if response.status == 200:
- async for chunk_bytes in response.content:
- chunk_bytes = chunk_bytes.strip()
- if not chunk_bytes:
- continue
- chunk_bytes = chunk_bytes.decode("utf-8")
-
- # NOTE: Sometimes TGI returns a ping response without
- # any data, we should skip it.
- if chunk_bytes.startswith(":"):
- continue
- chunk = chunk_bytes.removeprefix("data:")
-
- data = json.loads(chunk)
- timestamp = time.perf_counter()
- # First token
- if ttft == 0.0:
- ttft = time.perf_counter() - st
- output.ttft = ttft
-
- # Decoding phase
- else:
- output.itl.append(timestamp - most_recent_timestamp)
-
- most_recent_timestamp = timestamp
- output.arrival_time.append(data["arrival_time"])
-
- output.latency = most_recent_timestamp - st
- output.success = True
- output.generated_text = data["generated_text"]
- else:
- output.error = response.reason or ""
- output.success = False
- except Exception:
- output.success = False
- exc_info = sys.exc_info()
- output.error = "".join(traceback.format_exception(*exc_info))
-
- if pbar:
- pbar.update(1)
- return output
-
-
-async def async_request_trt_llm(
- request_func_input: RequestFuncInput,
- pbar: Optional[tqdm] = None,
-) -> RequestFuncOutput:
- """Request an LLM using TRT's llm_server"""
- api_url = request_func_input.api_url
- assert api_url.endswith("generate_stream")
-
- async with aiohttp.ClientSession(trust_env=True, timeout=AIOHTTP_TIMEOUT) as session:
- payload = {
- "accumulate_tokens": True,
- "text_input": request_func_input.prompt,
- "temperature": 0.0,
- "top_p": 1.0,
- "max_tokens": request_func_input.output_len,
- "stream": True,
- }
- if request_func_input.ignore_eos:
- payload["min_length"] = request_func_input.output_len
- output = RequestFuncOutput()
- output.prompt_len = request_func_input.prompt_len
-
- ttft = 0.0
- st = time.perf_counter()
- most_recent_timestamp = st
- try:
- async with session.post(url=api_url, json=payload) as response:
- if response.status == 200:
- async for chunk_bytes in response.content:
- chunk_bytes = chunk_bytes.strip()
- if not chunk_bytes:
- continue
-
- chunk = chunk_bytes.decode("utf-8").removeprefix("data:")
-
- data = json.loads(chunk)
- output.generated_text += data["text_output"]
- timestamp = time.perf_counter()
- # First token
- if ttft == 0.0:
- ttft = timestamp - st
- output.ttft = ttft
-
- # Decoding phase
- else:
- output.itl.append(timestamp - most_recent_timestamp)
-
- most_recent_timestamp = timestamp
-
- output.latency = most_recent_timestamp - st
- output.success = True
-
- else:
- output.error = response.reason or ""
- output.success = False
- except Exception:
- output.success = False
- exc_info = sys.exc_info()
- output.error = "".join(traceback.format_exception(*exc_info))
-
- if pbar:
- pbar.update(1)
- return output
-
-
-async def async_request_deepspeed_mii(
- request_func_input: RequestFuncInput,
- pbar: Optional[tqdm] = None,
-) -> RequestFuncOutput:
- """Request an LLM using Deepspeed MII"""
- async with aiohttp.ClientSession(trust_env=True, timeout=AIOHTTP_TIMEOUT) as session:
-
- payload = {
- "prompt": request_func_input.prompt,
- "max_tokens": request_func_input.output_len,
- "temperature": 0.01, # deepspeed-mii does not accept 0.0 temp.
- "top_p": 1.0,
- }
- output = RequestFuncOutput()
- output.prompt_len = request_func_input.prompt_len
-
- # NOTE: DeepSpeed-MII doesn't support streaming as of Jan 28 2024,
- # will use 0 as placeholder.
- # See https://github.com/microsoft/DeepSpeed-MII/pull/311
- output.ttft = 0
-
- st = time.perf_counter()
- try:
- async with session.post(url=request_func_input.api_url, json=payload) as response:
- if response.status == 200:
- parsed_resp = await response.json()
- output.latency = time.perf_counter() - st
- if "choices" in parsed_resp:
- output.generated_text = parsed_resp["choices"][0]["text"]
- elif "text" in parsed_resp:
- output.generated_text = parsed_resp["text"][0]
- else:
- output.error = "Unexpected response format: " "neither 'choices' nor 'text' found"
- output.success = False
- output.success = True
- else:
- output.error = response.reason or ""
- output.success = False
- except Exception:
- output.success = False
- exc_info = sys.exc_info()
- output.error = "".join(traceback.format_exception(*exc_info))
-
- if pbar:
- pbar.update(1)
- return output
-
-
-async def async_request_openai_completions(
- request_func_input: RequestFuncInput,
- pbar: Optional[tqdm] = None,
-) -> RequestFuncOutput:
- """Request an LLM using OpenAI"""
- api_url = request_func_input.api_url
- assert api_url.endswith(
- ("completions", "profile")
- ), "OpenAI Completions API URL must end with 'completions' or 'profile'."
-
- async with aiohttp.ClientSession(trust_env=True, timeout=AIOHTTP_TIMEOUT) as session:
- payload = {
- "model": (request_func_input.model_name if request_func_input.model_name else request_func_input.model),
- "prompt": request_func_input.prompt,
- # "temperature": 0.0,
- "max_tokens": request_func_input.output_len,
- "logprobs": request_func_input.logprobs,
- "stream": True,
- # "stream_options": {
- # "include_usage": True,
- # },
- }
- if request_func_input.ignore_eos:
- payload["ignore_eos"] = request_func_input.ignore_eos
-
- headers = {"Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}"}
-
- output = RequestFuncOutput()
- output.prompt_len = request_func_input.prompt_len
-
- generated_text = ""
- st = time.perf_counter()
- most_recent_timestamp = st
- try:
- async with session.post(url=api_url, json=payload, headers=headers) as response:
- if response.status == 200:
- first_chunk_received = False
- async for chunk_bytes in response.content:
- chunk_bytes = chunk_bytes.strip()
- if not chunk_bytes:
- continue
-
- chunk = chunk_bytes.decode("utf-8").removeprefix("data: ")
- if chunk != "[DONE]":
- # print("####chunk:", chunk, type(chunk))
- data = json.loads(chunk)
-
- # NOTE: Some completion API might have a last
- # usage summary response without a token so we
- # want to check a token was generated
- if choices := data.get("choices"):
- # Note that text could be empty here
- # e.g. for special tokens
- text = choices[0].get("text")
- timestamp = time.perf_counter()
- # First token
- if not first_chunk_received:
- first_chunk_received = True
- ttft = time.perf_counter() - st
- output.ttft = ttft
-
- # Decoding phase
- else:
- output.itl.append(timestamp - most_recent_timestamp)
-
- most_recent_timestamp = timestamp
- generated_text += text or ""
- elif usage := data.get("usage"):
- output.output_tokens = usage.get("completion_tokens")
- if first_chunk_received:
- output.success = True
- else:
- output.success = False
- output.error = (
- "Never received a valid chunk to calculate TTFT." "This response will be marked as failed!"
- )
- output.generated_text = generated_text
- output.latency = most_recent_timestamp - st
- else:
- output.error = response.reason or ""
- output.success = False
- except Exception:
- output.success = False
- exc_info = sys.exc_info()
- output.error = "".join(traceback.format_exception(*exc_info))
-
- if pbar:
- pbar.update(1)
- return output
-
-
-async def async_request_openai_audio(
- request_func_input: RequestFuncInput,
- pbar: Optional[tqdm] = None,
-) -> RequestFuncOutput:
- """Request an LLM using OpenAI"""
- # Lazy import without PlaceholderModule to avoid vllm dep.
- import soundfile
-
- api_url = request_func_input.api_url
- assert api_url.endswith(
- ("transcriptions", "translations")
- ), "OpenAI Chat Completions API URL must end with 'transcriptions' "
- "or `translations`."
-
- async with aiohttp.ClientSession(trust_env=True, timeout=AIOHTTP_TIMEOUT) as session:
- content = [{"type": "text", "text": request_func_input.prompt}]
- payload = {
- "model": (request_func_input.model_name if request_func_input.model_name else request_func_input.model),
- "temperature": 0.0,
- "max_completion_tokens": request_func_input.output_len,
- "stream": True,
- "language": "en",
- # Flattened due to multipart/form-data
- "stream_include_usage": True,
- "stream_continuous_usage_stats": True,
- }
- if request_func_input.extra_body:
- payload.update(request_func_input.extra_body)
- headers = {
- "Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}",
- }
-
- # Send audio file
- def to_bytes(y, sr):
- buffer = io.BytesIO()
- soundfile.write(buffer, y, sr, format="WAV")
- buffer.seek(0)
- return buffer
-
- with to_bytes(*request_func_input.multi_modal_content["audio"]) as f:
- form = aiohttp.FormData()
- form.add_field("file", f, content_type="audio/wav")
- for key, value in payload.items():
- form.add_field(key, str(value))
-
- output = RequestFuncOutput()
- output.prompt_len = request_func_input.prompt_len
-
- generated_text = ""
- ttft = 0.0
- st = time.perf_counter()
- most_recent_timestamp = st
- try:
- async with session.post(url=api_url, data=form, headers=headers) as response:
- if response.status == 200:
- async for chunk_bytes in response.content:
- chunk_bytes = chunk_bytes.strip()
- if not chunk_bytes:
- continue
-
- chunk = chunk_bytes.decode("utf-8").removeprefix("data: ")
- if chunk != "[DONE]":
- timestamp = time.perf_counter()
- data = json.loads(chunk)
-
- if choices := data.get("choices"):
- content = choices[0]["delta"].get("content")
- # First token
- if ttft == 0.0:
- ttft = timestamp - st
- output.ttft = ttft
-
- # Decoding phase
- else:
- output.itl.append(timestamp - most_recent_timestamp)
-
- generated_text += content or ""
- elif usage := data.get("usage"):
- output.output_tokens = usage.get("completion_tokens")
-
- most_recent_timestamp = timestamp
-
- output.generated_text = generated_text
- output.success = True
- output.latency = most_recent_timestamp - st
- else:
- output.error = response.reason or ""
- output.success = False
- except Exception:
- output.success = False
- exc_info = sys.exc_info()
- output.error = "".join(traceback.format_exception(*exc_info))
-
- if pbar:
- pbar.update(1)
- return output
-
-
-ASYNC_REQUEST_FUNCS = {
- "tgi": async_request_tgi,
- "vllm": async_request_openai_completions,
- "lmdeploy": async_request_openai_completions,
- "deepspeed-mii": async_request_deepspeed_mii,
- "openai": async_request_eb_openai_completions,
- "openai-chat": async_request_eb_openai_chat_completions,
- "openai-chat-multi-turn": async_request_eb_openai_chat_completions_multi_turn,
- "openai-audio": async_request_openai_audio,
- "tensorrt-llm": async_request_trt_llm,
- "scalellm": async_request_openai_completions,
- "sglang": async_request_openai_completions,
-}
-
-OPENAI_COMPATIBLE_BACKENDS = [
- k
- for k, v in ASYNC_REQUEST_FUNCS.items()
- if v
- in (
- async_request_openai_completions,
- async_request_eb_openai_chat_completions,
- )
-]