通义千问1.5-1.8B-Chat-GPTQ-Int4开源大模型教程:vLLM服务与Redis缓存协同提升首字延迟
你是不是也遇到过这种情况:部署了一个大模型,功能都正常,但每次问它问题,总要等上好几秒才能看到第一个字蹦出来?这种“首字延迟”的等待感,确实有点磨人。
今天,我们就来解决这个问题。我们将基于通义千问1.5-1.8B-Chat-GPTQ-Int4这个轻量级开源模型,手把手教你如何用vLLM高效部署模型服务,并引入Redis缓存这个“加速器”,来显著提升响应的“第一印象”——也就是降低首字延迟。
整个过程就像给模型服务加装了一个“预读缓存”,让常用的回答能更快地呈现给用户。下面,我们就从零开始,一步步实现这个优化方案。
1. 项目概述与核心价值
在深入技术细节之前,我们先搞清楚两件事:我们要用什么,以及为什么要这么做。
我们要用的核心组件:
- 通义千问1.5-1.8B-Chat-GPTQ-Int4:这是一个经过量化处理的轻量级中文对话模型。“1.8B”代表它有18亿参数,规模适中;“GPTQ-Int4”意味着它被压缩成了4位整数格式,这能大幅减少模型对内存的占用,让它在消费级显卡上也能流畅运行,同时保持不错的对话能力。
- vLLM:一个专为大规模语言模型设计的高吞吐量、低延迟推理和服务引擎。它的核心“绝招”是PagedAttention技术,能像操作系统管理内存一样高效管理模型运行时的注意力缓存,从而显著提升推理速度,尤其是在处理多个并发请求时。
- Redis:一个高性能的键值对内存数据库。我们用它来缓存那些频繁被问及的、答案相对固定的问题,比如“你是谁?”、“你能做什么?”。当同样的问题再次出现时,系统可以直接从Redis中读取答案,完全跳过模型推理的步骤,实现“毫秒级”响应。
我们为什么要做这个优化?单纯用vLLM部署模型,虽然推理速度已经很快,但对于每个请求,模型仍然需要从头开始“思考”并生成文本。对于一些高频、通用的查询,这个过程是重复且不必要的。引入Redis缓存后,我们可以:
- 大幅降低首字延迟:对于缓存命中(即问题已有缓存答案)的请求,响应速度可以从几百毫秒甚至几秒降至几毫秒。
- 减轻模型服务器压力:缓存能拦截大量重复请求,让模型服务器能更专注于处理新的、复杂的查询。
- 提升用户体验:用户能立刻得到常见问题的反馈,交互感受更加流畅、即时。
接下来,我们就开始搭建这个“vLLM + Redis”的协同服务体系。
2. 环境准备与基础服务部署
工欲善其事,必先利其器。我们先确保有一个合适的环境,并把vLLM模型服务跑起来。
2.1 基础环境要求
建议在以下环境中进行操作:
- 操作系统:Ubuntu 20.04/22.04 或其它主流Linux发行版。
- Python:版本 3.8 到 3.11。
- 显卡:至少8GB显存的NVIDIA GPU(如RTX 3070, 4060等)。通义千问1.5-1.8B-Int4模型本身很小,但对vLLM的高并发支持需要一定显存。
- 内存:建议16GB或以上。
- 网络:能够顺畅访问Hugging Face等模型仓库。
2.2 部署vLLM模型服务
首先,我们使用vLLM来部署通义千问模型。vLLM的命令行工具让部署变得非常简单。
打开你的终端,执行以下命令:
# 使用vLLM启动模型服务 # --model 指定模型路径或HuggingFace ID # --served-model-name 定义服务名称 # --api-key 可选,设置一个简单的API密钥(生产环境建议用更安全的方式) # --port 指定服务端口,这里用8000 python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen1.5-1.8B-Chat-GPTQ-Int4 \ --served-model-name qwen1.5-1.8b-chat \ --api-key token-abc123 \ --port 8000命令参数简单说明:
--model Qwen/Qwen1.5-1.8B-Chat-GPTQ-Int4:告诉vLLM从Hugging Face下载并加载指定的模型。--served-model-name qwen1.5-1.8b-chat:给你的服务起个名字,后续调用时会用到。--port 8000:服务将在本机的8000端口监听请求。
执行命令后,你会看到vLLM开始下载模型(如果本地没有),然后加载模型到GPU。当看到类似INFO: Application startup complete.和Uvicorn running on http://0.0.0.0:8000的日志时,说明服务已经成功启动。
验证服务是否正常:打开另一个终端,我们可以用curl命令快速测试一下:
curl http://localhost:8000/v1/models如果返回类似下面的JSON,列出了我们刚启动的模型名称,就说明vLLM的OpenAI兼容API服务运行正常。
{ "object": "list", "data": [ { "id": "qwen1.5-1.8b-chat", "object": "model", "created": 1677610602, "owned_by": "vllm" } ] }基础模型服务已经就绪。接下来,我们要构建一个能够连接这个服务并集成Redis缓存的中间层。
3. 构建缓存代理服务
我们的核心思路是:在用户(或前端)和vLLM服务之间,增加一个“智能代理”。这个代理会先检查Redis里有没有缓存答案,有就直接返回,没有再去问vLLM,拿到答案后再存到Redis里。
我们将使用Python的FastAPI框架来快速构建这个代理服务,因为它轻量、异步支持好,非常适合这类任务。
3.1 安装依赖与项目结构
首先,创建一个新的项目目录,并安装必要的Python包。
# 创建项目目录并进入 mkdir qwen_vllm_redis_proxy && cd qwen_vllm_redis_proxy # 创建虚拟环境(可选,但推荐) python -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows # 安装核心依赖 pip install fastapi uvicorn httpx redis pydanticfastapi&uvicorn: 用于创建Web API服务和运行它。httpx: 一个现代的HTTP客户端库,用于异步地向vLLM服务发送请求。redis: Python的Redis客户端。pydantic: 用于数据验证和设置。
3.2 编写缓存代理服务代码
在项目根目录下创建一个名为main.py的文件,并写入以下代码:
from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import Optional import httpx import redis.asyncio as redis import hashlib import json import time app = FastAPI(title="Qwen vLLM Proxy with Redis Cache") # 配置信息 VLLM_API_URL = "http://localhost:8000/v1/chat/completions" VLLM_API_KEY = "token-abc123" # 与启动vLLM时设置的保持一致 MODEL_NAME = "qwen1.5-1.8b-chat" REDIS_URL = "redis://localhost:6379" # 默认Redis地址 CACHE_TTL = 3600 # 缓存过期时间,单位秒(例如1小时) # 初始化异步Redis客户端和HTTP客户端 redis_client = None http_client = None class ChatMessage(BaseModel): role: str content: str class ChatRequest(BaseModel): messages: list[ChatMessage] stream: Optional[bool] = False # 我们暂时处理非流式响应以简化缓存 def generate_cache_key(messages: list[ChatMessage]) -> str: """根据对话历史生成唯一的缓存键""" # 将消息列表转换为可哈希的字符串 content_str = json.dumps([msg.dict() for msg in messages], sort_keys=True) # 使用SHA256生成摘要作为键,避免特殊字符问题 return f"qwen_cache:{hashlib.sha256(content_str.encode()).hexdigest()}" @app.on_event("startup") async def startup_event(): """应用启动时初始化客户端""" global redis_client, http_client redis_client = redis.from_url(REDIS_URL, decode_responses=True) http_client = httpx.AsyncClient(timeout=30.0) # 设置合理的超时时间 @app.on_event("shutdown") async def shutdown_event(): """应用关闭时清理客户端""" if http_client: await http_client.aclose() if redis_client: await redis_client.aclose() @app.post("/v1/chat/completions") async def chat_completions(request: ChatRequest): """ 处理聊天补全请求。 1. 检查Redis缓存。 2. 若未命中,则请求vLLM服务。 3. 将vLLM的响应存入Redis缓存。 """ # 1. 生成缓存键并检查缓存 cache_key = generate_cache_key(request.messages) cached_response = await redis_client.get(cache_key) if cached_response: print(f"[Cache HIT] Key: {cache_key}") # 直接返回缓存的响应 return json.loads(cached_response) print(f"[Cache MISS] Key: {cache_key}. Querying vLLM...") # 2. 准备请求vLLM的载荷 vllm_payload = { "model": MODEL_NAME, "messages": [msg.dict() for msg in request.messages], "stream": request.stream, "max_tokens": 512, # 可根据需要调整 } headers = { "Authorization": f"Bearer {VLLM_API_KEY}", "Content-Type": "application/json" } # 3. 发送请求到vLLM服务 try: start_time = time.time() response = await http_client.post( VLLM_API_URL, json=vllm_payload, headers=headers ) response.raise_for_status() # 如果状态码不是2xx,抛出异常 vllm_data = response.json() end_time = time.time() print(f"[vLLM Response] Time: {end_time - start_time:.2f}s") # 4. 将vLLM的响应存入Redis缓存 # 我们只缓存成功的、非流式的响应 if not request.stream and "choices" in vllm_data: await redis_client.setex(cache_key, CACHE_TTL, json.dumps(vllm_data)) print(f"[Cache SET] Key: {cache_key}, TTL: {CACHE_TTL}s") return vllm_data except httpx.HTTPStatusError as e: raise HTTPException(status_code=e.response.status_code, detail=f"vLLM server error: {e}") except Exception as e: raise HTTPException(status_code=500, detail=f"Internal server error: {e}") @app.get("/health") async def health_check(): """健康检查端点""" return {"status": "healthy", "service": "qwen_vllm_redis_proxy"} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8080)代码核心逻辑解读:
- 定义API:我们创建了一个FastAPI应用,它对外提供的
/v1/chat/completions接口,与vLLM原生的接口格式完全兼容。这意味着之前调用vLLM的前端代码,几乎可以无缝切换到我们这个代理地址。 - 缓存键生成:
generate_cache_key函数将用户输入的对话历史(messages)通过JSON序列化和SHA256哈希,生成一个唯一的字符串作为Redis的键。这确保了相同的问题能得到相同的缓存键。 - 请求处理流程:
- 收到请求后,先根据对话历史生成缓存键。
- 查询Redis,如果找到缓存,立即返回,流程结束。(这就是首字延迟降低的关键!)
- 如果没找到(缓存未命中),则构造请求,转发给后端的vLLM服务。
- 拿到vLLM的响应后,在返回给用户的同时,将这个响应存入Redis,并设置一个过期时间(TTL),避免缓存永久存储陈旧数据。
- 异步处理:我们使用
async/await和异步客户端(AsyncClient,aioredis),这使得代理在等待Redis或vLLM响应时不会阻塞,可以处理更多并发请求。
3.3 启动Redis服务
我们的代理服务依赖Redis。如果你还没有安装Redis,可以快速安装并启动一个。
在Ubuntu/Debian上:
sudo apt update sudo apt install redis-server sudo systemctl start redis-server sudo systemctl enable redis-server使用Docker(推荐,方便干净):
docker run -d --name redis-cache -p 6379:6379 redis:alpine启动后,可以通过redis-cli ping命令测试Redis是否正常运行,如果返回PONG即表示成功。
3.4 启动缓存代理服务
确保vLLM服务(在8000端口)和Redis服务都在运行。然后,在新的终端中,进入你的项目目录,启动我们的FastAPI代理服务:
cd /path/to/qwen_vllm_redis_proxy source venv/bin/activate # 激活虚拟环境 uvicorn main:app --reload --host 0.0.0.0 --port 8080--reload参数使得在开发时修改代码后会自动重启服务。现在,你的缓存代理服务就在http://localhost:8080上运行了。
4. 效果测试与对比
现在,让我们来实际感受一下缓存带来的速度提升。我们将分别向vLLM直接服务和我们的缓存代理服务发送相同的请求。
4.1 测试脚本
创建一个test_speed.py文件:
import httpx import time import asyncio async def test_endpoint(endpoint_url, endpoint_name, use_cache=False): """测试指定端点的响应速度""" headers = {"Content-Type": "application/json"} if "localhost:8000" in endpoint_url: headers["Authorization"] = "Bearer token-abc123" payload = { "model": "qwen1.5-1.8b-chat", "messages": [{"role": "user", "content": "你好,请介绍一下你自己。"}], "max_tokens": 100, "stream": False } async with httpx.AsyncClient() as client: times = [] for i in range(5): # 每个端点测试5次 start = time.time() try: resp = await client.post(endpoint_url, json=payload, headers=headers, timeout=30) resp.raise_for_status() elapsed = time.time() - start times.append(elapsed) print(f" {endpoint_name} 第{i+1}次请求: {elapsed:.3f} 秒") if use_cache and i == 0: print(f" 首次请求(应未命中缓存)") elif use_cache and i > 0: print(f" 后续请求(应命中缓存)") except Exception as e: print(f" {endpoint_name} 请求失败: {e}") times.append(None) await asyncio.sleep(0.5) # 短暂间隔 # 计算平均时间(排除失败的请求) valid_times = [t for t in times if t is not None] if valid_times: avg_time = sum(valid_times) / len(valid_times) print(f"\n{endpoint_name} 平均响应时间: {avg_time:.3f} 秒\n") else: print(f"\n{endpoint_name} 所有请求均失败\n") async def main(): print("开始测试响应速度...\n") print("="*50) # 测试直接访问vLLM print("1. 直接访问 vLLM 服务 (http://localhost:8000/v1/chat/completions):") await test_endpoint("http://localhost:8000/v1/chat/completions", "vLLM Direct") print("="*50) # 测试访问缓存代理(首次请求会缓存) print("2. 访问 缓存代理服务 (http://localhost:8080/v1/chat/completions):") print(" (首次请求将查询vLLM并缓存,后续请求将从Redis读取)") await test_endpoint("http://localhost:8080/v1/chat/completions", "Cache Proxy", use_cache=True) print("="*50) print("测试完成!") if __name__ == "__main__": asyncio.run(main())4.2 运行测试并观察结果
在终端运行测试脚本:
python test_speed.py你会看到类似下面的输出(具体时间取决于你的硬件):
开始测试响应速度... ================================================== 1. 直接访问 vLLM 服务 (http://localhost:8000/v1/chat/completions): vLLM Direct 第1次请求: 0.845 秒 vLLM Direct 第2次请求: 0.832 秒 vLLM Direct 第3次请求: 0.838 秒 vLLM Direct 第4次请求: 0.841 秒 vLLM Direct 第5次请求: 0.830 秒 vLLM Direct 平均响应时间: 0.837 秒 ================================================== 2. 访问 缓存代理服务 (http://localhost:8080/v1/chat/completions): (首次请求将查询vLLM并缓存,后续请求将从Redis读取) Cache Proxy 第1次请求: 0.851 秒 首次请求(应未命中缓存) Cache Proxy 第2次请求: 0.012 秒 后续请求(应命中缓存) Cache Proxy 第3次请求: 0.008 秒 后续请求(应命中缓存) Cache Proxy 第4次请求: 0.007 秒 后续请求(应命中缓存) Cache Proxy 第5次请求: 0.007 秒 后续请求(应命中缓存) Cache Proxy 平均响应时间: 0.177 秒 ================================================== 测试完成!结果分析:
- 直接访问vLLM:每次请求都需要模型进行推理,响应时间稳定在0.83秒左右。
- 访问缓存代理:
- 第一次请求:由于缓存是空的,代理需要将请求转发给vLLM,并将结果写回Redis,耗时约0.85秒,与直接访问相当。
- 后续请求:奇迹发生了!响应时间骤降至0.01秒级别(约10毫秒)。这是因为代理直接从Redis内存中读取了缓存的结果,完全跳过了模型推理和网络传输到vLLM的过程。
结论:对于缓存命中(即重复问题)的请求,响应速度提升了数十倍甚至上百倍,首字延迟从接近1秒降低到了几乎无法感知的毫秒级。这对于提升高频、通用问答场景的用户体验是颠覆性的。
5. 总结与进阶思考
通过本教程,我们成功搭建了一个由vLLM(高性能推理)和Redis(高速缓存)协同工作的通义千问模型服务架构。这个方案的核心优势在于,它用很低的复杂度,显著优化了用户可感知的响应速度。
5.1 核心收获回顾
- vLLM部署简化:我们学会了如何使用一行命令快速部署一个支持OpenAI API格式的量化版通义千问模型服务。
- 缓存策略实现:我们构建了一个智能的FastAPI代理层,它透明地处理了缓存逻辑,对前端应用来说,API接口没有任何变化,但性能却得到了极大提升。
- 性能对比验证:通过实际测试,我们直观地看到了缓存带来的巨大性能收益,特别是对于重复性查询。
5.2 可选的进阶优化方向
当前的实现是一个基础但有效的版本。你可以根据实际需求进行扩展:
- 缓存粒度控制:不是所有对话都适合缓存。可以对请求内容进行分析,例如,只为特定类型的问题(如问候、定义查询)或包含特定关键词的问题启用缓存。
- 缓存键优化:当前的缓存键基于完整的对话历史。在实际聊天中,可能只需要基于最近一轮或最近几轮对话生成缓存键,以避免因冗长历史导致的缓存命中率过低。
- 缓存失效策略:除了固定的TTL,还可以实现更复杂的失效策略。例如,当模型更新时,可以清空所有或部分缓存。
- 分布式与高可用:生产环境中,可以考虑使用Redis集群来提高缓存服务的可用性和容量。代理服务本身也可以通过多实例部署,并用Nginx等负载均衡器来分发流量。
- 监控与指标:集成Prometheus、Grafana等工具,监控缓存命中率、请求延迟、服务错误率等关键指标,以便更好地了解服务状态和优化方向。
这个“vLLM + Redis缓存”的模式,是一种经典的“计算密集型服务+内存缓存”的优化组合,其思路不仅可以用于大语言模型,也可以广泛应用于其他AI服务(如图像生成、语音识别等)的性能优化中。希望这个教程能为你部署和优化自己的AI应用带来启发。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。