Skip to content

端点发现、长流选路与真实推理性能口径 ​

模型已经 Ready,用户却说“点了十秒才出第一个字”。Ready 只回答后端能不能接请求。网关还得回答三个问题:这个人能否用这个模型,哪一个端点现在更空,以及响应中断后能不能换端点。顺着第一次写出数据的时刻,重试边界就清楚了。

sequenceDiagram
User->>Gateway: 请求模型并携带租户凭据
Gateway->>Gateway: 检查授权和端点快照
Gateway->>Runtime: 传递可取消的请求
Runtime-->>Gateway: 首个内容块
Gateway-->>User: 写入并 Flush
User-->>Gateway: 客户端断开
Gateway-->>Runtime: 取消上游

集群网关在 Pod 中读取 EndpointSlice,过滤非 Ready/正在终止的端点,维护服务名到 Pod IP+port 的快照。入口校验租户密钥与该租户允许的模型,再按每分钟窗口限流。选取和增加在途数在同一锁内完成,网络 I/O 在锁外。快照超过 15 秒没有刷新则停止派发。客户端取消会传播到上游,逐段写入并 Flush;头部失败或 503 可试其它候选,已开始下传的数据不拼接重试。上游可能已经计算过一部分,故这种重试不保证算力消耗恰好一次。

Round Robin 对长短生成混合负载不敏感;最少在途也不能区分长上下文与短请求。候选打分可用排队请求、正在 decode 的序列、KV 占用与样本新鲜度;不要把百分比与请求数直接相加。大规模时取两个健康候选并比较可减轻全局排序和热点追逐。集群实现每轮在 2 秒预算内并发探测端点 /metrics,评分为本地在途数 + 2×waiting + running + 10×KV 使用比例;这是教学权重。采样缺失时回退到本地计数。已测试指标解析和 CPU 请求,未用真实 vLLM 调优这些权重。可通过 REDIS_URL 增加共享窗口计数:Lua 在首次 INCR 时设置 TTL,Redis 失败返回 503。该固定窗口与本机滑动窗口并存,边界语义不同。

Prefill 并行处理 prompt,首 token 延迟还含排队/网络;Decode 逐步依赖前一 token 的结果。KV Cache 保存先前 attention 的 K、V,生成越长,占用越大。PagedAttention 把逻辑序列映射到离散物理块,缓解连续空间要求,不消除块内浪费。Continuous Batching 在迭代边界接纳/退出序列,吞吐可能改善,排队或长 prefill 仍会拖累 TTFT。vLLM 官方指标。

sh
python3 scripts/inference_bench.py --requests 12 --concurrency 4
# 已部署真实 vLLM 时:保留相同模型、prompt 长度、输出上限与硬件,分档改变并发
python3 scripts/inference_bench.py --usage --model YOUR_MODEL --url http://localhost:8000/v1/chat/completions

端点需要鉴权时先在本机设置 GATEWAY_TOKEN。TTFT 取首个非空 content 时间;TPOT 用首末内容的间隔除以生成 token 数减一,分块传输下是近似值。无真实 token 用量时输出 null,不把 SSE chunk 算一个 token。P95 用完成请求的最近秩统计,并另外报告错误数,避免把失败样本悄悄算成快速成功。

按请求的寿命读实现 ​

refresh 在后台抓 EndpointSlice 和各端点近期负载,serve 在锁内复制路由快照、取候选并增加在途数;锁外执行网络 I/O,结束后递减。把网络请求放进全局锁会让一条慢流挡住其他租户。score 是教学启发式而非调度保证;样本老化要保守退化,否则负反馈会变成所有请求同时追一个旧的空闲节点。

完整源码:modeldock/operator/cmd/gateway/main.go · 第 199–226 行

go
		g.Unlock()
		http.Error(w, "rate limit", 429)
		return
	}
	g.windows[tenant] = append(window, now)
	peers := append([]string{}, g.routes[b.Model]...)
	if now.Sub(g.last) > 15*time.Second {
		peers = nil
	}
	g.Unlock()
	for len(peers) > 0 {
		g.Lock()
		sort.Slice(peers, func(i, j int) bool {
			return float64(g.active[peers[i]])+g.penalty[peers[i]] < float64(g.active[peers[j]])+g.penalty[peers[j]]
		})
		p := peers[0]
		peers = peers[1:]
		g.active[p]++
		g.Unlock()
		req, _ := http.NewRequestWithContext(r.Context(), "POST", p+"/v1/chat/completions", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		resp, err := g.http.Do(req)
		if err != nil || resp.StatusCode == 503 {
			if resp != nil {
				resp.Body.Close()
			}
			g.Lock()
			g.active[p]--

本轮 Kind CPU 协议实验:并发 4、12 个完成、0 个错误,平均首内容 365 ms,整请求 P95 636 ms。这里首内容是固定模拟文本,不是 GPU token;TPOT 与 token/s 均为空。在真实 vLLM 上同时报告 prompt 长度、输出长度、warmup、每档并发和失败样本。请求集中长 prompt 时 prefill 会挤占执行预算;长输出增加 KV 留存时间,增加副本不一定立即降低尾延迟,因为新副本有下载权重和预热时间。