[torchlib] aten_lstm / aten_gru: runtime shape computation between layers makes multi-layer RNNs ~10% slower
I maintainer di solito rispondono entro 3 giorni
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 68/100
- Tipo di issue
- Refactoring
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Ambito
- machine-learning, performance
Direzione di ricerca
Inizia in onnxscript/function_libs/torch_lib/ops/core.py da aten_lstm e aten_gru, dove l'output di ciascun layer viene rimodellato come input del layer successivo. Aggiungi un helper che esegue lo squeeze dell'asse di direzione quando bidirectional è False e usa un reshape costante [0, 0, 2 * hidden_size] altrimenti. Esegui i test torch_lib per LSTM e GRU, inclusi i casi bidirezionali, per confermare che gli output non cambino e che i nodi Shape/Slice a runtime siano scomparsi.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
aten_lstm and aten_gru (onnxscript/function_libs/torch_lib/ops/core.py, introduced in #2674) convert each layer's ONNX output
Y: [seq_length, num_directions, batch_size, hidden_size] into the next layer input
[seq_length, batch_size, num_directions * hidden_size] with:
Transpose(perm=[0, 2, 1, 3]) -> Shape -> 4x Slice -> Mul -> Reshape(-1) -> Concat -> Reshape
That is 10 nodes per layer, including a target shape computed at runtime. Two things about the shape are known when the graph is traced:
bidirectionalis a Python bool. When it isFalse,num_directions == 1, so the whole sequence is justSqueeze(Y, axes=[1]), which is what the TorchScript exporter emits.hidden_sizeis a static int (it is already passed as the LSTM/GRUhidden_sizeattribute). WhenbidirectionalisTrue, the target shape can be the constant[0, 0, 2 * hidden_size], where0copies the input dimension.
With a dynamic batch axis, ONNX Runtime cannot constant-fold the shape computation, so every layer runs these extra tiny kernels.
In our case, it made the dynamo export slower than the TorchScript export of the same model (can be reproduced with dynamo=False).
Repro
Versions: torch 2.13.0+cpu, onnxscript main @ 9ff5a7c (same code in 0.7.1 / 0.7.2), onnxruntime 1.31.0, CPU, 1 thread.
import collections, sys, time
import numpy as np, onnx, onnxruntime as ort, torch
class StepLSTM(torch.nn.Module):
def __init__(self, input_size=256, hidden_size=256, num_layers=4):
super().__init__()
self.lstm = torch.nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
def forward(self, x, h0, c0):
return self.lstm(x, (h0, c0))
torch.manual_seed(0)
model = StepLSTM().eval()
batch = 5
inputs = (torch.randn(batch, 1, 256), torch.randn(4, batch, 256), torch.randn(4, batch, 256))
batch_dim = torch.export.Dim("batch")
path = sys.argv[1]
torch.onnx.export(
model, inputs, path, dynamo=True,
input_names=["x", "h0", "c0"],
dynamic_shapes=({0: batch_dim}, {1: batch_dim}, {1: batch_dim}),
)
counts = collections.Counter(node.op_type for node in onnx.load(path).graph.node)
print(f"nodes={sum(counts.values())} {dict(sorted(counts.items()))}")
options = ort.SessionOptions()
options.intra_op_num_threads = 1
options.inter_op_num_threads = 1
session = ort.InferenceSession(path, options, providers=["CPUExecutionProvider"])
feeds = {name: tensor.numpy() for name, tensor in zip(["x", "h0", "c0"], inputs)}
for _ in range(500):
session.run(None, feeds)
timings = []
for _ in range(5):
start = time.perf_counter()
for _ in range(2000):
session.run(None, feeds)
timings.append((time.perf_counter() - start) / 2000 * 1e6)
print(f"ort_latency={min(timings):.1f} us/step (best of 5)")
Results
| nodes | Reshape | Shape | Slice | ORT latency (us/step) | |
|---|---|---|---|---|---|
| main | 104 | 8 | 4 | 56 | 991 - 1014 |
| with the proposed change | 68 | 0 | 0 | 40 | 908 - 911 |
Outputs are identical: max abs diff vs PyTorch is 8.3e-07 in both cases. That is about 9-10% faster for this model
Proposed fix
Factor the conversion into a helper used by both aten_lstm and aten_gru:
def _rnn_output_to_layer_output(Y: TFloat, bidirectional: bool, hidden_size: int) -> TFloat:
"""Y: [seq_length, num_directions, batch_size, hidden_size]
Returns: [seq_length, batch_size, num_directions * hidden_size]
"""
if not bidirectional:
# num_directions == 1: dropping the axis is enough
return op.Squeeze(Y, [1])
# [seq_length, batch_size, num_directions, hidden_size]
Y = op.Transpose(Y, perm=[0, 2, 1, 3])
# 0 copies the input dimension (seq_length, batch_size): no runtime shape computation needed
return op.Reshape(Y, op.Constant(value_ints=[0, 0, 2 * hidden_size]))
- Lingua principale
- Python
- Stelle
- 456
- Fork
- 148
- Merge medio
- 3g 13h
- PR unite (30g)
- 21
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di microsoft/onnxscript
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
microsoft/onnxscript#3003 · 3 commenti ·
I maintainer di solito rispondono entro 3 giorni
-
CI : Pin github actions to full commit SHAForse già presa @Aniketsy l’ha presa 178 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
microsoft/onnxscript#2903 · 6 commenti ·
I maintainer di solito rispondono entro 3 giorni
-
optimize() crashes when constant folding produces a string tensorForse già presa @nniiovoo l’ha presa 3 giorni fa. Aperta
Difficoltà 3/5 1-2 giorni Idoneità per principianti 35/100
microsoft/onnxscript#3129 ·
I maintainer di solito rispondono entro 3 giorni
-
bug module: torchlib
Difficoltà 3/5 1-2 giorni Idoneità per principianti 64/100
microsoft/onnxscript#3124 · 3 commenti · 1 reazione ·
I maintainer di solito rispondono entro 3 giorni
-
[torchlib] Float remainder is off by the divisor when the quotient rounds up, and nan for an infinite divisorForse già presa @usmanmateen l’ha presa 8 giorni fa. Aperta
Difficoltà 3/5 1-2 giorni Idoneità per principianti 68/100
microsoft/onnxscript#3116 ·
I maintainer di solito rispondono entro 3 giorni
Tutte le issue di microsoft/onnxscript
Issue simili
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 68/100
I maintainer di solito rispondono entro 4 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
ClockGU/clockwork-backend#169 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
aws-samples/sample-per-model-bedrock#616 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
chnm/teachinghistory-hugo#47 ·
-
good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
pandas-dev/pandas-stubs#2047 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno