馃悰 Describe the bug
On iOS, the MLX delegate consumes far more memory for one specific model than the rest of its own family, to the point of being killed by jetsam.
whisper-small exported to MLX at bf16 (484 MB .pte) is terminated on an iPhone 16 with reason: per-process-limit at 3.80 GB resident, after about 17 s of CPU time. Every other Whisper size and precision runs fine on the same device, same runtime, same harness, and stays under 2x its file size in resident growth.
Measurements
Resident size (TASK_BASIC_INFO.resident_size, the metric jetsam's rpages counts), sampled in a freshly launched process per variant so no residue carries between models. Baseline is sampled before any model is loaded and was stable at 554 to 556 MB across all six launches.
| variant |
.pte size |
after load |
after inference |
delta over baseline |
delta / file |
| tiny/bf16 |
76.1 MB |
578.1 MB |
681.3 MB |
126.2 MB |
1.66x |
| tiny/int8 |
59.9 MB |
578.1 MB |
664.4 MB |
110.2 MB |
1.84x |
| base/bf16 |
145.9 MB |
578.4 MB |
754.5 MB |
199.8 MB |
1.37x |
| base/int8 |
102.6 MB |
579.5 MB |
711.1 MB |
156.1 MB |
1.52x |
| small/int8 |
289.5 MB |
584.6 MB |
907.6 MB |
351.9 MB |
1.22x |
| small/bf16 |
484.4 MB |
n/a |
killed |
~3245 MB |
~6.70x |
The last row is derived from the jetsam report (rpages 232153 x 16 KB = 3.80 GB) minus the 555 MB baseline.
Why this looks like a bug rather than expected cost
- The ratio decreases with model size for every variant that runs (bf16: 1.66x, 1.37x, then expected ~1.2x). Extrapolating,
small/bf16 should land near 580 MB of growth, roughly 1.1 GB total. It instead reached 3.2 GB of growth, about 5.6x more than the trend predicts.
- bf16 is not inherently heavier than int8 here. At
base, bf16 is cheaper than int8 (1.37x vs 1.52x), so this is not simply a dtype conversion cost.
- Loading is not where the memory goes.
after load sits 24 to 29 MB above baseline for every variant regardless of file size, consistent with the .pte being mmapped. The growth appears during inference.
So it is not explained by "bf16 doubles to fp32" (that would be about 2x, uniformly) nor by "large models scale badly" (the ratio improves with size).
Versions
- ExecuTorch 1.4.1
- Device: iPhone 16 (iPhone17,3), iOS 26.5, Release-configuration delegate
- Backend:
MLXPartitioner with get_default_passes() and EdgeCompileConfig(_check_ir_validity=False, _skip_dim_order=True)
- Two-method program (
encode, decode) plus a get_model_schema constant method
- Model: OpenAI Whisper small, compute dtype bf16, LayerNorm forced to fp32, KV cache via
mlx::kv_cache_update + mlx::custom_sdpa
I can share the exporter and the failing .pte if useful. What I cannot determine from outside the delegate is which allocation accounts for the excess, so I have characterised the anomaly rather than diagnosed it.
cc @metascroy
馃悰 Describe the bug
On iOS, the MLX delegate consumes far more memory for one specific model than the rest of its own family, to the point of being killed by jetsam.
whisper-smallexported to MLX at bf16 (484 MB.pte) is terminated on an iPhone 16 withreason: per-process-limitat 3.80 GB resident, after about 17 s of CPU time. Every other Whisper size and precision runs fine on the same device, same runtime, same harness, and stays under 2x its file size in resident growth.Measurements
Resident size (
TASK_BASIC_INFO.resident_size, the metric jetsam'srpagescounts), sampled in a freshly launched process per variant so no residue carries between models. Baseline is sampled before any model is loaded and was stable at 554 to 556 MB across all six launches.The last row is derived from the jetsam report (
rpages232153 x 16 KB = 3.80 GB) minus the 555 MB baseline.Why this looks like a bug rather than expected cost
small/bf16should land near 580 MB of growth, roughly 1.1 GB total. It instead reached 3.2 GB of growth, about 5.6x more than the trend predicts.base, bf16 is cheaper than int8 (1.37x vs 1.52x), so this is not simply a dtype conversion cost.after loadsits 24 to 29 MB above baseline for every variant regardless of file size, consistent with the.ptebeing mmapped. The growth appears during inference.So it is not explained by "bf16 doubles to fp32" (that would be about 2x, uniformly) nor by "large models scale badly" (the ratio improves with size).
Versions
MLXPartitionerwithget_default_passes()andEdgeCompileConfig(_check_ir_validity=False, _skip_dim_order=True)encode,decode) plus aget_model_schemaconstant methodmlx::kv_cache_update+mlx::custom_sdpaI can share the exporter and the failing
.pteif useful. What I cannot determine from outside the delegate is which allocation accounts for the excess, so I have characterised the anomaly rather than diagnosed it.cc @metascroy