本站内容由 AI 生成,可能存在错误。如发现问题,欢迎到 GitHub Issues 反馈。

动手:HF → GGUF / ONNX / OpenVINO 三条路径端到端

动手:HF → GGUF / ONNX / OpenVINO 三条路径端到端

更新于 2026-04-20

§1 开篇:三条路径,一个起点

前两篇文章,我们从”全景视角”理解了量化与模型转换工具链的分层结构,又在 Intel 深潜中拆解了 Optimum Intel / NNCF / OpenVINO 三件套的内部调用关系。理论讲完了,该动手了。

本文的起点是同一个模型:meta-llama/Llama-3.1-8B-Instruct——Hugging Face Hub 上以 FP16 safetensors 格式发布的 8B 参数指令微调模型。我们将从这个 checkpoint 出发,走完三条完整的转换 + 量化 + 推理路径:

路径转换流程推理引擎
Path AHF → GGUF (convert_hf_to_gguf.py + llama-quantize)llama.cpp CPU 推理
Path BHF → ONNX (optimum-cli export onnx)ONNX Runtime 推理
Path CHF → OpenVINO IR (optimum-cli export openvino + NNCF)OpenVINO iGPU 推理

每条路径走完后,我们会关注三个维度:转换复杂度(步骤数、耗时、额外依赖)、模型体积(量化前后的文件大小)、推理速度(首 token 延迟 TTFT + 解码吞吐 TPS)。

三条转换路径总览Llama-3.1-8B-InstructHF FP16 safetensors (~15 GB)Path Aconvert + quantizeGGUFllama.cpp CPU推理引擎Path Boptimum-cli exportONNXONNX Runtime推理引擎Path Coptimum-cli + NNCFOpenVINO IROpenVINO iGPU推理引擎同一起点,三条端到端路径

声明:本文命令基于各工具的官方文档编写。由于工具版本迭代较快,建议读者在实际操作前查阅对应版本的最新文档。精度和速度数据为代表性参考值,实际结果因硬件和环境而异。

§2 环境准备

软件版本

以下是本文使用的软件版本组合。这些版本在撰写时(2026 年 4 月)是各工具的最新稳定版或推荐版:

组件版本用途
Python3.11运行环境
llama.cppb5200 (latest stable tag)Path A: GGUF 转换与推理
Optimum Intel1.22.0Path B/C: ONNX/OpenVINO 导出
OpenVINO2025.1Path C: OpenVINO IR 推理
ONNX Runtime1.21.0Path B: ONNX 推理
NNCF3.1.0Path C: 精度感知量化
transformers4.48+模型与 tokenizer 加载

安装依赖

Path B 和 Path C 的 Python 依赖可以一条命令搞定:

pip install optimum-intel[openvino] openvino==2025.1 \
  onnxruntime==1.21.0 nncf==3.1.0

optimum-intel[openvino] 会自动拉入 optimumtransformersopenvino 等依赖。如果你只走 Path B(ONNX),可以单装 optimum[onnxruntime]

llama.cpp 编译(Path A)

llama.cpp 需要从源码编译。在 Intel CPU 上推荐开启 BLAS 加速:

git clone https://github.com/ggml-org/llama.cpp.git
cd llama.cpp && git checkout b5200

# Intel CPU: 开启 OpenBLAS 加速
cmake -B build -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS
cmake --build build --config Release -j$(nproc)

如果你想尝试 llama.cpp 的 SYCL 后端(Intel iGPU 加速,实验性质):

# 实验性:Intel iGPU via SYCL(需要 Intel oneAPI 2025.1+)
cmake -B build -DGGML_SYCL=ON -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx
cmake --build build --config Release -j$(nproc)

注意:llama.cpp 的 SYCL 后端尚在积极开发中,稳定性和性能均不如 OpenVINO 在 iGPU 上的表现。如果你的目标是 Intel iGPU 推理,建议走 Path C。

Intel iGPU 驱动(Path C)

在 Ubuntu 上,确保安装了 OpenCL 和 Level Zero 运行时:

# Ubuntu 22.04 / 24.04
sudo apt install intel-opencl-icd intel-level-zero-gpu level-zero

在 Windows 上,Intel 显卡驱动通常自带 OpenCL 和 Level Zero 支持。WSL2 用户需要确认已安装 intel-opencl-icd 并且宿主机驱动版本 >= 31.0.101.5592。

