Support (Bias)SkipLayerNormalization fusion in GPT2 (#13988)

This commit is contained in:
Hariharan Seshadri 2022-12-21 23:04:44 -08:00 committed by GitHub
parent baba312e30
commit 7ed8bd4f95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 545 additions and 168 deletions

View file

@ -3840,7 +3840,7 @@ This version of the operator has been available since version 1 of the 'com.micr
<dd>1D bias tensor with shape (hidden_size</dd>
</dl>
#### Outputs (1 - 3)
#### Outputs (1 - 4)
<dl>
<dt><tt>output</tt> : T</dt>
@ -3849,6 +3849,8 @@ This version of the operator has been available since version 1 of the 'com.micr
<dd>Saved mean used during training to speed up gradient computation</dd>
<dt><tt>inv_std_var</tt> (optional) : U</dt>
<dd>Saved inverse standard variance used during training to speed up gradient computation.</dd>
<dt><tt>input_skip_sum</tt> (optional) : T</dt>
<dd>Sum of the input and skip inputs with shape (batch_size, sequence_length, hidden_size).</dd>
</dl>
#### Type Constraints

View file

@ -438,7 +438,7 @@ Do not modify directly.*
|QuickGelu|*in* X:**T**<br> *out* Y:**T**|1+|**T** = tensor(float)|
|Range|*in* start:**T**<br> *in* limit:**T**<br> *in* delta:**T**<br> *out* Y:**T**|1+|**T** = tensor(double), tensor(float), tensor(int16), tensor(int32), tensor(int64)|
|SampleOp|*in* X:**T**<br> *out* Y:**T**|1+|**T** = tensor(float)|
|SkipLayerNormalization|*in* input:**T**<br> *in* skip:**T**<br> *in* gamma:**T**<br> *in* beta:**T**<br> *in* bias:**T**<br> *out* output:**T**<br> *out* mean:**U**<br> *out* inv_std_var:**U**|1+|**T** = tensor(double), tensor(float)|
|SkipLayerNormalization|*in* input:**T**<br> *in* skip:**T**<br> *in* gamma:**T**<br> *in* beta:**T**<br> *in* bias:**T**<br> *out* output:**T**<br> *out* mean:**U**<br> *out* inv_std_var:**U**<br> *out* input_skip_sum:**T**|1+|**T** = tensor(double), tensor(float)|
|SparseToDenseMatMul|*in* A:**T**<br> *in* B:**T1**<br> *out* Y:**T1**|1+|**T** = sparse_tensor(double), sparse_tensor(float), sparse_tensor(int32), sparse_tensor(int64), sparse_tensor(uint32), sparse_tensor(uint64)<br/> **T1** = tensor(double), tensor(float), tensor(int32), tensor(int64), tensor(uint32), tensor(uint64)|
|Tokenizer|*in* X:**T**<br> *out* Y:**T**|1+|**T** = tensor(string)|
|TransposeMatMul|*in* A:**T**<br> *in* B:**T**<br> *out* Y:**T**|1+|**T** = tensor(float)|
@ -797,7 +797,7 @@ Do not modify directly.*
|RemovePadding|*in* input:**T**<br> *in* sequence_token_count:**M**<br> *out* output:**T**<br> *out* token_offset:**M**<br> *out* cumulated_seq_len:**M**<br> *out* max_seq_len:**M**|1+|**T** = tensor(float), tensor(float16)|
|RestorePadding|*in* input:**T**<br> *in* token_offset:**M**<br> *out* output:**T**|1+|**T** = tensor(float), tensor(float16)|
|Rfft|*in* X:**T**<br> *out* Y:**T**|1+|**T** = tensor(double), tensor(float), tensor(float16)|
|SkipLayerNormalization|*in* input:**T**<br> *in* skip:**T**<br> *in* gamma:**T**<br> *in* beta:**T**<br> *in* bias:**T**<br> *out* output:**T**<br> *out* mean:**U**<br> *out* inv_std_var:**U**|1+|**T** = tensor(float), tensor(float16)|
|SkipLayerNormalization|*in* input:**T**<br> *in* skip:**T**<br> *in* gamma:**T**<br> *in* beta:**T**<br> *in* bias:**T**<br> *out* output:**T**<br> *out* mean:**U**<br> *out* inv_std_var:**U**<br> *out* input_skip_sum:**T**|1+|**T** = tensor(float), tensor(float16)|
|TransposeMatMul|*in* A:**T**<br> *in* B:**T**<br> *out* Y:**T**|1+|**T** = tensor(bfloat16), tensor(double), tensor(float), tensor(float16)|
|Trilu|*in* X:**T**<br> *in* k:**tensor(int64)**<br> *out* Y:**T**|1+|**T** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)|
| |
@ -1135,7 +1135,7 @@ Do not modify directly.*
|QLinearAdd|*in* A:**T**<br> *in* A_scale:**tensor(float)**<br> *in* A_zero_point:**T**<br> *in* B:**T**<br> *in* B_scale:**tensor(float)**<br> *in* B_zero_point:**T**<br> *in* C_scale:**tensor(float)**<br> *in* C_zero_point:**T**<br> *out* C:**T**|1+|**T** = tensor(int8), tensor(uint8)|
|QLinearSigmoid|*in* X:**T**<br> *in* X_scale:**tensor(float)**<br> *in* X_zero_point:**T**<br> *in* Y_scale:**tensor(float)**<br> *in* Y_zero_point:**T**<br> *out* Y:**T**|1+|**T** = tensor(int8), tensor(uint8)|
|QuantizeLinear|*in* x:**T1**<br> *in* y_scale:**T1**<br> *in* y_zero_point:**T2**<br> *out* y:**T2**|1+|**T1** = tensor(float)<br/> **T2** = tensor(uint8)|
|SkipLayerNormalization|*in* input:**T**<br> *in* skip:**T**<br> *in* gamma:**T**<br> *in* beta:**T**<br> *in* bias:**T**<br> *out* output:**T**<br> *out* mean:**U**<br> *out* inv_std_var:**U**|1+|**T** = tensor(float), tensor(float16)|
|SkipLayerNormalization|*in* input:**T**<br> *in* skip:**T**<br> *in* gamma:**T**<br> *in* beta:**T**<br> *in* bias:**T**<br> *out* output:**T**<br> *out* mean:**U**<br> *out* inv_std_var:**U**<br> *out* input_skip_sum:**T**|1+|**T** = tensor(float), tensor(float16)|
| |
| |
|**Operator Domain:** *com.microsoft.dml*||||

View file

@ -39,6 +39,9 @@ Status SkipLayerNorm<T>::Compute(OpKernelContext* p_ctx) const {
const Tensor* beta = p_ctx->Input<Tensor>(3);
const Tensor* bias = p_ctx->Input<Tensor>(4);
Tensor* output = p_ctx->Output(0, input->Shape());
// For inferencing, we support one more optional output which is the sum
// of the input and skip tensors
Tensor* skip_input_add_output = p_ctx->Output(3, input->Shape());
const auto& input_dims = input->Shape().GetDims();
if (input_dims.size() != 3) {
@ -98,18 +101,28 @@ Status SkipLayerNorm<T>::Compute(OpKernelContext* p_ctx) const {
T* output_data = output->MutableData<T>();
// For inferencing, we support one more optional output which is the sum
// of the input and skip tensors
T* skip_input_add_output_data = skip_input_add_output != nullptr ? skip_input_add_output->MutableData<T>() : nullptr;
concurrency::ThreadPool::TryBatchParallelFor(
p_ctx->GetOperatorThreadPool(), static_cast<int32_t>(task_count),
[&](ptrdiff_t task_idx) {
const T* p_input = input_data + task_idx * hidden_size;
const T* p_skip = skip_data + task_idx * hidden_size;
T* p_output = output_data + task_idx * hidden_size;
auto offset = task_idx * hidden_size;
const T* p_input = input_data + offset;
const T* p_skip = skip_data + offset;
T* p_output = output_data + offset;
T* p_skip_input_add_output_data = skip_input_add_output_data != nullptr ? skip_input_add_output_data + offset : nullptr;
T mean = 0;
T mean_square = 0;
for (int64_t h = 0; h < hidden_size; h++) {
T value = p_input[h] + p_skip[h];
if (nullptr != p_skip_input_add_output_data) {
p_skip_input_add_output_data[h] = value;
}
if (nullptr != bias_data) {
value += bias_data[h];
}

View file

@ -41,6 +41,10 @@ Status SkipLayerNorm<T>::ComputeInternal(OpKernelContext* ctx) const {
Tensor* output = ctx->Output(0, input->Shape());
// For inferencing, we support one more optional output which is the sum
// of the input and skip tensors
Tensor* skip_input_add_output = ctx->Output(3, input->Shape());
if (input->Shape() != skip->Shape()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"skip is expected to have same shape as input");
@ -99,6 +103,7 @@ Status SkipLayerNorm<T>::ComputeInternal(OpKernelContext* ctx) const {
return LaunchSkipLayerNormKernel<CudaT>(
Stream(ctx),
reinterpret_cast<CudaT*>(output->MutableData<T>()),
skip_input_add_output != nullptr ? reinterpret_cast<CudaT*>(skip_input_add_output->MutableData<T>()) : nullptr,
reinterpret_cast<const CudaT*>(input->Data<T>()),
reinterpret_cast<const CudaT*>(skip->Data<T>()),
reinterpret_cast<const CudaT*>(gamma->Data<T>()),

View file

@ -50,8 +50,9 @@ half maybe2half(float x) {
template <typename T, unsigned TPB>
__global__ void SkipLayerNormKernel(
const int ld, const T* input, const T* skip, const T* beta, const T* gamma, const T* bias,
const T epsilon, T* output) {
const int ld, const T* input, const T* skip,
const T* beta, const T* gamma, const T* bias,
const T epsilon, T* output, T* skip_input_add_output) {
const T reverse_ld = T(1.f / ld);
const int offset = blockIdx.x * ld;
@ -61,6 +62,11 @@ __global__ void SkipLayerNormKernel(
for (int i = threadIdx.x; i < ld; i += TPB) {
const int idx = offset + i;
if (skip_input_add_output != nullptr) {
skip_input_add_output[idx] = input[idx] + skip[idx];
}
const T val = (bias == nullptr) ? input[idx] + skip[idx] : input[idx] + skip[idx] + bias[i];
const T rldval = reverse_ld * val;
thread_data = pair_sum(thread_data, cub::KeyValuePair<T, T>(rldval, rldval * val));
@ -74,13 +80,14 @@ __global__ void SkipLayerNormKernel(
template <typename T, unsigned TPB, int ILP>
__global__ void SkipLayerNormKernelSmall(
const int ld, const T* input, const T* skip, const T* beta, const T* gamma,
const T* bias, const T epsilon, T* output, bool hasBias) {
const T* bias, const T epsilon, T* output, T* skip_input_add_output,
bool hasBias, bool hasSkipInputAdditionOutput) {
const T rld = T(1.f / ld);
const int idx = blockIdx.x * ld + threadIdx.x * ILP; // grid_size = n / ld
using VecT = aligned_vector<T, ILP>;
T input_v[ILP], skip_v[ILP], bias_v[ILP];
T input_v[ILP], skip_v[ILP], bias_v[ILP], skip_input_add_output_v[ILP];
VecT* input_val = reinterpret_cast<VecT*>(&input_v);
*input_val = *reinterpret_cast<const VecT*>(&input[idx]);
@ -100,11 +107,20 @@ __global__ void SkipLayerNormKernelSmall(
T rldvalsq_sum = T(0.f);
#pragma unroll
for (int i = 0; i < ILP; i++) {
if (hasSkipInputAdditionOutput) {
skip_input_add_output_v[i] = input_v[i] + skip_v[i];
}
input_v[i] += hasBias ? skip_v[i] + bias_v[i] : skip_v[i];
const T rldval = rld * input_v[i];
rldval_sum += rldval;
rldvalsq_sum += rldval * input_v[i];
}
if (hasSkipInputAdditionOutput) {
*(reinterpret_cast<VecT*>(&skip_input_add_output[idx])) = *reinterpret_cast<VecT*>(&skip_input_add_output_v);
}
thread_data = cub::KeyValuePair<T, T>(rldval_sum, rldvalsq_sum);
}
LayerNormSmall<T, TPB, ILP>(input_v, thread_data, ld, idx, beta, gamma, epsilon, output);
@ -112,49 +128,57 @@ __global__ void SkipLayerNormKernelSmall(
template <typename T>
Status LaunchSkipLayerNormKernel(
cudaStream_t stream, T* output, const T* input, const T* skip, const T* gamma,
cudaStream_t stream, T* output, T* skip_input_add_output, const T* input, const T* skip, const T* gamma,
const T* beta, const T* bias, float epsilon, const int ld, const int element_count,
size_t element_size) {
// this must be true because n is the total size of the tensor
assert(element_count % ld == 0);
bool hasBias = (bias == nullptr) ? false : true;
bool hasSkipInputAdditionOutput = (skip_input_add_output == nullptr) ? false : true;
if (0 == (ld % 4)) {
const int grid_size = element_count / ld;
if (ld <= 32) {
constexpr int block_size = 32;
SkipLayerNormKernelSmall<T, block_size, 1>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output, hasBias);
maybe2half<T>(epsilon), output,
skip_input_add_output, hasBias, hasSkipInputAdditionOutput);
} else if (ld <= 64) {
constexpr int block_size = 64 / 2;
SkipLayerNormKernelSmall<T, block_size, 2>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output, hasBias);
maybe2half<T>(epsilon), output,
skip_input_add_output, hasBias, hasSkipInputAdditionOutput);
} else if (ld <= 128) {
constexpr int block_size = 128 / 4;
SkipLayerNormKernelSmall<T, block_size, 4>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output, hasBias);
maybe2half<T>(epsilon), output,
skip_input_add_output, hasBias, hasSkipInputAdditionOutput);
} else if (ld <= 384) {
constexpr int block_size = 384 / 4;
SkipLayerNormKernelSmall<T, block_size, 4>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output, hasBias);
maybe2half<T>(epsilon), output,
skip_input_add_output, hasBias, hasSkipInputAdditionOutput);
} else if (ld <= 768) {
constexpr int block_size = 768 / 4;
SkipLayerNormKernelSmall<T, block_size, 4>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output, hasBias);
maybe2half<T>(epsilon), output,
skip_input_add_output, hasBias, hasSkipInputAdditionOutput);
} else if (ld <= 1024) {
constexpr int block_size = 1024 / 4;
SkipLayerNormKernelSmall<T, block_size, 4>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output, hasBias);
maybe2half<T>(epsilon), output,
skip_input_add_output, hasBias, hasSkipInputAdditionOutput);
} else {
constexpr int block_size = 256;
SkipLayerNormKernel<T, block_size>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output);
maybe2half<T>(epsilon), output, skip_input_add_output);
}
} else {
const int grid_size = element_count / ld;
@ -162,41 +186,47 @@ Status LaunchSkipLayerNormKernel(
constexpr int block_size = 32;
SkipLayerNormKernelSmall<T, block_size, 1>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output, hasBias);
maybe2half<T>(epsilon), output,
skip_input_add_output, hasBias, hasSkipInputAdditionOutput);
} else if (ld <= 64) {
constexpr int block_size = 64;
SkipLayerNormKernelSmall<T, block_size, 1>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output, hasBias);
maybe2half<T>(epsilon), output,
skip_input_add_output, hasBias, hasSkipInputAdditionOutput);
} else if (ld <= 128) {
constexpr int block_size = 128;
SkipLayerNormKernelSmall<T, block_size, 1>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output, hasBias);
maybe2half<T>(epsilon), output,
skip_input_add_output, hasBias, hasSkipInputAdditionOutput);
} else if (ld == 384) {
constexpr int block_size = 384;
SkipLayerNormKernelSmall<T, block_size, 1>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output, hasBias);
maybe2half<T>(epsilon), output,
skip_input_add_output, hasBias, hasSkipInputAdditionOutput);
} else {
constexpr int block_size = 256;
SkipLayerNormKernel<T, block_size>
<<<grid_size, block_size, 0, stream>>>(ld, input, skip, beta, gamma, bias,
maybe2half<T>(epsilon), output);
maybe2half<T>(epsilon), output, skip_input_add_output);
}
}
return CUDA_CALL(cudaGetLastError());
}
template Status LaunchSkipLayerNormKernel<float>(cudaStream_t stream, float* output, const float* input,
const float* skip, const float* gamma, const float* beta,
const float* bias, float epsilon, const int ld,
const int element_count, size_t element_size);
template Status LaunchSkipLayerNormKernel<float>(cudaStream_t stream, float* output, float* skip_input_add_output,
const float* input,
const float* skip, const float* gamma, const float* beta,
const float* bias, float epsilon, const int ld,
const int element_count, size_t element_size);
template Status LaunchSkipLayerNormKernel<half>(cudaStream_t stream, half* output, const half* input,
const half* skip, const half* gamma, const half* beta,
const half* bias, float epsilon, const int ld,
const int element_count, size_t element_size);
template Status LaunchSkipLayerNormKernel<half>(cudaStream_t stream, half* output, half* skip_input_add_output,
const half* input,
const half* skip, const half* gamma, const half* beta,
const half* bias, float epsilon, const int ld,
const int element_count, size_t element_size);
} // namespace cuda
} // namespace contrib

