为了账号安全,请及时绑定邮箱和手机立即绑定

在Google Colab上试用VLLM和DeepSeek R1模型:快速入门指南

最近,我在没有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会卡住,我们无法继续进行操作(我认为可能有其他方法,但我选择这样做)。接下来我们就可以看到

  1. — trust-remote-code,因此它信任远程代码的安全性。
  2. — dtype,使用 half 来减少内存占用。
  3. — max-model-len,指你想要发送和接收的最大输入和输出标记数的总和。
  4. — enable-chunked-prefill,指的是在生成开始前将标记 预加载(预填充) 到模型中的过程。
  5. — 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 格式返回。

主要特点:

  • 阻塞呼叫(等待直到响应完全生成)。
  • 请求失败时会抛出异常。
stream_llm_response 这个函数

目的:
将 vLLM 的响应流式传输,而不是等待所有内容输出完毕。

它是怎么工作的:

  • 发送一个带有 "stream": TruePOST 请求,以启用分块响应
  • 使用 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 一样。

点击这里可以查看完整的代码

https://github.com/naufalhakim23/deepseek-r1-playground/blob/main/deepseek_r1_distill_qwen_fast_api.ipynb

干脆把它结束。

经过这一切,我建议你们……如果你们有钱的话,买个GPU来本地运行,但这需要较好的GPU和高额的电费,但你可以完全掌控自己的数据。你也可以使用 chat.deepseek.com,如果你想用FAAASSTTTT的DeepSeek大模型(有6710亿个参数),但你的数据不完全属于你,就像Claude和OpenAI这样的其他平台。

大家对如何在Google Colab中测试vllm有什么想法吗?可以在评论区留言,或者分享你们的小技巧。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消