Datawhale AI 夏令营——CPU部署大模型(LLM天池挑战赛)——Task1学习笔记

        本次赛题任务:基于推荐或自选的开源大语言模型,开发及展示面向通用或特定领域的应用/服务的原型进行提交,并在基于阿里云服务平台的英特尔至强可扩展处理器的实例上完成部署及高效运行。

        本篇博客为学习笔记分享,用来解释 baseline 的个人理解,后续优化在下一篇 Task2 。

项目代码链接:‌⁠‌⁠⁠​​‬‬‍​⁠‍‌​​​​⁠​​​⁠​‍⁠​​​⁠​​‍​‌​‬​‍‌​‍‌‬从零入门CPU部署大模型&应用开发 - 飞书云文档 (feishu.cn)

        初始时给定 5 个文件,分别随机测试文件、baseline1 源文件、baseline2 源文件、实践指南。

        第一个文件是用来向大模型提问的,可以随便替换;中间两个源文件,放入初始代码;后两个文件是指导手册,接下来我就按照指导手册一步步完成,并给出自己的见解。

        我的Notebook · 魔搭社区 (modelscope.cn)运行云端 CPU,规格如下:

        实例代码中给的注释很详细,而且上下文均有注解,这里我只作简单介绍。


baseline1解读

一、安装环境

%%writefile /mnt/workspace/install.sh
# 切换到 conda 的环境文件夹
cd  /opt/conda/envs 
mkdir ipex
# 下载 ipex-llm 官方环境
wget https://s3.idzcn.com/ipex-llm/ipex-llm-2.1.0b20240410.tar.gz 
# 解压文件夹以便恢复原先环境
tar -zxvf ipex-llm-2.1.0b20240410.tar.gz -C ipex/ && rm ipex-llm-2.1.0b20240410.tar.gz
# 安装 ipykernel 并将其注册到 notebook 可使用内核中
/opt/conda/envs/ipex/bin/python3 -m pip install ipykernel && /opt/conda/envs/ipex/bin/python3 -m ipykernel install --name=ipex

        初始虚拟环境的内核环境为 python3.10-torch2.1,实际上需要新建一个虚拟环境用来进行 LLM 的推理以及 RAG 的应用,与原先的默认环境隔离。这里的新环境命名为 ipex。

        上面的代码运行后会将需要下载的内容放到 install.sh 中,运行命令 bash install.sh 去安装 ipex 需要的压缩包。

        下载完成后,切换 ipex 的环境。

        一键运行 初赛baseline1 文件中的剩余代码。

二、模型准备

import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
# 第一个参数表示下载模型的型号,第二个参数是下载后存放的缓存地址,第三个表示版本号,默认 master
model_dir = snapshot_download('Qwen/Qwen2-1.5B-Instruct', cache_dir='qwen2chat_src', revision='master')

        导入一堆包,涉及模型下载、自动化模型加载和自动化标记器加载等任务。

        第一个参数说明上传者名称为 Qwen,模型名称为 Qwen2-1.5B-Instruct,也即15亿参数集的模型;第二个参数是存入的文件夹名称,可以自行修改;第三个参数就是版本号,用过 git 的人都比较熟悉,常用默认的就是 master,当然也能修改。

from ipex_llm.transformers import AutoModelForCausalLM
from transformers import  AutoTokenizer
import os
if __name__ == '__main__':
    model_path = os.path.join(os.getcwd(),"qwen2chat_src/Qwen/Qwen2-1___5B-Instruct")
    model = AutoModelForCausalLM.from_pretrained(model_path, load_in_low_bit='sym_int4', trust_remote_code=True)
    tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
    model.save_low_bit('qwen2chat_int4')
    tokenizer.save_pretrained('qwen2chat_int4')

        虽然这里的模型已经比较小,但是对于 CPU 来说可能还是一个挑战,所以可以进行量化。下载完成后,对 qwen2 模型进行低精度量化至 int4 ,低精度量化(Low Precision Quantization)是指将浮点数转换为低位宽的整数(这里是int4),以减少计算资源的需求和提高系统的效率。

        sym_int4 表示对称量化至 int4 的位宽。

%%writefile /mnt/workspace/run.py
# 导入必要的库
import os
# 设置OpenMP线程数为8,优化CPU并行计算性能
os.environ["OMP_NUM_THREADS"] = "8"
import torch
import time
from ipex_llm.transformers import AutoModelForCausalLM
from transformers import AutoTokenizer

