Skip to content
This repository was archived by the owner on Mar 20, 2026. It is now read-only.

Commit 57526c6

Browse files
Xianfeng Ruifacebook-github-bot
authored andcommitted
Update Fairseq LSTM to jitable version (#2016)
Summary: Pull Request resolved: #2016 It is to update Fairseq LSTM to jitable version Reviewed By: cndn Differential Revision: D20937370 fbshipit-source-id: 26f677fcb58bbeaa507d303e9a81060ff78f0502
1 parent 89e75fa commit 57526c6

4 files changed

Lines changed: 212 additions & 62 deletions

File tree

‎fairseq/models/fairseq_decoder.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def get_normalized_probs(
5959
self,
6060
net_output: Tuple[Tensor, Optional[Dict[str, List[Optional[Tensor]]]]],
6161
log_probs: bool,
62-
sample: Optional[Dict[str, Tensor]],
62+
sample: Optional[Dict[str, Tensor]] = None,
6363
):
6464
"""Get normalized probabilities (or log probs) from a net's output."""
6565

‎fairseq/models/lstm.py‎

Lines changed: 96 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616
register_model_architecture,
1717
)
1818
from fairseq.modules import AdaptiveSoftmax
19+
from torch import Tensor
20+
from typing import Dict, List, Optional, Tuple
21+
1922

2023
DEFAULT_MAX_SOURCE_POSITIONS = 1e5
2124
DEFAULT_MAX_TARGET_POSITIONS = 1e5
2225

26+
2327
@register_model('lstm')
2428
class LSTMModel(FairseqEncoderDecoderModel):
2529
def __init__(self, encoder, decoder):
@@ -176,6 +180,19 @@ def load_pretrained_embedding_from_file(embed_path, dictionary, embed_dim):
176180
)
177181
return cls(encoder, decoder)
178182

183+
def forward(
184+
self,
185+
src_tokens,
186+
src_lengths,
187+
prev_output_tokens,
188+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None,
189+
):
190+
encoder_out = self.encoder(src_tokens, src_lengths=src_lengths)
191+
decoder_out = self.decoder(
192+
prev_output_tokens, encoder_out=encoder_out, incremental_state=incremental_state
193+
)
194+
return decoder_out
195+
179196

