EXPERIMENT REPORT

实验三:量化精度损失与算子一致性校验

2025-12-20 10 MIN READ

1. 实验三:基础实验 (硅后功能验证)

注:

  • 本实验所有机器均属于 AutoDL 平台,建议选择 RTX 4090 作为 Golden Reference,或使用 摩尔线程 MTT S4000/S5000 (如有) 进行对比测试。
  • Happy Path:标准量化模型的跑分测试。
  • Sad Path:检测算子在极值下的数值稳定性。

1.1 第一阶段:环境准备与工具链安装

目标:配置用于评估模型精度的 lm-evaluation-harness 和量化推理后端 AutoGPTQ / vLLM

  1. 环境认知: 在 GPU 研发内部,我们称之为 Post-Silicon Validation。很多人以为 GPU 只要能亮机就行,但对于 AI 芯片,「算得快」是能力,「算得准」才是底线

  2. 打开终端 (Terminal): 在 JupyterLab 页面底部点击“终端”图标。

  3. 安装依赖库

    我们需要安装评估框架和量化库。注意:如果是国产卡环境,需替换为厂商提供的 musa-vllmtorch_musa

    hljs bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 基础依赖
    pip install --upgrade pip
    pip install torch transformers accelerate modelscope
    
    # 安装评估工具 lm-eval
    pip install lm_eval==0.4.1
    
    # 安装量化后端 (以 AutoGPTQ 为例,N卡环境)
    pip install auto-gptq optimum
    

1.2 第二阶段:模型准备 (Golden Baseline vs Quantized)

目标:准备“标尺”(BF16 原版模型)和“测试对象”(Int4 量化模型)。

  1. 创建下载脚本

    hljs bash
    1
    touch download_eval_models.py
    
  2. 编辑下载代码

    我们需要同时下载 BF16 和 Int4 版本的 Qwen2.5-7B。

    hljs python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    from modelscope import snapshot_download
    
    # 下载 BF16 原版 (作为 Golden Reference)
    model_dir_bf16 = snapshot_download('qwen/Qwen2.5-7B-Instruct', cache_dir='/root/autodl-tmp')
    print(f"BF16 Model: {model_dir_bf16}")
    
    # 下载 Int4 量化版 (作为 Device Under Test)
    model_dir_int4 = snapshot_download('Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4', cache_dir='/root/autodl-tmp')
    print(f"Int4 Model: {model_dir_int4}")
    
  3. 执行下载

    hljs bash
    1
    python download_eval_models.py
    

1.3 第三阶段:构建评估脚本 (The Experiment)

目标:运行 GSM8K(数学推理)基准测试,量化精度损失。

  1. 创建主实验脚本

    hljs bash
    1
    touch run_accuracy_test.sh
    
  2. 编写代码

    这里直接调用 lm_eval 命令行工具。这是行业内验证 Tensor Core 计算单元正确性的黄金标准。

    hljs bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #!/bin/bash
    
    # 1. 评估 BF16 基准 (Expected: ~75-80 acc)
    echo "Starting BF16 Baseline Evaluation..."
    lm_eval --model hf \
        --model_args pretrained=/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct,dtype=bfloat16,trust_remote_code=True \
        --tasks gsm8k \
        --batch_size auto \
        --output_path results_bf16.json
    
    # 2. 评估 Int4 量化版 (Expected: Drop < 5%)
    echo "Starting Int4 Quantization Evaluation..."
    lm_eval --model hf \
        --model_args pretrained=/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct-GPTQ-Int4,autogptq=True,gptq_use_triton=True,trust_remote_code=True \
        --tasks gsm8k \
        --batch_size auto \
        --output_path results_int4.json
    

    老鸟注gptq_use_triton=True 是关键。在 N 卡上它调用 Triton 算子;在 MUSA 架构上,我们需要确认是否调用了针对 Int4 优化的 muDNN 算子,否则会回退到通用计算,速度极慢。


1.4 第四阶段:执行与观测

  1. 开启系统监控: 新建终端,监控显存带宽和计算利用率。

    hljs bash
    1
    2
    # N卡用 nvidia-smi,摩尔线程卡用 musa-smi
    watch -n 1 nvidia-smi
    
  2. 运行实验

    hljs bash
    1
    bash run_accuracy_test.sh
    
  3. 实验结果示例

    hljs output
    1
    2
    3
    4
    5
    |    Tasks    |Version|  Filter  |n-shot|  Metric  |   |Value |   |Stderr|
    |-------------|-------|----------|-----:|----------|---|-----:|---|-----:|
    |gsm8k        |      3|flexible-extract|     5|exact_match|↑  |0.7852|±  |0.0113|
    
    (BF16 得分约 78.5,若 Int4 低于 74.0,说明量化算子实现有问题)
    

1.5 第五阶段:实验结果分析指南

关键指标判定标准

  1. Accuracy / Exact Match:Int4 相对 BF16 下降幅度应 < 5%。如果下降超过 10%,通常不是模型的问题,而是硬件的 Int4 累加器(Accumulator)精度不足。
  2. Kernel Latency:观察 nvidia-smimusa-smi 的 Compute Util。量化推理时,核心利用率应极高。如果显存带宽没跑满但计算也没跑满,说明指令发射效率低,卡在了 Instruction Bound