# 指定模型加载路径
load_path = "qwen2chat_int4"
# 加载低位(int4)量化模型,trust_remote_code=True允许执行模型仓库中的自定义代码
model = AutoModelForCausalLM.load_low_bit(load_path, trust_remote_code=True)
# 加载对应的分词器
tokenizer = AutoTokenizer.from_pretrained(load_path, trust_remote_code=True)

# 定义输入prompt
prompt = "给我讲一个芯片制造的流程"

# 构建符合模型输入格式的消息列表
messages = [{"role": "user", "content": prompt}]

# 使用推理模式,减少内存使用并提高推理速度
with torch.inference_mode():
    # 应用聊天模板,将消息转换为模型输入格式的文本
    text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    # 将文本转换为模型输入张量,并移至CPU (如果使用GPU,这里应改为.to('cuda'))
    model_inputs = tokenizer([text], return_tensors="pt").to('cpu')

    st = time.time()
    # 生成回答,max_new_tokens限制生成的最大token数
    generated_ids = model.generate(model_inputs.input_ids,
                                   max_new_tokens=512)
    end = time.time()

    # 初始化一个空列表,用于存储处理后的generated_ids
    processed_generated_ids = []

    # 使用zip函数同时遍历model_inputs.input_ids和generated_ids
    for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids):
        # 计算输入序列的长度
        input_length = len(input_ids)
        
        # 从output_ids中截取新生成的部分
        # 这是通过切片操作完成的,只保留input_length之后的部分
        new_tokens = output_ids[input_length:]
        
        # 将新生成的token添加到处理后的列表中
        processed_generated_ids.append(new_tokens)

    # 将处理后的列表赋值回generated_ids
    generated_ids = processed_generated_ids

    # 解码模型输出,转换为可读文本
    response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
    
    # 打印推理时间
    print(f'Inference time: {end-st:.2f} s')
    # 打印原始prompt
    print('-'*20, 'Prompt', '-'*20)
    print(text)
    # 打印模型生成的输出
    print('-'*20, 'Output', '-'*20)
    print(response)

        这里的代码有注释,介绍一下流程:加载模型与分词器 -> 指定用户 user 与问题 prompt -> 应用推理(消息转换 -> 一系列处理 -> 解码输出)。

        在上面的代码中,结果完全处理后才会输出打印,和常规的 GPT 不同,如何变成那种几个字几个字的及时响应呢?使用 stream 流式输出。

%%writefile /mnt/workspace/run_stream.py
# 设置OpenMP线程数为8
import os
os.environ["OMP_NUM_THREADS"] = "8"

import time
from transformers import AutoTokenizer
from transformers import TextStreamer

# 导入Intel扩展的Transformers模型
from ipex_llm.transformers import AutoModelForCausalLM
import torch

# 加载模型路径
load_path = "qwen2chat_int4"

# 加载4位量化的模型
model = AutoModelForCausalLM.load_low_bit(load_path, trust_remote_code=True)

# 加载对应的tokenizer
tokenizer = AutoTokenizer.from_pretrained(load_path, trust_remote_code=True)

# 创建文本流式输出器
streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)

# 设置提示词
prompt = "给我讲一个芯片制造的流程"

# 构建消息列表
messages = [{"role": "user", "content": prompt}]
    
# 使用推理模式
with torch.inference_mode():

    # 应用聊天模板,添加生成提示
    text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    
    # 对输入文本进行编码
    model_inputs = tokenizer([text], return_tensors="pt")
    
    print("start generate")
    st = time.time()  # 记录开始时间
    
    # 生成文本
    generated_ids = model.generate(
        model_inputs.input_ids,
        max_new_tokens=512,  # 最大生成512个新token
        streamer=streamer,   # 使用流式输出
    )
    
    end = time.time()  # 记录结束时间
    
    # 打印推理时间
    print(f'Inference time: {end-st} s')

        这一部分的代码与上一部分的关键差别在于倒数第七行的 streamer=streamer,表明了流式输出。

        上面我们已经装好了环境,现在只需要激活并装包即可,依次运行下面的命令。