下载模型

# 需要先在 https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct
# 接受 Llama 3.1 Community License Agreement
huggingface-cli login
huggingface-cli download meta-llama/Llama-3.1-8B-Instruct \
  --local-dir ./llama-3.1-8b-fp16

Llama 3.1 是 gated model(受限模型),需要先在 Hugging Face Hub 页面同意 Meta 的 Community License Agreement 后才能下载。下载后你会得到一个约 15 GB 的目录,包含 FP16 safetensors 权重文件和 tokenizer。

§3 Path A: HF → GGUF

GGUF 是 llama.cpp 的原生格式,也是社区中”本地跑模型”最流行的路径。整个流程分三步:格式转换 → 量化 → 推理。

Path A: HF → GGUF 流水线Path A: HF → GGUFHF FP161格式转换convert_hf_to_gguf.pyGGUF FP16~15 GB2量化llama-quantizeQ4_K_M~4.7 GB3推理llama-cli文本输出FP16 → Q4_K_M: 压缩 ~3.2×

Step 1: 格式转换(HF safetensors → GGUF FP16)

python llama.cpp/convert_hf_to_gguf.py ./llama-3.1-8b-fp16 \
  --outfile llama-3.1-8b-f16.gguf \
  --outtype f16

convert_hf_to_gguf.py 读取 HF checkpoint 的 config.json 识别模型架构(LlamaForCausalLM),解析 safetensors 权重文件,然后按 GGUF 的 tensor layout 重新打包。--outtype f16 表示不做量化,只做格式转换,权重精度保持 FP16。

这一步的产物 llama-3.1-8b-f16.gguf 大约 15 GB,和原始 safetensors 体积相当。

Step 2: 量化(GGUF FP16 → Q4_K_M)

./llama.cpp/build/bin/llama-quantize \
  llama-3.1-8b-f16.gguf \
  llama-3.1-8b-q4km.gguf \
  Q4_K_M

llama-quantize 是 llama.cpp 的内置量化工具(Layer 4:推理引擎内置量化)。Q4_K_M 是 K-quant 系列中平衡精度和体积的经典选择:4-bit 量化 + 多级 super-block scale + mixed precision(attention 的 output projection 层和 feed-forward 的 gate 层保留 6-bit)。

量化后的 llama-3.1-8b-q4km.gguf 大约 4.7 GB——相比 FP16 压缩了约 3.2 倍。

Step 3: 推理测试

# 快速推理测试
./llama.cpp/build/bin/llama-cli \
  -m llama-3.1-8b-q4km.gguf \
  -p "Hello, world!" \
  -n 50

如果一切正常,你会看到模型逐 token 生成 50 个 token 的续写。

Step 4: Perplexity 测量

# 下载 WikiText-2 测试集(llama.cpp 使用 raw 文本格式)
# 文件来源: https://huggingface.co/datasets/ggml-org/ci/resolve/main/wikitext-2-raw-v1.zip
wget -O wiki.test.raw https://huggingface.co/datasets/ggml-org/ci/resolve/main/wikitext-2-raw-v1.zip
unzip wikitext-2-raw-v1.zip

./llama.cpp/build/bin/llama-perplexity \
  -m llama-3.1-8b-q4km.gguf \
  -f wikitext-2-raw/wiki.test.raw \
  --chunks 100

llama-perplexity 逐 chunk 计算 perplexity,--chunks 100 限制只跑前 100 个 chunk 以节省时间。完整测试集约 240 个 chunk,全跑完更准确但耗时更长。

Step 5: 速度基准测试

./llama.cpp/build/bin/llama-bench \
  -m llama-3.1-8b-q4km.gguf \
  -t 8 \
  -ngl 0

-t 8 指定使用 8 个 CPU 线程,-ngl 0 表示不 offload 任何层到 GPU(纯 CPU 推理)。llama-bench 会输出 prompt processing(prefill)和 token generation(decode)的速度。

常见陷阱

Tokenizer 处理差异convert_hf_to_gguf.py 在转换时会从 HF checkpoint 的 tokenizer.json / tokenizer_config.json 重建 tokenizer,并嵌入到 GGUF 文件中。Llama 3 使用的是基于 tiktoken 的 BPE tokenizer(不是 Llama 2 的 SentencePiece),转换脚本需要正确识别这一点。如果遇到 tokenizer 相关错误,首先检查你的 convert_hf_to_gguf.py 是否和 llama.cpp 版本一致。

