braindecode 1.8.1.dev174295099__tar.gz → 1.8.1.dev174425547__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. {braindecode-1.8.1.dev174295099/braindecode.egg-info → braindecode-1.8.1.dev174425547}/PKG-INFO +1 -1
  2. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/functional/functions.py +19 -8
  3. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/axon.py +42 -8
  4. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/brainmodule.py +13 -1
  5. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eegnex.py +6 -2
  6. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eegpt.py +12 -8
  7. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/medformer.py +47 -20
  8. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/patchedtransformer.py +3 -7
  9. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/reve.py +3 -2
  10. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/signal_jepa.py +81 -3
  11. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/zuna.py +9 -8
  12. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/attention.py +47 -5
  13. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/dance_modules.py +13 -1
  14. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/layers.py +34 -1
  15. braindecode-1.8.1.dev174425547/braindecode/version.py +1 -0
  16. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547/braindecode.egg-info}/PKG-INFO +1 -1
  17. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/whats_new.rst +18 -0
  18. braindecode-1.8.1.dev174295099/braindecode/version.py +0 -1
  19. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/LICENSE.txt +0 -0
  20. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/MANIFEST.in +0 -0
  21. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/NOTICE.txt +0 -0
  22. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/README.rst +0 -0
  23. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/__init__.py +0 -0
  24. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/augmentation/__init__.py +0 -0
  25. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/augmentation/base.py +0 -0
  26. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/augmentation/functional.py +0 -0
  27. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/augmentation/transforms.py +0 -0
  28. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/classifier.py +0 -0
  29. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/__init__.py +0 -0
  30. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/_notebook_viewer.py +0 -0
  31. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/base.py +0 -0
  32. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/bbci.py +0 -0
  33. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/bcicomp.py +0 -0
  34. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/bids/__init__.py +0 -0
  35. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/bids/datasets.py +0 -0
  36. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/bids/format.py +0 -0
  37. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/bids/hub.py +0 -0
  38. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/bids/hub_format.py +0 -0
  39. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/bids/hub_io.py +0 -0
  40. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/bids/hub_validation.py +0 -0
  41. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/bids/iterable.py +0 -0
  42. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/chb_mit.py +0 -0
  43. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/collate.py +0 -0
  44. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/mne.py +0 -0
  45. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/moabb.py +0 -0
  46. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/nmt.py +0 -0
  47. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/registry.py +0 -0
  48. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/siena.py +0 -0
  49. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
  50. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/sleep_physionet.py +0 -0
  51. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/tuh.py +0 -0
  52. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/utils.py +0 -0
  53. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datasets/xy.py +0 -0
  54. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datautil/__init__.py +0 -0
  55. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datautil/channel_utils.py +0 -0
  56. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datautil/hub_formats.py +0 -0
  57. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datautil/serialization.py +0 -0
  58. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/datautil/util.py +0 -0
  59. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/eegneuralnet.py +0 -0
  60. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/functional/__init__.py +0 -0
  61. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/functional/_real_dft.py +0 -0
  62. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/functional/initialization.py +0 -0
  63. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/__init__.py +0 -0
  64. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/atcnet.py +0 -0
  65. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/attentionbasenet.py +0 -0
  66. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/attn_sleep.py +0 -0
  67. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/barista.py +0 -0
  68. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/base.py +0 -0
  69. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/bendr.py +0 -0
  70. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/biot.py +0 -0
  71. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/brainbert.py +0 -0
  72. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/brainomni.py +0 -0
  73. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/brant.py +0 -0
  74. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/cbramod.py +0 -0
  75. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/codebrain.py +0 -0
  76. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/config.py +0 -0
  77. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/contrawr.py +0 -0
  78. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/csbrain.py +0 -0
  79. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/ctnet.py +0 -0
  80. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/dance.py +0 -0
  81. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/deep4.py +0 -0
  82. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/deepsleepnet.py +0 -0
  83. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/dgcnn.py +0 -0
  84. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/diver1.py +0 -0
  85. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eeg_clip.py +0 -0
  86. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eegconformer.py +0 -0
  87. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eegdino.py +0 -0
  88. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eeginception_erp.py +0 -0
  89. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eeginception_mi.py +0 -0
  90. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eegitnet.py +0 -0
  91. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eegminer.py +0 -0
  92. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eegnet.py +0 -0
  93. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eegsimpleconv.py +0 -0
  94. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eegsym.py +0 -0
  95. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/eegtcnet.py +0 -0
  96. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/emg2qwerty.py +0 -0
  97. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/fbcnet.py +0 -0
  98. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/fblightconvnet.py +0 -0
  99. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/fbmsnet.py +0 -0
  100. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/guetschel2026.py +0 -0
  101. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/hybrid.py +0 -0
  102. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/ifnet.py +0 -0
  103. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/labram.py +0 -0
  104. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/luna.py +0 -0
  105. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/mapa.py +0 -0
  106. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/meta_neuromotor.py +0 -0
  107. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/mirepnet.py +0 -0
  108. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/mscformer.py +0 -0
  109. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/msvtnet.py +0 -0
  110. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/mvpformer.py +0 -0
  111. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/neuropose.py +0 -0
  112. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/neurorvq.py +0 -0
  113. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/neurorvq_tokenizer.py +0 -0
  114. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/popt.py +0 -0
  115. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/sccnet.py +0 -0
  116. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/seizure_transformer.py +0 -0
  117. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/sensingdynamics.py +0 -0
  118. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/shallow_fbcsp.py +0 -0
  119. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/sinc_shallow.py +0 -0
  120. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
  121. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
  122. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/sleepfm.py +0 -0
  123. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/sparcnet.py +0 -0
  124. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/sstdpn.py +0 -0
  125. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/steegformer.py +0 -0
  126. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/summary.csv +0 -0
  127. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/syncnet.py +0 -0
  128. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/tcformer.py +0 -0
  129. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/tcn.py +0 -0
  130. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/tfm_tokenizer.py +0 -0
  131. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/tidnet.py +0 -0
  132. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/tmsanet.py +0 -0
  133. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/tsinception.py +0 -0
  134. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/usleep.py +0 -0
  135. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/util.py +0 -0
  136. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/models/vemg2pose.py +0 -0
  137. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/__init__.py +0 -0
  138. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/activation.py +0 -0
  139. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/blocks.py +0 -0
  140. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/channels.py +0 -0
  141. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/convolution.py +0 -0
  142. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/filter.py +0 -0
  143. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/linear.py +0 -0
  144. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/linear_attention.py +0 -0
  145. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/parametrization.py +0 -0
  146. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/popt_modules.py +0 -0
  147. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/quantization.py +0 -0
  148. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/stats.py +0 -0
  149. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/util.py +0 -0
  150. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/modules/wrapper.py +0 -0
  151. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/preprocessing/__init__.py +0 -0
  152. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/preprocessing/eegprep_preprocess.py +0 -0
  153. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/preprocessing/mne_preprocess.py +0 -0
  154. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/preprocessing/preprocess.py +0 -0
  155. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/preprocessing/util.py +0 -0
  156. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/preprocessing/windowers.py +0 -0
  157. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/regressor.py +0 -0
  158. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/samplers/__init__.py +0 -0
  159. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/samplers/base.py +0 -0
  160. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/samplers/ssl.py +0 -0
  161. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/training/__init__.py +0 -0
  162. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/training/callbacks.py +0 -0
  163. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/training/losses.py +0 -0
  164. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/training/scoring.py +0 -0
  165. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/util.py +0 -0
  166. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/visualization/__init__.py +0 -0
  167. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/visualization/activations.py +0 -0
  168. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/visualization/attribution.py +0 -0
  169. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/visualization/confusion_matrices.py +0 -0
  170. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/visualization/frequency.py +0 -0
  171. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/visualization/metrics.py +0 -0
  172. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/visualization/sanity.py +0 -0
  173. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode/visualization/topology.py +0 -0
  174. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode.egg-info/SOURCES.txt +0 -0
  175. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode.egg-info/dependency_links.txt +0 -0
  176. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode.egg-info/requires.txt +0 -0
  177. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/braindecode.egg-info/top_level.txt +0 -0
  178. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/Makefile +0 -0
  179. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/_templates/autosummary/class.rst +0 -0
  180. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
  181. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/_templates/autosummary/function.rst +0 -0
  182. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
  183. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/agent_docs.rst +0 -0
  184. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/api.rst +0 -0
  185. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/cite.rst +0 -0
  186. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/conf.py +0 -0
  187. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/help.rst +0 -0
  188. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/index.rst +0 -0
  189. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/install/install.rst +0 -0
  190. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/install/install_pip.rst +0 -0
  191. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/install/install_source.rst +0 -0
  192. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/categorization/attention.rst +0 -0
  193. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/categorization/channel.rst +0 -0
  194. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/categorization/convolution.rst +0 -0
  195. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/categorization/filterbank.rst +0 -0
  196. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/categorization/gnn.rst +0 -0
  197. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/categorization/interpretable.rst +0 -0
  198. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/categorization/lbm.rst +0 -0
  199. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/categorization/recurrent.rst +0 -0
  200. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/categorization/spd.rst +0 -0
  201. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/models.rst +0 -0
  202. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/models_categorization.rst +0 -0
  203. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/models_table.rst +0 -0
  204. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/models/models_visualization.rst +0 -0
  205. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/sg_execution_times.rst +0 -0
  206. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/docs/user_guide/channel_strategies.rst +0 -0
  207. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/pyproject.toml +0 -0
  208. {braindecode-1.8.1.dev174295099 → braindecode-1.8.1.dev174425547}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braindecode
