Tech11 min read

Running MiniCPM-V 4.7 35B on a 16GB M4 Mac mini: MoE Setup and Benchmarks

IkesanContents

OpenBMB claims that MiniCPM-V 4.7 35B-A3B runs on an iPhone using just 3GB of RAM.
Curious whether it would run on a 16GB M4 Mac mini, we set it up to test firsthand. If it worked, we wanted to evaluate it against prior LLM comparisons on identical coding, reasoning, and table extraction benchmarks.

Complete Package Size vs. MoE Expert Splitting

According to the official OpenBMB README, the model generates over 13 tokens per second (13 tokens/s) on an iPhone 17 Pro Max.
The target model is MiniCPM-V 4.7 35B-A3B. A separate 1B edition exists, but we focused strictly on the 35B-A3B version.

The 35B-A3B architecture is a Mixture of Experts (MoE) model with roughly 35 billion total parameters, activating approximately 3 billion parameters per token.
MoE systems select specialized computational subsets (experts) depending on incoming tokens. Because unselected experts may be called in subsequent tokens, the complete weight collection still needs persistent storage.

Checking the official mobile apps documentation, the mobile bundle splits into a core file and distinct expert files.
The experts reside on the SSD, loading into RAM on demand. This differs from conventional GGUF models where the entire network footprint is held in memory.

Package FormatFile SizeSetup Usage
Standard Q4_K_M21.17 GB (vision weights separate)Not used
LES Core GGUF1.66 GBUsed
LES 40 Expert Files13.84 GB totalUsed
mmproj Vision Projector1.12 GBUsed for vision tasks
Complete LES Bundle16.62 GB (includes manifest)Stored on internal SSD

Sizes were calculated from byte counts in the standard release repo and the split release repo (using 1 GB = 1 billion bytes). We used the tc-mb/MiniCPM-V-Apps-gguf repository referenced by OpenBMB.

Speculative Loading with LES

Reading expert weights from SSD on demand introduces latency that bottlenecks generation speed.
To mitigate this, Lookahead Expert Scheduling (LES) speculatively predicts which experts subsequent layers will need, overlapping SSD read operations with GPU computation.

The prediction governs read order; it does not drop unpredicted experts chosen by the actual router. All routed experts are loaded and calculated. We did not artificially restrict the active expert count.

Official benchmarks also use quantization and DSpark speculative decoding. For our initial baseline, we used mixed quantization with LES while keeping DSpark disabled.

Building and Setup on an M4 Mac mini

The test environment was configured as follows, placing all model files on the internal SSD to maximize transfer rates:

ComponentSpecification
MachineMac mini (Apple M4)
Memory16 GiB (17,179,869,184 bytes)
OSmacOS 26.5.2
Inference EngineOpenBMB/llama.cpp-omni (commit 0b74404)
GPU BackendMetal
ModelMiniCPM-V-4_7-35B-A3B-mix_q3gud_im-les.gguf and associated experts
Model Revision8716e5209f308fd53f197ccd7d50c6fdcc2dc37d
ScriptsPython 3.14.4 within a dedicated uv virtual environment

The core model file cannot run without the experts/ directory and mmproj-model-f16.gguf.
The 43 files totaled 16,623,585,508 bytes.

The engine was built from source. The specific commit lacked a standard llama-server build target, so we compiled and ran via llama-cli. We did not modify engine source files.

cmake -S llama.cpp-omni -B llama.cpp-omni/build \
  -DCMAKE_BUILD_TYPE=Release -DGGML_METAL=ON \
  -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_APP=OFF -DLLAMA_BUILD_UI=OFF
cmake --build llama.cpp-omni/build --target llama-cli -j 4

A smoke test prompt (“What is the capital of Japan? Answer only the city name in Japanese.”) returned “東京” (Tokyo).
Total run time was approximately 6.6 seconds, with a peak resident set size (RSS) of 2.46 GB and a reported generation speed of 10.0 tokens/s. Because the reply was so brief, this prompt was not used for throughput evaluation.

Running Benchmark Tasks

We reused the test prompts and validation harness from our earlier comparison, omitting an ambiguous edge question from task D.

TaskObjectiveEvaluation Metric
Python Interval MergingMerge overlapping intervals; keep touching endpoints separate; handle invalid types24 test cases including empty input, NaN, and huge integers
Event AggregationDeduplicate by highest version, break ties by arrival order, sum paid events by currencyMatch against golden JSON
Document Fact VerificationDistinguish inputs from outputs, preview from GA, and pricing constraintsCorrectness on 3 targeted questions

All three tasks were combined into a single prompt and submitted across three independent runs per model.
Generated Python scripts were checked for unauthorized network or file access before local execution. Models had no runtime Internet or tool execution access.

Thinking mode (thinking) produces internal reasoning tokens before final output. We disabled it (off) for the initial baseline comparison across MiniCPM-V, Qwen, and DeepSeek.

