跳到主要内容

MiniCPM-V 4.6

MiniCPM-V 多模态系列

用更小的权重换取更低的显存与存储占用。GGUF 适用于本地与 CPU / Metal 运行,BNB 与 AWQ 面向 GPU 推理。可直接使用官方预量化权重,也可按本页脚本自行量化。

格式精度工具适用场景
GGUFQ4_K_M 等多档llama.cppllama.cpp / Ollama 本地运行
BNBint4bitsandbytesGPU 推理,脚本最简单
AWQ4-bitAutoAWQGPU 推理,需校准数据
相比 MiniCPM-V 4.5 的变化

MiniCPM-V 4.6 的 GGUF 转换更简洁。模型已合并入 transformers>=5.7.0,llama.cpp 标准的 convert_hf_to_gguf.py(release b9049 及之后)一并处理语言模型与视觉 projector,不再需要 minicpmv-surgery.py + minicpmv-convert-image-encoder-to-gguf.py 这套老脚本。

GGUF​

本指南将引导您将 MiniCPM-V 4.6 PyTorch 模型转换为 GGUF 格式并进行量化。

生成的 GGUF 可与 llama.cpp 或 ollama 配合使用。

1. 下载 PyTorch 模型​

按需选择 checkpoint:

2. 转换为 GGUF​

在 llama.cpp(release b9049 及之后)的仓库根目录下执行:

Bash
# 步骤 1:将语言模型 + 视觉 merger 转为 F16 GGUF
python ./convert_hf_to_gguf.py /path/to/MiniCPM-V-4.6 \
--outfile /path/to/MiniCPM-V-4.6/MiniCPM-V-4.6-F16.gguf \
--outtype f16

# 步骤 2:转换视觉 projector(mmproj)
python ./convert_hf_to_gguf.py /path/to/MiniCPM-V-4.6 \
--mmproj \
--outfile /path/to/MiniCPM-V-4.6/mmproj-MiniCPM-V-4.6-F16.gguf

convert_hf_to_gguf.py 会从 config.json 自动识别 MiniCPMV4_6ForConditionalGeneration。

3. INT4 量化​

得到 F16 的语言模型 GGUF 后,使用 llama-quantize 进行量化:

Bash
./llama-quantize \
/path/to/MiniCPM-V-4.6/MiniCPM-V-4.6-F16.gguf \
/path/to/MiniCPM-V-4.6/MiniCPM-V-4.6-Q4_K_M.gguf \
Q4_K_M

常用的量化档位还有 Q5_K_M、Q6_K、Q8_0,根据精度和显存预算选择即可。

视觉 mmproj 文件本身较小,建议保持 F16 与所选 LM 量化版本搭配使用。

BNB​

支持版本: MiniCPM-V 4.6 / MiniCPM-V 4.5 / MiniCPM-V 4.0 / MiniCPM-V 2.6 / MiniCPM-V 2.5

1. 下载模型​

从 HuggingFace 下载 MiniCPM-V-4.6 模型(Thinking 版本为 openbmb/MiniCPM-V-4.6-Thinking),解压到本地目录。

2. 量化脚本​

下方脚本会加载原始模型,使用 bitsandbytes 量化为 4-bit,运行一次推理校验,并保存量化后的模型。

需要 transformers>=5.7.0(MiniCPM-V 4.6 在该版本注册为独立架构)。

Python
import os
import time
import torch
from transformers import (
AutoProcessor,
AutoModelForImageTextToText,
BitsAndBytesConfig,
)
from PIL import Image

assert torch.cuda.is_available(), "CUDA is not available, but this code requires a GPU."

device = "cuda"
model_path = "/model/MiniCPM-V-4.6" # 原始模型路径
save_path = "./model/MiniCPM-V-4.6-int4" # 量化模型保存路径
image_path = "./assets/airplane.jpeg"

# 4-bit 量化配置
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
load_in_8bit=False,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_quant_storage=torch.uint8,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
llm_int8_enable_fp32_cpu_offload=False,
llm_int8_has_fp16_weight=False,
llm_int8_skip_modules=["lm_head"],
llm_int8_threshold=6.0,
)

processor = AutoProcessor.from_pretrained(model_path)
model = AutoModelForImageTextToText.from_pretrained(
model_path,
device_map=device,
quantization_config=quantization_config,
)

image = Image.open(image_path).convert("RGB")
messages = [{
"role": "user",
"content": [
{"type": "image", "image": image},
{"type": "text", "text": "图中是什么?"},
],
}]

inputs = processor.apply_chat_template(
messages,
add_generation_prompt=True,
tokenize=True,
return_dict=True,
return_tensors="pt",
).to(model.device)

start = time.time()
out_ids = model.generate(**inputs, max_new_tokens=256)
response = processor.decode(
out_ids[0][inputs["input_ids"].shape[-1]:],
skip_special_tokens=True,
)

print("量化后输出:", response)
print("量化后推理耗时:", time.time() - start)

# 保存量化模型与 tokenizer / processor
os.makedirs(save_path, exist_ok=True)
model.save_pretrained(save_path, safe_serialization=True)
processor.save_pretrained(save_path)

3. 预期输出​

量化完成后大致会看到:

Plain Text
量化后输出: 图中是一架空客 A380-800 飞机,正在晴朗蓝天中飞行。机身以白色为主,垂直尾翼是醒目的蓝色,并印有红白配色的徽标。……
量化后推理耗时: 单卡 A100/4090 上约 9 s

量化模型会保存到 save_path,可通过 AutoModelForImageTextToText.from_pretrained(save_path) 重新加载继续推理或微调。


要自定义模型路径、图片或保存路径,修改脚本顶部的变量即可。

AWQ​

