How to make AI responses faster on Microsoft Foundry

题图来自 Microsoft Foundry 博客的 How to make AI responses faster on Microsoft Foundry: lessons from 2,040 measurements,作者是 Yassine El Ghali。

模型换得更快,应用不一定就更快。这篇文章的作者在 Microsoft Foundry 上把文本、图片、文件、工具和 MCP 几类工作负载都测了一遍,想知道哪些改动真能降低延迟,又不牺牲正确性。我读下来最大的感受是,延迟的来源比我平时想的杂得多:输出长度、重复的 prompt 内容、图片和文档处理、工具选择、连接建立、串行等待,还有多出来的模型请求,都会占时间。

按作者的口径,把优化过的软件配置和经过验证的 Priority Processing 组合起来,与未优化的 Standard 按量付费相比,AI 路径的中位延迟降低了 23% 到 50%,具体看工作负载。要注意,这个数字没有单独拆出 Priority Processing 的贡献。

先看短版本:

  • 去掉不必要的输出、视觉处理和工具定义。
  • 复用稳定的 prompt 前缀,以及由应用管理的 MCP 会话。
  • 去掉串行等待和多余的模型轮次,再对剩下的工作测试 Priority Processing。

延迟要和正确性、可靠性一起测。又快又错的答案,不算性能提升。

测量方法

这里的一次测量,指的是某个场景、某个变体、某个 fixture 的一次执行。每个用例在五个确定性 fixture 上,按带种子的随机顺序跑出 30 个有效观测。处理组和对照组在同一个 fixture、同一轮、同一次执行里运行时,会配对比较。

分析用的数据集一共有 2,040 次测量。审计记录里有 2,160 次基准执行,是发布前那轮实验里记下来的,不含云端预检。另有 120 次更早的客户端生命周期运行,因为后来修正了计时边界,被排除在外。

自动化的确定性评分器会检查必填字段、事实、工具调用和参数。失败和不正确的运行仍然算在可靠性统计里,延迟百分位只用正确完成的运行来算,也不会悄悄重试失败的请求。

AI 路径延迟,从模型请求(或一段编排好的模型加工具工作流)发出之前开始计时,到该响应或工作流完成为止。校验决定结果能否进入延迟分析,但校验耗时不在主计时器里。用户到应用之间的网络、UI 渲染,以及图片缩放、PDF 生成、文本提取、OCR 这类预处理,也都在计时之外。

基准只用了一个 Azure 账号,资源在 East US 2。除非某个处理组明确要测内部请求或工具的扇出,否则一次只跑一个基准用例。组合基准用的是 Azure OpenAI Global Standard 部署,所以 East US 2 只说明资源所在区域,并不保证推理一直在这个区域里完成。单项优化测试用的是 gpt-4.1-mini,组合基准用的是 gpt-4.1,版本 2025-04-14。这不是负载测试,两组结果的百分比不能放在一起算。

fixture 是合成的、确定性的。组合基准复用了同一批 fixture 家族,所以它不是在留出的生产输入上做的独立复现。中位数效应用的是 5,000 次抽样的 bootstrap 区间,区间没有针对多重比较做校正,30 个观测算出的 p95 只能当作描述性数字。协议和局限在 lab report 里有记录,执行 ID、产物版本、区间和可靠性事件在 result ledger 里。

结果速览

下表汇总了最值得参考的单变量结果。

测试的改动 工作负载 观测到的中位数结果 重要的限定条件
生成简洁的文本回答 文本 快 26.0% 29/30 正确,有一次流在完成前就结束了
生成简洁的 JSON 图片 快 24.9% 30/30 正确
复用可缓存的 prompt 前缀 文本 / 图片 分别快 14.9% / 23.6% 请求必须报告命中了缓存 token
使用低图片细节 图片 快 17.8% 细小文字和视觉证据必须仍然准确
发送已提取的文本,而不是 PDF 文件 AI 路径快 17.2% 提取或 OCR 的时间在计时之外
并发处理三个相互独立的页面 文件 快 56.8% 模型请求还是那三个,只改了调度
在一次模型响应里请求两个必需的函数 函数工具 快 28.2% 主要是少了一次模型请求,不只是处理函数并行了
暴露 5 个工具而不是 20 个 函数工具 快 16.8% 必需的工具必须仍然可用
使用最简工具描述 函数工具 快 14.4% 工具选择和参数必须仍然正确
复用由应用管理的 MCP 会话 MCP 冷会话慢 34.1% 需要安全的生命周期和恢复处理
给 50 个工具的 Toolbox 加上 tool search Toolbox / MCP p50 慢 43.0% 输入 token 降了 26%;正确率 30/30 对 29/30,描述性 p95 更低

