通过 CloudWatch REPORT 日志区分 Init Duration 和 Handler Duration,证明模型 API 调用才是冷路径性能瓶颈,而非初始化。
一次耗时四秒的 Lambda 调用模型 provider,在诊断时几乎每次都会被判定为冷启动,但通常并非如此。这两项耗时在你的日志里已经是分开计量的,其中一项通常比另一项大两个数量级。
两个独立的计时器
每次 Lambda 调用都会向 CloudWatch Logs 写入一条 REPORT 行,在冷启动调用中它会携带一个额外的字段:
REPORT RequestId: 8f4d... Duration: 3412.55 ms Billed Duration: 3610 ms
Memory Size: 1024 MB Max Memory Used: 118 MB Init Duration: 196.44 ms
Init Duration 是执行环境的创建和你模块级代码的运行时间。Duration 则是你的 handler。两者是累加关系,但并非同一计时器,而且只有前者才是冷启动。在上面这行示例中,init 在 3.6 秒请求中占 196ms,约 5%。把这一点写出来是因为你自己的 REPORT 行已经包含了对应的数据。在优化之前,先过滤出包含 Init Duration 的日志行,对比两列数据。
另一个值得掌握的指标是实际需要为此付账的频率。AWS 在 2025 年 4 月的 Compute Blog 文章(宣布 INIT 将开始计费)中披露,在生产环境的 Lambda 工作负载中"INIT(冷启动)通常发生在不到 1% 的调用中"。
这 1% 是 AWS 的数据,发表于 2025 年 4 月 29 日,文章标题为《AWS Lambda standardizes billing for INIT Phase》,描述的是跨多个工作负载的聚合情况,而非对你的承诺。每小时调用一次的函数每次都要为此付账。同篇 文章还记录了从 2025 年 8 月 1 日起,使用 ZIP 打包托管运行时的按需函数的 INIT 阶段将纳入计费时长——自定义运行时、预留并发和容器镜像此前已经计费。
init 阶段负责下载代码、启动运行时,以及运行 handler 之外的所有内容。AWS 文档说明其限制为 10 秒;超过此限制后 Lambda 会在首次调用时重试 init,此时计入函数超时而非 init 超时。
对于一个职责是调用模型 API 的函数而言,init 阶段要做的事情很少:解释器启动、SDK 导入,以及客户端构造。其中有两点值得特别了解。构造 SDK 客户端时会解析凭证和端点配置,所以在模块级别做这件事可以做到每个环境付一次成本,而非每次请求都付一次——这是标准建议,而且正确。另外,导入一个大型 SDK 包往往是 init 中最大的一行开销,这就是为什么只导入特定的客户端而非整个 SDK 比你想象的更重要。
这些操作没有任何一项会随工作负载增长。init 是每个环境大致恒定的成本,只付一次,然后分摊到该环境处理的每个请求上。
模型调用的耗时随答案长度增长。生成是顺序的:一次前向传播产生一个 token,所以一次响应是一个循环,其长度等于输出 token 数。总时间大约是 ttft + (output_tokens / tokens_per_second),这与自回归生成的一般推导公式相同。
不用凭空假设数字,来为这个结构赋值。一个 400 token 的答案,以每秒 40 token 的速度生成需要 10 秒;以每秒 100 token 的速度生成需要 4 秒。在这两种情况下,200ms 的 init 都不重要,而且任何合理的 init 数值都不重要——AWS 将整个 init 阶段上限设为 10 秒,而生成项没有上限(不超过函数超时时长)。这就是机制所在:init 有界且恒定,生成无界且与输出长度成正比。
由此引出三个结论,而这正是这件事具有操作性意义而非学术意义的原因:
超时由模型决定,而非运行时。 Lambda 的最大函数超时是 900 秒,而真正值得关心的是在此之前很久发生的事:boto3 的默认 read_timeout 是 60 秒,所以一次长生成可能在一个还剩几分钟时间的函数内部失败。要主动设置 read_timeout,并将函数超时设置在它之上,否则你会得到一个 task-timed-out 而没有任何有用的错误信息。
重试会放大那个大数字。 SDK 的默认重试模式会重新发送一个已经消耗了你大部分预算的请求。对于一次成功路径耗时数秒的调用,重试意味着一次完整的重新生成。
并发由耗时驱动。 并发执行数等于每秒请求数乘以平均耗时,所以在相同速率下,一个持有 10 秒模型调用的函数需要的并发数是一个持有 1 秒调用的函数的十倍。这就是模型集成如何在悄无声息中耗尽一个账户默认的 1000 并发执行数。
预留并发(Provisioned Concurrency)预初始化执行环境,使 init 阶段在请求到达之前就已完成。它确实消除了 init 的开销——而这也它能消除的全部。由于 AWS 将整个 init 阶段上限设为 10 秒,而对生成没有任何等效的限制,因此对于工作负载是单次模型调用的函数,预留并发的最佳效果的上限就是你自己的 Init Duration 字段所报告的数值,代价是无论是否有人调用都要持续为这些环境付费。
当 init 确实很大时,这是一把趁手的工具:重型依赖树、JVM、启动时加载到内存的模型、带大型包的 VPC 附挂函数。对于调用 HTTP API 的轻量函数,它近乎是一种浪费。判断方法看 Init Duration 列,而不是凭直觉。AI 工作负载用预留并发要算得过来才行。
增加内存是一个类似的故事,但有一个转折。Lambda 按内存比例分配 CPU,AWS 文档指出 1,769 MB 是函数获得等效一个 vCPU 的临界点,所以更多内存确实能让 init 和任何本地工作更快。它对等待 socket 的时间完全不起作用,而那才是模型调用的大部分耗时。AWS 同样文档记录了每个执行环境 625 Mbps 的网络带宽——对 token 流来说绰绰有余,并非人们以为的瓶颈。
如果目标是更快的用户可见响应,杠杆在生成项上:
流式输出。 它不会让生成更快,但用户可以在首 token 时间(time-to-first-token)开始阅读,而非等到全部生成结束。对于同步 Lambda,这意味着响应流式 function URL,因为缓冲响应无论模型做什么都无法流式输出。注意 AWS 文档记录流式响应上限为 200 MB,首 6 MB 带宽不限,之后 2 MBps——对文本不是问题,但对其他内容是真实的限制。
减少输出 token 数。 这是唯一直接减少主项的杠杆,而且通常只需要改 prompt。
缓存前缀。 Prompt 缓存减少了长共享前缀的首 token 时间,这是延迟的另一半,也是随系统 prompt 增长的那一半。
别为等待买单。 如果没有人在盯着,请求根本不需要是同步的。把它移到队列后面,或转为批处理任务,才是真正解决问题而非优化它。