Importance Matrix (imatrix) 的影响。上面的 Step 2 使用了”plain”量化——直接对所有层统一量化。llama.cpp 还支持 importance matrix 辅助量化:先在校准数据上计算每个权重的重要性分数,然后量化时给重要权重分配更多 bit。对于 Q4_K_M 这种本身就带 mixed precision 的方案,imatrix 的增益在 0.02-0.05 PPL 左右,但对更激进的量化(如 IQ2_XS)影响显著。

# 可选:生成 importance matrix
./llama.cpp/build/bin/llama-imatrix \
  -m llama-3.1-8b-f16.gguf \
  -f calibration_data.txt \
  -o imatrix.dat

# 使用 imatrix 量化
./llama.cpp/build/bin/llama-quantize \
  --imatrix imatrix.dat \
  llama-3.1-8b-f16.gguf \
  llama-3.1-8b-q4km-imat.gguf \
  Q4_K_M

为什么不推荐 GGUF 跑 iGPU? llama.cpp 的 SYCL 后端(用于 Intel iGPU)仍处于早期阶段,支持的量化类型有限,kernel 优化远不如 CUDA 后端成熟。相比之下,OpenVINO 是 Intel 官方为自家硬件深度优化的推理引擎,在 iGPU 上的性能远优于 llama.cpp SYCL。如果你的目标硬件是 Intel iGPU,请直接看 Path C。

§4 Path B: HF → ONNX

ONNX (Open Neural Network Exchange) 是跨平台模型交换的”通用语言”。如果你不确定模型最终会部署在什么硬件上,或者需要在多种后端之间灵活切换,ONNX 是一个安全的中间选择。

Path B: HF → ONNX 流水线Path B: HF → ONNX1ONNX 导出optimum-cli export onnx--task text-generation-with-past~15 GB (FP16)2INT8 量化optimum-cli onnxruntime quantize动态量化 (无需校准数据)~7.8 GB3Python 推理ORTModelForCausalLMtransformers 兼容 APIONNX 核心价值:同一模型,切换 Execution Provider 即可跨硬件运行

Step 1: 导出 ONNX

optimum-cli export onnx \
  --model ./llama-3.1-8b-fp16 \
  --task text-generation-with-past \
  ./llama-3.1-8b-onnx

optimum-cli export onnx 是 Hugging Face Optimum 提供的导出命令。--task text-generation-with-past 非常关键——它告诉导出器这是一个自回归语言模型,需要处理 KV cache(past key values)。如果不加 -with-past,导出的模型在每次生成 token 时都要重新计算整个序列的 attention,推理速度会极慢。

导出完成后,./llama-3.1-8b-onnx 目录包含 .onnx 模型文件和对应的 tokenizer 文件。FP16 的 ONNX 模型大约 15 GB。

Step 2: INT8 动态量化

optimum-cli onnxruntime quantize \
  --onnx_model ./llama-3.1-8b-onnx \
  --avx512_vnni \
  -o ./llama-3.1-8b-onnx-int8

--avx512_vnni 启用 AVX-512 VNNI 指令集优化——这是 Intel 10 代酷睿及更新 CPU 支持的向量化整数运算指令集,对 INT8 推理有显著加速。如果你的 CPU 不支持 AVX-512 VNNI,去掉这个选项即可(会退化为通用 INT8 量化路径)。

量化后的模型大约 7.8 GB。这里用的是动态量化 (dynamic quantization):权重提前量化为 INT8 存储,激活值在推理时动态量化。相比静态量化(需要校准数据集),动态量化不需要额外数据,但精度损失通常略大。

Step 3: Python 推理

from optimum.onnxruntime import ORTModelForCausalLM
from transformers import AutoTokenizer

model = ORTModelForCausalLM.from_pretrained(
    './llama-3.1-8b-onnx-int8'
)
tok = AutoTokenizer.from_pretrained('./llama-3.1-8b-fp16')

inputs = tok('Hello, how are you?', return_tensors='pt')
output = model.generate(**inputs, max_new_tokens=50)
print(tok.decode(output[0], skip_special_tokens=True))