支持版本: MiniCPM-V 4.6(Instruct & Thinking)/ MiniCPM-V 4.5

MiniCPM-V 使用的 AutoAWQ 工作流位于 tc-mb/AutoAWQ(上游 casper-hansen/AutoAWQ 已停止维护)。

MiniCPM-V 4.6 要求 transformers>=5.7.0;上述 AutoAWQ fork 同步跟进了这个依赖。

方法 1 — 用预量化模型 + vLLM 推理​

1. 下载预量化模型​

Bash
git clone https://huggingface.co/openbmb/MiniCPM-V-4.6-AWQ

(Thinking 版本发布后将是 openbmb/MiniCPM-V-4.6-Thinking-AWQ。)

2. 用 vLLM 运行​

Python
from PIL import Image
from transformers import AutoProcessor
from vllm import LLM, SamplingParams

MODEL_NAME = "openbmb/MiniCPM-V-4.6-AWQ"
IMAGES = ["image.png"]

image = Image.open(IMAGES[0]).convert("RGB")
processor = AutoProcessor.from_pretrained(MODEL_NAME)

llm = LLM(
model=MODEL_NAME,
max_model_len=8192,
trust_remote_code=True,
# limit_mm_per_prompt={"image": 5},
)

messages = [{
"role": "user",
"content": [
{"type": "image"},
{"type": "text", "text": "请描述这张图片的内容"},
],
}]

prompt = processor.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)

sampling_params = SamplingParams(
stop_token_ids=[248044, 248046], # v4.6 使用 Qwen3.5 词表
temperature=0.7,
top_p=0.8,
max_tokens=1024,
)

outputs = llm.generate(
{
"prompt": prompt,
"multi_modal_data": {"image": image},
},
sampling_params=sampling_params,
)
print(outputs[0].outputs[0].text)

方法 2 — 用 AutoAWQ 直接运行 AWQ 模型​

1. 下载模型​

Bash
git clone https://huggingface.co/openbmb/MiniCPM-V-4.6-AWQ

2. 从源码安装 AutoAWQ​

Bash
git clone https://github.com/tc-mb/AutoAWQ.git
cd AutoAWQ
pip install -e .

3. 推理脚本​

Python
import torch
from PIL import Image
from transformers import AutoProcessor
from awq import AutoAWQForCausalLM

model_path = "openbmb/MiniCPM-V-4.6-AWQ"
image_path = "./assets/airplane.jpeg"

model = AutoAWQForCausalLM.from_quantized(
model_path, trust_remote_code=True
).to("cuda")
processor = AutoProcessor.from_pretrained(model_path)

image = Image.open(image_path).convert("RGB")
messages = [{
"role": "user",
"content": [
{"type": "image", "image": image},
{"type": "text", "text": "图中是什么?"},
],
}]

inputs = processor.apply_chat_template(
messages,
add_generation_prompt=True,
tokenize=True,
return_dict=True,
return_tensors="pt",
).to("cuda")

with torch.inference_mode():
out_ids = model.generate(**inputs, max_new_tokens=256)

response = processor.decode(
out_ids[0][inputs["input_ids"].shape[-1]:],
skip_special_tokens=True,
)
print("输出:", response)

方法 3 — 自己进行 AWQ 量化​

1. 下载原始模型​

Bash
git clone https://huggingface.co/openbmb/MiniCPM-V-4.6

2. 从源码安装 AutoAWQ​

Bash
git clone https://github.com/tc-mb/AutoAWQ.git
cd AutoAWQ
pip install -e .

3. 量化脚本​

Python
import os
import shutil
import torch
from datasets import load_dataset
from transformers import AutoTokenizer
from awq import AutoAWQForCausalLM

model_path = "/openbmb/MiniCPM-V-4.6"
quant_path = "/model_quantized/minicpmv4_6_awq"

# AWQ 配置 — 4-bit 权重,group size 128,GEMM 后端
quant_config = {
"zero_point": True,
"q_group_size": 128,
"w_bit": 4,
"version": "GEMM",
}

model = AutoAWQForCausalLM.from_pretrained(
model_path, trust_remote_code=True, torch_dtype=torch.bfloat16
)
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)


def copy_files_not_in_B(A_path, B_path):
"""如果文件在 A 中存在但 B 中不存在,则从 A 拷贝到 B(跳过权重文件)。"""
if not os.path.exists(A_path):
raise FileNotFoundError(f"The directory {A_path} does not exist.")
if not os.path.exists(B_path):
os.makedirs(B_path)

files_in_A = set(
f for f in os.listdir(A_path)
if not (".bin" in f or "safetensors" in f)
)
files_in_B = set(os.listdir(B_path))

for f in files_in_A - files_in_B:
src = os.path.join(A_path, f)
dst = os.path.join(B_path, f)
if os.path.isfile(src):
shutil.copy2(src, dst)


def load_alpaca():
data = load_dataset("tatsu-lab/alpaca", split="train")

def concatenate(x):
if x["input"] and x["instruction"]:
msgs = [
{"role": "system", "content": x["instruction"]},
{"role": "user", "content": x["input"]},
{"role": "assistant", "content": x["output"]},
]
elif x["input"]:
msgs = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": x["input"]},
{"role": "assistant", "content": x["output"]},
]
else:
msgs = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": x["instruction"]},
{"role": "assistant", "content": x["output"]},
]
text = tokenizer.apply_chat_template(
msgs, tokenize=False, add_generation_prompt=True
)
return {"text": text}

return [r["text"] for r in data.map(concatenate)][:1024]


calib_data = load_alpaca()
model.quantize(tokenizer, quant_config=quant_config, calib_data=calib_data)

model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)

copy_files_not_in_B(model_path, quant_path)
print(f'Model is quantized and saved at "{quant_path}"')