View file

@ -11,15 +11,16 @@ namespace cuda {
template <typename T>
Status LaunchSkipLayerNormKernel(
cudaStream_t stream,
T* output, // output tensor
const T* input, // input tensor
const T* skip, // skip tensor
const T* gamma, // Layer normalization gamma tensor
const T* beta, // Layer normalization beta tensor
const T* bias, // Layer normalization beta tensor
float epsilon, // Layer normalization epsilon
int hidden_size, // hidden size, it is the leading dimension (ld)
int element_count, // number of elements in input tensor
T* output, // normalized output tensor
T* skip_input_add_output, // sum of the input and skip tensors output
const T* input, // input tensor
const T* skip, // skip tensor
const T* gamma, // Layer normalization gamma tensor
const T* beta, // Layer normalization beta tensor
const T* bias, // Layer normalization beta tensor
float epsilon, // Layer normalization epsilon
int hidden_size, // hidden size, it is the leading dimension (ld)
int element_count, // number of elements in input tensor
size_t element_size);
} // namespace cuda

View file

@ -110,12 +110,12 @@ void RestorePaddingTypeAndShapeInference(ONNX_NAMESPACE::InferenceContext& ctx)
auto& token_offset_shape = getInputShape(ctx, 1);
if (input_shape.dim().size() != 2) {
fail_shape_inference("input shall be 2 dimensions");
}
fail_shape_inference("input shall be 2 dimensions");
}
if (token_offset_shape.dim().size() != 2) {
fail_shape_inference("token_offset shall be 2 dimensions");
}
fail_shape_inference("token_offset shall be 2 dimensions");
}
ONNX_NAMESPACE::TensorShapeProto output_shape;
*output_shape.add_dim() = token_offset_shape.dim(0);
@ -125,7 +125,6 @@ void RestorePaddingTypeAndShapeInference(ONNX_NAMESPACE::InferenceContext& ctx)
}
}
constexpr const char* Attention_ver1_doc = R"DOC(
Multi-Head Attention that can be either unidirectional (like GPT-2) or bidirectional (like BERT).
@ -390,6 +389,7 @@ ONNX_MS_OPERATOR_SET_SCHEMA(SkipLayerNormalization, 1,
.Output(0, "output", "3D output tensor with shape (batch_size, sequence_length, hidden_size)", "T")
.Output(1, "mean", "Saved mean used during training to speed up gradient computation", "U", OpSchema::Optional)
.Output(2, "inv_std_var", "Saved inverse standard variance used during training to speed up gradient computation.", "U", OpSchema::Optional)
.Output(3, "input_skip_sum", "Sum of the input and skip inputs with shape (batch_size, sequence_length, hidden_size).", "T", OpSchema::Optional)
.TypeConstraint("T", {"tensor(float)", "tensor(float16)"}, "Constrain input and output types to float or half tensors.")
.TypeConstraint("U", {"tensor(float)"}, "Constrain mean and inv_std_var to float tensors.")
.TypeAndShapeInferenceFunction(ONNX_NAMESPACE::propagateShapeAndTypeFromFirstInput));
@ -508,7 +508,6 @@ ONNX_MS_OPERATOR_SET_SCHEMA(
RemovePaddingTypeAndShapeInference(ctx);
}));
constexpr const char* RestorePadding_ver1_doc = R"DOC(
Restore paddings and fill padding with zeros.