ModelInvocationParameters
MiniCPM-V 4.7 35B-A3BLocal llama-clithinking off, temperature 0.2, seeds 43/44/45
Qwen3.8 MaxModelScope (Qwen-Ambassador account)enable_thinking=false, temperature 0.2
GPT-6.1 SolCodex CLI 0.162.1medium reasoning effort
Claude Opus 5.5Claude Code 2.1.285medium reasoning effort
DeepSeek-V4.1-FlashModelScopethinking.type=disabled, temperature 0.2

MiniCPM-V was configured with an 8,192-token context window and a 3,072-token output limit. Qwen and DeepSeek also had a 3,072-token limit. Sol and Opus used CLI defaults with reasoning set to medium.

MiniCPM-V expert caching parameters were set to --moe-slot-bank 640, LLAMA_MOE_DECODE_WINDOW=16, and lookahead depth 8. Batch and micro-batch were both 128. Processes restarted clean between runs.

Text Benchmark Results

Across three test runs, all models scored 100% on event aggregation (3/3) and fact verification (9/9).
The differentiator was the Python interval merging problem: MiniCPM-V and Qwen3.8 Max scored 23/24 across all three runs, while the other three models scored 24/24.

ModelCode Passing (out of 24)Response Time (Mean ± Std Dev)
MiniCPM-V 4.7 35B-A3B23, 23, 2393.24 ± 2.17 s
Qwen3.8 Max23, 23, 237.76 ± 0.18 s
GPT-6.1 Sol24, 24, 2419.21 ± 2.90 s
Claude Opus 5.524, 24, 2418.82 ± 0.74 s
DeepSeek-V4.1-Flash24, 24, 244.13 ± 0.38 s

Response times include process startup, model loading, and network latency for cloud endpoints. MiniCPM-V executed locally on the M4 Mac mini, whereas the others ran on cloud APIs.

OverflowError in Float Conversion on Large Integers

Both MiniCPM-V and Qwen3.8 Max failed on an edge case interval with an endpoint of 10**400.
Both implementations called math.isnan() or math.isinf() on integer inputs, triggering an OverflowError in Python 3.14.4.

MiniCPM-V’s generated solution checked values without distinguishing integers from floating-point numbers:

if math.isnan(start) or math.isnan(end):
    raise ValueError("NaN values are not allowed")
if math.isinf(start) or math.isinf(end):
    raise ValueError("Infinite values are not allowed")

Under Python’s math library, isnan and isinf expect floating-point arguments. Passing an arbitrary-precision integer that exceeds float limits triggers an explicit overflow during float conversion.

In contrast, Sol, Opus, and DeepSeek verified finiteness only when the input was already a float:

if isinstance(endpoint, float) and not math.isfinite(endpoint):
    raise ValueError('endpoints must be finite')

Testing with Japanese Order Table Images

Because MiniCPM-V supports visual inputs, we tested it with an image of a Japanese order table.
The test image was a 1,000×650 pixel PNG containing five order rows.

Japanese order table with five rows showing columns for Order ID, Status, Quantity, and Unit Price

We instructed models to transcribe all rows into JSON, calculate the total value of paid orders, identify the paid order with the highest value, and return null for date (which is absent from the image). The table contains the following data:

Order IDStatusQuantityUnit Price
A-17Paid (支払済)31,200
B-04Pending (保留)23,400
C-23Paid (支払済)7450
D-81Cancelled (キャンセル)52,100
E-95Paid (支払済)4875

The correct paid total is 10,250 yen (3×1,200 + 7×450 + 4×875), the highest paid order is A-17 (3,600 yen), and the date is null.

We compared MiniCPM-V with Qwen3.5-35B-A3B on ModelScope and GPT-6.1 Sol via Codex CLI, sending identical prompts across three runs.

All three models achieved perfect OCR transcription (3/3) and correctly returned null for the missing date (3/3).
The divergence came during arithmetic summation and highest-value identification.

ModelPaid Sum CorrectHighest Order ID CorrectResponse Time (Mean ± Std Dev)
MiniCPM-V 4.7 35B-A3B0/33/368.09 ± 1.21 s
Qwen3.5-35B-A3B0/32/37.57 ± 0.80 s
GPT-6.1 Sol3/33/312.78 ± 1.36 s

MiniCPM-V returned 7,700 yen on all three runs.
Qwen3.5 returned 8,500 yen, 9,200 yen, and 8,300 yen, misidentifying the highest order as C-23 on its second run. Sol answered accurately on all attempts.

The JSON output from both MiniCPM-V and Qwen captured every row, quantity, and price accurately. The OCR stage succeeded; the arithmetic step failed. Extracting data to JSON and computing sums with code avoids this arithmetic issue.

Comparing Input Formats and Thinking Mode

