大模型落地必经之路:LLM 服务部署方案,从推理引擎到流量治理
一、Token 账单与毫秒响应的双重夹击:大模型落地的"省钱"痛点
当大模型从 PoC 阶段走向生产环境,工程团队会立刻撞上两堵墙:一是推理成本居高不下,单次请求的 GPU 占用让账单飞速攀升;二是响应延迟不可控,P99 延迟动辄数秒,用户体验断崖式下跌。这两者看似矛盾——降成本需要提高 GPU 利用率,降延迟又需要预留计算冗余——但本质上都指向同一个核心问题:LLM 服务的部署架构是否足够精细。
在实际生产中,一个 7B 参数模型的推理服务,若采用最朴素的单实例部署,QPS 往往只有个位数。而企业级场景对吞吐和延迟的要求,迫使我们必须从推理引擎选型、模型量化、动态批处理、流量路由等多个层面进行系统性优化。这不是简单的"加机器"问题,而是架构层面的取舍博弈。
二、推理引擎选型与请求调度:从模型加载到 Token 流出
LLM 服务部署的核心链路可以概括为:模型加载 → 请求接收 → 调度排队 → 推理执行 → Token 流式返回。每个环节都有独立的优化空间,而引擎选型决定了整条链路的天花板。
flowchart TB A[客户端请求] --> B[API Gateway] B --> C[请求队列] C --> D{动态批处理调度器} D -->|Batch 组装| E[推理引擎 vLLM/TGI/TrtLLM] D -->|超时降级| F[缓存命中检查] F -->|命中| G[直接返回缓存结果] F -->|未命中| E E --> H[Token 流式输出] H --> I[SSE 推送客户端] G --> I当前主流推理引擎的定位差异明显:
- vLLM:采用 PagedAttention 技术,通过虚拟内存分页管理 KV Cache,显存利用率高,适合多并发场景。其 Continuous Batching 机制可以在不等待整个 batch 完成的情况下插入新请求,显著提升吞吐。
- Text Generation Inference (TGI):HuggingFace 出品,原生支持 Flash Attention 和水印校验,部署简单,社区生态好,但极限吞吐不如 vLLM。
- TensorRT-LLM:NVIDIA 官方方案,深度优化 kernel,支持 FP8 量化,单卡性能极致,但编译周期长、灵活性低,适合模型稳定后的极致优化阶段。
选型不是"哪个最好"的问题,而是"当前阶段最需要什么"的问题。PoC 阶段优先 TGI 快速验证,规模化阶段转向 vLLM 提升吞吐,极致优化阶段再考虑 TensorRT-LLM。
三、生产级部署:从单实例到弹性伸缩的完整实现
以下是基于 vLLM 的生产级部署方案,包含动态批处理、健康检查与弹性伸缩配置:
# docker-compose.yml — vLLM 生产部署核心配置 version: "3.8" services: vllm-server: image: vllm/vllm-openai:latest deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] command: > --model /models/qwen2-7b-instruct --served-model-name qwen2-7b --max-model-len 8192 --gpu-memory-utilization 0.90 --max-num-seqs 64 --enable-prefix-caching --dtype float16 environment: - VLLM_WORKER_MULTIPROC_METHOD=spawn healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 ports: - "8000:8000" volumes: - ./models:/models// Spring Boot — LLM 请求客户端,含重试、超时与熔断 @Service public class LlmInferenceClient { private final WebClient webClient; private final CircuitBreaker circuitBreaker; public LlmInferenceClient(WebClient.Builder builder, CircuitBreakerRegistry registry) { this.webClient = builder .baseUrl("http://vllm-server:8000") // 连接超时 5 秒,读取超时 60 秒(流式响应需要较长窗口) .clientConnector(new ReactorClientHttpConnector( HttpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) .responseTimeout(Duration.ofSeconds(60)))) .build(); this.circuitBreaker = registry.circuitBreaker("llm-inference"); } /** * 流式调用 LLM 推理接口,通过 SSE 逐 Token 返回结果 * 熔断器保护:连续 5 次失败后进入 OPEN 状态,30 秒后尝试半开恢复 */ public Flux<String> streamChat(String prompt, int maxTokens) { Map<String, Object> requestBody = Map.of( "model", "qwen2-7b", "messages", List.of(Map.of("role", "user", "content", prompt)), "max_tokens", maxTokens, "stream", true ); return circuitBreaker.decorateSupplier(() -> webClient.post() .uri("/v1/chat/completions") .contentType(MediaType.APPLICATION_JSON) .bodyValue(requestBody) .retrieve() .bodyToFlux(String.class) // 过滤 SSE 心跳帧,只保留有效 Token .filter(line -> line.startsWith("data: ") && !line.contains("[DONE]")) .map(line -> extractContent(line)) .onErrorResume(WebClientRequestException.class, e -> Flux.just("[ERROR] 推理服务暂时不可用,请稍后重试")) ).get(); } private String extractContent(String sseLine) { // 从 SSE 帧中提取 content 字段 String json = sseLine.substring(6); return JsonPath.read(json, "$.choices[0].delta.content"); } }关键配置说明:--gpu-memory-utilization 0.90预留 10% 显存给系统开销,避免 OOM;--enable-prefix-caching对重复前缀的 Prompt 启用 KV Cache 复用,可降低 30%-50% 的重复推理成本;--max-num-seqs 64控制最大并发序列数,防止显存溢出。
四、显存墙与冷启动:部署方案的隐性代价
任何 LLM 部署方案都不是免费的午餐,以下是必须正视的 Trade-offs:
显存墙问题:GPU 显存是硬约束。7B 模型 FP16 约需 14GB 显存,加上 KV Cache 和运行时开销,单卡 A100(80GB)在 max-model-len=8192 下最多支撑约 60 并发序列。若业务需要 128K 上下文,单请求的 KV Cache 就可能吃掉半张卡。解决方案是量化(INT8/INT4),但量化会带来精度损失,尤其是对长尾知识的召回率下降。
冷启动延迟:模型加载到 GPU 的耗时通常在 30-120 秒。弹性伸缩场景下,新 Pod 启动期间流量会被已有实例承担,可能导致级联过载。生产环境中需要预热池(Warm Pool)策略,保持一定数量的待命实例。
批处理与延迟的矛盾:Dynamic Batching 通过合并请求提升吞吐,但等待组 batch 的窗口会增加首 Token 延迟。vLLM 的 Continuous Batching 缓解了这一问题,但在低 QPS 时,批处理收益有限,反而增加了调度开销。
成本与一致性的取舍:Prefix Caching 能显著降低重复 Prompt 的推理成本,但缓存失效策略(LRU)可能导致相同 Prompt 在不同时刻得到不同结果(缓存命中时跳过了部分推理步骤),在需要严格确定性的场景中需要谨慎使用。
五、总结
LLM 服务部署是一项系统工程,核心决策链路为:推理引擎选型 → 量化策略 → 批处理调度 → 弹性伸缩 → 流量治理。落地建议如下:
- 起步阶段:用 TGI 或 vLLM 默认配置快速上线,优先验证模型效果与业务匹配度,不必过早优化。
- 规模化阶段:启用 Prefix Caching 和 Continuous Batching,将 GPU 利用率从 30% 提升到 80% 以上,同时引入熔断与降级机制保障可用性。
- 极致优化阶段:评估 TensorRT-LLM + INT8/FP8 量化方案,配合 Warm Pool 策略解决冷启动问题,最终实现成本与延迟的平衡。
部署没有银弹,每个阶段的选择都意味着对某些指标的妥协。关键是明确当前阶段的核心矛盾,用数据驱动决策,而非盲目追求"最优方案"。