部署一个推理 LLM#

   

推理 LLM 可处理需要更深入分析或逐步思考的任务。它会在得出最终答案之前生成中间推理过程,因此更适合那些需要仔细逻辑或结构化问题解决能力,而非速度或效率的场景。

本教程将使用 Ray Serve LLM 部署一个推理 LLM。


比较推理模型和非推理模型#

推理模型模拟逐步、结构化的思考过程来解决复杂的任务,例如数学、多跳问答或代码生成。相比之下,非推理模型提供快速、直接的响应,侧重于流畅性或指令遵循,而无需显式的中间推理。关键区别在于模型在回答前是否尝试“思考”问题。

模型类型

核心行为

用例示例

限制

推理模型

显式的多步思考过程

数学、编码、逻辑谜题、多跳问答、思维链提示 (CoT prompting)

响应时间较慢,使用的 token 较多。

非推理模型

直接生成答案

随意查询、简短指令、单步回答

可能难以处理复杂的推理或解释性。

许多支持推理的模型会使用特殊标记来组织其输出,例如 <think> 标签,或者在 OpenAI API 响应的 reasoning_content 等专用字段中暴露推理轨迹。请务必查阅模型的文档,了解如何构建和控制推理过程。

注意:推理 LLM 通常受益于长上下文窗口(32K 至 100 万 token)、高 token 吞吐量、低温度解码(贪婪采样)以及强大的指令微调或草稿式推理。


选择何时使用推理模型#

是否应使用推理模型取决于您的提示中已提供了多少信息。

如果您的输入清晰且完整,标准模型通常更快、更有效。如果您的输入含糊或复杂,推理模型效果更好,因为它可以通过逐步分析问题并利用中间推理来填补空白。


解析推理输出#

推理模型通常使用 <think>...</think> 等标签将推理最终答案分开。如果未使用适当的解析器,此推理过程可能会出现在 content 字段而不是专用的 reasoning_content 字段中。

要正确提取推理过程,请在您的 Ray Serve 部署中配置一个 reasoning_parser。这将告知 vLLM 如何将模型的思考过程与输出的其余部分隔离。
注意:例如,QwQ 使用 deepseek-r1 解析器。其他模型可能需要不同的解析器。请参阅 vLLM 文档或您的模型文档,查找支持的解析器,或者如果需要,自行构建一个

applications:
- name: reasoning-llm-app
  ...
  args:
    llm_configs:
      - model_loading_config:
          model_id: my-qwq-32B
          model_source: Qwen/QwQ-32B
        ...
        engine_kwargs:
          ...
          reasoning_parser: deepseek_r1 # <-- for QwQ models

请参阅 配置 Ray Serve LLM 获取完整示例。

示例响应
使用推理解析器时,响应通常结构如下

ChatCompletionMessage(
    content="The temperature is...",
    ...,
    reasoning_content="Okay, the user is asking for the temperature today and tomorrow..."
)

您可以按如下方式提取内容和推理过程

response = client.chat.completions.create(
  ...
)

print(f"Content: {response.choices[0].message.content}")
print(f"Reasoning: {response.choices[0].message.reasoning_content}")

配置 Ray Serve LLM#

在配置文件中设置您的 Hugging Face 令牌,以便访问受限模型。

Ray Serve LLM 提供了多种 Python API 来定义您的应用程序。使用 build_openai_app 从您的 LLMConfig 对象构建完整的应用程序。

设置 tensor_parallel_size=8 将模型权重分发到节点上的 8 个 GPU。

# serve_qwq_32b.py
from ray.serve.llm import LLMConfig, build_openai_app
import os

llm_config = LLMConfig(
    model_loading_config=dict(
        model_id="my-qwq-32B",
        model_source="Qwen/QwQ-32B",
    ),
    accelerator_type="L40S", # Or "A100-40G"
    deployment_config=dict(
        # Increase number of replicas for higher throughput/concurrency.
        autoscaling_config=dict(
            min_replicas=1,
            max_replicas=2,
        )
    ),
    ### Uncomment if your model is gated and needs your Hugging Face token to access it
    # runtime_env=dict(env_vars={"HF_TOKEN": os.environ.get("HF_TOKEN")}),
    engine_kwargs=dict(
        # 4 GPUs is enough but you can increase tensor_parallel_size to fit larger models.
        tensor_parallel_size=4, max_model_len=32768, reasoning_parser="deepseek_r1"
    ),
)