ORTModelForCausalLM 是 Optimum 对 ONNX Runtime 的封装,API 和 Hugging Face transformersAutoModelForCausalLM 高度一致,可以无缝替换。

常见陷阱

KV cache 处理。前面提到的 --task text-generation-with-past 至关重要。Optimum 在导出时会将模型拆分为两个子图:初始 prompt 处理(无 past)和增量 token 生成(有 past key values)。如果用错了 task type(例如只写 text-generation),模型虽然能导出但推理效率极低。

Execution Provider 选择。ONNX Runtime 通过 Execution Provider (EP) 支持不同的硬件后端:

EP硬件说明
CPUExecutionProvider通用 CPU默认,无需额外安装
OpenVINOExecutionProviderIntel CPU/GPU/NPU需要 onnxruntime-openvino
CUDAExecutionProviderNVIDIA GPU需要 onnxruntime-gpu
DirectMLExecutionProviderWindows GPU (通用)需要 onnxruntime-directml

如果你在 Intel 硬件上跑 ONNX Runtime,可以选择 OpenVINO EP 来获得更好的性能——但这时你实际上是在用 OpenVINO 作为后端,那为什么不直接走 Path C?ONNX 的核心价值不在于单一硬件的极致性能,而在于跨平台通用性:同一个 ONNX 模型可以在 Intel CPU、NVIDIA GPU、ARM 手机上运行,只需切换 EP。

ONNX 模型体积偏大。ONNX 的 INT8 量化通常采用对称量化 + 动态范围,压缩比不如 GGUF 的 K-quant(Q4_K_M 是 4-bit + mixed precision)。如果体积是首要考量,GGUF 几乎总是更小。

§5 Path C: HF → OpenVINO IR

OpenVINO 是 Intel 官方的推理引擎,对 Intel CPU、iGPU、NPU 做了深度优化。Path C 是三条路径中选项最多的——从简单的一键导出到复杂的精度感知量化,可以根据需求选择不同深度。

Path C: OpenVINO 三种量化配置Path C: OpenVINO 三种量化配置配置模型体积配置 1: FP16--weight-format fp16~15 GB基线配置 2: INT8--weight-format int8~7.5 GB无需校准配置 3: INT4--weight-format int4~4.2 GB--group-size 128 --ratio 0.8INT4 (4.2 GB) vs GGUF Q4_K_M (4.7 GB):体积相近,后端不同

配置 1: FP16 基线(不量化)

optimum-cli export openvino \
  --model ./llama-3.1-8b-fp16 \
  --task text-generation-with-past \
  --weight-format fp16 \
  ./llama-3.1-8b-ov-fp16

这是最简单的导出——只做格式转换(HF safetensors → OpenVINO IR),权重保持 FP16。产物是 openvino_model.xml(图结构)+ openvino_model.bin(权重),总计约 15 GB。

配置 2: INT8 weight-only(一行命令)

optimum-cli export openvino \
  --model ./llama-3.1-8b-fp16 \
  --weight-format int8 \
  ./llama-3.1-8b-ov-int8

--weight-formatfp16 改成 int8,Optimum Intel 在导出时会自动调用 NNCF 进行 INT8 weight-only 量化(对称量化,per-channel)。模型体积降至约 7.5 GB。

注意这里不需要校准数据集——8-bit 量化的表示范围足够覆盖绝大多数权重分布,简单的 min-max 统计即可。

配置 3: INT4 weight-only(进阶)

optimum-cli export openvino \
  --model ./llama-3.1-8b-fp16 \
  --weight-format int4 \
  --group-size 128 \
  --ratio 0.8 \
  ./llama-3.1-8b-ov-int4

INT4 是更激进的量化。两个关键参数:

  • --group-size 128:每 128 个权重共享一组 scale 和 zero-point。group-size 越小精度越高但 overhead 越大。128 是推荐默认值,64 更精确但速度稍慢。
  • --ratio 0.8:80% 的层用 INT4 量化,剩余 20% 的敏感层(如 attention 的 output projection)用 INT8(backup_mode)。ratio 越高压缩越激进、精度损失越大。

INT4 量化后模型约 4.2 GB——仅比 GGUF Q4_K_M 的 4.7 GB 略小。

