AI 应用应提交工作负载,而非直接选 GPU
AI 应用直接硬编码 GPU 型号/区域选择会导致跨供应商移植时大量基础设施逻辑渗入业务代码。提出应设计「工作负载描述」抽象层,由调度系统决定底层 GPU 分配,简化应用层逻辑。
完整中文译文
开发者正在构建一个需要运行 GPU 推理任务的 AI 应用。
初始实现看起来很直接:
# Simplified example
provider = CloudGPUProvider(api_key=API_KEY)
instance = provider.launch_instance(
region="us-east",
instance_type="gpu.large",
gpu_model="specific-gpu-model",
image="registry.example.com/inference:v1",
)
provider.run_command(
instance_id=instance.id,
command="python inference.py --input /data/request.json",
)
然后所选区域容量耗尽了。
开发者又添加了另一个区域。
第二个区域不提供相同的实例类型,因此应用需要针对硬件的特定分支。另一个提供商有可用的 GPU,但其 API 使用不同的生命周期模型。有的提供商期望应用管理虚拟机,有的直接启动容器,还有的暴露任务概念,但通过独立的服务返回日志和产物。
最初的推理功能逐渐变成了基础设施编排系统。
应用代码现在包含:
区域选择逻辑
实例生命周期管理
应用最初的需求很简单:
运行这个 AI 任务。
最终却变成了描述任务应该在何处以及如何运行的基础设施特定代码。
这是错误的抽象层。
AI 应用应该描述它们需要执行的任务。基础设施层应该决定如何满足这个请求。
应用应该说:
从这个确切的提供商启动这个确切的 GPU 实例。
应用应该说:
在运行时、内存、延迟、兼容性和成本约束下执行这个任务。
这一转变——从实例供给到 AI 任务执行——将基础设施决策从应用中移出,同时并不假装硬件需求不重要。
GPU 选择将基础设施复杂性泄漏到应用中
选择 GPU 不是一个单一决策。
它通常隐含以下决策:
网络配置
当一个应用选择一个确切的实例类型时,它继承了这个实例附带的所有假设。
部署代码可能假设:
该实例在请求的区域中可用。
提供商 API 响应正常。
所选 GPU 有足够的内存。
容器镜像与机器兼容。
提供商能在预期时间内启动任务。
可以通过已知端点检索日志。
应用可以安全地重试请求。
提供商将继续提供相同的实例类型。
这些假设最终成为生产依赖。
提供商锁定不仅限于合同或定价。
它也出现在代码中。
直接管理提供商实例的应用会与该提供商的以下方面耦合:
认证系统
将任务迁移到其他地方需要不仅仅是更改端点,还需要重写操作逻辑。
提供商可能支持某个 GPU 型号,但在应用需要时并没有该 GPU 可用。
硬编码一个提供商和区域会将临时容量短缺变成应用故障。
硬件特定逻辑
应用有时确实需要确切的硬件,但许多任务需要的是能力而非产品名称。
例如,任务可能需要:
至少一定量的 GPU 内存
支持的加速器架构
兼容的软件运行时
最大执行成本
首选启动时间
最低 CPU 或存储分配
将这些需求编码为特定的实例 SKU,会丢失任务真正需要的东西和开发过程中恰好能工作的东西之间的区别。
复杂的回退系统
一旦主要提供商失败,开发者开始添加回退逻辑:
Try provider A in region 1
|
+-- unavailable --> try provider A in region 2
|
+-- unavailable --> map workload to provider B
|
+-- incompatible GPU --> try provider C
每个应用团队最终都在重建一个部分调度器。
结果通常是脆弱的,因为容量发现、兼容性检查、重试和提供商选择都不是应用的主要关注点。
直接供给也创造了持续的运营工作。
必须有人维护提供商 SDK、更新实例映射、监控 API 变更、分类提供商错误、对账已弃用的机器,并确保失败的任务不会继续消耗资源。
随着更多提供商和任务类型的加入,这种开销变得愈发不可预测。
任务规格应该包含什么
任务规格应该描述工作及其约束。
它不应该不必要地规定基础设施实现。
根据任务的不同,规格可以传达:
容器或已批准的运行时
命令或入口点
GPU 内存需求
CPU 和系统内存需求
加速器兼容性
最大执行时间
环境配置
地理或合规限制
确切字段取决于执行平台。
重要原则是规格应该将需求与放置决策分离。
这个任务需要至少 24 GB 的 GPU 内存。
这是任务约束。
在提供商区域 zone-a 启动 x9-gpu-24gb 实例类型。
这是基础设施决策。
第一个陈述为执行层留出了寻找兼容容量的空间。
第二个陈述阻止了基础设施层选择等效或更好的执行路径。
前:基于实例的供给
应用选择提供商、区域、实例和生命周期。
// Simplified illustrative example
const provider = new SpecificCloudProvider({
apiKey: process.env.PROVIDER_API_KEY!,
});
const machine = await provider.createInstance({
region: "region-a",
instanceType: "provider-specific-gpu-instance",
imageId: "provider-specific-image",
});
await provider.waitUntilReady(machine.id);
await provider.execute(machine.id, {
command: ["python", "worker.py"],
environment: {
INPUT_PATH: "/inputs/request.json",
OUTPUT_PATH: "/outputs/result.json",
},
});
const logs = await provider.getLogs(machine.id);
const result = await provider.downloadFile(
machine.id,
"/outputs/result.json",
);
await provider.terminateInstance(machine.id);
应用拥有完整的基础设施生命周期。
它还必须决定任何步骤失败时会发生什么。
后:任务级提交
应用描述需要运行什么。
// Pseudocode only.
// This is not the verified Jungle Grid API schema.
const workload = {
runtime: {
type: "container",
image: "registry.example.com/ai-worker:v2",
command: ["python", "worker.py"],
},
resources: {
accelerator: "gpu",
minimumGpuMemoryGb: 24,
cpuCores: 8,
systemMemoryGb: 32,
},
constraints: {
maximumRuntimeSeconds: 3600,
maximumEstimatedCost: 5.0,
},
inputs: [
{
name: "request",
source: "storage://datasets/request.json",
},
],
outputs: [
{
name: "result",
path: "/outputs/result.json",
},
],
};
const execution = await executionLayer.submit(workload);
应用拥有任务定义。
执行层拥有放置和执行。
基于意图的执行与基于实例的供给
区别可以总结如下:
任务提交并不消除基础设施。
它将基础设施决策移入一个专门为此设计的层。
示例任务请求
以下 JSON 是说明性的伪代码。它不是当前的 Jungle Grid API 架构。
{
"runtime": {
"type": "container",
"image": "registry.example.com/batch-inference:v4",
"command": [
"python",
"run_inference.py"
]
},
"resources": {
"accelerator": "gpu",
"minimum_gpu_memory_gb": 24,
"cpu_cores": 8,
"system_memory_gb": 32
},
"constraints": {
"maximum_runtime_seconds": 7200,
"maximum_estimated_cost": 10,
"preferred_startup_latency_seconds": 120
},
"inputs": [
{
"name": "dataset",
"source": "storage://datasets/inference-batch"
}
],
"outputs": [
{
"name": "predictions",
"path": "/outputs/predictions.jsonl"
}
]
}
应用传达其意图:
运行特定的容器。
提供所需的输入。
分配足够的资源。
保持执行在定义的限制内。
返回特定的输出产物。
在执行层检查容量之前,它不需要选择提供商特定的实例。
提供商容量随时间变化
静态部署代码将基础设施视为可预测的。
GPU 基础设施通常并非如此。
提供商在开发期间可能有容量,但在生产高峰时没有。一个月前可靠的区域可能变得紧张。新的提供商可能以更低的成本提供兼容的硬件。首选的 GPU 可能不可用,而另一个合适的加速器却处于空闲状态。
应用不应该在容量格局每次变化时都需要新的部署。
任务执行层可以在提交时评估可用的执行路径。
该评估可能考虑:
当前提供商可用性
硬件兼容性
区域限制
历史执行可靠性
这是 GPU 任务调度,而不是简单的实例创建。
执行决策变得依赖上下文。
相同的任务可能在不同时间运行在不同基础设施上,同时保持相同的应用级接口。
为什么每个应用都不应该实现自己的重试
重试在任务变为异步之前看起来很简单。
当没有收到响应时,HTTP 请求通常可以安全地重试。
GPU 任务可能已经在运行,即使提交响应超时。
再次提交可能造成:
重复的推理任务
重复的训练运行
冲突的产物
不必要的 GPU 费用
不一致的应用状态
可靠的重试系统需要持久化的执行记录。
任务是否被接受
执行尝试是否已开始
尝试是否产生了输出
失败是由容量还是应用代码导致
重试是否安全
另一个提供商是否能执行相同的任务
重试应该保留还是替换之前的产物
每个应用都不应该独立解决这些问题。
重试属于执行状态附近。
负责调度和观察任务的层拥有决定是否进行另一次尝试的最佳信息。
Jungle Grid 如何处理任务
Jungle Grid 被设计为 AI 任务和 Agent 的执行层。
开发者提交他们想要运行的任务,而不是要求应用手动供给特定的提供商实例。
Jungle Grid 处理执行任务涉及的基础设施决策,包括:
容量感知的提供商选择
跨提供商的扩展
预期的分离很简单:
Application
|
| submits workload and constraints
v
Jungle Grid execution layer
|
| selects and manages execution infrastructure
v
Available compute provider
应用仍然负责准确地描述任务。
Jungle Grid 负责将该描述转化为基础设施执行。
开发者可以查看 Jungle Grid 架构概述、阅读 GPU 编排指南,或检查 Jungle Grid API 文档中的已验证接口。
一旦验证完毕,可以在此添加一个具体的公开案例研究:
[INSERT VERIFIED EXAMPLE]
何时仍然需要显式硬件选择
GPU 抽象层不应该假装每个加速器都是等效的。
有些任务确实需要确切的硬件。
在以下情况下可能需要显式选择:
内核针对特定 GPU 架构进行了优化。
任务依赖于特定的指令集。
基准测试需要受控的硬件环境。
模型超过其他加速器可用的内存。
监管策略要求在批准的区域或提供商中执行。
软件栈仅针对特定机器认证。
性能一致性比放置灵活性更重要。
团队有预留容量应该始终优先使用。
低级硬件研究需要直接设备控制。
在这些情况下,任务规格应该能够表达严格的约束。
区别不在于有硬件需求和没有硬件需求之间。
对任务正确性至关重要的需求
被意外硬编码到应用逻辑中的基础设施选择
好的执行接口在控制重要的地方保留控制。
它应该允许开发者在需要时指定最低 GPU 内存需求、确切的加速器系列、允许的提供商集、批准的区域或其他强制约束。
执行层应该只在这些边界内进行优化。
应用应该描述结果,而不是操作机器
AI 应用开发者越来越多地被要求成为基础设施调度器。
他们必须选择提供商、映射 GPU 型号、检测容量、实现回退、分类失败、流式日志、管理重试、收集产物和终止资源。
这些工作是必要的。
但它不应该出现在每个应用中。
应用应该描述:
它需要什么资源
必须遵守什么约束
期望什么输出
AI 任务执行层应该决定:
任务应该在哪里运行
哪些可用基础设施满足约束
如何观察执行
当容量消失时会发生什么
失败的尝试是否可以重试
如何返回日志和产物
这是供给基础设施与提交工作的区别。
前者迫使每个应用理解其下的计算市场。
后者创建一个稳定的执行边界。
应用不应该仅仅因为 GPU 执行工作就需要选择 GPU。
它们应该提交任务。
基础设施应该决定如何执行它们。
通过 Jungle Grid 测试任务
查看 Jungle Grid 编排指南、探索架构页面,或使用 API 文档来估算或提交 AI 任务。
通过 Jungle Grid 测试任务