2. 实验三:进阶实验 (Sad Path & Limit Testing)

既然要验证硬件的“良心”,就不能只看总分。我们需要拆解到 Layer 级 的数值一致性。

2.1 进阶 1:Layer-wise Divergence Test (逐层发散检测)

  • 痛点:GSM8K 掉了 5 分,你不知道是第 1 层错了还是第 32 层错了。
  • 操作:喂入相同的 Prompt,冻结随机种子,对比 BF16 和 Int4 每一层输出 Tensor 的余弦相似度。
  • 观察
    • N 厂现象:余弦相似度通常保持在 0.99 以上。
    • 国产挑战:如果在某一层突然掉到 0.9 以下,说明该层的算子(如 LayerNorm 或 Softmax)在 MUSA 上的实现精度不足,或者 Int4 解包(Dequantize)逻辑有 Bug。

执行步骤

  1. 创建文件 layer_divergence.py

    目标:捕捉模型每一层的输出,计算 BF16 与 Int4 的距离。

    hljs python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    import torch
    from transformers import AutoModelForCausalLM, AutoTokenizer
    import numpy as np
    
    # 路径配置
    PATH_BF16 = "/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct"
    PATH_INT4 = "/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct-GPTQ-Int4"
    
    def get_last_token_logits(model_path, device="cuda"):
        print(f"Loading {model_path}...")
        # 注意:为了简化代码,这里只对比最终 Logits,实际工程需 Hook 中间层
        model = AutoModelForCausalLM.from_pretrained(model_path, device_map="auto", trust_remote_code=True)
        tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
        
        inputs = tokenizer("The answer to the ultimate question of life, the universe and everything is", return_tensors="pt").to(device)
        
        with torch.no_grad():
            outputs = model(**inputs)
        
        # 提取最后一个 token 的 logits
        return outputs.logits[0, -1, :].float().cpu()
    
    print("--- Starting Divergence Check ---")
    
    # 1. 获取 BF16 结果
    logits_bf16 = get_last_token_logits(PATH_BF16)
    
    # 2. 清理显存 (模拟真实硬件环境资源受限)
    torch.cuda.empty_cache()
    
    # 3. 获取 Int4 结果
    logits_int4 = get_last_token_logits(PATH_INT4)
    
    # 4. 计算余弦相似度
    cos_sim = torch.nn.functional.cosine_similarity(logits_bf16.unsqueeze(0), logits_int4.unsqueeze(0))
    mse_loss = torch.nn.functional.mse_loss(logits_bf16, logits_int4)
    
    print(f"\n[Result] Cosine Similarity: {cos_sim.item():.6f} (Should > 0.99)")
    print(f"[Result] MSE Loss: {mse_loss.item():.6f} (Should be minimal)")
    
    if cos_sim.item() < 0.95:
        print("\n[CRITICAL] ⚠️ 严重精度丢失!检测到算子实现可能存在 SDC (Silent Data Corruption)。")
    else:
        print("\n[PASS] ✅ 精度符合预期。")
    
  2. 运行文件

    hljs bash
    1
    python layer_divergence.py
    
  3. 观察结果: Int4 的量化噪声是必然的,但如果 Cosine Similarity 低于 0.95,对于推理任务来说是灾难性的。在国产卡开发初期,这通常意味着 GEMM 算子的累加器发生了溢出(Saturation)或截断错误。


3. 实验总结与核心知识点

3.1【核心结论】

量化不是免费的午餐。Int4 能跑通只是第一步,「精度对齐」才是国产 GPU 软件栈成熟的标志。如果你发现模型开始胡言乱语(PPL 爆炸),大概率是硬件的 Int4 指令在处理边界值时算“飞”了。

3.2【技术解剖:量化的代价】

  1. N 厂方案 (Tensor Core): 利用 IMMA (Integer Matrix Multiply Accumulate) 指令,硬件级自动处理 Int4 输入和 Int32 累加输出,配合 cuBLAS 库的高度优化,精度损失极小。

  2. 国产化挑战 (MUSA / Others): 我们的 Tensor Engine 往往需要手动管理数据的 Packing/Unpacking(打包/解包)。如果驱动编译器(Compiler)在指令调度时没处理好数据对齐,或者为了省面积缩减了累加器位宽(例如用 Int16 存结果),就会导致大数值计算发生回卷 (Wrap-around),引发静默错误。

3.3【关键概念 (Knowledge Points)】

  • SDC (Silent Data Corruption):静默数据损坏。显卡没报错,驱动没崩溃,但算出来的数是错的。这是高性能计算领域最可怕的噩梦。
  • Accumulator Saturation (累加器饱和):当大量 Int8/Int4 数值相加超过容器上限时,是截断成最大值(Saturation)还是变成负数(Overflow/Wrap)。AI 算子必须使用饱和截断,否则模型参数会瞬间崩坏。
  • Roofline Model (屋顶模型):本实验的潜台词是验证计算密度。FP16 受限于显存带宽(Memory Bound),Int4 理应受限于计算速度(Compute Bound)。如果 Int4 没有显著变快,说明我们的硬件算力没有被榨干,卡在了软件栈上。

老鸟总结: 造显卡像造车,发动机(硬件算力)马力再大,变速箱(驱动调度)逻辑不紧,换挡照样顿挫。给我们时间,驱动团队正在 996 补课。