MiniCPM-V 4.7
MiniCPM-V Multimodal Series
Trade smaller weights for lower GPU memory and storage. GGUF suits local and CPU / Metal runs, BNB / AWQ / GPTQ target GPU inference, while the MoE experts in 35B-A3B require llm-compressor. Use the official pre-quantized weights, or quantize yourself with the scripts on this page.
| Format | 1B | 35B-A3B | Repo suffix | Tool |
|---|---|---|---|---|
| GGUF | ✅ | ✅ | -gguf | llama.cpp |
| BNB (int4) | ✅ | Use GGUF / pre-quantized weights instead | -int4 | bitsandbytes |
| AWQ | ✅ | Use GGUF / pre-quantized weights instead | -AWQ | AutoAWQ |
| GPTQ | ✅ | Use GGUF / pre-quantized weights instead | -GPTQ | AutoGPTQ |
| FP8 / INT4 | — | ✅ | -FP8 / -INT4 | llm-compressor |
MiniCPM-V 4.7 35B-A3B's MoE experts are stored as fused parameters (mlp.experts.gate_up_proj, mlp.experts.down_proj). bitsandbytes / AutoAWQ / AutoGPTQ only quantize nn.Linear, leaving the experts (about 92% of weights) at bf16 (saved checkpoint still ~60 GB). For usable 4-bit on 35B-A3B, use GGUF or the published -INT4 / -FP8 weights, see FP8 / INT4.
GGUF
Convert the PyTorch model to GGUF and quantize it, producing outputs for llama.cpp or Ollama.
Pre-built GGUFs are available: openbmb/MiniCPM-V-4.7-1B-gguf / openbmb/MiniCPM-V-4.7-35B-A3B-gguf.
Convert to GGUF
Run from the root of a current (includes PR #29416) llama.cpp checkout:
# Step 1 — convert the language model + vision merger to F16 GGUF
python ./convert_hf_to_gguf.py /path/to/MiniCPM-V-4.7-1B \
--outfile /path/to/MiniCPM-V-4.7-1B/MiniCPM-V-4_7-1B-F16.gguf \
--outtype f16
# Step 2 — convert the vision projector (mmproj)
python ./convert_hf_to_gguf.py /path/to/MiniCPM-V-4.7-1B \
--mmproj \
--outfile /path/to/MiniCPM-V-4.7-1B/mmproj-model-f16.gguf
convert_hf_to_gguf.py autodetects MiniCPMV4_7ForConditionalGeneration from config.json.
To export the 4× vision variant: copy the model directory, change "downsample_mode" to "4x" in preprocessor_config.json, then run --mmproj again to produce mmproj-model-f16-4x.gguf.
Quantize to INT4
Once you have the F16 LM GGUF, quantize it with llama-quantize:
./llama-quantize \
/path/to/MiniCPM-V-4.7-1B/MiniCPM-V-4_7-1B-F16.gguf \
/path/to/MiniCPM-V-4.7-1B/MiniCPM-V-4_7-1B-Q4_K_M.gguf \
Q4_K_M
Other common quant types: Q5_K_M, Q6_K, Q8_0, pick based on your accuracy vs. memory tradeoff. The mmproj file is already small — keep it at F16 and pair it with whichever quantization you chose for the LM.
35B-A3B mixed quantization (recommended)
The MoE expert tensors in 35B-A3B account for about 92% of weights. On top of the default Q4_K_M mixture, reduce just the expert gate / up / down to Q3_K and use imatrix to produce a model around 14.4 GiB with accuracy comparable to Q4_K_M.
tensor-types.txt content:
ffn_(gate|up|down)_exps=Q3_K
Quantize with imatrix:
./llama-quantize \
--imatrix /path/to/v47-35a3.imatrix.gguf \
--tensor-type-file ./tensor-types.txt \
/path/to/MiniCPM-V-4.7-35B-A3B/MiniCPM-V-4_7-35B-A3B-F16.gguf \
/path/to/MiniCPM-V-4.7-35B-A3B/MiniCPM-V-4_7-35B-A3B-mix_q3gud_im.gguf \
Q4_K_M 32
--tensor-type-file is first-match-wins — more specific rules go first.
BNB (int4)
Load the original model, quantize to NF4 with bitsandbytes, run an inference sanity-check, then save the quantized model.
import os
import time
import torch
from PIL import Image
from transformers import AutoProcessor, AutoModelForImageTextToText, BitsAndBytesConfig
assert torch.cuda.is_available(), "GPU is required."
model_path = "openbmb/MiniCPM-V-4.7-1B" # Original model
save_path = "./MiniCPM-V-4.7-1B-int4" # Save path
image_path = "./assets/airplane.jpeg"
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
load_in_8bit=False,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_quant_storage=torch.uint8,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
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="auto",
quantization_config=quantization_config,
)
image = Image.open(image_path).convert("RGB")
messages = [{
"role": "user",
"content": [
{"type": "image", "image": image},
{"type": "text", "text": "What is in this image?"},
],
}]
inputs = processor.apply_chat_template(
messages, add_generation_prompt=True, tokenize=True,
return_dict=True, return_tensors="pt",
).to(model.device)
start = time.time()
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("Quantized output:", response)
print("Quantized inference time:", time.time() - start)
os.makedirs(save_path, exist_ok=True)
model.save_pretrained(save_path, safe_serialization=True)
processor.save_pretrained(save_path)
The quantized model is saved in save_path, and can be reloaded with AutoModelForImageTextToText.from_pretrained(save_path).
Results (1B):
| Item | Value |
|---|---|
| Precision | 4 (NF4, double quant) |
quant_method | bitsandbytes |
| Size | ~1.5 GB (bf16 is 3.0 GB) |
AWQ
The AutoAWQ workflow for MiniCPM-V is hosted at tc-mb/AutoAWQ (the upstream casper-hansen/AutoAWQ is archived). This fork adds minicpmv4_7 architecture registration.
Use pre-quantized model + vLLM
from PIL import Image
from transformers import AutoProcessor
from vllm import LLM, SamplingParams
MODEL_NAME = "openbmb/MiniCPM-V-4.7-1B-AWQ"
image = Image.open("image.png").convert("RGB")
processor = AutoProcessor.from_pretrained(MODEL_NAME)
llm = LLM(model=MODEL_NAME, max_model_len=8192)
messages = [{
"role": "user",
"content": [
{"type": "image"},
{"type": "text", "text": "Describe the content of this image"},
],
}]
prompt = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
outputs = llm.generate(
{"prompt": prompt, "multi_modal_data": {"image": image}},
SamplingParams(temperature=0.0, max_tokens=1024),
)
print(outputs[0].outputs[0].text)
Load AWQ weights with AutoAWQ
git clone https://github.com/tc-mb/AutoAWQ.git
cd AutoAWQ
pip install -e .
import torch
from PIL import Image
from transformers import AutoProcessor
from awq import AutoAWQForCausalLM
model_path = "openbmb/MiniCPM-V-4.7-1B-AWQ"
image_path = "./assets/airplane.jpeg"
model = AutoAWQForCausalLM.from_quantized(model_path).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": "What is in this image?"},
],
}]
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)
print("Output:", processor.decode(out_ids[0][inputs["input_ids"].shape[-1]:], skip_special_tokens=True))
Quantize yourself
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.7-1B"
quant_path = "./MiniCPM-V-4.7-1B-AWQ"
quant_config = {"zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM"}
model = AutoAWQForCausalLM.from_pretrained(model_path, torch_dtype=torch.bfloat16)
model.model.to("cuda:0") # Ensure backbone is on GPU
tokenizer = AutoTokenizer.from_pretrained(model_path)
def copy_files_not_in_B(A_path, B_path):
if not os.path.exists(B_path):
os.makedirs(B_path)
files_in_A = {f for f in os.listdir(A_path) if not (".bin" in f or "safetensors" in f)}
for f in files_in_A - set(os.listdir(B_path)):
src, dst = os.path.join(A_path, f), 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):
msgs = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": x["instruction"]},
{"role": "assistant", "content": x["output"]},
]
return {"text": tokenizer.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)}
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 quantized and saved to "{quant_path}"')
Results (1B):
| Item | Value |
|---|---|
| Quantization method | awq (GEMM) |
| Precision / group size | 4 / 128 |
| Zero point | Yes |
| Size | ~2.3 GB (bf16 is 3.0 GB) |
GPTQ
The AutoGPTQ fork for MiniCPM-V is tc-mb/AutoGPTQ. MiniCPM-V 4.7 1B's text backbone is qwen3_5_text, so it reuses the existing Qwen3_5GPTQForCausalLM quantizer.
Use pre-quantized model + vLLM
from PIL import Image
from transformers import AutoProcessor
from vllm import LLM, SamplingParams
MODEL_NAME = "openbmb/MiniCPM-V-4.7-1B-GPTQ"
image = Image.open("image.png").convert("RGB")
processor = AutoProcessor.from_pretrained(MODEL_NAME)
llm = LLM(model=MODEL_NAME, max_model_len=2048)
messages = [{"role": "user", "content": [
{"type": "text", "text": "(<image>./</image>)\nDescribe the content of this image"},
]}]
prompt = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
outputs = llm.generate(
{"prompt": prompt, "multi_modal_data": {"image": image}},
SamplingParams(temperature=0.0, max_tokens=1024),
)
print(outputs[0].outputs[0].text)