大模型落地必经之路: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 服务部署是一项系统工程,核心决策链路为:推理引擎选型 → 量化策略 → 批处理调度 → 弹性伸缩 → 流量治理。落地建议如下:

  1. 起步阶段:用 TGI 或 vLLM 默认配置快速上线,优先验证模型效果与业务匹配度,不必过早优化。
  2. 规模化阶段:启用 Prefix Caching 和 Continuous Batching,将 GPU 利用率从 30% 提升到 80% 以上,同时引入熔断与降级机制保障可用性。
  3. 极致优化阶段:评估 TensorRT-LLM + INT8/FP8 量化方案,配合 Warm Pool 策略解决冷启动问题,最终实现成本与延迟的平衡。

部署没有银弹,每个阶段的选择都意味着对某些指标的妥协。关键是明确当前阶段的核心矛盾,用数据驱动决策,而非盲目追求"最优方案"。

Logo

免费领 200 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