企业私有化大模型部署落地全流程:从硬件评估到服务上线
给一个金融企业客户做私有化大模型部署,核心诉求很明确:数据绝对不能出内网,模型和推理服务全部部署在客户机房。从硬件评估到最终上线交付,花了三周。这篇完整复盘全流程。
私有化部署的核心约束
跟云端调用 API 完全是两个世界:
┌──────────────────────────────────────────────────────────────┐
│ 云端 API vs 私有化部署 │
├────────────────────────┬─────────────────────────────────────┤
│ 云端 API │ 私有化部署 │
├────────────────────────┼─────────────────────────────────────┤
│ 按量付费,用多少花多少 │ 一次性硬件投入,固定成本 │
│ 弹性扩缩容 │ 硬件固定,算力天花板明确 │
│ 模型随便换 │ 部署后换模型成本高 │
│ 运维云厂商负责 │ 运维全靠自己 │
│ 数据上传到云端 │ 数据不出内网 │
│ 模型效果直接用最好的 │ 受硬件限制,模型可能要量化压缩 │
└────────────────────────┴─────────────────────────────────────┘私有化部署的核心矛盾是:在有限的硬件预算下,跑出接近云端 API 的效果。
第一步:硬件评估
这是整个项目的基础,算错了要么浪费钱,要么跑不起来。
显存需求计算公式
模型显存占用 = 模型参数权重 + KV-Cache + 激活值
┌─────────────────────────────────────────────────────────────┐
│ 13B 模型显存计算示例(FP16 精度) │
│ │
│ 模型参数:13B × 2 bytes = 26 GB │
│ KV-Cache(每路并发)≈ 1.5 GB │
│ 激活值 + 碎片 ≈ 2 GB │
│ │
│ 基础占用:26 + 2 = 28 GB │
│ 每增加 1 路并发:+ 1.5 GB │
│ │
│ 目标 10 路并发:28 + 10 × 1.5 = 43 GB → A100 80G 够用 │
│ 目标 20 路并发:28 + 20 × 1.5 = 58 GB → A100 80G 够用 │
│ 目标 30 路并发:28 + 30 × 1.5 = 73 GB → A100 80G 刚好 │
│ │
│ 如果用 INT4 量化:参数 26GB → 6.5 GB │
│ 基础占用降到 8.5 GB,同卡可扛 40+ 路并发 │
└─────────────────────────────────────────────────────────────┘硬件选型方案
根据客户预算和性能要求,给了三档方案:
| 方案 | GPU | 显存 | 适配模型 | 并发能力 | 大致成本 |
|---|---|---|---|---|---|
| 高配 | 1× A100 80G | 80 GB | 13B FP16 / 70B INT4 | 20-30 路 | ~15 万 |
| 中配 | 1× A10 24G | 24 GB | 7B FP16 / 13B INT4 | 8-12 路 | ~4 万 |
| 低配 | 1× RTX 4090 | 24 GB | 7B FP16 / 13B INT4 | 8-12 路 | ~1.5 万 |
客户选了中配方案:A10 24G + 13B 模型 INT4 量化。原因:金融场景对效果有要求但不能接受 A100 的成本,13B INT4 是效果和成本的平衡点。
容易忽略的硬件细节
- CPU:推理服务 CPU 占用不高,但 tokenizer 编码和数据预处理需要算力。建议 8 核以上
- 内存:至少是显存的 2 倍。模型加载时需要先把权重读进内存再传到显存
- 磁盘:SSD 必须。模型文件动辄几十 GB,HDD 加载慢到怀疑人生
- 网络:内网千兆够用,但如果多卡分布式推理需要万兆
第二步:模型选型与量化
模型选型
金融场景对中文理解和专业术语要求高,候选模型:
| 模型 | 参数量 | 中文能力 | 金融领域 | 显存(FP16) |
|---|---|---|---|---|
| Qwen2-13B-Chat | 13B | 优秀 | 中等 | 26 GB |
| Qwen2-7B-Chat | 7B | 优秀 | 中等 | 14 GB |
| ChatGLM3-6B | 6B | 优秀 | 较弱 | 12 GB |
| Baichuan2-13B | 13B | 优秀 | 中等 | 26 GB |
最终选 Qwen2-13B-Chat:中文能力最强,社区生态好,vLLM 支持完善。
量化方案对比
A10 24G 跑 13B FP16 显存不够(26 GB > 24 GB),必须量化。
量化方案对比:
精度 显存(13B) 效果损失 推理速度 推荐场景
─────────────────────────────────────────────────────────
FP16 26 GB 基准 基准 显存够就用
INT8 13 GB ~2% 略快 显存不够但想保效果
INT4(AWQ) 6.5 GB ~5% 快 显存紧张、要并发
INT4(GPTQ) 6.5 GB ~6% 快 同上,AWQ 替代
GGUF Q4 7 GB ~5% 较慢 Ollama 本地跑选了 AWQ INT4 量化:效果损失最小(约 5%),推理速度比 FP16 还快(计算量小),显存降到 6.5 GB,24G 显存卡能扛 10+ 路并发。
量化操作
直接用社区预制的 AWQ 量化模型,不用自己量化(自己量化需要校准数据集,耗时长且效果不可控):
# 下载 AWQ 量化模型
huggingface-cli download Qwen/Qwen2-13B-Chat-AWQ \
--local-dir /models/Qwen2-13B-Chat-AWQ
# 验证模型完整性
ls -lh /models/Qwen2-13B-Chat-AWQ/
# 总大小约 7-8 GB第三步:推理服务搭建
部署架构
┌─────────────────────────────────────────────────────────────┐
│ 客户内网环境 │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Nginx │────→│ API 网关 │────→│ vLLM 推理服务 │ │
│ │ (443) │ │ (FastAPI) │ │ (GPU 推理) │ │
│ └──────────┘ └──────┬───────┘ └───────┬───────┘ │
│ │ │ │
│ ┌──────┴───────┐ ┌──────┴───────┐ │
│ │ Redis │ │ GPU: A10 │ │
│ │ (会话缓存) │ │ 显存: 24GB │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 监控 & 运维 │ │
│ │ Prometheus + Grafana (GPU/服务监控) │ │
│ │ 日志收集 (Loki / 文件日志) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 所有数据在内网流转,不访问任何外部网络 │
└─────────────────────────────────────────────────────────────┘vLLM 推理服务
用 Docker 部署,保证环境一致性:
FROM vllm/vllm-openai:latest
# 拷贝模型文件(内网无法从 HuggingFace 下载)
COPY ./models/Qwen2-13B-Chat-AWQ /models/Qwen2-13B-Chat-AWQ
# 启动命令
CMD ["--model", "/models/Qwen2-13B-Chat-AWQ", \
"--quantization", "awq", \
"--max-model-len", "4096", \
"--max-num-seqs", "12", \
"--gpu-memory-utilization", "0.9", \
"--host", "0.0.0.0", \
"--port", "8000"]# docker-compose.yml
version: '3.8'
services:
vllm:
build: .
container_name: vllm-server
restart: always
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
ports:
- "8000:8000"
volumes:
- ./models:/models:ro
environment:
- NVIDIA_VISIBLE_DEVICES=0
gateway:
image: python:3.11-slim
container_name: api-gateway
restart: always
depends_on:
- vllm
ports:
- "8080:8080"
# 网关代码...
redis:
image: redis:7-alpine
container_name: redis
restart: always
ports:
- "6379:6379"vLLM 兼容 OpenAI API 格式
vLLM 默认提供 OpenAI 兼容的 API 接口,业务代码不用改:
from openai import OpenAI
# 指向内网 vLLM 服务
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="internal-key" # 内网也需要鉴权
)
response = client.chat.completions.create(
model="/models/Qwen2-13B-Chat-AWQ",
messages=[{"role": "user", "content": "解释一下资产负债率"}],
temperature=0.3,
max_tokens=512
)第四步:安全加固
金融客户对安全的要求极其严格,以下几项必须做到:
1. 网络隔离
# Nginx 只开放 443 端口,强制 HTTPS
server {
listen 443 ssl;
server_name llm.internal.company.com;
ssl_certificate /etc/ssl/internal.crt;
ssl_certificate_key /etc/ssl/internal.key;
# 只允许内网网段访问
allow 10.0.0.0/8;
deny all;
location / {
proxy_pass http://gateway:8080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# SSE 流式响应配置
proxy_buffering off;
proxy_read_timeout 120s;
}
}2. API 鉴权
即使内网也不能裸奔,加一层 API Key 鉴权 + 速率限制:
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
import time
app = FastAPI()
VALID_KEYS = {"internal-service-key-xxx"}
# 速率限制:每分钟最多 60 次
rate_limit = {}
@app.middleware("http")
async def auth_and_rate_limit(request: Request, call_next):
# 鉴权
api_key = request.headers.get("Authorization", "").replace("Bearer ", "")
if api_key not in VALID_KEYS:
raise HTTPException(401, "Unauthorized")
# 速率限制
client_ip = request.client.host
now = time.time()
window = rate_limit.get(client_ip, [])
window = [t for t in window if now - t < 60] # 清理 60 秒外的记录
if len(window) >= 60:
raise HTTPException(429, "Rate limit exceeded")
window.append(now)
rate_limit[client_ip] = window
return await call_next(request)3. 内容安全过滤
金融场景不能输出违规内容,加一层输入/输出过滤:
SENSITIVE_PATTERNS = [
"股票代码", "买入", "卖出", "涨停", # 投资建议类
"身份证", "银行卡号", # 隐私信息类
]
def filter_content(text: str) -> str:
"""过滤敏感内容"""
for pattern in SENSITIVE_PATTERNS:
if pattern in text:
return f"[内容已过滤:包含敏感词「{pattern}」]"
return text
# 在生成前后都做过滤
def safe_chat(prompt: str) -> str:
# 输入过滤
if filtered := filter_content(prompt):
if filtered != prompt:
return "抱歉,您的输入包含敏感内容"
# 调用模型
response = call_llm(prompt)
# 输出过滤
return filter_content(response)第五步:监控与运维
GPU 监控
部署 dcgm-exporter 采集 GPU 指标:
# Prometheus 采集 GPU 指标
scrape_configs:
- job_name: 'gpu'
static_configs:
- targets: ['dcgm-exporter:9400']
metrics_path: '/metrics'核心监控指标:
| 指标 | 告警阈值 | 说明 |
|---|---|---|
| GPU 显存使用率 | > 95% | 即将 OOM |
| GPU 利用率 | < 10% 持续 5min | 服务可能挂了 |
| 推理延迟 P99 | > 10s | 排队过长 |
| 请求错误率 | > 5% | 服务异常 |
| 队列深度 | > 20 | 并发过高 |
日志收集
import logging
import json
logger = logging.getLogger("llm-service")
def log_request(request_id: str, prompt: str, response: str, usage: dict):
"""结构化日志,方便后续分析"""
logger.info(json.dumps({
"request_id": request_id,
"prompt_length": len(prompt),
"response_length": len(response),
"prompt_tokens": usage.get("prompt_tokens"),
"completion_tokens": usage.get("completion_tokens"),
"latency_ms": usage.get("latency_ms"),
"timestamp": time.time()
}, ensure_ascii=False))
# 注意:不记录 prompt 和 response 原文(金融合规要求)上线效果
最终交付的实测数据:
| 指标 | 目标 | 实测 | 达标 |
|---|---|---|---|
| 首 token 延迟 | < 2s | 0.8s | ✅ |
| 完整回答延迟 | < 10s | 4.5s | ✅ |
| 并发支持 | 10 路 | 12 路 | ✅ |
| 显存占用 | < 90% | 78% | ✅ |
| 数据外泄 | 0 | 0 | ✅ |
复盘总结
企业私有化部署和云端调 API 是完全不同的工程。核心区别在于:你要对从硬件到应用的每一层负责。
关键经验清单:
| 环节 | 核心决策 | 经验 |
|---|---|---|
| 硬件评估 | 显存 = 参数 + KV-Cache × 并发 | 留 20% 余量 |
| 模型选型 | 中文场景优先 Qwen / Baichuan | 看社区生态 |
| 量化方案 | AWQ INT4 是性价比之王 | 效果损失约 5% |
| 推理引擎 | vLLM 生产首选 | Continuous Batching |
| 安全加固 | 鉴权 + 限流 + 内容过滤 | 内网也不能裸奔 |
| 监控运维 | GPU 显存 + 延迟 + 错误率 | 提前配告警 |
最后一句:私有化部署不是「把模型跑起来」就完事,而是交付一个稳定、安全、可运维的完整服务。硬件评估、量化选型、安全加固、监控运维,缺一不可。