这些都是针对具体工作负载的发现,不是服务保证。只有配对的中位数效应 95% 区间不包含零时,作者才标注为"更快"。

一、去掉任务不需要的工作

只生成需要的输出

文本方面,证据最清楚的优化是缩短输出约定。

文本任务里,要求紧凑回答把中位延迟从 2,078 ms 降到 1,537 ms,在正确完成的请求里提升了 26.0%。

图片提取任务里,改成简洁 JSON 把中位数从 1,646 ms 降到 1,237 ms,提升 24.9%,30/30 正确完成。

这不是说每个回答都该写得很短。意思是,模型不该生成下一步应用会丢掉的内容。

一张 UI 卡片可能只要五个字段,一个路由步骤可能只要一个枚举值,一个工具规划器可能只需要校验过的参数。先把这份约定明确写出来,再验证更短的回答是否仍然满足产品需求。

减少输入则是另一回事。去掉 12 轮无关的历史对话,文本中位延迟降了 12.7%,但配对的 95% 置信区间包含零,所以在这个小测试里结论不确定。去掉无关上下文仍然可以降低输入 token 的成本,但这个实验并没有证明它能降低延迟。

只用任务需要的视觉表示

在提取发票 ID、总额和状态的任务上,低图片细节比高细节快 17.8%。

低细节和缩小源文件不是一回事。完整图片仍然会发送,只是 detail: "low" 让服务去分析一个 512 x 512 的表示,而不是做高分辨率的分块检查。参见 Configure image detail level。

对于大而清晰的印刷字段,这样做效果不错,但它可能漏掉小字、手写内容、图表或空间位置上的证据。只有确定性校验证实所有必需字段仍然准确时,才从低细节开始。

缩放图片也把中位数降了 12.8%,但描述性 p95 从 5.1 秒升到了 8.8 秒。这不影响中位数的结论,只是变慢的尾部延迟观测还需要进一步调查。本地缩放耗时同样在 AI 路径计时器之外。

文件方面,发送已经提取好的文本,比直接发原生 PDF 快 17.2%。这个结论刻意限定得很窄,因为提取或 OCR 发生在计时之前。

任务只依赖文字事实时,用文本就行。版面、签名、图表、手写内容或其他视觉证据重要时,就保留原生文档处理。要做完整流水线的决策,必须把提取成本和可靠性算进去。

二、复用工作和连接

把重复的 prompt 内容放在最前面

Prompt caching 缓存的不是模型的回答。它临时复用的是服务已经对长输入开头部分做过的处理。第一次请求照常处理,之后的请求可以复用匹配的前缀。

对实验里的 gpt-4.1 模型来说,符合条件的请求至少要有 1,024 个输入 token,并且前 1,024 个 token 要与最近的某次请求一致。

服务不会分别缓存请求里的各个字段,而是从开头开始,只复用匹配的内容,直到出现第一处差异为止。所以请求里内容的顺序很重要。

下面是一个简化的 Responses API 请求:

{
  "model": "<deployment-name>",
  "instructions": "[REUSABLE] Stable system instructions, examples, and output rules",
  "input": [
    {
      "type": "message",
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "[REUSABLE] Reference content shared by many requests"
        },
        {
          "type": "input_text",
          "text": "[CHANGES] The current user's question"
        }
      ]
    }
  ]
}

可复用的开头部分可以包含系统指令、示例、输出规则、工具定义或共享的参考文本。保持它们完全一致并放在最前面,当前的问题、请求 ID、图片、文档和其他会变的内容放在后面。

如果开头出现了时间戳或请求 ID,各个请求从一开始就不同,后面那些稳定的指令也就没法形成一段足够长的匹配前缀。

预热前缀的缓存让文本中位延迟降了 14.9%,图片降了 23.6%。文本的平均首 token 时间从 991 ms 降到 602 ms。

作者没有想当然地认为机制生效,而是做了验证。大约 93% 到 100% 的重复请求报告了缓存命中。对照组在开头改了一个值,就没有报告任何缓存 token。

做法就是:共享内容放前面,请求特有的内容放后面,再检查响应里的 cached_tokens。

在这次运行里,缓存对文件任务和函数工具任务没有带来明确的中位数改善。当文档处理、工具选择或别的环节占了大头时,复用 prompt 的处理就没那么重要了。

最新的资格条件和保留时长见 Prompt caching。

别让每个任务都重新连接 MCP

当应用自己充当 MCP 客户端时,连接建立和工具发现可能在关键路径上占相当一部分。

复用会话时,p50 是 2,283 ms。每个任务都新开一个会话并发现工具,p50 升到 3,061 ms,冷路径比复用路径慢 34.1%。建立会话加工具发现平均花 1,016 ms,本地工具本身不到 5 ms。

