大规模评估任务优化:Inspect AI并行计算与资源管理策略
Inspect AI作为一个强大的大语言模型评估框架,其核心优势在于高效处理大规模评估任务。本文将深入探讨如何通过并行计算与资源管理策略,充分发挥Inspect AI的性能潜力,实现评估任务的快速、稳定执行。
核心并发架构概览 ⚡️
Inspect AI采用并行异步架构,能够在高效执行大量样本的同时,通过各种限制机制确保资源不会过度饱和。这种架构主要关注以下几个关键并发点:
- 模型API连接并发控制
- 多模型并行评估能力
- 多任务并行执行机制
- 沙箱环境并发管理
- 自定义工具、求解器和评分器中的并行代码编写
默认情况下,Inspect使用Python的asyncio作为异步后端,但也可以配置为使用trio框架。这种灵活性使得框架能够适应不同的使用场景和性能需求。
模型连接并发控制 📡
最大连接数配置
模型API连接是最基本的并发管理单元。限制模型API并发的主要因素不是本地计算或网络可用性,而是模型API提供商施加的速率限制。通过调整最大连接数,可以在充分利用API配额的同时避免频繁触发速率限制。
$ inspect eval --model openai/gpt-4 --max-connections 20
默认的最大连接数为10。增加这一数值可能会因更高的并行度而获得更好的性能,但如果导致频繁触发速率限制(会通过指数退避进行重试),性能反而会下降。最佳的最大连接数需要根据实际的速率限制和评估任务的规模和复杂性进行调整。
注意:最大连接数是按模型应用的。这意味着如果评估时使用来自不同提供商的评分模型,将获得额外的并发性(因为每个模型将执行自己的最大连接限制)。
智能重试策略
默认情况下,当发生可恢复的HTTP错误时,Inspect会无限期地重试模型API调用(使用指数退避策略)。初始退避时间为3秒,指数增长会导致第10次请求等待25分钟(第11次及后续请求等待30分钟)。你可以使用--max-retries选项限制Inspect的重试次数:
inspect eval --model openai/gpt-4 --max-retries 10
还可以使用--timeout选项限制重试的总时间:
inspect eval --model openai/gpt-4 --timeout 600
调试与监控
如果需要更深入地了解模型API连接和重试情况,可以指定log_level=http:
inspect eval --model openai/gpt-4 --log-level=http
还可以使用inspect trace http命令查看当前(或最近)评估运行的所有HTTP请求:
inspect trace http # 显示所有HTTP请求
inspect trace http --failed # 仅显示失败的请求
多模型并行评估 🚀
Inspect AI允许通过向eval()函数传递模型列表来并行评估多个模型:
eval("mathematics.py", model=[
"openai/gpt-4-turbo",
"anthropic/claude-3-opus-20240229",
"google/gemini-2.5-pro"
])
由于每个模型提供商都有自己的max_connections,它们不会相互争夺资源。如果需要评估多个模型,强烈建议并发进行。
使用CLI参数或环境变量指定多个模型时,只需用逗号分隔模型名称:
INSPECT_EVAL_MODEL=openai/gpt-4-turbo,google/gemini-2.5-pro
多任务并行执行 📊
默认情况下,Inspect一次运行一个任务。这是因为大多数任务包含10个或更多样本,样本并行性通常足以充分利用活动模型定义的max_connections。
然而,如果每个任务的样本数量远低于max_connections,那么并行运行多个任务可能会带来好处。可以通过--max-tasks CLI选项或eval()函数的max_tasks参数实现:
$ inspect eval . --max-tasks=5
另一个常见场景是使用超参数的变体运行相同的任务(例如提示词、生成配置等):
tasks = [
Task(
dataset=csv_dataset("dataset.csv"),
solver=[system_message(SYSTEM_MESSAGE), generate()],
scorer=match(),
config=GenerateConfig(temperature=temperature),
)
for temperature in [0.5, 0.6, 0.7, 0.8, 0.9, 1]
]
eval(tasks, max_tasks=5)
需要强调的是,只有当样本数量非常少时,这才能提供性能提升。例如,如果数据集包含10个样本,而max_connections为10,并行运行任务不会有任何收益。
还可以将并行任务与并行模型结合:
eval(
tasks, # 6个不同温度值的任务
model=["openai/gpt-4", "anthropic/claude-3-haiku-20240307"],
max_tasks=5,
)
此代码将评估总共12个任务(6个温度变体分别对应2个模型),最多并行运行5个任务。
沙箱环境并发管理 🔒
沙箱环境(如Docker容器)通常按样本分配资源,并使用Inspect的subprocess()函数在环境中执行命令。有效的沙箱并发管理对于评估性能至关重要。
最大沙箱数
max_sandboxes选项决定了可以并行执行的沙箱数量。各个沙箱提供程序可以建立自己的默认限制(例如,Docker提供程序的默认值为2 * os.cpu_count())。可以根据需要修改此选项,但要注意容器运行时有资源限制,超过限制可能导致不稳定和评估失败。
当应用max_sandboxes时,任务状态屏幕底部会显示一个指示器:
注意:应用
max_sandboxes时,这实际上创建了一个等于max_sandboxes的全局max_samples限制。
最大子进程数
max_subprocesses选项决定了可以并行运行的子进程调用数量。默认情况下,这设置为os.cpu_count()。根据沙箱环境中执行的任务性质,可能需要增加或减少max_subprocesses。
可以通过以下命令修改最大子进程数:
$ inspect eval --model openai/gpt-4 --max-subprocesses 4
最大样本数
max_samples是另一个需要考虑的因素,它是一个任务中可以并发运行的最大样本数。更大数量的并发样本将提高吞吐量,但也会导致完成的样本被写入日志文件的频率降低,因此在任务中断的情况下可恢复的样本总数会减少。
默认情况下,Inspect将max_samples的值设置为max_connections + 1(注意将其设置为低于max_connections通常没有意义)。默认的max_connections为10,这通常会导致样本被频繁写入日志。另一方面,设置非常大的max_connections(例如对于包含100个样本的数据集设置100个max_connections)可能会在中断的情况下导致可恢复的样本非常少。
提示:如果任务涉及工具调用和/或沙箱,可能需要将
max_samples设置为大于max_connections,因为样本有时会调用模型(使用并发连接),有时会在沙箱中执行代码(使用并发子进程调用)。运行任务时,可以实时查看连接和子进程的利用率,并相应地调整max_samples。
高效并行代码编写指南 🛠️
REST API调用最佳实践
自定义求解器、工具或评分器可能需要调用其他REST API。执行此操作时需要记住两点:
-
至关重要的是,与其他API的连接使用
asyncHTTP API(即httpx模块而不是requests模块)。这是因为Inspect的并行性依赖于所有内容都是async的,因此如果使用requests进行阻塞HTTP调用,实际上会阻碍系统中的所有其他工作! -
与模型API一样,速率限制可能会起作用,因此重要的是不要过度饱和这些连接。回想一下,Inspect并行运行所有样本,因此如果有500个样本并且不采取任何措施限制并发,可能会同时对API进行数百次调用。
以下是一些(简化的)示例代码,说明如何在Inspect组件中调用REST API。我们使用httpx模块的async接口,并使用Inspect的concurrency()函数将同时连接限制为10:
import httpx
from inspect_ai.util import concurrency
from inspect_ai.solver import Generate, TaskState
client = httpx.AsyncClient()
async def solve(state: TaskState, generate: Generate):
...
# 将client.get()调用包装在async concurrency中
# 阻止将同时连接限制为10
async with concurrency("my-rest-api", 10):
response = await client.get("https://example.com/api")
注意,我们向concurrency()函数传递了一个名称("my-rest-api")。这为管理对特定API/服务的调用提供了命名范围。
并行代码编写模式
一般来说,应该尝试使在Inspect求解器、工具和评分器中编写的所有代码尽可能并行。主要思想是尽可能急切地发布工作,然后允许上述各种并发控制来处理不过度负载远程API或本地资源。编写并行代码有两个关键:
-
对所有潜在的昂贵操作使用
async。如果正在调用远程API,请使用httpx.AsyncClient。如果正在运行本地代码,请使用上面描述的subprocess()函数。 -
如果
async工作可以并行化,请使用asyncio.gather()。例如,如果调用三个不同的模型API来评分任务,可以并行调用它们。或者如果需要检索10个网页,不需要循环执行——可以一次获取所有网页。
模型请求并行化
假设你有一个评分器,它使用三个不同的模型来基于多数投票进行评分。可以如下并行进行所有模型API调用:
from inspect_ai.model import get_model
models = [
get_model("openai/gpt-5"),
get_model("anthropic/claude-sonnet-4-5"),
get_model("mistral/mistral-large-latest")
]
output = "Output to be scored"
prompt = f"Could you please score the following output?\n\n{output}"
graders = [model.generate(prompt) for model in models]
grader_outputs = await asyncio.gather(*graders)
注意,在构建评分器列表时,我们不会等待model.generate()调用。相反,asyncio.gather()调用将等待每个请求,并在所有请求完成时返回。Inspect对模型API的max_connections的内部处理将限制这些请求,因此无需担心并发请求的数量。
网络请求并行化
以下是使用asyncio.gather()并行化Web请求的示例:
import asyncio
import httpx
client = httpx.AsyncClient()
pages = [
"https://www.openai.com",
"https://www.anthropic.com",
"https://www.google.com",
"https://mistral.ai/"
]
downloads = [client.get(page) for page in pages]
results = await asyncio.gather(*downloads)
注意,在构建downloads列表时,我们不会await客户端请求。相反,我们让asyncio.gather()等待所有请求,仅在所有结果可用时返回。与循环下载每个页面相比,这将执行得更快。
如果要向可能有速率限制的REST API发送请求,应考虑将HTTP请求包装在concurrency()块中:
from inspect_ai.util import concurrency
async def download(page):
async with concurrency("my-web-api", 2):
return await client.get(page)
downloads = [download(page) for page in pages]
results = await asyncio.gather(*downloads)
子进程管理
自定义求解器、工具或评分器可能需要启动子进程来执行各种任务。子进程与调用API有类似的考虑:要确保它们不会阻塞Inspect中的其他工作(因此应该用async调用),还要确保它们不会提供过多的并发性(例如,不想在4核机器上同时启动200个进程!)
为了帮助解决这个问题,Inspect提供了subprocess()函数。这个async函数接受命令和参数,并异步调用指定的命令,收集并返回stdout和stderr。subprocess()函数还会自动将并发子进程限制为系统上的CPU数量(os.cpu_count())。以下是list_files()工具实现的示例:
@tool
def list_files():
async def execute(dir: str):
"""列出目录中的文件。
参数:
dir: 目录
返回:
目录的文件列表
"""
result = await subprocess(["ls", dir])
if result.success:
return result.stdout
else:
raise ToolError(result.stderr)
return execute
可以使用--max-subprocesses选项修改并发子进程的最大数量:
$ inspect eval --model openai/gpt-4 --max-subprocesses 4
如果需要在评估中执行计算密集型代码,应该始终将其转化为对subprocess()的调用,以便获得最佳的并发性和性能。
超时控制
如果需要确保子进程运行不超过指定的时间间隔,可以使用timeout选项:
try:
result = await subprocess(["ls", dir], timeout = 30)
except TimeoutError:
...
如果发生超时,将抛出TimeoutError(代码通常应以适当的方式处理)。
异步后端选择 🔄
Inspect异步代码使用AnyIO库编写,这是一个独立于异步后端的异步原语实现(例如任务、同步、子进程、流等)。
AnyIO支持两个后端:Python内置的asyncio库以及Trio异步框架。默认情况下,Inspect使用asyncio,并且与使用原生asyncio函数的用户代码兼容。
使用Trio
要将Inspect配置为使用Trio,请设置INSPECT_ASYNC_BACKEND环境变量:
export INSPECT_ASYNC_BACKEND=trio
inspect eval math.py
注意,使用Trio时,Inspect的某些功能尚不工作,包括:
-
全屏任务显示使用textual框架,目前仅适用于asyncio。使用Trio时,Inspect将自动切换到"rich"任务显示(交互性较差)。
-
与AWS S3的交互(例如用于日志存储)使用s3fs包,目前仅适用于asyncio。
可移植异步代码
如果在Inspect求解器、工具、评分器或扩展中编写异步代码,应尽可能使用AnyIO库而不是asyncio。如果这样做,无论使用什么异步后端,Inspect代码都将正确工作。
AnyIO在asyncio之上实现了类似Trio的结构化并发(SC),并与Trio本身的原生SC和谐工作。
总结与最佳实践 🎯
通过合理配置和优化Inspect AI的并行计算与资源管理策略,可以显著提高大规模评估任务的效率。以下是一些关键最佳实践:
-
从默认值开始:使用默认的
max_connections(10)开始,然后根据观察到的速率限制调整 -
监控关键指标:关注终端显示的HTTP重试次数,调整并发设置以达到理想的利用率
-
组合并行策略:同时使用多模型和多任务并行,充分利用系统资源
-
合理设置沙箱限制:根据可用CPU核心数配置
max_sandboxes和max_subprocesses -
编写异步代码:使用
async/await模式和asyncio.gather()最大化并行性 -
限制外部API调用:对自定义REST API调用使用
concurrency()上下文管理器 -
适当设置超时:为子进程和API调用设置合理的超时,避免长时间阻塞
通过实施这些策略,你可以充分利用Inspect AI的并行计算能力,实现高效、可靠的大规模语言模型评估。无论是学术研究、产品开发还是模型优化,这些技术都将帮助你更快地获得更全面的评估结果。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考






