本地多GPU环境跑OpenAI兼容的微调API,限流不能只看请求数,要看同时占用的训练作业数。微调请求会拉起训练进程,训练进程驻留显存的时长远高于普通推理,并发一过显存容量会OOM,表现为进程被杀或接口返回5xx。下面的步骤针对自建兼容接口;如果只是调用官方托管API,本地GPU不参与限流,请直接看“客户端退避”一节。
前置条件
- 先确认接口角色:官方托管API,还是本地自建OpenAI兼容接口。官方API的并发限制在服务端,本地多GPU配置不生效。
- 拿到单卡显存和单个微调任务的显存峰值。在训练运行时执行
nvidia-smi `--query-gpu`=memory.used,memory.total `--format`=csv -l 5,记录Memory-Usage的最大值。 - 确认训练框架是否允许同卡多作业。PyTorch 多进程训练通常按作业独占显存,建议先按“每卡最多一个微调作业”设置,而不是按请求数设置。
服务端并发限制:按训练作业数设信号量
在自建接口服务里维护一个 active_jobs 集合,请求来了先检查当前训练作业数量,达到上限就返回 429。下面的 FastAPI 例子不等待训练完成才返回,而是创建后台任务,作业退出后再释放容量。
import asyncio
import uuid
from fastapi import FastAPI, HTTPException
app = FastAPI()
MAX_JOBS = 2 # 先按 GPU 卡数写,卡多就从卡数开始
active_training_jobs = set()
async def run_training_job(job_id: str):
# 替换成实际训练命令;finetune.py 需要自己处理 `--job` 参数
proc = await asyncio.create_subprocess_exec(
'python', 'finetune.py', '`--job`', job_id
)
await proc.wait()
active_training_jobs.discard(job_id)
@app.post('/v1/fine_tuning/jobs')
async def create_fine_tuning_job(request: dict):
if len(active_training_jobs) >= MAX_JOBS:
raise HTTPException(status_code=429, detail='no training slot')
job_id = uuid.uuid4().hex
active_training_jobs.add(job_id)
asyncio.create_task(run_training_job(job_id))
return {'job_id': job_id, 'status': 'started'}
如果微调任务会跨多卡,MAX_JOBS 要按“总卡数 ÷ 单任务卡数”估算,比如 4 卡环境、每个任务用 2 卡,就设为 2。先设 1、验证显存有余量再递增,不要直接按卡数设满。
客户端退避:应对官方 API 或自建接口的 429
当接口返回 429,不要立刻重试。读取 Retry-After 头,没响应头就用指数退避。下面的伪代码骨架可以直接放在任务提交循环里。
import time
import requests
for attempt in range(6):
resp = requests.post(endpoint, headers=headers, json=payload, timeout=120)
if resp.status_code == 429:
wait = float(resp.headers.get('Retry-After', 2 ** attempt))
time.sleep(wait)
continue
resp.raise_for_status()
break
如果调用官方 OpenAI 微调 API,把 endpoint 换成官方地址,并确认账号的并发额度;本地脚本能做的只有限制自己发起的任务数,以及把退避时间拉长。
验证并发限制是否生效
- 同时发 5 个创建任务请求,观察返回码:
seq 1 5 | xargs -P 5 -I {} curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:8000/v1/fine_tuning/jobs -H 'Content-Type: application/json' -d '{}'。预期一部分返回 200,超过 MAX_JOBS 的部分返回 429。 - 训练运行中执行
watch -n 1 nvidia-smi,看每个训练进程的显存使用。如果Memory-Usage达到 100% 且出现进程被杀,并发数仍然偏高。 - 检查服务端日志,确认 429 都发生在训练作业尚未结束时,而不是训练结束后。
失败时回退
- 429 出现但 GPU 显存还有明显余量:把 MAX_JOBS 加 1,观察一轮训练后再决定是否继续增加。
- 显存溢出或进程被杀:把 MAX_JOBS 降回 1,并且不要只靠限流解决,训练本身要调小 batch size、开启梯度检查点或降低序列长度。
- 如果客户端不打算做重试,就在服务端改排队:把 429 改为“任务已入队”,但队列要设最大长度,超长还是返回 429,否则高并发会把控制平面拖垮。
- 如果本地接口偶尔返回 5xx,先查是不是 OOM 引发,而不是盲目加超时时间。用
dmesg | grep -i kill查看是否有进程被内核杀掉。