最近,我在没有GPU的情况下尝试了在本地运行DeepSeek R1(基于Qwen 7B蒸馏过)。我的所有CPU核心和线程都被充分利用到了极限,温度最高达到了90摄氏度(Ryzen 5 7600)。
我的CPU在运行ollama deepseek-r1的精简版qwen 7b
我的朋友说为什么不用Google Colab呢?因为它免费提供了3到4个小时的GPU。他用RAG应用程序解析超过80页的PDF并串联大型语言模型,因为我们仍然可以利用这个免费资源。
而且我确实做到了,我试用了T4(20系列)但有一些限制条件(后面我会解释,简而言之,由于它是免费的)。所以我一直在用它在Google Colab上测试VLLM。我用FastAPI和ngrok来公开API(出于测试目的,为什么不呢?)。
好啦,是时候说清楚所有的事情以及我为什么这么做了。
(警告:此仅为测试目的)
PIP(Pip 包管理工具)它允许你安装和管理不在标准Python库中的额外库和依赖。我们现在要通过在Jupyter Notebook代码中输入命令!来使用命令行来安装它。
!pip install fastapi nest-asyncio pyngrok uvicorn
!pip install vllm
我们将安装FastAPI、nest-asyncio、pyngrok和Uvicorn,用它们来处理来自外部来源的HTTP请求。VLLM主要是用于LLM推理和提供服务的库,而我们主要会用它来提供服务。虽然Ollama也是一个选择,但我认为这种方法会更有效。
现在我们即将开始与VLLM功能互动。 # 加载和运行模型:
import subprocess
import time
import os
# 在后台启动 vllm 服务
vllm_process = subprocess.Popen([
'vllm',
'serve', # 子命令必须跟随 vllm
'deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B',
'--trust-remote-code',
'--dtype', 'half',
'--max-model-len', '16384', # 这是你可以发送和接收的最大 token 长度
'--enable-chunked-prefill', 'true',
'--tensor-parallel-size', '1'
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, start_new_session=True)
好的,这是我是如何加载模型的,方法是,在后台启动vllm服务器,因为在Jupyter Notebook中运行vllm会卡住,我们无法继续进行操作(我认为可能有其他方法,但我选择这样做)。接下来我们就可以看到
- — trust-remote-code,因此它信任远程代码的安全性。
- — dtype,使用 half 来减少内存占用。
- — max-model-len,指你想要发送和接收的最大输入和输出标记数的总和。
- — enable-chunked-prefill,指的是在生成开始前将标记 预加载(预填充) 到模型中的过程。
- — tensor-parallel-size,将模型 拆分到多个 GPU 以加速推理。
这样做我们就达不到T4的极限了
- 注意CUDA内存不足的错误,我们只有15GB的VRAM。
- Colab的GPU内存限制可能需要调整参数,以适应这种情况。
- 12GB的RAM应该足够了……我想是够用了。
现在就运行它。
子任务好的,因为我们用了 subprocess
并设置了 start_new_session=True
,我们通常无法看到错误信息,直到出错。所以我们无法正常地将输出进行管道处理,如果有错误,我们无法在错误发生之前看到错误信息。
import requests
def check_vllm_status():
try:
response = requests.get("http://localhost:8000/health")
if response.status_code == 200:
print("vllm服务器已经准备好,可以开始服务了。")
return True
except requests.exceptions.ConnectionError:
print("vllm服务器没有运行")
return False
try:
# 监控vllm进程
while True:
if check_vllm_status() == True:
print("vllm服务器已经准备好,可以开始服务了。")
break
else:
print("vllm服务器停止了。")
stdout, stderr = vllm_process.communicate(timeout=10)
print(f"STDOUT: {stdout.decode('utf-8')}")
print(f"STDERR: {stderr.decode('utf-8')}")
break
time.sleep(5) # 每5秒检查一次
except KeyboardInterrupt:
print("停止检查vllm服务器...")
它每5秒检查一次,如果出现错误则尝试管道输出,或打印vllm的stderr和out信息。如果vllm没有问题,则可以继续执行下一段代码。
创建vLLM函数调用import requests
import json
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from fastapi.responses import StreamingResponse
# 输入请求的模式
class QuestionRequest(BaseModel):
question: str
model: str = "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B" # 默认模型
def ask_model(question: str, model: str):
"""
向模型服务器发送请求并获取响应。
"""
url = "http://localhost:8000/v1/chat/completions" # 如果 URL 不同,请调整为正确的 URL
headers = {"Content-Type": "application/json"}
data = {
"model": model,
"messages": [
{
"role": "user",
"content": question
}
]
}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status() # 对 HTTP 错误抛出异常,例如 400 或 500 状态码
return response.json()
# 使用示例:
result = ask_model("法国的首都是哪里?", "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B")
print(json.dumps(result, indent=2, ensure_ascii=False))
def stream_llm_response(question: str, model: str):
url = "http://localhost:8000/v1/chat/completions"
headers = {"Content-Type": "application/json"}
data = {
"model": model,
"messages": [{"role": "user", "content": question}],
"stream": True # 🔥 启用流式传输
}
with requests.post(url, headers=headers, json=data, stream=True) as response:
for line in response.iter_lines():
if line:
# OpenAI 风格的流式响应以 "data:" 为前缀
decoded_line = line.decode("utf-8").replace("data:", "")
yield decoded_line + "\n"
我们有两个API(应用程序接口)用来测试,
请求模型函数目的:
向vLLM服务器发送一个请求并等待完整的响应。
它是怎么工作的:
- 向
[http://localhost:8000/v1/chat/completions](http://localhost:8000/v1/chat/completions.)
发送一个 POST 请求。 -
发送包含以下内容的 JSON 数据:
-
模型名称。
- 用户的问题(作为消息)。
- 等待响应并以 JSON 格式返回。
主要特点:
- 阻塞呼叫(等待直到响应完全生成)。
- 请求失败时会抛出异常。
目的:
将 vLLM 的响应流式传输,而不是等待所有内容输出完毕。
它是怎么工作的:
- 发送一个带有
"stream": True
的 POST 请求,以启用分块响应。 - 使用
response.iter_lines()
实时 处理每个接收到的分块。 - 每个接收到的分块会被解码并作为流输出。
关键特性:
- 非阻塞流(非常适合聊天机器人和互动应用)。
- 数据以小部分返回,减少延迟感。
我们试了一下,结果就像这样:
{
"id": "chatcmpl-680bc07cd6de42e7a00a50dfbd99e833",
"object": "chat.completion",
"created": 1738129381,
"model": "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "<think>\n我在试着找出法国的首都是哪里。嗯,我记得听人说过一些城市的名字是以神话命名的。让我想想。我想 Neuchâtel 是那个逗号的名字。对了,那是对的,不过有时他们改过,但我想现在还是那个名字。然后还有 Charles-de-Lorraine。我以前在各种上下文中见过这个名字,可能是经理之类的人。还有 Saint Malo,我也觉得那是个法国重要的城市。等等,我有点困惑于最后一个。那是首都还是其他什么地方?我想那个首都曾经让我想不起来,我现在也记不起来了。让我想想一些名字。可能是巴黎?但还有其他名字吗?我听说过 Guingamp 和 Agoura 这些名字也是神话人物命名的,但他们是不是首都?我不这么认为。所以在这几个重要的名字里,可能 Neuchâtel, Charles-de-Lorraine 和 Saint Malo 都是首都的名字,但我不确定是哪一个。等等,我想我可能把其中一些混淆了。让我试着查一下真正的首都。法国的首都是巴黎,而不是 Place de la Confluense 或 la Confluense。巴黎是法国的首都,而不是其他地方。巴黎是法国的首都,不是其他地方。所以首都是巴黎,而不是其他地方。巴黎是法国的首都。巴黎是法国的首都,而不是其他地方。\n</think>\n\n法国的首都是巴黎。它的正式名称是 "巴黎"。",
"tool_calls": []
},
"logprobs": null,
"finish_reason": "stop",
"stop_reason": null
}
],
"usage": {
"prompt_tokens": 10,
"total_tokens": 550,
"completion_tokens": 540,
"prompt_tokens_details": null
},
"prompt_logprobs": null
}
API 路径
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import nest_asyncio
from pyngrok import ngrok
import uvicorn
import getpass
from pyngrok import ngrok, conf
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)
@app.get('/')
async def root():
return {'hello': 'world'}
@app.post("/api/v1/generate-response")
def generate_response(request: QuestionRequest):
"""
API endpoint to generate a response from the model.
"""
try:
response = ask_model(request.question, request.model)
return {"response": response}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/v1/generate-response-stream")
def stream_response(request: QuestionRequest):
try:
response = stream_llm_response(request.question, request.model)
return StreamingResponse(response, media_type="text/event-stream")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
好的,现在我们为每个创建的功能生成API路由,每个API会使用不同的方式,是使用流式处理还是生成响应但不阻塞。这里我们需要快速处理,所以会允许所有请求。如果遇到错误,我们会直接返回一个内部服务器错误并详细记录错误信息。
ngrok -> 公网隧道设置 Ngrok 的自动认证令牌: `! ngrok config add-authtoken ${your-ngrok-token}`
请将 `${your-ngrok-token}` 替换为您的 Ngrok 认证令牌.
我们要添加配置令牌,只需从ngrok仪表板此处复制并粘贴你的令牌。
曝光它然后我们把它展示出来。
端口 = 8081
# 将 ngrok 隧道打开到 HTTP 服务器上
public_url = ngrok.connect(端口).public_url
print(f" * ngrok tunnel \"{public_url}\" -> \"http://127.0.0.1:{端口}\" 如下所示")
我们使用curl或postman来获取返回隧道的数据。
nest_asyncio.apply()
uvicorn.run(app, port=port)
就这样吧,运行服务后,它就顺利运行了……我想。
现在你可以这样打它。
curl --location 'https://6ee6-34-125-245-24.ngrok-free.app/api/v1/generate-response-stream' \
--header 'Content-Type: application/json' \
--data '{
"question": "巴黎在哪儿?",
"model": "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B"
}'
如果你选了每个令牌流的回复,得到的回答会更像这样。
对每个令牌通过流式响应进行回应
gaaaly 还不错,反应还挺快……输出的答案也挺好的(如果你想得到更简洁的回答,比如关于代码,而不是创意方面)。这对于一个开源模型来说已经相当不错了,就像 Facebook 开源的 Llama 一样。
点击这里可以查看完整的代码
干脆把它结束。经过这一切,我建议你们……如果你们有钱的话,买个GPU来本地运行,但这需要较好的GPU和高额的电费,但你可以完全掌控自己的数据。你也可以使用 chat.deepseek.com,如果你想用FAAASSTTTT的DeepSeek大模型(有6710亿个参数),但你的数据不完全属于你,就像Claude和OpenAI这样的其他平台。
大家对如何在Google Colab中测试vllm有什么想法吗?可以在评论区留言,或者分享你们的小技巧。
共同学习,写下你的评论
评论加载中...
作者其他优质文章