Serverless GPU 冷启动验证:脚本与测试流程
提供一套可复用的 Serverless GPU 冷启动验证脚本与测试流程;按 RunPod 对冷启动的起止定义组织环境、模型加载、实例就绪和请求链路记录,便于在自己的业务场景中复测比较。
验证 Serverless GPU 冷启动,应把环境与模型准备、实例转为可服务、请求首响应和请求完成分别计时,而不是只记录单次请求的总耗时。做算力租赁验收时,用同一脚本跑冷态与热态,才能判断等待来自平台调度、实例初始化还是模型加载,并保持模型、镜像、请求和并发条件一致。下面给出一个通用记录脚本、完整测试流程和费用核对方法。
先统一冷启动口径
按 RunPod 对冷启动的定义,起点是 endpoint 在没有运行中 worker 时收到请求,终点是 worker 完全预热并可以处理请求。Modal 的口径涉及没有可用 container 时的启动过程;Modal 的冷启动说明将等待拆成等待 warm container 和首次调用初始化,并说明所有 enter methods 完成后才会接收输入。延长 scaledown_window 可以降低后续请求遇到冷启动的概率,但闲置资源仍会计费。
因此,客户端看到的首响应时间不能直接当作平台定义的冷启动时间。记录中应同时保存:
- 模型标识、镜像或 container 版本、请求文件和并发条件;
- 请求前是否已有运行中的 worker 或 ready container;
- 平台能够提供的启动、模型加载和就绪时间;
- 客户端请求开始、收到响应头、首个非空响应行、请求完成、状态码和错误。
算力租赁测试前要固定哪些条件
先从所选平台当前 API 文档填写 YOUR_ENDPOINT、鉴权方式和 YOUR_REQUEST_FILE,不要把示例占位符当成真实路径。冷态样本开始前,应确认没有可服务实例;如果平台没有提供可核对的入口或日志,就把该次记录标为“冷态未确认”,不要仅凭等待时间猜测。
同一组冷态与热态测试必须使用相同的模型、请求内容、镜像、endpoint 和并发条件。鉴权值只放在运行环境变量中,不要写入 metadata 或结果文件。平台、模型或请求文件发生变化后,应开启新记录,避免把配置变化误判为冷启动波动。
用统一脚本留下请求时间线
下面的 Python 脚本只做通用 HTTP 记录,不假定任何平台的私有 endpoint。它会读取 metadata 和请求文件,并把结果追加到 cold-start.jsonl:
# cold_start_probe.py
import json
import os
import time
from datetime import datetime, timezone
from pathlib import Path
from urllib.request import Request, urlopen
endpoint = os.environ["ENDPOINT"]
request_file = os.environ["REQUEST_FILE"]
metadata_file = os.environ["METADATA_FILE"]
timeout = float(os.environ["REQUEST_TIMEOUT_SECONDS"])
metadata = json.loads(Path(metadata_file).read_text())
body = Path(request_file).read_bytes()
headers = {"Content-Type": "application/json"}
auth_header = os.environ.get("AUTH_HEADER")
if auth_header:
headers["Authorization"] = auth_header
extra_headers = os.environ.get("EXTRA_HEADERS_JSON")
if extra_headers:
headers.update(json.loads(extra_headers))
request = Request(endpoint, data=body, headers=headers, method="POST")
started_at = datetime.now(timezone.utc).isoformat()
started = time.monotonic()
result = {**metadata, "client_started_at": started_at}
try:
with urlopen(request, timeout=timeout) as response:
result["status"] = response.status
result["headers_s"] = time.monotonic() - started
result["first_body_line_s"] = None
for chunk in response:
if result["first_body_line_s"] is None and chunk.strip():
result["first_body_line_s"] = time.monotonic() - started
result["completed_s"] = time.monotonic() - started
except Exception as exc:
result["error"] = f"{type(exc).__name__}: {exc}"
finally:
result["client_finished_at"] = datetime.now(timezone.utc).isoformat()
with Path("cold-start.jsonl").open("a") as output:
output.write(json.dumps(result, ensure_ascii=False) + "\n")
填写平台当前文档要求的变量后运行:
export ENDPOINT="YOUR_VALUE"
export REQUEST_FILE="YOUR_VALUE"
export METADATA_FILE="YOUR_VALUE"
export REQUEST_TIMEOUT_SECONDS="YOUR_VALUE"
export AUTH_HEADER="YOUR_VALUE"
export EXTRA_HEADERS_JSON="YOUR_VALUE"
python cold_start_probe.py
不需要 AUTH_HEADER 或额外请求头时,删除对应环境变量。对于流式响应,首个非空响应行可作为客户端观察点;对于普通 JSON 响应,它可能到响应结束才出现,因此不能直接解释为 TTFT。
从冷态跑到热态,结果怎样才算有效
冷态测试从确认没有 ready 实例后开始,脚本发出请求,同时保留平台侧的启动和就绪信息。热态测试则在前一次请求完成、实例仍处于可服务状态时复用相同输入。比较时先检查 metadata,再对照客户端首响应、平台就绪和最终完成时间,不要用不同配置的两组结果直接下结论。
如果结果要作为 MLPerf submission,则 MLPerf Inference Rules要求所有提交使用 LoadGen,并把延迟定义为 LoadGen 向被测系统发送查询到收到回复的时间;LLM benchmark 还记录 TTFT 和 TPOT。通用探针可以辅助定位环境问题,但不能代替符合规则的提交流程。
已有流程如果使用 GenAI-Perf,还应重新核对工具状态。NVIDIA 在 GenAI-Perf README中说明不再积极开发新功能,并将新的性能测试需求指向 AIPerf;迁移时应按 AIPerf 当前文档填写命令和 flags,而不是沿用未经核对的旧参数。
冷启动结果如何核对费用
RunPod 的 Serverless 计费说明规定,从 worker 启动到完全停止都会计费,并向上取整到下一秒;可计费时间包含 container 初始化与模型加载、执行请求以及 idle timeout。可按自己的日志或账单填写:
BILLABLE_DURATION = CEILING((WORKER_STOP_TIME - WORKER_START_TIME) / BILLING_UNIT) × BILLING_UNIT
这里的 WORKER_STOP_TIME、WORKER_START_TIME 和 BILLING_UNIT 都应来自 RunPod 当前页面、实际账单或报价,不能用客户端请求耗时代替。其他平台应按各自计费页面重新计算。
Replicate 的计费说明则区分了延迟与费用:public model 只对 active processing time 收费,setup 和 idle time 不收费;默认 hardware pool 与其他客户共享,请求可能经历共享队列等待。因此,客户端等待变长不一定等于实例启动变慢,也不一定产生同样方向的计费变化。
常见出错点
- 从实例就绪后开始计时:这样会漏掉真正的冷启动阶段。
- 只保存请求总耗时:无法区分排队、模型加载、首响应和生成阶段。
- 冷态、热态使用不同输入:结果差异可能来自请求,而不是实例状态。
- 把失败请求算作成功样本:带
error的记录应单独归类。 - 把首行响应直接称为首 token:只有接口和响应格式明确支持时才能这样解释。
- 只比较客户端时钟:平台事件与客户端时间应分别保存,再按同一事件边界核对。
本文仅依据正文所链接的官方文档整理,未执行脚本或云端请求;流程和平台行为应在使用前按当前版本核对。