Skip to content

Commit 9a2cae2

Browse files
committed
wip
1 parent 1194018 commit 9a2cae2

3 files changed

Lines changed: 864 additions & 231 deletions

File tree

returnn/frontend/_backend.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,7 +1337,8 @@ def scaled_dot_product_attention(
13371337
*,
13381338
attention_mask: Optional[Tensor] = None,
13391339
dropout: float = 0.0,
1340-
embed_dim: Dim,
1340+
v_embed_dim: Dim,
1341+
qk_embed_dim: Dim,
13411342
kv_spatial_dim: Dim,
13421343
query_spatial_dim: Dim,
13431344
is_causal: bool = False,
@@ -1351,7 +1352,8 @@ def scaled_dot_product_attention(
13511352
:param value:
13521353
:param attention_mask:
13531354
:param dropout:
1354-
:param embed_dim: Embedding dimension of key (and query)
1355+
:param v_embed_dim: Embedding dimension of value
1356+
:param qk_embed_dim: Embedding dimension of key and query
13551357
:param kv_spatial_dim: Spatial axis of key/value to attend over
13561358
:param query_spatial_dim: Spatial axis of query
13571359
:param is_causal: Special case when the attention mask should be causal (e.g. for auto-regressive decoding).
@@ -1360,7 +1362,7 @@ def scaled_dot_product_attention(
13601362
:return: attention output
13611363
"""
13621364

1363-
query *= embed_dim.dimension**-0.5 if scale is None else scale
1365+
query *= qk_embed_dim.dimension**-0.5 if scale is None else scale
13641366

13651367
if is_causal:
13661368
assert attention_mask is None
@@ -1379,7 +1381,7 @@ def scaled_dot_product_attention(
13791381
else:
13801382
attn_bias = attention_mask # assume float-like
13811383

1382-
energy = rf.matmul(query, key, reduce=embed_dim) # [.., Q_spatial, K_spatial]
1384+
energy = rf.matmul(query, key, reduce=qk_embed_dim) # [.., Q_spatial, K_spatial]
13831385
if attn_bias is not None:
13841386
energy = energy + attn_bias
13851387
att_weights = rf.softmax(energy, axis=kv_spatial_dim) # [.., Q_spatial, K_spatial]

returnn/frontend/attention.py

Lines changed: 177 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212

1313
__all__ = [
14+
"scaled_dot_product_attention",
1415
"dot_attention",
1516
"SelfAttentionBase",
1617
"SelfAttention",
@@ -27,6 +28,52 @@
2728
]
2829

2930

31+
def scaled_dot_product_attention(
32+
query: Tensor,
33+
key: Tensor,
34+
value: Tensor,
35+
*,
36+
attention_mask: Optional[Tensor] = None,
37+
dropout: float = 0.0,
38+
v_embed_dim: Dim,
39+
qk_embed_dim: Dim,
40+
kv_spatial_dim: Dim,
41+
query_spatial_dim: Dim,
42+
is_causal: bool = False,
43+
scale: Optional[float] = None,
44+
):
45+
"""
46+
Scaled dot-product attention.
47+
48+
:param query:
49+
:param key:
50+
:param value:
51+
:param attention_mask:
52+
:param dropout:
53+
:param v_embed_dim: Embedding dimension of value
54+
:param qk_embed_dim: Embedding dimension of key (and query)
55+
:param kv_spatial_dim: Spatial axis of key/value to attend over
56+
:param query_spatial_dim: Spatial axis of query
57+
:param is_causal: Special case when the attention mask should be causal (e.g. for auto-regressive decoding).
58+
Allows for more efficient implementation in some backends.
59+
:param scale: Scaling factor applied prior to softmax
60+
:return: attention output
61+
"""
62+
return query._raw_backend.scaled_dot_product_attention(
63+
query,
64+
key,
65+
value,
66+
attention_mask=attention_mask,
67+
dropout=dropout,
68+
v_embed_dim=v_embed_dim,
69+
qk_embed_dim=qk_embed_dim,
70+
kv_spatial_dim=kv_spatial_dim,
71+
query_spatial_dim=query_spatial_dim,
72+
is_causal=is_causal,
73+
scale=scale,
74+
)
75+
76+
3077
def dot_attention(
3178
query: Tensor,
3279
keys: Tensor,
@@ -54,17 +101,52 @@ def dot_attention(
54101
normally not wanted. disabled by default since behavior version 19.
55102
:return: like values but with axis removed, and maybe any additional axes from query
56103
"""
57-
query *= key_dim.dimension**-0.5
58-
energy = rf.matmul(query, keys, reduce=key_dim)
59-
att_weights = rf.softmax(energy, axis=axis)
60-
if att_dropout_broadcast is None:
61-
att_dropout_broadcast = _att_dropout_broadcast_default()
62-
att_weights = rf.dropout(att_weights, att_dropout, axis=att_dropout_broadcast and axis)
63-
# Masking not needed because softmax should already have masked,
64-
# so we have 0.0 att weights for padded frames.
65-
att = rf.matmul(att_weights, values, reduce=axis, use_mask=False)
66-
if values.feature_dim in att.dims:
67-
att.feature_dim = values.feature_dim
104+
assert axis in keys.dims_set
105+
assert axis not in query.dims_set
106+
# print(f"keys dims: {keys.dims}, query dims: {query.dims}, values dims: {values.dims}")
107+
query_non_batch_dims = query.remaining_dims(keys.dims_set - {axis})
108+
if len(query_non_batch_dims) == 0:
109+
query_spatial = Dim(1, name="dot_att_query_spatial_dummy")
110+
query = rf.expand_dim(query, dim=query_spatial)
111+
else:
112+
assert len(query_non_batch_dims) == 1, f"qspat={query_non_batch_dims}, q={query.dims}, k={keys.dims}"
113+
query_spatial = query_non_batch_dims[0]
114+
115+
v_embed_dim = values.feature_dim
116+
if v_embed_dim is None:
117+
if key_dim in values.dims_set:
118+
v_embed_dim = key_dim
119+
else:
120+
relevant_dims = values.dims_set - keys.dims_set
121+
if len(relevant_dims) == 1:
122+
v_embed_dim = list(relevant_dims)[0]
123+
else:
124+
raise ValueError(f"Cannot infer v_embed_dim from values.dims={values.dims}, keys.dims={keys.dims}")
125+
126+
att = scaled_dot_product_attention(
127+
query,
128+
keys,
129+
values,
130+
dropout=att_dropout,
131+
v_embed_dim=v_embed_dim,
132+
qk_embed_dim=key_dim,
133+
kv_spatial_dim=axis,
134+
query_spatial_dim=query_spatial,
135+
is_causal=False,
136+
)
137+
if len(query_non_batch_dims) == 0:
138+
att = rf.squeeze(att, axis=query_spatial)
139+
# query *= key_dim.dimension**-0.5
140+
# energy = rf.matmul(query, keys, reduce=key_dim)
141+
# att_weights = rf.softmax(energy, axis=axis)
142+
# if att_dropout_broadcast is None:
143+
# att_dropout_broadcast = _att_dropout_broadcast_default()
144+
# att_weights = rf.dropout(att_weights, att_dropout, axis=att_dropout_broadcast and axis)
145+
# # Masking not needed because softmax should already have masked,
146+
# # so we have 0.0 att weights for padded frames.
147+
# att = rf.matmul(att_weights, values, reduce=axis, use_mask=False)
148+
# if values.feature_dim in att.dims:
149+
# att.feature_dim = values.feature_dim
68150
return att
69151

70152

@@ -149,7 +231,11 @@ def forward_qkv(self, source: Tensor) -> Tuple[Tensor, Tensor, Tensor]:
149231
q, k, v = rf.split(
150232
qkv,
151233
axis=self.qkv_dim_per_head,
152-
out_dims=(self.key_dim_per_head, self.key_dim_per_head, self.value_dim_per_head),
234+
out_dims=(
235+
self.key_dim_per_head,
236+
self.key_dim_per_head,
237+
self.value_dim_per_head,
238+
),
153239
)
154240
return q, k, v
155241

@@ -164,7 +250,11 @@ def attention(self, q: Tensor, k: Tensor, v: Tensor, *, kv_axis: Dim) -> Tensor:
164250
att_dropout=self.att_dropout,
165251
att_dropout_broadcast=self.att_dropout_broadcast,
166252
)
167-
output, _ = rf.merge_dims(att, dims=(self.num_heads, self.value_dim_per_head), out_dim=self.value_dim_total)
253+
output, _ = rf.merge_dims(
254+
att,
255+
dims=(self.num_heads, self.value_dim_per_head),
256+
out_dim=self.value_dim_total,
257+
)
168258
if self.proj:
169259
output = self.proj(output)
170260
return output
@@ -257,7 +347,13 @@ class CausalSelfAttentionState(rf.State):
257347
State for :class:`StepwiseCausalSelfAttention`.
258348
"""
259349

260-
def __init__(self, *_args, k_accum: Tensor = None, v_accum: Tensor = None, accum_axis: Dim = None):
350+
def __init__(
351+
self,
352+
*_args,
353+
k_accum: Tensor = None,
354+
v_accum: Tensor = None,
355+
accum_axis: Dim = None,
356+
):
261357
"""
262358
:param k_accum: accumulated keys
263359
:param v_accum: accumulated values
@@ -330,7 +426,11 @@ def __call__(
330426
q = _apply_rope(
331427
q,
332428
(
333-
rf.gather(pos_enc, axis=hist_dim, indices=rf.last_frame_position_of_dim(hist_dim))
429+
rf.gather(
430+
pos_enc,
431+
axis=hist_dim,
432+
indices=rf.last_frame_position_of_dim(hist_dim),
433+
)
334434
if axis == single_step_dim
335435
else rf.replace_dim(pos_enc, in_dim=hist_dim, out_dim=axis)[0]
336436
),
@@ -430,7 +530,9 @@ def __init__(
430530
self.linear_pos = None
431531
if with_linear_pos:
432532
self.linear_pos = rf.Linear(
433-
self.in_dim, self.key_dim_total if separate_pos_emb_per_head else self.key_dim_per_head, with_bias=False
533+
self.in_dim,
534+
self.key_dim_total if separate_pos_emb_per_head else self.key_dim_per_head,
535+
with_bias=False,
434536
)
435537
self.learned_pos_emb = None
436538
if learnable_pos_emb:
@@ -454,14 +556,21 @@ def __call__(self, source: Tensor, *, axis: Dim, **_kwargs) -> Tensor:
454556
pos_emb, pos_emb_spatial_dim = self.learned_pos_emb(query_spatial_dim=axis, key_value_spatial_dim=axis)
455557
else:
456558
pos_emb, pos_emb_spatial_dim = relative_positional_encoding(
457-
query_spatial_dim=axis, key_value_spatial_dim=axis, feat_dim=self.pos_emb_feat_dim, device=source.device
559+
query_spatial_dim=axis,
560+
key_value_spatial_dim=axis,
561+
feat_dim=self.pos_emb_feat_dim,
562+
device=source.device,
458563
)
459564
if self.pos_emb_dropout:
460565
pos_emb = rf.dropout(pos_emb, self.pos_emb_dropout)
461566
if self.linear_pos is not None:
462567
pos_emb = self.linear_pos(pos_emb)
463568
if self.separate_pos_emb_per_head:
464-
pos_emb = rf.split_dims(pos_emb, axis=self.key_dim_total, dims=(self.num_heads, self.key_dim_per_head))
569+
pos_emb = rf.split_dims(
570+
pos_emb,
571+
axis=self.key_dim_total,
572+
dims=(self.num_heads, self.key_dim_per_head),
573+
)
465574
# pos_emb: (head, 2*time1-1, d_k)
466575

467576
q, k, v = self.forward_qkv(source)
@@ -490,7 +599,11 @@ def __call__(self, source: Tensor, *, axis: Dim, **_kwargs) -> Tensor:
490599
# Masking not needed because softmax should already have masked,
491600
# so we have 0.0 att weights for padded frames.
492601
att = rf.matmul(att_weights, v, reduce=hist_dim, use_mask=False)
493-
output, _ = rf.merge_dims(att, dims=(self.num_heads, self.value_dim_per_head), out_dim=self.value_dim_total)
602+
output, _ = rf.merge_dims(
603+
att,
604+
dims=(self.num_heads, self.value_dim_per_head),
605+
out_dim=self.value_dim_total,
606+
)
494607
if self.proj:
495608
output = self.proj(output)
496609
return output
@@ -581,7 +694,9 @@ def __init__(
581694
self.linear_pos = None
582695
if with_linear_pos:
583696
self.linear_pos = rf.Linear(
584-
self.in_dim, self.key_dim_total if separate_pos_emb_per_head else self.key_dim_per_head, with_bias=False
697+
self.in_dim,
698+
self.key_dim_total if separate_pos_emb_per_head else self.key_dim_per_head,
699+
with_bias=False,
585700
)
586701
self.learned_pos_emb = None
587702
if learnable_pos_emb:
@@ -600,7 +715,11 @@ def __init__(
600715
self.pos_emb_dropout = pos_emb_dropout
601716

602717
def __call__(
603-
self, source: Tensor, *, axis: Dim, state: Optional[CausalSelfAttentionState] = None
718+
self,
719+
source: Tensor,
720+
*,
721+
axis: Dim,
722+
state: Optional[CausalSelfAttentionState] = None,
604723
) -> Tuple[Tensor, CausalSelfAttentionState]:
605724
"""forward"""
606725
q, k, v = self.forward_qkv(source)
@@ -621,7 +740,11 @@ def __call__(
621740
if self.linear_pos is not None:
622741
pos_emb = self.linear_pos(pos_emb)
623742
if self.separate_pos_emb_per_head:
624-
pos_emb = rf.split_dims(pos_emb, axis=self.key_dim_total, dims=(self.num_heads, self.key_dim_per_head))
743+
pos_emb = rf.split_dims(
744+
pos_emb,
745+
axis=self.key_dim_total,
746+
dims=(self.num_heads, self.key_dim_per_head),
747+
)
625748
# pos_emb: (head, 2*time1-1, d_k)
626749

627750
q_with_bias_u = (q + self.pos_bias_u) if self.pos_bias_u is not None else q # (batch, head, time1, d_k)
@@ -645,7 +768,11 @@ def __call__(
645768
# Masking not needed because softmax should already have masked,
646769
# so we have 0.0 att weights for padded frames.
647770
att = rf.matmul(att_weights, v, reduce=hist_dim, use_mask=False)
648-
output, _ = rf.merge_dims(att, dims=(self.num_heads, self.value_dim_per_head), out_dim=self.value_dim_total)
771+
output, _ = rf.merge_dims(
772+
att,
773+
dims=(self.num_heads, self.value_dim_per_head),
774+
out_dim=self.value_dim_total,
775+
)
649776
if self.proj:
650777
output = self.proj(output)
651778
return output, new_state
@@ -773,7 +900,11 @@ def attention(self, q: Tensor, k: Tensor, v: Tensor, *, kv_axis: Dim) -> Tensor:
773900
att_dropout=self.att_dropout,
774901
att_dropout_broadcast=self.att_dropout_broadcast,
775902
)
776-
output, _ = rf.merge_dims(att, dims=(self.num_heads, self.value_dim_per_head), out_dim=self.value_dim_total)
903+
output, _ = rf.merge_dims(
904+
att,
905+
dims=(self.num_heads, self.value_dim_per_head),
906+
out_dim=self.value_dim_total,
907+
)
777908
if self.proj:
778909
output = self.proj(output)
779910
return output
@@ -788,7 +919,14 @@ class LearnedRelativePositionalEncoding(rf.Module):
788919
https://github.com/rwth-i6/returnn_common/wiki/Relative-positional-encoding
789920
"""
790921

791-
def __init__(self, feat_dim: Dim, *, clipping: int = 16, dtype: Optional[str] = None, causal: bool = False):
922+
def __init__(
923+
self,
924+
feat_dim: Dim,
925+
*,
926+
clipping: int = 16,
927+
dtype: Optional[str] = None,
928+
causal: bool = False,
929+
):
792930
"""
793931
:param feat_dim: feature dim, for the emb matrix and output
794932
:param clipping: max distance to consider. emb matrix shape is [2 * clipping + 1, feat_dim] if not causal,
@@ -889,7 +1027,10 @@ def _make_indices(
8891027
# The min value is with kv_pos=0, q_pos=q_len-1: -(q_len-1)
8901028
# The max value is with kv_pos=kv_len-1, q_pos=0: k_len-1
8911029
indices, _ = rf.concat(
892-
(q_pos_vec - query_spatial_dim_m1.get_dim_value_tensor(), query_spatial_dim_m1),
1030+
(
1031+
q_pos_vec - query_spatial_dim_m1.get_dim_value_tensor(),
1032+
query_spatial_dim_m1,
1033+
),
8931034
(kv_pos_vec, key_value_spatial_dim),
8941035
out_dim=out_spatial_dim,
8951036
handle_dynamic_dims=False,
@@ -933,9 +1074,18 @@ def relative_positional_encoding(
9331074
"""
9341075
if not dtype:
9351076
dtype = rf.get_default_float_dtype()
1077+
9361078
if not device:
9371079
device = rf.get_default_device()
938-
cache_key = (query_spatial_dim, key_value_spatial_dim, feat_dim, query_offset, dtype, device)
1080+
1081+
cache_key = (
1082+
query_spatial_dim,
1083+
key_value_spatial_dim,
1084+
feat_dim,
1085+
query_offset,
1086+
dtype,
1087+
device,
1088+
)
9391089
cache_entry = _relative_positional_encoding_cache.get(cache_key)
9401090
if cache_entry is not None:
9411091
return cache_entry

0 commit comments

Comments
 (0)