一、简介

文本采用待量化模型自己生成数据集的方式,使用llmcompressor对智谱公司的GLM-4-9b-chat-hf模型进行AWQ量化。

二、环境安装

conda activate meeting

pip install vllm llmcompressor --index-url https://pypi.org/simple/
pip install transformers accelerate datasets safetensors --index-url https://pypi.org/simple/

三、准备数据集

import os
import json
import torch
from datasets import Dataset, DatasetDict
from transformers import AutoModelForCausalLM, AutoTokenizer
from tqdm import tqdm
import argparse

# ======================
# 配置参数
# ======================
def parse_args():
    parser = argparse.ArgumentParser(description="生成GLM-4-9b-chat-hf问答数据集")
    parser.add_argument("--model_path", type=str, default="/data2/model/zhipu/glm-4-9b-chat-hf",
                       help="原始模型路径")
    parser.add_argument("--output_dir", type=str, default="./glm-4-9b-chat-dataset",
                       help="数据集保存路径")
    parser.add_argument("--num_samples", type=int, default=128,
                       help="生成的样本数量")
    parser.add_argument("--max_new_tokens", type=int, default=200,
                       help="生成回答的最大token数")
    parser.add_argument("--temperature", type=float, default=0.7,
                       help="生成温度")
    parser.add_argument("--top_p", type=float, default=0.9,
                       help="top-p采样参数")
    parser.add_argument("--batch_size", type=int, default=8,
                       help="批量生成的大小")
    return parser.parse_args()

# ======================
# 问题列表
# ======================
QUESTION_SETS = {
    "general_knowledge": [
        "你是谁?",
        "中华人民共和国成立是哪一天?",
        "什么是人工智能?",
        "解释一下机器学习的基本概念",
        "Python是什么编程语言?",
        "太阳系有哪些行星?",
        "水的化学式是什么?",
        "中国的首都是哪里?",
        "什么是深度学习?",
        "解释一下牛顿第一定律",
        "如何保持健康的生活方式?",
        "什么是气候变化?",
        "解释一下光合作用的过程",
        "什么是区块链技术?",
        "中国的四大发明是什么?",
        "什么是量子计算?",
        "如何学习编程?",
        "什么是可再生能源?",
        "解释一下相对论的基本概念",
        "什么是大数据?"
    ],
    
    "science_tech": [
        "什么是神经网络?",
        "解释一下Transformer架构",
        "什么是自然语言处理?",
        "量子力学的基本原理是什么?",
        "什么是基因编辑技术?",
        "解释一下5G技术",
        "什么是物联网?",
        "人工智能有哪些应用场景?",
        "什么是虚拟现实?",
        "解释一下云计算",
        "什么是边缘计算?",
        "自动驾驶汽车是如何工作的?",
        "什么是机器学习中的过拟合?",
        "解释一下梯度下降算法",
        "什么是GPT模型?",
        "什么是强化学习?",
        "什么是计算机视觉?",
        "解释一下注意力机制",
        "什么是大语言模型?",
        "如何评估机器学习模型的性能?"
    ],
    
    "history_culture": [
        "中国历史上第一个封建王朝是哪个?",
        "文艺复兴时期有哪些重要人物?",
        "解释一下工业革命的影响",
        "中国古代的丝绸之路是什么?",
        "什么是唐宋八大家?",
        "解释一下儒家思想的核心",
        "第二次世界大战的主要事件有哪些?",
        "中国的春节有哪些传统习俗?",
        "什么是古希腊哲学?",
        "解释一下启蒙运动",
        "中国的长城是什么时候修建的?",
        "什么是佛教的基本教义?",
        "解释一下法国大革命",
        "中国古代的科举制度是什么?",
        "什么是印象派艺术?",
        "解释一下美国独立战争",
        "中国的茶文化有哪些特点?",
        "什么是罗马帝国的兴衰?",
        "解释一下中世纪欧洲的社会结构",
        "什么是中国的京剧?"
    ],
    
    "daily_life": [
        "如何做西红柿炒鸡蛋?",
        "早上如何快速高效地开始一天?",
        "如何提高学习效率?",
        "什么是健康饮食的基本原则?",
        "如何缓解工作压力?",
        "如何进行时间管理?",
        "如何开始健身锻炼?",
        "什么是有效的沟通技巧?",
        "如何养成良好的阅读习惯?",
        "如何规划个人财务?",
        "如何改善睡眠质量?",
        "什么是积极的心态?",
        "如何处理人际关系中的冲突?",
        "如何进行职业规划?",
        "什么是环保的生活方式?",
        "如何学习一门外语?",
        "如何培养创造力?",
        "什么是有效的学习方法?",
        "如何进行情绪管理?",
        "如何准备一次旅行?"
    ],
    
    "philosophy_ethics": [
        "什么是道德?",
        "解释一下功利主义",
        "什么是自由意志?",
        "什么是存在主义?",
        "解释一下康德的道德哲学",
        "什么是社会契约论?",
        "什么是幸福?如何获得幸福?",
        "解释一下柏拉图的理论",
        "什么是公平正义?",
        "什么是人工智能伦理?",
        "什么是环境保护的伦理基础?",
        "解释一下亚里士多德的伦理学",
        "什么是个人与集体的关系?",
        "什么是真理?",
        "解释一下道家思想",
        "什么是人生的意义?",
        "什么是权力与责任?",
        "解释一下尼采的哲学",
        "什么是美德伦理学?",
        "什么是科学与伦理的关系?"
    ]
}

