LangChain 实战07 - 模型调用选择OpenAi vs Llama2

326 阅读10分钟

我正在参加「豆包MarsCode AI练中学体验活动」详情请看:掘金小册上线 AI练中学功能 | 你的 AI 编程助教喊你免费领小册啦!

前言

提及语言模型的发展历程,不得不提到2018年Google发布的划时代论文《Attention is All You Need》,该论文首次介绍了Transformer架构,这一创新成为了后来众多预训练模型的基础,点燃了新一轮的人工智能革命。基于Transformer架构训练的大规模语言模型常被称为“基础模型”或“基座模型”,它们通过海量的数据学习掌握了词汇、语法、句法结构和上下文等多种语言要素,形成了一个通用且富含信息的语言表征体系,为后续的各种特定任务(例如情感分析、文本分类、命名实体识别和问答系统等)打下了坚实的基础。

在这一波浪潮初期,BERT模型以其独特的双向编码机制脱颖而出,成为最具标志性和影响力的模型之一。通过同时考虑单词的前后文环境,BERT实现了对文本更深层次的理解。自此以后,各类大型预训练模型层出不穷,标志着自然语言处理领域步入了一个全新的纪元。这些先进的模型极大地促进了NLP技术的进步,使得机器翻译、文本摘要生成、人机对话等一系列挑战性任务得到了有效解决,为用户提供了前所未有的强大工具。

image.png

预训练+微调的模式

预训练模型的微调(Fine-tuning)相比于从头训练一个模型要快得多,且需要的数据量也要少得多,这使得作为工程师的我们能够更高效地开发和部署各种NLP解决方案。

image.png

image.png

  • 预训练:通过大量未标记的文本数据对模型进行初始训练,目的是使模型掌握自然语言的基本表达方式、上下文关联及深层含义,从而为接下来的任务打下坚实的语言理解基础。
  • 微调:基于已有的预训练模型,针对特定的应用场景进一步优化模型性能。如今,各行业人士常提到:“我们的强项在于专业知识!虽然无法与国内外大型模型匹敌,但我们可以通过开放源码模型来专注于特定领域!即开发专业领域的模型!”—— 这里的“专业领域”指的是利用特定领域的数据对公开模型进行精细化调整的过程。

这种预训练结合微调的大模型应用策略确实展现出显著的优势。一方面,预训练模型有能力将广泛积累的一般性语言知识有效转移至不同的下游任务中,这意味着应用开发者无需自行搜集庞大的语料库,也不必从零开始训练大型模型,从而大幅节省了训练时间并降低了数据收集的要求。另一方面,通过微调阶段,可以迅速针对具体任务对模型进行个性化调整,不仅简化了模型的实施过程,还提升了其针对性和实用性。此外,预训练加微调的框架设计具备高度的灵活性和适应性,能够轻松应对各类自然语言处理挑战,极大地促进了NLP技术在现实世界应用中的高效性和普及度,为用户提供了极大的便利。

用 HuggingFace 跑开源模型

注册并安装 HuggingFace

第一步,还是要登录 HuggingFace 网站,并拿到专属于你的Token。

第二步,用 pip install transformers 安装HuggingFace Library。详见 这里

第三步,在命令行中运行 huggingface-cli login,设置你的API Token。

image.png

# 导入HuggingFace API Token
import os
os.environ['HUGGINGFACEHUB_API_TOKEN'] = '你的HuggingFace API Token'

使用 Meta 的 Llama2 模型

在HuggingFace的Model中,找到 meta-llama/Llama-2-7b。注意,各种各样版本的Llama2模型多如牛毛,我们这里用的是最小的7B版。

image.png

选择了Meta-LLAMA/Llama-2-7b这一模型后,你可以查看到该模型的相关基本信息。对于初次使用Llama模型的用户来说,需要先提交访问权限的申请。由于我已经完成了申请流程,因此在屏幕中央会显示:“您已获得此模型的访问权限。” 从提交申请到获得批准,整个过程通常只需要几分钟的时间。

image.png

通过 HuggingFace 调用 Llama

# 导入必要的库
from transformers import AutoTokenizer, AutoModelForCausalLM

