第1632篇:企业私有化部署LLM的完整技术方案——从选型到上线
第1632篇:企业私有化部署LLM的完整技术方案——从选型到上线
最近有不少读者问我同一类问题:公司想把大模型能力搬进内网,不想数据出去,但完全不知道从哪开始。这个话题我在几家企业都深度参与过,踩过不少坑,今天从技术角度把完整路径讲一遍。
先说一个真实情况:很多企业在这件事上花了大量时间,结果因为前期选型错误,部署完发现效果不达标,或者运维成本远超预期,最后项目流产。私有化部署LLM不是装个软件那么简单,涉及硬件选型、模型选择、推理框架、服务治理、监控运维一整套体系。
为什么要私有化部署
不是所有场景都需要私有化部署,先搞清楚动机。常见原因:
- 数据安全合规:金融、医疗、政府行业,用户数据和业务数据不能出内网
- 成本控制:调用量极大时,API费用比自建成本高
- 低延迟要求:公网API的延迟无法满足实时性要求
- 定制化需求:需要在特定垂直领域进行微调,不希望把训练数据给第三方
如果你的日均调用量在万次以下,数据没有严格合规要求,老实说公有云API是更好的选择,没必要折腾私有化。
第一关:硬件选型
这是最容易被坑的地方。我见过一家公司买了一堆服务器,结果发现不支持NVLink,多卡推理效率极差,最后只能单卡跑,白白花了冤枉钱。
GPU选型核心指标
显存容量:决定能跑多大的模型
显存带宽:影响推理速度(比算力更重要!)
互联方式:NVLink vs PCIe,多卡时差距巨大常见GPU的推理性能对比(仅供参考,具体要看模型和框架):
| GPU | 显存 | 显存带宽 | 适合场景 |
|---|---|---|---|
| A100 80G | 80GB | 2TB/s | 70B+模型,高吞吐 |
| H100 80G | 80GB | 3.35TB/s | 最高性能,价格贵 |
| A10 | 24GB | 600GB/s | 7B-13B模型,性价比 |
| RTX 4090 | 24GB | 1TB/s | 开发测试,不适合生产 |
| L40S | 48GB | 864GB/s | 13B-34B模型 |
一个常用的计算模型显存需求的公式:
FP16精度:参数量(B) × 2 字节
INT8量化:参数量(B) × 1 字节
INT4量化:参数量(B) × 0.5 字节
实际推理还需要额外20%-30%的KV Cache显存举例:70B模型用FP16跑需要约140GB显存,一张A100装不下,需要2张。用INT4量化可以压到35GB,一张A100够了,但精度会有损失。
服务器配置建议
# 小规模(支持10并发以内)
CPU: 32核以上
内存: 256GB
GPU: 2x A10 (48GB总显存)
存储: NVMe SSD 2TB
网络: 万兆以太网
# 中规模(支持50并发)
CPU: 64核以上
内存: 512GB
GPU: 4x A100 80G (需要NVLink互联)
存储: NVMe SSD 4TB RAID
网络: 25GbE或InfiniBand
# 大规模(支持200+并发)
需要多节点集群,这时候通常要上Kubernetes了第二关:模型选型
国内的话,主流开源选择:
- Qwen2.5系列:中文能力强,指令跟随好,推荐首选
- DeepSeek-V2/V3:MoE架构,推理效率高,成本低
- Llama 3.1:英文能力强,社区生态好
- GLM-4:清华出品,中英双语均衡
我的选型建议:
第三关:推理框架选型
这是技术含量最高的一关。
vLLM——目前最主流的选择
vLLM是UC Berkeley开源的,最大特点是PagedAttention技术,把KV Cache用虚拟内存的方式管理,显存利用率比传统方式高很多。
# 安装
pip install vllm
# 启动服务(以Qwen2.5-7B为例)
python -m vllm.entrypoints.openai.api_server \
--model /data/models/Qwen2.5-7B-Instruct \
--served-model-name qwen2.5-7b \
--host 0.0.0.0 \
--port 8000 \
--tensor-parallel-size 2 \
--max-model-len 32768 \
--dtype auto \
--api-key your-internal-key \
--gpu-memory-utilization 0.90vLLM暴露的是OpenAI兼容的API,所以接入Spring AI非常简单,直接用OpenAI的starter改一下baseUrl就好:
spring:
ai:
openai:
base-url: http://your-vllm-server:8000
api-key: your-internal-key
chat:
options:
model: qwen2.5-7bOllama——本地开发首选
Ollama更适合开发测试场景,安装极其简单:
# macOS
brew install ollama
# 启动并下载模型
ollama serve &
ollama pull qwen2.5:7b
# 测试
curl http://localhost:11434/api/chat -d '{
"model": "qwen2.5:7b",
"messages": [{"role": "user", "content": "你好"}]
}'Spring AI对Ollama有原生支持:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>TGI(Text Generation Inference)——HuggingFace出品
docker run --gpus all \
-v /data/models:/data \
-p 8080:80 \
ghcr.io/huggingface/text-generation-inference:latest \
--model-id /data/qwen2.5-7b-instruct \
--num-shard 2 \
--max-batch-prefill-tokens 4096TGI的连续批处理做得很好,延迟控制比vLLM稳定,但吞吐量稍差。如果你的场景是低延迟优先,TGI可以考虑。
各框架对比
| 特性 | vLLM | TGI | Ollama |
|---|---|---|---|
| 吞吐量 | 最高 | 中等 | 低 |
| 延迟 | 中等 | 低 | 中等 |
| 多卡支持 | 好 | 好 | 有限 |
| 量化支持 | AWQ/GPTQ/GGUF | GPTQ | GGUF |
| 运维复杂度 | 中等 | 中等 | 低 |
| 生产推荐 | 是 | 是 | 否 |
第四关:服务治理
裸跑一个vLLM进程不是生产级别的部署。我们需要考虑:负载均衡、熔断限流、健康检查、滚动更新。
用Nginx做负载均衡
如果有多个推理实例:
upstream vllm_cluster {
least_conn; # 最少连接数策略,更适合LLM这种长连接
server 10.0.0.1:8000 weight=1;
server 10.0.0.2:8000 weight=1;
server 10.0.0.3:8000 weight=2; # 这台机器配置更好,权重高
keepalive 32;
}
server {
listen 80;
location /v1/ {
proxy_pass http://vllm_cluster;
proxy_http_version 1.1;
proxy_set_header Connection "";
# 流式响应必须关闭缓冲
proxy_buffering off;
proxy_cache off;
# 超时设置,LLM推理时间可能很长
proxy_connect_timeout 10s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
}Spring Cloud Gateway接入AI服务
如果你们已经有Spring Cloud生态,推荐用Gateway做统一入口:
@Configuration
public class AIGatewayConfig {
@Bean
public RouteLocator aiRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("vllm-route", r -> r
.path("/ai/**")
.filters(f -> f
.stripPrefix(1)
.circuitBreaker(config -> config
.setName("vllm-circuit-breaker")
.setFallbackUri("forward:/ai/fallback"))
.requestRateLimiter(config -> config
.setRateLimiter(redisRateLimiter())
.setKeyResolver(userKeyResolver()))
.retry(retryConfig -> retryConfig
.setRetries(2)
.setStatuses(HttpStatus.SERVICE_UNAVAILABLE))
)
.uri("lb://vllm-service")
)
.build();
}
@Bean
public RedisRateLimiter redisRateLimiter() {
// 每秒10个请求,突发20个
return new RedisRateLimiter(10, 20);
}
@Bean
public KeyResolver userKeyResolver() {
// 按用户ID限流
return exchange -> Mono.justOrEmpty(
exchange.getRequest().getHeaders().getFirst("X-User-Id")
).defaultIfEmpty("anonymous");
}
}第五关:Docker容器化部署
用Docker Compose管理整个私有化LLM服务栈:
version: '3.8'
services:
vllm-primary:
image: vllm/vllm-openai:latest
runtime: nvidia
environment:
- NVIDIA_VISIBLE_DEVICES=0,1
volumes:
- /data/models:/data/models
- /data/vllm/logs:/logs
ports:
- "8000:8000"
command: >
--model /data/models/Qwen2.5-14B-Instruct
--served-model-name qwen-14b
--tensor-parallel-size 2
--max-model-len 32768
--gpu-memory-utilization 0.90
--api-key ${VLLM_API_KEY}
--host 0.0.0.0
--port 8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 120s
restart: unless-stopped
vllm-secondary:
image: vllm/vllm-openai:latest
runtime: nvidia
environment:
- NVIDIA_VISIBLE_DEVICES=2,3
volumes:
- /data/models:/data/models
ports:
- "8001:8000"
command: >
--model /data/models/Qwen2.5-14B-Instruct
--served-model-name qwen-14b
--tensor-parallel-size 2
--max-model-len 32768
--gpu-memory-utilization 0.90
--api-key ${VLLM_API_KEY}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 120s
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- vllm-primary
- vllm-secondary
restart: unless-stopped
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
restart: unless-stopped
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
volumes:
- grafana-data:/var/lib/grafana
restart: unless-stopped
volumes:
grafana-data:第六关:监控体系
私有化部署的模型没有监控等于裸跑,出了问题都不知道。
vLLM内置了Prometheus指标,访问/metrics可以拿到:
vllm:gpu_cache_usage_perc # GPU KV Cache使用率
vllm:num_running_seqs # 正在推理的序列数
vllm:num_waiting_seqs # 等待队列长度
vllm:time_to_first_token_seconds # 首token延迟
vllm:e2e_request_latency_seconds # 端到端延迟Prometheus配置:
# prometheus.yml
scrape_configs:
- job_name: 'vllm'
static_configs:
- targets: ['vllm-primary:8000', 'vllm-secondary:8000']
metrics_path: '/metrics'
scrape_interval: 15sGrafana里建议关注的核心指标:
- GPU KV Cache使用率 > 90% 时要警惕,说明快OOM了
- 等待队列长度 持续增长说明容量不够
- 首token延迟 P95 应该控制在3秒以内
- 请求成功率 要高于99.5%
第七关:安全加固
内网部署不等于不用管安全,特别是访问控制这块:
@Component
public class InternalApiKeyFilter implements WebFilter {
private final Set<String> validApiKeys;
public InternalApiKeyFilter(@Value("${ai.internal.api-keys}") String keysConfig) {
this.validApiKeys = Arrays.stream(keysConfig.split(","))
.map(String::trim)
.collect(Collectors.toSet());
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String apiKey = exchange.getRequest()
.getHeaders()
.getFirst("Authorization");
if (apiKey == null || !apiKey.startsWith("Bearer ")) {
return unauthorized(exchange);
}
String key = apiKey.substring(7);
if (!validApiKeys.contains(key)) {
return unauthorized(exchange);
}
// 把调用方信息加到请求头,方便日志追踪
ServerHttpRequest mutatedRequest = exchange.getRequest()
.mutate()
.header("X-Caller-Id", resolveCallerId(key))
.build();
return chain.filter(exchange.mutate().request(mutatedRequest).build());
}
private Mono<Void> unauthorized(ServerWebExchange exchange) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}上线前的检查清单
走了这么多步,上线前我通常会过一遍这个清单:
硬件层
[ ] GPU显存够用(留20%余量)
[ ] 存储IOPS满足模型加载需求
[ ] 网络带宽足够
软件层
[ ] 推理框架版本固定,不用latest标签
[ ] 模型文件MD5校验
[ ] 环境变量通过Secret管理,不硬编码
服务层
[ ] 健康检查接口正常
[ ] 负载均衡配置验证
[ ] 熔断器参数调优
监控层
[ ] Prometheus指标采集正常
[ ] Grafana Dashboard配置完毕
[ ] 告警规则配置完毕(GPU使用率>90%、队列积压>50等)
安全层
[ ] API Key管理方案确认
[ ] 网络访问控制配置
[ ] 日志脱敏处理
运维层
[ ] 模型热更新方案确认
[ ] 回滚方案准备好
[ ] 应急响应手册编写私有化部署是个系统工程,不可能靠一篇文章说完所有细节。这篇主要给大家建立一个完整的框架认知。后续我会针对其中几个点——比如模型的蓝绿发布、多租户权限隔离——单独深入讲。
