352 lines
12 KiB
Python
352 lines
12 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
# pylint: disable=invalid-name,missing-function-docstring,unused-variable
|
|
"""Intrinsics for tensorization on Apple GPU."""
|
|
|
|
from typing import Literal
|
|
|
|
from tvm.script import tirx as T
|
|
from tvm.tirx import Buffer, Expr, PrimFunc, TensorIntrin
|
|
|
|
######## simdgroup matrix intrinsics ########
|
|
|
|
|
|
def get_simdgroup_index(buffer: Buffer, stride: Expr, col: int, row: int):
|
|
"""Compute simdgroup index using elem_offset of the buffer"""
|
|
|
|
# NOTE: Need further check the usage between `col`` and `row`
|
|
# Currently, Metal only supports 8x8, which means the values of `col` and `row` are the same
|
|
frag_index_m = buffer.elem_offset // stride // col
|
|
frag_index_n = buffer.elem_offset % stride // row
|
|
|
|
num_fragments_per_row = stride // row
|
|
return frag_index_m * num_fragments_per_row + frag_index_n
|
|
|
|
|
|
def get_make_filled_simdgroup_matrix_intrin(
|
|
dtype: str, col: int = 8, row: int = 8
|
|
) -> tuple[PrimFunc, PrimFunc]:
|
|
@T.prim_func(s_tir=True)
|
|
def desc(a: T.handle) -> None:
|
|
A = T.match_buffer(a, (col, row), dtype, scope="metal.simdgroup", offset_factor=1)
|
|
with T.sblock("root"):
|
|
T.reads()
|
|
T.writes(A[0:col, 0:row])
|
|
for i, j in T.grid(col, row):
|
|
with T.sblock("init"):
|
|
vi, vj = T.axis.remap("SS", [i, j])
|
|
A[vi, vj] = T.float32(0)
|
|
|
|
@T.prim_func(s_tir=True)
|
|
def impl(a: T.handle) -> None:
|
|
d0, d1 = T.int32(), T.int32()
|
|
A = T.match_buffer(
|
|
a, (col, row), dtype, scope="metal.simdgroup", strides=[d1, d0], offset_factor=1
|
|
)
|
|
with T.sblock("root"):
|
|
T.reads()
|
|
T.writes(A[0:col, 0:row])
|
|
T.metal.make_filled_simdgroup_matrix(
|
|
A.data,
|
|
index=get_simdgroup_index(A, d1, col, row),
|
|
value=T.float32(0),
|
|
col=col,
|
|
row=row,
|
|
)
|
|
|
|
return desc, impl
|
|
|
|
|
|
def get_simdgroup_load_intrin(
|
|
dtype: str,
|
|
scope: Literal["global", "shared"],
|
|
col: int = 8,
|
|
row: int = 8,
|
|
transpose_matrix: bool = False,
|
|
) -> tuple[PrimFunc, PrimFunc]:
|
|
align = col * row
|
|
|
|
@T.prim_func(s_tir=True)
|
|
def desc(a: T.handle, c: T.handle) -> None:
|
|
A = T.match_buffer(a, (col, row), dtype, align=align, scope=scope, offset_factor=1)
|
|
C = T.match_buffer(
|
|
c, (col, row), dtype, align=align, scope="metal.simdgroup", offset_factor=1
|
|
)
|
|
with T.sblock("root"):
|
|
T.reads(A[0:col, 0:row])
|
|
T.writes(C[0:col, 0:row])
|
|
for i, j in T.grid(col, row):
|
|
with T.sblock("load"):
|
|
vii, vjj = T.axis.remap("SS", [i, j])
|
|
if transpose_matrix:
|
|
# C[vii, vjj] = A[vjj, vii]
|
|
C[vjj, vii] = A[vii, vjj]
|
|
else:
|
|
C[vii, vjj] = A[vii, vjj]
|
|
|
|
@T.prim_func(s_tir=True)
|
|
def impl(a: T.handle, c: T.handle) -> None:
|
|
s0, s1, d0, d1 = T.int32(), T.int32(), T.int32(), T.int32()
|
|
A = T.match_buffer(
|
|
a,
|
|
(col, row),
|
|
dtype,
|
|
align=align,
|
|
scope=scope,
|
|
strides=[s1, s0],
|
|
offset_factor=1,
|
|
)
|
|
C = T.match_buffer(
|
|
c,
|
|
(col, row),
|
|
dtype,
|
|
align=align,
|
|
scope="metal.simdgroup",
|
|
strides=[d1, d0],
|
|
offset_factor=1,
|
|
)
|
|
with T.sblock("root"):
|
|
T.reads(A[0:col, 0:row])
|
|
T.writes(C[0:col, 0:row])
|
|
T.metal.simdgroup_load(
|
|
C.data,
|
|
index=get_simdgroup_index(C, d1, col, row),
|
|
ptr=A.access_ptr("r", ptr_type=dtype),
|
|
stride=s1,
|
|
col=col,
|
|
row=row,
|
|
transpose_matrix=transpose_matrix,
|
|
)
|
|
|
|
return desc, impl
|
|
|
|
|
|
def get_simdgroup_store_intrin(
|
|
dtype: str,
|
|
scope: Literal["global", "shared"],
|
|
col: int = 8,
|
|
row: int = 8,
|
|
transpose_matrix: bool = False,
|
|
) -> tuple[PrimFunc, PrimFunc]:
|
|
align = col * row
|
|
|
|
@T.prim_func(s_tir=True)
|
|
def desc(a: T.handle, c: T.handle) -> None:
|
|
A = T.match_buffer(
|
|
a, (col, row), dtype, align=align, scope="metal.simdgroup", offset_factor=1
|
|
)
|
|
C = T.match_buffer(c, (col, row), dtype, align=align, scope=scope, offset_factor=1)
|
|
with T.sblock("root"):
|
|
T.reads(A[0:col, 0:row])
|
|
T.writes(C[0:col, 0:row])
|
|
for i, j in T.grid(col, row):
|
|
with T.sblock("store"):
|
|
vii, vjj = T.axis.remap("SS", [i, j])
|
|
if transpose_matrix:
|
|
C[vjj, vii] = A[vii, vjj]
|
|
else:
|
|
C[vii, vjj] = A[vii, vjj]
|
|
|
|
@T.prim_func(s_tir=True)
|
|
def impl(a: T.handle, c: T.handle) -> None:
|
|
s0, s1, d0, d1 = T.int32(), T.int32(), T.int32(), T.int32()
|
|
A = T.match_buffer(
|
|
a,
|
|
(col, row),
|
|
dtype,
|
|
align=align,
|
|
scope="metal.simdgroup",
|
|
strides=[s1, s0],
|
|
offset_factor=1,
|
|
)
|
|
C = T.match_buffer(
|
|
c, (col, row), dtype, align=align, scope=scope, strides=[d1, d0], offset_factor=1
|
|
)
|
|
with T.sblock("root"):
|
|
T.reads(A[0:col, 0:row])
|
|
T.writes(C[0:col, 0:row])
|
|
T.metal.simdgroup_store(
|
|
A.data,
|
|
index=get_simdgroup_index(A, s1, col, row),
|
|
ptr=C.access_ptr("w", ptr_type=dtype),
|
|
stride=d1,
|
|
col=col,
|
|
row=row,
|
|
transpose_matrix=transpose_matrix,
|
|
)
|
|
|
|
return desc, impl
|
|
|
|
|
|
def get_simdgroup_multiply_accumulate_intrin(
|
|
m_dim: int, n_dim: int, k_dim: int, dtype: str
|
|
) -> tuple[PrimFunc, PrimFunc]:
|
|
@T.prim_func(s_tir=True)
|
|
def desc(a: T.handle, b: T.handle, c: T.handle) -> None:
|
|
A = T.match_buffer(a, (m_dim, k_dim), dtype, scope="metal.simdgroup", offset_factor=1)
|
|
B = T.match_buffer(b, (k_dim, n_dim), dtype, scope="metal.simdgroup", offset_factor=1)
|
|
C = T.match_buffer(c, (m_dim, n_dim), dtype, scope="metal.simdgroup", offset_factor=1)
|
|
with T.sblock("root"):
|
|
T.reads(C[0:m_dim, 0:n_dim], A[0:m_dim, 0:k_dim], B[0:k_dim, 0:n_dim])
|
|
T.writes(C[0:m_dim, 0:n_dim])
|
|
for i, j, k in T.grid(m_dim, n_dim, k_dim):
|
|
with T.sblock(""):
|
|
vii, vjj, vkk = T.axis.remap("SSR", [i, j, k])
|
|
C[vii, vjj] += A[vii, vkk] * B[vkk, vjj]
|
|
|
|
@T.prim_func(s_tir=True)
|
|
def impl(a: T.handle, b: T.handle, c: T.handle) -> None:
|
|
a0, a1, b0, b1, c0, c1 = T.int32(), T.int32(), T.int32(), T.int32(), T.int32(), T.int32()
|
|
A = T.match_buffer(
|
|
a, (m_dim, k_dim), dtype, scope="metal.simdgroup", strides=[a1, a0], offset_factor=1
|
|
)
|
|
B = T.match_buffer(
|
|
b, (k_dim, n_dim), dtype, scope="metal.simdgroup", strides=[b1, b0], offset_factor=1
|
|
)
|
|
C = T.match_buffer(
|
|
c, (m_dim, n_dim), dtype, scope="metal.simdgroup", strides=[c1, c0], offset_factor=1
|
|
)
|
|
with T.sblock("root"):
|
|
T.reads(C[0:m_dim, 0:n_dim], A[0:m_dim, 0:k_dim], B[0:k_dim, 0:n_dim])
|
|
T.writes(C[0:m_dim, 0:n_dim])
|
|
T.metal.simdgroup_multiply_accumulate(
|
|
C.data,
|
|
get_simdgroup_index(C, c1, m_dim, n_dim),
|
|
A.data,
|
|
get_simdgroup_index(A, a1, m_dim, k_dim),
|
|
B.data,
|
|
get_simdgroup_index(B, b1, k_dim, n_dim),
|
|
C.data,
|
|
get_simdgroup_index(C, c1, m_dim, n_dim),
|
|
)
|
|
|
|
return desc, impl
|
|
|
|
|
|
# Make filled simdgroup matrix intrinsics
|
|
|
|
SIMDGROUP_MAKE_FILLED_8x8x8_f16_INTRIN = "simdgroup_make_filled_8x8x8_f16"
|
|
TensorIntrin.register(
|
|
SIMDGROUP_MAKE_FILLED_8x8x8_f16_INTRIN,
|
|
*get_make_filled_simdgroup_matrix_intrin("float16", 8, 8),
|
|
)
|
|
|
|
SIMDGROUP_FILLED_8x8x8_f32_INTRIN = "simdgroup_fill_8x8x8_f32"
|
|
TensorIntrin.register(
|
|
SIMDGROUP_FILLED_8x8x8_f32_INTRIN, *get_make_filled_simdgroup_matrix_intrin("float32", 8, 8)
|
|
)
|
|
|
|
SIMDGROUP_FILLED_8x8x8_bf16_INTRIN = "simdgroup_fill_8x8x8_bf16"
|
|
TensorIntrin.register(
|
|
SIMDGROUP_FILLED_8x8x8_bf16_INTRIN, *get_make_filled_simdgroup_matrix_intrin("bfloat16", 8, 8)
|
|
)
|
|
|
|
# Load intrinsics
|
|
|
|
SIMDGROUP_LOAD_8x8x8_f16_SHARED_INTRIN = "simdgroup_load_8x8x8_f16_shared"
|
|
TensorIntrin.register(
|
|
SIMDGROUP_LOAD_8x8x8_f16_SHARED_INTRIN,
|
|
*get_simdgroup_load_intrin("float16", "shared", 8, 8, False),
|
|
)
|
|
|
|
SIMDGROUP_LOAD_8x8x8_f16_SHARED_TRANS_INTRIN = "simdgroup_load_8x8x8_f16_shared_trans"
|
|
TensorIntrin.register(
|
|
SIMDGROUP_LOAD_8x8x8_f16_SHARED_TRANS_INTRIN,
|
|
*get_simdgroup_load_intrin("float16", "shared", 8, 8, True),
|
|
)
|
|
|
|
# Store intrinsics
|
|
|
|
SIMDGROUP_STORE_8x8x8_f16_GLOBAL_INTRIN = "simdgroup_store_8x8x8_f16_global"
|
|
TensorIntrin.register(
|
|
SIMDGROUP_STORE_8x8x8_f16_GLOBAL_INTRIN,
|
|
*get_simdgroup_store_intrin("float16", "global", 8, 8, False),
|
|
)
|
|
|
|
SIMDGROUP_STORE_8x8x8_f16_SHARED_INTRIN = "simdgroup_store_8x8x8_f16_shared"
|
|
TensorIntrin.register(
|
|
SIMDGROUP_STORE_8x8x8_f16_SHARED_INTRIN,
|
|
*get_simdgroup_store_intrin("float16", "shared", 8, 8, False),
|
|
)
|
|
# Multiply accumulate intrinsics
|
|
|
|
SIMDGROUP_MULTI_ACC_8x8x8_f16_INTRIN = "simdgroup_multiply_accumulate_8x8x8_f16"
|
|
TensorIntrin.register(
|
|
SIMDGROUP_MULTI_ACC_8x8x8_f16_INTRIN,
|
|
*get_simdgroup_multiply_accumulate_intrin(8, 8, 8, "float16"),
|
|
)
|
|
|
|
|
|
def get_simdgroup_intrin_group(
|
|
load_scope: Literal["shared"],
|
|
store_scope: Literal["global", "shared"],
|
|
dtype: str,
|
|
trans_a: bool = False,
|
|
trans_b: bool = False,
|
|
) -> dict[str, str]:
|
|
"""Get a group of intrinsics for tensorization on Apple GPU.
|
|
|
|
Parameters
|
|
----------
|
|
load_scope : Literal["shared"]
|
|
The memory scope of the input buffer.
|
|
|
|
store_scope : Literal["global", "shared"]
|
|
The memory scope of the result buffer.
|
|
|
|
dtype : str
|
|
The data type of the input and output buffers.
|
|
|
|
trans_a : bool
|
|
Whether the input matrix A is transposed.
|
|
|
|
trans_b : bool
|
|
Whether the input matrix B is transposed.
|
|
|
|
Returns
|
|
-------
|
|
ret : Dict[str, str]
|
|
A group of tensor intrinsics.
|
|
"""
|
|
assert load_scope in ["shared"]
|
|
assert store_scope in ["global", "shared"]
|
|
assert dtype in ["float16", "bfloat16", "float32"]
|
|
|
|
shape = "8x8x8"
|
|
dtype = "f16" if dtype == "float16" else "bf16" if dtype == "bfloat16" else "f32"
|
|
trans_a = "_trans" if trans_a else ""
|
|
trans_b = "_trans" if trans_b else ""
|
|
|
|
# e.g. simdgroup_load_8x8x8_f16_shared
|
|
load_a_intrin = f"simdgroup_load_{shape}_{dtype}_{load_scope}{trans_a}"
|
|
# e.g. simdgroup_load_8x8x8_f16_shared_trans
|
|
load_b_intrin = f"simdgroup_load_{shape}_{dtype}_{load_scope}{trans_b}"
|
|
# e.g. simdgroup_multiply_accumulate_8x8x8_f16
|
|
compute_intrin = f"simdgroup_multiply_accumulate_{shape}_{dtype}"
|
|
# e.g. simdgroup_make_filled_8x8x8_f16
|
|
init_intrin = f"simdgroup_make_filled_{shape}_{dtype}"
|
|
# e.g. simdgroup_store_8x8x8_f16_global
|
|
store_intrin = f"simdgroup_store_{shape}_{dtype}_{store_scope}"
|
|
|
|
return {
|
|
"init": init_intrin,
|
|
"load_a": load_a_intrin,
|
|
"load_b": load_b_intrin,
|
|
"compute": compute_intrin,
|
|
"store": store_intrin,
|
|
}
|