@@ -414,6 +414,7 @@ def __init__(
414414 manual_update : bool = False ,
415415 traces : bool = False ,
416416 compute_dtype : Optional [torch .dtype ] = None ,
417+ sparse_compute : bool = False ,
417418 ** kwargs ,
418419 ) -> None :
419420 # language=rst
@@ -429,6 +430,8 @@ def __init__(
429430 :param traces: Set to :code:`True` to record history of connection activity (for monitors)
430431 :param compute_dtype: Optional low-precision dtype (e.g. ``torch.bfloat16``)
431432 for the matmul fast path only; ``None`` (default) keeps full ``float32``.
433+ :param sparse_compute: Set to :code:`True` to compute sparse activity (<~20% neurons active)
434+ efficiently
432435 """
433436
434437 super ().__init__ (source , target , device , pipeline , ** kwargs )
@@ -440,6 +443,7 @@ def __init__(
440443 self ._w_eff = None # 'Folded' weight matrix for faster computing
441444 self ._has_learning = None
442445 self .compute_dtype = compute_dtype
446+ self .sparse_compute = sparse_compute
443447
444448 def _pipeline_has_learning (self ) -> bool :
445449 # language=rst
@@ -480,6 +484,20 @@ def _folded_weight(self) -> Optional[torch.Tensor]:
480484 self ._w_eff = w_eff
481485 return w_eff
482486
487+ def _sparse_matmul (self , s_flat : torch .Tensor , w_eff : torch .Tensor ) -> torch .Tensor :
488+ # language=rst
489+ """
490+ Activity-sparse form of ``s_flat @ w_eff``. Read only
491+ the rows of ``w_eff`` for source neurons that spiked this step. Numerically
492+ identical to the dense matmul. More efficient when few source neurons are active.
493+ """
494+ active = s_flat .any (dim = 0 ).nonzero (as_tuple = False ).squeeze (1 )
495+ if active .numel () == 0 :
496+ return torch .zeros (
497+ s_flat .size (0 ), self .target .n , device = w_eff .device , dtype = w_eff .dtype
498+ )
499+ return s_flat [:, active ].to (w_eff .dtype ) @ w_eff .index_select (0 , active )
500+
483501 def compute (self , s : torch .Tensor ) -> torch .Tensor :
484502 # language=rst
485503 """
@@ -493,7 +511,11 @@ def compute(self, s: torch.Tensor) -> torch.Tensor:
493511 # Fast path: when the whole pipeline folds to a static elementwise-multiply
494512 w_eff = self ._folded_weight ()
495513 if w_eff is not None :
496- out_signal = s .view (s .size (0 ), self .source .n ).to (w_eff .dtype ) @ w_eff
514+ s_flat = s .view (s .size (0 ), self .source .n )
515+ if self .sparse_compute :
516+ out_signal = self ._sparse_matmul (s_flat , w_eff )
517+ else :
518+ out_signal = s_flat .to (w_eff .dtype ) @ w_eff
497519 if out_signal .dtype != torch .float32 :
498520 out_signal = out_signal .float ()
499521 if self .traces :
0 commit comments