Some DFPs hang the MX3 firmware — minimal reproducer, and a way to recover without rebooting

Hi @lindsay.morel and team:

I have been benchmarking models on an M.2 module (4 chips, Cascade+ at 600 MHz, SDK 2.2.5, driver 1.3.13.1, firmware 2.2 / FW_CommitID=0x196bb59f) and I hit a failure that took me a couple of days to pin down. I am sharing it because the reproducer is small, the behaviour is deterministic, I have swept it far enough to give the exact range, and there is a recovery that is not in the docs.

The symptom

Some DFPs compile cleanly (Assembling DFP (Done), 4 MXAs) and then wedge the board at runtime. They do not fail on load, which took me a while to see: /sys/memx0/hitcount counts the ingress-done interrupt per chip, and it keeps going up while a bad DFP runs. Between 127 and 168 frames get acknowledged by chip 0 before anything goes wrong. So the group gets configured, the model gets downloaded, the stream gets enabled, and more than a hundred frames go through normally. Only after that the data path stops.

At that moment the firmware is still answering. /sys/memx0/temperature gives normal values (56 C / 51 C, not the -274 C of a dead chip), so the admin channel is alive, but every DFP already fails and systemctl restart mxa-manager does not help, so it is not a stale device lock either. A few minutes later the admin channel goes too, and that is when you get this:

Driver reqruied firmware anti_rollback cnt >= 6
Cur firmware: cnt 0 ver 0x0
ioctl MEMX_CONFIG_MPU_GROUP failed
RuntimeError: Init DFP Runner failed!

and dmesg shows this:

memryx: fops_write: wait timeout 1(s), retrying again
memryx: wait_for_fw_ack: failed!
memryx: memx_firmware_init: get hardware info from fw failed
memryx: admin timeout device status 1 subop 4 chip 0..3

Two of those lines are easy to misread, so it is worth saying. memx_firmware_init: get hardware info from fw failed is not printed by the driver probe — the string lives inside memx_get_hw_info, and the MEMX_CONFIG_MPU_GROUP ioctl calls that function too, so here it comes from the ioctl hitting a firmware that is already gone. And fops_write: wait timeout has no retry limit, the loop in memx_cascade_pciemain.c only exits on abort or on a signal, so “212 timeouts” means 212 seconds of waiting and not 212 attempts.

From that point the board is dead for every other DFP, including ones that measured fine seconds before. /dev/memx0 and the kernel module are still loaded, and the PCIe link is still healthy (8 GT/s x2, no fatal errors), so it is the firmware that dies, not the card dropping off the bus.

Minimal reproducer

A chain of N identical 3x3 convolutions, 64 channels, 64x64 input. Nothing else:

# N Conv 3x3, c=64, 64x64 -> ONNX -> mx_nc -c 4 -e hard
for i in range(n):
    nodes.append(helper.make_node("Conv", [prev, f"W{i}", f"B{i}"], [f"v{i}"],
                 kernel_shape=[3,3], pads=[1]*4))
    prev = f"v{i}"

Then mx_bench -d fF<N>_hard.dfp -f 300. I swept 35 values of N, with a known-good control run after every single point. Results on my board:

N result N result
24 1443.79 FPS 94 hangs
36 961.70 95 hangs
42 963.15 96 hangs
46 963.14 97 hangs
47 953.98 98 hangs
48 hangs the firmware 100 hangs
49 552.60 102 hangs
50 552.25 104 291.03
60 553.54 108 288.59
72 553.00 114 289.35
78 477.97 120 290.21
80 479.46 130 213.50
82 478.22 144 171.55
84 hangs 145 160.80
90 hangs 168 95.54
185 117.27
192 74.46
216 76.77
240 70.33
288 31.38

So the failures are not scattered: there is one isolated value at N=48, and a contiguous band from 84 to 102. Both edges are tight — 47 and 49 run either side of 48, 82 runs and 84 hangs, 102 hangs and 104 runs. And larger is not worse: 288 layers run fine and 84 do not.

It is repeatable

I ran the two sides of each boundary several times, with a known-good control DFP after each run to tell “this DFP does not execute” apart from “the chip was already dead”:

  • fF48: killed the firmware 5 out of 5
  • fF60: ran 5 out of 5 (553.58, 553.70, 549.37, 553.57, 553.57)
  • fF96: killed 3 out of 3
  • fF120: ran 3 out of 3 (290.25, 290.20, 290.12)

That control after each run matters. Without it I wrongly flagged several DFPs as broken (fF145, fF185 and a modified yolo11n) that actually run perfectly — they had simply been measured after a hang.

