Running Nam A2-Full Natively on an ESP32-P4 The ESP32-P4 now runs a full Neural Amp Modeler A2-Full capture natively in real time on the same chip that hosts USB audio and the pedal's touch interface, using an integer engine with a 16-bit path and a 24-bit wide path for early layers. Across 193 open source A2-Full captures, the engine nulls against the original NeuralAmpModelerCore at a median of −62.4 dB, with the worst at −51.0 dB and no arithmetic overflows. Running NAM A2-Full natively on an ESP32-P4 A Neural Amp Modeler A2-Full capture is a twenty-three-layer network that has to run once for every output sample, forty-eight thousand times a second. It is the full eight-channel architecture the captures are made with, not the three-channel A2-Lite that embedded hardware normally settles for — about seven times the arithmetic. The ESP32-P4 now runs it in real time, on the same chip that also hosts USB audio and draws the pedal's touch interface. The chip has no floating point where it matters The P4's vector unit is fourteen times faster than its floating-point unit at what a convolution actually does, and all 410 of its instructions are integer. That is not a preference we could design around. A float multiply-accumulate on this core is two instructions — one to load the weight, one to multiply-add it — and the weights differ for all 1,248 tap-and-input pairs in a frame, so none of them can stay in a register. At 11,584 multiply-accumulates a frame that is 23,168 instructions against the 15,000 cycles both cores have. Even written perfectly by hand, float A2-Full needs 154% of the entire chip. So the model runs in integers, or it does not run. Sixteen bits, and then twenty-four A straight int16 engine works. It sounds fine, and against the original A2-Full render it nulls at −27 dB — close enough to beat A2-Lite, and not close enough to call it the same amp. What closed the gap was to stop treating a lane as a whole number. Each 16-bit lane holds the top half of a 24-bit value, and a second pass recovers the low eight bits. The accumulator's extract shifts in place, so both halves come out of two successive extracts — the vector unit ends up computing a wider number than it has any instruction for. Doing that on every layer would miss the deadline, and it turns out not to be necessary. Precision compounds through the early layers and stops mattering near the output, so the wide path goes where the compounding is: | Layers on the wide path | Null against A2-Full | |---|---| | none | −27.2 dB | | the first eight | −53.9 dB | | the first twelve | −70.0 dB | | all twenty-three | −75.9 dB | Thirteen early layers and two later hot layers ship on it. The rest stay 16-bit, and the common case stays cheap. It is one engine and one set of rules for every profile — no per-profile scales, exceptions or hand-tuned patches. What that costs, across 193 amps A hand-picked list of wide layers invites an obvious suspicion: that it was quietly fitted to whichever capture happened to be on the bench. The only way to answer that is to stop choosing the test. So we downloaded 193 open source A2-Full captures — clean, crunch and high gain alike — and ran the same fifteen-second guitar DI through every one of them, one ordinary riff at 48 kHz. Each output was then subtracted sample for sample from that profile's own A2-Full render, with no gain matching and no alignment shift. The reference deserves a sentence of its own. It is the original NeuralAmpModelerCore — the canonical A2 code — playing the original .nam capture files at full width. Every board capture on this page is nulled against that code's own output; the sweep's in-repo float harness reproduces it to −122 dB, a thousandth of the engine's own error. | Result across 193 profiles | Null against the original A2-Full | |---|---| | Worst — SLO100 C1 V30 | −51.0 dB | | Median | −62.4 dB | | Best — THC C2 V30 | −70.1 dB | | Profiles with a true arithmetic overflow | 0 | Nothing lands in the −40s, the −30s or the −20s. The last row carries more weight than it looks. This vector unit's accumulator extract wraps rather than saturating, so an overflow does not soften into distortion the way clipping does — it flips the sign and fires a full-scale click. A single one anywhere in the sweep would be impossible to miss. That is 193 amps and one input signal, so it is a measured floor for these profiles and this DI rather than a guarantee for every guitar signal. A relative null, though, is the engine's report card, not your ears'. It divides each profile's error by that profile's own output, so a quiet capture with a proportionally large error tops the table while producing a residual nobody can hear. What a listener experiences is the error's absolute level. So we rendered the residual of all 193 profiles, sorted them by RMS, took the three loudest — the worst offenders any listener could ever find in this library — and captured those off the physical board. Each is subtracted from the original NeuralAmpModelerCore's render; alongside its level, the column that bounds the worst instant anywhere in fifteen seconds: the largest single-sample difference between the board and the original code, out of 720,000 samples. | Profile, loudest residuals of the 193 | Null vs original NAMCore | Residual RMS | Largest single-sample error | |---|---|---|---| | Zrek / 2x12 — crunch | −59.5 dB | −75.3 dBFS | 6.1e-4 −64.3 dBFS | | SS100 Plus / 4x12 — high gain | −59.0 dB | −77.8 dBFS | 9.5e-3 −40.4 dBFS | | JEL50 / 4x12 — crunch | −62.2 dB | −78.1 dBFS | 1.7e-3 −55.5 dBFS | Every other profile in the library leaves a quieter error than these three. That is the entire cost of running the eight-channel model on an integer vector unit, and you can listen to all of it below. Listen to it First the levels, because a null test means nothing if the two files were not the same loudness to begin with: | Render | Integrated LUFS | RMS | Peak | |---|---|---|---| | DI going in | −35.8 | −35.32 dBFS | −7.54 dBFS | | A2-Full, original NAMCore — the reference | −15.6 | −16.15 dBFS | −2.48 dBFS | | A2-Full on the ESP32-P4 | −15.6 | −16.16 dBFS | −2.49 dBFS | | A2-Lite, float | −15.7 | −16.16 dBFS | −2.63 dBFS | The board matches the original code to 0.0 LU and a hundredth of a decibel RMS, and all three amp renders land inside 0.1 LU of each other. None of this is gain-matched after the fact — they come out that way. So anything you hear between them is timbre. Every file here is lossless float32, end to end — the differences are the point, and a lossy codec would put its own floor on top of them. The first four rows below are those same fifteen seconds through each engine, including the one captured off the board rather than rendered on a computer. Play the original NeuralAmpModelerCore render and the P4 back to back. Then open the differences, which is where the actual answer is. Subtract the board's output from the original render and what is left is everything the integer engine got wrong — at its true level, against the model gap it replaced.