这是应用生命周期层面的优化,不是模型层面的优化,结果也不会自动适用于服务托管的 MCP。

可以在多个任务之间复用一个兼容的会话和已经发现的工具目录。一个会话无法安全支撑预期并发时,就用一个有上限的连接池。复用要按身份和租户划定范围,并且要考虑过期、重连、凭据轮换、目录刷新、健康检查和故障隔离。

仓库里附有完整的 MCP 连接辅助代码。

三、去掉串行等待和不必要的模型请求

并发处理相互独立的页面请求

单变量里效果最大、且有证据支持的,是并发处理三个相互独立的文档页面。

两种配置发出的模型请求都是三个。串行版本要等一个响应回来才发起下一个,并发版本是三个请求都发出去之后再等结果。

中位延迟从 2,811 ms 降到 1,216 ms,提升 56.8%。

这是调度层面的结果,只在请求相互独立、模型配额也撑得住突发流量时才成立。生产代码还是需要有上限的并发、超时和部分失败的处理。

去掉函数工作流里可避免的一轮模型调用

这个函数工具任务需要同时查天气和当地时间:

Slower: model -> weather -> model -> time -> model -> final answer
Faster: model -> weather + time -> model -> final answer

更快的设计让一次模型响应同时请求两个函数,把工作流从三次模型请求减到两次,p50 从 3,646 ms 降到 2,617 ms,提升 28.2%。

合成的处理函数都在 5 ms 内完成,所以收益大部分来自少了一次模型请求。让处理函数并发执行是另一种机制。

对于由应用执行的函数:

  • 允许模型请求多个调用
  • 校验工具名称和参数
  • 确认这些调用相互独立,且可以安全地重叠执行
  • 把异步 I/O 放在一起跑
  • 为每个原始调用 ID 返回一个输出
  • 所有必需的结果都到齐后,再让模型生成最终回答

parallel_tool_calls=True 只是允许多个调用,并不保证模型会选出每个必需的工具。asyncio.gather 能让异步 I/O 重叠,但不会让阻塞的同步代码变成并行。

28.2% 这个结果只适用于由应用执行的函数工具。没有单独的基准测试,也没有能说明实际执行行为的 trace,就不要把它套到服务托管的 MCP 或 Foundry Agent Service 上。

四、限制每个请求可用的工具

工具定义也是模型输入的一部分。即使函数本身几毫秒就跑完,工具选择和最终生成仍然可能要花几秒。

把活跃的工具目录从 20 个函数工具减到 5 个,p50 降了 16.8%。使用最简描述,p50 降了 14.4%。

可以参考的设计模式是:

  1. 先把请求路由到某个领域
  2. 只暴露该领域相关的工具
  3. 名称、描述和 schema 保持简洁,但要有区分度
  4. 校验工具选择和参数,不能只看 token 数

其他针对工具 schema 的处理,在这个样本量下结论都不确定。暴露 50 个工具、加入复杂 schema、把描述写得含糊、调整定义顺序,都没有产生有统计支持的中位延迟变化。有些描述性的 p95 更慢,但 30 次尝试不足以断定这些处理会稳定地损害尾部延迟。

Toolbox 能改善工具选择,但多了一轮搜索

Microsoft Foundry Toolbox 的 tool search 会把庞大的工具目录挡在初始 prompt 之外,需要时再去发现相关工具。

在这个合成的 50 工具测试里,它把平均输入 token 降低了 26%,正确完成了 30/30 次,直连 MCP 是 29/30。不过多出来的搜索这一轮,把 p50 从 2,528 ms 提高到 3,614 ms,增加了 43.0%。

单个测试不能证明 Toolbox 总体上能提高准确率,它展示的是一种取舍:目录规模、上下文压力或工具选择是主要问题时,用 tool search。如果首次响应的延迟最重要,而且应用已经知道相关的子集,直接暴露这个更小的子集可能更快。

参见 Tool search 和 Toolbox overview。

五、软件优化之后再测 Priority Processing

作者最早那批标注为 Priority Processing 的请求,给了他们一个重要的验证教训:所选的模型并不支持请求的那个 tier,每个响应报告的都是 service_tier=default。

这些观测留在了审计记录里,但不作为 Priority Processing 的证据。

和 gpt-4.1-mini 上的单项优化测试不同,组合基准用了支持该 tier 的 gpt-4.1 部署。每个成功的 Priority Processing 响应报告的都是 service_tier=priority。

对比的是三种配置:

  1. 未优化的 Standard 按量付费
  2. 优化后的 Standard 按量付费
  3. 同样的优化配置,加上 Priority Processing