3
- Version: 1.8.1.dev174295099
3
+ Version: 1.8.1.dev174425547
4
4
  Summary: Deep learning software to decode EEG, ECG or MEG signals
5
5
  Author-email: Robin Tibor Schirrmeister <robintibor@gmail.com>, Bruno Aristimunha Pinto <b.aristimunha@gmail.com>, Alexandre Gramfort <agramfort@meta.com>
6
6
  Maintainer-email: Alexandre Gramfort <agramfort@meta.com>, Bruno Aristimunha Pinto <b.aristimunha@gmail.com>, Robin Tibor Schirrmeister <robintibor@gmail.com>
@@ -91,16 +91,27 @@ def fft_conv1d(
91
91
  n_fft = 2 * _next_fast_len((n_times + kernel_size) // 2)
92
92
  xs = spectral_input(x)
93
93
  # conv1d is a cross-correlation: a convolution with the flipped kernel
94
- x_f = torch.fft.rfft(xs, n=n_fft).permute(2, 0, 1) # (freq, batch, in)
94
+ x_f = torch.fft.rfft(xs, n=n_fft) # (batch, in, freq)
95
95
  w_f = torch.fft.rfft(weight.to(xs).flip(-1), n=n_fft).permute(2, 1, 0)
96
96
  # Per frequency (batch, in) @ (in, out) in complex numbers, as one real
97
- # matmul (a complex one is slower on CPU): [Re x; Im x] @ [Re w, Im w]
98
- # (columns interleaved) holds the four real products.
99
- p = torch.cat([x_f.real, x_f.imag], 1) @ torch.view_as_real(w_f).flatten(2)
100
- p = p.unflatten(1, [2, x.shape[0]]).unflatten(3, [-1, 2]) # x re/im, w re/im
101
- y_f = torch.complex(
102
- p[:, 0, :, :, 0] - p[:, 1, :, :, 1], p[:, 0, :, :, 1] + p[:, 1, :, :, 0]
103
- )
97
+ # matmul (a complex one is slower on CPU).
98
+ needs_grad = torch.is_grad_enabled() and (x.requires_grad or weight.requires_grad)
99
+ if needs_grad or x.shape[0] >= 2 * x.shape[1]:
100
+ # [Re x, Im x] @ [[Re w, Im w], [-Im w, Re w]] (rows of i * w): the
101
+ # output columns are the (Re, Im) pairs of the result, read as complex
102
+ # without a copy (and without a costly backward).
103
+ w_mat = torch.view_as_real(torch.stack([w_f, w_f * 1j], 1)).flatten(1, 2)
104
+ y_f = torch.view_as_real(x_f).permute(2, 0, 3, 1).flatten(2) @ w_mat.flatten(2)
105
+ y_f = torch.view_as_complex(y_f.unflatten(-1, [-1, 2]))
106
+ else:
107
+ # Forward only with fewer rows than twice the input channels: the
108
+ # doubled weight costs more than combining [Re x; Im x] @ [Re w, Im w].
109
+ x_f = x_f.permute(2, 0, 1)
110
+ p = torch.cat([x_f.real, x_f.imag], 1) @ torch.view_as_real(w_f).flatten(2)
111
+ p = p.unflatten(1, [2, x.shape[0]]).unflatten(3, [-1, 2])
112
+ y_f = torch.complex(
113
+ p[:, 0, :, :, 0] - p[:, 1, :, :, 1], p[:, 0, :, :, 1] + p[:, 1, :, :, 0]
114
+ )
104
115
  y = torch.fft.irfft(y_f.permute(1, 2, 0), n=n_fft)
105
116
  # "same" output t is sample t + kernel_size // 2 of the full convolution
106
117
  y = y[..., kernel_size // 2 : kernel_size // 2 + n_times]
@@ -14,6 +14,7 @@ from torch import nn
14
14
  from braindecode.functional import sinusoidal_positional_encoding
15
15
  from braindecode.models.base import EEGModuleMixin
16
16
  from braindecode.models.util import warn_if_sfreq_differs
17
+ from braindecode.modules.attention import _scaled_dot_product_attention
17
18
  from braindecode.modules.channels import _resolve
18
19
 
19
20
 
@@ -462,11 +463,33 @@ class _AxisAttention(nn.Module):
462
463
  .unbind(0)
463
464
  )
464
465
  drop = self.att_drop_prob if self.training else 0.0
465
- attended = F.scaled_dot_product_attention(
466
+ attended = _scaled_dot_product_attention(
466
467
  query, key, value, attn_mask=mask, dropout_p=drop
467
468
  )
468
469
  return attended.transpose(1, 2).reshape(n_seq, length, -1)
469
470
 
471
+ def _attend_two_scales(
472
+ self, projected: torch.Tensor, band: torch.Tensor, weights: torch.Tensor
473
+ ) -> torch.Tensor:
474
+ """Banded and full attention from one score matrix, mixed per query.
475
+
476
+ ``weights`` (N, L, 2) are the scale-gate weights. Both softmaxes share
477
+ the scores and one product with the values serves the mixture.
478
+ """
479
+ n_seq, length, _ = projected.shape
480
+ query, key, value = (
481
+ projected.reshape(n_seq, length, 3, self.num_heads, self.head_dim)
482
+ .permute(2, 0, 3, 1, 4)
483
+ .unbind(0)
484
+ )
485
+ scores = torch.matmul(query, key.transpose(-2, -1)) * self.head_dim**-0.5
486
+ weights = weights.reshape(n_seq, 1, length, 2)
487
+ probs = weights[..., 0:1] * scores.masked_fill(~band, float("-inf")).softmax(
488
+ -1
489
+ ) + weights[..., 1:2] * scores.softmax(-1)
490
+ attended = torch.matmul(probs, value)
491
+ return attended.transpose(1, 2).reshape(n_seq, length, -1)
492
+
470
493
  def forward(self, x: torch.Tensor, band: Optional[torch.Tensor]) -> torch.Tensor:
471
494
  batch, n_chans, n_patches, dim = x.shape
472
495
  axis_weights = self.axis_gate(x) # (B, C, P, 2)
@@ -476,15 +499,26 @@ class _AxisAttention(nn.Module):
476
499
  # the restricted branch is the full one; attend once unless the two
477
500
  # branches draw their own attention dropout.
478
501
  per_channel = self.temporal_qkv(x.reshape(batch * n_chans, n_patches, dim))
479
- restricted = self.temporal_out(self._attend(per_channel, band)).reshape(
480
- batch, n_chans, n_patches, dim
481
- )
482
- full = restricted
483
- if band is not None or (self.training and self.att_drop_prob > 0):
484
- full = self.temporal_out(self._attend(per_channel, None)).reshape(
502
+ dropout = self.training and self.att_drop_prob > 0
503
+ if band is not None and not dropout:
504
+ # The scale-gate weights sum to one, so the two branches share the
505
+ # scores, the product with the values and temporal_out.
506
+ attended = self._attend_two_scales(per_channel, band, scale_weights)
507
+ temporal = self.temporal_out(attended).reshape(
485
508
  batch, n_chans, n_patches, dim
486
509
  )
487
- temporal = scale_weights[..., 0:1] * restricted + scale_weights[..., 1:2] * full
510
+ else:
511
+ restricted = self.temporal_out(self._attend(per_channel, band)).reshape(
512
+ batch, n_chans, n_patches, dim
513
+ )
514
+ full = restricted
515
+ if dropout:
516
+ full = self.temporal_out(self._attend(per_channel, None)).reshape(
517
+ batch, n_chans, n_patches, dim
518
+ )
519
+ temporal = (
520
+ scale_weights[..., 0:1] * restricted + scale_weights[..., 1:2] * full
521
+ )
488
522
 
489
523
  # Spatial path: one sequence of C tokens per time step.
490
524
  per_step = self.spatial_qkv(
@@ -20,6 +20,7 @@ from torch.nn import functional as F
20
20
  from braindecode.models.base import EEGModuleMixin
21
21
  from braindecode.models.util import has_valid_locations, positions_from_chs_info
22
22
  from braindecode.modules import ChannelMerger
23
+ from braindecode.modules.attention import _hpu_lazy
23
24
  from braindecode.modules.layers import SubjectLayers
24
25
 
25
26
  __all__ = ["BrainModule"]
@@ -712,7 +713,7 @@ class _ConvSequence(nn.Module):
712
713
  nn.Conv1d(
713
714
  chout, 2 * chout, 1 + 2 * glu_context, padding=glu_context
714
715
  ),
715
- nn.GLU(dim=1),
716
+ _GLU(dim=1),
716
717
  )
717
718
  )
718
719
  else:
@@ -727,6 +728,17 @@ class _ConvSequence(nn.Module):
727
728
  return x
728
729
 
729
730
 
731
+ class _GLU(nn.GLU):
732
+ """``nn.GLU`` that Intel Gaudi's lazy mode can compile."""
733
+
734
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
735
+ if not torch.jit.is_scripting() and x.device.type == "hpu" and _hpu_lazy():
736
+ # Its graph compiler fails on ``aten.glu`` after a convolution.
737
+ a, b = x.chunk(2, dim=self.dim)
738
+ return a * torch.sigmoid(b)
739
+ return F.glu(x, self.dim)
740
+
741
+
730
742
  def _pad_multiple(x: torch.Tensor, base: int) -> torch.Tensor:
731
743
  """Pad tensor to be a multiple of base."""
732
744
  length = x.shape[-1]
@@ -358,10 +358,14 @@ class EEGNeX(EEGModuleMixin, nn.Module):
358
358
  class _TemporalConv(nn.Conv2d):
359
359
  """``(1, k)`` Conv2d with "same" output length; on CPU and HPU, through
360
360
  :func:`~braindecode.functional.fft_conv1d` where
361
- :func:`~braindecode.functional.prefer_fft_conv` expects it to be faster."""
361
+ :func:`~braindecode.functional.prefer_fft_conv` expects it to be faster.
362
+ On HPU only when gradients are computed: the FFT pays off in the
363
+ backward, and Gaudi's direct forward alone is cheaper."""
362
364
 
363
365
  def forward(self, x: torch.Tensor) -> torch.Tensor:
364
- if not prefer_fft_conv(x, self.kernel_size[1]):
366
+ if not prefer_fft_conv(x, self.kernel_size[1]) or (
367
+ x.device.type == "hpu" and not torch.is_grad_enabled()
368
+ ):
365
369
  return self._conv_forward(x, self.weight, self.bias)
366
370
  b, _, h, t = x.shape # the conv1d of each of the b * h rows
367
371
  x = x.transpose(1, 2).reshape(b * h, -1, t)
@@ -13,6 +13,7 @@ from torch import nn
13
13
 
14
14
  from braindecode.models.base import EEGModuleMixin
15
15
  from braindecode.modules import DropPath
16
+ from braindecode.modules.attention import _scaled_dot_product_attention
16
17
  from braindecode.modules.convolution import Conv1dWithConstraint
17
18
  from braindecode.modules.linear import LinearWithConstraint
18
19
  from braindecode.util import resolve_montage_name
@@ -949,14 +950,17 @@ class _Attention(nn.Module):
949
950
  # 3. Flash Attention
950
951
  # Use PyTorch's optimized scaled_dot_product_attention (SDPA)
951
952
  # which internally uses FlashAttention or EfficientAttention kernels.
952
- y = torch.nn.functional.scaled_dot_product_attention(
953
- q,
954
- k,
955
- v,
956
- attn_mask=None,
957
- dropout_p=self.attn_drop.p if self.training else 0.0,
958
- is_causal=self.is_causal,
959
- )
953
+ dropout_p = self.attn_drop.p if self.training else 0.0
954
+ if self.is_causal:
955
+ y = torch.nn.functional.scaled_dot_product_attention(
956
+ q, k, v, attn_mask=None, dropout_p=dropout_p, is_causal=True
957
+ )
958
+ else:
959
+ # Gaudi's fused kernel also in lazy mode: faster for EEGPT's
960
+ # 23-token sequences except on the shortest inputs (256 samples).
961
+ y = _scaled_dot_product_attention(
962
+ q, k, v, dropout_p=dropout_p, fused_in_lazy=True
963
+ )
960
964
 
961
965
  # Reshape back: (batch, num_heads, seq_len, head_dim) -> (batch, seq_len, embed_dim)
962
966
  x = y.transpose(1, 2).flatten(2)
@@ -342,17 +342,13 @@ class _PositionalEmbedding(nn.Module):
342
342
 
343
343
 
344
344
  class _CrossChannelTokenEmbedding(nn.Module):
345
- def __init__(
346
- self, c_in: int, l_patch: int, d_model: int, stride: Optional[int] = None
347
- ):
345
+ def __init__(self, c_in: int, l_patch: int, d_model: int):
348
346
  super().__init__()
349
- if stride is None:
350
- stride = l_patch
351
347
  self.token_conv = nn.Conv2d(
352
348
  in_channels=1,
353
349
  out_channels=d_model,
354
350
  kernel_size=(c_in, l_patch),
355
- stride=(1, stride),
351
+ stride=(1, l_patch),
356
352
  padding=0,
357
353
  padding_mode="circular",
358
354
  bias=False,
@@ -364,14 +360,18 @@ class _CrossChannelTokenEmbedding(nn.Module):
364
360
  )
365
361
 
366
362
  def forward(self, x: torch.Tensor) -> torch.Tensor:
367
- # The kernel spans all c_in rows: run it as a conv1d with c_in input
368
- # channels. Gaudi2 rejects conv kernels taller than 256 (c_in = n_times).
369
- x = nn.functional.conv1d(
370
- x.squeeze(1),
371
- self.token_conv.weight.squeeze(1),
372
- stride=self.token_conv.stride[1],
363
+ # The kernel spans all c_in rows and the patches do not overlap, so
364
+ # the conv is one GEMM of its weight with the patch-reshaped input
365
+ # (Gaudi2 rejects conv kernels taller than 256 rows, c_in = n_times).
366
+ l_patch = self.token_conv.kernel_size[1]
367
+ n_patches = x.shape[-1] // l_patch
368
+ x = x[:, 0, :, : n_patches * l_patch].reshape(
369
+ x.shape[0], x.shape[2], n_patches, l_patch
373
370
  )
374
- return x.unsqueeze(2)
371
+ x = torch.matmul(
372
+ x.transpose(1, 2).flatten(2), self.token_conv.weight.flatten(1).t()
373
+ )
374
+ return x.transpose(1, 2).unsqueeze(2)
375
375
 
376
376
 
377
377
  class _ListPatchEmbedding(nn.Module):
@@ -493,11 +493,32 @@ class _AttentionLayer(nn.Module):
493
493
  _, key_len, _ = keys.shape
494
494
  num_heads = self.num_heads
495
495
 
496
- queries = self.query_projection(queries).view(
497
- batch_size, query_len, num_heads, -1
498
- ) # multi-head
499
- keys = self.key_projection(keys).view(batch_size, key_len, num_heads, -1)
500
- values = self.value_projection(values).view(batch_size, key_len, num_heads, -1)
496
+ if queries is keys and keys is values:
497
+ # Self-attention: one GEMM for the three projections.
498
+ weight = torch.cat(
499
+ [
500
+ self.query_projection.weight,
501
+ self.key_projection.weight,
502
+ self.value_projection.weight,
503
+ ]
504
+ )
505
+ bias = torch.cat(
506
+ [
507
+ self.query_projection.bias,
508
+ self.key_projection.bias,
509
+ self.value_projection.bias,
510
+ ]
511
+ )
512
+ queries, keys, values = nn.functional.linear(queries, weight, bias).chunk(
513
+ 3, dim=-1
514
+ )
515
+ else:
516
+ queries = self.query_projection(queries)
517
+ keys = self.key_projection(keys)
518
+ values = self.value_projection(values)
519
+ queries = queries.view(batch_size, query_len, num_heads, -1) # multi-head
520
+ keys = keys.view(batch_size, key_len, num_heads, -1)
521
+ values = values.view(batch_size, key_len, num_heads, -1)
501
522
 
502
523
  out, attn = self.inner_attention(
503
524
  queries, keys, values, attn_mask, tau=tau, delta=delta
@@ -561,7 +582,13 @@ class _FullAttention(nn.Module):
561
582
  else:
562
583
  scale = self.scale
563
584
 
564
- scores = torch.einsum("blhe,bshe->bhls", queries, keys)
585
+ # (batch, heads, len, dim) operands: both products are batched GEMMs.
586
+ queries, keys, values = (
587
+ queries.transpose(1, 2),
588
+ keys.transpose(1, 2),
589
+ values.transpose(1, 2),
590
+ )
591
+ scores = torch.matmul(queries, keys.transpose(-2, -1))
565
592
 
566
593
  if self.mask_flag:
567
594
  if attn_mask is None:
@@ -577,7 +604,7 @@ class _FullAttention(nn.Module):
577
604
  attention_weights = self.dropout(
578
605
  torch.softmax(scale * scores, dim=-1)
579
606
  ) # Scaled Dot-Product Attention
580
- output_values = torch.einsum("bhls,bshd->blhd", attention_weights, values)
607
+ output_values = torch.matmul(attention_weights, values).transpose(1, 2)
581
608
 
582
609
  if self.output_attention:
583
610
  return output_values.contiguous(), attention_weights
@@ -12,6 +12,7 @@ import torch.nn as nn
12
12
  import torch.nn.functional as F
13
13
 
14
14
  from braindecode.models.base import EEGModuleMixin
15
+ from braindecode.modules.attention import _scaled_dot_product_attention
15
16
 
16
17
 
17
18
  class PBT(EEGModuleMixin, nn.Module):
@@ -411,13 +412,8 @@ class _MHSA(nn.Module):
411
412
  k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
412
413
  v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
413
414
 
414
- y = torch.nn.functional.scaled_dot_product_attention(
415
- q,
416
- k,
417
- v,
418
- attn_mask=None,
419
- dropout_p=self.drop_prob if self.training else 0.0,
420
- is_causal=False,
415
+ y = _scaled_dot_product_attention(
416
+ q, k, v, dropout_p=self.drop_prob if self.training else 0.0
421
417
  )
422
418
 
423
419
  # re-assemble heads -> (B, T, C)
@@ -15,10 +15,10 @@ import torch
15
15
  import torch.nn.functional as F
16
16
  from mne.datasets.utils import _get_path
17
17
  from torch import nn
18
- from torch.nn import RMSNorm
19
18
 
20
19
  from braindecode.models.base import EEGModuleMixin
21
20
  from braindecode.modules.attention import _scaled_dot_product_attention
21
+ from braindecode.modules.layers import RMSNorm
22
22
 
23
23
  logger = logging.getLogger(__name__)
24
24
 
@@ -565,7 +565,8 @@ class Attention(nn.Module):
565
565
  q = q.unflatten(-1, (self.heads, -1)).transpose(1, 2)
566
566
  k = k.unflatten(-1, (self.heads, -1)).transpose(1, 2)
567
567
  v = v.unflatten(-1, (self.heads, -1)).transpose(1, 2)
568
- out = _scaled_dot_product_attention(q, k, v)
568
+ # ~700-token sequences: the fused kernel also beats lazy mode's graph.
569
+ out = _scaled_dot_product_attention(q, k, v, fused_in_lazy=True)
569
570
  return self.to_out(out.transpose(1, 2).flatten(2))
570
571
 
571
572
 
@@ -13,6 +13,10 @@ from einops.layers.torch import Rearrange
13
13
  from torch import nn
14
14
 
15
15
  from braindecode.models.base import EEGModuleMixin
16
+ from braindecode.modules.attention import (
17
+ _hpu_lazy,
18
+ _scaled_dot_product_attention,
19
+ )
16
20
 
17
21
  _DEFAULT_CONV_LAYER_SPEC = ( # downsampling: 128Hz -> 1Hz, receptive field 1.1875s, stride 1s
18
22
  (8, 32, 8),
@@ -329,6 +333,56 @@ class _BaseSignalJEPA(EEGModuleMixin, nn.Module):
329
333
  batch_first=True,
330
334
  )
331
335
 
336
+ def _encode(self, x: torch.Tensor) -> torch.Tensor:
337
+ if self.training and not torch.jit.is_scripting():
338
+ return self._encode_train(x)
339
+ return self.transformer.encoder(x) # type: ignore
340
+
341
+ @torch.jit.unused
342
+ def _encode_train(self, x: torch.Tensor) -> torch.Tensor:
343
+ """``self.transformer.encoder(x)`` in training, without SDPA's ``_safe_softmax``.
344
+
345
+ With dropout, torch's attention takes SDPA's math path, whose
346
+ ``_safe_softmax`` adds four passes over the (batch, heads, tokens,
347
+ tokens) scores to zero the rows a mask hides; there is no mask here.
348
+ Ops, layouts and dropout draws are otherwise torch's: outputs and
349
+ gradients are bit-identical on CPU.
350
+ """
351
+ encoder = self.transformer.encoder # type: ignore
352
+ for layer in encoder.layers:
353
+ x = layer.norm1(x + layer.dropout1(_self_attention(layer.self_attn, x)))
354
+ h = layer.dropout(layer.activation(layer.linear1(x)))
355
+ x = layer.norm2(x + layer.dropout2(layer.linear2(h)))
356
+ if encoder.norm is not None:
357
+ x = encoder.norm(x)
358
+ return x
359
+
360
+
361
+ def _self_attention(mha: nn.MultiheadAttention, x: torch.Tensor) -> torch.Tensor:
362
+ """``mha(x, x, x, need_weights=False)[0]`` (batch first, no mask) as
363
+ ``F.multi_head_attention_forward`` runs it, with the attention itself
364
+ through braindecode's SDPA helper (no ``_safe_softmax`` pass)."""
365
+ x = x.transpose(1, 0)
366
+ n_tokens, batch_size, emb_dim = x.shape
367
+ n_heads = mha.num_heads
368
+ head_dim = emb_dim // n_heads
369
+ qkv = nn.functional.linear(x, mha.in_proj_weight, mha.in_proj_bias)
370
+ qkv = qkv.unflatten(-1, (3, emb_dim)).unsqueeze(0).transpose(0, -2)
371
+ qkv = qkv.squeeze(-2).contiguous()
372
+ q, k, v = (
373
+ t.view(n_tokens, batch_size * n_heads, head_dim)
374
+ .transpose(0, 1)
375
+ .view(batch_size, n_heads, n_tokens, head_dim)
376
+ for t in (qkv[0], qkv[1], qkv[2])
377
+ )
378
+ out = _scaled_dot_product_attention(
379
+ q, k, v, dropout_p=mha.dropout if mha.training else 0.0
380
+ )
381
+ out = out.permute(2, 0, 1, 3).contiguous()
382
+ out = out.view(batch_size * n_tokens, emb_dim)
383
+ out = nn.functional.linear(out, mha.out_proj.weight, mha.out_proj.bias)
384
+ return out.view(n_tokens, batch_size, emb_dim).transpose(1, 0)
385
+
332
386
 
333
387
  class SignalJEPA(_BaseSignalJEPA):
334
388
  r"""Architecture introduced in signal-JEPA for self-supervised pre-training, Guetschel, P et al (2024) [1]_
@@ -490,7 +544,7 @@ class SignalJEPA(_BaseSignalJEPA):
490
544
  local_features = self.feature_encoder(X) # type: ignore
491
545
  pos_encoding = self.pos_encoder(local_features) # type: ignore
492
546
  local_features += pos_encoding # type: ignore
493
- contextual_features = self.transformer.encoder(local_features) # type: ignore
547
+ contextual_features = self._encode(local_features) # type: ignore
494
548
  if return_features:
495
549
  out: Dict[str, Optional[torch.Tensor]] = {
496
550
  "features": contextual_features,
@@ -677,7 +731,7 @@ class SignalJEPA_Contextual(_BaseSignalJEPA):
677
731
  local_features = self.feature_encoder(X) # type: ignore
678
732
  pos_encoding = self.pos_encoder(local_features) # type: ignore
679
733
  local_features += pos_encoding # type: ignore
680
- contextual_features = self.transformer.encoder(local_features) # type: ignore
734
+ contextual_features = self._encode(local_features) # type: ignore
681
735
  if return_features:
682
736
  out: Dict[str, Optional[torch.Tensor]] = {
683
737
  "features": contextual_features,
@@ -1351,7 +1405,7 @@ class _ConvFeatureEncoder(nn.Sequential):
1351
1405
  return nn.Sequential(
1352
1406
  conv,
1353
1407
  nn.Dropout(p=drop_prob),
1354
- nn.GroupNorm(output_channels, output_channels, affine=True),
1408
+ _GroupNorm(output_channels, output_channels, affine=True),
1355
1409
  activation(),
1356
1410
  )
1357
1411
  else:
@@ -1361,6 +1415,30 @@ class _ConvFeatureEncoder(nn.Sequential):
1361
1415
  return _n_times_out(self.conv_layers_spec, n_times)
1362
1416
 
1363
1417
 
1418
+ class _GroupNorm(nn.GroupNorm):
1419
+ """``nn.GroupNorm``; in HPU eager mode written out with mean and variance.
1420
+
1421
+ In HPU eager mode the backward of torch's group norm compiles new graphs at
1422
+ every call (about 0.2 s per call on the feature encoder's first block);
1423
+ the same normalisation from mean/var ops runs from cached kernels.
1424
+ Elsewhere (CPU, CUDA, HPU lazy) this is ``F.group_norm``.
1425
+ """
1426
+
1427
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
1428
+ if not torch.jit.is_scripting() and x.device.type == "hpu" and not _hpu_lazy():
1429
+ xg = x.reshape(x.shape[0], self.num_groups, -1)
1430
+ mean = xg.mean(-1, keepdim=True)
1431
+ var = xg.var(-1, unbiased=False, keepdim=True)
1432
+ x = ((xg - mean) * torch.rsqrt(var + self.eps)).reshape(x.shape)
1433
+ if self.affine:
1434
+ shape = (1, -1) + (1,) * (x.dim() - 2)
1435
+ x = x * self.weight.reshape(shape) + self.bias.reshape(shape)
1436
+ return x
1437
+ return nn.functional.group_norm(
1438
+ x, self.num_groups, self.weight, self.bias, self.eps
1439
+ )
1440
+
1441
+
1364
1442
  class _ChannelEmbedding(nn.Embedding):
1365
1443
  r"""Embedding layer for EEG channels.
1366
1444
 
@@ -12,12 +12,12 @@ import torch
12
12
  from einops import rearrange
13
13
  from einops.layers.torch import Rearrange
14
14
  from torch import nn
15
- from torch.nn import RMSNorm
16
15
 
17
16
  from braindecode.models.base import EEGModuleMixin
18
17
  from braindecode.models.util import extract_channel_locations_from_chs_info
19
18
  from braindecode.modules import PatchTokenizer
20
19
  from braindecode.modules.attention import _scaled_dot_product_attention
20
+ from braindecode.modules.layers import RMSNorm
21
21
 
22
22
 
23
23
  class ZUNA(EEGModuleMixin, nn.Module, license="apache-2.0"):
@@ -404,18 +404,16 @@ class _RotaryPositionEmbedding(nn.Module):
404
404
 
405
405
  batch_size, sequence_length, num_heads, head_dim = query.shape
406
406
  work = torch.promote_types(query.dtype, torch.float32)
407
+ # Each (real, imaginary) pair (a, b) rotates to (-b, a): swap the pair
408
+ # here; the sign is in ``rotary_sine`` (see _ZUNAEncoder).
407
409
  query_pairs = query.to(work).reshape(
408
410
  batch_size, sequence_length, num_heads, head_dim // 2, 2
409
411
  )
410
412
  key_pairs = key.to(work).reshape(
411
413
  batch_size, sequence_length, num_heads, head_dim // 2, 2
412
414
  )
413
- rotated_query = self.merge_rotary_pairs(
414
- torch.stack((-query_pairs[..., 1], query_pairs[..., 0]), dim=-1)
415
- )
416
- rotated_key = self.merge_rotary_pairs(
417
- torch.stack((-key_pairs[..., 1], key_pairs[..., 0]), dim=-1)
418
- )
415
+ rotated_query = self.merge_rotary_pairs(query_pairs.flip(-1))
416
+ rotated_key = self.merge_rotary_pairs(key_pairs.flip(-1))
419
417
 
420
418
  query = (query.to(work) * rotary_cosine + rotated_query * rotary_sine).type_as(
421
419
  query
@@ -607,9 +605,12 @@ class _ZUNAEncoder(nn.Module):
607
605
  rotary_frequencies.cos(),
608
606
  persistent=False,
609
607
  )
608
+ # Sine with the pair rotation's sign folded in: -sin on the real, +sin
609
+ # on the imaginary component of every pair (exact, saves a negation).
610
+ pair_sign = torch.tensor([-1.0, 1.0]).repeat(rotary_frequencies.shape[-1] // 2)
610
611
  self.register_buffer(
611
612
  "rotary_sine",
612
- rotary_frequencies.sin(),
613
+ rotary_frequencies.sin() * pair_sign,
613
614
  persistent=False,
614
615
  )
615
616
 
@@ -836,31 +836,61 @@ class CATLite(nn.Module):
836
836
 
837
837
  @torch.jit.unused
838
838
  def _hpu_eager_fused_sdpa(
839
- query: Tensor, key: Tensor, value: Tensor, scale: Optional[float] = None
839
+ query: Tensor,
840
+ key: Tensor,
841
+ value: Tensor,
842
+ scale: Optional[float] = None,
843
+ fused_in_lazy: bool = False,
840
844
  ) -> Optional[Tensor]:
841
- # Lazy mode: None, the graph compiler already fuses torch's SDPA there.
845
+ # Lazy mode: None, the graph compiler already fuses torch's SDPA there
846
+ # (unless the caller measured the fused kernel faster for its shapes).
842
847
  from habana_frameworks.torch.utils.internal import is_lazy
843
848
 
844
- if is_lazy():
849
+ if is_lazy() and not fused_in_lazy:
845
850
  return None
846
851
  from habana_frameworks.torch.hpex.kernels import FusedSDPA
847
852
 
853
+ # Strided q/k/v views (heads split off a projection) give wrong outputs and
854
+ # gradients at some shapes; contiguous copies give the right result.
855
+ query, key, value = query.contiguous(), key.contiguous(), value.contiguous()
848
856
  return FusedSDPA.apply(query, key, value, None, 0.0, False, scale, "None", False)
849
857
 
850
858
 
859
+ @torch.jit.unused
860
+ def _hpu_lazy() -> bool:
861
+ from habana_frameworks.torch.utils.internal import is_lazy
862
+
863
+ return is_lazy()
864
+
865
+
866
+ def _sdpa_math_dropout(
867
+ query: Tensor, key: Tensor, value: Tensor, dropout_p: float
868
+ ) -> Tensor:
869
+ # torch's math path for SDPA with dropout, op for op, without the pass of
870
+ # ``_safe_softmax`` that zeroes fully masked rows (none without a mask):
871
+ # same values, same dropout draws.
872
+ scale = math.sqrt(1.0 / math.sqrt(query.size(-1)))
873
+ attn = torch.matmul(query * scale, key.transpose(-2, -1) * scale)
874
+ attn = F.dropout(attn.softmax(-1), dropout_p, True)
875
+ return torch.matmul(attn, value)
876
+
877
+
851
878
  def _scaled_dot_product_attention(
852
879
  query: Tensor,
853
880
  key: Tensor,
854
881
  value: Tensor,
855
882
  attn_mask: Optional[Tensor] = None,
856
883
  dropout_p: float = 0.0,
884
+ fused_in_lazy: bool = False,
857
885
  ) -> Tensor:
858
886
  """``F.scaled_dot_product_attention``; Gaudi's fused kernel on HPU.
859
887
 
860
888
  In HPU eager mode, torch's SDPA runs op by op (the full
861
889
  ``(batch, heads, seq, seq)`` scores in device memory), 2-3.5x slower than
862
890
  the bridge's fused kernel on long sequences. The fused kernel is used
863
- without a mask or dropout (its dropout would draw other masks).
891
+ without a mask or dropout (its dropout would draw other masks). In lazy
892
+ mode only with ``fused_in_lazy=True``: the graph compiler fuses torch's
893
+ SDPA too, faster on long sequences, slower on short ones.
864
894
  """
865
895
  if (
866
896
  not torch.jit.is_scripting()
@@ -869,9 +899,21 @@ def _scaled_dot_product_attention(
869
899
  and dropout_p == 0.0
870
900
  and (query.dtype == torch.float32 or query.dtype == torch.bfloat16)
871
901
  ):
872
- out = _hpu_eager_fused_sdpa(query, key, value)
902
+ out = _hpu_eager_fused_sdpa(query, key, value, fused_in_lazy=fused_in_lazy)
873
903
  if out is not None:
874
904
  return out
905
+ if (
906
+ not torch.jit.is_scripting()
907
+ and dropout_p > 0.0
908
+ and attn_mask is None
909
+ and (query.dtype == torch.float32 or query.dtype == torch.float64)
910
+ and (
911
+ query.device.type == "cpu"
912
+ or (query.device.type == "hpu" and not _hpu_lazy())
913
+ )
914
+ ):
915
+ # CPU and HPU eager run SDPA with dropout through the math path.
916
+ return _sdpa_math_dropout(query, key, value, dropout_p)
875
917
  return F.scaled_dot_product_attention(
876
918
  query, key, value, attn_mask=attn_mask, dropout_p=dropout_p
877
919
  )
@@ -28,7 +28,19 @@ class _ConvBlock(nn.Module):
28
28
  self.act = None if last else activation()
29
29
 
30
30
  def forward(self, x):
31
- x = self.conv(x)
31
+ # Taps more than (T - 1) // dilation steps off-centre only ever read the
32
+ # zero padding: drop them (the same sum without its zero terms, fewer
33
+ # multiply-adds at long dilations).
34
+ d = self.conv.dilation[0]
35
+ half = self.conv.kernel_size[0] // 2
36
+ reach = (x.shape[-1] - 1) // d
37
+ if reach < half:
38
+ weight = self.conv.weight[:, :, half - reach : half + reach + 1]
39
+ x = nn.functional.conv1d(
40
+ x, weight, self.conv.bias, padding=reach * d, dilation=d
41
+ )
42
+ else:
43
+ x = self.conv(x)
32
44
  if self.act is not None:
33
45
  x = self.act(x)
34
46
  return x