尽管LLM在各个行业似乎无法阻挡地被采用,但它们只是支持新一波人工智能浪潮的广泛技术生态系统的一个组成部分。许多对话式人工智能应用场景需要像Llama 2、Flan T5和Bloom这样的LLM来回答用户的查询。这些模型依赖参数化知识来回答问题。模型在训练过程中学习并将此知识编码为模型参数。为了更新这种知识,我们必须重新训练LLM,这需要很多时间和金钱。
幸运的是,我们还可以使用源知识来通知我们的LLM。源知识是通过输入提示输入LLM的信息。提供源知识的一种常见方法是检索增强生成(RAG)。使用RAG,我们从外部数据源检索相关信息并将该信息输入LLM。
在本博文中,我们将探讨如何使用Amazon Sagemaker JumpStart部署Llama-2等LLM,并通过使用Pinecone矢量数据库中的检索增强生成(RAG)来及时更新相关信息,以防止AI幻觉。
Amazon SageMaker中的检索增强生成(RAG)
Pinecone将处理RAG的检索组件,但您还需要两个关键组件:用于运行LLM推理的某个位置和用于运行嵌入模型的某个位置。
Amazon SageMaker Studio是一个集成的开发环境(IDE),提供了一个单一的基于Web的可视界面,您可以在其中访问专为执行所有机器学习(ML)开发而构建的工具。它提供了SageMaker JumpStart,这是一个模型中心,用户可以在自己的SageMaker帐户中找到、预览和启动特定模型。它提供了预训练的、公开可用的和专有的模型,用于解决各种问题类型,包括基础模型。
Amazon SageMaker Studio为开发支持RAG的LLM流水线提供了理想的环境。首先,使用AWS控制台进入Amazon SageMaker并创建SageMaker Studio域,并打开Jupyter Studio笔记本。
先决条件
完成以下先决条件步骤:
- 设置Amazon SageMaker Studio。
- 加入Amazon SageMaker Domain。
- 注册免费版Pinecone矢量数据库。
- 先决条件库:SageMaker Python SDK,Pinecone Client
解决方案演练
使用SageMaker Studio笔记本,我们首先需要安装先决条件库:
!pip install -qU sagemaker pinecone-client==2.2.1 ipywidgets==7.0.0
部署LLM
在本文中,我们讨论了两种部署LLM的方法。第一种是通过HuggingFaceModel
对象。当直接从Hugging Face模型中心部署LLM(和嵌入模型)时,您可以使用此方法。
例如,您可以创建一个可以部署的google/flan-t5-xl
模型的配置,如以下屏幕截图所示:
import sagemakerfrom sagemaker.huggingface import (HuggingFaceModel, get_huggingface_llm_image_uri)role = sagemaker.get_execution_role()hub_config = {'HF_MODEL_ID':'google/flan-t5-xl', # model_id from hf.co/models'HF_TASK':'text-generation' # NLP task you want to use for predictions# retrieve the llm image urillm_image = get_huggingface_llm_image_uri("huggingface", version="0.8.2")huggingface_model = HuggingFaceModel(env=hub_config, role=role, # iam role with permissions to create an Endpoint image_uri=llm_image)
当直接从Hugging Face部署模型时,使用my_model_configuration
进行初始化,其中包含以下内容:
env
配置告诉我们要使用哪个模型以及用于什么任务。- 我们的SageMaker执行
role
给我们部署模型的权限。 image_uri
是专门用于部署来自Hugging Face的LLM的图像配置。
<!–或者,SageMaker还有一组与更简单的JumpStartModel
对象兼容的模型。许多流行的LLM模型,如Llama 2,都支持该模型,可以按照以下屏幕截图所示进行初始化:
import sagemaker from sagemaker.jumpstart.model import JumpStartModel
role = sagemaker.get_execution_role()
my_model = JumpStartModel(model_id = "meta-textgeneration-llama-2-7b-f")
对于my_model
的两个版本,按照以下屏幕截图所示部署它们:
predictor = my_model.deploy(initial_instance_count=1, instance_type="ml.g5.4xlarge", endpoint_name="llama-2-generator")
查询预训练LLM
使用我们初始化的LLM端点,您可以开始查询。我们的查询格式可能会有所不同(特别是对话和非对话LLM之间的区别),但是过程通常是相同的。对于Hugging Face模型,请执行以下操作:
# https://aws.amazon.com/blogs/machine-learning/llama-2-foundation-models-from-meta-are-now-available-in-amazon-sagemaker-jumpstart/prompt = """根据给定的上下文回答以下问题。如果您不知道答案,并且上下文中不包含答案,请真实地说"我不知道。答案:"""payload = {"inputs":[[{"role":"system","content":prompt},{"role":"user","content":question}]],"parameters":{"max_new_tokens":64,"top_p":0.9,"temperature":0.6,"return_full_text":false}}out = predictor.predict(payload,custom_attributes='accept_eula=true')out[0]['generation']['content']
您可以在GitHub存储库中找到解决方案。
我们在这里收到的生成答案没有太多意义-它只是一种幻觉。
为LLM提供额外的上下文
Llama 2试图仅基于内部参数化知识回答我们的问题。显然,模型参数未存储我们可以在SageMaker中进行托管散点训练的实例的知识。
要正确回答此问题,我们必须使用源知识。也就是说,我们通过提示直接为模型添加附加信息作为额外的上下文。
context = """在Amazon SageMaker中,可以使用在所有受支持的实例中使用托管散点训练。托管散点训练在当前可用的所有AWS区域中均受支持。"""prompt_template = """根据给定的上下文回答以下问题。如果您不知道答案,并且上下文中不包含答案,请真实地说"我不知道"。上下文:{context}答案:"""text_input = prompt_template.replace("{context}", context).replace("{question}", question)payload = {"inputs":[[{"role":"system","content":text_input},{"role":"user","content":question}]],"parameters":{"max_new_tokens":64,"top_p":0.9,"temperature":0.6,"return_full_text":false}}out = predictor.predict(payload,custom_attributes='accept_eula=true')generated_text = out[0]['generation']['content']print(f"[输入]:{question}\n[输出]:{generated_text}")
现在我们看到了问题的正确答案;这很容易!然而,用户不太可能在他们的提示中插入上下文,他们已经知道问题的答案。
与其手动插入单个上下文,不如自动从更广泛的信息数据库中识别相关信息。为此,您需要检索增强生成。
检索增强生成
通过检索增强生成,您可以将信息数据库编码为向量空间,其中向量之间的接近程度表示它们的相关性/语义相似度。有了这个向量空间作为知识库,您可以将用户的新查询编码成相同的向量空间,并检索之前索引的最相关记录。
在检索到这些相关记录之后,选择其中几个并将它们包含到LLM提示中作为附加上下文,为LLM提供高度相关的源知识。这是一个两步过程:
- 索引将数据集的信息填充到向量索引中。
- 检索发生在查询过程中,我们从向量索引中检索相关信息。
这两个步骤都需要一个嵌入模型将我们可读的纯文本转换成语义向量空间。使用高效的Hugging Face MiniLM句子转换器,如下所示的屏幕截图所示。该模型不是一个LLM,因此它的初始化方式与我们的Llama 2模型不同。
hub_config = { "HF_MODEL_ID": "sentence-transformers/all-MiniLM-L6-v2", # hf.co/models中的model_id "HF_TASK": "feature-extraction",}huggingface_model = HuggingFaceModel( env=hub_config, role=role, transformers_version="4.6", # 使用的transformers版本 pytorch_version="1.7", # 使用的pytorch版本 py_version="py36", # DLC的python版本)
在hub_config
中,按照上面的屏幕截图显示指定模型ID,但对于任务,请使用feature-extraction,因为我们生成的是向量嵌入,而不是像我们的LLM一样的文本。接下来,像之前一样,使用HuggingFaceModel
初始化模型配置,但这次不带LLM图像,并带有一些版本参数。
encoder = huggingface_model.deploy( initial_instance_count=1, instance_type="ml.t2.large", endpoint_name="minilm-embedding")
您可以再次使用deploy
部署模型,使用较小的(仅支持CPU)ml.t2.large
实例。MiniLM模型很小,因此不需要太多内存,也不需要GPU,因为它可以在CPU上快速创建嵌入。如果希望,您可以在GPU上更快地运行该模型。
要创建嵌入,使用predict
方法,通过inputs
键传递要编码的上下文列表,如下所示:
out = encoder.predict({"inputs": ["此处有一些文本", "这里还有一些更多的文本"]})
传入了两个输入上下文,返回了两个上下文向量嵌入,如下所示:
len(out)
2
MiniLM模型的嵌入维度是384
,这意味着MiniLM输出的每个向量嵌入的维度应为384
。然而,通过查看我们的嵌入长度,您会看到以下内容:
len(out[0]), len(out[1])
(8, 8)
两个列表每个都包含八个项目。MiniLM首先通过标记化步骤处理文本。该标记化将我们可读的纯文本转换为可由模型读取的标记ID列表。在模型的输出特征中,您可以看到标记级别的嵌入。其中一个嵌入显示了期望的384
维度,如下所示:
len(out[0][0])
384
通过在每个向量维度上计算平均值,将这些标记级别的嵌入转换为文档级别的嵌入,如下图所示。
通过平均池化操作获得一个单一的384维向量。
import numpy as np embeddings = np.mean(np.array(out), axis=1)embeddings.shape(2, 384)
使用两个384维向量嵌入,一个用于每个输入文本。为了使我们的生活更轻松,将编码过程包装到一个单独的函数中,如下面的屏幕截图所示:
from typing import List
def embed_docs(docs: List[str]) -> List[List[float]]:
out = encoder.predict({"inputs": docs})
embeddings = np.mean(np.array(out), axis=1)
return embeddings.tolist()
下载数据集
下载亚马逊SageMaker FAQs作为知识库,以获取包含问题和答案两列的数据。
下载亚马逊SageMaker FAQs
在执行搜索时,只查找答案,因此可以舍弃问题列。 详细信息请参阅笔记本。
我们的数据集和嵌入管道已准备就绪。现在我们只需要一个地方来存储这些嵌入。
索引
Pinecone向量数据库以高效的方式存储向量嵌入并进行搜索。要创建一个数据库,您将需要从Pinecone获取一个免费的API密钥。
import pineconeimport os# 从app.pinecone.io导入Pinecone API密钥api_key = os.environ.get("PINECONE_API_KEY") or "YOUR_API_KEY"# 设置Pinecone环境 - 找到它在控制台上的API密钥旁边的env = os.environ.get("PINECONE_ENVIRONMENT") or "YOUR_ENV"pinecone.init(api_key=api_key, environment=env)
连接到Pinecone向量数据库后,创建一个单一的向量索引(类似于传统数据库中的表)。将索引命名为retrieval-augmentation-aws
并将索引dimension
和metric
参数与嵌入模型(在本例中为MiniLM)所需的参数对齐。
import timeindex_name = "retrieval-augmentation-aws"if index_name in pinecone.list_indexes(): pinecone.delete_index(index_name)pinecone.create_index(name=index_name, dimension=embeddings.shape[1], metric="cosine")# 等待索引完成初始化while not pinecone.describe_index(index_name).status["ready"]: time.sleep(1)
要开始插入数据,请运行以下命令:
from tqdm.auto import tqdmbatch_size = 2 # 可以增加,但需要较大的实例大小,否则实例会耗尽内存vector_limit = 1000answers = df_knowledge[:vector_limit]index = pinecone.Index(index_name)for i in tqdm(range(0, len(answers), batch_size)): # 找到批处理的结束位置 i_end = min(i + batch_size, len(answers)) # 创建ID批处理 ids = [str(x) for x in range(i, i_end)] # 创建元数据批处理 metadatas = [{"text": text} for text in answers["Answer"][i:i_end]] # 创建嵌入 texts = answers["Answer"][i:i_end].tolist() embeddings = embed_docs(texts) # 创建用于upsert的记录列表 records = zip(ids, embeddings, metadatas) # upsert到Pinecone index.upsert(vectors=records)
您可以开始使用本文中早先提到的问题查询索引。
# 提取问题的嵌入query_vec = embed_docs(question)[0]# 查询pineconeres = index.query(query_vec, top_k=1, include_metadata=True)# 显示结果res{'matches': [{'id': '90','metadata': {'text': 'Managed Spot Training can be used with all ''instances supported in Amazon ''SageMaker.\r\n'},'score': 0.881181657,'values': []}],'namespace': ''}
上面的输出显示我们返回了相关的上下文,以帮助我们回答问题。由于我们使用了top_k = 1
,所以index.query
返回了最高的结果,同时还返回了元数据,其中包含”Managed Spot Training can be used with all instances supported in Amazon”。
增强提示
使用检索到的上下文来增强提示,并决定要将多少上下文输入到LLM中。使用1000
字符的限制,逐个将每个返回的上下文添加到提示中,直到超过内容长度。
增强提示
将context_str
输入LLM提示,如以下截图所示:
payload = create_payload(question, context_str)out = predictor.predict(payload, custom_attributes='accept_eula=true')generated_text = out[0]['generation']['content']print(f"[输入]: {question}\n[输出]: {generated_text}")
[输入]: 在SageMaker中我可以使用哪些实例?[输出]: 根据提供的上下文,您可以在Amazon SageMaker中使用所有支持的实例。因此,答案是:Amazon SageMaker中支持的所有实例。
逻辑正确,因此将其整理为一个单一函数以保持代码的整洁。
def rag_query(question: str) -> str: # 创建查询向量 query_vec = embed_docs(question)[0] # 查询pinecone res = index.query(query_vec, top_k=5, include_metadata=True) # 获取上下文 contexts = [match.metadata["text"] for match in res.matches] # 构建多个上下文的字符串 context_str = construct_context(contexts=contexts) # 创建我们增强的检索提示 payload = create_payload(question, context_str) # 进行预测 out = predictor.predict(payload, custom_attributes='accept_eula=true') return out[0]["generation"]["content"]
现在您可以像以下示例一样提出问题:
rag_query("SageMaker是否支持spot实例?")' 是的,Amazon SageMaker支持托管的spot实例。根据提供的上下文,可以在Amazon SageMaker中使用所有支持的实例,而Managed Spot Training在当前可用的所有AWS区域中都得到支持。\n\n因此,您的问题的答案是:\n\n是的,SageMaker在Amazon SageMaker可用的所有区域中支持spot实例。'
清理
为了防止产生不必要的费用,删除模型和端点。
encoder.delete_model()encoder.delete_endpoint()
结论
在本文中,我们向您介绍了在SageMaker上使用开放访问的LLM的RAG。我们还展示了如何使用Llama 2部署Amazon SageMaker Jumpstart模型,使用Flan T5进行Hugging Face LLMs,以及使用MiniLM进行嵌入模型。
我们使用我们的开放访问模型和Pinecone向量索引实现了一个完整的端到端的RAG流水线。通过这样做,我们展示了如何最小化幻觉,保持LLM知识的最新更新,并最终提升用户体验和对我们系统的信任。
要在您自己的环境中运行此示例,请克隆此GitHub存储库,并按照GitHub上的问答笔记本进行操作的先前步骤。