# 加载预训练模型的分词器
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-chat-hf")

# 加载预训练的模型
# 使用 device_map 参数将模型自动加载到可用的硬件设备上,例如GPU
model = AutoModelForCausalLM.from_pretrained(
          "meta-llama/Llama-2-7b-chat-hf",
          device_map = 'auto')

# 定义一个提示,希望模型基于此提示生成故事
prompt = "请给我讲个玫瑰的爱情故事?"

# 使用分词器将提示转化为模型可以理解的格式,并将其移动到GPU上
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

# 使用模型生成文本,设置最大生成令牌数为2000
outputs = model.generate(inputs["input_ids"], max_new_tokens=2000)

# 将生成的令牌解码成文本,并跳过任何特殊的令牌,例如[CLS], [SEP]等
response = tokenizer.decode(outputs[0], skip_special_tokens=True)

# 打印生成的响应
print(response)
  • 引入 AutoTokenizer:这是一种工具,用于自动加载与预训练模型配套的分词器,其主要职责是把原始文本转换成模型能识别的数字形式。
  • 引入 AutoModelForCausalLM:这是专门用来加载因果语言模型(主要用于生成文本)的工具。
  • 利用 from_pretrained 方法加载已经训练好的分词器和模型。设置参数 device_map='auto' 可确保模型自动分配到合适的计算资源上,比如图形处理器(GPU)。
  • 接着,设定一个输入提示(Prompt):"能否讲述一个关于玫瑰的爱情传说?",并通过分词器将其转换为模型可接受的数据格式,参数 return_tensors='pt' 指明返回的是PyTorch类型的张量。这里的 .to('cuda') 是为了适应GPU环境下的数据类型转换,如果您的环境是基于CPU运行,可以尝试去掉这部分代码。
  • 最终,调用模型的 .generate() 方法来生成相应的文本输出,参数 max_new_tokens=2000 用于限定生成文本的最大长度。通过分词器的 .decode() 函数将生成的数字序列还原为人类可读的文本内容,同时忽略所有特殊符号。

由于是在本地执行推理任务,整个过程可能会比较耗时。在我的计算机上,从启动到获取结果大约需要30秒到2分钟的时间。

LangChain 和 HuggingFace

通过 HuggingFace Hub

第一种集成方法是利用 HuggingFace Hub。HuggingFace Hub 作为一个开放的模型集中存储平台,旨在促进预训练模型、数据集及相关组件的共享、合作与保存。这为开发者提供了一个便捷的途径,可以轻松访问和使用大量的高质量模型及资源。

# 导入HuggingFace API Token
import os
os.environ['HUGGINGFACEHUB_API_TOKEN'] = '你的HuggingFace API Token'

# 导入必要的库
from langchain import PromptTemplate, HuggingFaceHub, LLMChain

# 初始化HF LLM
llm = HuggingFaceHub(
    repo_id="google/flan-t5-small",
    #repo_id="meta-llama/Llama-2-7b-chat-hf",
)

# 创建简单的question-answering提示模板
template = """Question: {question}
              Answer: """

# 创建Prompt
prompt = PromptTemplate(template=template, input_variables=["question"])

# 调用LLM Chain --- 我们以后会详细讲LLM Chain
llm_chain = LLMChain(
    prompt=prompt,
    llm=llm
)

# 准备问题
question = "Rose is which type of flower?"

# 调用模型并返回结果
print(llm_chain.run(question))

通过 HuggingFace Pipeline

HuggingFace 的 Pipeline 是一款高级工具,旨在简化常见的自然语言处理(NLP)任务流程。通过这一工具,即使用户不具备深厚的技术背景,也可以轻松调用预训练模型执行特定任务。Pipeline 能够自动处理从输入预处理到模型推理,再到输出后处理的整个流程,从而让用户更加专注于解决实际问题而非技术细节。

 指定预训练模型的名称
model = "meta-llama/Llama-2-7b-chat-hf"

# 从预训练模型中加载词汇器
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(model)

# 创建一个文本生成的管道
import transformers
import torch
pipeline = transformers.pipeline(
    "text-generation",
    model=model,
    torch_dtype=torch.float16,
    device_map="auto",
    max_length = 1000
)