app = build_openai_app({"llm_configs": [llm_config]})

注意:在迁移到生产环境之前,请迁移到 Serve 配置文件,以便您的部署版本控制、可重现,并且更容易维护 CI/CD 管道。请参阅服务 LLM - 快速入门示例:生产指南以获取示例。


本地部署#

先决条件

  • GPU 计算访问权限。

  • (可选) 如果使用受限模型,则需要 **Hugging Face 令牌**。将其存储在 export HF_TOKEN=<YOUR-TOKEN-HERE>

注意:根据情况,您通常可以在模型的 Hugging Face 页面上请求访问权限。例如,Meta 的 Llama 模型批准可能需要几小时到几周的时间。

依赖项

pip install "ray[serve,llm]"

启动服务#

请遵循 配置 Ray Serve LLM 中的说明,在 Python 模块 serve_qwq_32b.py 中定义您的应用程序。

在终端中,运行

serve run serve_qwq_32b:app --non-blocking

部署通常需要几分钟时间,因为集群会被配置、vLLM 服务器会启动,并且模型会被下载。


发送请求#

您的端点在本地的 https://:8000 上可用,您可以使用占位符认证 token 来访问 OpenAI 客户端,例如 "FAKE_KEY"

示例 curl

curl -X POST http://localhost:8000/v1/chat/completions \
  -H "Authorization: Bearer FAKE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "model": "my-qwq-32B", "messages": [{"role": "user", "content": "Pick three random words with 3 syllables each and count the number of R'\''s in each of them"}] }'

示例 Python

#client.py
from urllib.parse import urljoin
from openai import OpenAI

API_KEY = "FAKE_KEY"
BASE_URL = "https://:8000"

client = OpenAI(base_url=urljoin(BASE_URL, "v1"), api_key=API_KEY)

response = client.chat.completions.create(
    model="my-qwq-32B",
    messages=[
        {"role": "user", "content": "What is the sum of all even numbers between 1 and 100?"}
    ]
)

print(f"Reasoning: \n{response.choices[0].message.reasoning_content}\n\n")
print(f"Answer: \n {response.choices[0].message.content}")

如果您配置了有效的推理解析器,推理输出应出现在响应消息的 reasoning_content 字段中。否则,它可能包含在主 content 字段中,通常用 <think>...</think> 标签括起来。有关更多信息,请参阅 解析推理输出


关停#

关停您的 LLM 服务

serve shutdown -y

使用 Anyscale 服务部署到生产环境#

对于生产环境,请使用 Anyscale 服务将您的 Ray Serve 应用程序部署到专用集群,无需修改代码。Anyscale 提供可伸缩性、容错性和负载均衡,确保在节点故障、高流量和滚动更新时具有弹性。请参阅 部署中等规模 LLM 了解使用类似QwQ-32B 这样中等规模模型(此处使用)的示例。


流式传输推理内容#

推理模型可能需要更长时间才能开始生成主要内容。您可以像流式传输主要内容一样,流式传输其中间推理输出。

#client_streaming.py
from urllib.parse import urljoin
from openai import OpenAI

API_KEY = "FAKE_KEY"
BASE_URL = "https://:8000"

client = OpenAI(base_url=urljoin(BASE_URL, "v1"), api_key=API_KEY)

# Example: Complex query with thinking process
response = client.chat.completions.create(
    model="my-qwq-32B",
    messages=[
        {"role": "user", "content": "I need to plan a trip to Paris from Seattle. Can you help me research flight costs, create an itinerary for 3 days, and suggest restaurants based on my dietary restrictions (vegetarian)?"}
    ],
    stream=True
)

# Stream
for chunk in response:
    # Stream reasoning content
    if hasattr(chunk.choices[0].delta, "reasoning_content"):
        data_reasoning = chunk.choices[0].delta.reasoning_content
        if data_reasoning:
            print(data_reasoning, end="", flush=True)
    # Later, stream the final answer
    if hasattr(chunk.choices[0].delta, "content"):
        data_content = chunk.choices[0].delta.content
        if data_content:
            print(data_content, end="", flush=True)

总结#

在本教程中,您从开发到生产部署了一个使用 Ray Serve LLM 的推理 LLM。您学习了如何使用正确的推理解析器配置 Ray Serve LLM,在您的 Ray 集群上部署您的服务,发送请求,以及解析响应中的推理输出。