blob: 270fc65b37caa5dbf300eb3c47f03288909cdfd2 [file]
# 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.
# ruff: noqa: F401
"""Async software pipeline tests."""
import numpy as np
import tvm
from tvm import tir
from tvm.script import tir as T
from .infrastructure import get_hexagon_target
def compute(comp_type, outer, inner, dtype):
"""Generate compute function."""
if comp_type == "single_input":
@T.prim_func
def a_plus_1_primfunc(
a_buffer: T.Buffer((outer, inner), dtype), out: T.Buffer((outer, inner), dtype)
):
for i in T.serial(outer):
for j in T.serial(inner):
with T.sblock("compute"):
with T.sblock():
out[i, j] = a_buffer[i, j] + T.cast(1, dtype)
return a_plus_1_primfunc
else:
@T.prim_func
def a_plus_b_plus_1_primfunc(
a_buffer: T.Buffer((outer, inner), dtype),
b_buffer: T.Buffer((outer, inner), dtype),
out: T.Buffer((outer, inner), dtype),
):
for i in T.serial(outer):
for j in T.serial(inner):
with T.sblock("compute"):
with T.sblock():
out[i, j] = a_buffer[i, j] + b_buffer[i, j] + T.cast(1, dtype)
return a_plus_b_plus_1_primfunc
class TestAsyncSoftwarePipeline:
"""Async software pipeline test class."""
outer = tvm.testing.parameter(8, 16)
inner = tvm.testing.parameter(64, 128)
dtype = tvm.testing.parameter("uint8", "float16")
scope = tvm.testing.parameter("global", "global.vtcm")
# TODO(Joseph) Turn on "multi_input_diffQ" compute type once we have upstreamed
# changes in the InjectSoftwarePipeline pass to alleviate this restriction:
# 'a_buffer dependency on multiple async stages is not supported'
comp_type = tvm.testing.parameter("single_input", "multi_input_sameQ")
# TODO(Straw) Add back "cache_write" schedule type once we have upstreamed
# buffer dependency analysis in InjectSoftwarePipeline pass
# to insert approprite TIR "wait" attributes for this schedule
sched_type = tvm.testing.parameter("cache_read", "cache_read_write")
@tvm.testing.fixture
def data(self, comp_type, outer, inner, dtype):
out_np = np.random.uniform(low=0, high=128, size=(outer, inner)).astype(dtype)
a_np = np.random.uniform(low=0, high=128, size=(outer, inner)).astype(dtype)
if comp_type == "single_input":
return out_np, a_np
else:
b_np = np.random.uniform(low=0, high=128, size=(outer, inner)).astype(dtype)
return out_np, a_np, b_np
@tvm.testing.fixture
def verify(self, dtype):
def check(out, ref):
if "int" in dtype:
np.testing.assert_equal(out.numpy(), ref)
else:
tvm.testing.assert_allclose(out.numpy(), ref, rtol=1e-3, atol=1e-3)
return check
@tvm.testing.fixture
def reference(self, comp_type):
"""Returns reference data."""
if comp_type == "single_input":
def a_plus_1_ref(a):
return a + 1
return a_plus_1_ref
else:
def a_plus_b_plus_1_ref(a, b):
return a + b + 1
return a_plus_b_plus_1_ref
@tvm.testing.fixture
def schedule(self, comp_type, sched_type, outer, inner, dtype, scope):
"""Generate schedule."""
sch = tvm.s_tir.Schedule(compute(comp_type, outer, inner, dtype))
compute_block = sch.get_sblock("compute")
i, _ = sch.get_loops(compute_block)
if "read" in sched_type:
cache_read_a = sch.cache_read(compute_block, 0, scope)
sch.compute_at(cache_read_a, i)
if "multi_input" in comp_type:
cache_read_b = sch.cache_read(compute_block, 1, scope)
sch.compute_at(cache_read_b, i)
if "write" in sched_type:
cache_write_out = sch.cache_write(compute_block, 0, scope)
sch.reverse_compute_at(cache_write_out, i)
if "read" in sched_type and "write" in sched_type:
if comp_type == "single_input":
sch.annotate(i, "software_pipeline_stage", [0, 1, 2])
sch.annotate(i, "software_pipeline_order", [0, 1, 2])
sch.annotate(i, "software_pipeline_async_stages", [0, 2])
elif comp_type == "multi_input_sameQ":
sch.annotate(i, "software_pipeline_stage", [0, 0, 1, 2])
sch.annotate(i, "software_pipeline_order", [0, 1, 2, 3])
sch.annotate(i, "software_pipeline_async_stages", [0, 2])
elif comp_type == "multi_input_diffQ":
sch.annotate(i, "software_pipeline_stage", [0, 1, 2, 3])
sch.annotate(i, "software_pipeline_order", [0, 1, 2, 3])
sch.annotate(i, "software_pipeline_async_stages", [0, 1, 2])
elif "read" in sched_type:
if comp_type == "single_input":
sch.annotate(i, "software_pipeline_stage", [0, 1])
sch.annotate(i, "software_pipeline_order", [0, 1])
sch.annotate(i, "software_pipeline_async_stages", [0])
elif comp_type == "multi_input_sameQ":
sch.annotate(i, "software_pipeline_stage", [0, 0, 1])
sch.annotate(i, "software_pipeline_order", [0, 1, 2])
sch.annotate(i, "software_pipeline_async_stages", [0])
elif comp_type == "multi_input_diffQ":
sch.annotate(i, "software_pipeline_stage", [0, 1, 2])
sch.annotate(i, "software_pipeline_order", [0, 1, 2])
sch.annotate(i, "software_pipeline_async_stages", [0, 1])
elif "write" in sched_type:
sch.annotate(i, "software_pipeline_stage", [0, 1])
sch.annotate(i, "software_pipeline_order", [0, 1])
sch.annotate(i, "software_pipeline_async_stages", [1])
return sch
@tvm.testing.requires_hexagon
def test_async_software_pipeline(
self, hexagon_launcher, comp_type, data, reference, schedule, verify
):
"""Async software pipeline test."""
out_np = data[0]
a_np = data[1]
if comp_type == "single_input":
ref = reference(a_np)
else:
b_np = data[2]
ref = reference(a_np, b_np)
with tvm.transform.PassContext(
config={
"tir.use_async_copy": 1,
"tir.experimental_dma_bypass_cache": 1,
}
):
func = tvm.compile(schedule.mod["main"], target=get_hexagon_target("v68"))
with hexagon_launcher.create_session() as hexagon_session:
dev = hexagon_session.device
mod = hexagon_session.load_module(func)
out = tvm.runtime.tensor(out_np, device=dev)
a = tvm.runtime.tensor(a_np, device=dev)
if comp_type == "single_input":
mod(a, out)
else:
b = tvm.runtime.tensor(b_np, device=dev)
mod(a, b, out)
verify(out, ref)
if __name__ == "__main__":
tvm.testing.main()