bruAristimunha commited on
Commit
5b5dcbc
·
verified ·
1 Parent(s): dd0d981

Add MEG-XL meg-xl-med-v2 weights converted for braindecode.models.MEGXL (pnpl/MEG-XL@79838987, braindecode PR #1554 @ bf95a14d)

Browse files
Files changed (5) hide show
  1. README.md +121 -0
  2. config.json +0 -0
  3. convert.py +78 -0
  4. model.safetensors +3 -0
  5. pytorch_model.bin +3 -0
README.md ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ library_name: braindecode
4
+ tags:
5
+ - braindecode
6
+ - pytorch
7
+ - safetensors
8
+ - meg
9
+ - megxl
10
+ - brain-to-text
11
+ - foundation-model
12
+ ---
13
+
14
+ # MEG-XL (pretrained, `meg-xl-med-v2`)
15
+
16
+ Weights of MEG-XL (Jayalath and Parker Jones, ICML 2026) for
17
+ [`braindecode.models.MEGXL`](https://braindecode.org/stable/generated/braindecode.models.MEGXL.html):
18
+ the released `meg-xl-med-v2.ckpt` (step 113000, the checkpoint the authors' model card
19
+ links) with its frozen BioCodec tokenizer, converted to braindecode's parameter names.
20
+ The class is added by [braindecode#1554](https://github.com/braindecode/braindecode/pull/1554)
21
+ (braindecode 2.1).
22
+
23
+ ```python
24
+ from braindecode.models import MEGXL
25
+
26
+ model = MEGXL.from_pretrained(
27
+ "braindecode/megxl-pretrained", chs_info=raw.info["chs"], n_outputs=2
28
+ )
29
+ ```
30
+
31
+ The files hold no classification head: it is not pretrained and its shape depends on the
32
+ number of channels, so `from_pretrained` builds a new one (PyTorch's default init) for
33
+ your `chs_info`. Fine-tune or linear-probe before use.
34
+
35
+ ## Intended input
36
+
37
+ - MEG only. `chs_info` must carry sensor positions and coil orientations (`loc`);
38
+ magnetometers and gradiometers (planar or axial) get their own type embeddings. The
39
+ `chs_info` in `config.json` (the 306 MEG channels of MNE's `test_raw.fif`, Neuromag) is
40
+ only a default.
41
+ - 50 Hz, preprocessed as in the authors' code: 0.1-40 Hz band-pass, baseline correction
42
+ and robust scaling per 3 s chunk, clipped to `[-5, 5]`.
43
+ - Pretrained on 150 s windows (`n_times=7500`); the stored RoPE table covers those 625
44
+ token positions, longer windows rebuild it.
45
+
46
+ ## Source
47
+
48
+ - Paper: D. Jayalath, O. Parker Jones. *MEG-XL: Data-Efficient Brain-to-Text via
49
+ Long-Context Pre-Training.* ICML 2026. [arXiv:2602.02494](https://arxiv.org/abs/2602.02494)
50
+ - Code: [neural-processing-lab/MEG-XL @ `97da04171d22e8fe4822cfeb3047a57ecf86b318`](https://github.com/neural-processing-lab/MEG-XL/tree/97da04171d22e8fe4822cfeb3047a57ecf86b318) (MIT)
51
+ - Weights: [pnpl/MEG-XL](https://huggingface.co/pnpl/MEG-XL) at revision
52
+ `79838987b0fea6bd55bb9b4cccbe1e6f59e97362`, file `meg-xl-med-v2.ckpt`,
53
+ sha256 `a8be0f06416bebcbea896db8bfdc8027ec25072a64b7e2ab925908dd6edfe88c`
54
+ (model card `license: mit`).
55
+ - The tokenizer inside the checkpoint equals the separate BioCodec release
56
+ `biocodec_ckpt.pt` (sha256 `b8b5c864bd92242a2aa70c414ab1abe23231772dc2f2e39a8f509fdded728af1`,
57
+ [klean2050/BioCodec-release @ `575bf6ea7ae14a34754b217d9b940597a16951bd`](https://github.com/klean2050/BioCodec-release/tree/575bf6ea7ae14a34754b217d9b940597a16951bd), MIT),
58
+ max-abs difference 0.0.
59
+
60
+ ## Conversion
61
+
62
+ `convert.py` (in this repository) was run with braindecode at
63
+ `bruAristimunha/braindecode@bf95a14d2ea2847013aead661646dcacf51d9aa3` (branch `feat/meg-megxl`, PR #1554):
64
+
65
+ ```
66
+ python convert.py meg-xl-med-v2.ckpt <out_dir> test_raw.fif
67
+ ```
68
+
69
+ It renames the checkpoint keys to braindecode's and writes `config.json` with
70
+ `save_pretrained`, then `model.safetensors` / `pytorch_model.bin` without the head. Tensor
71
+ values are copied unchanged, except the RoPE table, which the authors' loader recomputes
72
+ from `freqs` (`torch.polar`, 625 positions) and which is stored as `(cos, sin)` pairs.
73
+
74
+ Dropped (pretraining only or duplicated): the BioCodec decoder, the duplicated
75
+ `biocodec_embedding_*` codebooks, `mask_token`, the code-prediction `output_head`, the
76
+ complex RoPE cache and the Lightning/optimizer state.
77
+
78
+ | file | sha256 |
79
+ |---|---|
80
+ | `model.safetensors` | `0817547bc254bae86fd74c993a4c5ed941e2e2f35c16d2990046cf32916a99cf` |
81
+ | `pytorch_model.bin` | `cb8b1cb6642bd8ee8070d859b3158b979342a9878f18aee2e7e4dde06c15974e` |
82
+ | `config.json` | `ba072e1d8ccd8db88ed44160deedd8af1499d9ac6884c392732911512d61482c` |
83
+ | `convert.py` | `825bfa078974adb4dc54fc3e5d93f2fbece1f2b0530b9c58cd188e448d8b675b` |
84
+
85
+ ## Verification
86
+
87
+ `MEGXL.from_pretrained` on these files (only `final_layer.*` missing, no unexpected key;
88
+ loaded tensors equal `convert.py` applied to the original checkpoint) against the
89
+ authors' code with the same weights and inputs, CPU float32, eval mode, 20 s: codes
90
+ identical, features and the gradients of all 119 parameters of one backward pass equal
91
+ with max-abs difference 0.0, for 306 Neuromag channels (batch 1 and 2) and 157 KIT
92
+ axial gradiometers. For that comparison the original's attention dropout is set to 0: the
93
+ released `SelfAttention` keeps dropout 0.1 active in eval mode, braindecode turns it off.
94
+
95
+ ## License
96
+
97
+ MIT, as the original release: [MEG-XL licence](https://github.com/neural-processing-lab/MEG-XL/blob/main/LICENSE)
98
+ (code; the weights' model card on `pnpl/MEG-XL` says `license: mit`) and
99
+ [BioCodec licence](https://github.com/klean2050/BioCodec-release/blob/main/LICENSE).
100
+ Converted and re-hosted with attribution to the authors; for corrections or removal, open
101
+ an issue at https://github.com/braindecode/braindecode/issues.
102
+
103
+ ## Citation
104
+
105
+ ```bibtex
106
+ @inproceedings{jayalath2026megxl,
107
+ title = {{MEG-XL}: Data-Efficient Brain-to-Text via Long-Context Pre-Training},
108
+ author = {Jayalath, Dulhan and Parker Jones, Oiwi},
109
+ booktitle = {Forty-third International Conference on Machine Learning (ICML)},
110
+ year = {2026},
111
+ note = {arXiv:2602.02494},
112
+ }
113
+
114
+ @article{aristimunha2025braindecode,
115
+ title = {Braindecode: a deep learning library for raw electrophysiological data},
116
+ author = {Aristimunha, Bruno and others},
117
+ journal = {Zenodo},
118
+ year = {2025},
119
+ doi = {10.5281/zenodo.17699192},
120
+ }
121
+ ```
config.json ADDED
The diff for this file is too large to render. See raw diff
 
convert.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Convert the released MEG-XL Lightning checkpoint to braindecode's MEGXL.
2
+
3
+ usage: python convert.py meg-xl-med-v2.ckpt <out_dir> <fif with the default chs_info>
4
+
5
+ The checkpoint holds the frozen BioCodec tokenizer, so ``biocodec_ckpt.pt`` is
6
+ not needed (its tensors equal the copy inside the checkpoint, max-abs 0.0).
7
+ The keys are renamed to braindecode's (``RENAME``). Dropped: the BioCodec
8
+ decoder, the duplicated ``biocodec_embedding_*`` codebooks, ``mask_token``,
9
+ ``output_head`` (pretraining only) and the complex RoPE cache. The port's
10
+ RoPE table (``rotate``, as many positions as the model's buffer: 625, the
11
+ 150 s pretraining window) is recomputed from ``freqs`` with ``torch.polar``
12
+ on the CPU, as the authors' loader recomputes it, and stored as ``(cos, sin)``
13
+ pairs.
14
+
15
+ ``<out_dir>`` gets ``config.json`` from ``save_pretrained`` (the 306 MEG
16
+ channels of the given FIF file as the default ``chs_info``, 150 s at 50 Hz)
17
+ and ``model.safetensors`` / ``pytorch_model.bin`` without the classification
18
+ head: its shape depends on the number of channels and it is not pretrained,
19
+ so ``from_pretrained`` builds a new one for the caller's ``chs_info``.
20
+ """
21
+
22
+ import sys
23
+
24
+ import torch
25
+
26
+ DROP = ("tokenizer.decoder.", "biocodec_embedding_", "mask_token", "output_head.")
27
+ RENAME = (
28
+ ("tokenizer.encoder.model.", "encoder."),
29
+ (".conv.conv.", ".conv."),
30
+ ("tokenizer.quantizer.vq.", "quantizer."),
31
+ ("criss_cross_transformer.layers.", "blocks."),
32
+ (".ff.layer.0.", ".ff.0."),
33
+ (".ff.layer.2.", ".ff.3."),
34
+ ("position_fourier_emb.B", "position_freqs"),
35
+ ("orientation_fourier_emb.B", "orientation_freqs"),
36
+ )
37
+
38
+
39
+ def convert(state_dict, model):
40
+ """Map ``state_dict`` onto ``model``'s keys."""
41
+ out = {}
42
+ for key, value in state_dict.items():
43
+ if key.startswith(DROP) or key.endswith("rope_embedding_layer.rotate"):
44
+ continue
45
+ for old, new in RENAME:
46
+ key = key.replace(old, new)
47
+ out[key] = value
48
+ for key, value in model.state_dict().items():
49
+ if key.endswith("rope_embedding_layer.rotate"):
50
+ freqs = out[key.replace("rotate", "freqs")]
51
+ angles = torch.outer(torch.arange(value.shape[0]).type_as(freqs), freqs)
52
+ out[key] = torch.view_as_real(torch.polar(torch.ones_like(angles), angles))
53
+ return out
54
+
55
+
56
+ if __name__ == "__main__":
57
+ import mne
58
+ from safetensors.torch import save_file
59
+
60
+ from braindecode.models import MEGXL
61
+
62
+ ckpt_path, out_dir, fif = sys.argv[1:4]
63
+ raw = mne.io.read_raw_fif(fif, verbose=False)
64
+ raw.pick(mne.pick_types(raw.info, meg=True, ref_meg=False, exclude=[]))
65
+ torch.manual_seed(0)
66
+ model = MEGXL(chs_info=raw.info["chs"], n_outputs=2, n_times=7500, sfreq=50.0)
67
+ ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False)
68
+ state = convert(ckpt["state_dict"], model)
69
+ missing, unexpected = model.load_state_dict(state, strict=False)
70
+ assert unexpected == [] and set(missing) == {"final_layer.weight", "final_layer.bias"}
71
+ model.save_pretrained(out_dir)
72
+ weights = {
73
+ k: v.contiguous()
74
+ for k, v in model.state_dict().items()
75
+ if not k.startswith("final_layer.")
76
+ }
77
+ save_file(weights, f"{out_dir}/model.safetensors", metadata={"format": "pt"})
78
+ torch.save(weights, f"{out_dir}/pytorch_model.bin")
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0817547bc254bae86fd74c993a4c5ed941e2e2f35c16d2990046cf32916a99cf
3
+ size 97008696
pytorch_model.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cb8b1cb6642bd8ee8070d859b3158b979342a9878f18aee2e7e4dde06c15974e
3
+ size 97061043