上海开源大赛公布了 DaoCloud 赛题,选做相关题目还可获得额外现金激励。比激励本身更值得关注的是题目指向的问题:当异构集群从千卡扩展到万卡,智算云的主要矛盾已经从“有没有算力”转向“算力能否稳定、高效地转化为训练与推理吞吐”。
GPU 到位并不等于任务跑得顺。训练作业可能长时间等待配额,推理服务会被 KV Cache 命中率和首 Token 时延拖住,几十乃至上百 GB 的模型权重还可能在训练节点、推理节点与终端之间反复搬运。参赛方案如果只展示一个能运行的 Demo,很难触及问题核心;更有价值的方向,是把调度、缓存、模型分发和可观测性连成一条可测量的工程链路。
瓶颈已经从设备数量转向数据与任务流动
智算集群中的“利用率低”通常不是单一组件造成的。GPU 空闲可能意味着没有任务,也可能意味着任务正在等待数据、权重、网络或其他 GPU。
可以把问题拆成四条路径:
| 路径 | 常见现象 | 建议观察的指标 |
|---|---|---|
| 训练调度 | 作业排队、资源碎片、部分卡空闲 | 排队时间 P95、Gang 调度成功率、GPU 利用率 |
| 在线推理 | 首 Token 慢、吞吐抖动 | TTFT P50/P95、输出 Token/s、请求失败率 |
| KV Cache | 重复前缀仍被重新计算 | Cache 命中率、复用 Token 数、缓存淘汰次数 |
| 模型分发 | Pod 启动慢、网络反复传输权重 | 模型加载时间、跨节点流量、缓存命中后的启动时间 |
这里最容易踩的坑,是把 GPU 利用率当成唯一目标。利用率升高不一定代表业务体验改善:激进批处理可能提高吞吐,却让交互请求的首 Token 时延变差;缓存保留过多可以提高命中率,也可能挤占显存并触发 OOM。
因此,方案需要明确优化目标,例如:
在请求失败率不超过基线的前提下:
1. 将 TTFT P95 降低 20%;
2. 将重复前缀请求的计算量降低 30%;
3. 将模型副本启动时间降低 40%;
4. 不增加单节点峰值显存占用。
这些数字只是制定实验目标的示例,不代表赛事官方指标。实际提交应以赛题说明和评测规则为准。
先建立基线,再讨论缓存和调度优化
推理优化经常从“感觉更快了”开始,却因为缺少基线而无法复现。一个更稳妥的做法,是固定模型、输入、并发和硬件,分别测量首次请求与重复前缀请求的 TTFT。
下面的脚本只依赖 Python 标准库,可测试兼容 OpenAI Chat Completions 协议的流式推理服务。它会发送多轮具有相同长前缀的请求,并记录首个有效内容到达时间和总耗时。
将 --endpoint、--model 和可选的 --token 改成自己的服务参数即可运行:
#!/usr/bin/env python3
import argparse
import json
import statistics
import time
import urllib.request
def run_once(endpoint, model, token, prompt):
body = json.dumps({
'model': model,
'stream': True,
'temperature': 0,
'messages': [
{'role': 'user', 'content': prompt}
]
}).encode('utf-8')
headers = {'Content-Type': 'application/json'}
if token:
headers['Authorization'] = f'Bearer {token}'
request = urllib.request.Request(
endpoint.rstrip('/') + '/v1/chat/completions',
data=body,
headers=headers,
method='POST'
)
started = time.perf_counter()
first_content_at = None
output = []
with urllib.request.urlopen(request, timeout=300) as response:
for raw_line in response:
line = raw_line.decode('utf-8').strip()
if not line.startswith('data:'):
continue
data = line[5:].strip()
if data == '[DONE]':
break
event = json.loads(data)
choices = event.get('choices', [])
if not choices:
continue
content = choices[0].get('delta', {}).get('content')
if content:
if first_content_at is None:
first_content_at = time.perf_counter()
output.append(content)
finished = time.perf_counter()
return {
'ttft_ms': round((first_content_at - started) * 1000, 2)
if first_content_at else None,
'total_ms': round((finished - started) * 1000, 2),
'output_chars': len(''.join(output))
}
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--endpoint', required=True)
parser.add_argument('--model', required=True)
parser.add_argument('--token', default='')
parser.add_argument('--rounds', type=int, default=5)
args = parser.parse_args()
fixed_prefix = (
'你是集群故障分析助手。以下规则在每次请求中保持不变:'
'优先检查资源、调度、网络、存储和缓存,并给出验证命令。'
) * 200
prompt = fixed_prefix + '\n问题:为什么推理服务的首 Token 时延突然升高?'
results = []
for index in range(args.rounds):
result = run_once(
args.endpoint, args.model, args.token, prompt
)
result['round'] = index + 1
results.append(result)
print(json.dumps(result, ensure_ascii=False))
valid_ttft = [r['ttft_ms'] for r in results if r['ttft_ms'] is not None]
if valid_ttft:
print(json.dumps({
'ttft_median_ms': round(statistics.median(valid_ttft), 2),
'first_round_ms': valid_ttft[0],
'repeated_round_median_ms': round(
statistics.median(valid_ttft[1:] or valid_ttft), 2
)
}, ensure_ascii=False))
if __name__ == '__main__':
main()
例如:
python3 benchmark_ttft.py \
--endpoint http://127.0.0.1:8000 \
--model your-model-name \
--rounds 8
如果服务需要鉴权,再添加:
--token "$INFERENCE_API_TOKEN"
首轮与后续请求的差异可以作为缓存效果的线索,但不能直接等同于 KV Cache 命中率。连接复用、模型预热、动态批处理和并发噪声都会影响结果。正式实验还应增加对照组:保持输入长度一致但改变前缀,并在相同并发下交替发送两组请求。
一个有说服力的方案应该闭合四个环节
围绕智算云问题,可以这样组织一个可验证的原型,而不是堆叠互不关联的功能。
1. 调度层:知道任务为什么等
调度器不仅要报告“资源不足”,还应区分 GPU 型号不匹配、拓扑约束、配额不足、资源碎片和 Gang 成员未就绪。参赛原型至少要让用户看到排队原因与持续时间。
2. 缓存层:知道哪些计算可以复用
KV Cache 优化需要定义缓存键、隔离边界与淘汰策略。多租户环境中不能为了命中率忽略数据隔离;包含敏感上下文的缓存也不能被其他租户复用。
可以重点比较:
- 相同系统提示词、不同用户问题;
- 完全相同的多轮上下文;
- 不同租户下内容相同的请求;
- 显存压力下缓存淘汰前后的延迟变化。
3. 分发层:避免每次启动都搬完整权重
模型权重分发可以采用共享存储、节点本地缓存、分层缓存或预热 DaemonSet。选择方案时不能只看下载速度,还要检查版本一致性、校验失败、磁盘回收以及滚动升级期间的双版本占用。
模型文件至少应带有不可变版本和摘要,例如:
model:
repository: object-storage/models/example-llm
revision: sha256-7b8d3f2
expectedDigest: sha256:REPLACE_WITH_REAL_DIGEST
cache:
nodeLocalPath: /var/lib/model-cache
maxSizeGiB: 500
evictionPolicy: lru
这是一份可改造的配置示例,并非赛事指定格式。真正下载模型时,应在加载前校验摘要,避免同名权重被覆盖后继续使用旧缓存。
4. 观测层:证明收益来自哪里
仪表盘不应只放一条 GPU 利用率曲线。建议同时展示:
- 队列长度与排队时长;
- TTFT、端到端延迟和 Token 吞吐;
- GPU 利用率、显存占用与 OOM 次数;
- KV Cache 命中、淘汰与占用;
- 模型下载流量、加载耗时与节点缓存命中率。
把这些指标放在同一时间轴上,才能回答“延迟下降究竟来自缓存、预热、批处理还是负载下降”。
参赛与落地前的检查清单
DaoCloud 赛题提供了一个把真实集群难题做成开源原型的入口,额外现金激励则降低了选做成本。不过,具体奖金、提交方式、时间节点和评测口径仍应以大赛正式规则为准。
准备方案时,可以逐项检查:
- [ ] 是否给出了可复现的硬件、模型、并发与输入配置;
- [ ] 是否同时记录优化前后的基线;
- [ ] 是否报告 P50、P95,而不只展示平均值;
- [ ] 是否说明缓存隔离、数据安全和故障回退策略;
- [ ] 是否覆盖模型升级、节点重启与缓存失效;
- [ ] 是否能解释吞吐、延迟、显存和成本之间的取舍;
- [ ] 是否提供一条命令即可运行的最小验证路径。
真正有价值的智算云方案,不是让某一次演示跑出漂亮数字,而是让任务在资源竞争、节点故障和模型升级时仍然跑得稳,并且能够用数据解释为什么更快。