INT4 量化需要校准数据集。Optimum Intel 默认使用内置的 wikitext 子集作为校准数据。如果你有领域特定的数据(如法律文档或医疗对话),可以通过 NNCF API 自定义校准集(见下文”高级用法”)。

推理:OpenVINO GenAI Pipeline

import openvino_genai as ov_genai

pipe = ov_genai.LLMPipeline(
    './llama-3.1-8b-ov-int4',
    device='GPU'  # 'CPU' for CPU inference
)
print(pipe.generate('Hello, how are you?', max_new_tokens=50))

openvino_genai 是 OpenVINO 为 LLM 推理提供的高层 API(C++ 实现,Python binding),内部封装了 tokenizer、KV cache 管理、beam search / sampling 等。device='GPU' 指定使用 Intel iGPU 推理。

也可以用 Optimum Intel 的 Python API(与 transformers 风格一致):

from optimum.intel import OVModelForCausalLM
from transformers import AutoTokenizer

model = OVModelForCausalLM.from_pretrained(
    './llama-3.1-8b-ov-int4',
    device='GPU'
)
tok = AutoTokenizer.from_pretrained('./llama-3.1-8b-fp16')

inputs = tok('Hello, how are you?', return_tensors='pt')
output = model.generate(**inputs, max_new_tokens=50)
print(tok.decode(output[0], skip_special_tokens=True))

高级用法: NNCF 精度感知量化 (AAQ)

如果你对精度有严格要求(例如医疗对话、代码生成),可以绕过 Optimum CLI,直接使用 NNCF 的 quantize_with_accuracy_control API。这在 Intel 深潜 的 §3.3 中有详细介绍,这里给出完整的可运行脚本:

import nncf
import openvino as ov
from functools import partial
from datasets import load_dataset
from transformers import AutoTokenizer
from optimum.intel import OVModelForCausalLM

# ── 1. 加载 FP16 OpenVINO 模型 ──
model_path = './llama-3.1-8b-ov-fp16'
ov_model = ov.Core().read_model(f'{model_path}/openvino_model.xml')
tokenizer = AutoTokenizer.from_pretrained(model_path)

# ── 2. 准备校准数据集 ──
calibration_data = load_dataset(
    'wikitext', 'wikitext-2-raw-v1', split='train[:1000]'
)

def preprocess_fn(example, tokenizer):
    return tokenizer(
        example['text'], truncation=True, max_length=512
    )

calibration_dataset = nncf.Dataset(
    calibration_data,
    partial(preprocess_fn, tokenizer=tokenizer)
)

# ── 3. 准备验证数据集和验证函数 ──
val_data = load_dataset(
    'wikitext', 'wikitext-2-raw-v1', split='validation[:200]'
)
validation_dataset = nncf.Dataset(
    val_data,
    partial(preprocess_fn, tokenizer=tokenizer)
)

def validation_fn(compiled_model, validation_data):
    # 简化示例:计算 validation loss 作为精度指标
    # 实际使用中可替换为 MMLU / HumanEval 等 task-specific 指标
    total_loss = 0.0
    count = 0
    for batch in validation_data:
        # ... 前向传播计算 loss ...
        count += 1
    return (total_loss / max(count, 1),)

# ── 4. 执行精度感知量化 ──
quantized_model = nncf.quantize_with_accuracy_control(
    ov_model,
    calibration_dataset=calibration_dataset,
    validation_dataset=validation_dataset,
    validation_fn=validation_fn,
    max_drop=0.01,           # 精度下降不超过 1%
    drop_type=nncf.DropType.ABSOLUTE,
    preset=nncf.QuantizationPreset.MIXED,
    advanced_parameters=nncf.AdvancedQuantizationParameters(
        overflow_fix=nncf.OverflowFix.DISABLE  # LLM 通常不需要 overflow fix
    )
)

# ── 5. 保存量化模型 ──
ov.save_model(quantized_model, './llama-3.1-8b-ov-int4-aaq/openvino_model.xml')
# 别忘了复制 tokenizer 文件到输出目录

这个脚本和 Optimum CLI 一行命令的区别在于:

  1. 精度约束max_drop=0.01 设置了精度下降上限。AAQ 会先尝试全量 INT4 量化,如果精度下降超过阈值,自动将敏感层回退到 INT8 或 FP16,直到满足约束。
  2. 自定义验证函数:你可以定义任意评估逻辑(如 pass@1、BLEU、domain-specific accuracy),不局限于 perplexity。
  3. 完全控制:可以调整 preset、overflow fix、subset 大小等高级参数。

