biosn2 commited on
Commit
fca3797
·
verified ·
1 Parent(s): df6bc10

Upload indextts/gpt/conformer/attention.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. indextts/gpt/conformer/attention.py +312 -0
indextts/gpt/conformer/attention.py ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2019 Shigeki Karita
2
+ # 2020 Mobvoi Inc (Binbin Zhang)
3
+ # 2022 Xingchen Song (sxc19@mails.tsinghua.edu.cn)
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ """Multi-Head Attention layer definition."""
18
+
19
+ import math
20
+ from typing import Tuple
21
+
22
+ import torch
23
+ from torch import nn
24
+
25
+
26
+ class MultiHeadedAttention(nn.Module):
27
+ """Multi-Head Attention layer.
28
+
29
+ Args:
30
+ n_head (int): The number of heads.
31
+ n_feat (int): The number of features.
32
+ dropout_rate (float): Dropout rate.
33
+
34
+ """
35
+ def __init__(self, n_head: int, n_feat: int, dropout_rate: float):
36
+ """Construct an MultiHeadedAttention object."""
37
+ super().__init__()
38
+ assert n_feat % n_head == 0
39
+ # We assume d_v always equals d_k
40
+ self.d_k = n_feat // n_head
41
+ self.h = n_head
42
+ self.linear_q = nn.Linear(n_feat, n_feat)
43
+ self.linear_k = nn.Linear(n_feat, n_feat)
44
+ self.linear_v = nn.Linear(n_feat, n_feat)
45
+ self.linear_out = nn.Linear(n_feat, n_feat)
46
+ self.dropout = nn.Dropout(p=dropout_rate)
47
+
48
+ def forward_qkv(
49
+ self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor
50
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
51
+ """Transform query, key and value.
52
+
53
+ Args:
54
+ query (torch.Tensor): Query tensor (#batch, time1, size).
55
+ key (torch.Tensor): Key tensor (#batch, time2, size).
56
+ value (torch.Tensor): Value tensor (#batch, time2, size).
57
+
58
+ Returns:
59
+ torch.Tensor: Transformed query tensor, size
60
+ (#batch, n_head, time1, d_k).
61
+ torch.Tensor: Transformed key tensor, size
62
+ (#batch, n_head, time2, d_k).
63
+ torch.Tensor: Transformed value tensor, size
64
+ (#batch, n_head, time2, d_k).
65
+
66
+ """
67
+ n_batch = query.size(0)
68
+ q = self.linear_q(query).view(n_batch, -1, self.h, self.d_k)
69
+ k = self.linear_k(key).view(n_batch, -1, self.h, self.d_k)
70
+ v = self.linear_v(value).view(n_batch, -1, self.h, self.d_k)
71
+ q = q.transpose(1, 2) # (batch, head, time1, d_k)
72
+ k = k.transpose(1, 2) # (batch, head, time2, d_k)
73
+ v = v.transpose(1, 2) # (batch, head, time2, d_k)
74
+
75
+ return q, k, v
76
+
77
+ def forward_attention(
78
+ self, value: torch.Tensor, scores: torch.Tensor,
79
+ mask: torch.Tensor = torch.ones((0, 0, 0), dtype=torch.bool)
80
+ ) -> torch.Tensor:
81
+ """Compute attention context vector.
82
+
83
+ Args:
84
+ value (torch.Tensor): Transformed value, size
85
+ (#batch, n_head, time2, d_k).
86
+ scores (torch.Tensor): Attention score, size
87
+ (#batch, n_head, time1, time2).
88
+ mask (torch.Tensor): Mask, size (#batch, 1, time2) or
89
+ (#batch, time1, time2), (0, 0, 0) means fake mask.
90
+
91
+ Returns:
92
+ torch.Tensor: Transformed value (#batch, time1, d_model)
93
+ weighted by the attention score (#batch, time1, time2).
94
+
95
+ """
96
+ n_batch = value.size(0)
97
+ # NOTE(xcsong): When will `if mask.size(2) > 0` be True?
98
+ # 1. onnx(16/4) [WHY? Because we feed real cache & real mask for the
99
+ # 1st chunk to ease the onnx export.]
100
+ # 2. pytorch training
101
+ if mask.size(2) > 0 : # time2 > 0
102
+ mask = mask.unsqueeze(1).eq(0) # (batch, 1, *, time2)
103
+ # For last chunk, time2 might be larger than scores.size(-1)
104
+ mask = mask[:, :, :, :scores.size(-1)] # (batch, 1, *, time2)
105
+ scores = scores.masked_fill(mask, -float('inf'))
106
+ attn = torch.softmax(scores, dim=-1).masked_fill(
107
+ mask, 0.0) # (batch, head, time1, time2)
108
+ # NOTE(xcsong): When will `if mask.size(2) > 0` be False?
109
+ # 1. onnx(16/-1, -1/-1, 16/0)
110
+ # 2. jit (16/-1, -1/-1, 16/0, 16/4)
111
+ else:
112
+ attn = torch.softmax(scores, dim=-1) # (batch, head, time1, time2)
113
+
114
+ p_attn = self.dropout(attn)
115
+ x = torch.matmul(p_attn, value) # (batch, head, time1, d_k)
116
+ x = (x.transpose(1, 2).contiguous().view(n_batch, -1,
117
+ self.h * self.d_k)
118
+ ) # (batch, time1, d_model)
119
+
120
+ return self.linear_out(x) # (batch, time1, d_model)
121
+
122
+ def forward(self, query: torch.Tensor, key: torch.Tensor,
123
+ value: torch.Tensor,
124
+ mask: torch.Tensor = torch.ones((0, 0, 0), dtype=torch.bool),
125
+ pos_emb: torch.Tensor = torch.empty(0),
126
+ cache: torch.Tensor = torch.zeros((0, 0, 0, 0))
127
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
128
+ """Compute scaled dot product attention.
129
+
130
+ Args:
131
+ query (torch.Tensor): Query tensor (#batch, time1, size).
132
+ key (torch.Tensor): Key tensor (#batch, time2, size).
133
+ value (torch.Tensor): Value tensor (#batch, time2, size).
134
+ mask (torch.Tensor): Mask tensor (#batch, 1, time2) or
135
+ (#batch, time1, time2).
136
+ 1.When applying cross attention between decoder and encoder,
137
+ the batch padding mask for input is in (#batch, 1, T) shape.
138
+ 2.When applying self attention of encoder,
139
+ the mask is in (#batch, T, T) shape.
140
+ 3.When applying self attention of decoder,
141
+ the mask is in (#batch, L, L) shape.
142
+ 4.If the different position in decoder see different block
143
+ of the encoder, such as Mocha, the passed in mask could be
144
+ in (#batch, L, T) shape. But there is no such case in current
145
+ Wenet.
146
+ cache (torch.Tensor): Cache tensor (1, head, cache_t, d_k * 2),
147
+ where `cache_t == chunk_size * num_decoding_left_chunks`
148
+ and `head * d_k == size`
149
+
150
+
151
+ Returns:
152
+ torch.Tensor: Output tensor (#batch, time1, d_model).
153
+ torch.Tensor: Cache tensor (1, head, cache_t + time1, d_k * 2)
154
+ where `cache_t == chunk_size * num_decoding_left_chunks`
155
+ and `head * d_k == size`
156
+
157
+ """
158
+ q, k, v = self.forward_qkv(query, key, value)
159
+
160
+ # NOTE(xcsong):
161
+ # when export onnx model, for 1st chunk, we feed
162
+ # cache(1, head, 0, d_k * 2) (16/-1, -1/-1, 16/0 mode)
163
+ # or cache(1, head, real_cache_t, d_k * 2) (16/4 mode).
164
+ # In all modes, `if cache.size(0) > 0` will alwayse be `True`
165
+ # and we will always do splitting and
166
+ # concatnation(this will simplify onnx export). Note that
167
+ # it's OK to concat & split zero-shaped tensors(see code below).
168
+ # when export jit model, for 1st chunk, we always feed
169
+ # cache(0, 0, 0, 0) since jit supports dynamic if-branch.
170
+ # >>> a = torch.ones((1, 2, 0, 4))
171
+ # >>> b = torch.ones((1, 2, 3, 4))
172
+ # >>> c = torch.cat((a, b), dim=2)
173
+ # >>> torch.equal(b, c) # True
174
+ # >>> d = torch.split(a, 2, dim=-1)
175
+ # >>> torch.equal(d[0], d[1]) # True
176
+ if cache.size(0) > 0:
177
+ key_cache, value_cache = torch.split(
178
+ cache, cache.size(-1) // 2, dim=-1)
179
+ k = torch.cat([key_cache, k], dim=2)
180
+ v = torch.cat([value_cache, v], dim=2)
181
+ # NOTE(xcsong): We do cache slicing in encoder.forward_chunk, since it's
182
+ # non-trivial to calculate `next_cache_start` here.
183
+ new_cache = torch.cat((k, v), dim=-1)
184
+
185
+ scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k)
186
+ return self.forward_attention(v, scores, mask), new_cache
187
+
188
+
189
+ class RelPositionMultiHeadedAttention(MultiHeadedAttention):
190
+ """Multi-Head Attention layer with relative position encoding.
191
+ Paper: https://arxiv.org/abs/1901.02860
192
+ Args:
193
+ n_head (int): The number of heads.
194
+ n_feat (int): The number of features.
195
+ dropout_rate (float): Dropout rate.
196
+ """
197
+ def __init__(self, n_head, n_feat, dropout_rate):
198
+ """Construct an RelPositionMultiHeadedAttention object."""
199
+ super().__init__(n_head, n_feat, dropout_rate)
200
+ # linear transformation for positional encoding
201
+ self.linear_pos = nn.Linear(n_feat, n_feat, bias=False)
202
+ # these two learnable bias are used in matrix c and matrix d
203
+ # as described in https://arxiv.org/abs/1901.02860 Section 3.3
204
+ self.pos_bias_u = nn.Parameter(torch.Tensor(self.h, self.d_k))
205
+ self.pos_bias_v = nn.Parameter(torch.Tensor(self.h, self.d_k))
206
+ torch.nn.init.xavier_uniform_(self.pos_bias_u)
207
+ torch.nn.init.xavier_uniform_(self.pos_bias_v)
208
+
209
+ def rel_shift(self, x, zero_triu: bool = False):
210
+ """Compute relative positinal encoding.
211
+ Args:
212
+ x (torch.Tensor): Input tensor (batch, time, size).
213
+ zero_triu (bool): If true, return the lower triangular part of
214
+ the matrix.
215
+ Returns:
216
+ torch.Tensor: Output tensor.
217
+ """
218
+
219
+ zero_pad = torch.zeros((x.size()[0], x.size()[1], x.size()[2], 1),
220
+ device=x.device,
221
+ dtype=x.dtype)
222
+ x_padded = torch.cat([zero_pad, x], dim=-1)
223
+
224
+ x_padded = x_padded.view(x.size()[0],
225
+ x.size()[1],
226
+ x.size(3) + 1, x.size(2))
227
+ x = x_padded[:, :, 1:].view_as(x)
228
+
229
+ if zero_triu:
230
+ ones = torch.ones((x.size(2), x.size(3)))
231
+ x = x * torch.tril(ones, x.size(3) - x.size(2))[None, None, :, :]
232
+
233
+ return x
234
+
235
+ def forward(self, query: torch.Tensor,
236
+ key: torch.Tensor, value: torch.Tensor,
237
+ mask: torch.Tensor = torch.ones((0, 0, 0), dtype=torch.bool),
238
+ pos_emb: torch.Tensor = torch.empty(0),
239
+ cache: torch.Tensor = torch.zeros((0, 0, 0, 0))
240
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
241
+ """Compute 'Scaled Dot Product Attention' with rel. positional encoding.
242
+ Args:
243
+ query (torch.Tensor): Query tensor (#batch, time1, size).
244
+ key (torch.Tensor): Key tensor (#batch, time2, size).
245
+ value (torch.Tensor): Value tensor (#batch, time2, size).
246
+ mask (torch.Tensor): Mask tensor (#batch, 1, time2) or
247
+ (#batch, time1, time2), (0, 0, 0) means fake mask.
248
+ pos_emb (torch.Tensor): Positional embedding tensor
249
+ (#batch, time2, size).
250
+ cache (torch.Tensor): Cache tensor (1, head, cache_t, d_k * 2),
251
+ where `cache_t == chunk_size * num_decoding_left_chunks`
252
+ and `head * d_k == size`
253
+ Returns:
254
+ torch.Tensor: Output tensor (#batch, time1, d_model).
255
+ torch.Tensor: Cache tensor (1, head, cache_t + time1, d_k * 2)
256
+ where `cache_t == chunk_size * num_decoding_left_chunks`
257
+ and `head * d_k == size`
258
+ """
259
+ q, k, v = self.forward_qkv(query, key, value)
260
+ q = q.transpose(1, 2) # (batch, time1, head, d_k)
261
+
262
+ # NOTE(xcsong):
263
+ # when export onnx model, for 1st chunk, we feed
264
+ # cache(1, head, 0, d_k * 2) (16/-1, -1/-1, 16/0 mode)
265
+ # or cache(1, head, real_cache_t, d_k * 2) (16/4 mode).
266
+ # In all modes, `if cache.size(0) > 0` will alwayse be `True`
267
+ # and we will always do splitting and
268
+ # concatnation(this will simplify onnx export). Note that
269
+ # it's OK to concat & split zero-shaped tensors(see code below).
270
+ # when export jit model, for 1st chunk, we always feed
271
+ # cache(0, 0, 0, 0) since jit supports dynamic if-branch.
272
+ # >>> a = torch.ones((1, 2, 0, 4))
273
+ # >>> b = torch.ones((1, 2, 3, 4))
274
+ # >>> c = torch.cat((a, b), dim=2)
275
+ # >>> torch.equal(b, c) # True
276
+ # >>> d = torch.split(a, 2, dim=-1)
277
+ # >>> torch.equal(d[0], d[1]) # True
278
+ if cache.size(0) > 0:
279
+ key_cache, value_cache = torch.split(
280
+ cache, cache.size(-1) // 2, dim=-1)
281
+ k = torch.cat([key_cache, k], dim=2)
282
+ v = torch.cat([value_cache, v], dim=2)
283
+ # NOTE(xcsong): We do cache slicing in encoder.forward_chunk, since it's
284
+ # non-trivial to calculate `next_cache_start` here.
285
+ new_cache = torch.cat((k, v), dim=-1)
286
+
287
+ n_batch_pos = pos_emb.size(0)
288
+ p = self.linear_pos(pos_emb).view(n_batch_pos, -1, self.h, self.d_k)
289
+ p = p.transpose(1, 2) # (batch, head, time1, d_k)
290
+
291
+ # (batch, head, time1, d_k)
292
+ q_with_bias_u = (q + self.pos_bias_u).transpose(1, 2)
293
+ # (batch, head, time1, d_k)
294
+ q_with_bias_v = (q + self.pos_bias_v).transpose(1, 2)
295
+
296
+ # compute attention score
297
+ # first compute matrix a and matrix c
298
+ # as described in https://arxiv.org/abs/1901.02860 Section 3.3
299
+ # (batch, head, time1, time2)
300
+ matrix_ac = torch.matmul(q_with_bias_u, k.transpose(-2, -1))
301
+
302
+ # compute matrix b and matrix d
303
+ # (batch, head, time1, time2)
304
+ matrix_bd = torch.matmul(q_with_bias_v, p.transpose(-2, -1))
305
+ # Remove rel_shift since it is useless in speech recognition,
306
+ # and it requires special attention for streaming.
307
+ # matrix_bd = self.rel_shift(matrix_bd)
308
+
309
+ scores = (matrix_ac + matrix_bd) / math.sqrt(
310
+ self.d_k) # (batch, head, time1, time2)
311
+
312
+ return self.forward_attention(v, scores, mask), new_cache