streamlit 搭建LLM交互应用
背景
本文主要讨论使用Qwen2-7B-Instruct
搭建一个可调节核心参数的LLM对话式应用。页面交互采用streamlit
,模型推理部分采langchain
和vllm
两种部署推理框架。
本文将可以学习了解一下内容:
- streamlit的搭建流程,以及常见的组件使用
- 使用
langchain
推理框架部署LLM模型 - 使用
vllm
推理框部署LLM模型 langchain
和vllm
的主要区别及LLM使用中的一些核心参数的含义
预备知识
1、常见模型名称的理解
LLM 中会经常看到以Instruct和chat结尾的模型名称,其含义是不同的
Qwen2-7B-Instruct
侧重于理解和执行用户给出的指令或任务。
Qwen2-7B-Chat
侧重于自然流畅的对话交互。
比如编写代码或者回答具体的指令问题 那么 Qwen2-7B-Instruct
可能是更好的选择。
如果你的目标是建立一个能够进行自然对话的聊天机器人,则 Qwen2-7B-Chat
更合适。
2、模型输出随机性参数控制
temperature 参数
:用于控制生成文本的随机性,影响模型在生成文本时如何选择下一个词汇的概率分布。
当temperature
较高时,模型会更倾向于探索低概率的词汇,这增加了生成文本的随机性和多样性。
当temperature
较低时,模型会更倾向于选择高概率的词汇,这使得生成的文本更加可预测和保守。
top_p 参数
:用于控制核采样策略,选择累积概率达到一定阈值 top_p 的词汇子集进行采样。
当top_p
设置为较小的值时,只有少数高概率的词汇会被保留下来,通常会导致生成的文本更加聚焦和连贯,可能减少多样性。反之会增加生成文本的多样性和创造性。
top_k 参数
用于控制生成多样性和文本质量的一种策略。
当top_k
设置为较大的值时,更多的词汇会被考虑,可能会增加生成文本的多样性,但同时也可能导致文本质量的下降。相反,当top_k
设置为较小的值时,只有少数高概率的词汇会被考虑,通常会使生成的文本更加聚焦和连贯,但也可能减少多样性。
repetition_penalty 参数
:用来控制生成文本中重复内容的数量。
repetition_penalty
的默认值通常是1.0,意味着没有重复惩罚,即模型在生成下一个词汇时不会特别避免重复之前已经生成过的词汇。
当repetition_penalty
的值大于1.0时,模型会在计算候选词汇的概率时对已经出现过的词汇加一定的惩罚,从而降低它们再次被选中的概率。值越大,惩罚力度越强,生成的文本中重复的内容就越少。
如果repetition_penalty
的值小于1.0,将会鼓励模型重复生成某些词汇,但这在实践中很少使用,因为通常我们希望生成的文本尽可能多样化。
小结:
top_p
更多地影响生成文本的多样性
top_k
考虑候选词汇的数量
temperature
更多地影响生成文本的随机性和连贯性
repetition_penalty
文本词汇的重复程度
环境搭建以及模型下载
本项目仓地址github
langchain推理部署
自定义Qwen2_LLM类继承LLM,重写__init__
,_call
,_llm_type
函数,代码如下
from langchain.llms.base import LLM
from typing import Any, List, Optional
from langchain.callbacks.manager import CallbackManagerForLLMRun
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
import torch
class Qwen2_LLM(LLM):
# 基于本地Qwen2自定义LLM类
tokenizer: AutoTokenizer = None
model: AutoModelForCausalLM = None
def __init__(self, model_name_or_path: str):
super().__init__()
print("正在从本地加载模型...")
self.tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, use_fast=False)
self.model = AutoModelForCausalLM.from_pretrained(
model_name_or_path, torch_dtype=torch.bfloat16, device_map="auto"
)
self.model.generation_config = GenerationConfig.from_pretrained(model_name_or_path)
print("完成本地模型的加载")
def _call(
self,
prompt: str,
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any
):
messages = [{"role": "user", "content": prompt}]
input_ids = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
model_inputs = self.tokenizer([input_ids], return_tensors="pt").to("cuda")
# 使用 kwargs 中的 temperature 参数覆盖默认值
base_generation_config = self.model.generation_config.to_dict()
updated_generation_config = {
**base_generation_config,
**kwargs,
}
generated_ids = self.model.generate(model_inputs.input_ids, **updated_generation_config)
generated_ids = [
output_ids[len(input_ids) :] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = self.tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
return response
@property
def _llm_type(self) -> str:
return "Qwen2-7B-Instruct"
推理使用
from langchain_model import Qwen2_LLM
from langchain.callbacks.base import BaseCallbackHandler
from langchain.callbacks.manager import CallbackManager
from typing import Any
from config import base_config
# 定义一个简单的回调类,可忽视
class SimpleCallbackHandler(BaseCallbackHandler):
def on_llm_start(self, serialized: dict, prompts: list, **kwargs: Any) -> None:
print(f"Starting LLM run with prompts: {prompts}")
def on_llm_end(self, response: Any, **kwargs: Any) -> None:
print(f"LLM run completed. Response: {response}")
def on_llm_error(self, error: Exception, **kwargs: Any) -> None:
print(f"Error during LLM run: {error}")
callback_handler = SimpleCallbackHandler()
callback_manager = CallbackManager([callback_handler])
# 需按实际模型位置进行修改
model_name_or_path = base_config.model_name_or_path
llm = Qwen2_LLM(model_name_or_path)
# langchain会调用子类的_call函数
# response = llm("你是谁", callbacks=callback_manager)
# 携带自定义参数
param = {"temperature": 0.5, "max_new_tokens": 512}
response = llm("你是谁", **param)
print(response)
当前文件运行 streamlit run langchain_streamlit_demo.py
界面如下
vllm推理部署
vllm_model.py文件如下
1、加载模型对象
from vllm import LLM, SamplingParams
def get_model(model_name_or_path):
llm = LLM(
model=model_name_or_path,
tokenizer=model_name_or_path,
max_num_seqs=100,
trust_remote_code=True,
)
return llm
2、重写文本生成采样率
def get_completion(tokenizer=None, repetition_penalty=1.05, top_p=0.8, top_k=20, **kwargs):
stop_token_ids = [151329, 151336, 151338]
# SamplingParams部分默认参数和Qwen2-7B-Instruct/generation_config.json配置的参数不一致
# 修改SamplingParams的默认值,使repetition_penalty、top_p、top_k和原模型保持一致
kwargs.update({"repetition_penalty": repetition_penalty, "top_p": top_p, "top_k": top_k})
# print(f"SamplingParams param: {kwargs}")
sampling_params = SamplingParams(**kwargs, stop_token_ids=stop_token_ids)
return sampling_params
3、调用模型生成文本
def call(llm, prompt, sampling_params):
outputs = llm.generate(prompt, sampling_params)
for output in outputs:
response = output.outputs[0].text
return response
测试如下
from vllm_model import call, get_model, get_completion
from config import base_config
model_name_or_path = base_config.model_name_or_path
llm = get_model(model_name_or_path)
# 多样性文本参数控制
param = dict(max_tokens=512, temperature=0.7)
sampling_params = get_completion(**param)
# 用户提示词
prompt = "介绍你自己?"
response = call(
llm,
prompt,
sampling_params,
)
print(response)
langchain和vllm的主要区别
LangChain 是一个用于端到端构建应用程序的框架,专注于利用大型语言模型(LLMs)来处理自然语言任务。
特点:
- 提供了一套工具和接口来连接不同的数据源和模型。
- 支持多种大型语言模型,如 OpenAI 的模型等。
- 可以构建复杂的语言处理流水线,比如问答系统或文本生成应用。
VLLM 是一个针对大型语言模型进行优化推理和部署的引擎方案。
特点:
- 主要用于部署和运行大规模预训练语言模型。
- 提供了高效的批量处理和序列化功能,以提高推理速度。
- 通常用于生产环境中的高性能推理服务。