# ======================
# 生成回答
# ======================
def generate_answer(model, tokenizer, question, generation_config):
    """生成单个问题的回答"""
    messages = [{"role": "user", "content": question}]
    
    # 使用chat_template格式化输入
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    
    inputs = tokenizer(text, return_tensors="pt").to(model.device)
    
    with torch.no_grad():
        outputs = model.generate(
            **inputs,
            **generation_config
        )
    
    # 解码输出,跳过输入部分
    full_output = tokenizer.decode(outputs[0], skip_special_tokens=True)
    # 提取生成的回答部分
    answer = full_output[len(tokenizer.decode(inputs['input_ids'][0], skip_special_tokens=True)):].strip()
    
    return answer

def batch_generate_answers(model, tokenizer, questions, generation_config, batch_size=8):
    """批量生成回答"""
    answers = []
    
    for i in tqdm(range(0, len(questions), batch_size), desc="生成回答"):
        batch_questions = questions[i:i+batch_size]
        batch_answers = []
        
        for question in batch_questions:
            answer = generate_answer(model, tokenizer, question, generation_config)
            batch_answers.append(answer)
        
        answers.extend(batch_answers)
    
    return answers

# ======================
# 创建数据集
# ======================
def create_dataset(args):
    """创建问答数据集"""
    
    print("="*60)
    print(f"加载模型: {args.model_path}")
    print("="*60)
    
    # 加载模型和tokenizer
    model = AutoModelForCausalLM.from_pretrained(
        args.model_path,
        dtype=torch.float16,
        device_map="auto",
        trust_remote_code=True
    )
    
    tokenizer = AutoTokenizer.from_pretrained(
        args.model_path,
        trust_remote_code=True
    )
    
    # 确保pad_token设置正确
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token
    
    # 设置生成参数
    generation_config = {
        "max_new_tokens": args.max_new_tokens,
        "temperature": args.temperature,
        "do_sample": True,
        "top_p": args.top_p,
        "pad_token_id": tokenizer.pad_token_id,
        "eos_token_id": tokenizer.eos_token_id,
    }
    
    # 准备问题列表
    all_questions = []
    for category, questions in QUESTION_SETS.items():
        all_questions.extend(questions)
    
    # 如果问题数量不够,重复使用
    if len(all_questions) < args.num_samples:
        print(f"问题库只有{len(all_questions)}个,需要{args.num_samples}个,将重复使用问题...")
        repeated_questions = []
        while len(repeated_questions) < args.num_samples:
            repeated_questions.extend(all_questions)
        all_questions = repeated_questions[:args.num_samples]
    else:
        # 随机选择指定数量的样本
        import random
        random.seed(42)
        all_questions = random.sample(all_questions, args.num_samples)
    
    print(f"总共使用 {len(all_questions)} 个问题")
    
    # 批量生成回答
    answers = batch_generate_answers(
        model, 
        tokenizer, 
        all_questions, 
        generation_config,
        batch_size=args.batch_size
    )
    
    # 构建数据集样本
    samples = []
    for i, (question, answer) in enumerate(zip(all_questions, answers)):
        sample = {
            "messages": [
                {"role": "user", "content": question},
                {"role": "assistant", "content": answer}
            ]
        }
        samples.append(sample)
        
        # 打印前几个样本
        if i < 3:
            print(f"\n样本 {i+1}:")
            print(f"问题: {question}")
            print(f"回答: {answer[:100]}...")
    
    # 创建数据集
    dataset = Dataset.from_list(samples)
    
    # 创建DatasetDict(可选)
    dataset_dict = DatasetDict({
        "train": dataset
    })
    
    # 保存数据集
    os.makedirs(args.output_dir, exist_ok=True)
    
    # 保存为多种格式
    dataset.save_to_disk(args.output_dir)
    
    # 保存为JSON文件(便于查看)
    json_path = os.path.join(args.output_dir, "dataset.json")
    with open(json_path, 'w', encoding='utf-8') as f:
        json_data = []
        for sample in samples:
            json_data.append(sample)
        json.dump(json_data, f, ensure_ascii=False, indent=2)
    
    # 保存为parquet格式
    parquet_path = os.path.join(args.output_dir, "dataset.parquet")
    dataset.to_parquet(parquet_path)
    
    print("\n" + "="*60)
    print("数据集生成完成!")
    print(f"样本数量: {len(dataset)}")
    print(f"保存路径: {args.output_dir}")
    print("="*60)
    
    # 验证数据集可以正常加载
    print("\n验证数据集加载...")
    try:
        from datasets import load_from_disk
        loaded_dataset = load_from_disk(args.output_dir)
        print(f"成功加载数据集,包含 {len(loaded_dataset)} 个样本")
        print(f"第一个样本: {loaded_dataset[0]}")
    except Exception as e:
        print(f"加载数据集时出错: {e}")
    
    # 清理内存
    del model
    torch.cuda.empty_cache()
    
    return dataset

