vLLM 是一个专门为大语言模型(LLM)推理优化的高效推理框架,旨在大幅提升大模型的推理速度,并降低显存占用。它采用了一系列优化技术,如高效的连续批处理(PagedAttention)和动态 KV 缓存管理,使其在 GPU 推理场景下相比传统方法更具优势。

1. 直接启动本地服务

LazyLLM 原生支持了 LightLLM、LMDeploy 和 vLLM 三种大模型推理加速框架和 Infinity 嵌入模型加速框架,用户在使用时仅需通过 TrainableModule.deploy_method 指定对应的框架即可。

from lazyllm import TrainableModule, deploy

llm = TrainableModule('model_name').deploy_method(deploy.vllm)

vLLM 支持多数大模型的推理服务,您可以先确认您想用的模型是否在 LazyLLM 支持的模型列表中。在这个列表中后面带有 AWQ、4bit 等标识的都是量化模型,您可以根据您的需求选择一个量化模型然后启动对话服务,例如:

from lazyllm import TrainableModule, deploy

llm = TrainableModule('Qwen2-72B-Instruct-AWQ').deploy_method(deploy.vllm).start()
print(llm("hello, who are you?"))

我们以 Qwen2-72B-Instruct 和它的 AWQ 量化版本为例,对比一下二者的大小及运行速度。运行 BF16 或 FP16 的 Qwen2-72B-Instruct 模型运行需要至少 144GB 显存(例如 2xA100-80G 或 5xV100-32G);运行 Int4 模型至少需要 48GB 显存(例如 1xA100-80G 或 2xV100-32G),是原来的 33%

(数据来源于 Qwen 官方魔塔社区:

https://modelscope.cn/models/qwen/Qwen-72B/)。

下面我们简单对比一下二者的运行速度,更严谨的情况下可以对比多次执行的差异

(完整代码见 GitHub 链接:

https://github.com/LazyAGI/Tutorial/blob/patch-4/rag/codes/chapter12/use_quantized_llm.py):

import time
from lazyllm import TrainableModule, deploy, launchers

start_time = time.time()
llm = TrainableModule('Qwen2-72B-Instruct').deploy_method(
        deploy.Vllm).start()

end_time = time.time()
print("原始模型加载耗时:", end_time-start_time)

start_time = time.time()
llm_awq = TrainableModule('Qwen2-72B-Instruct-AWQ').deploy_method(deploy.Vllm).start()
end_time = time.time()
print("AWQ量化模型加载耗时:", end_time-start_time)

query = "生成一份1000字的人工智能发展相关报告"

start_time = time.time()
llm(query)
end_time = time.time()
print("原始模型耗时:", end_time-start_time)

start_time = time.time()
llm_awq(query)
end_time = time.time()
print("AWQ量化模型耗时:", end_time-start_time)

同样在 4 张 A800 卡加载和执行的速度如下所示:

  • 原始模型加载耗时: 129.6051540374756

  • 原始模型耗时: 13.104065895080566

  • AWQ 量化模型加载耗时: 86.4980857372284

  • AWQ 量化模型耗时: 8.81701111793518

LazyLLM 输出的日志信息中的 token/s 信息分别为:

  • INFO 03-12 19:52:50 metrics.py:341] Avg prompt throughput: 6.6 tokens/s, Avg generation throughput: 30.8 tokens/s

  • INFO 03-12 20:00:03 metrics.py:341] Avg prompt throughput: 6.6 tokens/s, Avg generation throughput: 41.2 tokens/s

