Skip to content

Commit 78f1250

Browse files
committed
[Relax][PyTorch] Add rnn_tanh.input converter
1 parent 29408e0 commit 78f1250

1 file changed

Lines changed: 184 additions & 0 deletions

File tree

python/tvm/relax/frontend/torch/exported_program_translator.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,189 @@ def _gru(self, node: fx.Node) -> relax.Var:
918918

919919
return output
920920

921+
def _rnn_tanh_cell_unroll(
922+
self,
923+
input_reshaped,
924+
weight_ih,
925+
weight_hh,
926+
bias_ih,
927+
bias_hh,
928+
h_prev,
929+
seq_len,
930+
reverse=False,
931+
):
932+
"""Unroll vanilla tanh-RNN cells for a single direction."""
933+
# Transpose weights for matmul: (hidden_size, in) -> (in, hidden_size)
934+
weight_ih_t = self.block_builder.emit(relax.op.premute_dims(weight_ih, axes=[1, 0]))
935+
weight_hh_t = self.block_builder.emit(relax.op.premute_dims(weight_hh, axes=[1, 0]))
936+
937+
outputs = []
938+
time_steps = range(seq_len - 1, -1 -1) if reverse else range(seq_len)
939+
940+
for t in time_steps:
941+
# Input at time t: (batch_size, input_size)
942+
x_t = self.block_builder.emit(
943+
relax.op.take(input_reshaped, relax.const(t, "int64"), axis=0, mode="clip")
944+
)
945+
946+
# h_t = tanh(W_ih @ x_t + b_ih + W_hh @ h_{t-1} + b_hh)
947+
ih = self.block_builder.emit(relax.op.linear_algebra.matmul(x_t, weight_ih_t))
948+
hh = self.block_builder.emit(relax.op.linear_algebra.matmul(h_prev, weight_hh_t))
949+
if bias_ih is not None and bias_hh is not None:
950+
ih = self.block_builder.emit(relax.op.add(ih, bias_ih))
951+
hh = self.block_builder.emit(relax.op.add(hh, bias_hh))
952+
h_t = self.block_builder.emit(relax.op.tanh(relax.op.add(ih, hh)))
953+
954+
outputs.append(h_t)
955+
h_prev = h_t
956+
957+
if reverse:
958+
outputs = outputs[::-1]
959+
960+
output = self.block_builder.emit(relax.op.stack(outputs, axis=0))
961+
return output
962+
963+
def _rnn_tanh(self, node: fx.Node) -> relax.Var:
964+
args = self.retrieve_args(node)
965+
input_tensor = args[0]
966+
hx = args[1] if len(args) > 1 else None
967+
params = args[2] if len(args) > 2 else None
968+
has_biases = args[3] if len(args) > 3 else True
969+
num_layers = args[4] if len(args) > 4 else 1
970+
_dropout = args[5] if len(args) > 5 else 0.0 # Not used in inference
971+
_train = args[6] if len(args) > 6 else False # Not used in inference
972+
bidirectional = args[7] if len(args) > 7 else False
973+
batch_first = args[8] if len(args) > 8 else False
974+
975+
if num_layers > 1:
976+
raise NotImplementedError("Multi-layer RNN is not yet supported")
977+
978+
input_shape = self.shape_of(input_tensor)
979+
if batch_first:
980+
batch_size, seq_len, input_size = input_shape
981+
else:
982+
seq_len, batch_size, input_size = input_shape
983+
984+
seq_len = int(seq_len) if isinstance(seq_len, tvm.tirx.IntImm) else seq_len
985+
batch_size = int(batch_size) if isinstance(batch_size, tvm.tirx.IntImm) else batch_size
986+
input_size = int(input_size) if isinstance(input_size, tvm.tirx.IntImm) else input_size
987+
988+
# params per direction: weight_ih, weight_hh, [bias_ih, bias_hh]
989+
params_per_direction = 4 if has_biases else 2
990+
991+
# A vanilla RNN has a single gate, so weight_ih has shape (hidden_size, input_size)
992+
if params and len(params) >= 2:
993+
hidden_size = self.shape_of(params[0])[0]
994+
else:
995+
hidden_size = 16
996+
hidden_size = int(hidden_size) if isinstance(hidden_size, tvm.tirx.IntImm) else hidden_size
997+
998+
dtype = input_tensor.struct_info.dtype
999+
1000+
# Forward direction weights
1001+
if params and len(params) >= params_per_direction:
1002+
weight_ih_fwd = params[0]
1003+
weight_hh_fwd = params[1]
1004+
bias_ih_fwd = params[2] if has_biases else None
1005+
bias_hh_fwd = params[3] if has_biases else None
1006+
else:
1007+
weight_ih_fwd = self.block_builder.emit(
1008+
relax.op.zeros(relax.ShapeExpr((hidden_size, input_size)), dtype)
1009+
)
1010+
weight_hh_fwd = self.block_builder.emit(
1011+
relax.op.zeros(relax.ShapeExpr((hidden_size, hidden_size)), dtype)
1012+
)
1013+
bias_ih_fwd = None
1014+
bias_hh_fwd = None
1015+
1016+
# Backward direction weights if bidirectional
1017+
if bidirectional:
1018+
if params and len(params) >= params_per_direction * 2:
1019+
weight_ih_bwd = params[params_per_direction]
1020+
weight_hh_bwd = params[params_per_direction + 1]
1021+
bias_ih_bwd = params[params_per_direction + 2] if has_biases else None
1022+
bias_hh_bwd = params[params_per_direction + 3] if has_biases else None
1023+
else:
1024+
weight_ih_bwd = self.block_builder.emit(
1025+
relax.op.zeros(relax.ShapeExpr((hidden_size, input_size)), dtype)
1026+
)
1027+
weight_hh_bwd = self.block_builder.emit(
1028+
relax.op.zeros(relax.ShapeExpr((hidden_size, hidden_size)), dtype)
1029+
)
1030+
bias_ih_bwd = None
1031+
bias_hh_bwd = None
1032+
else:
1033+
weight_ih_bwd = None
1034+
weight_hh_bwd = None
1035+
bias_ih_bwd = None
1036+
bias_hh_bwd = None
1037+
1038+
# Initial hidden states
1039+
if hx is not None:
1040+
h_prev_fwd = self.block_builder.emit(
1041+
relax.op.zeros(hx, relax.const(0, "int64"), axis=0, mode="clip")
1042+
)
1043+
h_prev_bwd = (
1044+
self.block_builder.emit(
1045+
relax.op.take(hx, relax.const(1, "int64"), axis=0, mode="clip")
1046+
)
1047+
if bidirectional
1048+
else None
1049+
)
1050+
else:
1051+
h_prev_fwd = self.block_builder.emit(
1052+
relax.op.zeros(relax.ShapeExpr((batch_size, hidden_size)), dtype)
1053+
)
1054+
h_prev_bwd = (
1055+
self.block_builder.emit(
1056+
relax.op.zeros(relax.ShapeExpr((batch_size, hidden_size)), dtype)
1057+
)
1058+
if bidirectional
1059+
else None
1060+
)
1061+
1062+
# Reshape input to (seq_len, batch_size, input_size)
1063+
input_reshaped = (
1064+
self.block_builder.emit(relax.op.permute_dims(input_tensor, axes=[1, 0, 2]))
1065+
if batch_first
1066+
else input_tensor
1067+
)
1068+
1069+
# Process forward direction
1070+
output_fwd = self._rnn_tanh_cell_unroll(
1071+
input_reshaped,
1072+
weight_ih_fwd,
1073+
weight_hh_fwd,
1074+
bias_ih_fwd,
1075+
bias_hh_fwd,
1076+
h_prev_fwd,
1077+
seq_len,
1078+
reverse=False,
1079+
)
1080+
1081+
# Process backward direction if bidirectional
1082+
if bidirectional:
1083+
output_bwd = self._rnn_tanh_cell_unroll(
1084+
input_reshaped,
1085+
weight_ih_bwd,
1086+
weight_hh_bwd,
1087+
bias_ih_bwd,
1088+
bias_hh_bwd,
1089+
h_prev_bwd,
1090+
seq_len,
1091+
reverse=True,
1092+
)
1093+
# Concatenate forward and backward outputs along feature dimension
1094+
output = self.block_builder.emit(relax.op.concat([output_fwd, output_bwd], axis=2))
1095+
else:
1096+
output = output_fwd
1097+
1098+
# Reshape back to batch_first if needed
1099+
if batch_first:
1100+
output = self.block_builder.emit(relax.op.permute_dims(output, axes=[1, 0, 2]))
1101+
1102+
return output
1103+
9211104
########## Manipulation ##########
9221105

9231106
def _narrow(self, node: fx.Node) -> relax.Var:
@@ -1704,6 +1887,7 @@ def create_convert_map(
17041887
"linear.default": self._linear,
17051888
"lstm.input": self._lstm,
17061889
"gru.input": self._gru,
1890+
"rnn_tanh.input": self._rnn_tanh,
17071891
"max_pool1d.default": self._max_pool1d,
17081892
"max_pool2d.default": self._max_pool2d,
17091893
"max_pool2d_with_indices.default": self._max_pool2d_with_indices,

0 commit comments

Comments
 (0)