# ======================
# 使用示例
# ======================
def main():
    args = parse_args()
    
    print("GLM-4-9b-chat-hf问答数据集生成器")
    print(f"模型路径: {args.model_path}")
    print(f"输出目录: {args.output_dir}")
    print(f"样本数量: {args.num_samples}")
    print(f"批量大小: {args.batch_size}")
    
    dataset = create_dataset(args)
    
    # 生成使用说明
    readme_content = f"""
# GLM-4-9b-chat-hf 问答数据集

## 基本信息
- 模型: {args.model_path}
- 样本数量: {args.num_samples}
- 生成时间: {os.path.basename(args.output_dir)}
- 格式: HuggingFace Dataset

## 数据集结构
每个样本的格式为:
```json
{{
  "messages": [
    {{"role": "user", "content": "问题文本"}},
    {{"role": "assistant", "content": "回答文本"}}
  ]
}}
"""


if __name__ == "__main__":
    main()

执行:

python generate_dataset.py \
  --model_path /data2/model/zhipu/glm-4-9b-chat-hf \
  --output_dir ./glm-4-9b-chat-dataset \
  --num_samples 128

执行完后,就会在当前目录下面多出一个glm-4-9b-chat-dataset目录,里面就是生成的数据集。

五、AWQ量化

import os
import torch
import numpy as np
from datasets import load_from_disk

