Replies: 2 comments 1 reply
Thanks for writing this up. It was the only prior art I could find and it stopped me returning three monitors I'd assumed were faulty. I ran the same panels on a different topology for two weeks and ended up with the constants behind the behaviour you measured. Short version:
The modelEvery external display controller publishes its limits. One command:
Units are 10⁵ active px/s, so one pipe = 1.37 Gpx/s. A display is granted a seat of one or two pipes. BetterDisplay already reports it as What each mode costs, and therefore what it's granted:
Four pipes, and a 165 Hz display eats two of them. So:
Exactly one display can hold a pair, so exactly one can exceed
|
| Rate | Units | Margin | Replug | Cold boot | |
|---|---|---|---|---|---|
| 85 | 12,534 | 5.6% | ✅ | ✅ | reliable |
| 86 | 12,681 | 4.4% | ✅ | ✅ | highest passing rate |
| 87 | 12,829 | 3.3% | ❌ | ||
| 88 | 12,976 | 2.2% | ❌ | ✅ | passed cold boot, died on first replug |
| 90 | 13,271 | 0% | ❌ |
The allocator wants 4 to 5 percent headroom below the refusal point. Two things worth stealing: cold boot is not the hardest test, re-enumeration is, and 12,976 is intermittent rather than dead, which is why 88 and 90 both looked fine before they weren't.
Your three questions
1. Does IOKit corroborate the model? Yes, above. Your framing is right in shape. The mechanism is quantised rather than continuous, and the quanta are published.
2. Why does HDMI act as an independent pipe? Here it doesn't, and this contradicts your finding 6. On this machine the output path makes no difference at all. Every combination gives every panel an identical 13700 seat, 4 lanes, 16.00 bpp:
| Topology | |
|---|---|
| HDMI + 2× TB5 direct | ✅ |
| HDMI + one wing direct + one on the hub | ✅ |
| centre on USB-C, no HDMI connected at all | ✅ |
| all three on USB-C | ✅ |
| all three on one hub, single Mac port | ❌ third refused at any rate |
That last row is the only real path limit, and it isn't bandwidth: two displays per Thunderbolt port, which Apple documents for M1 to M4 Pro/Max. A count limit, so no refresh reduction helps.
Your Studio granting 165/165/60 needs a fifth pipe, and your Result 7 says where it is. Mine has no fifth pipe because the built-in HDMI port is internally a DisplayPort link into a converter chip (Data source: DisplayPort to HDMI converter, HBR3, 25.92 Gbps). It works, 165 Hz included. What it costs is compression:
| Path | DSC target |
|---|---|
| built-in HDMI @ 85 Hz | 16.00 bpp |
| built-in HDMI @ 90 Hz | 12.00 bpp |
| built-in HDMI, top end | 8.00 bpp |
| USB-C, or DP UHBR20 @ 165 | 16.00 bpp |
3. Could a pre-connection EDID override change the grant? Yes, but you shrink the ladder rather than enlarge it. Apple's own DTS engineer describes this method in developer forums thread 814201:
the EDID Emulator then side steps this issue by forcing the initial display to use only one pipe, freeing up a second pipe for the other display
The rule that makes a triple config permanent:
Every single-pipe display's top mode must fit one pipe, because any of them may be dealt last. Exactly one display may hold a pair, and it is exempt, since a pair is satisfiable from any two remaining pipes.
Cap both wings at 85, leave the centre's ladder topping out at 165, and deal order stops mattering.
One correction to your finding 3
Assignment at enumeration is right and it's the key insight in your post. But display sleep also re-deals the map, not just physical replug. An 80-minute unattended idle, everything attached and untouched:
21:20-21:36 stable, all three 13700, DSC 16.00
21:39:55 two panels each reallocate to 27400
the third's bandwidth field goes empty
21:40-22:40 continuous re-enumeration
22:40:43 key pressed, one panel absent for a poll
22:41:17 all three back at 13700, DSC 16.00
With the third panel quiescent, the other two each grabbed a full pair. On wake they must release before the third can re-enter, and that release is a race. It won that night; the mornings a panel is missing are the same race lost.
That's why your finding 7 workaround is real but fragile. It holds until the monitors first blank.
Two side findings from the same capture: the panels never de-enumerated during sleep (display count held at 3, so LG's Deep Sleep does not drop HPD), and the dock was not involved (AC power throughout, the hub's 10G NIC never disappeared).
If you build ladders, four things that cost me time
- Each input is a separate product ID with its own factory ladder. HDMI
0x5CF7= 165/144/60, DP0x5CF8= 165/120/60, USB-C0x5CF9= 144/120/60. An override binds per input, so moving the cable to a different socket on the monitor silently drops it. - Always keep a custom ladder applied. Strip it and the factory fallback goes to 4K@120, not 5K@60, because macOS prefers a CTA-block 4K timing over a lower-rate 5K one. A ladder whose lowest rung is 5K means a bad allocation costs refresh, not resolution.
- A custom EDID carries the serial of the panel it came from. Apply panel A's blob to B and B reports as A permanently. Since BetterDisplay matches on vendor + serial, the relabelled panel keeps matching the wrong profile and can't identify its way out. Check for three distinct serials after every cable move.
- The USB-C input's DP version selector tops out at 1.4 (1.4 or 1.2, no 2.1), so HBR3 is that input's ceiling, matching LG's own 5K144 spec. The DP input offers 2.1 and trains UHBR20 ×4 at 77.58 Gbps.
A test for your Studio: I think 165/165/86 is available — number superseded, see the follow-up
Your third display landed on 60 Hz. I don't think that was a bandwidth ceiling, I think it was your panel's ladder. There is no rung between 60 and 120 on any input (see item 1 above), and 120 needs two pipes. The allocator took the largest mode the panel offered that fit one pipe. That's 60 on every input.
If so your third seat has been running at 68% of capacity. Cap that panel's EDID at 86, replug all three. If 86 doesn't take, walk down 85, 80, 75. Whatever appears measures your third seat exactly.
I'd say 60/40 it works outright. The doubt is your 4K@97: that's only ~8,050 units, well inside a whole 13700 pipe, so a full pipe should have offered far more. Something narrower is binding on your third seat and I can't tell what from here. Your constants would show it:
ioreg -l -w0 | grep -a MaxDisplayBandwidthLimitKey
sysctl -n hw.model
sw_vers -productVersion
Anyone with an M4 Pro, M3 Max or M5 Max, that output would be useful too. (For anyone tempted to upgrade for this: Apple's M5 Max display table is character-for-character identical to M4 Max. The only change is displays per Thunderbolt port, 2 → 4.)
@waydabber, two small things, take or leave.
BetterDisplay already reports Maximum Source Bandwidth, and the MaxGP* constants are one ioreg read away. Showing "granted seat vs pipe budget", or warning when a requested mode would cross a pipe boundary (the 86 → 90 step), would have saved me two weeks.
Also a reporting bug: on a UHBR10 link the report shows Link encoding: 8b/10b and computes effective bandwidth as 40 × 0.8 = 32.00 Gbps. DP 2.1 mandates 128b/132b for all UHBR rates, so the true figure is 38.79. Reproducible arithmetic fingerprint if you want to chase it.
None of this was measurable without the CLI. Thank you for it.
|
Right, I need to correct this properly. The numbers in my comment above are wrong. The allocator isn't counting active pixels, it's counting total pixels including blanking. So for the GM9 that's
The 90 Hz row is the one that matters. 14,075 is over 13,700 but under 27,400, so a 90 Hz display doesn't sit in one pipe the way I said. It takes two. Which means a mode list that bottoms out at 90 doesn't degrade gracefully when it can't get its top mode, it just quietly eats a second controller and something else doesn't come up. I had The headroom thingI also said the allocator wants 4 to 5 percent headroom below the limit and put a margin column in to back it up. There's no headroom. That was me fitting a rule to bad arithmetic. It's a hard step: And you don't have to infer it, the allocator logs its own budgets at init:
That puts the one-pipe ceiling at 87.60 Hz for 5K. The reason I think it's exact rather than roughly-there is a 4K sweep I'd done on the same panels and never connected to this: 150 works at 13,266, 155 fails at 13,708. Eight units over. You don't get that out of a percentage rule. 87 Hz works, and the dock was the problemI reported 87 failing a dock replug. It doesn't. 13,606, well inside, and it survives a cold boot and two replugs. What was actually failing was my OWC hub not coming back on the Thunderbolt bus. I've since had that at 85, and once with the hub not in the TB tree at all and nothing in the Thunderbolt logs, so the host was never even offered a link. Never had anything to do with refresh rate. Which also means "stable through dock replug" comes out of my summary, because it isn't. Where 13700 livesWent looking for it after all this. It's not in the kernel, not in any kext, not in the device tree. It's in the DCP firmware, Note the divide. It compares in units of 10 Mpx/s, so 13700 is really 137, and anything from 13700 to 13799 behaves the same. Signed firmware, so there's nothing to patch. There are 317 runtime properties in there you can set by boot-arg and I went through the lot of them, none is this. So it's still capping the EDID or nothing, same as before, but at least I've stopped wondering. @bnaert, revised testMy "cap it at 86" was built on the ceiling I've just retracted, so ignore the number. The reasoning under it still holds I think: your 60 wasn't a bandwidth ceiling, it was what your EDID was offering. There's no mode between 60 and 120 on any input of that panel and 120 needs two pipes, so 60 was just the biggest single-pipe mode on the list. Worth doing now because the answer tells us something either way. If your fifth pipe is another Your 4K@97 still bothers me though. Blanking-inclusive that's only about 8,600 units, nowhere near either limit, so something narrower is constraining that seat and I can't see what from here. Checking your own machineioreg -rlw0 | grep -a MaxDisplayBandwidthLimitKey
sysctl -n hw.model; sw_vers -productVersion -buildVersionUse the The sudo log config --subsystem com.apple.driver.AppleDisplayManager \
--mode "level:debug,persist:debug"
# reboot, then
log show --last boot --predicate 'eventMessage CONTAINS "pipe limits"' --style compact
sudo log config --subsystem com.apple.driver.AppleDisplayManager --mode "level:default"Still after M4 Pro / M3 Max / M3 Ultra / M5 Max numbers if anyone's got them. macOS 27.0 |
Uh oh!
There was an error while loading. Please reload this page.
Measured: how the M4 Max display allocator actually behaves (3× 5K/165 panels + mixed configs)
This community — and discussion #4871 in particular — is where I found the only real prior art on macOS multi-display high-refresh limits, so I wanted to bring the data back here. I spent a week probing an M4 Max Mac Studio (macOS 27 Public Beta 2) with three LG 27GM950B panels (5120×2880 @ 165Hz native, DP 2.1/HDMI 2.1, DSC), plus a 4K/240 ASUS PG32UCDM3 and two QHD/144 ProArts for controls. Summary of what the allocator does, with the receipts condensed:
Findings
1. Per-display limits are soft. A single 27GM950B negotiates full native 5K @ 165 (2.43 Gpx/s) over both HDMI 2.1 and TB5 → USB-C-to-DP 2.1 cable — the monitor OSD confirms a genuine DP 2.1 link on the TB path. Apple documents 5K/120 as the single-display max; DSC makes the extra headroom real.
2. Two fast pipes, both claimable. Two GM9s run 5K/165 simultaneously (~4.87 Gpx/s) — one HDMI, one TB→DP. Apple's docs imply only one "fast class" display in 3+ display configs; in practice both fast pipes stay grantable.
3. Assignment is sticky and happens at enumeration. Once displays are connected, nothing re-deals the pipes. Concrete proof: in a triple config with the third display capped at 5K/60, manually dropping all three displays to 60Hz (2.65 Gpx/s total — headroom everywhere) still leaves the third display's mode list topping out at 60. Freeing bandwidth after the fact is invisible. Only physical replug re-enumerates.
4. Three distinct over-budget behaviors, never renegotiation:
5. The 97Hz mode is a continuous probe of the budget. 4K@97 ≈ 0.805 Gpx/s; add 2× 2.433 and the machine total is ~5.67 Gpx/s. It also retro-explains the GM9's 5K/60 grant on the same seat: 0.885 fits, 5K/65 (0.96) wouldn't — 60 was the largest EDID mode fitting the remainder, same algorithm. Working model: two priority fast pipes drawing first from a ~5.7 Gpx/s machine budget, third seat sized to the remainder.
6. Pooling by output path. With both fast grants on Thunderbolt, a third display on any TB port gets nothing, while the same panel on HDMI comes up at the remainder mode. The HDMI output behaves like an independent pipe — in the triple config it's the reason a third display exists at all.
7. Workaround that works without EDID tools: pre-setting the connected displays' refresh before hot-plugging the next display changes what the allocator grants (120/120 pre-set → third display enumerates instead of refusing). This is how the first triple config came up.
8. No Adaptive-Sync anywhere. None of these panels (full G-Sync/FreeSync on Windows) ever show a variable option on macOS in these configs — presumably the usual DP-only / no-HDMI-VRR / scaled+DSC interference constraints.
Full pixel-rate ledger
(For contrast: the same three panels on an RTX 5090 run 165/165/165 at 10-bit — the limits above are allocator behavior, not the panels or links.)
Questions for people who know the internals
Full writeup with the step-by-step receipts: https://gist.github.com/bnaert/14a4ad2e3ab5ece3a3520fe44a928fb9
All reactions