View file

@ -537,8 +537,6 @@ class FusionEmbedLayerNoMask(Fusion):
if two_gather is None:
return False
add_output = add_before_layernorm.output[0]
word_embedding_gather, position_embedding_gather = two_gather
input_ids = word_embedding_gather.input[1]
position_ids = position_embedding_gather.input[1]
@ -549,9 +547,22 @@ class FusionEmbedLayerNoMask(Fusion):
if not self.check_embedding(word_embedding_gather, None, position_embedding_gather):
return False
# If the add_before_layernorm node is an Add node, then the add_output output is the first index
# output of this node.
# If the add_before_layernorm node is SkipLayerNormalization node, then the add_output output
# is the (optional) fourth index output of this node.
add_output = None
optional_embedding_sum_output = False
if self.is_embedding_sum_needed(add_before_layernorm):
if (add_before_layernorm.op_type == "Add" and self.is_embedding_sum_needed(add_before_layernorm)) or (
add_before_layernorm.op_type == "SkipLayerNormalization" and len(add_before_layernorm.output) >= 4
):
optional_embedding_sum_output = True
add_output = (
add_before_layernorm.output[0]
if add_before_layernorm.op_type == "Add"
else add_before_layernorm.output[3]
)
# make the fused node
embed_node = self.create_fused_node(

View file

@ -17,7 +17,7 @@ class FusionGptAttentionPastBase(Fusion):
"""Base class for GPT Attention Fusion with past state"""
def __init__(self, model: OnnxModel, num_heads: int):
super().__init__(model, "Attention", "LayerNormalization", "with past")
super().__init__(model, "Attention", ["LayerNormalization", "SkipLayerNormalization"], "with past")
self.num_heads = num_heads
self.utils = FusionUtils(model)
self.casted_attention_mask = {} # map from name of attention mask to the name that casted to int32
@ -224,26 +224,52 @@ class FusionGptAttention(FusionGptAttentionPastBase):
past = None
present = None
return_indice = []
qkv_nodes = self.model.match_parent_path(
normalize_node,
["Add", "Reshape", "Gemm", "Reshape", "Reshape", "Transpose", "MatMul"],
[0, None, 0, 0, 0, 0, 0],
output_name_to_node=output_name_to_node,
return_indice=return_indice,
) # yapf: disable
is_normalize_node_skiplayernorm = normalize_node.op_type == "SkipLayerNormalization"
qkv_nodes = None
if not is_normalize_node_skiplayernorm:
qkv_nodes = self.model.match_parent_path(
normalize_node,
["Add", "Reshape", "Gemm", "Reshape", "Reshape", "Transpose", "MatMul"],
[0, None, 0, 0, 0, 0, 0],
output_name_to_node=output_name_to_node,
return_indice=return_indice,
) # yapf: disable
else:
qkv_nodes = self.model.match_parent_path(
normalize_node,
["Reshape", "Gemm", "Reshape", "Reshape", "Transpose", "MatMul"],
[None, 0, 0, 0, 0, 0],
output_name_to_node=output_name_to_node,
return_indice=return_indice,
) # yapf: disable
if qkv_nodes is None:
return
(
add_qkv,
reshape_qkv,
gemm_qkv,
reshape_1,
reshape_2,
transpose_qkv,
matmul_qkv,
) = qkv_nodes
another_input = add_qkv.input[1 - return_indice[0]]
another_input = None
if not is_normalize_node_skiplayernorm:
(
add_qkv,
reshape_qkv,
gemm_qkv,
reshape_1,
reshape_2,
transpose_qkv,
matmul_qkv,
) = qkv_nodes
another_input = add_qkv.input[1 - return_indice[0]]
else:
(
reshape_qkv,
gemm_qkv,
reshape_1,
reshape_2,
transpose_qkv,
matmul_qkv,
) = qkv_nodes
v_nodes = self.model.match_parent_path(matmul_qkv, ["Concat", "Transpose", "Reshape", "Split"], [1, 1, 0, 0])
if v_nodes is None:
@ -251,22 +277,46 @@ class FusionGptAttention(FusionGptAttentionPastBase):
return
(concat_v, transpose_v, reshape_v, split_fc) = v_nodes
# Try match pattern using Gemm + LayerNormalization
fc_nodes = self.model.match_parent_path(
split_fc,
["Reshape", "Gemm", "Reshape", "LayerNormalization"],
[0, 0, 0, 0],
output_name_to_node,
)
# Try match pattern using Gemm + SkipLayerNormalization
if fc_nodes is None:
fc_nodes = self.model.match_parent_path(
split_fc,
["Reshape", "Gemm", "Reshape", "SkipLayerNormalization"],
[0, 0, 0, 0],
output_name_to_node,
)
# Try match pattern using MatMul
if fc_nodes is None:
# LayerNormalization
fc_nodes = self.model.match_parent_path(
split_fc,
["Add", "MatMul", "LayerNormalization"],
[0, None, 0],
output_name_to_node,
)
# SkipLayerNormalization
if fc_nodes is None:
fc_nodes = self.model.match_parent_path(
split_fc,
["Add", "MatMul", "SkipLayerNormalization"],
[0, None, 0],
output_name_to_node,
)
if fc_nodes is None:
logger.debug("fuse_attention: failed to match fc path")
return
fc_weight = fc_nodes[1].input[1]
i, _ = self.model.get_constant_input(fc_nodes[0])
fc_bias = fc_nodes[0].input[i]
@ -276,8 +326,13 @@ class FusionGptAttention(FusionGptAttentionPastBase):
layernorm_before_attention = fc_nodes[-1]
if not another_input in layernorm_before_attention.input:
logger.debug("Add and LayerNormalization shall have one same input")
# `another_input` will be non-None only if
# (1) SkipLayerNorm fusion wasn't turned ON
# (2) SkipLayerNorm fusion was turned ON but upstream layer's LayerNorm + Add was not
# fused into a SkipLayerNorm. This can happen if the shapes to the Add node are different.
# So, keep the following check if SkipLayerNorm fusion is turned ON or OFF.
if another_input is not None and not another_input in layernorm_before_attention.input:
logger.debug("Upstream Add and (Skip)LayerNormalization shall have one same input")
return
is_unidirectional = True

View file

@ -144,6 +144,14 @@ class FusionGptAttentionMegatron(FusionGptAttentionPastBase):
["Unsqueeze", "Sub", "Gather", "Shape", "LayerNormalization"],
[1, 0, 1, 0, 0],
)
if first_slice_sub_1 is None:
first_slice_sub_1 = self.model.match_parent_path(
slice_mask,
["Unsqueeze", "Sub", "Gather", "Shape", "SkipLayerNormalization"],
[1, 0, 1, 0, 0],
)
if first_slice_sub_1 is None or first_slice_sub_1[-1] != layernorm_before_attention:
logger.debug("fuse_attention: failed to match last slice sub path 1")
return None
@ -154,24 +162,49 @@ class FusionGptAttentionMegatron(FusionGptAttentionPastBase):
past = None
present = None
qkv_nodes = self.model.match_parent_path(
normalize_node,
["Add", "Add", "MatMul", "Reshape", "Transpose", "MatMul"],
[0, 1, None, 0, 0, 0],
output_name_to_node=output_name_to_node,
) # yapf: disable
is_normalize_node_skiplayernorm = normalize_node.op_type == "SkipLayerNormalization"
qkv_nodes = None
if not is_normalize_node_skiplayernorm:
qkv_nodes = self.model.match_parent_path(
normalize_node,
["Add", "Add", "MatMul", "Reshape", "Transpose", "MatMul"],
[0, 1, None, 0, 0, 0],
output_name_to_node=output_name_to_node,
) # yapf: disable
else:
qkv_nodes = self.model.match_parent_path(
normalize_node,
["Add", "MatMul", "Reshape", "Transpose", "MatMul"],
[1, None, 0, 0, 0],
output_name_to_node=output_name_to_node,
) # yapf: disable
if qkv_nodes is None:
return
(
add_skip,
add_after_attention,
matmul_after_attention,
reshape_qkv,
transpose_qkv,
matmul_qkv,
) = qkv_nodes
skip_input = add_skip.input[0]
skip_input = None
if not is_normalize_node_skiplayernorm:
(
add_skip,
add_after_attention,
matmul_after_attention,
reshape_qkv,
transpose_qkv,
matmul_qkv,
) = qkv_nodes
skip_input = add_skip.input[0]
else:
(
add_after_attention,
matmul_after_attention,
reshape_qkv,
transpose_qkv,
matmul_qkv,
) = qkv_nodes
skip_input = normalize_node.input[0]
v_nodes = self.model.match_parent_path(
matmul_qkv,
@ -186,6 +219,22 @@ class FusionGptAttentionMegatron(FusionGptAttentionPastBase):
],
[1, 1, 0, 0, 0, None, 0],
) # yapf: disable
if v_nodes is None:
v_nodes = self.model.match_parent_path(
matmul_qkv,
[
"Concat",
"Transpose",
"Reshape",
"Split",
"Add",
"MatMul",
"SkipLayerNormalization",
],
[1, 1, 0, 0, 0, None, 0],
) # yapf: disable
if v_nodes is None:
logger.debug("fuse_attention: failed to match v path")
return
@ -198,7 +247,18 @@ class FusionGptAttentionMegatron(FusionGptAttentionPastBase):
matmul_before_split,
layernorm_before_attention,
) = v_nodes
if skip_input != layernorm_before_attention.input[0]:
if (
layernorm_before_attention.op_type == "LayerNormalization"
and skip_input != layernorm_before_attention.input[0]
):
logger.debug("fuse_attention: skip_input != layernorm_before_attention.input[0]")
return
if (
layernorm_before_attention.op_type == "SkipLayerNormalization"
and skip_input != layernorm_before_attention.output[3]
):
logger.debug("fuse_attention: skip_input != layernorm_before_attention.input[0]")
return

