- 大模型
- NLP
- 基础模型
- 模型评测
- 模型量化
【免费下载链接】GLM-130B
GLM-130B: An Open Bilingual Pre-Trained Model (ICLR 2023)
导读
本文以 docs/quantization.md 为核心,完整讲解 GLM-130B(130B 参数开源双语预训练模型)的量化方案:如何在几乎无损的前提下把模型权重压缩到 INT8 甚至 INT4,从而把推理硬件门槛从 8 × A100(40G) 降到 4 × RTX 3090(24G) 的单一服务器。读者将掌握量化原理(W8A16 权重量化、向量级对称量化)、checkpoint 张量并行维度转换、INT4/INT8 配置文件切换、量化 checkpoint 生成与加载,以及量化层的底层 CUDA 实现与性能基准。
一、为什么 GLM-130B 需要量化
GLM-130B 拥有 130B 参数,全精度 FP16 推理通常需要 8 × A100(40G) 级别的服务器。为降低硬件门槛,官方于 2022.08.24 发布了量化版本:保持激活精度为 FP16,仅将模型权重量化到最低 INT4,性能几乎无损失,从而将硬件需求降低到单台 4 × RTX 3090(24G) 服务器(见 README.md 的 News 与 Getting Started 章节)。
仓库为不同精度提供了独立的模型配置(见 configs 目录):
| 硬件 | GPU 显存 | 量化 | 权重卸载 |
|---|---|---|---|
| 8 × A100 | 40 GB | 无 | 无 |
| 8 × V100 | 32 GB | 无 | 有(BMInf) |
| 8 × V100 | 32 GB | INT8 | 无 |
| 8 × RTX 3090 | 24 GB | INT8 | 无 |
| 4 × RTX 3090 | 24 GB | INT4 | 无 |
| 8 × RTX 2080 Ti | 11 GB | INT4 | 无 |
二、量化方案设计:W8A16(只量化权重,激活保持 FP16)
2.1 激活异常值(Outliers)带来的困境
典型的量化方法会同时把模型权重和激活量化为 INT8,从而使用高效的 INT8 矩阵乘内核。但 GLM-130B 的激活值中存在异常值(outlier),使得降低激活精度变得困难——如上图所示,激活张量中存在少量数值极大的特征维度。
值得注意的是,Meta AI(LLM.int8(),见 arXiv:2208.07339)同期也发现大型 Transformer(>6.8B)存在这种 "emergent outliers" 现象,与 GLM-130B 的观测一致。他们的深入分析表明,异常值仅约占全部特征维度的 0.1%,因此可以对矩阵乘做分解:对包含异常值的少数维度用高精度乘法,其余维度用低精度乘法。
2.2 GLU 结构放大了异常值问题
GLM-130B 的情况更特殊:由于使用了GLU(门控线性单元)作为 FFN 变体,激活异常值有时最多可占特征维度的30%,这使得"混合精度分解"的矩阵乘效率远低于单个 FP16 矩阵乘。
经过反复试验,官方最终决策是:
- 激活精度保留 FP16,只对模型权重进行量化(即 W8A16/W4A16 方案);
- 量化后的权重在运行时动态转换回 FP16参与计算,引入少量计算开销,但大幅降低存储权重的显存需求。
2.3 量化的对象与方式
- 量化对象:所有线性层(占模型参数的大部分);除输入/输出 embedding、LayerNorm 和 bias 之外的模型权重。
- 量化方式:向量级(vector-wise)对称量化,即按权重矩阵的每一行(每个输出通道)计算一个量化 scale。
- INT4 存储压缩:INT4 精度下,两个 INT4 权重被打包进一个 INT8 权重以节省显存,最终INT4 模型权重仅需约 70GB GPU 显存。
三、快速上手:三步完成量化推理
3.1 环境要求
- SwissArmyTransformer >= 0.2.11(量化功能必需);
- Python 3.9+ / CUDA 11+ / PyTorch 1.10+ / DeepSpeed 0.6+,并安装带 CUDA 与 C++ 扩展的 Apex,依赖见 requirements.txt。
3.2 设置 CHECKPOINT_PATH 与 CPU 内存要求
把CHECKPOINT_PATH设置为你本地的 checkpoint 文件夹(对应configs/model_glm_130b_{int4/int8}.sh)。加载流程是:
模型首先在 CPU 内存中从 FP16 checkpoint 初始化,然后被动态量化并转移到 GPU 内存。
因此请确保你有足够的 CPU 内存(> 260GB)来存放 FP16 模型权重。
3.3 张量并行维度转换(关键步骤)
仓库官方分发的 checkpoint 是8 路张量并行(8-way tensor parallel),即 8 张 GPU 共同存储一个完整模型。若你需要在更少的 GPU 上推理(例如 4 × RTX 3090 跑 INT4),必须先把 checkpoint 转换为 4 路张量并行,并同步修改配置文件中的MP_SIZE:
python tools/convert_tp.py \ --input-folder <SRC_CKPT_PATH> \ --output-folder <DST_CKPT_PATH> \ --target-tp 4tools/convert_tp.py除转换并行度外,还支持在转换时直接产出量化权重(见下文 3.5 与第五节)。
3.4 切换配置文件并运行脚本
将脚本(如 scripts/generate.sh)中的模型配置从configs/model_glm_130b.sh换成对应的量化配置,然后照常运行:
bash scripts/generate.sh --input-source interactivegenerate.sh通过source "${main_dir}/configs/model_glm_130b.sh"引入模型配置,并把MODEL_ARGS传给 generate.py,再以torchrun --nproc_per_node $MP_SIZE启动。所以更换配置文件的本质是改变MODEL_ARGS中的--quantization-bit-width、--from-quantized-checkpoint与MP_SIZE。
3.5 量化 checkpoint 的生成与加载
- 默认行为:加载全精度(FP16)checkpoint,运行时动态量化。
- 生成量化权重:运行转换脚本并指定量化位宽,即可产出量化后的模型权重:
python tools/convert_tp.py \ --input-folder <SRC_CKPT_PATH> \ --output-folder <DST_CKPT_PATH> \ --target-tp <TARGET_TP> \ --quantization-bit-width 4 # 或 8- 从量化 checkpoint 加载:在模型配置文件中添加
--from-quantized-checkpoint,同时必须保留--quantization-bit-width <4 或 8>以告知解压逻辑。
四、INT4 / INT8 配置文件深度解析
三个配置文件的差异集中在MP_SIZE与量化参数上:
| 配置项 | FP16(model_glm_130b.sh) | INT8(model_glm_130b_int8.sh) | INT4(model_glm_130b_int4.sh) |
|---|---|---|---|
MP_SIZE(张量并行度) | 8 | 8 | 4 |
--quantization-bit-width | 无 | 8 | 4 |
--from-quantized-checkpoint | 无 | 可选(加载量化权重时添加) | 可选(加载量化权重时添加) |
三者共享的模型结构参数(来自configs/model_glm_130b_int4.sh):
MODEL_TYPE="glm-130b" CHECKPOINT_PATH="<your checkpoint path>" MP_SIZE=4 MODEL_ARGS="--model-parallel-size ${MP_SIZE} \ --num-layers 70 \ --hidden-size 12288 \ --inner-hidden-size 32768 \ --vocab-size 150528 \ --num-attention-heads 96 \ --max-sequence-length 2048 \ --tokenizer-type icetk-glm-130B \ --layernorm-order post \ --quantization-bit-width 4 \ --load ${CHECKPOINT_PATH} \ --skip-init \ --fp16"参数要点:
--model-parallel-size ${MP_SIZE}:张量并行度,必须与 checkpoint 的实际切分方式一致(用convert_tp.py转换后同步修改);--quantization-bit-width:取值4 或 8,定义量化位宽;--load ${CHECKPOINT_PATH}:指向 checkpoint 目录;--skip-init跳过随机初始化,--fp16使用半精度计算;--from-quantized-checkpoint:加载量化权重时必须添加(见 initialize.py)。
scripts/generate.sh、scripts/evaluate.sh、scripts/benchmark.sh均通过 source 对应配置文件获取MODEL_ARGS,因此只需修改配置文件即可全局切换精度模式。
五、源码级实现原理
5.1 初始化与量化时机(initialize.py)
initialize.py 的initialize_model_and_tokenizer是量化的核心调度点,按张量并行 rank 逐个初始化:
model = GLM130B(args).half()创建模型;- 若
args.from_quantized_checkpoint:先断言quantization_bit_width已设置,然后调用quantize(model, ...)在加载 checkpoint 之前将模型结构替换为量化层(initialize.py); load_checkpoint(model, args)加载(量化)权重;- 若设置了
--quantization-bit-width但不是从量化 checkpoint 加载,则在加载后调用quantize(model, ...)动态量化(initialize.py); - 最后把模型转移到 GPU。
量化参数由add_quantization_args注册(initialize.py):--quantization-bit-width(默认None)与--from-quantized-checkpoint。
5.2 层替换(quantization/init.py)
quantize(model, weight_bit_width)遍历model.transformer.layers的每一层,把四个权重占比最大的线性层替换为量化版本(quantization/init.py):
| 原层 | 替换为 | 类型 |
|---|---|---|
attention.query_key_value | QuantizedColumnParallelLinear | 列并行 |
attention.dense | QuantizedRowParallelLinear | 行并行 |
mlp.dense_h_to_4h | QuantizedColumnParallelLinear | 列并行 |
mlp.dense_4h_to_h | QuantizedRowParallelLinear | 行并行 |
这与 tools/convert_tp.py 中QUANTIZED_LAYERS列表完全一致——四个权重张量即"被量化的线性层"。
5.3 量化存储与对称 scale(quantization/layers.py)
QuantizedColumnParallelLinear/QuantizedRowParallelLinear(quantization/layers.py)的实现要点:
权重以
torch.int8存储,张量形状变为shape[1] * weight_bit_width // 8(INT4 时宽度减半,实现两个 INT4 打包进一个 INT8,对应文档中"70GB 显存"的结论);向量级对称量化:按行计算 scale:
self.weight_scale = (weight.abs().max(dim=-1).values / ((2 ** (weight_bit_width - 1)) - 1)).half() self.weight = torch.round(weight / self.weight_scale[:, None]).to(torch.int8)即每行以最大绝对值除以位宽上限(INT4 为 7,INT8 为 127)得到缩放因子,再将权重四舍五入到整数区间;
INT4 时调用
compress_int4_weight(self.weight)压缩打包;weight与weight_scale都注册为requires_grad=False的Parameter;前向计算通过
W8A16Linear.apply(...)完成(quantization/layers.py),并保持 SAT 的张量并行通信语义(copy_to_model_parallel_region、gather_from_model_parallel_region、reduce_from_model_parallel_region等)。
5.4 运行时解压与矩阵乘(quantization/functional.py)
W8A16Linear(quantization/functional.py)的前向过程体现了"动态转换回 FP16"的设计:
- 把输入 reshape 为 2D;
weight = extract_weight_to_half(quant_w, scale_w, weight_bit_width)在 GPU 上把量化权重解压回 FP16;output = inp.mm(weight.t())直接调用 FP16 矩阵乘(PyTorch 自带的高效内核)。
类还实现了backward,因此在有反向需求时(如继续微调实验)也能正常计算梯度。
5.5 CUDA 内核(cuda/quantization.cu 与 kernels/init.py)
解压与压缩的底层实现是手写 CUDA 内核(cuda/quantization.cu):
- INT4 压缩
int4WeightCompressionDevice:output[i] = (input[i * 2] << 4) | (input[i * 2 + 1] & 0b00001111),把连续两个 INT4 值拼进一个 INT8(cuda/quantization.cu); - INT4 解压
int4WeightExtractionDevice:high = original >> 4、low = original << 4; low >>= 4,拆出高 4 位与低 4 位并乘以该行的 scale(cuda/quantization.cu); - INT8 解压
int8WeightExtractionDevice:output[i] = T(weight[i]) * scale_list[blockIdx.x](cuda/quantization.cu); - 对外分别暴露
int4WeightExtractionHalf/Float、int8WeightExtractionHalf/Float与int4WeightCompression全局内核。
kernels/init.py 通过ctypes加载预编译的 kernels/quantization.fatbin,封装出两个可直接调用的函数:
compress_int4_weight(weight):调用int4WeightCompression,把(n, m)的 INT8 权重压缩为(n, m/2);extract_weight_to_half(weight, scale_list, source_bit_width):按位宽选择int4/int8WeightExtractionHalf内核,解压为(n, m * 8 // bit_width)的 FP16 张量。
预编译的 fatbin 通过 cuda/Makefile 用nvcc -fatbin生成,覆盖sm_61/sm_62/sm_70/sm_72/sm_75/sm_80/sm_86多代 GPU 架构(分别对应 RTX 2080 Ti、V100、A100、RTX 3090 等)。
六、评估结果:量化几乎无损
官方在 docs/quantization.md 中给出了 FP16 与量化版本在 5 个基准上的对比:
| 精度 | MMLU(Accuracy↑) | LAMBADA(Accuracy↑) | WikiText-2(PPL↓) | WikiText-103(PPL↓) | PTB(PPL↓) |
|---|---|---|---|---|---|
| FP16 | 44.751 | 80.206 | 10.901 | 10.759 | 18.964 |
| INT8 | 44.709 | 80.206 | 10.904 | 10.763 | 18.994 |
| INT4 | 44.801 | 79.468 | 11.167 | 11.046 | 19.535 |
可见 INT8 与 FP16 几乎完全一致;INT4 在 LAMBADA 与 PPL 上有极小波动,MMLU 甚至略高(44.801 vs 44.751)。这些结果可用仓库的 scripts/evaluate.sh 在对应任务 YAML(如 tasks/mmlu/mmlu.yaml、tasks/lambada/lambada.yaml)上复现。
七、空间与速度基准
7.1 SAT 推理基准
以下结果是使用 SAT(SwissArmyTransformer)测试的端到端生成耗时:
| 硬件 | GPU 显存 | 精度 | 512 | 1024 | 2048 |
|---|---|---|---|---|---|
| 8 × A100 | 40 GB | FP16 | 45.21 s | 89.00 s | 179.22 s |
| 8 × V100 | 32 GB | INT8 | 106.35 s | 216.50 s | 449.17 s |
| 4 × RTX 3090 | 24 GB | INT4 | 138.66 s | 292.69 s | 649.64 s |
| 8 × RTX 2080 Ti | 11 GB | INT4 | 117.39 s | 240.96 s | 528.66 s |
(表格列头 "512/1024/2048" 对应生成序列长度;耗时单位为秒。)
7.2 FasterTransformer 加速基准
使用 NVIDIA FasterTransformer 可提速2 倍以上,详细用法见 Inference with FasterTransformer。Encode / Decode 耗时(毫秒 / 秒):
| 硬件 | GPU 显存 | 精度 | 128 Encode / Decode | 512 Encode / Decode | 1024 Encode / Decode | 2048 Encode / Decode |
|---|---|---|---|---|---|---|
| 8 × A100 | 40 GB | INT4 | 145 ms / 4.29 s | 183 ms / 17.7 s | 313 ms / 37.8 s | 495 ms / 86.0 s |
| 4 × A100 | 80 GB | INT4 | 174 ms / 6.62 s | 272 ms / 27.1 s | 439 ms / 56.2 s | 810 ms / 123 s |
| 8 × V100 | 32 GB | INT4 | 309 ms / 6.97 s | 666 ms / 28.1 s | 1208 ms / 58.4 s | 2304 ms / 125 s |
| 4 × V100 | 32 GB | INT4 | 448 ms / 11.4 s | 843 ms / 45.87 s | 1488 ms / 93.5 s | 2803 ms / 196 s |
| 8 × RTX 3090 | 24 GB | INT4 | 283 ms / 5.07 s | 915 ms / 20.5 s | 1793 ms / 42.7 s | 3477 ms / 90.3 s |
| 4 × RTX 3090 | 24 GB | INT4 | 374 ms / 8.16 s | 1300 ms / 32.3 s | OOM / 66.5 s | OOM / 150 s |
| 8 × RTX 2080 Ti | 11 GB | INT4 | 392 ms / 6.77 s | 1044 ms / 27.29 s | OOM / 56.02 s | OOM / OOM |
从表可以看出,在 4 × RTX 3090(24GB)这类入门级配置上,INT4 + FasterTransformer 即可支撑 512~1024 token 级别的解码任务(OOM 出现在更长序列)。
八、注意事项与最佳实践
- CPU 内存是硬约束:无论 INT4 还是 INT8,启动时都要先在 CPU 上加载完整 FP16 checkpoint(>260GB),请使用足够内存的机器并优先把 checkpoint 放在 SSD 或内存盘上以缩短加载时间(参考 README.md)。
- 并行度必须匹配:官方 checkpoint 为 8 路张量并行;改用 4 卡必须先用
convert_tp.py --target-tp 4转换,并把配置中的MP_SIZE改为 4(对应 configs/model_glm_130b_int4.sh)。 --quantization-bit-width与--from-quantized-checkpoint配套使用:加载量化权重时两者缺一不可,否则要么加载失败、要么在运行时再次执行不必要的动态量化。- 量化范围:只有线性层权重(
attention.dense、attention.query_key_value、mlp.dense_h_to_4h、mlp.dense_4h_to_h)参与量化,embedding、LayerNorm、bias 保持 FP16;INT4 模式下的显存节省主要来自权重压缩打包。 - 追求更高吞吐:若延迟敏感,建议直接使用 FasterTransformer 推理路径(docs/inference-with-fastertransformer.md),其 INT4 权重可复用本仓库的转换产物。
相关文档
- docs/quantization.md:量化方案的原始说明与全部基准数据
- docs/inference-with-fastertransformer.md:FasterTransformer 2 倍以上加速推理
- docs/low-resource-inference.md:结合 BMInf 权重卸载的更小显存推理
- docs/evaluate-your-own-tasks.md:自定义评测任务
- configs/model_glm_130b_int4.sh / configs/model_glm_130b_int8.sh:INT4/INT8 配置文件
- tools/convert_tp.py:张量并行转换与量化权重生成脚本
- quantization/layers.py 与 quantization/functional.py:量化层实现
- cuda/quantization.cu 与 kernels/quantization.fatbin:压缩/解压 CUDA 内核
通过本文的配置与源码解析,你可以独立完成 GLM-130B 在 INT8/INT4 精度下的 checkpoint 转换、配置切换与推理部署,在近乎无损的模型质量下把显存开销降低数倍。
- 大模型
- NLP
- 基础模型
- 模型评测
- 模型量化
【免费下载链接】GLM-130B
GLM-130B: An Open Bilingual Pre-Trained Model (ICLR 2023)
相关推荐
vLLM INT8 W4A8 量化实战指南:用 LLM Compressor 实现 INT4 权重 + INT8 激活的模型压缩与高效推理
vLLM INT8 W4A8 量化实战指南:用 LLM Compressor 实现 INT4 权重 + INT8 激活的模型压缩与高效推理 vLLM 支持将模型
人工智能大模型模型推理服务推理引擎本地部署bitsandbytes 量化格式全指南:INT8、NF4、FP4 与双重量化的原理、配置与实战
bitsandbytes 量化格式全指南:INT8、NF4、FP4 与双重量化的原理、配置与实战 本篇技术指南以 bitsandbytes 的量化格式为核心,系
AI 技能人工智能大模型深度学习基于 Transformers 的 XVERSE-7B-Chat 推理与 INT4/INT8 量化实战指南
基于 Transformers 的 XVERSE 7B Chat 推理与 INT4/INT8 量化实战指南 XVERSE 7B Chat 是深圳元象科技开源的
大模型人工智能教程本地部署微调
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考