onnxruntime/onnxruntime/test/python/transformers/test_whisper.py
kunal-vaishnavi 2a17d5cf32
LLaMA Model Optimization (#18021)
### Description
This PR contains fusion-level and kernel-level optimizations for [Meta's
LLaMA-2](https://blogs.microsoft.com/blog/2023/07/18/microsoft-and-meta-expand-their-ai-partnership-with-llama-2-on-azure-and-windows/).

Some of the added optimizations include:

- SimplifiedLayerNorm changes
  - Fusions for multiple variants
- SkipSimplifiedLayerNorm changes
  - Kernel support for CPU
- Rotary embeddings (previously did not exist)
  - Fusions for multiple variants
  - CPU and CUDA kernels
  - Supports interleaving and non-interleaving in the same kernels
  - Optimized cache that requires half of its originally exported sizes
- Reduced from `(max_sequence_length, head_size)` to
`(max_sequence_length, head_size / 2)`
- Multi-head attention
  - Support for 2D and 3D attention masks
- Group query attention (for FP16 CUDA and INT4 CUDA)
  - Integration with flash attention v2 and past-present buffer sharing
- Removes need for `attention_mask` input as it is supported in the
kernel
- 4 bit quantization
  - `block_size` parameter is available for customizing
- Support the new changes for [Microsoft
version](https://github.com/microsoft/Llama-2-Onnx)
- Support combinations of the below variants (ex: export ORT version and
run with Optimum)

Supported variants of LLaMA-2 include:
- [ORT
version](https://github.com/microsoft/onnxruntime/tree/main/onnxruntime/python/tools/transformers/models/llama)
- Produces one ONNX file that is already optimized (and quantized if
requested)
  - Integrates with Optimum
- [Another Microsoft version](https://github.com/microsoft/Llama-2-Onnx)
  - Already exported and available off-the-shelf
  - Faster versions of those models will be uploaded there soon
- [Hugging Face version](https://huggingface.co/meta-llama)
  - Models that end with `-hf`
- Some older and current versions of
[`transformers`](https://github.com/huggingface/transformers) and
[`optimum`](https://github.com/huggingface/optimum) that export the
model to ONNX differently
- Note that while some older versions are supported, it is recommended
to use the latest package versions.

### Usage

To use the optimizations, please see `README.md` for details. Please
note the various `requirements.txt` files for the package versions
recommended in order to use these changes.

To run the ORT transformer optimizer separately, run the script as
follows:
```
$ cd onnxruntime/onnxruntime/python/tools/transformers/
$ python3 optimizer.py --input <filename>.onnx --output <filename>.onnx --model_type gpt2 --num_heads <number of attention heads> --hidden_size <attention hidden size> --use_external_data_format --opt_level 0
```

### Motivation and Context
This PR helps the following issues:
- https://github.com/microsoft/onnxruntime/issues/14997
- https://github.com/microsoft/onnxruntime/issues/16254
- https://github.com/microsoft/onnxruntime/issues/17681
- https://github.com/microsoft/onnxruntime/issues/17925
- https://github.com/microsoft/onnxruntime-inference-examples/issues/320

This PR uses changes from the following PRs:
- https://github.com/pytorch/pytorch/pull/104468
- https://github.com/pytorch/pytorch/pull/109759
- https://github.com/microsoft/onnxruntime/pull/17020
- https://github.com/microsoft/onnxruntime/pull/17674
- https://github.com/microsoft/onnxruntime/pull/17890
- https://github.com/microsoft/onnxruntime/pull/17920
- https://github.com/huggingface/transformers/pull/26162
- https://github.com/huggingface/optimum/pull/1257
- https://github.com/huggingface/optimum/pull/1289
- https://github.com/huggingface/optimum/pull/1462

### New TorchDynamo Exporter (experimental stage)

This PR uses changes from the following issues and PRs to begin
supporting the [new TorchDynamo
exporter](https://pytorch.org/docs/stable/onnx.html#torchdynamo-based-onnx-exporter):
- https://github.com/huggingface/transformers/pull/26307
- https://github.com/pytorch/pytorch/issues/104903
- https://github.com/pytorch/pytorch/pull/105040
- https://github.com/microsoft/onnxscript/pull/847
- https://github.com/microsoft/onnxscript/pull/862
- https://github.com/microsoft/onnxscript/issues/493
2023-10-23 13:00:56 -07:00

194 lines
8.5 KiB
Python

# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
import os
import unittest
import onnx
from parity_utilities import find_transformers_source
from whisper_model_generator import (
create_whisper_decoder_attention,
create_whisper_decoder_multihead_attention,
create_whisper_decoder_with_past_multihead_cross_attention,
create_whisper_decoder_with_past_multihead_self_attention,
create_whisper_encoder_attention,
)
if find_transformers_source():
from fusion_options import FusionOptions
from onnx_model import OnnxModel
from optimizer import optimize_model
else:
from onnxruntime.transformers.fusion_options import FusionOptions
from onnxruntime.transformers.onnx_model import OnnxModel
from onnxruntime.transformers.optimizer import optimize_model
class TestFusion(unittest.TestCase):
def verify_fusion(self, optimized_model, expected_model_filename):
optimized_model.topological_sort(is_deterministic=True)
expected_model_path = os.path.join(
os.path.dirname(__file__), "test_data", "models", "whisper", expected_model_filename
)
expected_model = OnnxModel(onnx.load(expected_model_path))
expected_model.topological_sort(is_deterministic=True)
nodes = optimized_model.model.graph.node
self.assertEqual(len(nodes), len(expected_model.model.graph.node))
for i in range(len(nodes)):
self.assertEqual(nodes[i], expected_model.model.graph.node[i])
for expected_initializer in expected_model.model.graph.initializer:
self.assertTrue(
OnnxModel.has_same_value(
optimized_model.get_initializer(expected_initializer.name), expected_initializer
)
)
# Attention type #1 in fusion_bart_attention.py
def test_encoder_attention_fusion_with_skiplayernorm(self):
num_heads = 4
hidden_size = 64
model = create_whisper_encoder_attention(
num_heads=num_heads, hidden_size=hidden_size, add_before_layernorm=False
)
dir = "."
model_path = os.path.join(dir, "whisper_encoder_attention_sln.onnx")
onnx.save(model, model_path)
options = FusionOptions("bart")
optimized_model = optimize_model(
model_path, model_type="bart", num_heads=num_heads, hidden_size=hidden_size, optimization_options=options
)
os.remove(model_path)
self.verify_fusion(optimized_model, "encoder_attention_with_sln_fused.onnx")
# Attention type #2 in fusion_bart_attention.py
def test_decoder_attention_fusion_with_skiplayernorm(self):
num_heads = 4
hidden_size = 64
model = create_whisper_decoder_attention(
num_heads=num_heads, hidden_size=hidden_size, add_before_layernorm=False
)
dir = "."
model_path = os.path.join(dir, "whisper_decoder_attention_sln.onnx")
onnx.save(model, model_path)
options = FusionOptions("bart")
optimized_model = optimize_model(
model_path, model_type="bart", num_heads=num_heads, hidden_size=hidden_size, optimization_options=options
)
os.remove(model_path)
self.verify_fusion(optimized_model, "decoder_attention_with_sln_fused.onnx")
# Attention type #4 in fusion_bart_attention.py
def test_decoder_multihead_attention_fusion(self):
num_heads = 4
hidden_size = 64
model = create_whisper_decoder_multihead_attention(num_heads=num_heads, hidden_size=hidden_size)
dir = "."
model_path = os.path.join(dir, "whisper_decoder_mha.onnx")
onnx.save(model, model_path)
options = FusionOptions("bart")
options.use_multi_head_attention = True
optimized_model = optimize_model(
model_path, model_type="bart", num_heads=num_heads, hidden_size=hidden_size, optimization_options=options
)
os.remove(model_path)
self.verify_fusion(optimized_model, "decoder_mha_fused.onnx")
# Attention type #3 in fusion_bart_attention.py
def test_decoder_with_past_multihead_self_attention_fusion_with_skiplayernorm(self):
num_heads = 4
hidden_size = 64
model = create_whisper_decoder_with_past_multihead_self_attention(
num_heads=num_heads, hidden_size=hidden_size, add_before_layernorm=False
)
dir = "."
model_path = os.path.join(dir, "whisper_decoder_with_past_self_mha.onnx")
onnx.save(model, model_path)
options = FusionOptions("bart")
options.use_multi_head_attention = True
optimized_model = optimize_model(
model_path, model_type="bart", num_heads=num_heads, hidden_size=hidden_size, optimization_options=options
)
os.remove(model_path)
self.verify_fusion(optimized_model, "decoder_with_past_self_mha_fused.onnx")
# Attention type #5 in fusion_bart_attention.py
def test_decoder_with_past_multihead_cross_attention_fusion(self):
num_heads = 4
hidden_size = 64
model = create_whisper_decoder_with_past_multihead_cross_attention(num_heads=num_heads, hidden_size=hidden_size)
dir = "."
model_path = os.path.join(dir, "whisper_decoder_with_past_cross_mha.onnx")
onnx.save(model, model_path)
options = FusionOptions("bart")
options.use_multi_head_attention = True
optimized_model = optimize_model(
model_path, model_type="bart", num_heads=num_heads, hidden_size=hidden_size, optimization_options=options
)
os.remove(model_path)
self.verify_fusion(optimized_model, "decoder_with_past_cross_mha_fused.onnx")
# Attention type #4 in fusion_bart_attention.py
def test_decoder_multihead_attention_split_bias_fusion(self):
num_heads = 4
hidden_size = 64
model = create_whisper_decoder_multihead_attention(num_heads=num_heads, hidden_size=hidden_size)
dir = "."
model_path = os.path.join(dir, "whisper_decoder_mha.onnx")
onnx.save(model, model_path)
options = FusionOptions("bart")
options.use_multi_head_attention = True
options.disable_multi_head_attention_bias = True
optimized_model = optimize_model(
model_path, model_type="bart", num_heads=num_heads, hidden_size=hidden_size, optimization_options=options
)
os.remove(model_path)
self.verify_fusion(optimized_model, "decoder_mha_split_bias_fused.onnx")
# Attention type #3 in fusion_bart_attention.py
def test_decoder_with_past_multihead_self_attention_split_bias_fusion_with_skiplayernorm(self):
num_heads = 4
hidden_size = 64
model = create_whisper_decoder_with_past_multihead_self_attention(
num_heads=num_heads, hidden_size=hidden_size, add_before_layernorm=False
)
dir = "."
model_path = os.path.join(dir, "whisper_decoder_with_past_self_mha.onnx")
onnx.save(model, model_path)
options = FusionOptions("bart")
options.use_multi_head_attention = True
options.disable_multi_head_attention_bias = True
optimized_model = optimize_model(
model_path, model_type="bart", num_heads=num_heads, hidden_size=hidden_size, optimization_options=options
)
os.remove(model_path)
self.verify_fusion(optimized_model, "decoder_with_past_self_mha_split_bias_fused.onnx")
# Attention type #5 in fusion_bart_attention.py
def test_decoder_with_past_multihead_cross_attention_split_bias_fusion(self):
num_heads = 4
hidden_size = 64
model = create_whisper_decoder_with_past_multihead_cross_attention(num_heads=num_heads, hidden_size=hidden_size)
dir = "."
model_path = os.path.join(dir, "whisper_decoder_with_past_cross_mha.onnx")
onnx.save(model, model_path)
options = FusionOptions("bart")
options.use_multi_head_attention = True
options.disable_multi_head_attention_bias = True
optimized_model = optimize_model(
model_path, model_type="bart", num_heads=num_heads, hidden_size=hidden_size, optimization_options=options
)
os.remove(model_path)
self.verify_fusion(optimized_model, "decoder_with_past_cross_mha_split_bias_fused.onnx")
if __name__ == "__main__":
unittest.main()