180197
class LSTMEncoder(FairseqEncoder):
181198
"""LSTM encoder."""
@@ -213,13 +230,17 @@ def __init__(
213230
if bidirectional:
214231
self.output_units *= 2
215232

216-
def forward(self, src_tokens, src_lengths):
233+
def combine_bidir(self, outs, bsz: int):
234+
out = outs.view(self.num_layers, 2, bsz, -1).transpose(1, 2).contiguous()
235+
return out.view(self.num_layers, bsz, -1)
236+
237+
def forward(self, src_tokens, src_lengths: Tensor):
217238
if self.left_pad:
218239
# nn.utils.rnn.pack_padded_sequence requires right-padding;
219240
# convert left-padding to right-padding
220241
src_tokens = utils.convert_padding_direction(
221242
src_tokens,
222-
self.padding_idx,
243+
torch.zeros_like(src_tokens).fill_(self.padding_idx),
223244
left_to_right=True,
224245
)
225246

@@ -233,7 +254,7 @@ def forward(self, src_tokens, src_lengths):
233254
x = x.transpose(0, 1)
234255

235256
# pack embedded source tokens into a PackedSequence
236-
packed_x = nn.utils.rnn.pack_padded_sequence(x, src_lengths.data.tolist())
257+
packed_x = nn.utils.rnn.pack_padded_sequence(x, src_lengths.data)
237258

238259
# apply LSTM
239260
if self.bidirectional:
@@ -245,34 +266,32 @@ def forward(self, src_tokens, src_lengths):
245266
packed_outs, (final_hiddens, final_cells) = self.lstm(packed_x, (h0, c0))
246267

247268
# unpack outputs and apply dropout
248-
x, _ = nn.utils.rnn.pad_packed_sequence(packed_outs, padding_value=self.padding_idx)
269+
x, _ = nn.utils.rnn.pad_packed_sequence(packed_outs, padding_value=self.padding_idx*1.0)
249270
x = F.dropout(x, p=self.dropout_out, training=self.training)
250271
assert list(x.size()) == [seqlen, bsz, self.output_units]
251272

252273
if self.bidirectional:
253274

254-
def combine_bidir(outs):
255-
out = outs.view(self.num_layers, 2, bsz, -1).transpose(1, 2).contiguous()
256-
return out.view(self.num_layers, bsz, -1)
257-
258-
final_hiddens = combine_bidir(final_hiddens)
259-
final_cells = combine_bidir(final_cells)
275+
final_hiddens = self.combine_bidir(final_hiddens, bsz)
276+
final_cells = self.combine_bidir(final_cells, bsz)
260277

261278
encoder_padding_mask = src_tokens.eq(self.padding_idx).t()
262279

263280
return {
264281
'encoder_out': (x, final_hiddens, final_cells),
265-
'encoder_padding_mask': encoder_padding_mask if encoder_padding_mask.any() else None
282+
'encoder_padding_mask': (encoder_padding_mask, torch.empty(0), torch.empty(0)),
266283
}
267284

268-
def reorder_encoder_out(self, encoder_out, new_order):
269-
encoder_out['encoder_out'] = tuple(
270-
eo.index_select(1, new_order)
271-
for eo in encoder_out['encoder_out']
272-
)
273-
if encoder_out['encoder_padding_mask'] is not None:
274-
encoder_out['encoder_padding_mask'] = \
275-
encoder_out['encoder_padding_mask'].index_select(1, new_order)
285+
def reorder_encoder_out(self, encoder_out: Dict[str, Tuple[Tensor, Tensor, Tensor]], new_order):
286+
encoder_out['encoder_out'] = (
287+
encoder_out['encoder_out'][0].index_select(1, new_order),
288+
encoder_out['encoder_out'][1].index_select(1, new_order),
289+
encoder_out['encoder_out'][2].index_select(1, new_order))
290+
if encoder_out['encoder_padding_mask'][0] is not None:
291+
encoder_out['encoder_padding_mask'] = (
292+
encoder_out['encoder_padding_mask'][0].index_select(1, new_order),
293+
encoder_out['encoder_padding_mask'][1],
294+
encoder_out['encoder_padding_mask'][2])
276295
return encoder_out
277296

278297
def max_positions(self):
@@ -329,6 +348,7 @@ def __init__(
329348
self.share_input_output_embed = share_input_output_embed
330349
self.need_attn = True
331350
self.max_target_positions = max_target_positions
351+
self.num_layers = num_layers
332352

333353
self.adaptive_softmax = None
334354
num_embeddings = len(dictionary)
@@ -369,36 +389,51 @@ def __init__(
369389
elif not self.share_input_output_embed:
370390
self.fc_out = Linear(out_embed_dim, num_embeddings, dropout=dropout_out)
371391

372-
def forward(self, prev_output_tokens, encoder_out=None, incremental_state=None, **kwargs):
392+
def get_cached_state(self, incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]]):
393+
394+
cached_state = self.get_incremental_state(incremental_state, 'cached_state')
395+
assert cached_state is not None
396+
prev_hiddens_ = cached_state["prev_hiddens"]
397+
assert prev_hiddens_ is not None
398+
prev_cells_ = cached_state["prev_cells"]
399+
assert prev_cells_ is not None
400+
prev_hiddens = [prev_hiddens_[i] for i in range(self.num_layers)]
401+
prev_cells = [prev_cells_[j] for j in range(self.num_layers)]
402+
input_feed = cached_state["input_feed"]
403+
assert input_feed is not None
404+
return prev_hiddens, prev_cells, input_feed
405+
406+
def forward(self, prev_output_tokens, encoder_out: Dict[str, Tuple[Tensor, Tensor, Tensor]],
407+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]]):
373408
x, attn_scores = self.extract_features(
374409
prev_output_tokens, encoder_out, incremental_state
375410
)
376411
return self.output_layer(x), attn_scores
377412

