Hardware Bottleneck: AppleThunderboltRDMA hard limit of 100 MRs caps JACCL multi-node scaling
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 28.5k
- Forks
- 2.3k
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 62
Description
Hey folks,
While experimenting with multi-node Tensor Parallelism across a small Mac Studio cluster on macOS 26.3, we ran into what appears to be a memory region (MR) registration ceiling in the AppleThunderboltRDMA driver that seems to cap out around 100 MRs per device. We're not 100% sure if this is a hard driver limit or something specific to our setup, but we wanted to share what we've seen in case it's useful.
We put together a small libibverbs stress test (independent of MLX) to try to isolate the behavior, and here's what we observed across our 3 nodes:
What We Saw
| Node | RDMA Devices | Active Device | MRs Before Failure | Failure Mode |
|---|---|---|---|---|
| Mac Studio M4-1 (128GB) | 4 | rdma_en3 |
~100 | Graceful (NULL return) |
| Mac Studio M4-2 (128GB) | 4 | rdma_en3 |
~100 | Graceful (NULL return) |
| MacBook Pro M4 (36GB) | 3 | rdma_en1 |
~100 | Graceful (NULL return) |
Buffer size per MR: 512KB. Registrations used IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ.
Why It Might Matter for JACCL
If we're reading the JACCL code correctly, MR allocation scales roughly as: MRs ≈ BUFFER_SIZES × NUM_BUFFERS × size_ × 2 (send + recv). If that's right, and assuming BUFFER_SIZES=8, the math looks something like:
| TP Peers | NUM_BUFFERS | Estimated MRs | vs. ~100 Limit | |
|---|---|---|---|---|
| 2 | 4 | ~64 | 36 headroom | ✅ OK |
| 2 | 6 | ~96 | 4 headroom | ⚠️ Tight |
| 2 | 8 | ~128 | Over by ~28 | ❌ Fails |
| 3 | 4 | ~96 | 4 headroom | ⚠️ Tight |
| 3 | 5 | ~120 | Over by ~20 | ❌ Fails |
So it seems like the current NUM_BUFFERS=4 with a 2-node TP pair sits safely within limits, but there isn't much room to grow the pipeline depth without bumping into this ceiling.
Practical Impact Even at Small Scale (2–4 Nodes)
This isn't just a server-scale concern — even for small Thunderbolt-connected Mac Studio clusters (which max out at ~4 nodes physically), the MR limit constrains performance optimization:
- Pipeline depth = throughput. Increasing
NUM_BUFFERSallows more RDMA transfers to be in-flight simultaneously, which helps overlap communication with computation. AtNUM_BUFFERS=4we're leaving performance on the table, but can't safely go higher without risking the MR ceiling. - At 3 nodes,
NUM_BUFFERS=4already uses ~96 of ~100 MRs, leaving almost zero headroom. Any additional MR usage (e.g., from future features or optimizations) would push it over. - At 4 nodes, even
NUM_BUFFERS=3would use ~96 MRs, meaning the pipeline would have to be made shallower as you add nodes — the opposite of what you'd want for performance.
Being able to deepen the pipeline (e.g., NUM_BUFFERS=8) at even 2 nodes could meaningfully improve throughput by better hiding RDMA latency behind GPU compute. Right now the limit makes that impossible.
Some Observations (Take With a Grain of Salt)
- The limit being exactly 100 (a round decimal number) makes us think it might be a software-defined cap rather than a hardware constraint, but we could be wrong about that.
- On older macOS builds, we saw segfaults when exceeding this limit; on 26.3 it seems to return NULL gracefully, which is a nice improvement.
The Question
We realize this may be outside the scope of the MLX project itself, but since the distributed backend is affected by this limit — would the team happen to know if there's a way to raise it, or if it'd be possible to flag this to the macOS kernel/I/O team? Even bumping it to 256 or making it configurable via sysctl could open up a lot of headroom for multi-node inference scaling. Totally understand if this isn't something you can action on — just wanted to surface the data point.
rdma_stress.c (Reproduction Script)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <infiniband/verbs.h>
#define BUFFER_SIZE (512 * 1024)
#define MAX_MRS 256
int main() {
struct ibv_device **dev_list;
struct ibv_context *context;
struct ibv_pd *pd;
int num_devices;
dev_list = ibv_get_device_list(&num_devices);
if (!dev_list || num_devices == 0) return 1;
context = ibv_open_device(dev_list[0]);
if (!context) return 1;
pd = ibv_alloc_pd(context);
if (!pd) return 1;
struct ibv_mr *mrs[MAX_MRS];
void *buffers[MAX_MRS];
for (int i = 0; i < MAX_MRS; i++) {
posix_memalign(&buffers[i], 4096, BUFFER_SIZE);
memset(buffers[i], 0xAA, BUFFER_SIZE);
mrs[i] = ibv_reg_mr(pd, buffers[i], BUFFER_SIZE,
IBV_ACCESS_LOCAL_WRITE |
IBV_ACCESS_REMOTE_WRITE |
IBV_ACCESS_REMOTE_READ);
if (!mrs[i]) {
fprintf(stderr, "\n[!] ibv_reg_mr gracefully failed at iteration %d\n", i);
break;
}
}
return 0;
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the provided rdma_stress.c reproduction on the affected macOS and AppleThunderboltRDMA setup, then compare its results with the JACCL MR allocation described in the issue. Done would require confirming whether the limit is imposed by the driver or setup and identifying an actionable path for MLX or an upstream macOS/kernel report.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, cpp, macos
- Domain
- distributed-systems, networking, performance
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100