工作负载 未优化 Standard 优化后 Standard 优化 + Priority Processing 组合提升
文本 2,070 ms 2,125 ms 1,222 ms 快 41.0%
图片 2,872 ms 2,234 ms 1,448 ms 快 49.6%
文件 1,512 ms 1,245 ms 1,165 ms 快 23.0%
函数工具 3,817 ms 2,824 ms 2,393 ms 快 37.3%

表里每个值都是 AI 路径延迟的中位数。组合提升比较的是"优化后的软件加 Priority Processing"与"未优化的 Standard"。

单靠软件优化,图片、文件和函数工具三类工作负载都有明确改善。优化后的文本配置在 gpt-4.1 上结果不确定,延迟甚至略高了一点(2,070 ms 对 2,125 ms)。

在优化配置上再加 Priority Processing,四类工作负载观测到的 p50 都降了。文本和图片的配对区间不包含零,文件和函数工具的区间跨过了零,所以这两类上的增量效果不确定。

Standard 的 240 次运行全部正确完成,Priority Processing 是 119/120,有一次函数工具失败。文件和函数工具的描述性 p95,在这次运行里 Priority Processing 比优化后的 Standard 更慢。每组只有 30 次尝试,这只能说明在定 SLO 之前该多收集一些尾部观测,不是确定的尾部延迟结论。

Priority Processing 是按量付费,有自己的价格。Provisioned Throughput 则是预留以 PTU 计量的专用容量,不在这次对比范围内。作者没有算具体的货币成本,所以需要另外评估当前各区域的定价。选处理方式之前,先看一下当前的 deployment categories 和 Priority Processing 文档。

没有产生明确中位数改善的做法

负面和不确定的发现,同样是结果的一部分:

  • gpt-4.1-nano 并没有稳定地比 gpt-4.1-mini 快,在测试的图片任务上反而慢了 25.0%
  • 去掉 12 轮无关的文本历史,对延迟的影响不确定
  • 重新创建 SDK 客户端,影响不确定
  • 只选一页 PDF 而不是三页,影响不确定
  • 在测试的文件和函数工具任务上,prompt caching 的影响不确定
  • 多参数和嵌套的 schema、含糊的描述、重新排序的工具定义,影响都不确定

这些发现并不证明这些改动永远没用。它们说明的是,模型名称、token 数和架构上的直觉都不算性能证据。要在真实任务上测,给正确性打分,保留失败记录,并找出真正占大头的是哪个环节。

实用决策指南

可能的延迟来源 该测什么 必须仍然成立的条件
生成的回答太长 只请求需要的输出 回答仍然完整且正确
重复的长 prompt 内容 把稳定内容放前面并保持一致 响应报告了缓存 token
图片处理 试试低细节 细小文字和必需的视觉证据仍然准确
文档的表示方式 对比原生 PDF 和提取后的文本 计入提取时间,需要时保留视觉证据
相互独立的页面请求 并发运行 请求之间确实独立,配额撑得住突发
工具之间多出来的模型轮次 在一次模型响应里请求必需的函数 选出的函数和参数相同
慢的、相互独立的应用端函数 让异步 I/O 重叠 副作用、配额、超时和失败都能安全处理
庞大的静态工具集 路由到更小的相关目录 正确的工具仍然可用
庞大的动态目录 测试 Toolbox tool search token 和选择上的收益值得多花那一轮搜索
重复的 MCP 建立过程 复用由应用管理的会话 认证、过期、重连和隔离仍然安全
服务端的处理时间 测试 Priority Processing 返回的 tier、p50、p95、可靠性、质量、成本和数据驻留都满足目标

每个实验可以按下面的顺序来做:

  1. 定义一个正确答案必须包含什么
  2. 写明计时器确切的开始和结束位置
  3. 在有代表性的 fixture 上配对对照组和处理组
  4. 随机决定谁先运行
  5. 把失败保留在可靠性结果里
  6. 只用正确完成的运行来计算延迟
  7. 把兼容的优化放在一起测试,不要把各自孤立的百分比加起来

复现或查看这个实验

AI response performance benchmark 仓库里有基准的实现、Terraform 环境、确定性 fixture、原始结果的处理脚本和报告。

照着仓库 README 配置好 Azure 环境,就能运行自带的 smoke、单变量和组合基准配置。在跑要花钱的基准轮次之前,先从 smoke 配置开始。使用合成数据或经批准的数据,先确认配额和预期成本,再把自带的 fixture 换成能代表你生产工作负载的样例。

仓库里还有:

我自己最想带走的是顺序:先定义什么叫正确,把每个阶段的耗时量出来,砍掉能避免的工作,最后才轮到平台选项,去优化关键路径上还剩下的那段时间。