EEG reconstruction · fp32 profiles
ZUNA1.1 · Core ML
Missing channels, estimated on device.
Zyphra’s ZUNA1.1 on Apple platforms: EEG denoising, missing-channel reconstruction, and spatial superresolution in Core ML.
- Upstream
- Zyphra ZUNA1.1 · 380M
- Window
- 5 s at 256 Hz
- Precision
- fp32 only
- Default profile
- 14 channels, EPOC X layout
- License
- Apache-2.0
- Updated
- 18 Aug 2026
Overview
ZUNA1.1 is Zyphra’s masked diffusion autoencoder for scalp EEG. Given a subset of channels and their 3D electrode positions, it denoises what was measured, estimates what was not, and can predict signals at new positions on the scalp.
This release brings the pretrained model to Apple platforms as fixed Core ML profiles. The default profile matches the 14-channel Emotiv EPOC X layout, so a consumer headset’s recording can be cleaned and extended on the device that receives it.
Fig. 01 · Illustration
Fourteen electrodes, three missing
ZUNA takes the channels that were recorded, with their 3D positions, and estimates the ones that were not by iterative denoising.
5 cmTop view · t = 3.53 s
Seconds · synthetic, not recorded EEG
How it works
Fig. 02 · Inference
Encode once, denoise N times
Diagram of ZUNA inference, drawn for the 14-channel EPOC X profile. A five-second EEG window of C channels at 256 Hz, 1,280 samples per channel, in which T7, O2 and F4 were not recorded, is cut into tokens of 32 samples, 40 per channel, shape [1, C·40, 32]: 560 tokens for 14 channels. Each token carries an index, tok_idx = {x, y, z, t}: its electrode position in metres and its place in time; the tokens of the missing channels are masked and keep only their positions. The ZunaEncoder Core ML package encodes the tokens once, and its encoding is reused at every step. The ZunaDecoderStep package performs one denoising step on the latent z and returns v; the host applies the Euler update z ← z − dt·v; the new z goes back into the step, N times. The final z is turned back into a [C, 1280] signal: measured channels denoised, missing channels estimated. Traces are synthetic.
EEG window
[C, 1280]
C channels × 1,280 samples: five seconds at 256 Hz. Drawn for the 14-channel EPOC X profile, with T7, O2 and F4 not recorded.
Tokenizeevery 32 samples
Tokens
[1, C·40, 32]
Forty tokens of 32 samples per channel; 560 for 14 channels. Each carries tok_idx = {x, y, z, t}. The rows of T7, O2 and F4 are masked: position only.
AF3 · token 12 of 40
- values
- 32 samples, 1.375–1.500 s
- x, y, z · position
- AF3: −0.034, 0.077, 0.021 m
- t
- 12 of 40 in time
Point at or tap any token, or use the arrow keys on the focused grid, to follow it instead
Tokens + tok_idx
Encode once, denoise N times
The encoder reads the tokens once. The decoder then denoises a latent z, one step per call. N is the host’s choice.
Back to samplesz after N steps
Signal
[C, 1280]
z after N steps, back in samples: measured channels denoised; T7, O2 and F4 estimated from their positions.
measured, denoisedestimated
Each channel’s five-second window is cut into 40 coarse time tokens of 32 samples. A token’s index carries its electrode position and time, {x, y, z, t}, which is why coordinates are part of the input contract. The encoder runs once; the decoder then denoises a latent over a fixed number of diffusion steps before the tokens are turned back into signal.
Why fp32 only
The original ZUNA runs in fp16: its 14-, 16-, 32- and 64-channel fp16 profiles pass their parity gates. ZUNA1.1 does not. The base model was trained in bf16, and its decoder activations exceed the fp16 range, so no fp16 profile passes parity. Every ZUNA1.1 profile ships in fp32; the 14-channel profile is the default.
A proposed fix for Core ML itself
The port surfaced a bug in coremltools. Its rms_norm translation rescales the input by its maximum before squaring, to protect fp16 on the Neural Engine, but adds epsilon after the rescale. Algebraically the epsilon is inflated by max(|x|)². On spiky activations, which are common in transformer residual streams, that changes outputs by whole percents rather than rounding error, even at fp32 on the CPU.
It was reported as apple/coremltools#2821, with a proposed fix in #2822: rescale epsilon by 1/m², clamp the scale so all-zero rows no longer divide 0 by 0, and add regression tests for both.
Fig. 03 · The bug, in one line
Epsilon, inflated by the rescale
What the conversion computed
rms = √( mean((x / m)²) + ε ) · m = √( mean(x²) + ε · m² )
After the fix
m ← max(m, 1), ε ← ε / m² ⇒ √( mean(x²) + ε )
m = max |x|. When one activation is much larger than the rest, m² is large and the added term stops being negligible.
Input contract
- Sampling rate
- 256 Hz
- Window
- 5 seconds, 1,280 samples
- Tokens
- 32 samples each, 40 per channel
- Positions
- 3D electrode coordinates in meters, part of every token
- Normalization
- Aligned with upstream inference,
data_norm = 10 - Default profile
14ch-fp32, EPOC X channel order
Know the boundaries
- Reconstructed and superresolved channels are estimates. Do not present them as measurements.
- Reconstruction is not decoding. This model does not infer intent, commands, or language.
- Research and engineering use only. Not validated for diagnosis, treatment, or clinical decisions.
- Keep the exact preprocessing: the pretrained weights were optimized for it.
About this page
Summarized from the public model card and its published files. Repository updated 18 Aug 2026; reviewed 26 Sep 2026. The Hugging Face card is the source of truth for licenses and current details.
Upstream: Zyphra / ZUNA1.1