diff --git a/.github/actions/ci-env/action.yml b/.github/actions/ci-env/action.yml index 3dcf4d1040b..30f0279d983 100644 --- a/.github/actions/ci-env/action.yml +++ b/.github/actions/ci-env/action.yml @@ -4,7 +4,7 @@ description: "Single source of truth for shared CI constants (TheRock ref, Docke outputs: therock-ref: description: "TheRock commit hash to use" - value: "94675052fd427fd2cc6ec9180079b3a0dc7d26b2" # 2026-05-27 commit + value: "9c0ca0a4a6900c6dfb5ede35f2034f303b53433e" docker-image: description: "Docker container image for Linux builds" value: "ghcr.io/rocm/therock_build_manylinux_x86_64@sha256:48492540591673fdc8d51beb89bde41e7ba13cbb528f643c0a481ba42c4058f2" diff --git a/.github/scripts/therock_matrix.py b/.github/scripts/therock_matrix.py index 7da86c74a88..132bfc7d37a 100644 --- a/.github/scripts/therock_matrix.py +++ b/.github/scripts/therock_matrix.py @@ -109,6 +109,7 @@ "projects_to_test": [ "hipdnn", "hipdnn_install", + "hipdnn_python_bindings", "hipdnn-samples", "miopenprovider", "hipblasltprovider", diff --git a/.github/workflows/therock-ci-linux.yml b/.github/workflows/therock-ci-linux.yml index 6ec9a2d5c78..272dc22bd3e 100644 --- a/.github/workflows/therock-ci-linux.yml +++ b/.github/workflows/therock-ci-linux.yml @@ -51,8 +51,7 @@ jobs: with: repository: "ROCm/TheRock" path: TheRock - ref: ${{ inputs.therock_ref }} - + ref: 9c0ca0a4a6900c6dfb5ede35f2034f303b53433e - name: Install python deps run: | pip install -r TheRock/requirements.txt diff --git a/.github/workflows/therock-ci-nightly.yml b/.github/workflows/therock-ci-nightly.yml index 247913493a4..e2c3ca9c14b 100644 --- a/.github/workflows/therock-ci-nightly.yml +++ b/.github/workflows/therock-ci-nightly.yml @@ -43,8 +43,7 @@ jobs: with: repository: "ROCm/TheRock" path: TheRock - ref: ${{ steps.ci-env.outputs.therock-ref }} - + ref: 9c0ca0a4a6900c6dfb5ede35f2034f303b53433e - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/therock-ci-windows.yml b/.github/workflows/therock-ci-windows.yml index 08edd5b1966..76e9b13ddfd 100644 --- a/.github/workflows/therock-ci-windows.yml +++ b/.github/workflows/therock-ci-windows.yml @@ -52,7 +52,7 @@ jobs: with: repository: "ROCm/TheRock" path: "TheRock" - ref: ${{ inputs.therock_ref }} + ref: 9c0ca0a4a6900c6dfb5ede35f2034f303b53433e - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/therock-ci.yml b/.github/workflows/therock-ci.yml index 9e11c78bd56..edb5b5ce30e 100644 --- a/.github/workflows/therock-ci.yml +++ b/.github/workflows/therock-ci.yml @@ -69,8 +69,7 @@ jobs: with: repository: "ROCm/TheRock" path: TheRock - ref: ${{ steps.ci-env.outputs.therock-ref }} - + ref: 9c0ca0a4a6900c6dfb5ede35f2034f303b53433e - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/therock-test-component.yml b/.github/workflows/therock-test-component.yml index b1a049e596e..0ccf337ad45 100644 --- a/.github/workflows/therock-test-component.yml +++ b/.github/workflows/therock-test-component.yml @@ -73,7 +73,8 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: "ROCm/TheRock" - ref: ${{ inputs.therock_ref }} + ref: 9c0ca0a4a6900c6dfb5ede35f2034f303b53433e + - name: Configure git for long paths on Windows if: ${{ runner.os == 'Windows' }} diff --git a/.github/workflows/therock-test-packages.yml b/.github/workflows/therock-test-packages.yml index 78c80080df5..268a0f53cc5 100644 --- a/.github/workflows/therock-test-packages.yml +++ b/.github/workflows/therock-test-packages.yml @@ -46,7 +46,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: "ROCm/TheRock" - ref: ${{ inputs.therock_ref }} + ref: 9c0ca0a4a6900c6dfb5ede35f2034f303b53433e - name: Setting up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/projects/hipdnn/python/CMakeLists.txt b/projects/hipdnn/python/CMakeLists.txt index 983d0f7a6fd..39599c6d245 100644 --- a/projects/hipdnn/python/CMakeLists.txt +++ b/projects/hipdnn/python/CMakeLists.txt @@ -59,4 +59,7 @@ else() install(TARGETS hipdnn_frontend_python DESTINATION "${_staging}") install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/hipdnn_frontend/__init__.py" DESTINATION "${_staging}") + install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/hipdnn_frontend/test/" + DESTINATION "share/hipdnn/tests/python" + FILES_MATCHING PATTERN "*.py") endif() diff --git a/projects/hipdnn/python/hipdnn_frontend/test/__init__.py b/projects/hipdnn/python/hipdnn_frontend/test/__init__.py new file mode 100644 index 00000000000..c2a8ff22361 --- /dev/null +++ b/projects/hipdnn/python/hipdnn_frontend/test/__init__.py @@ -0,0 +1,2 @@ +# Copyright © Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT diff --git a/projects/hipdnn/python/hipdnn_frontend/test/conftest.py b/projects/hipdnn/python/hipdnn_frontend/test/conftest.py new file mode 100644 index 00000000000..6acb1bc0037 --- /dev/null +++ b/projects/hipdnn/python/hipdnn_frontend/test/conftest.py @@ -0,0 +1,24 @@ +# Copyright © Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT + +"""Shared pytest fixtures for hipDNN Python binding tests.""" + +import pytest + +import hipdnn_frontend as hipdnn + + +@pytest.fixture() +def handle(): + """Create a hipDNN handle for GPU operations.""" + return hipdnn.create_handle() + + +@pytest.fixture() +def graph(): + """Create a hipDNN Graph configured with FLOAT data types.""" + g = hipdnn.Graph() + g.set_io_data_type(hipdnn.DataType.FLOAT) + g.set_intermediate_data_type(hipdnn.DataType.FLOAT) + g.set_compute_data_type(hipdnn.DataType.FLOAT) + return g diff --git a/projects/hipdnn/python/hipdnn_frontend/test/helpers.py b/projects/hipdnn/python/hipdnn_frontend/test/helpers.py new file mode 100644 index 00000000000..f35b34ed79e --- /dev/null +++ b/projects/hipdnn/python/hipdnn_frontend/test/helpers.py @@ -0,0 +1,88 @@ +# Copyright © Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT + +"""Shared helper functions for hipDNN Python binding tests.""" + +import numpy as np + +import hipdnn_frontend as hipdnn + + +def build_conv_fprop_graph( + graph, + n=16, + c=16, + h=16, + w=16, + k=16, + r=3, + s=3, + stride=1, + pad=1, + dilation=1, +): + """Build a complete convolution forward propagation graph. + + Returns: + Tuple of (graph, x_tensor, weight_tensor, y_tensor, out_h, out_w). + """ + out_h = (h + 2 * pad - dilation * (r - 1) - 1) // stride + 1 + out_w = (w + 2 * pad - dilation * (s - 1) - 1) // stride + 1 + + graph.set_name("conv_fprop_test") + + x = hipdnn.Tensor.create([n, c, h, w], hipdnn.DataType.FLOAT) + x.set_name("input_x") + + weight = hipdnn.Tensor.create([k, c, r, s], hipdnn.DataType.FLOAT) + weight.set_name("weight") + + conv_attrs = hipdnn.ConvFpropAttributes() + conv_attrs.set_name("conv_fprop_node") + conv_attrs.set_padding([pad, pad]) + conv_attrs.set_stride([stride, stride]) + conv_attrs.set_dilation([dilation, dilation]) + + y = graph.conv_fprop(x, weight, conv_attrs) + y.set_name("output_y") + y.set_output(True) + + return graph, x, weight, y, out_h, out_w + + +def execute_graph(graph, handle, tensor_uid_to_data): + """Execute a graph with the given tensor data. + + Args: + graph: A fully-built hipDNN graph (validated, built, plans created). + handle: A hipDNN handle. + tensor_uid_to_data: Dict mapping tensor UIDs to numpy arrays. + Output tensors should have zero-initialized arrays. + + Returns: + Dict mapping tensor UIDs to result numpy arrays (copied from device). + """ + buffers = {} + variant_pack = {} + for uid, data in tensor_uid_to_data.items(): + buf = hipdnn.DeviceBuffer(data.nbytes) + buf.copy_from_host(data.tobytes()) + buffers[uid] = (buf, data.shape, data.dtype) + variant_pack[uid] = buf.ptr() + + workspace_size = graph.get_workspace_size() + workspace_buffer = None + workspace_ptr = 0 + if workspace_size > 0: + workspace_buffer = hipdnn.DeviceBuffer(workspace_size) + workspace_ptr = workspace_buffer.ptr() + + exec_result = graph.execute(handle, variant_pack, workspace_ptr) + assert exec_result.is_good(), f"Graph execution failed: {exec_result.get_message()}" + + results = {} + for uid, (buf, shape, dtype) in buffers.items(): + host_bytes = buf.copy_to_host() + results[uid] = np.frombuffer(host_bytes, dtype=dtype).reshape(shape) + + return results diff --git a/projects/hipdnn/python/hipdnn_frontend/test/test_conv_dgrad.py b/projects/hipdnn/python/hipdnn_frontend/test/test_conv_dgrad.py new file mode 100644 index 00000000000..76c5752fe07 --- /dev/null +++ b/projects/hipdnn/python/hipdnn_frontend/test/test_conv_dgrad.py @@ -0,0 +1,104 @@ +# Copyright © Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT + +"""Integration tests for convolution backward data gradient.""" + +import numpy as np +import pytest + +import hipdnn_frontend as hipdnn + +from .helpers import execute_graph + +# Dimensions used across tests +N, C, H, W = 16, 16, 16, 16 +K, R, S = 16, 3, 3 +STRIDE, PAD, DIL = 1, 1, 1 +OUT_H = (H + 2 * PAD - DIL * (R - 1) - 1) // STRIDE + 1 +OUT_W = (W + 2 * PAD - DIL * (S - 1) - 1) // STRIDE + 1 + + +def _build_conv_dgrad_graph(graph): + """Build a conv_dgrad graph returning (graph, dy, weight, dx).""" + graph.set_name("conv_dgrad_test") + + dy = hipdnn.Tensor.create([N, K, OUT_H, OUT_W], hipdnn.DataType.FLOAT) + dy.set_name("output_gradient_dy") + + weight = hipdnn.Tensor.create([K, C, R, S], hipdnn.DataType.FLOAT) + weight.set_name("weight") + + conv_attrs = hipdnn.ConvDgradAttributes() + conv_attrs.set_name("conv_dgrad_node") + conv_attrs.set_pre_padding([PAD, PAD]) + conv_attrs.set_post_padding([PAD, PAD]) + conv_attrs.set_stride([STRIDE, STRIDE]) + conv_attrs.set_dilation([DIL, DIL]) + + dx = graph.conv_dgrad(dy, weight, conv_attrs) + dx.set_name("input_gradient_dx") + dx.set_output(True) + + return graph, dy, weight, dx + + +@pytest.mark.gpu +@pytest.mark.integration +class TestConvDgrad: + """Tests for convolution backward data gradient end-to-end pipeline.""" + + def test_graph_validates_successfully(self, graph): + """Build a conv_dgrad graph and verify validation passes.""" + graph, dy, weight, dx = _build_conv_dgrad_graph(graph) + + result = graph.validate() + assert result.is_good(), f"Validation failed: {result.get_message()}" + + def test_operation_graph_builds(self, graph, handle): + """Build a conv_dgrad operation graph with backend handle.""" + graph, dy, weight, dx = _build_conv_dgrad_graph(graph) + + result = graph.validate() + assert result.is_good(), f"Validation failed: {result.get_message()}" + + result = graph.build_operation_graph(handle) + assert result.is_good(), f"Build operation graph failed: {result.get_message()}" + + def test_execution_plans_created(self, graph, handle): + """Build execution plans for conv_dgrad.""" + graph, dy, weight, dx = _build_conv_dgrad_graph(graph) + + assert graph.validate().is_good() + assert graph.build_operation_graph(handle).is_good() + assert graph.create_execution_plans().is_good() + assert graph.check_support().is_good() + assert graph.build_plans().is_good() + + def test_execution_produces_nonzero_output(self, graph, handle): + """Full end-to-end conv_dgrad: execute and verify non-zero output.""" + graph, dy, weight, dx = _build_conv_dgrad_graph(graph) + + assert graph.validate().is_good() + assert graph.build_operation_graph(handle).is_good() + assert graph.create_execution_plans().is_good() + assert graph.check_support().is_good() + assert graph.build_plans().is_good() + + dy_data = np.random.uniform( + 0.0, + 1.0, + [N, K, OUT_H, OUT_W], + ).astype(np.float32) + w_data = np.random.uniform(0.0, 1.0, [K, C, R, S]).astype(np.float32) + dx_data = np.zeros([N, C, H, W], dtype=np.float32) + + tensor_data = { + dy.get_uid(): dy_data, + weight.get_uid(): w_data, + dx.get_uid(): dx_data, + } + + results = execute_graph(graph, handle, tensor_data) + dx_result = results[dx.get_uid()] + + assert not np.all(dx_result == 0), "Conv dgrad output is all zeros" diff --git a/projects/hipdnn/python/hipdnn_frontend/test/test_conv_fprop.py b/projects/hipdnn/python/hipdnn_frontend/test/test_conv_fprop.py new file mode 100644 index 00000000000..8be730bdda0 --- /dev/null +++ b/projects/hipdnn/python/hipdnn_frontend/test/test_conv_fprop.py @@ -0,0 +1,122 @@ +# Copyright © Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT + +"""Integration tests for convolution forward propagation.""" + +import numpy as np +import pytest + +from .helpers import build_conv_fprop_graph, execute_graph + +# Dimensions used across tests +N, C, H, W = 16, 16, 16, 16 +K, R, S = 16, 3, 3 +STRIDE, PAD, DIL = 1, 1, 1 +OUT_H = (H + 2 * PAD - DIL * (R - 1) - 1) // STRIDE + 1 +OUT_W = (W + 2 * PAD - DIL * (S - 1) - 1) // STRIDE + 1 + + +@pytest.mark.gpu +@pytest.mark.integration +class TestConvFprop: + """Tests for convolution forward propagation end-to-end pipeline.""" + + def test_graph_validates_successfully(self, graph): + """Build a conv_fprop graph and verify validation passes.""" + graph, x, weight, y, out_h, out_w = build_conv_fprop_graph( + graph, + n=N, + c=C, + h=H, + w=W, + k=K, + r=R, + s=S, + stride=STRIDE, + pad=PAD, + dilation=DIL, + ) + + result = graph.validate() + assert result.is_good(), f"Validation failed: {result.get_message()}" + + def test_operation_graph_builds(self, graph, handle): + """Build a conv_fprop operation graph with backend handle.""" + graph, x, weight, y, out_h, out_w = build_conv_fprop_graph( + graph, + n=N, + c=C, + h=H, + w=W, + k=K, + r=R, + s=S, + stride=STRIDE, + pad=PAD, + dilation=DIL, + ) + + result = graph.validate() + assert result.is_good(), f"Validation failed: {result.get_message()}" + + result = graph.build_operation_graph(handle) + assert result.is_good(), f"Build operation graph failed: {result.get_message()}" + + def test_execution_plans_created(self, graph, handle): + """Build execution plans for conv_fprop.""" + graph, x, weight, y, out_h, out_w = build_conv_fprop_graph( + graph, + n=N, + c=C, + h=H, + w=W, + k=K, + r=R, + s=S, + stride=STRIDE, + pad=PAD, + dilation=DIL, + ) + + assert graph.validate().is_good() + assert graph.build_operation_graph(handle).is_good() + assert graph.create_execution_plans().is_good() + assert graph.check_support().is_good() + assert graph.build_plans().is_good() + + def test_execution_produces_nonzero_output(self, graph, handle): + """Full end-to-end conv_fprop: execute and verify non-zero output.""" + graph, x, weight, y, out_h, out_w = build_conv_fprop_graph( + graph, + n=N, + c=C, + h=H, + w=W, + k=K, + r=R, + s=S, + stride=STRIDE, + pad=PAD, + dilation=DIL, + ) + + assert graph.validate().is_good() + assert graph.build_operation_graph(handle).is_good() + assert graph.create_execution_plans().is_good() + assert graph.check_support().is_good() + assert graph.build_plans().is_good() + + x_data = np.random.uniform(0.0, 1.0, [N, C, H, W]).astype(np.float32) + w_data = np.random.uniform(0.0, 1.0, [K, C, R, S]).astype(np.float32) + y_data = np.zeros([N, K, out_h, out_w], dtype=np.float32) + + tensor_data = { + x.get_uid(): x_data, + weight.get_uid(): w_data, + y.get_uid(): y_data, + } + + results = execute_graph(graph, handle, tensor_data) + y_result = results[y.get_uid()] + + assert not np.all(y_result == 0), "Conv fprop output is all zeros" diff --git a/projects/hipdnn/python/hipdnn_frontend/test/test_conv_wgrad.py b/projects/hipdnn/python/hipdnn_frontend/test/test_conv_wgrad.py new file mode 100644 index 00000000000..85fbc556947 --- /dev/null +++ b/projects/hipdnn/python/hipdnn_frontend/test/test_conv_wgrad.py @@ -0,0 +1,104 @@ +# Copyright © Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT + +"""Integration tests for convolution backward weight gradient.""" + +import numpy as np +import pytest + +import hipdnn_frontend as hipdnn + +from .helpers import execute_graph + +# Dimensions used across tests +N, C, H, W = 16, 16, 16, 16 +K, R, S = 16, 3, 3 +STRIDE, PAD, DIL = 1, 1, 1 +OUT_H = (H + 2 * PAD - DIL * (R - 1) - 1) // STRIDE + 1 +OUT_W = (W + 2 * PAD - DIL * (S - 1) - 1) // STRIDE + 1 + + +def _build_conv_wgrad_graph(graph): + """Build a conv_wgrad graph returning (graph, dy, x, dw).""" + graph.set_name("conv_wgrad_test") + + dy = hipdnn.Tensor.create([N, K, OUT_H, OUT_W], hipdnn.DataType.FLOAT) + dy.set_name("output_gradient_dy") + + x = hipdnn.Tensor.create([N, C, H, W], hipdnn.DataType.FLOAT) + x.set_name("input_x") + + conv_attrs = hipdnn.ConvWgradAttributes() + conv_attrs.set_name("conv_wgrad_node") + conv_attrs.set_pre_padding([PAD, PAD]) + conv_attrs.set_post_padding([PAD, PAD]) + conv_attrs.set_stride([STRIDE, STRIDE]) + conv_attrs.set_dilation([DIL, DIL]) + + dw = graph.conv_wgrad(dy, x, conv_attrs) + dw.set_name("weight_gradient_dw") + dw.set_output(True) + + return graph, dy, x, dw + + +@pytest.mark.gpu +@pytest.mark.integration +class TestConvWgrad: + """Tests for convolution backward weight gradient end-to-end pipeline.""" + + def test_graph_validates_successfully(self, graph): + """Build a conv_wgrad graph and verify validation passes.""" + graph, dy, x, dw = _build_conv_wgrad_graph(graph) + + result = graph.validate() + assert result.is_good(), f"Validation failed: {result.get_message()}" + + def test_operation_graph_builds(self, graph, handle): + """Build a conv_wgrad operation graph with backend handle.""" + graph, dy, x, dw = _build_conv_wgrad_graph(graph) + + result = graph.validate() + assert result.is_good(), f"Validation failed: {result.get_message()}" + + result = graph.build_operation_graph(handle) + assert result.is_good(), f"Build operation graph failed: {result.get_message()}" + + def test_execution_plans_created(self, graph, handle): + """Build execution plans for conv_wgrad.""" + graph, dy, x, dw = _build_conv_wgrad_graph(graph) + + assert graph.validate().is_good() + assert graph.build_operation_graph(handle).is_good() + assert graph.create_execution_plans().is_good() + assert graph.check_support().is_good() + assert graph.build_plans().is_good() + + def test_execution_produces_nonzero_output(self, graph, handle): + """Full end-to-end conv_wgrad: execute and verify non-zero output.""" + graph, dy, x, dw = _build_conv_wgrad_graph(graph) + + assert graph.validate().is_good() + assert graph.build_operation_graph(handle).is_good() + assert graph.create_execution_plans().is_good() + assert graph.check_support().is_good() + assert graph.build_plans().is_good() + + dy_data = np.random.uniform( + 0.0, + 1.0, + [N, K, OUT_H, OUT_W], + ).astype(np.float32) + x_data = np.random.uniform(0.0, 1.0, [N, C, H, W]).astype(np.float32) + dw_data = np.zeros([K, C, R, S], dtype=np.float32) + + tensor_data = { + dy.get_uid(): dy_data, + x.get_uid(): x_data, + dw.get_uid(): dw_data, + } + + results = execute_graph(graph, handle, tensor_data) + dw_result = results[dw.get_uid()] + + assert not np.all(dw_result == 0), "Conv wgrad output is all zeros" diff --git a/projects/hipdnn/python/hipdnn_frontend/test/test_device_buffer.py b/projects/hipdnn/python/hipdnn_frontend/test/test_device_buffer.py new file mode 100644 index 00000000000..6b504691031 --- /dev/null +++ b/projects/hipdnn/python/hipdnn_frontend/test/test_device_buffer.py @@ -0,0 +1,55 @@ +# Copyright © Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT + +"""GPU tests for DeviceBuffer allocation and data transfer.""" + +import numpy as np +import pytest + +import hipdnn_frontend as hipdnn + + +@pytest.mark.gpu +class TestDeviceBuffer: + """Tests for DeviceBuffer creation and host-device data transfer.""" + + def test_buffer_creation(self): + """DeviceBuffer can be created with a given byte size.""" + buf = hipdnn.DeviceBuffer(1024) + assert buf is not None + + def test_buffer_ptr_nonzero(self): + """DeviceBuffer.ptr() returns a non-zero device pointer.""" + buf = hipdnn.DeviceBuffer(256) + assert buf.ptr() != 0 + + def test_buffer_size(self): + """DeviceBuffer.size() returns the requested byte count.""" + buf = hipdnn.DeviceBuffer(512) + assert buf.size() == 512 + + def test_buffer_host_roundtrip(self): + """Data copied to device and back matches the original.""" + data = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float32) + buf = hipdnn.DeviceBuffer(data.nbytes) + + buf.copy_from_host(data.tobytes()) + result_bytes = buf.copy_to_host() + result = np.frombuffer(result_bytes, dtype=np.float32) + + np.testing.assert_array_equal(result, data) + + def test_buffer_zeros(self): + """DeviceBuffer.zeros() fills the buffer with zeros.""" + size = 64 * 4 # 64 float32 values + buf = hipdnn.DeviceBuffer(size) + + # Fill with non-zero first to confirm zeros() works + data = np.ones(64, dtype=np.float32) + buf.copy_from_host(data.tobytes()) + + buf.zeros() + + result_bytes = buf.copy_to_host() + result = np.frombuffer(result_bytes, dtype=np.float32) + np.testing.assert_array_equal(result, np.zeros(64, dtype=np.float32)) diff --git a/projects/hipdnn/python/hipdnn_frontend/test/test_engine_id_overloads.py b/projects/hipdnn/python/hipdnn_frontend/test/test_engine_id_overloads.py index 04c971d8592..f3a23aa9e25 100644 --- a/projects/hipdnn/python/hipdnn_frontend/test/test_engine_id_overloads.py +++ b/projects/hipdnn/python/hipdnn_frontend/test/test_engine_id_overloads.py @@ -1,119 +1,90 @@ -#!/usr/bin/env python3 -""" -Test script for set_preferred_engine_id_ext() and get_preferred_engine_id_ext() overloads. +# Copyright © Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT + +"""Tests for set_preferred_engine_id_ext() and get_preferred_engine_id_ext() overloads. -This script verifies that: +Verifies that: 1. Setting by int64 works 2. Setting by string works 3. Setting to None clears the preference 4. Setting to empty string clears the preference 5. Getting the preference returns the correct value 6. Method chaining works - -USAGE: - # From the python/hipdnn_frontend/test directory, after building and installing: - python test_engine_id_overloads.py """ -import sys -import os - import hipdnn_frontend as fe def test_set_by_int(): """Test setting preferred engine ID by integer.""" - print("Test 1: Set by int...") graph = fe.Graph() - # Set by int graph.set_preferred_engine_id_ext(12345) - # Verify engine_id = graph.get_preferred_engine_id_ext() assert engine_id is not None, "Engine ID should be set" assert engine_id == 12345, f"Expected 12345, got {engine_id}" - print(" OK Set by int works") def test_set_by_string(): """Test setting preferred engine ID by string.""" - print("Test 2: Set by string...") graph = fe.Graph() - # Set by string test_engine_name = "TEST_ENGINE_NAME" graph.set_preferred_engine_id_ext(test_engine_name) - # Verify it was set (we can't easily verify the exact ID without engineNameToId in Python) engine_id = graph.get_preferred_engine_id_ext() assert engine_id is not None, "Engine ID should be set" assert isinstance(engine_id, int), f"Engine ID should be int, got {type(engine_id)}" - print(f" OK Set by string works (ID: {engine_id})") def test_clear_with_none(): """Test clearing preference with None.""" - print("Test 3: Clear with None...") graph = fe.Graph() - # Set then clear graph.set_preferred_engine_id_ext(12345) assert graph.get_preferred_engine_id_ext() is not None, "Should be set" graph.set_preferred_engine_id_ext(None) assert graph.get_preferred_engine_id_ext() is None, "Should be cleared" - print(" OK Clear with None works") def test_clear_with_empty_string(): """Test clearing preference with empty string.""" - print("Test 4: Clear with empty string...") graph = fe.Graph() - # Set then clear graph.set_preferred_engine_id_ext("TEST_ENGINE") assert graph.get_preferred_engine_id_ext() is not None, "Should be set" graph.set_preferred_engine_id_ext("") assert graph.get_preferred_engine_id_ext() is None, "Should be cleared" - print(" OK Clear with empty string works") def test_overload_interaction(): """Test that overloads can override each other.""" - print("Test 5: Overload interaction...") graph = fe.Graph() - # Set by string graph.set_preferred_engine_id_ext("ENGINE_A") id_from_string = graph.get_preferred_engine_id_ext() - # Override with int graph.set_preferred_engine_id_ext(999) id_from_int = graph.get_preferred_engine_id_ext() assert id_from_int == 999, f"Expected 999, got {id_from_int}" assert id_from_int != id_from_string, "IDs should be different" - print(" OK Overload interaction works") def test_method_chaining(): """Test that set_preferred_engine_id_ext() supports method chaining.""" - print("Test 6: Method chaining...") graph = fe.Graph() - # Chain multiple setters result = ( graph.set_name("test_graph") .set_preferred_engine_id_ext(12345) .set_compute_data_type(fe.DataType.FLOAT) ) - # Verify chaining returns the graph assert result is graph, "Chaining should return the same graph object" - - # Verify values were set assert ( graph.get_name() == "test_graph" ), f"Expected name 'test_graph', got '{graph.get_name()}'" @@ -122,15 +93,12 @@ def test_method_chaining(): assert ( graph.get_compute_data_type() == fe.DataType.FLOAT ), f"Expected FLOAT, got {graph.get_compute_data_type()}" - print(" OK Method chaining works") def test_chaining_with_string_overload(): """Test chaining with string overload.""" - print("Test 7: Chaining with string overload...") graph = fe.Graph() - # Chain with string overload result = ( graph.set_name("test_graph") .set_preferred_engine_id_ext("MY_ENGINE") @@ -139,38 +107,3 @@ def test_chaining_with_string_overload(): assert result is graph, "Chaining should return the same graph object" assert graph.get_preferred_engine_id_ext() is not None - print(" OK Chaining with string overload works") - - -def main(): - """Run all tests.""" - print("=" * 60) - print("Testing set_preferred_engine_id_ext() overloads") - print("=" * 60) - - try: - test_set_by_int() - test_set_by_string() - test_clear_with_none() - test_clear_with_empty_string() - test_overload_interaction() - test_method_chaining() - test_chaining_with_string_overload() - - print("=" * 60) - print("OK All tests passed!") - print("=" * 60) - return 0 - except AssertionError as e: - print(f"\nX Test failed: {e}") - return 1 - except Exception as e: - print(f"\nX Unexpected error: {e}") - import traceback - - traceback.print_exc() - return 1 - - -if __name__ == "__main__": - exit(main()) diff --git a/projects/hipdnn/python/hipdnn_frontend/test/test_graph_api.py b/projects/hipdnn/python/hipdnn_frontend/test/test_graph_api.py new file mode 100644 index 00000000000..397da4a41e1 --- /dev/null +++ b/projects/hipdnn/python/hipdnn_frontend/test/test_graph_api.py @@ -0,0 +1,72 @@ +# Copyright © Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT + +"""API tests for Graph configuration (mostly no GPU required).""" + +import hipdnn_frontend as hipdnn + + +class TestGraphConfiguration: + """Tests for Graph setter and getter methods.""" + + def test_graph_set_name(self): + """set_name() / get_name() roundtrip.""" + g = hipdnn.Graph() + g.set_name("test_graph") + assert g.get_name() == "test_graph" + + def test_graph_set_compute_data_type(self): + """set_compute_data_type() / get_compute_data_type() roundtrip.""" + g = hipdnn.Graph() + g.set_compute_data_type(hipdnn.DataType.FLOAT) + assert g.get_compute_data_type() == hipdnn.DataType.FLOAT + + def test_graph_set_io_data_type(self): + """set_io_data_type() / get_io_data_type() roundtrip.""" + g = hipdnn.Graph() + g.set_io_data_type(hipdnn.DataType.FLOAT) + assert g.get_io_data_type() == hipdnn.DataType.FLOAT + + def test_graph_set_intermediate_data_type(self): + """set_intermediate_data_type() / get_intermediate_data_type() roundtrip.""" + g = hipdnn.Graph() + g.set_intermediate_data_type(hipdnn.DataType.FLOAT) + assert g.get_intermediate_data_type() == hipdnn.DataType.FLOAT + + def test_graph_method_chaining(self): + """Chained setter calls return the same graph object.""" + g = hipdnn.Graph() + result = ( + g.set_name("chained_graph") + .set_io_data_type(hipdnn.DataType.FLOAT) + .set_compute_data_type(hipdnn.DataType.FLOAT) + .set_intermediate_data_type(hipdnn.DataType.FLOAT) + ) + assert result is g + assert g.get_name() == "chained_graph" + + +class TestGraphTensorCreation: + """Tests for creating tensors via the Graph API.""" + + def test_graph_tensor_creation(self): + """Tensor.create() produces a tensor with expected dims and dtype.""" + t = hipdnn.Tensor.create([2, 3, 4], hipdnn.DataType.FLOAT) + t.set_stride([12, 4, 1]) + + assert t is not None + assert t.get_dim() == [2, 3, 4] + assert t.get_data_type() == hipdnn.DataType.FLOAT + assert t.get_stride() == [12, 4, 1] + + def test_graph_tensor_like(self): + """Graph.tensor_like() creates a tensor with matching dims but new uid.""" + original = hipdnn.Tensor.create([4, 8, 16], hipdnn.DataType.FLOAT) + original.set_name("original") + + copy = hipdnn.Graph.tensor_like(original) + assert copy is not None + assert copy.get_dim() == original.get_dim() + assert copy.get_data_type() == original.get_data_type() + # tensor_like clears the uid, so has_uid should be False + assert not copy.has_uid() diff --git a/projects/hipdnn/python/hipdnn_frontend/test/test_matmul.py b/projects/hipdnn/python/hipdnn_frontend/test/test_matmul.py new file mode 100644 index 00000000000..cc81b591309 --- /dev/null +++ b/projects/hipdnn/python/hipdnn_frontend/test/test_matmul.py @@ -0,0 +1,53 @@ +# Copyright © Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT + +"""Integration tests for matrix multiplication.""" + +import pytest + +import hipdnn_frontend as hipdnn + +# Dimensions: A [M, K], B [K, N] -> C [M, N] +M, K, N = 4, 3, 5 + + +def _build_matmul_graph(graph): + """Build a matmul graph returning (graph, a, b, c).""" + graph.set_name("matmul_test") + + a = hipdnn.Tensor.create([M, K], hipdnn.DataType.FLOAT) + a.set_name("A") + + b = hipdnn.Tensor.create([K, N], hipdnn.DataType.FLOAT) + b.set_name("B") + + attrs = hipdnn.MatmulAttributes() + attrs.set_name("matmul_node") + + c = graph.matmul(a, b, attrs) + c.set_name("C") + c.set_output(True) + + return graph, a, b, c + + +@pytest.mark.gpu +class TestMatmul: + """Tests for matrix multiplication graph building.""" + + def test_graph_validates(self, graph): + """Create a matmul graph and verify validation passes.""" + graph, a, b, c = _build_matmul_graph(graph) + + result = graph.validate() + assert result.is_good(), f"Validation failed: {result.get_message()}" + + def test_operation_graph_builds(self, graph, handle): + """Validate and build matmul operation graph.""" + graph, a, b, c = _build_matmul_graph(graph) + + result = graph.validate() + assert result.is_good(), f"Validation failed: {result.get_message()}" + + result = graph.build_operation_graph(handle) + assert result.is_good(), f"Build operation graph failed: {result.get_message()}" diff --git a/projects/hipdnn/python/hipdnn_frontend/test/test_tensor_api.py b/projects/hipdnn/python/hipdnn_frontend/test/test_tensor_api.py new file mode 100644 index 00000000000..1342b296b85 --- /dev/null +++ b/projects/hipdnn/python/hipdnn_frontend/test/test_tensor_api.py @@ -0,0 +1,76 @@ +# Copyright © Advanced Micro Devices, Inc., or its affiliates. +# SPDX-License-Identifier: MIT + +"""API tests for Tensor creation and configuration (no GPU required).""" + +import hipdnn_frontend as hipdnn + + +class TestTensorCreate: + """Tests for Tensor.create() and basic property accessors.""" + + def test_tensor_create_sets_dimensions(self): + """Tensor dimensions match the shape passed to create().""" + dims = [2, 3, 4, 5] + t = hipdnn.Tensor.create(dims, hipdnn.DataType.FLOAT) + assert t.get_dim() == dims + + def test_tensor_create_sets_data_type(self): + """Tensor data type matches the type passed to create().""" + t = hipdnn.Tensor.create([1, 2, 3], hipdnn.DataType.FLOAT) + assert t.get_data_type() == hipdnn.DataType.FLOAT + + def test_tensor_uid_is_not_auto_assigned(self): + """Tensor.create() does not auto-assign a uid; manual set_uid works.""" + t1 = hipdnn.Tensor.create([1, 2], hipdnn.DataType.FLOAT) + assert not t1.has_uid() + + t1.set_uid(1) + t2 = hipdnn.Tensor.create([3, 4], hipdnn.DataType.FLOAT) + t2.set_uid(2) + assert t1.get_uid() != t2.get_uid() + + +class TestTensorSetters: + """Tests for Tensor setter methods.""" + + def test_tensor_set_name(self): + """set_name() / get_name() roundtrip.""" + t = hipdnn.Tensor.create([1, 2], hipdnn.DataType.FLOAT) + t.set_name("my_tensor") + assert t.get_name() == "my_tensor" + + def test_tensor_set_stride(self): + """set_stride() / get_stride() roundtrip.""" + t = hipdnn.Tensor.create([2, 3, 4], hipdnn.DataType.FLOAT) + strides = [12, 4, 1] + t.set_stride(strides) + assert t.get_stride() == strides + + def test_tensor_set_output(self): + """set_output() marks tensor as a graph output and supports chaining.""" + t = hipdnn.Tensor.create([1, 2], hipdnn.DataType.FLOAT) + result = t.set_output(True) + # set_output returns self for method chaining + assert result is t + + def test_tensor_set_is_virtual(self): + """set_is_virtual() marks tensor as virtual (intermediate).""" + t = hipdnn.Tensor.create([1, 2], hipdnn.DataType.FLOAT) + t.set_is_virtual(True) + assert t.get_is_virtual() is True + + def test_tensor_method_chaining(self): + """Chained setter calls return the same tensor object.""" + t = hipdnn.Tensor.create([2, 3], hipdnn.DataType.FLOAT) + result = t.set_name("chained").set_uid(42).set_data_type(hipdnn.DataType.FLOAT) + assert result is t + assert t.get_name() == "chained" + assert t.get_uid() == 42 + + def test_tensor_validate(self): + """A properly configured tensor passes validation.""" + t = hipdnn.Tensor.create([2, 3, 4], hipdnn.DataType.FLOAT) + t.set_name("valid_tensor") + result = t.validate() + assert result.is_good(), f"Validation failed: {result.get_message()}" diff --git a/projects/hipdnn/python/pyproject.toml b/projects/hipdnn/python/pyproject.toml index dec4eef64b0..281bb798ecb 100644 --- a/projects/hipdnn/python/pyproject.toml +++ b/projects/hipdnn/python/pyproject.toml @@ -40,3 +40,9 @@ dev = [ [tool.scikit-build] cmake.build-type = "Release" wheel.packages = [] + +[tool.pytest.ini_options] +markers = [ + "gpu: test requires a ROCm-capable GPU", + "integration: end-to-end integration test (slower)", +]