Files
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

478 lines
17 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
"""Intrinsics for AMDGPU tensorization."""
from tvm.runtime import convert
from tvm.script import tirx as T
from tvm.tirx.expr import Cast, IntImm
from .. import TensorIntrin
from .dot_product_common import get_dp4a_intrin
lift = convert
@T.prim_func(s_tir=True)
def sdot4(
A: T.Buffer((4,), "int8", offset_factor=1, align=4, scope="shared"),
B: T.Buffer((4,), "int8", offset_factor=1, align=4, scope="shared"),
C: T.Buffer((1,), "int32", offset_factor=1, align=4, scope="local"),
) -> None:
with T.sblock("root"):
T.reads(C[0], A[0:4], B[0:4])
T.writes(C[0])
C[0] += T.call_llvm_pure_intrin(
T.llvm_lookup_intrinsic_id("llvm.amdgcn.sdot4"),
T.reinterpret(A.vload([0], "int8x4"), dtype="int32"),
T.reinterpret(B.vload([0], "int8x4"), dtype="int32"),
T.int32(0),
T.bool(1),
dtype="int32",
)
AMDGPU_SDOT4_INTRIN = "sdot4"
dp4a_desc, _ = get_dp4a_intrin("int8", "int8", "int32")
TensorIntrin.register(AMDGPU_SDOT4_INTRIN, dp4a_desc, sdot4)
WARP_SIZE = 64
M_DIM = 16
N_DIM = 16
def shared_16x4_to_local_64x1_layout_A(i, j):
thread_id = j * 16 + i
return thread_id, convert(0)
def thread_id_shared_access_64x1_to_16x4_layout_A(thread_id, local_id):
i = thread_id % 16
j = thread_id // 16 + local_id
return i, j
def shared_4x16_to_local_64x1_layout_B(i, j):
thread_id = i * 16 + j
return thread_id, convert(0)
def thread_id_shared_access_64x1_to_4x16_layout_B(thread_id, local_id):
i = thread_id // 16
j = thread_id % 16 + local_id
return i, j
def shared_16x16_to_local_64x4_layout_C(i, j):
thread_id = j + (i // 4) * 16
local = i % 4
return thread_id, local
def thread_id_shared_access_64x4_to_16x16_layout_A(thread_id, local_id):
i = thread_id % 16
j = (thread_id // 16) * 4 + local_id
return i, j
def shared_16x16_to_local_64x4_layout_A(i, j):
thread_id = i + 16 * (j // 4)
local = j % 4
return thread_id, local
def thread_id_shared_access_64x4_to_16x16_layout_B(thread_id, local_id):
i = local_id + (thread_id // 16) * 4
j = thread_id % 16
return i, j
def shared_16x16_to_local_64x4_layout_B(i, j):
thread_id = j + (i // 4) * 16
local = i % 4
return thread_id, local
def thread_id_shared_access_64x4_to_16x16_layout_C(thread_id, local_id):
i = local_id + (thread_id // 16) * 4
j = thread_id % 16
return i, j
def get_mma_fill_intrin(dtype, local_size):
zero = IntImm("int32", 0).astype(dtype)
# Assume M = N = 16
index_map = shared_16x16_to_local_64x4_layout_C
@T.prim_func(s_tir=True)
def mma_fill_desc(a: T.handle) -> None:
C_warp = T.match_buffer(a, [WARP_SIZE, local_size], dtype=dtype, scope="warp")
with T.sblock("root"):
T.reads()
T.writes(C_warp[0:WARP_SIZE, 0:local_size])
for i0, i1 in T.grid(M_DIM, N_DIM):
with T.sblock("C_warp"):
i, j = T.axis.remap("SS", [i0, i1])
thread_id, local_id = T.meta_var(index_map(i, j))
T.reads()
T.writes(C_warp[thread_id, local_id])
C_warp[thread_id, local_id] = zero
@T.prim_func(s_tir=True)
def mma_fill_impl(a: T.handle) -> None:
C_warp = T.match_buffer(
a, [WARP_SIZE, local_size], dtype=dtype, scope="warp", offset_factor=1
)
with T.sblock("root"):
T.reads()
T.writes(C_warp[0:WARP_SIZE, 0:local_size])
tx = T.env_thread("threadIdx.x")
T.launch_thread(tx, WARP_SIZE)
for local_id in T.serial(0, local_size):
C_warp[tx, local_id] = zero
return mma_fill_desc, mma_fill_impl
def get_mfma_load_intrin(
k_dim=4,
dtype="float32",
scope="shared",
is_b=False,
transposed=False,
):
local_size = (M_DIM * k_dim) // WARP_SIZE if not is_b else (N_DIM * k_dim) // WARP_SIZE
memory_shape = (M_DIM, k_dim)
if is_b:
memory_shape = (N_DIM, k_dim) if transposed else (k_dim, N_DIM)
row_dim, col_dim = memory_shape
if k_dim == 4:
index_map = shared_16x4_to_local_64x1_layout_A
reverse_index_map = thread_id_shared_access_64x1_to_16x4_layout_A
if is_b:
index_map = (
shared_16x4_to_local_64x1_layout_A
if transposed
else shared_4x16_to_local_64x1_layout_B
)
reverse_index_map = (
thread_id_shared_access_64x1_to_16x4_layout_A
if transposed
else thread_id_shared_access_64x1_to_4x16_layout_B
)
elif k_dim == 16:
index_map = shared_16x16_to_local_64x4_layout_A
reverse_index_map = thread_id_shared_access_64x4_to_16x16_layout_A
if is_b:
index_map = (
shared_16x16_to_local_64x4_layout_A
if transposed
else shared_16x16_to_local_64x4_layout_B
)
reverse_index_map = (
thread_id_shared_access_64x4_to_16x16_layout_A
if transposed
else thread_id_shared_access_64x4_to_16x16_layout_B
)
else:
raise ValueError("k_dim must be 4 or 16 currently")
@T.prim_func(s_tir=True)
def mfma_load_desc(reg_handle: T.handle, memory_handle: T.handle) -> None:
memory = T.match_buffer(
memory_handle,
memory_shape,
dtype,
offset_factor=1,
scope=scope,
)
reg = T.match_buffer(
reg_handle, (WARP_SIZE, local_size), dtype, offset_factor=1, scope="warp"
)
with T.sblock("root"):
T.reads(memory[0:row_dim, 0:col_dim])
T.writes(reg[0:WARP_SIZE, 0:local_size])
for ax0, ax1 in T.grid(row_dim, col_dim):
with T.sblock("memory_reg"):
v0, v1 = T.axis.remap("SS", [ax0, ax1])
T.reads(memory[v0, v1])
thread_id, local_id = T.meta_var(index_map(v0, v1))
T.writes(reg[thread_id, local_id])
reg[thread_id, local_id] = memory[v0, v1]
@T.prim_func(s_tir=True)
def mfma_load_impl(reg_handle: T.handle, memory_handle: T.handle) -> None:
s0 = T.int32()
s1 = T.int32()
memory = T.match_buffer(
memory_handle,
memory_shape,
dtype,
align=64,
offset_factor=1,
scope=scope,
strides=[s0, s1],
)
reg = T.match_buffer(
reg_handle, (WARP_SIZE, local_size), dtype, align=64, offset_factor=1, scope="warp"
)
with T.sblock("root"):
T.reads(memory[0:row_dim, 0:col_dim])
T.writes(reg[0:WARP_SIZE, 0:local_size])
tx = T.env_thread("threadIdx.x")
for local_id in T.serial(0, local_size):
row, col = T.meta_var(reverse_index_map(tx, local_id))
T.launch_thread(tx, WARP_SIZE)
reg[tx, local_id] = memory[row, col]
return mfma_load_desc, mfma_load_impl
def get_mfma_intrin(k_dim, in_dtype="float32", out_dtype="float32", b_transposed=False):
local_size = (M_DIM * k_dim) // WARP_SIZE
local_size_out = (M_DIM * N_DIM) // WARP_SIZE
if k_dim == 4:
index_map_A = shared_16x4_to_local_64x1_layout_A
index_map_B = shared_4x16_to_local_64x1_layout_B
index_map_C = shared_16x16_to_local_64x4_layout_C
elif k_dim == 16:
index_map_A = shared_16x16_to_local_64x4_layout_A
index_map_B = shared_16x16_to_local_64x4_layout_B
index_map_C = shared_16x16_to_local_64x4_layout_C
else:
raise ValueError("k_dim must be 4 or 16 currently")
out_dtype_abbrv = {"float16": "f16", "float32": "f32", "int8": "i8", "int32": "i32"}[out_dtype]
in_dtype_abbrv = {"float16": "f16", "float32": "f32", "int8": "i8", "int32": "i32"}[in_dtype]
mfma_intrin = f"llvm.amdgcn.mfma.{out_dtype_abbrv}.{M_DIM}x{N_DIM}x{k_dim}{in_dtype_abbrv}"
def maybe_cast(v):
if out_dtype != in_dtype:
return Cast(out_dtype, v)
return v
def maybe_swap(i, j):
if b_transposed:
return j, i
return i, j
@T.prim_func(s_tir=True)
def mfma_sync_desc(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (WARP_SIZE, local_size), in_dtype, offset_factor=1, scope="warp")
B = T.match_buffer(b, (WARP_SIZE, local_size), in_dtype, offset_factor=1, scope="warp")
C = T.match_buffer(c, (WARP_SIZE, local_size_out), out_dtype, offset_factor=1, scope="warp")
with T.sblock("root"):
T.reads(
C[0:WARP_SIZE, 0:local_size_out],
A[0:WARP_SIZE, 0:local_size],
B[0:WARP_SIZE, 0:local_size],
)
T.writes(C[0:WARP_SIZE, 0:local_size_out])
for i, j, k in T.grid(M_DIM, N_DIM, k_dim):
with T.sblock("C"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
b_row_ind, b_col_ind = T.meta_var(maybe_swap(vk, vj))
thread_id_C, local_id_C = T.meta_var(index_map_C(vi, vj))
thread_id_A, local_id_A = T.meta_var(index_map_A(vi, vk))
thread_id_B, local_id_B = T.meta_var(index_map_B(b_row_ind, b_col_ind))
T.reads(
C[thread_id_C, local_id_C],
A[thread_id_A, local_id_A],
B[thread_id_B, local_id_B],
)
T.writes(C[thread_id_C, local_id_C])
C[thread_id_C, local_id_C] += maybe_cast(
A[thread_id_A, local_id_A]
) * maybe_cast(B[thread_id_B, local_id_B])
@T.prim_func(s_tir=True)
def mfma_sync_impl_float(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (WARP_SIZE, local_size), in_dtype, offset_factor=1, scope="warp")
B = T.match_buffer(b, (WARP_SIZE, local_size), in_dtype, offset_factor=1, scope="warp")
C = T.match_buffer(c, (WARP_SIZE, local_size_out), out_dtype, offset_factor=1, scope="warp")
with T.sblock("root"):
T.reads(
A[0:WARP_SIZE, 0:local_size],
B[0:WARP_SIZE, 0:local_size],
C[0:WARP_SIZE, 0:local_size_out],
)
T.writes(C[0:WARP_SIZE, 0:local_size_out])
tx = T.env_thread("threadIdx.x")
T.launch_thread(tx, WARP_SIZE)
C[tx, 0:local_size_out] = T.call_llvm_pure_intrin(
T.llvm_lookup_intrinsic_id(mfma_intrin),
A[tx, 0:local_size],
B[tx, 0:local_size],
C[tx, 0:local_size_out],
T.int32(0),
T.int32(0),
T.int32(0),
dtype=f"{out_dtype}x4",
)
@T.prim_func(s_tir=True)
def mfma_sync_impl_integer(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (WARP_SIZE, local_size), in_dtype, offset_factor=1, scope="warp")
B = T.match_buffer(b, (WARP_SIZE, local_size), in_dtype, offset_factor=1, scope="warp")
C = T.match_buffer(c, (WARP_SIZE, local_size_out), out_dtype, offset_factor=1, scope="warp")
with T.sblock("root"):
T.reads(
A[0:WARP_SIZE, 0:local_size],
B[0:WARP_SIZE, 0:local_size],
C[0:WARP_SIZE, 0:local_size_out],
)
T.writes(C[0:WARP_SIZE, 0:local_size_out])
tx = T.env_thread("threadIdx.x")
T.launch_thread(tx, WARP_SIZE)
C[tx, 0:local_size_out] = T.call_llvm_pure_intrin(
T.llvm_lookup_intrinsic_id(mfma_intrin),
T.call_intrin("int32", "tirx.reinterpret", A[tx, 0:local_size]),
T.call_intrin("int32", "tirx.reinterpret", A[tx, 0:local_size]),
C[tx, 0:local_size_out],
T.int32(0),
T.int32(0),
T.int32(0),
dtype=f"{out_dtype}x4",
)
return (
(mfma_sync_desc, mfma_sync_impl_integer)
if in_dtype == "int8"
else (mfma_sync_desc, mfma_sync_impl_float)
)
def get_mfma_store_intrin(local_size=4, dtype="float32", scope="global"):
index_map = shared_16x16_to_local_64x4_layout_C
@T.prim_func(s_tir=True)
def mfma_store_desc(a: T.handle, c: T.handle) -> None:
C_warp = T.match_buffer(a, [WARP_SIZE, local_size], dtype=dtype, scope="warp")
C = T.match_buffer(c, [M_DIM, N_DIM], dtype=dtype, scope=scope)
with T.sblock("root"):
T.reads(C_warp[0:WARP_SIZE, 0:local_size])
T.writes(C[0:M_DIM, 0:N_DIM])
for i0, i1 in T.grid(M_DIM, N_DIM):
with T.sblock("C_warp"):
v0, v1 = T.axis.remap("SS", [i0, i1])
thread_id, local_id = T.meta_var(index_map(v0, v1))
T.reads(C_warp[thread_id, local_id])
T.writes(C[v0, v1])
C[v0, v1] = C_warp[thread_id, local_id]
@T.prim_func(s_tir=True)
def mfma_store_impl(a: T.handle, c: T.handle) -> None:
s0 = T.int32()
s1 = T.int32()
C_warp = T.match_buffer(
a, [WARP_SIZE, local_size], dtype=dtype, scope="warp", offset_factor=1
)
C = T.match_buffer(
c, [M_DIM, N_DIM], dtype=dtype, scope=scope, offset_factor=1, strides=[s0, s1]
)
with T.sblock("root"):
T.reads(C_warp[0:WARP_SIZE, 0:local_size])
T.writes(C[0:M_DIM, 0:N_DIM])
tx = T.env_thread("threadIdx.x")
T.launch_thread(tx, WARP_SIZE)
for i in range(local_size):
C[((tx // 16) * 4) + i, (tx % 16)] = C_warp[tx, i]
return mfma_store_desc, mfma_store_impl
ROCM_MFMA_fill_16x16_f32_INTRIN = "ROCM_mfma_fill_16x16_f32"
TensorIntrin.register(ROCM_MFMA_fill_16x16_f32_INTRIN, *get_mma_fill_intrin("float32", 4))
ROCM_MFMA_fill_16x16_i32_INTRIN = "ROCM_mfma_fill_16x16_i32"
TensorIntrin.register(ROCM_MFMA_fill_16x16_i32_INTRIN, *get_mma_fill_intrin("int", 4))
ROCM_MFMA_LOAD_16x16_A_SHARED_s8_INTRIN = "rocm_mfma_load_16x16_a_shared_s8"
TensorIntrin.register(
ROCM_MFMA_LOAD_16x16_A_SHARED_s8_INTRIN, *get_mfma_load_intrin(16, "int8", "shared")
)
ROCM_MFMA_LOAD_16x16_B_SHARED_s8_INTRIN = "rocm_mfma_load_b_16x16_shared_s8"
TensorIntrin.register(
ROCM_MFMA_LOAD_16x16_B_SHARED_s8_INTRIN, *get_mfma_load_intrin(16, "int8", "shared", is_b=True)
)
ROCM_MFMA_LOAD_16x16_A_SHARED_f16_INTRIN = "rocm_mfma_load_16x16_a_shared_f16"
TensorIntrin.register(
ROCM_MFMA_LOAD_16x16_A_SHARED_f16_INTRIN, *get_mfma_load_intrin(16, "float16", "shared")
)
ROCM_MFMA_LOAD_16x16_B_SHARED_f16_INTRIN = "rocm_mfma_load_b_16x16_shared_f16"
TensorIntrin.register(
ROCM_MFMA_LOAD_16x16_B_SHARED_f16_INTRIN,
*get_mfma_load_intrin(16, "float16", "shared", is_b=True),
)
ROCM_MFMA_LOAD_16x4_A_SHARED_f32_INTRIN = "rocm_mfma_load_16x4_a_shared_f32"
TensorIntrin.register(
ROCM_MFMA_LOAD_16x4_A_SHARED_f32_INTRIN, *get_mfma_load_intrin(4, "float32", "shared")
)
ROCM_MFMA_LOAD_16x4_B_SHARED_f32_INTRIN = "rocm_mfma_load_b_16x4_shared_f32"
TensorIntrin.register(
ROCM_MFMA_LOAD_16x4_B_SHARED_f32_INTRIN,
*get_mfma_load_intrin(4, "float32", "shared", is_b=True),
)
ROCM_MFMA_f32f32f32_INTRIN = "rocm_mfma_f32f32f32"
TensorIntrin.register(ROCM_MFMA_f32f32f32_INTRIN, *get_mfma_intrin(4, "float32", "float32"))
ROCM_MFMA_f16f16f32_INTRIN = "rocm_mfma_f16f16f32"
TensorIntrin.register(ROCM_MFMA_f16f16f32_INTRIN, *get_mfma_intrin(16, "float16", "float32"))
ROCM_MFMA_s8s8s32_INTRIN = "rocm_mfma_s8s8s32"
TensorIntrin.register(ROCM_MFMA_s8s8s32_INTRIN, *get_mfma_intrin(16, "int8", "int32"))
ROCM_MFMA_STORE_16x16_s32_INTRIN = "rocm_mfma_store_16x16_s32"
TensorIntrin.register(
ROCM_MFMA_STORE_16x16_s32_INTRIN, *get_mfma_store_intrin(4, "int32", "global")
)
ROCM_MFMA_STORE_16x16_f32_INTRIN = "rocm_mfma_store_16x16_f32"
TensorIntrin.register(
ROCM_MFMA_STORE_16x16_f32_INTRIN, *get_mfma_store_intrin(4, "float32", "global")
)