[Feature | Bug Fix] <ChatGLM2-6B-int4 使用CPU部署报错:找不到文件quantization_kernels_parallel.so>
Is your feature request related to a problem? Please describe.
按照Readme的描述使用CPU推理ChatGLM2-6B-int4量化版本时报错,报错信息如下:
已完成的步骤:
将模型下载至本地并使用本地路径
改用.float()使用cpu
已安装[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的代码,即
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中的
from .quantization import quantize
if self.quantized:
logger.info("Already quantized.")
return self修改为(参照chatglm):
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的这条语句
cpu_kernels = CPUKernel()后面加上所需的load_cpu_kernel函数
cpu_kernels = CPUKernel()
def load_cpu_kernel(**kwargs):
global cpu_kernels
cpu_kernels = CPUKernel(**kwargs)OK,到这里就可以运行了。
Additional context
省流:
编译正确的so文件: 使用[项目](https://github.com/skeeto/w64devkit/releases)中的gcc编译c文件生成so文件
加载正确的so文件:
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")修改
modeling_chatglm.py的函数quantize中的from .quantization import quantize if self.quantized: logger.info("Already quantized.") return self为:
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的这条语句cpu_kernels = CPUKernel()后面加上
load_cpu_kernel函数cpu_kernels = CPUKernel() def load_cpu_kernel(**kwargs): global cpu_kernels cpu_kernels = CPUKernel(**kwargs)
Source: zai-org/ChatGLM2-6B