张小云的个人主页

Hello, 本地部署GLM-4

--以及我踩过的坑

Hello!你们的老朋友我又回来了!最近几天都一直很忙,没什么时间去在主页上写文章,今天是可算有空了,赶紧来更新一篇,分享一下我这几天在本地部署AI时,得到的经验&踩到的坑。

So let we go!

先讲一下原因

为什么我要本地部署AI?

答案很简单:线上的指不定哪天就“凉”了,指不定可能还有厂商心血来潮把免费改成付费的风险。于是我望向我空空如也的零花钱包,我想到了本地部署AI。本地部署不仅意味着我能够随时随地访问AI,还可以减少对网络的依赖,提高响应速度,并且我可以定制和控制整个环境。此外,本地部署还可以更好地保护我的数据隐私,因为数据不会离开我的设备,这给了我更多的安全保障和控制权。最后,本地部署AI还有可能进行一些微调,比如增强代码能力,减少“废话”等,这些都是其好处。

于是,我决定在我家的笔记本(联想ThinkBook)和台式机(自己装的)上面本地部署一个开源大语言模型。

但是,从之前的一些前人的踩坑经验来看,这明显不是一件容易的事。我再稍微查了一些文档,,, 发现有CUDA问题的、cuDNN问题的、PyTorch问题的——简直是数不胜数!路还很长,废话就不多说(这句话就是废话),我们开始吧!

准备工作

警告:接下来可能会废话连篇,并且还有强烈的主观感受!

首先先说一下,作者的策略均在ThinkBook上测试过。如果有因读者操作错误导致的问题,可以再读 一遍,再试一次。墙裂建议在虚拟机中尝试,以免系统故障。另外,在进行任何重要操作之前,请务必备份重要数据,以免造成不可挽回的损失。希望这些额外的建议能够帮助您更好地应用作者的策略。

1. 选模型

首先,既然是本地部署大模型,就要先选择一款大模型。我们理一下我们的需求: 首先,不管效果怎么样,能在RTX 4060 laptop上跑起来肯定是最重要的; 其次,要能在Windows11上可用; 然后,尽量使用Python3.10; 最后,为了性能考量,能用NVIDIA CUDA。 还有,尽量支持国产软件和产品~ 我在国产大模型中,选了我一向很喜欢的THUDM(清华大学KEG的)的大语言模型GLM的第四代90亿参数版本:GLM-4-9b。

2. 下模型

2.1 下代码

好了,模型选好了,是时候下载一下我们的AI模型了! 众所周知,开源模型一般会在GitHub上开源代码,那我们就开始下载代码吧!首先试一下GitHub源站下载:

GitHub下载:

在GitHub上,我们可以通过搜索功能找到感兴趣的开源项目,或者直接通过项目的链接进入项目主页。在项目主页,我们可以找到“Code”按钮,点击这个按钮就能够获取项目的Git地址,然后使用Git命令或者GitHub提供的桌面客户端来克隆或下载代码。

果然,试试就失败了。不出意外,仍然是出了意外,这页面卡的是拔凉拔凉,我悬着的心终于掉了下来。

不过别担心,GitHub可是有“镜像站”的。打开KGitHub再试一次吧!

OK!成功了!不过搜索界面仍然有亿丢丢慢,那直接输URL试试看:GitHub – THUDM/GLM-4 下载之后解压缩,就OK了。

注意!解压缩之后,把重复的目录(如./aaa/aaa/)删掉一层(只保留单层)!

2.2 下模型

先试一下HF源站下载:huggingface.co/THUDM/glm-4-9b-chat。

好吧,将URL中的huggingface.co换为hf-mirror.com,如法炮制。 之前下载代码的目录下,新建models目录 下载了模型之后,解压缩,把模型放在models/glm-4-9b-chat下。 注意!解压缩之后,把重复的目录(如./aaa/aaa/)删掉一层(只保留单层)!

3.装依赖

3.1 开COMMAND

然后打开一个Command: 输入[win]+r,打开运行框: 输入cmd 如果看到弹出来的黑框框,那就成功了!

3.2 下LIB

在黑窗口里输入: cd "路径" 将“路径”换位你实际下载模型的路径。 然后,在黑窗口里输入:

pip install -r requirements.txt

让它下载好。不出意外的话,他会报:Connect Time Out,我们可以先设置一下镜像源:

pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

然后再下载。 应该会输出类似这样的内容:

看到

Successfully installed ***

即可。

3.3 下CUDA

找到CUDA Toolkit 12.4 Update 1 Downloads | NVIDIA 开发者,选择合适的版本,下载后自行安装即可。 注意!一定要记好安装目录和版本! 检查环境是否含有环境变量,鼠标右键点击

点击系统–>高级系统设置–>环境变量–>系统变量(下面那个),

新建CUDA_PATH,值为刚刚安装的CUDA的目录。 新建CUDA_PATH_V**_*,值为刚刚安装的CUDA的目录。

现在必需要重启电脑,然后Win+R进入cmd界面,输入nvcc -V,出现如下界面,cuda已经安装成功了。 3.4 下CUDNN

找到cuDNN 9.2.0,选择合适的版本,下载后自行安装即可。 如果下载下来是exe,直接安装,一定要勾选“添加到Path”; 如果下载下来是一些文件夹,这样: 找到cuda的安装路径,将cuDNN三个文件夹的内容分别复制到cuda对应的文件夹里面 找到环境变量-系统变量-双击PATH, 将刚刚移进去的那三个文件夹子填进Path。 进入到cuda的安装路径,在终端中运行deviceQuery.exe和bandwidthTest.exe, 如果报Result = PASS,就成功了。

3.5 下PYTORCH