from transformers import AutoModelForCausalLM, AutoTokenizer
from llmcompressor import oneshot
from llmcompressor.modifiers.awq import AWQModifier, AWQMapping

# ======================
# 1. 内存优化设置
# ======================
os.environ["PYTORCH_ALLOC_CONF"] = "expandable_segments:True"


# ======================
# 2. 模型配置
# ======================
MODEL_PATH = "/data2/model/zhipu/glm-4-9b-chat-hf"
SAVE_DIR = "./glm-4-9b-chat-awq-int4-self"

# ======================
# 3. 加载模型
# ======================
print(f"Loading model from {MODEL_PATH} ...")
torch.cuda.empty_cache()

model = AutoModelForCausalLM.from_pretrained(
    MODEL_PATH,
    dtype=torch.float16,
    device_map="auto",
    low_cpu_mem_usage=True,
    max_memory={0: "20GB"}
)

tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)

# ======================
# 4. 创建校准数据集
# ======================
NUM_CALIBRATION_SAMPLES = 128
MAX_SEQUENCE_LENGTH = 512


print("Loading calibration dataset...")
ds = load_from_disk("./glm-4-9b-chat-dataset")
ds = ds.shuffle(seed=42)


print("********************************")
print("原始数据样本结构:")
print(ds[0])

# 将聊天数据转成纯文本,使用 chat_template
def preprocess(example):
    return {
        "text": tokenizer.apply_chat_template(
            example["messages"],
            tokenize=False,
        )
    }

ds = ds.map(preprocess)
print("================================")
print("预处理后的数据样本:")
print(ds[0])
print("********************************")

# 测试 chat_template 转换
test_example = {
    "messages": [
        {"role": "user", "content": "What's AI?"},
        {"role": "assistant", "content": "AI means Artificial Intelligence."},
        {"role": "user", "content": "Give examples."},
        {"role": "assistant", "content": "Examples: self-driving cars, chatbots."}
    ]
}

result = preprocess(test_example)
print("测试 chat_template 转换:")
print(result)
#输出: {'text': "[gMASK]<sop><|user|>\nWhat's AI?<|assistant|>\nAI means Artificial Intelligence.<|user|>\nGive examples.<|assistant|>\nExamples: self-driving cars, chatbots."}
print("********************************")

# ======================
# 5. 配置AWQ量化
# ======================
print("Configuring AWQ quantization...")

recipe = [
    AWQModifier(
        scheme="W4A16",
        targets=["Linear"],
        ignore=["lm_head", "re:.*mlp\.gate_up_proj$", "re:.*mlp\.down_proj$"],
        mappings=[
            AWQMapping(
                smooth_layer='re:.*layers\.\d+\.input_layernorm$',
                balance_layers=[
                    're:.*layers\.\d+\.self_attn\.q_proj$',
                    're:.*layers\.\d+\.self_attn\.k_proj$',
                    're:.*layers\.\d+\.self_attn\.v_proj$'
                ]
            ),
        ]
    ),
]

