Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
Back to tool/Back to issues
#352·ChatGLM2-6B

[Feature | Bug Fix] <ChatGLM2-6B-int4 使用CPU部署报错:找不到文件quantization_kernels_parallel.so>

Author: bwj124Created Jul 21, 2023Updated Dec 3, 2024

Is your feature request related to a problem? Please describe.

按照Readme的描述使用CPU推理ChatGLM2-6B-int4量化版本时报错,报错信息如下:

image-20230721201053533

已完成的步骤:

  1. 将模型下载至本地并使用本地路径

  2. 改用.float()使用cpu

  3. 已安装[TDM-GCC](https://jmeubank.github.io/tdm-gcc/),且勾选了OpenMP

Solutions

我的解决思路是运行ChatGLM-6b-int4,如果ChatGLM-6b-int4可以运行,那么可以参照着ChatGLM-6b-int一步步调试以最终跑通ChatGLM2-6b-int4。

结果是发现ChatGLM-6b-int4也跑不通,不过已经有一些相关的[issue](https://github.com/THUDM/ChatGLM-6B/issues/166)。

参考其他issue我解决了一个问题:编译出来的quantization_kernels_parallel.so和quantization_kernels.so其实并不能用。因此上面的报错本质上其实不是文件找不到,而是文件无法加载。(相关代码在quantization.py的CPUKernel类,包括编译和加载CPU Kernel)

经过仔细阅读源码,以及参考ChatGLM-6B中的issue,我发现其实解决的办法很简单。

对于ChatGLM-6b-int4,出现问题的原因只有:编译出来的so文件有问题,因而无法被加载。(因为后来手动编译的so文件和原始的so文件大小差异明显)

关于为什么按照教程走,但是编译出的文件有问题,我想很大概率是因为我的电脑中安装了不止一个gcc,包括MingGW64以及Cygwin。因此很可能找到TDM-GCC的gcc路径,用绝对路径去手动编译可以获得可用的so文件。但是我没有尝试,而是用了上面issue中[gongjimin推荐的gcc](https://github.com/skeeto/w64devkit/releases)。

所以解决办法就2步:编译正确的so文件和加载正确的so文件。

有人可能有疑问:编译好正确的so文件放到所需路径不就可以了吗?我最初也是这样想的,但无奈的发现程序会重新编译c文件,因此可用的so会被覆盖再次变成不可用的so文件。

编译正确的so文件:刚才也提到了,使用[该项目](https://github.com/skeeto/w64devkit/releases)的GCC编译即可。命令如下:

gcc -fPIC -pthread -fopenmp -std=c99 quantization_kernels.c -shared -o quantization_kernels.so
gcc -fPIC -pthread -fopenmp -std=c99 quantization_kernels_parallel.c -shared -o quantization_kernels_parallel.so

加载正确的so文件:在加载模型的代码后加一句加载量化模型所需kernel的代码,即

python
tokenizer = AutoTokenizer.from_pretrained("chatglm2-6b-int4", trust_remote_code=True, revision="v1.0")
model = AutoModel.from_pretrained("chatglm2-6b-int4", trust_remote_code=True, revision="v1.0").float()  #.cuda()
model = model.quantize(bits=4, kernel_file=r"E:\Code\PyCharm\PyCharmProjects\ChatGLM2\chatglm2-6b-int4\quantization_kernels.so")

kernel_file为你编译好的so文件路径,亲测quantization_kernels_parallel.so和quantization_kernels.so都可以运行。

如果模型是ChatGLM-6b-int4,那么到这里就可以运行了。

但是ChatGLM2-6b-int4还不行,为什么呢?我也很疑惑,我想既然chatglm可以运行了,为什么chatglm2还是有问题。于是我在模型加载kernel的部分单步调试,最终发现了:哦!原来chatglm2直接把CPU的量化版本加载kernel的代码删除了!不知道是不是因为太少人用CPU的量化模型部署了。

于是我按照chatglm的代码,把加载kernel的代码加上就可以运行了。修改的代码不是很多,就两段。

第一段是在modeling_chatglm.py中修改最后一个函数quantize中的

python
from .quantization import quantize

if self.quantized:
    logger.info("Already quantized.")
    return self

修改为(参照chatglm):

python
from .quantization import quantize, load_cpu_kernel

if self.quantized:
    if self.device == torch.device("cpu"):
        logger.info("Already quantized, reloading cpu kernel.")
        load_cpu_kernel(**kwargs)
    else:
        logger.info("Already quantized.")
        return self

    self.quantized = True

第二段是在quantization.py的这条语句

python
cpu_kernels = CPUKernel()

后面加上所需的load_cpu_kernel函数

python
cpu_kernels = CPUKernel()


def load_cpu_kernel(**kwargs):
    global cpu_kernels
    cpu_kernels = CPUKernel(**kwargs)

OK,到这里就可以运行了。

Additional context

省流:

  1. 编译正确的so文件: 使用[项目](https://github.com/skeeto/w64devkit/releases)中的gcc编译c文件生成so文件

  2. 加载正确的so文件:

    python
    tokenizer = AutoTokenizer.from_pretrained("chatglm2-6b-int4", trust_remote_code=True)
    model = AutoModel.from_pretrained("chatglm2-6b-int4", trust_remote_code=True).float()
    model = model.quantize(bits=4, kernel_file="xxx\quantization_kernels.so")
  3. 修改modeling_chatglm.py的函数quantize中的

    python
    from .quantization import quantize
    
    if self.quantized:
        logger.info("Already quantized.")
        return self

    为:

    python
    from .quantization import quantize, load_cpu_kernel
    
    if self.quantized:
        if self.device == torch.device("cpu"):
            logger.info("Already quantized, reloading cpu kernel.")
            load_cpu_kernel(**kwargs)
        else:
            logger.info("Already quantized.")
            return self
    
        self.quantized = True
  4. 在quantization.py的这条语句

    python
    cpu_kernels = CPUKernel()

    后面加上load_cpu_kernel函数

    python
    cpu_kernels = CPUKernel()
    
    
    def load_cpu_kernel(**kwargs):
        global cpu_kernels
        cpu_kernels = CPUKernel(**kwargs)

Source: zai-org/ChatGLM2-6B

View original on GitHubView discussion on GitHub