To isolate whether arithmetic errors stemmed from visual encoding or core reasoning, we fed the same five rows as plain text and JSON.
We toggled reasoning mode (thinking on/off) across three runs per format on MiniCPM-V using identical seeds.

We also added three runs of Qwen3.5-35B-A3B with thinking on on the original image. Every configuration maintained 100% transcription accuracy (3/3).

ModelFormatThinkingPaid Sum CorrectResponse Time (Mean ± Std Dev)
MiniCPM-VImageoff0/368.09 ± 1.21 s
MiniCPM-VImageon1/3102.14 ± 66.96 s
MiniCPM-VTextoff0/346.82 ± 3.65 s
MiniCPM-VTexton3/3142.53 ± 18.51 s
MiniCPM-VJSONoff0/347.79 ± 3.90 s
MiniCPM-VJSONon3/3119.37 ± 17.18 s
Qwen3.5-35B-A3BImageoff0/37.57 ± 0.80 s
Qwen3.5-35B-A3BImageon3/310.44 ± 0.87 s

Without thinking mode (off), MiniCPM-V failed the arithmetic step across text (8,000 / 7,700 / 7,700 yen) and JSON (8,900 yen on all runs). The calculation errors occurred independently of visual processing.

Enabling thinking mode (on) resolved all arithmetic errors for text and JSON inputs (3/3).
On image input, however, MiniCPM-V scored only 1/3 with thinking enabled. On the two failed attempts, generation stopped prematurely while writing raw JSON inside the reasoning block (<think> tags) and left the outputs incomplete.

Enabling thinking mode on Qwen3.5-35B-A3B brought image arithmetic to 3/3 accuracy.

Additional Evaluation Across 10 Generated Tables

To test whether these observations were artifacts of a single table, we evaluated 10 distinct synthetic table images varying in row order, quantities, unit prices, and paid status.
Layout and typography remained identical: 5 rows each, with 1 to 4 paid rows.

All models achieved 10/10 on row transcription and 10/10 on returning null for dates.

ModelModePaid Sum CorrectHighest ID CorrectResponse Time (Mean ± Std Dev)
MiniCPM-Voff4/108/1061.95 ± 0.76 s
MiniCPM-Von3/108/1073.95 ± 33.50 s
Qwen3.5-35B-A3Boff2/108/106.96 ± 3.30 s
Qwen3.5-35B-A3Bon10/1010/1010.36 ± 2.02 s
GPT-6.1 Solmedium10/1010/1010.32 ± 1.63 s

For MiniCPM-V with thinking on, 9 out of 10 runs terminated inside the reasoning log before producing clean final answers. The single run that generated proper closing tags answered the arithmetic correctly.

Recalculating the totals by piping transcribed JSON outputs into Python produced 100% correct answers across all 50 runs.
Separating OCR transcription from arithmetic aggregation bypasses these calculation errors.

Memory Usage Across Workloads (Peak RSS)

We tracked peak resident set size (RSS) using /usr/bin/time -l alongside reported generation throughput:

WorkloadPeak RSS RangeGeneration Throughput
3 Text Tasks (No Images)3.52–3.53 GB7.6–8.0 tokens/s
1 Japanese Table Image4.60–4.61 GB5.8–6.0 tokens/s

The 16GB M4 Mac mini handled image inference without memory exhaustion.
The 3GB memory footprint advertised for iPhone targets iOS environments and differs from peak RSS accounting on macOS.

Text Generation Speed and Memory with DSpark

DSpark accelerates inference through speculative decoding, using a smaller draft model to generate candidate tokens verified in parallel by the main model.
We downloaded the official DSpark weights and converted them to an F16 GGUF file (approximately 789 MB).

While vision speculative decoding encountered Metal buffer constraints and positional embedding errors, text speculative decoding ran stably. We compared three runs each with DSpark enabled and disabled:

SettingParameter Value
Input5-row table passed as text
Thinking / Temperatureoff / 0
Context / Max Output8,192 / 1,024 tokens
Expert Cache Width / Lookahead32 / 8
Slot Bank640
DSpark Draft CandidatesUp to 3 tokens per step
DSparkSpeed (Mean ± Std Dev)Response Time (Mean ± Std Dev)Peak RSS Range
Disabled7.36 ± 0.23 tokens/s45.17 ± 1.35 s4.40–4.46 GB
Enabled8.05 ± 0.14 tokens/s43.24 ± 1.05 s5.52–5.53 GB

On this text query, DSpark increased generation throughput by roughly 9.5% and reduced total response time by 4.3%.
Peak RSS increased by approximately 1.1 GB. Across all runs, 201 out of 216 speculative draft tokens were accepted by the main model.

Test scripts, prompts, generated code, and raw outputs are stored in LiltingChannelLabo at 2026/10/14/minicpm-v-4-7-mac-mini/.