What it is not

  • Not thermal. I measured fF48 as the first load after a cold boot, entering at 54 C with ThermalThrottlingState: 0 in /sys/memx0/temperature. It hung with the same ~211 timeouts as when the board was at 67 C. For reference, healthy runs reach 93 C and only then does throttling start, so 54 C is nowhere near a thermal limit.
  • Not old firmware. Read while the chip is alive, verinfo reports Firmware version: 2.2, FW_CommitID=0x196bb59f. The cnt 0 ver 0x0 in the error is a consequence of the firmware not answering, not proof that it is old. Worth mentioning because a similar report was answered with a firmware reflash, and here that would not be the fix.
  • Not size or complexity. The largest DFP I have (57.5 MB) runs fine, and fF48 is 1.9 MB.
  • Not the effort setting. fF48 compiled with -e normal produces exactly the same mapping, 24 core-groups of (16,16), and hangs the same way.
  • Not visible in the compiler estimate. fF97 and fF120 both get estimated fps = 288.3. One kills the board, the other runs at 290.21.

That last one goes the other way too: fF36, fF42, fF46 and fF48 all report the same estimated fps = 991.0, and only fF48 hangs. So I compiled all 35 with -vvv and compared the mappings. Outside the M-Unit and core-group tables the logs are identical line for line between a DFP that hangs and one that runs: no warning, no extra field, same ports, same inter-chip transfer line, same 768 / 768 M-Cores, same 36.31 KByte of WMem per layer. The one thing that does change is how the mapper splits the 32 M-Cores of each core-group:

N core-group split result
24 24 x (32) runs
47 23 x (16,16) + 1 x (32) runs
48 24 x (16,16) hangs
72 24 x (10,11,11) runs
82 14 x (10,11,11) + 10 x (8,8,8,8) runs
84 12 x (10,11,11) + 12 x (8,8,8,8) hangs
96 24 x (8,8,8,8) hangs
102 6 x (6,6,6,7,7) + 18 x (8,8,8,8) hangs
104 8 x (6,6,6,7,7) + 16 x (8,8,8,8) runs
108 12 x (6,6,6,7,7) + 12 x (8,8,8,8) runs
192 24 x (4,4,4,4,4,4,4,4) runs

Every DFP that hangs has core-groups split into exactly equal parts of 16 or 8 M-Cores, and every DFP whose core-groups never get that kind of split runs. That said, it is not sufficient, and this is where I got stuck: fF84 and fF108 have the same twelve core-groups of (8,8,8,8), and one hangs while the other runs at 288.59. I tried four different rules on this table and each of them died on a specific measured point, so I am not going to claim a predictor. That pair is the cleanest bad/good comparison I have.

On scope, this looks like a toy-model problem. The YOLO DFPs I compiled have 0 or 1 core-groups with that kind of exactly equal split, because a real network has layers of many different sizes.

Recovering without a reboot

This is the part I would like to pass on, because it saved me a lot of time. Reloading the kernel module does not bring the board back:

sudo rmmod memx_cascade_plus_pcie && sudo modprobe memx_cascade_plus_pcie   # does not work

Removing the device from the bus and rescanning does:

sudo systemctl stop mxa-manager.service
sudo sh -c "echo 1 > /sys/bus/pci/devices/0000:06:00.0/remove"; sleep 3
sudo sh -c "echo 1 > /sys/bus/pci/rescan";                      sleep 5
sudo systemctl start mxa-manager.service

After that the board measures 145.43 FPS on my control model, against 146.58 before the hang. So the firmware is reloaded on the PCIe re-probe. Takes about 15 seconds and no reboot, which turned days of power cycles into unattended sweeps .. one warning though, check that the board actually executes a DFP afterwards, because the temperature reading and /dev/memx0 both come back before the firmware is really usable.

One thing to do before that, which I found late. The firmware keeps its own log in a ring buffer in host DRAM, so it can be read even while the board is wedged:

for c in 0 1 2 3; do echo "fwlog $c" | sudo tee /sys/memx0/cmd; done && sudo dmesg

The remove calls memx_fw_log_deinit and clears that buffer, so every recovery destroys the post-mortem. In my case that log has no crash trace at all, only the boot messages, so the firmware never got to write anything down — which may be useful to you on its own.

My question

Is this a known limitation of the allocation for these shapes, or should I file it? And is there anything in the mapping I should be checking that separates fF84 from fF108, since the reported numbers clearly do not. Happy to share the ONNX files, the DFPs and the full -vvv logs for any point in that table.