AWQ 模型在单卡上的加载和执行耗时为:137 秒和 23 秒,也就是说,当有 4 张卡的时候,量化模型可以多处理 1.3-1.4 倍的请求。而当用户无法满足执行原模型的资源时,可以通过量化模型获得与原模型效果差异不大的生成效果(官方数据 [https://qwen.readthedocs.io/en/latest/getting_started/speed_benchmark.html] 称量化模型在 MMLU,CEval,IEval 上的评测平均值只差 0.9 个点)。

2. 通过 OnlineChatModule 访问 vLLM API

vLLM 发布的 API 接口和 OpenAI 是一致的,因此我们可以通过 lazyllm.OnlineChatModule 实现接口访问。这种方式的优势是大模型服务与 RAG 系统解耦,重启系统时无需重新启动模型服务,节省模型加载的耗时。

对于 Qwen2-72B-Instruct 模型,直接使用 vLLM 的启动方式为在命令行输入:

vllm serve /path/to/your/model \
        --quantization awg_marlin \
        --served-model-name qwen2 \
        --host 0.0.0.0 \
        --port 25120 \ 
        --trust-remote-code

如果您的 vLLM 版本低于 0.5.3,则通过如下命令:

python -m vllm.entrypoints.openai.api_server
        --model /path/to/your/model
        --served-model-name qwen2 \
        --host 0.0.0.0 \
        --port 25120

然后我们在 LazyLLM 中访问:

from lazyllm import OnlineChatModule

# 需要抛出环境变量 LAZYLLM_OPENAI_API_KEY 指定服务接口为 openai 模式
# export LAZYLLM_OPENAI_API_KEY=sk...
llm = OnlineChatModule(model="qwen2", base_url="http://127.0.0.1:25120/v1/")
print(llm('hello'))

输出

Hello! How can I assist you today?

这种方式可以有效减少调试过程中频繁重启系统带来的时间消耗,在正式系统重启时也可以和持久化存储一起减少系统的启动时间。如果您想使用自己的 token 进行验证,可以在启动 vLLM 服务时通过传入 --api-key <your-api-key> ,然后在访问时通过设置环境变量 LAZYLLM_OPENAI_API_KEY 完成校验。

此外,如果您自定有了服务接口,您也可以通过继承实现 OnlineChatModuleBase 实现自己的在线对话接口:

import lazyllm
from lazyllm.module import OnlineChatModuleBase
from lazyllm.module.onlineChatModule.fileHandler import FileHandlerBase
class CustomChatModule(OnlineChatModuleBase):
    def __init__(self,
        base_url: str = "<new platform base url>",
        model: str = "<new platform model name>",
        system_prompt: str = "<new platform system prompt>",
        stream: bool = True,
        return_trace: bool = False):
        super().__init__(model_type="new_class_name",
            api_key=lazyllm.config['new_platform_api_key'],
            base_url=base_url,
            system_prompt=system_prompt,
            stream=stream,
            return_trace=return_trace)

3. 基于向量数据库和 vLLM 服务的 RAG 系统

将本篇教程中的所有策略(基于向量数据库的持久化存储和高速向量搜索,vLLM 启动量化推理模型)应用至进阶 1 介绍的多路召回 RAG ,则得到如下代码,在同等条件下,相比于前面的版本启动时间显著减少,响应时间有一定的缩短。

我们在进阶 1 中提到了使用 QA 对的用法并推荐在学习向量数据库后进行调试,这是因为 QA 对提取需要耗费较长的时间和较多的 token,如果每次重启系统都执行一次则会浪费大量时间和 token,但如果使用了持久化存储策略,则可以每次重启系统时的等待时间,结合文档管理接口,您还可以对大模型提取的 QA 对进行调整和删除。

import lazyllm
from lazyllm import bind

# 使用 Milvus 存储后端
chroma_store_conf = {
  'type': 'chroma', 
  'kwargs': {
    'dir': 'qa_pair_chromadb',
   },
  'indices': {
    'smart_embedding_index': {
      'backend': 'milvus',
      'kwargs': {
        'uri': "qa_pair/test.db",
        'index_kwargs': {
          'index_type': 'HNSW',
          'metric_type': 'COSINE',
        }
      },
    },
  },
}

rewriter_prompt = "你是一个查询重写助手,负责给用户查询进行模板切换。\
          注意,你不需要进行回答,只需要对问题进行重写,使更容易进行检索\
          下面是一个简单的例子:\
          输入:RAG是啥?\
          输出:RAG的定义是什么?"
rag_prompt = 'You will play the role of an AI Q&A assistant and complete a dialogue task.'\
    ' In this task, you need to provide your answer based on the given context and question.'

# 定义嵌入模型和重排序模型
# online_embedding = lazyllm.OnlineEmbeddingModule()
embedding_model = lazyllm.TrainableModule("bge-large-zh-v1.5").start()

# 如果您要使用在线重排模型
# 目前LazyLLM仅支持 qwen和glm 在线重排模型,请指定相应的 API key。
# online_rerank = lazyllm.OnlineEmbeddingModule(type="rerank")
# 本地重排序模型
offline_rerank = lazyllm.TrainableModule('bge-reranker-large').start()

llm = lazyllm.OnlineChatModule(base_url="http://127.0.0.1:36858/v1")

qa_parser = lazyllm.LLMParser(llm, language="zh", task_type="qa")

docs = lazyllm.Document("/path/to/your/document", embed=embedding_model, store_conf=chroma_store_conf)
docs.create_node_group(name='block', transform=(lambda d: d.split('\n')))
docs.create_node_group(name='qapair', transform=qa_parser)

def retrieve_and_rerank():
    with lazyllm.pipeline() as ppl:
        with lazyllm.parallel().sum as ppl.prl:
            # CoarseChunk是LazyLLM默认提供的大小为1024的分块名
            ppl.prl.retriever1 = lazyllm.Retriever(doc=docs, group_name="CoarseChunk", index="smart_embedding_index", topk=3)
            ppl.prl.retriever2 = lazyllm.Retriever(doc=docs, group_name="block", similarity="bm25_chinese", topk=3)
        ppl.reranker = lazyllm.Reranker("ModuleReranker",
                                         model=offline_rerank,
                                         topk=3) | bind(query=ppl.input)
    return ppl

with lazyllm.pipeline() as ppl:
    # llm.share 表示复用一个大模型,如果这里设置为promptrag_prompt则会覆盖rewrite_prompt
    ppl.query_rewriter = llm.share(lazyllm.ChatPrompter(instruction=rewriter_prompt))
    with lazyllm.parallel().sum as ppl.prl:
        ppl.prl.retrieve_rerank = retrieve_and_rerank()
        ppl.prl.qa_retrieve = lazyllm.Retriever(doc=docs, group_name="qapair", index="smart_embedding_index", topk=3)      
    ppl.formatter = (
          lambda nodes, query: dict(
              context_str='\n'.join([node.get_content() for node in nodes]),
              query=query)
        ) | bind(query=ppl.input)
    ppl.llm = llm.share(lazyllm.ChatPrompter(instruction=rag_prompt, extra_keys=['context_str']))

lazyllm.WebModule(ppl, port=23491, stream=True).start().wait()

基于本节教程内容关于 LazyLLM 如何使用向量数据库实现持久化存储和高速向量检索的相关内容,可以有效减少 RAG 系统在重启和执行阶段的计算耗时,在某些特定节点组的角度还可以节省大量 token 费用。其次,通过灵活使用 vLLM 框架,可以得到更快的推理速度,通过使用量化模型,可以在保留相似的生成效果的条件下,压低硬件要求,节省硬件成本,也可以减少模型启动时的模型加载时间。

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