378413
def extract_features(
379-
self, prev_output_tokens, encoder_out, incremental_state=None
414+
self, prev_output_tokens, encoder_out: Dict[str, Tuple[Tensor, Tensor, Tensor]],
415+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None
380416
):
381417
"""
382418
Similar to *forward* but only return features.
383419
"""
384420
if encoder_out is not None:
385-
encoder_padding_mask = encoder_out['encoder_padding_mask']
421+
encoder_padding_mask, _, _ = encoder_out['encoder_padding_mask']
386422
encoder_out = encoder_out['encoder_out']
387423
else:
388424
encoder_padding_mask = None
389425
encoder_out = None
390426

391-
if incremental_state is not None:
427+
if incremental_state is not None and len(incremental_state) > 0:
392428
prev_output_tokens = prev_output_tokens[:, -1:]
429+
393430
bsz, seqlen = prev_output_tokens.size()
394431

395432
# get outputs from encoder
433+
srclen = 0
396434
if encoder_out is not None:
397435
encoder_outs, encoder_hiddens, encoder_cells = encoder_out[:3]
398436
srclen = encoder_outs.size(0)
399-
else:
400-
srclen = None
401-
402437
# embed tokens
403438
x = self.embed_tokens(prev_output_tokens)
404439
x = F.dropout(x, p=self.dropout_in, training=self.training)
@@ -407,27 +442,25 @@ def extract_features(
407442
x = x.transpose(0, 1)
408443

409444
# initialize previous states (or get from cache during incremental generation)
410-
cached_state = utils.get_incremental_state(self, incremental_state, 'cached_state')
411-
if cached_state is not None:
412-
prev_hiddens, prev_cells, input_feed = cached_state
445+
446+
if incremental_state is not None and len(incremental_state) > 0:
447+
prev_hiddens, prev_cells, input_feed = self.get_cached_state(incremental_state)
413448
elif encoder_out is not None:
414449
# setup recurrent cells
415-
num_layers = len(self.layers)
416-
prev_hiddens = [encoder_hiddens[i] for i in range(num_layers)]
417-
prev_cells = [encoder_cells[i] for i in range(num_layers)]
450+
prev_hiddens = [encoder_hiddens[i] for i in range(self.num_layers)]
451+
prev_cells = [encoder_cells[i] for i in range(self.num_layers)]
418452
if self.encoder_hidden_proj is not None:
419-
prev_hiddens = [self.encoder_hidden_proj(x) for x in prev_hiddens]
420-
prev_cells = [self.encoder_cell_proj(x) for x in prev_cells]
453+
prev_hiddens = [self.encoder_hidden_proj(y) for y in prev_hiddens]
454+
prev_cells = [self.encoder_cell_proj(y) for y in prev_cells]
421455
input_feed = x.new_zeros(bsz, self.hidden_size)
422456
else:
423457
# setup zero cells, since there is no encoder
424-
num_layers = len(self.layers)
425458
zero_state = x.new_zeros(bsz, self.hidden_size)
426-
prev_hiddens = [zero_state for i in range(num_layers)]
427-
prev_cells = [zero_state for i in range(num_layers)]
459+
prev_hiddens = [zero_state for i in range(self.num_layers)]
460+
prev_cells = [zero_state for i in range(self.num_layers)]
428461
input_feed = None
429462

430-
assert srclen is not None or self.attention is None, \
463+
assert srclen > 0 or self.attention is None, \
431464
"attention is not supported if there are no encoder outputs"
432465
attn_scores = x.new_zeros(srclen, seqlen, bsz) if self.attention is not None else None
433466
outs = []
@@ -451,6 +484,7 @@ def extract_features(
451484

452485
# apply attention using the last layer's hidden state
453486
if self.attention is not None:
487+
assert attn_scores is not None
454488
out, attn_scores[:, j, :] = self.attention(hidden, encoder_outs, encoder_padding_mask)
455489
else:
456490
out = hidden
@@ -463,11 +497,14 @@ def extract_features(
463497
# save final output
464498
outs.append(out)
465499

466-
# cache previous states (no-op except during incremental generation)
467-
utils.set_incremental_state(
468-
self, incremental_state, 'cached_state',
469-
(prev_hiddens, prev_cells, input_feed),
470-
)
500+
# Stack all the necessary tensors together and store
501+
prev_hiddens_tensor = torch.stack(prev_hiddens)
502+
prev_cells_tensor = torch.stack(prev_cells)
503+
cache_state = torch.jit.annotate(
504+
Dict[str, Optional[Tensor]],
505+
{"prev_hiddens": prev_hiddens_tensor, "prev_cells": prev_cells_tensor, "input_feed": input_feed})
506+
self.set_incremental_state(
507+
incremental_state, 'cached_state', cache_state)
471508

472509
# collect outputs across time steps
473510
x = torch.cat(outs, dim=0).view(seqlen, bsz, self.hidden_size)
@@ -478,9 +515,9 @@ def extract_features(
478515
if hasattr(self, 'additional_fc') and self.adaptive_softmax is None:
479516
x = self.additional_fc(x)
480517
x = F.dropout(x, p=self.dropout_out, training=self.training)
481-
482518
# srclen x tgtlen x bsz -> bsz x tgtlen x srclen
483519
if not self.training and self.need_attn and self.attention is not None:
520+
assert attn_scores is not None
484521
attn_scores = attn_scores.transpose(0, 2)
485522
else:
486523
attn_scores = None
@@ -495,22 +532,22 @@ def output_layer(self, x):
495532
x = self.fc_out(x)
496533
return x
497534

498-
def reorder_incremental_state(self, incremental_state, new_order):
499-
super().reorder_incremental_state(incremental_state, new_order)
500-
cached_state = utils.get_incremental_state(self, incremental_state, 'cached_state')
501-
if cached_state is None:
502-
return
503-
504-
def reorder_state(state):
505-
if isinstance(state, list):
506-
return [reorder_state(state_i) for state_i in state]
507-
elif state is not None:
508-
return state.index_select(0, new_order)
509-
else:
510-
return None
535+
def reorder_state(self, state: List[Tensor], new_order):
536+
return [state_i.index_select(0, new_order) for state_i in state]
511537

512-
new_state = tuple(map(reorder_state, cached_state))
513-
utils.set_incremental_state(self, incremental_state, 'cached_state', new_state)
538+
def reorder_incremental_state(self, incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]], new_order):
539+
if incremental_state is None or len(incremental_state) == 0:
540+
return
541+
prev_hiddens, prev_cells, input_feed = self.get_cached_state(incremental_state)
542+
cached_state = (prev_hiddens, prev_cells, [input_feed])
543+
new_state = [self.reorder_state(state, new_order) for state in cached_state]
544+
prev_hiddens_tensor = torch.stack(new_state[0])
545+
prev_cells_tensor = torch.stack(new_state[1])
546+
cached_state_new = torch.jit.annotate(
547+
Dict[str, Optional[Tensor]],
548+
{"prev_hiddens": prev_hiddens_tensor, "prev_cells": prev_cells_tensor, "input_feed": new_state[2][0]})
549+
self.set_incremental_state(incremental_state, 'cached_state', cached_state_new),
550+
return
514551

515552
def max_positions(self):
516553
"""Maximum output length supported by the decoder."""

‎fairseq/utils.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def buffered_arange(max):
212212

213213

214214
def convert_padding_direction(
215-
src_tokens, padding_idx, right_to_left=False, left_to_right=False
215+
src_tokens, padding_idx, right_to_left: bool = False, left_to_right: bool = False
216216
):
217217
assert right_to_left ^ left_to_right
218218
pad_mask = src_tokens.eq(padding_idx)
@@ -226,7 +226,10 @@ def convert_padding_direction(
226226
# already left padded
227227
return src_tokens
228228
max_len = src_tokens.size(1)
229-
range = buffered_arange(max_len).type_as(src_tokens).expand_as(src_tokens)
229+
buffered = torch.empty(0).long()
230+
if max_len > 0:
231+
torch.arange(max_len, out=buffered)
232+
range = buffered.type_as(src_tokens).expand_as(src_tokens)
230233
num_pads = pad_mask.long().sum(dim=1, keepdim=True)
231234
if right_to_left:
232235
index = torch.remainder(range - num_pads, max_len)

0 commit comments

Comments
 (0)