# 创建HuggingFacePipeline实例
from langchain import HuggingFacePipeline
llm = HuggingFacePipeline(pipeline = pipeline,
                          model_kwargs = {'temperature':0})

# 定义输入模板,该模板用于生成花束的描述
template = """
              为以下的花束生成一个详细且吸引人的描述:
              花束的详细信息:
              ```{flower_details}```
           """

# 使用模板创建提示
from langchain import PromptTemplate,  LLMChain
prompt = PromptTemplate(template=template,
                     input_variables=["flower_details"])

# 创建LLMChain实例
from langchain import PromptTemplate
llm_chain = LLMChain(prompt=prompt, llm=llm)

# 需要生成描述的花束的详细信息
flower_details = "12支红玫瑰,搭配白色满天星和绿叶,包装在浪漫的红色纸中。"

# 打印生成的花束描述
print(llm_chain.run(flower_details))

image.png

用 LangChain 调用自定义语言模型

最后,我们来尝试回答这节课开头提出的问题,假设你就是想训练属于自己的模型。而且出于商业秘密的原因,不想开源它,不想上传到HuggingFace,就是要在本机运行模型。此时应该如何利用LangChain的功能?

我们可以创建一个LLM的衍生类,自己定义模型。而LLM这个基类,则位于langchain.llms.base中,通过from langchain.llms.base import LLM语句导入。

这个自定义的LLM类只需要实现一个方法:

  • _call方法:用于接收输入字符串并返回响应字符串。

以及一个可选方法:

  • _identifying_params方法:用于帮助打印此类的属性。

下面,让我们先从HuggingFace的 这里,下载一个llama-2-7b-chat.ggmlv3.q4_K_S.bin模型,并保存在本地。

自己训练一个能用的模型没那么容易。这个模型,它并不是原始的Llama模型,而是TheBloke这位老兄用他的手段为我们量化过的新模型,你也可以理解成,他已经为我们压缩或者说微调了Llama模型

image.png 所以,这里你就假设,咱们下载下来的llama-2-7b-chat.ggmlv3.q4_K_S.bin这个模型,就是你自己微调过的。将来你真的微调了Llama2、ChatGLM、百川或者千问的开源版,甚至是自己从头训练了一个mini-ChatGPT,你也可以保存为you_own_model.bin的格式,就按照下面的方式加载到LangChain之中。

然后,为了使用llama-2-7b-chat.ggmlv3.q4_K_S.bin这个模型,你需要安装 pip install llama-cpp-python 这个包。

# 导入需要的库
from llama_cpp import Llama
from typing import Optional, List, Mapping, Any
from langchain.llms.base import LLM

# 模型的名称和路径常量
MODEL_NAME = 'llama-2-7b-chat.ggmlv3.q4_K_S.bin'
MODEL_PATH = '/home/huangj/03_Llama/'

# 自定义的LLM类,继承自基础LLM类
class CustomLLM(LLM):
    model_name = MODEL_NAME

    # 该方法使用Llama库调用模型生成回复
    def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str:
        prompt_length = len(prompt) + 5
        # 初始化Llama模型,指定模型路径和线程数
        llm = Llama(model_path=MODEL_PATH+MODEL_NAME, n_threads=4)
        # 使用Llama模型生成回复
        response = llm(f"Q: {prompt} A: ", max_tokens=256)

        # 从返回的回复中提取文本部分
        output = response['choices'][0]['text'].replace('A: ', '').strip()

        # 返回生成的回复,同时剔除了问题部分和额外字符
        return output[prompt_length:]

    # 返回模型的标识参数,这里只是返回模型的名称
    @property
    def _identifying_params(self) -> Mapping[str, Any]:
        return {"name_of_model": self.model_name}

    # 返回模型的类型,这里是"custom"
    @property
    def _llm_type(self) -> str:
        return "custom"


# 初始化自定义LLM类
llm = CustomLLM()

# 使用自定义LLM生成一个回复
result = llm("昨天有一个客户抱怨他买了花给女朋友之后,两天花就枯了,你说作为客服我应该怎么解释?")

# 打印生成的回复
print(result)