Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6fb7369
initial
tianleiwu Nov 22, 2024
9b2dcc0
sd3.x and flux
tianleiwu Dec 2, 2024
7f925ce
update FastGelu and RMSNorm fusions
tianleiwu Dec 5, 2024
cf259e1
support Reciprocal in RMSNorm fusion
tianleiwu Dec 6, 2024
b38f12e
match_child_path interface change
tianleiwu Dec 13, 2024
a58b68c
clean up
tianleiwu Dec 13, 2024
c7317cb
MHA fusion for MMDit
tianleiwu Dec 13, 2024
2f5b9b9
cuda layernorm support broadcast
tianleiwu Dec 15, 2024
699a64c
force fuse layernorm
tianleiwu Dec 15, 2024
c1d0160
refactoring
tianleiwu Dec 15, 2024
1b9ea54
mha fusion for flux
tianleiwu Dec 19, 2024
5528276
remove transpose for query
tianleiwu Dec 20, 2024
89950d1
t5 optimization and mixed precision conversion
tianleiwu Dec 23, 2024
c869151
fix node name
tianleiwu Dec 23, 2024
84b1a51
Add option to use bfloat16
tianleiwu Dec 24, 2024
b7041d1
fix attention
tianleiwu Dec 25, 2024
455a3ea
update node block list of t5 encoder
tianleiwu Dec 25, 2024
dad0ac4
benchmark torch eager mode
tianleiwu Dec 25, 2024
8400558
update comment
tianleiwu Dec 25, 2024
9e43e20
benchmark torch compile
tianleiwu Dec 26, 2024
4bf9f25
refine benchmark_flux.sh
tianleiwu Dec 26, 2024
913c6ed
Merge branch 'main' into tlwu/sd3_optimum
tianleiwu Jan 6, 2025
a47b6af
undo layer norm kernel
tianleiwu Jan 10, 2025
55178d6
CMAKE_CUDA_ARCHITECTURES=native
tianleiwu Jan 11, 2025
dac8ea7
Merge branch 'main' into tlwu/sd3_optimum
tianleiwu Jan 11, 2025
ebade48
add tests
tianleiwu Jan 12, 2025
fd227bb
update tests
tianleiwu Jan 12, 2025
87bd3ec
undo some change (move to another PR)
tianleiwu Jan 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 25 additions & 36 deletions onnxruntime/contrib_ops/cuda/bert/attention_prepare_qkv.cu
Original file line number Diff line number Diff line change
Expand Up @@ -125,42 +125,31 @@ Status PrepareQkv_Attention(contrib::AttentionParameters& parameters,
bool use_fused_kernel = (nullptr != fused_runner && !parameters.is_unidirectional);
bool use_fused_causal = (nullptr != fused_runner && parameters.is_unidirectional);

if (data.bias == nullptr) {
assert(nullptr == fused_runner);
// For quantized attention, bias has been added so only need transpose here.
// gemm_buffer should be BxSx3xNxH => qkv: 3xBxNxSxH
assert(qk_head_size == v_head_size);
int matrix_to_trans = (past_present_share_buffer ? 1 : 3);
ORT_RETURN_IF_ERROR(LaunchTransQkv(stream, matrix_to_trans, sequence_length, batch_size, qk_head_size, num_heads,
max_threads_per_block, false, data.gemm_buffer, qkv, 3));
data.qkv_format = AttentionQkvFormat::Q_K_V_BNSH;
} else {
// For fused TRT attention, transpose qkv to BxSxNx3xH (format 2)
// For flash or memory efficient attention, transpose to 3xBxSxNxH (format 3)
// For unfused kernel, transpose to 3xBxNxSxH (format 1)
// For fused causal kernel, use format 1 since we need have K and V to update present state,
// at the same time, we update gemm_buffer BxSx3xNxH with bias which is used as input for fused causal kernel.
const int format = (use_fused_kernel ? 2 : (use_flash_or_efficient_attention ? 3 : 1));
data.qkv_format = use_fused_kernel
? AttentionQkvFormat::QKV_BSN3H
: (use_flash_or_efficient_attention
? AttentionQkvFormat::Q_K_V_BSNH
: (use_fused_causal
? AttentionQkvFormat::Q_K_V_BNSH_QKV_BS3NH
: AttentionQkvFormat::Q_K_V_BNSH));

// For fused causal, we will update gemm_buffer with bias directly.
T* qkv_add_bias = use_fused_causal ? data.gemm_buffer : nullptr;

int matrix_to_transpose = ((format == AttentionQkvFormat::Q_K_V_BNSH && past_present_share_buffer) ? 1 : 3);
// format 1: BxSx(NH + NH + NH_v) => BxNxSxH + BxNxSxH + BxNxSxH_v
// format 2: BxSx(NH + NH + NH) => BxSxNx(H + H + H)
LaunchAddBiasTranspose(stream, matrix_to_transpose, format, max_threads_per_block,
batch_size, sequence_length, num_heads, qk_head_size,
data.gemm_buffer, data.bias, qkv, true, v_head_size, qkv_add_bias,
3, parameters.do_rotary, parameters.rotary_embedding,
parameters.past_sequence_length);
}
// For fused TRT attention, transpose qkv to BxSxNx3xH (format 2)
// For flash or memory efficient attention, transpose to 3xBxSxNxH (format 3)
// For unfused kernel, transpose to 3xBxNxSxH (format 1)
// For fused causal kernel, use format 1 since we need have K and V to update present state,
// at the same time, we update gemm_buffer BxSx3xNxH with bias which is used as input for fused causal kernel.
const int format = (use_fused_kernel ? 2 : (use_flash_or_efficient_attention ? 3 : 1));
data.qkv_format = use_fused_kernel
? AttentionQkvFormat::QKV_BSN3H
: (use_flash_or_efficient_attention
? AttentionQkvFormat::Q_K_V_BSNH
: (use_fused_causal
? AttentionQkvFormat::Q_K_V_BNSH_QKV_BS3NH
: AttentionQkvFormat::Q_K_V_BNSH));

// For fused causal, we will update gemm_buffer with bias directly.
T* qkv_add_bias = use_fused_causal ? data.gemm_buffer : nullptr;

int matrix_to_transpose = ((format == AttentionQkvFormat::Q_K_V_BNSH && past_present_share_buffer) ? 1 : 3);
// format 1: BxSx(NH + NH + NH_v) => BxNxSxH + BxNxSxH + BxNxSxH_v
// format 2: BxSx(NH + NH + NH) => BxSxNx(H + H + H)
LaunchAddBiasTranspose(stream, matrix_to_transpose, format, max_threads_per_block,
batch_size, sequence_length, num_heads, qk_head_size,
data.gemm_buffer, data.bias, qkv, true, v_head_size, qkv_add_bias,
3, parameters.do_rotary, parameters.rotary_embedding,
parameters.past_sequence_length);
return Status::OK();
}

