199 lines
6.2 KiB
Python
199 lines
6.2 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.
|
|
"""Tests for T.cuda.warp_reduce / warp_sum / warp_max / warp_min intrinsics."""
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
import tvm
|
|
from tvm.script import tirx as T
|
|
from tvm.testing import env
|
|
|
|
TARGET = tvm.target.Target("cuda")
|
|
|
|
|
|
def _build_and_run(func, n=32):
|
|
mod = tvm.IRModule({"main": func})
|
|
mod = tvm.compile(mod, target=TARGET, tir_pipeline="tirx")
|
|
out_np = np.zeros(n, dtype="float32")
|
|
|
|
def run_and_check():
|
|
dev = tvm.cuda(0)
|
|
out = tvm.runtime.tensor(out_np, device=dev)
|
|
mod(out)
|
|
return out.numpy()
|
|
|
|
return tvm.testing.run_with_gpu_lock(run_and_check), mod
|
|
|
|
|
|
@pytest.mark.gpu
|
|
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
|
|
def test_warp_sum_full():
|
|
"""Full warp sum (width=32): each lane gets the sum of all 32 values."""
|
|
|
|
# fmt: off
|
|
@T.prim_func
|
|
def func(out_ptr: T.handle):
|
|
out = T.match_buffer(out_ptr, (32,), "float32")
|
|
T.device_entry()
|
|
cta_id = T.cta_id([1])
|
|
warp_id = T.warp_id([1])
|
|
lane = T.lane_id([32])
|
|
val: T.f32 = T.float32(lane + 1)
|
|
val = T.cuda.warp_sum(val)
|
|
out[lane] = val
|
|
# fmt: on
|
|
|
|
result, mod = _build_and_run(func)
|
|
expected = np.float32(32 * 33 / 2) # sum(1..32)
|
|
np.testing.assert_allclose(result, np.full(32, expected))
|
|
assert "warp_reduce_sum_32" in mod.mod.imports[0].inspect_source()
|
|
|
|
|
|
@pytest.mark.gpu
|
|
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
|
|
def test_warp_sum_partial_8():
|
|
"""Partial warp sum (width=8): 4 groups of 8 lanes, each group sums independently."""
|
|
|
|
# fmt: off
|
|
@T.prim_func
|
|
def func(out_ptr: T.handle):
|
|
out = T.match_buffer(out_ptr, (32,), "float32")
|
|
T.device_entry()
|
|
cta_id = T.cta_id([1])
|
|
warp_id = T.warp_id([1])
|
|
lane = T.lane_id([32])
|
|
val: T.f32 = T.float32(lane + 1)
|
|
val = T.cuda.warp_sum(val, width=8)
|
|
out[lane] = val
|
|
# fmt: on
|
|
|
|
result, _ = _build_and_run(func)
|
|
# Group 0: lanes 0-7 → sum(1..8) = 36
|
|
# Group 1: lanes 8-15 → sum(9..16) = 100
|
|
# Group 2: lanes 16-23 → sum(17..24) = 164
|
|
# Group 3: lanes 24-31 → sum(25..32) = 228
|
|
expected = np.zeros(32, dtype="float32")
|
|
for g in range(4):
|
|
group_sum = sum(range(g * 8 + 1, g * 8 + 9))
|
|
expected[g * 8 : (g + 1) * 8] = group_sum
|
|
np.testing.assert_allclose(result, expected)
|
|
|
|
|
|
@pytest.mark.gpu
|
|
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
|
|
def test_warp_max_partial_4():
|
|
"""Partial warp max (width=4): 8 groups of 4 lanes."""
|
|
|
|
# fmt: off
|
|
@T.prim_func
|
|
def func(out_ptr: T.handle):
|
|
out = T.match_buffer(out_ptr, (32,), "float32")
|
|
T.device_entry()
|
|
cta_id = T.cta_id([1])
|
|
warp_id = T.warp_id([1])
|
|
lane = T.lane_id([32])
|
|
val: T.f32 = T.float32(lane + 1)
|
|
val = T.cuda.warp_max(val, width=4)
|
|
out[lane] = val
|
|
# fmt: on
|
|
|
|
result, _ = _build_and_run(func)
|
|
expected = np.zeros(32, dtype="float32")
|
|
for g in range(8):
|
|
group_max = float(g * 4 + 4)
|
|
expected[g * 4 : (g + 1) * 4] = group_max
|
|
np.testing.assert_allclose(result, expected)
|
|
|
|
|
|
@pytest.mark.gpu
|
|
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
|
|
def test_warp_min_full():
|
|
"""Full warp min (width=32)."""
|
|
|
|
# fmt: off
|
|
@T.prim_func
|
|
def func(out_ptr: T.handle):
|
|
out = T.match_buffer(out_ptr, (32,), "float32")
|
|
T.device_entry()
|
|
cta_id = T.cta_id([1])
|
|
warp_id = T.warp_id([1])
|
|
lane = T.lane_id([32])
|
|
val: T.f32 = T.float32(lane + 1)
|
|
val = T.cuda.warp_min(val)
|
|
out[lane] = val
|
|
# fmt: on
|
|
|
|
result, _ = _build_and_run(func)
|
|
np.testing.assert_allclose(result, np.full(32, 1.0))
|
|
|
|
|
|
@pytest.mark.gpu
|
|
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
|
|
def test_warp_sum_partial_2():
|
|
"""Smallest partial warp sum (width=2): 16 pairs of adjacent lanes."""
|
|
|
|
# fmt: off
|
|
@T.prim_func
|
|
def func(out_ptr: T.handle):
|
|
out = T.match_buffer(out_ptr, (32,), "float32")
|
|
T.device_entry()
|
|
cta_id = T.cta_id([1])
|
|
warp_id = T.warp_id([1])
|
|
lane = T.lane_id([32])
|
|
val: T.f32 = T.float32(lane)
|
|
val = T.cuda.warp_sum(val, width=2)
|
|
out[lane] = val
|
|
# fmt: on
|
|
|
|
result, _ = _build_and_run(func)
|
|
# Pairs: (0,1)→1, (2,3)→5, (4,5)→9, ...
|
|
expected = np.zeros(32, dtype="float32")
|
|
for i in range(16):
|
|
pair_sum = float(2 * i + 2 * i + 1)
|
|
expected[2 * i] = pair_sum
|
|
expected[2 * i + 1] = pair_sum
|
|
np.testing.assert_allclose(result, expected)
|
|
|
|
|
|
@pytest.mark.gpu
|
|
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
|
|
@pytest.mark.parametrize("width", [2, 4, 8, 16, 32])
|
|
def test_warp_sum_all_widths(width):
|
|
"""Parametric test: warp_sum with every valid width."""
|
|
|
|
# fmt: off
|
|
@T.prim_func
|
|
def func(out_ptr: T.handle):
|
|
out = T.match_buffer(out_ptr, (32,), "float32")
|
|
T.device_entry()
|
|
cta_id = T.cta_id([1])
|
|
warp_id = T.warp_id([1])
|
|
lane = T.lane_id([32])
|
|
val: T.f32 = T.float32(lane)
|
|
val = T.cuda.warp_sum(val, width=width)
|
|
out[lane] = val
|
|
# fmt: on
|
|
|
|
result, _ = _build_and_run(func)
|
|
expected = np.zeros(32, dtype="float32")
|
|
num_groups = 32 // width
|
|
for g in range(num_groups):
|
|
group_sum = sum(range(g * width, (g + 1) * width))
|
|
expected[g * width : (g + 1) * width] = float(group_sum)
|
|
np.testing.assert_allclose(result, expected)
|