代价是:AAQ 需要在验证集上多次迭代(通常 5-20 轮),量化时间从分钟级上升到小时级。

常见陷阱

设备指定device='GPU' 指的是 Intel iGPU(集成显卡),不是独立显卡。如果系统没有安装 OpenCL/Level Zero 驱动或 iGPU 不可用,会报错。可以用 device='CPU' 退化为 CPU 推理。

--ratio 的影响。ratio 控制 INT4 和 INT8 的混合比例。在 Llama-3.1-8B 上,ratio=0.8(默认)时 PPL 增加约 0.45;ratio=1.0(全部 INT4)时 PPL 增加约 0.8。如果精度很重要,降低 ratio 或改用 AAQ。

--group-size 的权衡。group-size 128 是 INT4 的标准选择。group-size 64 每组只有 64 个权重共享 scale,量化误差更小,但 scale/zero-point 的存储 overhead 增加约 10%,推理时 dequantize 计算也更多。对精度敏感的场景可以尝试 64,但要实测确认速度没有明显下降。

WSL2 权限问题。在 WSL2 环境下运行 OpenVINO GPU 推理时,可能遇到 /dev/dri/renderD128 权限不足的问题。确保当前用户在 rendervideo 用户组中:

sudo usermod -a -G render,video $USER
# 重新登录 WSL2 session 生效

§6 端到端 Pipeline 的陷阱清单

走完三条路径,你会发现很多坑不是某一条路径特有的,而是跨路径的共性问题。这里汇总五个最容易踩的陷阱:

端到端 Pipeline 五大陷阱跨路径共性陷阱速查1Tokenizer 一致性GGUF/OV 各自重建 tokenizer,edge case 可能不同A / B / C2Chat Template 丢失GGUF 不保存 chat template,需手动指定A / B / C3动态 vs 静态 ShapeONNX/OV 需配置动态轴,否则 shape mismatchB / C4精度测量不可比Batch size / context len / few-shot 不同→PPL 不可比A / B / C5WikiText-2 版本差异同一数据集、不同 tokenizer → PPL 差 0.01-0.05A / B / C建议:使用统一评测框架 (lm-evaluation-harness) 对比精度

1. Tokenizer 一致性

模型转换不只是权重格式的变化,tokenizer 也会被重新打包。GGUF 文件内嵌了独立的 tokenizer,OpenVINO IR 目录也包含 tokenizer_config.json。问题在于:

  • llama.cpp 的 GGUF tokenizer 是从 HF 的 tokenizer.json 反向重建的,某些 edge case(如特殊 token 的 ID 映射、added_tokens 的顺序)可能和原始 tokenizer 不完全一致
  • 如果你在 A 路径的 tokenizer 上测 perplexity,又拿 C 路径的 tokenizer 跑推理,两者的 token 序列可能不同——即使输入文本完全一样

建议:对比测试时,确保所有路径使用同一份 tokenizer encode 输入文本。可以先用 HF AutoTokenizer encode,再把 token ID 传给各推理引擎。

2. Chat Template 丢失

Hugging Face checkpoint 的 tokenizer_config.json 通常包含 chat_template 字段(Jinja2 格式),定义了 system/user/assistant 消息的格式化规则。但:

  • GGUF 格式不保存 chat template。llama.cpp 需要在命令行通过 --chat-template 参数或在代码中手动指定
  • OpenVINO IR 导出时,Optimum Intel 会复制 tokenizer_config.json,但 openvino_genai.LLMPipeline 不一定能正确解析所有 Jinja2 template

建议:在使用指令微调模型(如 *-Instruct)时,务必验证 chat template 是否被正确应用。最简单的方法是手动构造一段 <|begin_of_text|><|start_header_id|>user<|end_header_id|>...<|eot_id|> 格式的输入,对比原始 HF 模型和转换后模型的输出是否一致。

3. 动态 Shape vs 静态 Shape

ONNX 和 OpenVINO 默认使用静态 shape 导出——输入的 sequence length 在导出时固定。但 LLM 的输入长度是动态的(不同 prompt 长度不同),需要显式配置动态 shape:

  • optimum-cli export onnx 默认已处理动态轴(batch_size 和 sequence_length 标记为动态),通常无需手动配置
  • optimum-cli export openvino 也类似,但如果你用 ov.convert_model 手动转换,需要自己指定 input 参数的动态维度