好,然后,如果你想用CUDA推理,那么卸载torch:

pip uninstall torch

这时候有人就要问了:小云,为什么要装了又卸载呢?

我可不愿意看到装了CPU版torch然后找不到不用CUDA推理的人越来越多。 先在之前的那个终端里输入

nvidia-smi

好,打开Start Locally | PyTorch,翻到这里:

选择版本(会很慢,请勿乱点),拷贝下面那行命令。 好,这时候,先不急着运行,我们现在终端里打开:

python

然后:

import torch

如果有报错:

Traceback (most recent call last):
File C:\Users\***\AppData\Local\Programs\Python\Python3.**\Lib\site-packages\torch_init_.py, line 141, in
    raise err
OSError: [WinError 126] The specified module could not be found. 
Error loading C:\Users\***\Lib\site-packages\torch\lib\fbgemm.dll or one of its dependencies.

可以下载一下 Microsoft Visual C++ 或者(和) libomp140.x86_64.dll 。 等到import后没有任何反应后,只是提示>>>时,输入

torch.__version__ ; torch.cuda.torch.cuda.is_available()

结果为

pytorch2.*.*-***+gpu
True

成功!

3.6 安装TENSORFLOW

终端:

pip install tensorflow

OK。

4 运行

1. 命令行

切换到代码目录,进入basic_demo,编辑器打开trans_cli_demo.py 改成:

"""
This script creates a CLI demo with transformers backend for the glm-4-9b model,
allowing users to interact with the model through a command-line interface.

Usage:
- Run the script to start the CLI demo.
- Interact with the model by typing questions and receiving responses.

Note: The script includes a modification to handle markdown to plain text conversion,
ensuring that the CLI interface displays formatted text correctly.
"""

import os
from threading import Thread

import torch
from transformers import AutoTokenizer, StoppingCriteria, StoppingCriteriaList, TextIteratorStreamer, AutoModel


# MODEL_PATH = os.environ.get('MODEL_PATH', 'THUDM/glm-9b-chat')
os.environ.setdefault('USE_FLASH_ATTENTION', '0')

def file_exist_check(record_dir, file_name):
    non_exist = False
    try:
        open('/'.join([record_dir, file_name]), 'r').readlines()
    except FileNotFoundError:
        non_exist = True

    return not non_exist


# If use peft model.
def load_model_and_tokenizer(model_dir, trust_remote_code: bool = True):
    adapter_config = file_exist_check(model_dir, 'adapter_config.json')
    global model
    global tokenizer

    if adapter_config:
        model = AutoModel.from_pretrained(
            model_dir, trust_remote_code=trust_remote_code, device_map='auto'
        )
        tokenizer_dir = model.peft_config['default'].base_model_name_or_path
    else:
        model = AutoModel.from_pretrained(
            model_dir, trust_remote_code=trust_remote_code, device_map='auto'
        )
        tokenizer_dir = model_dir
    tokenizer = AutoTokenizer.from_pretrained(
        tokenizer_dir, trust_remote_code=trust_remote_code, use_fast=False
    )
    return model, tokenizer


models = load_model_and_tokenizer('D:\AI\glm-4\model\glm-9b-chat')# input('Enter model path: '))

model = models[0]
# print(model.device)
# print(type(model.device))
tokenizer = models[1]
'''
AutoTokenizer.from_pretrained(
    MODEL_PATH,
    trust_remote_code=True,
    encode_special_tokens=True
)
model = AutoModel.from_pretrained(
    MODEL_PATH,
    trust_remote_code=True,
    device_map="auto",
    torch_dtype=torch.bfloat16).eval()
'''


class StopOnTokens(StoppingCriteria):
    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
        stop_ids = model.config.eos_token_id
        for stop_id in stop_ids:
            if input_ids[0][-1] == stop_id:
                return True
        return False


if __name__ == '__main__':

    history = []
    max_length = 8192
    top_p = 0.8
    temperature = 0.6
    stop = StopOnTokens()

    print("Welcome to the GLM-4-9B CLI chat. Type your messages below.")
    while True:
        print('')  # Split.
        user_input = input("You: ")
        if user_input.lower() in ["exit", "quit"]:
            break
        history.append([user_input, ""])

        messages = []
        for idx, (user_msg, model_msg) in enumerate(history):
            if idx == len(history) - 1 and not model_msg:
                messages.append({"role": "user", "content": user_msg})
                break
            if user_msg:
                messages.append({"role": "user", "content": user_msg})
            if model_msg:
                messages.append({"role": "assistant", "content": model_msg})
        model_inputs = tokenizer.apply_chat_template(
            messages,
            add_generation_prompt=True,
            tokenize=True,
            return_tensors="pt"
        ).to(model.device)  # .cuda() & .to('cuda:0') & to(model.device) is also OK
        streamer = TextIteratorStreamer(
            tokenizer=tokenizer,
            timeout=60,
            skip_prompt=True,
            skip_special_tokens=True
        )
        generate_kwargs = {
            "input_ids": model_inputs,
            "streamer": streamer,
            "max_new_tokens": max_length,
            "do_sample": True,
            "top_p": top_p,
            "temperature": temperature,
            "stopping_criteria": StoppingCriteriaList([stop]),
            "repetition_penalty": 1.2,
            "eos_token_id": model.config.eos_token_id,
        }
        t = Thread(target=model.generate, kwargs=generate_kwargs)
        t.start()

        print("GLM-4: ", end="", flush=True)
        for new_token in streamer:
            if new_token:
                print(new_token, end="", flush=True)
                history[-1][1] += new_token

        history[-1][1] = history[-1][1].strip()

OK,保存,终端运行:

cd 模型目录/basic_demo
python trans_cli_demo.py