View file

@ -20,7 +20,7 @@ class FusionGptAttentionNoPast(Fusion):
"""
def __init__(self, model: OnnxModel, num_heads: int):
super().__init__(model, "Attention", "LayerNormalization", "without past")
super().__init__(model, "Attention", ["LayerNormalization", "SkipLayerNormalization"], "without past")
# TODO: detect num_heads from graph like FusionAttention
self.num_heads = num_heads
@ -60,27 +60,56 @@ class FusionGptAttentionNoPast(Fusion):
self.node_name_to_graph_name[add_node.name] = self.this_graph_name
def fuse(self, normalize_node, input_name_to_nodes, output_name_to_node):
# (TODO) hasesh/tlwu: Investigate what fixes the following logic needs in order
# to fuse the Attention sub-graph. With some changes to other fusions, this stopped
# working.
return_indice = []
qkv_nodes = self.model.match_parent_path(
normalize_node,
["Add", "Reshape", "Gemm", "Reshape", "Reshape", "Transpose", "MatMul"],
[0, None, 0, 0, 0, 0, 0],
output_name_to_node=output_name_to_node,
return_indice=return_indice,
) # yapf: disable
is_normalize_node_skiplayernorm = normalize_node.op_type == "SkipLayerNormalization"
qkv_nodes = None
if not is_normalize_node_skiplayernorm:
qkv_nodes = self.model.match_parent_path(
normalize_node,
["Add", "Reshape", "Gemm", "Reshape", "Reshape", "Transpose", "MatMul"],
[0, None, 0, 0, 0, 0, 0],
output_name_to_node=output_name_to_node,
return_indice=return_indice,
) # yapf: disable
else:
qkv_nodes = self.model.match_parent_path(
normalize_node,
["Reshape", "Gemm", "Reshape", "Reshape", "Transpose", "MatMul"],
[None, 0, 0, 0, 0, 0],
output_name_to_node=output_name_to_node,
return_indice=return_indice,
) # yapf: disable
if qkv_nodes is None:
return
(
add_qkv,
reshape_qkv,
gemm_qkv,
reshape_1,
reshape_2,
transpose_qkv,
matmul_qkv,
) = qkv_nodes
another_input = add_qkv.input[1 - return_indice[0]]
another_input = None
if not is_normalize_node_skiplayernorm:
(
add_qkv,
reshape_qkv,
gemm_qkv,
reshape_1,
reshape_2,
transpose_qkv,
matmul_qkv,
) = qkv_nodes
another_input = add_qkv.input[1 - return_indice[0]]
else:
(
reshape_qkv,
gemm_qkv,
reshape_1,
reshape_2,
transpose_qkv,
matmul_qkv,
) = qkv_nodes
v_nodes = self.model.match_parent_path(
matmul_qkv,
@ -100,16 +129,25 @@ class FusionGptAttentionNoPast(Fusion):
) = v_nodes
layernorm_before_attention = self.model.get_parent(reshape_before_gemm, 0, output_name_to_node)
if layernorm_before_attention is None or layernorm_before_attention.op_type != "LayerNormalization":
if layernorm_before_attention is None or (
layernorm_before_attention.op_type != "LayerNormalization"
and layernorm_before_attention.op_type != "SkipLayerNormalization"
):
if layernorm_before_attention.op_type != "Add":
logger.debug(f"failed to get layernorm before gemm. Got {layernorm_before_attention.op_type}")
logger.debug(f"failed to get (skip)layernorm before gemm. Got {layernorm_before_attention.op_type}")
return
if not another_input in layernorm_before_attention.input:
# match openai-gpt
if not another_input in layernorm_before_attention.output:
logger.debug("Add and LayerNormalization shall have one same input")
return
# `another_input` will be non-None only if
# (1) SkipLayerNorm fusion wasn't turned ON
# (2) SkipLayerNorm fusion was turned ON but upstream layer's LayerNorm + Add was not
# fused into a SkipLayerNorm. This can happen if the shapes to the Add node are different.
# So, keep the following check if SkipLayerNorm fusion is turned ON or OFF.
if another_input is not None:
if not another_input in layernorm_before_attention.input:
# match openai-gpt
if not another_input in layernorm_before_attention.output:
logger.debug("Add and (Skip)LayerNormalization shall have one same input")
return
qk_nodes = self.model.match_parent_path(matmul_qkv, ["Softmax", "Sub", "Mul", "Div", "MatMul"], [0, 0, 0, 0, 0])
if qk_nodes is not None:

View file

@ -30,9 +30,6 @@ class FusionOptions:
self.enable_gemm_fast_gelu = False
self.attention_mask_format = AttentionMaskFormat.AttentionMask
if model_type == "gpt2":
self.enable_skip_layer_norm = False
def use_raw_attention_mask(self, use_raw_mask=True):
if use_raw_mask:
self.attention_mask_format = AttentionMaskFormat.AttentionMask

View file

@ -61,10 +61,31 @@ class FusionSkipLayerNormalization(Fusion):
if self.model.match_parent_path(gather_path[0], ["ConstantOfShape"], [1]) is None:
return
residual_add_has_multiple_consumers = False
add_children = self.model.get_children(add, input_name_to_nodes)
# This means that the residual Add before the LayerNormalization produces an output
# that is consumed by some other nodes other than the LayerNormalization itself
# We can still go ahead with the SkipLayerNormalization fusion but we need to
# preserve the output of Add and that needs to be produced by SkipLayerNormalization.
if len(add_children) != 1:
residual_add_has_multiple_consumers = True
outputs_to_keep = node.output
if residual_add_has_multiple_consumers:
outputs_to_keep.extend([add.output[0]])
outputs = [node.output[0]]
# Skip the other optional outputs of SkipLayerNormalization before adding the Add's output
if residual_add_has_multiple_consumers:
outputs.extend(["", "", add.output[0]])
if (
add is not None
and add.op_type == "Add"
and self.model.is_safe_to_fuse_nodes([add, node], node.output, input_name_to_nodes, output_name_to_node)
and self.model.is_safe_to_fuse_nodes([add, node], outputs_to_keep, input_name_to_nodes, output_name_to_node)
):
self.nodes_to_remove.extend([add, node])
@ -72,7 +93,7 @@ class FusionSkipLayerNormalization(Fusion):
normalize_node = helper.make_node(
"SkipLayerNormalization",
inputs=inputs,
outputs=[node.output[0]],
outputs=outputs,
name=self.model.create_node_name("SkipLayerNormalization", name_prefix="SkipLayerNorm"),
)
normalize_node.domain = "com.microsoft"
@ -101,8 +122,14 @@ class FusionBiasSkipLayerNormalization(Fusion):
return_indice = []
nodes = self.model.match_parent_path(node, ["Add", "MatMul"], [None, None], None, return_indice)
if nodes is None:
return
assert len(return_indice) == 2
# In case of fp16, we could have a Cast between the MatMul and the bias Add
nodes = self.model.match_parent_path(
node, ["Add", "Cast", "MatMul"], [None, None, None], None, return_indice
)
if nodes is None:
return
assert len(return_indice) == 2 or len(return_indice) == 3
add_input_index = return_indice[0]
if add_input_index >= 2:
return
@ -126,9 +153,7 @@ class FusionBiasSkipLayerNormalization(Fusion):
return
subgraph_nodes = [node, add]
if not self.model.is_safe_to_fuse_nodes(
subgraph_nodes, [node.output[0]], input_name_to_nodes, output_name_to_node
):
if not self.model.is_safe_to_fuse_nodes(subgraph_nodes, node.output, input_name_to_nodes, output_name_to_node):
logger.debug(f"Skip fusing SkipLayerNormalization with Bias since it is not safe")
return

View file

@ -17,6 +17,7 @@ static void RunTest(
const std::vector<float>& beta_data,
const std::vector<float>& bias_data,
const std::vector<float>& output_data,
const std::vector<float>& skip_input_add_output_data,
float epsilon,
int batch_size,
int sequence_length,
@ -54,6 +55,17 @@ static void RunTest(
}
test.AddOutput<float>("output", output_dims, output_data);
if (skip_input_add_output_data.size() != 0) {
// The second and third outputs are reserved for something else
test.AddOptionalOutputEdge<float>();
test.AddOptionalOutputEdge<float>();
test.AddOutput<float>("input_skip_input_add_output",
output_dims,
skip_input_add_output_data);
}
test.Run();
} else if (HasCudaEnvironment(530 /*min_cuda_architecture*/) ||
dml_ep != nullptr ||
@ -74,9 +86,19 @@ static void RunTest(
test.AddOutput<MLFloat16>("output", output_dims, ToFloat16(output_data));
if (skip_input_add_output_data.size() != 0) {
// The second and third outputs are reserved for something else
test.AddOptionalOutputEdge<MLFloat16>();
test.AddOptionalOutputEdge<MLFloat16>();
test.AddOutput<MLFloat16>("input_skip_input_add_output",
output_dims,
ToFloat16(skip_input_add_output_data));
}
std::vector<std::unique_ptr<IExecutionProvider>> execution_providers;
if (dml_ep != nullptr) {
execution_providers.push_back(DefaultDmlExecutionProvider());
execution_providers.push_back(DefaultDmlExecutionProvider());
} else if (rocm_ep != nullptr) {
execution_providers.push_back(DefaultRocmExecutionProvider());
} else {
@ -110,6 +132,7 @@ TEST(SkipLayerNormTest, SkipLayerNormNullInput) {
beta_data,
std::vector<float>(),
output_data,
{},
epsilon_,
batch_size,
sequence_length,
@ -145,6 +168,7 @@ TEST(SkipLayerNormTest, SkipLayerNormBatch1) {
beta_data,
std::vector<float>(),
output_data,
{},
epsilon_,
batch_size,
sequence_length,
@ -180,6 +204,7 @@ TEST(SkipLayerNormTest, SkipLayerNormBatch1_Float16) {
beta_data,
std::vector<float>(),
output_data,
{},
epsilon_,
batch_size,
sequence_length,
@ -272,6 +297,7 @@ TEST(SkipLayerNormTest, SkipLayerNormBatch1_Float16_vec) {
beta_data,
std::vector<float>(),
output_data,
{},
epsilon_,
batch_size,
sequence_length,
@ -307,6 +333,7 @@ TEST(SkipLayerNormTest, SkipLayerNormBatch1_NoBeta) {
beta_data,
std::vector<float>(),
output_data,
{},
epsilon_,
batch_size,
sequence_length,
@ -350,6 +377,7 @@ TEST(SkipLayerNormTest, SkipLayerNormBatch2) {
beta_data,
std::vector<float>(),
output_data,
{},
epsilon_,
batch_size,
sequence_length,
@ -394,11 +422,63 @@ TEST(SkipLayerNormTest, SkipLayerNormBatch2_Bias) {
beta_data,
bias_data,
output_data,
{},
epsilon_,
batch_size,
sequence_length,
hidden_size);
}
// Don't enable this test for DML/ROCM builds as these EP doesn't produce the new optional output yet
#if !defined(USE_ROCM) && !defined(USE_DML)
TEST(SkipLayerNormTest, SkipLayerNormBatch2_Bias_ProducingOptionalOutput) {
int batch_size = 2;
int sequence_length = 2;
int hidden_size = 4;
std::vector<float> input_data = {
0.7f, -0.4f, -0.2f, 1.2f,
0.4f, 0.3f, 0.1f, -0.4f,
0.7f, -0.4f, -0.2f, 1.2f,
0.4f, 0.3f, 0.1f, -0.4f};
std::vector<float> skip_data = {
0.1f, -0.2f, 0.3f, 1.0f,
0.5f, 0.1f, 0.4f, 1.6f,
1.8f, -0.3f, 0.0f, 1.f,
-0.5f, 0.4f, 0.8f, -0.6f};
std::vector<float> gamma_data = {
0.3f, 0.2f, 4.0f, 2.2f};
std::vector<float> beta_data = {
0.2f, 0.1f, 0.4f, 1.6f};
std::vector<float> bias_data = {
0.1f, -0.1f, 0.2f, -0.2f};
std::vector<float> output_data = {
0.28433859348297119, -0.17090578377246857, -0.92897164821624756, 4.6924152374267578,
0.46111652255058289, -0.21333980560302734, -0.29631003737449646, 3.5148544311523438,
0.55470430850982666, -0.15080101788043976, -2.3229825496673584, 3.255286693572998,
0.15631480515003204, 0.21066918969154358, 4.9432611465454102, -1.7957965135574341};
std::vector<float> input_skip_add_output_data(batch_size * sequence_length * hidden_size, 0.f);
std::transform(input_data.cbegin(), input_data.cend(), skip_data.cbegin(), input_skip_add_output_data.begin(), [](const float& i, const float& s) { return i + s; });
RunTest(input_data,
skip_data,
gamma_data,
beta_data,
bias_data,
output_data,
input_skip_add_output_data,
epsilon_,
batch_size,
sequence_length,
hidden_size);
}
#endif
} // namespace test
} // namespace onnxruntime

View file

@ -14,9 +14,11 @@ from model_loader import get_test_data_path
from parity_utilities import find_transformers_source
if find_transformers_source():
from fusion_options import FusionOptions
from onnx_model import OnnxModel
from optimizer import optimize_by_fusion, optimize_model
else:
from onnxruntime.transformers.fusion_options import FusionOptions
from onnxruntime.transformers.onnx_model import OnnxModel
from onnxruntime.transformers.optimizer import optimize_by_fusion, optimize_model
@ -120,32 +122,67 @@ class TestFusion(unittest.TestCase):
hidden_size = 64
num_heads = 4
for add_order in [False, True]:
model = create_gpt2_attention(
hidden_size=hidden_size,
num_heads=num_heads,
switch_add_inputs=add_order,
)
dir = "."
model_path = os.path.join(dir, "gpt2_attention.onnx")
onnx.save(model, model_path)
optimized_model = optimize_model(
model_path,
model_type="gpt2",
num_heads=num_heads,
hidden_size=hidden_size,
)
optimized_model.topological_sort()
os.remove(model_path)
for enable_skip_layer_norm_fusion in [False, True]:
model = create_gpt2_attention(
hidden_size=hidden_size,
num_heads=num_heads,
switch_add_inputs=add_order,
)
dir = "."
model_path = os.path.join(dir, "gpt2_attention.onnx")
onnx.save(model, model_path)
model_name = "gpt2_attention_{}.onnx".format("add_opt" if add_order else "opt")
self.verify_fusion(optimized_model, model_name)
options = FusionOptions("gpt2")
options.enable_skip_layer_norm = enable_skip_layer_norm_fusion
optimized_model = optimize_model(
model_path,
model_type="gpt2",
num_heads=num_heads,
hidden_size=hidden_size,
optimization_options=options,
)
optimized_model.topological_sort()
os.remove(model_path)
model_suffix = ""
if add_order and enable_skip_layer_norm_fusion:
model_suffix = "add_opt_skiplayernorm"
elif add_order and not enable_skip_layer_norm_fusion:
model_suffix = "add_opt_no_skiplayernorm"
elif not add_order and enable_skip_layer_norm_fusion:
model_suffix = "opt_skiplayernorm"
else:
model_suffix = "opt_no_skiplayernorm"
model_name = "gpt2_attention_{}.onnx".format(model_suffix)
self.verify_fusion(optimized_model, model_name)
def test_megatron_gpt2_attention_fusion(self):
path = get_test_data_path("models", "gpt2_megatron.onnx")
model = onnx.load(path)
optimized_model = optimize_by_fusion(model, model_type="gpt2")
for enable_skip_layer_norm_fusion in [False, True]:
path = get_test_data_path("models", "gpt2_megatron.onnx")
model = onnx.load(path)
self.verify_fusion(optimized_model, "gpt2_megatron_opt.onnx")
options = FusionOptions("gpt2")
options.enable_skip_layer_norm = enable_skip_layer_norm_fusion
optimized_model = optimize_by_fusion(
model,
model_type="gpt2",
num_heads=0,
hidden_size=0,
optimization_options=options,
)
model_suffix = ""
if enable_skip_layer_norm_fusion:
model_suffix = "opt_skiplayernorm"
else:
model_suffix = "opt_no_skiplayernorm"
model_name = "gpt2_megatron_{}.onnx".format(model_suffix)
self.verify_fusion(optimized_model, model_name)
if __name__ == "__main__":

View file

@ -132,19 +132,31 @@ class TestModelOptimization(unittest.TestCase):
self.assertTrue(False)
def test_gpt2_past(self):
input = _get_test_model_path("gpt2_past")
model = optimize_model(input, "gpt2", num_heads=2, hidden_size=4)
for enable_skip_layer_norm_fusion in [False, True]:
input_path = _get_test_model_path("gpt2_past")
expected_node_count = {
"EmbedLayerNormalization": 0,
"Attention": 12,
"Gelu": 0,
"FastGelu": 12,
"BiasGelu": 0,
"LayerNormalization": 25,
"SkipLayerNormalization": 0,
}
self.verify_node_count(model, expected_node_count, "test_gpt2_past")
options = FusionOptions("gpt2")
options.enable_skip_layer_norm = enable_skip_layer_norm_fusion
model = optimize_model(
input_path,
"gpt2",
num_heads=2,
hidden_size=4,
optimization_options=options,
)
expected_node_count = {
"EmbedLayerNormalization": 0,
"Attention": 12,
"Gelu": 0,
"FastGelu": 12,
"BiasGelu": 0,
# First LayerNorm is never fused to SkipLayerNorm as it doesn't meet the requirements
"LayerNormalization": 25 if not enable_skip_layer_norm_fusion else 1,
"SkipLayerNormalization": 0 if not enable_skip_layer_norm_fusion else 24,
}
self.verify_node_count(model, expected_node_count, "test_gpt2_past")
def test_gpt2_past_fp16(self):
input_model_path = _get_test_model_path("gpt2_past")
@ -156,18 +168,30 @@ class TestModelOptimization(unittest.TestCase):
self.assertEqual(output.type.tensor_type.elem_type, TensorProto.FLOAT16)
def test_gpt2_past_mask(self):
input = _get_test_model_path("gpt2_past_mask")
model = optimize_model(input, "gpt2", num_heads=2, hidden_size=4)
expected_node_count = {
"EmbedLayerNormalization": 1,
"Attention": 1,
"Gelu": 0,
"FastGelu": 1,
"BiasGelu": 0,
"LayerNormalization": 1,
"SkipLayerNormalization": 0,
}
self.verify_node_count(model, expected_node_count, "test_gpt2_past_mask")
for enable_skip_layer_norm_fusion in [False, True]:
input_path = _get_test_model_path("gpt2_past_mask")
options = FusionOptions("gpt2")
options.enable_skip_layer_norm = enable_skip_layer_norm_fusion
model = optimize_model(
input_path,
"gpt2",
num_heads=2,
hidden_size=4,
optimization_options=options,
)
expected_node_count = {
"EmbedLayerNormalization": 1,
"Attention": 1,
"Gelu": 0,
"FastGelu": 1,
"BiasGelu": 0,
"LayerNormalization": 1 if not enable_skip_layer_norm_fusion else 0,
"SkipLayerNormalization": 0 if not enable_skip_layer_norm_fusion else 1,
}
self.verify_node_count(model, expected_node_count, "test_gpt2_past_mask")
def test_multiple_embed(self):
input_model_path = _get_test_model_path("multiple_embed")