cd /mnt/workspace
conda activate ipex
python3 run.py

        命令行看到下面的回复:

        至此,我们实现了用英特尔 ipex-llm 工具在 CPU 上实现 qwen2 大模型高性能推理,掌握了完整输出 / 生成流式输出的调用方法。但是这一点有什么缺陷呢?没有 UI 界面,只能回答预先设定的一个问题,所以需要前端交互。

        Gradio 是一个开源的 Python 库,用于快速构建机器学习和数据科学演示应用。它使得开发者可以在几行代码中创建一个简单、可调整的用户界面,用于展示机器学习模型或数据科学工作流程。

        当然,想要使用这个库必须先安装,调用命令 pip install gradio。

        这里很重要的一步就是修改所有的文件,将实例代码中的编号改为自己的编号,否则会找不到路径。

        命令行输入 python3 run_gradio_stream.py 让 gradio 运行起来,点击下面的链接进入界面。

        进入界面后,尝试问几个问题,不过要注意它是没有 history 的,也就是没有上下文记忆。

        可以看到,界面比较单调,而且功能比较薄弱。在命令行按下 Crtl + C 可以终止模型。

        除了 Gradio 外,还有一个 streamlit 同样可以用于前端界面展示。还是同样的流程,装包,启动。

        Streamlit 的 UI 布局就更加简洁了,当然模型不变,功能也不会改变。


baseline2解读

三、使用 IPEX-LLM 运行 RAG 应用

         Retrieval-Augmented Generation(RAG)检索增强生成,顾名思义,就是利用检索来增强大模型的生成结果。它通过向量数据库检索的方式来获取我们问题预期想要回答涉及的知识,然后结合这个知识让大模型基于知识生成最后的问答结果,满足了我们对提问的真实性需求。

        那么,我们将如何构建一个 RAG 系统?

        具体而言,RAG 有很多的实现方式,这里使用最简单的实现方法,遵循最传统的规则,包括索引创建(Indexing)、检索(Retrieval)和生成(Generation),总体来说包括以下三个关键步骤:

  • 语料库被划分成一个个分散的块(chunk),然后使用 embedding 模型构建向量索引,并存储到向量数据库。
  • RAG 根据 query (当前提问)与索引块(Indexed Chunk)的向量相似度识别并对块进行检索。
  • 模型根据检索块(Retrieved Chunk)中获取的上下文信息生成答案。

        LlamaIndex 是一个 AI 框架,用于简化将私有数据与公共数据集成到大型语言模型(LLM)中的应用程序中。它提供了数据 ingestion、 indexing 和查询的工具,使其成为生成式AI需求的可靠解决方案。

       目标:结合它与 IPEX-LLM 实现一个简单的 RAG 系统。

        首先将最开始提到的 llamatiny.pdf 导入到文件中,用于后续的提问。

        接着配置环境,运行命令 pip install PyMuPDF llama-index-vector-stores-chroma llama-index-readers-file llama-index-embeddings-huggingface llama-index。

        打开 初赛baseline2 文件,最后的 Config 配置函数中的 question 代表问题,可以个性化定制为下面的这个,取决于个人想法。

        然后切换 ipex 环境,一键运行所有代码段。

        最后运行命令 python3 run_rag.py,表示将上面写入到文件中的代码真正运行。查看结果。

        实际上,打开文件,发现标题如下,与真实值不同。

       

        为什么答案不对?与文档的切分策略有关;检索过程中的计算方式不同;chat-model 等的计算量较小...


总结

        检索给出了结果,虽然当前的测试效果不好,但是整体流程正确,我们也确实成功搭建了 千问2 大模型,结合 LlamaIndex 实现了 RAG 系统。baseline 中的代码我并没有全部展示,这里只为了整体流程的通畅与逻辑的清晰。

        本篇文章使用的 开源大语言模型:通义千问 Qwen2,Intel 部署工具:IPEX-LLM,微调技术:RAG。

        可能的优化点:换新模型,改进模型参数,混合精度训练,增加模型部署及优化工具,修改量化程度,缓存机制,修改微调,使用 FAISS 等高效的向量检索Intel库,买高性能硬件...

涉及知识点:

  1. 在CPU环境下进行模型下载与部署

  2. intel部署及优化工具

  3. RAG理论:llamaindex

  4. Gradio/Streamlit 开发应用 Demo 入门

下一期链接(超精彩,值得收藏):Datawhale AI 夏令营——CPU部署大模型(LLM天池挑战赛)——Task2与3学习笔记-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值