Repository navigation
Expand file tree
/
Copy pathexport.py
More file actions
174 lines (150 loc) · 5.63 KB
/
Copy pathexport.py
File metadata and controls
174 lines (150 loc) · 5.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import os
import torch
from data.dataset import Dataset
from model import SpeechToUnitTransformer
from params import ModelParams
from utils import Dictionary, getModelStateDictFromPath, postProcessPrediction
class EncoderWrapper(torch.nn.Module):
def __init__(self, model):
super().__init__()
self.model = model
def forward(self, srcTokens, srcLengths):
encOut, encPaddingMask, _ = self.model.forwardEncoder(
srcTokens=srcTokens,
srcLengths=srcLengths
)
return encOut, encPaddingMask
class DecoderWrapper(torch.nn.Module):
def __init__(self, model):
super().__init__()
self.model = model
def forward(self, prevOutputTokens, encoderOut, encoderPaddingMask):
# For simplicity, incrementalState is empty here
# Use of incrementalState can help with KV-Cache to speed-up inference
# But in some cases can cause incorrect outputs
# For accuracy, it is recommended to export without incrementalState
# and maintain the KV-Cache manually through code
curOut, attn, _ = self.model.forwardDecoder(
prevOutputTokens=prevOutputTokens,
encoderOut=encoderOut,
encoderPaddingMask=encoderPaddingMask,
incrementalState={}
)
return curOut, attn
def export_to_onnx(model, export_dir="onnx_exports"):
os.makedirs(export_dir, exist_ok=True)
# Dummy inputs: T=1000 frames, feature dim=80
T = 1000
dummy_srcTokens = torch.randn(1, T, 80).float() # (batch=1, T, 80)
dummy_srcLengths = torch.tensor([T]).long() # (batch=1,) with value T
dummy_prevOutputTokens = torch.randint(0, 1000, (1, 1)).long()
# Encoder export
encoder = EncoderWrapper(model)
torch.onnx.export(
encoder,
(dummy_srcTokens, dummy_srcLengths),
os.path.join(export_dir, "encoder.onnx"),
input_names=["srcTokens", "srcLengths"],
output_names=["encoderOut", "encoderPaddingMask"],
dynamic_axes={
"srcTokens": {1: "srcLen"},
"encoderOut": {1: "srcLen"},
"encoderPaddingMask": {1: "srcLen"},
},
opset_version=14,
do_constant_folding=False,
)
print("Exported encoder to ONNX")
# Run encoder once to get dummy outputs
encOut, encPaddingMask = encoder(dummy_srcTokens, dummy_srcLengths)
# Decoder export
decoder = DecoderWrapper(model)
torch.onnx.export(
decoder,
(dummy_prevOutputTokens, encOut, encPaddingMask),
os.path.join(export_dir, "decoder.onnx"),
input_names=["prevOutputTokens", "encoderOut", "encoderPaddingMask"],
output_names=["decoderOut", "attn"],
dynamic_axes={
"prevOutputTokens": {1: "tgtLen"},
"encoderOut": {1: "srcLen"},
"encoderPaddingMask": {1: "srcLen"},
"decoderOut": {1: "tgtLen"}
},
opset_version=14
)
print("Exported decoder to ONNX")
def main():
ds = Dataset(
root="path/to/whisper/files",
nFramesPerStep=1,
)
batchSize = 1
dataloader = torch.utils.data.DataLoader(
dataset=ds,
batch_size=batchSize,
shuffle=False,
collate_fn=ds.collater,
)
# Build and load model
model = SpeechToUnitTransformer.buildModel(
params=ModelParams(),
tgtDict={}
)
model.load_state_dict(
getModelStateDictFromPath(
'path/to/checkpoint.pt',
model.state_dict(),
)
)
model = model.eval()
print("Loaded model state dict")
# Build target dictionary
tgtDict = Dictionary()
for i in range(1000):
tgtDict.addSymbol(str(i))
# Export to ONNX
export_to_onnx(model)
# Run one batch through encoder+decoder (greedy) to test the export
for batch in dataloader:
print(f"SrcLengths: {batch['netInput']['srcLengths']}")
srcTokens: torch.Tensor = batch['netInput']['srcTokens'][0].unsqueeze(0) # (1, T, 80)
srcLengths: torch.Tensor = batch['netInput']['srcLengths'][0].unsqueeze(0) # (1,)
filename: str = batch['filenames'][0]
encEmbed = model.forwardEncoder(
srcTokens=srcTokens,
srcLengths=srcLengths
)
maxLen = 10 # generate up to 10 tokens
prevOutputTokens = srcLengths.new_zeros((batchSize, 1)).long().fill_(tgtDict.eos())
incrementalState = {}
predOut = []
for _ in range(maxLen):
curOut, currAttn, _ = model.forwardDecoder(
prevOutputTokens=prevOutputTokens,
encoderOut=encEmbed[0],
encoderPaddingMask=encEmbed[1],
incrementalState=incrementalState
)
lprobs = model.getNormalizedProbs(curOut, logProbs=True)
print(lprobs.shape)
curPredLProb, curPredOut = torch.max(lprobs, dim=1)
predOut.append(curPredOut)
prevOutputTokens = torch.cat((prevOutputTokens, curPredOut.view(batchSize, 1)), dim=1)
if torch.any(curPredOut.squeeze(0) == tgtDict.eos()):
break
predOut = torch.cat(predOut, dim=0).view(batchSize, -1)
print(filename)
print("Out: ", predOut)
hypoTokens, hypoStr = postProcessPrediction(
hypoTokens=predOut,
tgtDict=tgtDict,
removeBpe=None,
extraSymbolsToIgnore=set()
)
print("Decoded string:", hypoStr)
# Compare the decoded string with a similar inference loop through the pt checkpoint
# to ensure the export is correct
break # just run one batch
if __name__ == "__main__":
main()