Real-model benchmark

September 17, 2026 · View on GitHub

Apple M4 / 16GB; Qwen3.5-0.8B 4-bit weights, float32 computation, batch size 4. One dog photo resized within 768×768, identical context and questions across paths. Model loading and warmup excluded. Three measured repetitions, order rotated. Values below are medians, not best runs.

generate_json calls the actual MLX-VLM generate() with greedy decoding and a compact object prompt. No JSON repair, forced grammar, probability fields or verbose explanation. independent is direct label-logit scoring without prefix reuse; shared reuses one visual/context prefill. All generated raw outputs and candidate results are retained in results.json.

DecisionsPathTotal msVision msPrefill msScoring/decode msLM forwardsVision forwardsPeak Metal GBRequested decisions/sValid runs
1generate_json810.6235.7443.683.01212.601.233/3
1independent598.5235.7324.14.8112.371.673/3
1shared630.0240.0294.737.6212.371.593/3
4generate_json1123.0237.6508.5328.43812.603.560/3
4independent2351.5967.61278.514.9442.371.703/3
4shared678.6239.7289.4108.4212.375.893/3
16generate_json1276.9242.9880.597.11412.6812.530/3
16independent9313.53877.95085.770.316162.371.723/3
16shared1051.5246.9300.1438.1512.3715.223/3
64generate_json2732.2236.72340.479.61512.8523.420/3
64independent37298.115538.720489.8291.164642.371.723/3
64shared2399.1240.1292.61777.21712.3626.683/3

Requested decisions/s is workload count divided by elapsed time, NOT completed throughput when generation fails. Each failed generated request has valid_decisions_per_second=0 in raw data. Typed-score paths assemble all fields deterministically; schema validity is not semantic accuracy.

Vision timing includes the encoder and multimodal embedding assembly; prefill is language-context forward work; scoring is suffix/projection/normalization, or decode forwards for generate. Cache fork, preprocessing, prompt assembly and output work are in total time; phase medians need not sum to the total median. Memory is peak live Metal allocation, not RSS, total system memory or discrete VRAM. Actual forwards are counted and synchronized. Generate prefill uses 512-token chunks to keep memory bounded.

Every latency sample (ms)

DecisionsPathSamples
1generate_json813.5, 805.1, 810.6
1independent630.0, 598.5, 585.5
1shared630.0, 599.5, 635.5
4generate_json1099.1, 1134.8, 1123.0
4independent2351.5, 2324.1, 2370.9
4shared692.1, 678.6, 674.6
16generate_json1276.9, 1243.1, 1283.8
16independent10001.9, 9313.5, 9212.6
16shared1052.8, 1051.5, 1021.3
64generate_json2757.3, 2732.2, 2590.7
64independent37298.1, 37828.3, 36035.7
64shared2399.1, 2390.8, 2409.4

Semantic agreement and failures

  • 1 decisions: direct/shared maximum probability delta 0.00000009; argmax agreement 100.0%; generated/shared agreement 1.0. None means generated output did not meet the declared schema.
  • 4 decisions: direct/shared maximum probability delta 0.00000050; argmax agreement 100.0%; generated/shared agreement None. None means generated output did not meet the declared schema.
  • 16 decisions: direct/shared maximum probability delta 0.00000274; argmax agreement 100.0%; generated/shared agreement None. None means generated output did not meet the declared schema.
  • 64 decisions: direct/shared maximum probability delta 0.00000274; argmax agreement 100.0%; generated/shared agreement None. None means generated output did not meet the declared schema.

Example failed generate output (4 decisions, repeat 0):

{"q00": "A dog", "q01": "White", "q02": "Outdoors", "q03": "Yes"}

Example failed generate output (16 decisions, repeat 0):

{"q00": "A dog"}

Example failed generate output (64 decisions, repeat 0):

{"other": "Another surface"}

The eight criteria are repeated to exercise 64-decision scaling; this is not a general accuracy benchmark or calibration experiment. Performance varies with image resolution, context, candidates, machine load and thermal state. Multi-token scoring correctness is independently checked against stock full-vocabulary model forwards in benchmarks/scoring-verification.json; this throughput table uses the default stable-label mode.