Get MiniMax H3 running on Apple’s MPS
People have been playing around with the latest open-weight video model MiniMax H3 (Hailuo 3) since its release. ComfyUI shipped day-0 support in this blog article, and that's when everybody started trying to run the model on their own computers—including me.
However, for a MacBook user, the PyTorch support for Apple's MPS backend is not really that great. The MiniMax H3 workflow happens to step right on one of its gaps: the INT8 ConvRot model requires aten::_int_mm, an operator for which PyTorch currently has no MPS kernel. This post is about how I got around that, and a small ComfyUI custom node you can install to do the same.
The symptom
If your workflow fails or gets stuck on SamplerCustomAdvanced at 0%, with this line in the log:
The operator 'aten::_int_mm' is not currently supported on the MPS backend and will fall back to run on the CPU. This may have performance implications.
then you have encountered the same problem as me. Depending on your setup, this manifests in one of two ways: with MPS fallback enabled, PyTorch copies the operands to the CPU, does the matrix multiplication there, and copies the result back—synchronization, data-transfer, and unified-memory overhead included. Without fallback enabled, the same operation simply fails.
Either way, the sampler is not going anywhere.
The quick fix
I made ComfyUI-MPS-INT8, a ComfyUI custom node that sidesteps the unsupported operator entirely. To install it:
Download the whole repository as a folder from GitHub.
Place it in ComfyUI's custom-node directory, e.g.:
~/ComfyUI-Installs/ComfyUI/ComfyUI/custom_nodes/ComfyUI-MPS-INT8/
Restart ComfyUI.
When the required runtime support is available, the extension registers a backend named mps_int8_native ahead of the eager comfy-kitchen backend, and the warning disappears for supported layers. That's it—if you just wanted your workflow to run, you can stop reading here.
If you are curious why this works, read on.
Why the warning happens
The warning comes from comfy-kitchen's default eager INT8 implementation, which was designed around an INT8-by-INT8 matrix multiplication path commonly used on CUDA.
For a quantized linear layer, comfy-kitchen normally:
- Applies the optional input activation and ConvRot transformation.
- Dynamically quantizes each activation row to INT8.
- Multiplies the INT8 activations by the INT8 weights using
torch.int8_mmortorch._int_mm. - Applies the activation and weight scales.
- Converts the result to the requested floating-point output type and adds the bias.
In simplified form, the eager computation is:
Here, and are INT8 tensors, while _int_mm produces INT32 accumulators. The problem is specifically step 3: PyTorch does not provide a native MPS kernel for aten::_int_mm, so the whole path either falls back to the CPU or fails.
This gap has been reported upstream in comfy-kitchen#92, and native _int_mm support for MPS is tracked in pytorch/pytorch#141287. Until either lands, though, the workflow needs another way around the operator.
How ComfyUI-MPS-INT8 avoids it
ComfyUI-MPS-INT8 does not implement _int_mm, nor does it suppress the warning. It avoids the operator entirely.
When ComfyUI loads the custom node, it registers a new comfy-kitchen backend named mps_int8_native. The backend is inserted ahead of the generic eager backend and is selected only when:
- The activation tensor is on MPS.
- The weights use
torch.int8. - The floating-point tensors use FP16, BF16, or FP32.
- The installed PyTorch build has a native MPS kernel for
aten::_weight_int8pack_mm.
Instead of calling _int_mm, the backend uses aten::_weight_int8pack_mm—a different PyTorch operator that does have a native MPS implementation. It accepts floating-point activations, INT8 weights, and weight scales. The weights remain stored as INT8, while their dequantization and multiplication are handled inside the MPS kernel. This avoids both the unsupported INT8-by-INT8 operation and the need to materialize a complete BF16 or FP32 copy of every weight matrix.
By default, the backend retains comfy-kitchen's row-wise activation quantization:
Although is converted back to the requested floating-point dtype before multiplication, its quantized integer values are preserved, and the activation scale is applied afterward. This reproduces the same quantization structure as the eager path while routing the expensive matrix multiplication through the native MPS weight-INT8 kernel.
Before invoking the native operator, the backend also does some housekeeping:
- Applies the requested input activation.
- Performs the optional ConvRot Hadamard transformation.
- Flattens the activation into a two-dimensional matrix.
- Expands scalar weight scales or validates per-output-channel scales.
- Moves the weights and scales to MPS without converting the weights to floating point.
- Matches the scale
dtypeto the activationdtype. - Restores the original output shape and adds the bias.
One caveat worth knowing: the native MPS operator requires both dimensions of the INT8 weight matrix to be divisible by 32. The backend validates this requirement explicitly, so unsupported shapes raise a clear error instead of silently returning to the _int_mm CPU-fallback path.
What this does (and does not) solve
The warning disappears for supported layers because aten::_int_mm is no longer executed. The computation is redirected to aten::_weight_int8pack_mm, which has an native MPS kernel and runs on the Apple GPU.
Therefore, this only solves this specific fallback issue. It does not provide a general MPS implementation for every INT8 operator, and it will not prevent unrelated unsupported operators from falling back to the CPU. And once the upstream fix for comfy-kitchen#92 lands, this custom node should no longer be necessary. But if your MiniMax H3 workflow was also stuck at 0% on your Mac, this should be enough to get it moving.
Hope this helps fellow MacBook users get MiniMax H3 running locally!