如果你看到推理时报 “shape mismatch” 或 “input size doesn’t match”,首先检查动态 shape 配置。

4. 精度测量不可比

量化精度通常用 perplexity (PPL) 和 benchmark accuracy (MMLU、HumanEval 等) 衡量。但不同工具的测量条件可能不同:

  • Batch size:PPL 对 batch size 敏感。llama.cpp 的 llama-perplexity 默认 batch=512,而 lm-evaluation-harness 可以设置不同的 batch size
  • Sequence length:context window 长度影响 PPL 值。用 2048 token 上下文测的 PPL 和用 4096 测的不一样
  • Few-shot count:MMLU 的 0-shot 和 5-shot 结果可能差 5-10 个百分点

建议:对比不同路径的精度时,使用同一套评测框架(推荐 lm-evaluation-harness)和完全相同的配置(batch size、context length、few-shot count)。

5. WikiText-2 版本差异

一个隐蔽但重要的问题:llama.cpp 的 llama-perplexity 使用的 wiki.test.raw 和 Hugging Face datasets 中的 wikitext-2-raw-v1 数据内容是一致的,但tokenization 不同

  • llama.cpp 使用内嵌在 GGUF 中的 tokenizer 直接 tokenize 原始文本
  • lm-evaluation-harness 使用 Hugging Face AutoTokenizer

两种 tokenizer 对同一文本的分词结果可能有微小差异(见陷阱 1),导致 PPL 数值不完全可比。差异通常在 0.01-0.05 PPL 范围内,不影响定性结论,但不应作为精确的量化比较依据。

建议:如需严格比较,统一使用 lm-evaluation-harness,对所有路径的量化模型跑相同的 benchmark。

§7 总结与路径选择

三条路径各有所长,选择取决于你的硬件和场景:

路径选择总结三条路径各有所长Path A: GGUF最佳场景本地快速跑量化体积4.7 GB复杂度2 条命令目标硬件CPU 优先Path B: ONNX最佳场景跨平台部署量化体积7.8 GB复杂度2 条命令目标硬件多硬件 (EP)Path C: OpenVINO最佳场景Intel 最优量化体积4.2 GB复杂度1 条命令目标硬件Intel iGPU/CPU

只想在本地快速跑起来,对精度不敏感GGUF Q4_K_M + llama.cpp。最简单的路径:两条命令搞定(convert + quantize),llama.cpp 的 CPU 推理性能极好,社区 GGUF 资源丰富(HuggingFace 上有大量预量化的 GGUF 文件可直接下载)。

跨平台部署,不确定最终硬件ONNX + ONNX Runtime。ONNX 是唯一真正跨硬件的格式——同一个模型文件可以在 Intel CPU、NVIDIA GPU、ARM 手机、Qualcomm NPU 上运行,只需切换 Execution Provider。代价是:每个硬件上的性能都不是最优的,但够用。

Intel 硬件 + 想要最佳性能 + 精度敏感OpenVINO IR + NNCF AAQ。Intel 官方的”全家桶”方案,对 Intel CPU/iGPU/NPU 有最深度的优化。INT4 + iGPU 能提供最高的推理速度(在 Intel 平台上),AAQ 可以在精度和性能之间找到最佳平衡。

这三条路径并非互斥——很多实际场景会组合使用。例如,开发调试阶段用 GGUF + llama.cpp 快速验证模型质量,确认后再用 OpenVINO 打包到生产环境的 Intel 硬件上部署。

本文是量化与模型转换工具链系列的第三篇(也是收尾篇),与前两篇形成完整的”理论→选型→实践”链条:

  • §全景篇:理解工具链的分层结构和生态全貌
  • §Intel 深潜:拆解 Optimum Intel / NNCF / OpenVINO 三件套
  • §动手指南(本文):端到端走完三条转换路径

同时,本文也是 Intel iGPU 推理深度解析 学习路径的倒数第二站——在理解了 Xe2 架构、oneDNN 原语、OpenVINO 图优化和 Intel 优化栈之后,这里把所有知识串联成完整的端到端实践。下一站是 iGPU 性能分析和 NPU 协同推理。