阿里云网站建设——部署与发布,做外贸做什么网站好,网页编辑字体加粗代码,廊坊网站建设廊坊网络公司驻梦简单聊聊可以在端侧运行的 Mini CPM 2B SFT / DPO 版本的模型。
写在前面
模型是好是坏#xff0c;其实不用看公众号们的营销#xff0c;小马过河问题#xff0c;自己试试就知道了。当然#xff0c;2B 参数量的模型#xff0c;适合的场景肯定不是 34B / 70B 所擅长的其实不用看公众号们的营销小马过河问题自己试试就知道了。当然2B 参数量的模型适合的场景肯定不是 34B / 70B 所擅长的审视标准应该是不同的。
去年年末的闭门分享会听到面壁联创大海老师的分享对其“不刷分不打榜”以及“产品价值还是服务价值” 的观点记忆深刻。所以朋友圈看到铺天盖地的“小钢炮”的发布介绍时第一时间就下手运行了一把还顺带修正了一些 vllm 运行过程中的问题。
这篇文章暂且不提耗时时间比较长的 vllm 高效运行的准备过程先简单聊聊如何快速的在本地运行这个“贺岁模型”。
准备工作
和往常一样准备工作只有两件事“准备模型的运行环境”和“下载 CPM 模型”。
准备容器环境
我个人比较倾向使用 Docker 作为运行环境在投入很少额外资源的情况下能够快速获得纯净、可复现的一致性非常棒的环境。
如果你选择 Docker 路线不论你的设备是否有显卡都可以根据自己的操作系统喜好参考这两篇来完成基础环境的配置《基于 Docker 的深度学习环境Windows 篇》、《基于 Docker 的深度学习环境入门篇》。当然使用 Docker 之后你还可以做很多事情比如之前几十篇有关 Docker 的实践在此就不赘述啦。
除此之外为了高效运行模型我推荐使用 Nvidia 官方的容器镜像nvcr.io/nvidia/pytorch:24.01-py3以及 HuggingFace 出品的 Transformers 工具包和快速完成模型 Web UI 界面搭建的 Gradio以及既能做模型加速推理又能做多卡分布式加速推理的 Accelerate 工具包。
我们可以基于上面的内容快速折腾一个干净、高效的基础运行环境。
考虑到我们可能会将模型应用运行在不同的环境比如云主机和服务器它们的网络环境可能有所不同。
当我们本地进行 Docker 镜像构建的时候配置软件镜像来加速可以大幅改善开发者体验。所以稍加调整我们可以得到下面的 Dockerfile 文件
FROM nvcr.io/nvidia/pytorch:24.01-py3
LABEL maintainersoultearygmail.com# setup Ubuntu and PyPi mirrors, refs: https://github.com/soulteary/docker-stable-diffusion-webui/blob/main/docker/Dockerfile.base
ARG USE_CHINA_MIRRORfalse
RUN if [ $USE_CHINA_MIRROR true ]; then \pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple \sed -i s/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g /etc/apt/sources.list \sed -i s/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g /etc/apt/sources.list; \fi# install dependencies
RUN pip install transformers4.37.2 gradio4.16.0 accelerate0.26.1将上面的内容保存为 Dockerfile然后执行下面的命令可以进行镜像构建
# 直接构建
docker build -t soulteary/minicpm:hf .# 启用国内的软件镜像加速构建
docker build --build-argUSE_CHINA_MIRRORtrue -t soulteary/minicpm:hf .稍等片刻等命令执行完毕基础镜像就构建好了我们的准备工作也就完成一半啦。
下载模型
接下来我们来完成镜像准备之外的 50% 的准备工作下载模型。
你可以根据你的实际网络情况来选择到底是从 HuggingFace 下载模型还是从 ModelScope 来下载模型。当然今年国内开发者有了一个新的选项WiseModel。你可以根据你的网络情况来选择最适合你的模型下载或者在线推理平台。
虽然官方一口气推出了很多版本不过在小参数量模型的能力和效果验证上我个人的观点是尽可能先下载尺寸最大的比如 dpo-fp32、sft-fp32 两个版本的模型来规避数据转换带来的测试结果的干扰。
模型下载完毕后确保目录结构类似下面这样
├── MiniCPM-2B-dpo-fp32
│ ├── README.md
│ ├── config.json
│ ├── configuration.json
│ ├── configuration_minicpm.py
│ ├── generation_config.json
│ ├── modeling_minicpm.py
│ ├── pytorch_model.bin
│ ├── special_tokens_map.json
│ ├── tokenizer.json
│ ├── tokenizer.model
│ └── tokenizer_config.json
└── MiniCPM-2B-sft-fp32├── README.md├── config.json├── configuration_minicpm.py├── generation_config.json├── modeling_minicpm.py├── pytorch_model.bin├── special_tokens_map.json├── tokenizer.json├── tokenizer.model└── tokenizer_config.json不论你从哪里获取模型都建议你在得到模型后进行文件 Hash 验证。
下面是 DPO 模型的 Hash
# shasum OpenBMB/MiniCPM-2B-dpo-fp32/*30f7faade4df3f061b3bfeda8dcce1f3dfaa5b6b OpenBMB/MiniCPM-2B-dpo-fp32/README.md
161c58f3802b0d67516d8efdd25b81317c0ac5bd OpenBMB/MiniCPM-2B-dpo-fp32/config.json
9b0b13d6cfed485a07b321bcc471bee0830004e4 OpenBMB/MiniCPM-2B-dpo-fp32/configuration.json
767ea019b50a5e4c6ef02887740c01018580b840 OpenBMB/MiniCPM-2B-dpo-fp32/configuration_minicpm.py
82bdc1029ba8b8181a4450f2c421cde60ba550c0 OpenBMB/MiniCPM-2B-dpo-fp32/generation_config.json
6c70fd73f2f07f86c0f8ff89b2ce4fa4265c369d OpenBMB/MiniCPM-2B-dpo-fp32/modeling_minicpm.py
bfdd8439579f93433234b46394cefb3cfe5ee94b OpenBMB/MiniCPM-2B-dpo-fp32/pytorch_model.bin
faed8efa07e75f91b0fe66219ad6bbfb14bfdb0e OpenBMB/MiniCPM-2B-dpo-fp32/special_tokens_map.json
b7392d123e20c5b770699f5440fd0f2a0d8a52fb OpenBMB/MiniCPM-2B-dpo-fp32/tokenizer.json
c57072fc486c976c8c617cc34c0bb742b6a29b19 OpenBMB/MiniCPM-2B-dpo-fp32/tokenizer.model
6d0a5c9ee222e1460d8df97d3ed36f934327cd06 OpenBMB/MiniCPM-2B-dpo-fp32/tokenizer_config.json以及 SFT 模型的 Hash
shasum OpenBMB/MiniCPM-2B-sft-fp32/*
c2bbcd5cdece248fab38f40fa679e8f958df48a0 OpenBMB/MiniCPM-2B-sft-fp32/README.md
50508d551e005168f8adcf2b98acb565bbb49f6f OpenBMB/MiniCPM-2B-sft-fp32/config.json
767ea019b50a5e4c6ef02887740c01018580b840 OpenBMB/MiniCPM-2B-sft-fp32/configuration_minicpm.py
82bdc1029ba8b8181a4450f2c421cde60ba550c0 OpenBMB/MiniCPM-2B-sft-fp32/generation_config.json
6c70fd73f2f07f86c0f8ff89b2ce4fa4265c369d OpenBMB/MiniCPM-2B-sft-fp32/modeling_minicpm.py
2b4d1fc9d13f6cb871f8e2d6735b6f0053b8d9d4 OpenBMB/MiniCPM-2B-sft-fp32/pytorch_model.bin
faed8efa07e75f91b0fe66219ad6bbfb14bfdb0e OpenBMB/MiniCPM-2B-sft-fp32/special_tokens_map.json
b7392d123e20c5b770699f5440fd0f2a0d8a52fb OpenBMB/MiniCPM-2B-sft-fp32/tokenizer.json
c57072fc486c976c8c617cc34c0bb742b6a29b19 OpenBMB/MiniCPM-2B-sft-fp32/tokenizer.model
6d0a5c9ee222e1460d8df97d3ed36f934327cd06 OpenBMB/MiniCPM-2B-sft-fp32/tokenizer_config.json编写推理程序
我们基于官方项目中的推理程序进行简单调整让它能够支持在容器中运行并且能够支持用模型原始数据类型进行推理尽量避免数据转换带来不必要的测试干扰问题。
from typing import Listimport argparse
import gradio as gr
import torch
from threading import Thread
from transformers import (AutoModelForCausalLM, AutoTokenizer,TextIteratorStreamer
)import warnings
warnings.filterwarnings(ignore, categoryUserWarning, messageTypedStorage is deprecated)parser argparse.ArgumentParser()
parser.add_argument(--model_path, typestr, default)
parser.add_argument(--torch_dtype, typestr, defaultbfloat16)
parser.add_argument(--server_name, typestr, default127.0.0.1)
parser.add_argument(--server_port, typeint, default7860)args parser.parse_args()# init model torch dtype
torch_dtype args.torch_dtype
if torch_dtype or torch_dtype bfloat16:torch_dtype torch.bfloat16
elif torch_dtype float32:torch_dtype torch.float32
else:raise ValueError(fInvalid torch dtype: {torch_dtype})# init model and tokenizer
path args.model_path
tokenizer AutoTokenizer.from_pretrained(path)
model AutoModelForCausalLM.from_pretrained(path, torch_dtypetorch_dtype, device_mapauto, trust_remote_codeTrue)# init gradio demo host and port
server_nameargs.server_name
server_portargs.server_portdef hf_gen(dialog: List, top_p: float, temperature: float, max_dec_len: int):generate model output with huggingface apiArgs:query (str): actual model input.top_p (float): only the smallest set of most probable tokens with probabilities that add up to top_p or higher are kept for generation.temperature (float): Strictly positive float value used to modulate the logits distribution.max_dec_len (int): The maximum numbers of tokens to generate.Yields:str: real-time generation results of hf model inputs tokenizer.apply_chat_template(dialog, tokenizeFalse, add_generation_promptFalse)enc tokenizer(inputs, return_tensorspt).to(cuda)streamer TextIteratorStreamer(tokenizer)generation_kwargs dict(enc,do_sampleTrue,top_ptop_p,temperaturetemperature,max_new_tokensmax_dec_len,pad_token_idtokenizer.eos_token_id,streamerstreamer,)thread Thread(targetmodel.generate, kwargsgeneration_kwargs)thread.start()answer for new_text in streamer:answer new_textyield answer[4 len(inputs):]def generate(chat_history: List, query: str, top_p: float, temperature: float, max_dec_len: int):generate after hitting submit buttonArgs:chat_history (List): [[q_1, a_1], [q_2, a_2], ..., [q_n, a_n]]. list that stores all QA recordsquery (str): query of current roundtop_p (float): only the smallest set of most probable tokens with probabilities that add up to top_p or higher are kept for generation.temperature (float): strictly positive float value used to modulate the logits distribution.max_dec_len (int): The maximum numbers of tokens to generate.Yields:List: [[q_1, a_1], [q_2, a_2], ..., [q_n, a_n], [q_n1, a_n1]]. chat_history QA of current round. assert query ! , Input must not be empty!!!# apply chat templatemodel_input []for q, a in chat_history:model_input.append({role: user, content: q})model_input.append({role: assistant, content: a})model_input.append({role: user, content: query})# yield model generationchat_history.append([query, ])for answer in hf_gen(model_input, top_p, temperature, max_dec_len):chat_history[-1][1] answer.strip(/s)yield gr.update(value), chat_historydef regenerate(chat_history: List, top_p: float, temperature: float, max_dec_len: int):re-generate the answer of last rounds queryArgs:chat_history (List): [[q_1, a_1], [q_2, a_2], ..., [q_n, a_n]]. list that stores all QA recordstop_p (float): only the smallest set of most probable tokens with probabilities that add up to top_p or higher are kept for generation.temperature (float): strictly positive float value used to modulate the logits distribution.max_dec_len (int): The maximum numbers of tokens to generate.Yields:List: [[q_1, a_1], [q_2, a_2], ..., [q_n, a_n]]. chat_history assert len(chat_history) 1, History is empty. Nothing to regenerate!!# apply chat templatemodel_input []for q, a in chat_history[:-1]:model_input.append({role: user, content: q})model_input.append({role: assistant, content: a})model_input.append({role: user, content: chat_history[-1][0]})# yield model generationfor answer in hf_gen(model_input, top_p, temperature, max_dec_len):chat_history[-1][1] answer.strip(/s)yield gr.update(value), chat_historydef clear_history():clear all chat historyReturns:List: empty chat history return []def reverse_last_round(chat_history):reverse last round QA and keep the chat history beforeArgs:chat_history (List): [[q_1, a_1], [q_2, a_2], ..., [q_n, a_n]]. list that stores all QA recordsReturns:List: [[q_1, a_1], [q_2, a_2], ..., [q_n-1, a_n-1]]. chat_history without last round. assert len(chat_history) 1, History is empty. Nothing to reverse!!return chat_history[:-1]# launch gradio demo
with gr.Blocks(themesoft) as demo:gr.Markdown(# MiniCPM Gradio Demo)with gr.Row():with gr.Column(scale1):top_p gr.Slider(0, 1, value0.8, step0.1, labeltop_p)temperature gr.Slider(0.1, 2.0, value0.8, step0.1, labeltemperature)max_dec_len gr.Slider(1, 1024, value1024, step1, labelmax_dec_len)with gr.Column(scale5):chatbot gr.Chatbot(bubble_full_widthFalse, height400)user_input gr.Textbox(labelUser, placeholderInput your query here!, lines8)with gr.Row():submit gr.Button(Submit)clear gr.Button(Clear)regen gr.Button(Regenerate)reverse gr.Button(Reverse)submit.click(generate, inputs[chatbot, user_input, top_p, temperature, max_dec_len], outputs[user_input, chatbot])regen.click(regenerate, inputs[chatbot, top_p, temperature, max_dec_len], outputs[user_input, chatbot])clear.click(clear_history, inputs[], outputs[chatbot])reverse.click(reverse_last_round, inputs[chatbot], outputs[chatbot])demo.queue()
demo.launch(server_nameserver_name, server_portserver_port, show_errorTrue)我们将上面的内容保存为 app.py然后启动 Docker 容器接着将程序挂载和模型挂载到容器内就可以开始体验模型了。
如果你和我一样将模型下载到了 models 目录中并且保持了上文提到的模型目录结构那么可以运行下面的命令来启动一个 MiniCPM-2B 模型的实例
docker run --rm -it -p 7860:7860 --gpus all --ipchost --ulimit memlock-1 -v pwd/models:/app/models soulteary/minicpm:hf python app.py --model_path./models/OpenBMB/MiniCPM-「具体模型型号」/ --server_name0.0.0.0你可以将上面命令中的 ./models/OpenBMB/MiniCPM-「具体模型型号」需要替换为真实的地址比如这样
docker run --rm -it -p 7860:7860 --gpus all --ipchost --ulimit memlock-1 -v pwd/models:/app/models soulteary/minicpm:hf python app.py --model_path./models/OpenBMB/MiniCPM-2B-dpo-fp32/ --server_name0.0.0.0官方程序默认逻辑中使用的是 bfloat16 来进行模型推理如果你下载的也是 float32 版本的模型我们可以在启动的时候额外增加一个参数 --torch_dtypefloat32来避免模型数据类型转换
docker run --rm -it -p 7860:7860 --gpus all --ipchost --ulimit memlock-1 -v pwd/models:/app/models soulteary/minicpm:hf python app.py --model_path./models/OpenBMB/MiniCPM-2B-dpo-fp32/ --server_name0.0.0.0 --torch_dtypefloat32当命令执行完毕后我们可以看到下面的日志输出 PyTorch
NVIDIA Release 24.01 (build 80741402)
PyTorch Version 2.2.0a081ea7a4Container image Copyright (c) 2023, NVIDIA CORPORATION AFFILIATES. All rights reserved.Copyright (c) 2014-2023 Facebook Inc.
Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert)
Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu)
Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu)
Copyright (c) 2011-2013 NYU (Clement Farabet)
Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston)
Copyright (c) 2006 Idiap Research Institute (Samy Bengio)
Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz)
Copyright (c) 2015 Google Inc.
Copyright (c) 2015 Yangqing Jia
Copyright (c) 2013-2016 The Caffe contributors
All rights reserved.Various files include modifications (c) NVIDIA CORPORATION AFFILIATES. All rights reserved.This container image and its contents are governed by the NVIDIA Deep Learning Container License.
By pulling and using the container, you accept the terms and conditions of this license:
https://developer.nvidia.com/ngc/nvidia-deep-learning-container-licenseWARNING: CUDA Minor Version Compatibility mode ENABLED.Using driver version 525.147.05 which has support for CUDA 12.0. This containerwas built with CUDA 12.3 and will be run in Minor Version Compatibility mode.CUDA Forward Compatibility is preferred over Minor Version Compatibility for usewith this container but was unavailable:[[Forward compatibility was attempted on non supported HW (CUDA_ERROR_COMPAT_NOT_SUPPORTED_ON_DEVICE) cuInit()804]]See https://docs.nvidia.com/deploy/cuda-compatibility/ for details.Running on local URL: http://0.0.0.0:7860To create a public link, set shareTrue in launch().日志输出内容中出现 Running on local URL: http://0.0.0.0:7860 时我们访问浏览器地址 http://容器IP地址:7860就能够来体验 SFT 或者 DPO 版本的小钢炮模型啦。 接下来就是随便的进行测试啦。
简单测试
关于模型的体验我之前在这篇知乎帖子中回答过。为了客观和真实性我们不妨再做几次测试。
首先测试下基础的“命题作文”的能力 接着问问条件有限的情况下的“料理” 这里的回答如果能够结合“下厨房的 API”应该会更靠谱一些虽然乍一看是符合要求的食谱方案但是仔细一看是没有遵循指令的要求中的食材是没有用到的。
继续顺便问问经典的“螺丝帽料理”话题 不正经的做饭倒是回答的不错先叠甲规避责任然后放飞自我的生成内容最后加一句“仅供娱乐不推荐食用”。
当然也可以问问“为什么伟大的音乐家没有再继续谱曲” 这里的回复准确度是有问题的月光不是莫扎特的得用官方推荐的 RAG 方式来做准确内容的生成。
当然模型还有很多问题除了想要得到答案需要反复的 Roll 来 Roll 去外也会出现怎么都刷不出来自己想要的答案的情况比如这个还是相对好的状态 在生成对联的过程中最容易出现的是重复内容的生成虽然可能和其他的模型一样通过添加 repetition_penalty 重复惩罚相关的参数并设置比较大的数值来减少一些情况的发生但可能最好的方案还是给这个小模型附加一个“知识库”避免完全进入知识不足的“局部最优解”的死循环中。
最后
这篇文章先写到这里关于本篇文章挖的一个坑如何在安装了最新版本 CUDA 、Torch 等软件版本的容器环境中使用 vllm 来高效运行模型下一篇相关的文章再聊。
–EOF 本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议欢迎转载、或重新修改使用但需要注明来源。 署名 4.0 国际 (CC BY 4.0)
本文作者: 苏洋
创建时间: 2024年02月02日 统计字数: 14180字 阅读时间: 29分钟阅读 本文链接: https://soulteary.com/2024/02/02/locally-run-modelbest-mini-cpm-2b.html