Expand Down
2 changes: 1 addition & 1 deletion onnxruntime/core/framework/print_tensor_statistics_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ void PrintFloatStats(const T* data, size_t count) {
size_t zero = 0;
size_t subnormal = 0;
for (size_t i = 0; i < count; i++) {
switch (my_fpclassify(*data)) {
switch (my_fpclassify(data[i])) {
case FP_INFINITE:
inf++;
break;
Expand Down
12 changes: 10 additions & 2 deletions onnxruntime/python/tools/transformers/compare_bert_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,23 @@ def compare(baseline_results, treatment_results, verbose, rtol=1e-1, atol=1e-3):
# Validate the output of baseline and treatment, to make sure the results are similar.
diff_count = 0
max_abs_diff = 0
max_diff_percentage = 0
case_passed = True
for test_case_id, results in enumerate(baseline_results):
case_passed = True
for i in range(len(results)):
treatment_output = treatment_results[test_case_id][i]
abs_diff = np.amax(np.abs(treatment_output - results[i]))
abs_diff_tensor = np.abs(treatment_output - results[i])
abs_diff = np.amax(abs_diff_tensor)
if verbose and abs_diff > atol:
print("abs_diff", abs_diff)
print("treatment", treatment_output)
print("baseline", results[i])

count_exceeding = np.sum(abs_diff_tensor > atol)
total_elements = abs_diff_tensor.size
percentage_exceeding = (count_exceeding / total_elements) * 100
max_diff_percentage = max(max_diff_percentage, percentage_exceeding)

max_abs_diff = max(max_abs_diff, abs_diff)
if not np.allclose(results[i].tolist(), treatment_output.tolist(), rtol=rtol, atol=atol):
if case_passed:
Expand All @@ -66,6 +73,7 @@ def compare(baseline_results, treatment_results, verbose, rtol=1e-1, atol=1e-3):
)

print(f"maximum absolute difference={max_abs_diff}")
print(f"maximum percentage of elements that exceeds atol={atol} is {max_diff_percentage:.3f}%")
return max_abs_diff, case_passed


Expand Down
69 changes: 55 additions & 14 deletions onnxruntime/python/tools/transformers/float16.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ def make_value_info_from_tensor(tensor):
"Upsample",
]

# Some operators do not support bfloat16 in CUDA. This is not a full list, just some common operators in transformers.
BF16_OP_BLACK_LIST = ["SkipSimplifiedLayerNormalization", "Attention", "MultiHeadAttention"]

# Some operators has data type fixed as float for some inputs. Key is op_type, value is list of input indices
# Note that DirectML allows float16 gamma and beta in GroupNorm. Use force_fp16_inputs parameter could overwrite this.
Expand All @@ -155,14 +157,19 @@ class InitializerTracker:

def __init__(self, initializer: TensorProto):
self.initializer = initializer
self.bf16_nodes = []
self.fp32_nodes = []
self.fp16_nodes = []

def add_node(self, node: NodeProto, is_node_blocked):
if is_node_blocked:
def add_node(self, node: NodeProto, dtype: int):
if dtype == TensorProto.FLOAT:
self.fp32_nodes.append(node)
else:
elif dtype == TensorProto.BFLOAT16:
self.bf16_nodes.append(node)
elif dtype == TensorProto.FLOAT16:
self.fp16_nodes.append(node)
else:
raise ValueError("Invalid dtype")


def convert_float_to_float16(
Expand Down Expand Up @@ -195,6 +202,9 @@ def convert_float_to_float16(
Default to false, which will convert only the one needed to avoid precision loss.
force_fp16_inputs(Dict[str, List[int]]): Force the conversion of the inputs of some operators to float16, even if
this script's preference it to keep them in float32.
use_bfloat16_as_blocked_nodes_dtype(bool): use bfloat16 as the data type for blocked nodes. Default to False.
Comment thread
tianleiwu marked this conversation as resolved.
Outdated
If the node does not support bfloat16, it will remain in float.

Raises:
ValueError: input type is not ModelProto.

Expand Down Expand Up @@ -334,11 +344,19 @@ def convert_float_to_float16(
for i, input_name in enumerate(n.input):
if input_name in fp32_initializers:
# For Resize/GroupNorm, only the first input can be float16
use_fp32_weight = is_node_blocked or (
i in ALWAYS_FLOAT_INPUTS.get(n.op_type, [])
and i not in force_fp16_inputs_dict.get(n.op_type, [])
)
fp32_initializers[input_name].add_node(n, use_fp32_weight)
if i in ALWAYS_FLOAT_INPUTS.get(n.op_type, []) and i not in force_fp16_inputs_dict.get(
n.op_type, []
):
dtype = TensorProto.FLOAT
elif is_node_blocked:
dtype = (
TensorProto.BFLOAT16
if (use_bfloat16_as_blocked_nodes_dtype and n.op_type not in BF16_OP_BLACK_LIST)
Comment thread
tianleiwu marked this conversation as resolved.
Outdated
else TensorProto.FLOAT
)
else:
dtype = TensorProto.FLOAT16
fp32_initializers[input_name].add_node(n, dtype)

if is_node_blocked:
node_list.append(n)
Expand Down Expand Up @@ -405,15 +423,21 @@ def convert_float_to_float16(

queue = next_level

initializers_to_be_casted_to_bf16: Dict[str, TensorProto] = {}
for value in fp32_initializers.values():
# By default, to avoid precision loss, do not convert an initializer to fp16 when it is used only by fp32 nodes.
if force_fp16_initializers or value.fp16_nodes:
value.initializer = convert_tensor_float_to_float16(value.initializer, min_positive_val, max_finite_val)
value_info_list.append(make_value_info_from_tensor(value.initializer))
if value.fp32_nodes and not force_fp16_initializers:
if (value.fp32_nodes or value.bf16_nodes) and not force_fp16_initializers:
logger.info(
f"initializer is used by both fp32 and fp16 nodes. Consider add these nodes to block list:{value.fp16_nodes}"
f"initializer is used by both fp32/bf16 and fp16 nodes. Consider add these nodes to block list:{value.fp16_nodes}"
)
elif value.bf16_nodes:
# If float initializer is only used by bfloat16 nodes, need to convert it to bfloat16.
# However, numpy does not support bfloat16, so we will add a Cast node to conver it later.
initializers_to_be_casted_to_bf16[value.initializer.name] = value.initializer
continue

# Some operators have data type fixed as float for some input. Add a float16 to float cast for those inputs.
for node in mixed_float_type_node_list:
Expand All @@ -436,14 +460,16 @@ def convert_float_to_float16(
node.input[i] = output_name
break

accuracy_type = TensorProto.BFLOAT16 if use_bfloat16_as_blocked_nodes_dtype else TensorProto.FLOAT
# process the nodes in block list that doesn't support tensor(float16)
for node in node_list:
# if input's name is in the value_info_list meaning input is tensor(float16) type,
# insert a float16 to float Cast node before the node,
# insert a float16 to target type (float or bfloat16) Cast node before the node,
# change current node's input name and create new value_info for the new name
use_bf16 = use_bfloat16_as_blocked_nodes_dtype and node.op_type not in BF16_OP_BLACK_LIST
accuracy_type = TensorProto.BFLOAT16 if use_bf16 else TensorProto.FLOAT
for i in range(len(node.input)):
input_name = node.input[i]
is_input_converted = False
for value_info in value_info_list:
if input_name == value_info.name:
# create new value_info for current node's new input name
Expand All @@ -458,9 +484,24 @@ def convert_float_to_float16(
model.graph.node.extend(new_node)
# change current node's input name
node.input[i] = output_name
is_input_converted = True
break
# if output's name is in the value_info_list meaning output is tensor(float16) type, insert a float to
# float16 Cast node after the node, change current node's output name and create new value_info for the new name

# For bfloat16 nodes, we need to convert float initializers to bfloat16.
if (not is_input_converted) and use_bf16 and (input_name in initializers_to_be_casted_to_bf16):
output_name = node.name + "_input_cast_" + str(i)
value_info = helper.make_tensor_value_info(
name=output_name, elem_type=accuracy_type, shape=initializers_to_be_casted_to_bf16[input_name].dims
)
new_value_info = model.graph.value_info.add()
new_value_info.CopyFrom(value_info)
node_name = node.name + "_input_cast" + str(i)
new_node = [helper.make_node("Cast", [input_name], [output_name], to=accuracy_type, name=node_name)]
Comment thread
tianleiwu marked this conversation as resolved.
Outdated
model.graph.node.extend(new_node)
node.input[i] = output_name

# if output's name is in the value_info_list meaning output is tensor(float16) type, insert a Cast (to float16)
# node after it, change current node's output name and create new value_info for the new name.
for i in range(len(node.output)):
output = node.output[i]
for value_info in value_info_list:
Expand Down
39 changes: 0 additions & 39 deletions onnxruntime/python/tools/transformers/fusion_attention.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,45 +355,6 @@ def split_kv(self, present_k_name: str, present_v_name: str, kv_node: str):
self.node_name_to_graph_name[gather_k_name] = self.this_graph_name
self.node_name_to_graph_name[gather_v_name] = self.this_graph_name

def transpose_kv(self, past_k: str, past_v: str):
"""Transpose past_k and past_v from (B,N,P,H) to (B,P,N,H)

Args:
past_k (str): name of past K value of shape (B,N,P,H)
past_v (str): name of past V value of shape (B,N,P,H)

Returns:
past_k_transpose (str): name of past K value of shape (B,P,N,H)
past_v_transpose (str): name of past V value of shape (B,P,N,H)
"""
past_k_transpose = (past_k + "_transposed").replace(".", "_")
past_v_transpose = (past_v + "_transposed").replace(".", "_")
transpose_k_name = self.model.create_node_name("Transpose")
transpose_v_name = self.model.create_node_name("Transpose")

transpose_k = helper.make_node(
"Transpose",
inputs=[past_k],
outputs=[past_k_transpose],
name=transpose_k_name,
perm=[0, 2, 1, 3],
)
transpose_v = helper.make_node(
"Transpose",
inputs=[past_v],
outputs=[past_v_transpose],
name=transpose_v_name,
perm=[0, 2, 1, 3],
)

# Add reshape nodes to graph
self.nodes_to_add.append(transpose_k)
self.nodes_to_add.append(transpose_v)
self.node_name_to_graph_name[transpose_k_name] = self.this_graph_name
self.node_name_to_graph_name[transpose_v_name] = self.this_graph_name

return past_k_transpose, past_v_transpose

def create_combined_qkv_bias(
self,
q_add: NodeProto,
Expand Down
Loading