@@ -1096,3 +1096,86 @@ def test_most_recent_matching_value_wider_than_d_pos():
10961096 assert torch .allclose (result [0 ], value_in [0 ], atol = 1e-2 )
10971097 assert torch .allclose (result [1 ], value_in [0 ], atol = 1e-2 )
10981098 assert torch .allclose (result [2 ], value_in [2 ], atol = 1e-2 )
1099+
1100+
1101+ def test_most_recent_matching_exclude_self ():
1102+ """``exclude_self=True`` skips the current query position even when
1103+ its own key matches — picking the most recent matching position
1104+ strictly before self.
1105+
1106+ Setup: every position queries type 0; positions 0, 2, 3 are
1107+ type-0 keys (matches) and position 1 is type 1 (no match).
1108+
1109+ Default (exclude_self=False) behaviour:
1110+ pos 1 → match at pos 0 → value 10
1111+ pos 2 → match at pos 2 → value 30 (self matches)
1112+ pos 3 → match at pos 3 → value 40 (self matches)
1113+
1114+ With exclude_self=True the head shifts key/value back by one, so
1115+ self can no longer contribute and the predecessor's value is
1116+ returned at the rightmost slot:
1117+ pos 1 → predecessor of pos 1 is pos 0 (match) → value 10
1118+ pos 2 → predecessor of pos 2 is pos 1 (no match); next
1119+ most-recent match below self is pos 0 → value 10
1120+ pos 3 → predecessor of pos 3 is pos 2 (match) → value 30
1121+ Position 0 is degenerate under the shift (no prior position exists)
1122+ and is not asserted.
1123+ """
1124+ pe = _pe ()
1125+ query = InputNode ("query" , 4 , value_range = (- 1.0 , 1.0 ))
1126+ key = InputNode ("key" , 4 , value_range = (- 1.0 , 1.0 ))
1127+ value = InputNode ("value" , 1 , value_range = (- 100.0 , 100.0 ))
1128+
1129+ out_default = attend_most_recent_matching (pe , query , key , value )
1130+ out_excl = attend_most_recent_matching (pe , query , key , value , exclude_self = True )
1131+
1132+ n_pos = 4
1133+ # types per position: 0, 1, 0, 0 — positions 0, 2, 3 match type 0.
1134+ key_in = torch .eye (4 )[torch .tensor ([0 , 1 , 0 , 0 ])]
1135+ # All queries look for type 0.
1136+ query_in = torch .eye (4 )[torch .tensor ([0 , 0 , 0 , 0 ])]
1137+ value_in = torch .tensor ([[10.0 ], [20.0 ], [30.0 ], [40.0 ]])
1138+
1139+ result_default = _run (out_default , n_pos , query = query_in , key = key_in , value = value_in )
1140+ result_excl = _run (out_excl , n_pos , query = query_in , key = key_in , value = value_in )
1141+
1142+ # Sanity: default mode picks self when self matches.
1143+ assert abs (result_default [1 ].item () - 10.0 ) < 1e-2 , result_default [1 ]
1144+ assert abs (result_default [2 ].item () - 30.0 ) < 1e-2 , result_default [2 ]
1145+ assert abs (result_default [3 ].item () - 40.0 ) < 1e-2 , result_default [3 ]
1146+
1147+ # exclude_self mode: self's match doesn't count; we get the most
1148+ # recent strict-prior match instead.
1149+ assert abs (result_excl [1 ].item () - 10.0 ) < 1e-2 , result_excl [1 ]
1150+ assert abs (result_excl [2 ].item () - 10.0 ) < 1e-2 , result_excl [2 ]
1151+ assert abs (result_excl [3 ].item () - 30.0 ) < 1e-2 , result_excl [3 ]
1152+
1153+
1154+ def test_most_recent_matching_exclude_self_width_constraint ():
1155+ """``exclude_self=True`` requires ``len(value) <= d_pos`` and
1156+ ``len(key_vector) <= d_pos`` because each pre-shift compiles to a
1157+ single attention head whose width is bounded by ``d_pos``. The
1158+ default mode has no such limit.
1159+ """
1160+ import pytest
1161+
1162+ pe = _pe () # d_pos = 16
1163+ query = InputNode ("query" , 4 , value_range = (- 1.0 , 1.0 ))
1164+ key = InputNode ("key" , 4 , value_range = (- 1.0 , 1.0 ))
1165+ wide_value = InputNode ("value" , 24 , value_range = (- 100.0 , 100.0 ))
1166+
1167+ # Default mode accepts width 24 > d_pos = 16.
1168+ attend_most_recent_matching (pe , query , key , wide_value )
1169+
1170+ # exclude_self mode rejects it.
1171+ with pytest .raises (AssertionError , match = "value width" ):
1172+ attend_most_recent_matching (pe , query , key , wide_value , exclude_self = True )
1173+
1174+ # Symmetric check on key width.
1175+ wide_query = InputNode ("query" , 24 , value_range = (- 1.0 , 1.0 ))
1176+ wide_key = InputNode ("key" , 24 , value_range = (- 1.0 , 1.0 ))
1177+ narrow_value = InputNode ("value" , 1 , value_range = (- 100.0 , 100.0 ))
1178+ with pytest .raises (AssertionError , match = "key_vector width" ):
1179+ attend_most_recent_matching (
1180+ pe , wide_query , wide_key , narrow_value , exclude_self = True
1181+ )
0 commit comments