# ======================
# 6. 运行oneshot量化
# ======================
if __name__ == "__main__":
    print("\n" + "="*60)
    print("Starting AWQ Quantization")
    print("="*60)
    print(f"Model: {MODEL_PATH}")
    print(f"Output: {SAVE_DIR}")
    print(f"Calibration samples: {len(ds)}")
    print(f"Sequence length: {MAX_SEQUENCE_LENGTH}")
    print("="*60)
    
    torch.cuda.empty_cache()
    
    try:
        oneshot(
            model=model,
            dataset=ds,
            recipe=recipe,
            max_seq_length=MAX_SEQUENCE_LENGTH,
            num_calibration_samples=len(ds),
            output_dir=SAVE_DIR,
        )
        
        print("\n✅ AWQ量化成功!")
        print(f"模型已保存到: {SAVE_DIR}")
        
    except Exception as e:
        print(f"\n❌ AWQ量化失败: {e}")
        print("\n建议:")
        print("1. 减少NUM_CALIBRATION_SAMPLES到64")
        print("2. 减少MAX_SEQUENCE_LENGTH到256")
        print("3. 确保GPU有足够内存")
        exit(1)
    
    # ======================
    # 7. 测试量化模型(修复版本)
    # ======================
    print("\n" + "="*60)
    print("Testing Quantized Model")
    print("="*60)
    
    # 清理内存
    del model
    torch.cuda.empty_cache()
    
    # 重新加载量化模型
    print(f"Loading quantized model from {SAVE_DIR} ...")
    try:
        quantized_model = AutoModelForCausalLM.from_pretrained(
            SAVE_DIR,
            device_map="auto",
            trust_remote_code=True,
            torch_dtype=torch.float16,
        )
        
        # 设置生成参数,避免attention mask警告
        generation_config = {
            "max_new_tokens": 100,
            "temperature": 0.7,
            "do_sample": True,
            "top_p": 0.9,
            "pad_token_id": tokenizer.pad_token_id,
            "eos_token_id": tokenizer.eos_token_id,
        }
        
        # 测试多个问题
        test_questions = [
            "中华人民共和国成立日期是哪一天?",
            "什么是机器学习?"
        ]
        
        for i, question in enumerate(test_questions):
            print(f"\n[问题 {i+1}]: {question}")
            
            # 编码输入,包括attention_mask
            inputs = tokenizer(
                question, 
                return_tensors="pt",
                padding=True,
                truncation=True,
                max_length=512
            ).to(quantized_model.device)
            
            with torch.no_grad():
                output = quantized_model.generate(
                    **inputs,
                    **generation_config
                )
            
            # 解码输出,跳过输入部分
            full_output = tokenizer.decode(output[0], skip_special_tokens=True)
            # 提取生成的回答部分
            answer = full_output[len(question):].strip()
            print(f"[回答]: {answer}")
        
        print("\n" + "="*60)
        print("✅ 量化完成!模型已成功保存并通过测试")
        print("="*60)
        
        # ======================
        # 8. 额外:检查模型大小和性能
        # ======================
        print("\n" + "="*60)
        print("模型信息")
        print("="*60)
        
        import os
        import glob
        
        # 检查量化模型大小
        model_files = glob.glob(os.path.join(SAVE_DIR, "*.safetensors")) + \
                     glob.glob(os.path.join(SAVE_DIR, "*.bin"))
        
        total_size = sum(os.path.getsize(f) for f in model_files) / (1024**3)
        print(f"量化模型大小: {total_size:.2f} GB")
        
        # 原始模型大小(估计)
        print(f"原始模型大小(估计): ~18.0 GB (FP16)")
        print(f"压缩比例: {(1 - total_size/18.0)*100:.1f}%")
        
        # 检查量化后的参数
        print("\n量化后模型参数示例:")
        for name, param in quantized_model.named_parameters():
            if "q_proj" in name and "weight" in name:
                print(f"{name}: {param.shape}, dtype: {param.dtype}")
                break
        
    except Exception as e:
        print(f"\n❌ 测试量化模型失败: {e}")
        import traceback
        traceback.print_exc()
        print("量化模型可能已保存,但加载或测试时出错")

执行:

CUDA_VISIBLE_DEVICES=0,1 python awq_self_datasets_quantization.py

代码里面要注意的地方是:数据集要用apply_chat_template生成模型适配的格式,比如:{‘text’: “[gMASK]<|user|>\nWhat’s AI?<|assistant|>\nAI means Artificial Intelligence.<|user|>\nGive examples.<|assistant|>\nExamples: self-driving cars, chatbots.”},不同模型生成的格式可能会不一样。还有一点,oneshot函数,有一个参数output_dir直接就可以指定量化后模型的保存路径了。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