Interesting, the rocm dense schedule you mentioned is probably an old one copy-pasted from CUDA, so I don’t think it is supposed to be particularly fast.
Below is the compute DAG, and the final schedule found by Ansor, showing both TIR and equivalent python scheduling commands:
========== Task 1 (workload key: ["eca51cb8a8335304c6e670bdb115a9b7"]) ==========
placeholder = PLACEHOLDER [1, 2048]
placeholder = PLACEHOLDER [1000, 2048]
T_dense(i, j) += (placeholder[i, k]*placeholder[j, k])
placeholder = PLACEHOLDER [1000]
T_add(ax0, ax1) = (T_dense[ax0, ax1] + placeholder[ax1])
Lowered TIR:
primfn(placeholder_3: handle, placeholder_4: handle, placeholder_5: handle, T_add_1: handle) -> ()
attr = {"global_symbol": "main", "tir.noalias": True}
buffers = {T_add: Buffer(T_add_2: Pointer(float32), float32, [1, 1000], []),
placeholder_2: Buffer(placeholder_6: Pointer(float32), float32, [1000], []),
placeholder_1: Buffer(placeholder_7: Pointer(float32), float32, [1000, 2048], []),
placeholder: Buffer(placeholder_8: Pointer(float32), float32, [1, 2048], [])}
buffer_map = {placeholder_3: placeholder, placeholder_4: placeholder_1, placeholder_5: placeholder_2, T_add_1: T_add} {
attr [IterVar(blockIdx.x: int32, (nullptr), "ThreadIndex", "blockIdx.x")] "thread_extent" = 1000;
attr [normal_reduce_temp0: handle] "storage_scope" = "local";
allocate(normal_reduce_temp0, float32, [1]);
attr [reduce_temp0: handle] "storage_scope" = "local";
allocate(reduce_temp0, float32, [1]);
attr [T_dense: Pointer(float32)] "storage_scope" = "shared";
allocate(T_dense, float32, [1]) {
attr [IterVar(threadIdx.x: int32, (nullptr), "ThreadIndex", "threadIdx.x")] "thread_extent" = 1 {
normal_reduce_temp0[0] = 0f32
for (k.outer: int32, 0, 2048) {
normal_reduce_temp0[0] = ((float32*)normal_reduce_temp0[0] + ((float32*)placeholder_8[k.outer]*(float32*)placeholder_7[((blockIdx.x*2048) + k.outer)]))
}
attr [meta[tir.CommReducer][0]] "reduce_scope" = @tir.reinterpret(0u64, dtype=handle);
@tir.tvm_thread_allreduce(1u32, (float32*)normal_reduce_temp0[0], True, reduce_temp0, 0, dtype=handle)
T_dense[0] = (float32*)reduce_temp0[0]
}
attr [IterVar(threadIdx.x_1: int32, (nullptr), "ThreadIndex", "threadIdx.x")] "thread_extent" = 1;
T_add_2[blockIdx.x] = ((float32*)T_dense[0] + (float32*)placeholder_6[blockIdx.x])
}
}
Equivalent python schedule:
T_dense_i, T_dense_j, T_dense_k = tuple(T_dense.op.axis) + tuple(T_dense.op.reduce_axis)
T_add_ax0, T_add_ax1 = tuple(T_add.op.axis) + tuple(T_add.op.reduce_axis)
T_add_ax1_o, T_add_ax1_i = s[T_add].split(T_add_ax1, factor=1)
s[T_add].bind(T_add_ax1_i, te.thread_axis("threadIdx.x"))
T_dense_k_o, T_dense_k_i = s[T_dense].split(T_dense_k, factor=1)
s[T_dense].bind(T_dense_k_i, te.thread_axis("threadIdx.x"))
s[T_dense].compute_at(s[T_add], T_add_ax1_o)
T_add_ax0_ax1_o_fused = s[T_add].fuse(T_add_ax0, T_add_ax1_o)
s[T_add].bind(T_add_ax0_ax1_o_fused, te.thread_axis("blockIdx.x"))
s[T_dense].pragma(T_dense_i, "auto_unroll_max_step", 1024)
s[T_dense].pragma(T_dense_i, "unroll_explicit", True)
I’ve also checked the generated asm, the warp shuffle operation (ds_bpermute_b32) for rocm introduced in https://github.com/apache/tvm/pull/5727 is used in softmax, but not in dense. Are you saying CUDA backend uses warp reduction for dense too, and Ansor is relying on that assumption?
cc @t-vi