Skip to content

Commit e1a639c

Browse files
authored
[fix] Bugfixes for missing ttnn package, tt-mlir CAPI library resolution, and broken pytests (#208)
- Problem: TTNN package instability and configure-time availability check - ttnn package in `tt-mlir/third_party/tt-metal` may disappear or change, and `ttlang_check_ttnn_available()` checked at configure time when ttnn only available after build. Fix: Created `cmake/modules/CopyTTNNPythonPackage.cmake` to copy ttnn to `build/python_packages/ttnn/` during build. Removed `ttlang_check_ttnn_available()` from `cmake/modules/TTLangUtils.cmake`. Tests use runtime checks (`REQUIRES: ttnn` directive, `pytest.importorskip()`). - Problem: CAPI library resolution issues - `python/CMakeLists.txt` could find wrong `libTTMLIRPythonCAPI.so` from different tt-mlir install at configure time, and Python extension could resolve wrong library at runtime, causing MLIRContext registry mismatches. Fix: Made configure-time search paths mutually exclusive (only search `TTMLIR_BUILD_DIR` when defined, else search `TTMLIR_PATH`). Added `BUILD_RPATH` and `INSTALL_RPATH` properties to constrain runtime library search to configured tt-mlir directories. - Problem: Missing pytest infrastructure and broken test files - no pytest check target or configuration, lit tests incorrectly collected by pytest, `test_elementwise_ops.py` imported from non-existent `examples/utils` (deleted in #67), `test_block_allocation.py` used wrong function name `new_split_work_to_cores` and unsafe ttnn import. Fix: Added `check-ttlang-pytest` target to `test/CMakeLists.txt`. Created `test/python/conftest.py` with feature detection, markers, fixtures, and `collect_ignore` list. Recovered the deleted `examples/utils.py` that was required in pytests -- now `test/python/utils.py`. Fixed corresponding imports and function names in test files, added `# REQUIRES: ttnn` directive, changed to `pytest.importorskip("ttnn")`. - Problem: `test/python/simple_add_multitile.py` fails on qb because of ordering differences. Fix: Use `CHECK-DAG:` appropriately.
1 parent ba412e1 commit e1a639c

30 files changed

+463
-226
lines changed

.github/workflows/call-test-hardware.yml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,3 @@ jobs:
129129
name: hardware-test-reports-${{ inputs.runs-on }}
130130
path: build/python-lit-report.xml
131131
retention-days: 7
132-
133-
- name: Upload system descriptor artifacts
134-
uses: actions/upload-artifact@v4
135-
if: always()
136-
with:
137-
name: hardware-system-desc-${{ inputs.runs-on }}
138-
path: ttrt-artifacts
139-
retention-days: 7

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ if(MLIR_ENABLE_BINDINGS_PYTHON AND TTLANG_ENABLE_BINDINGS_PYTHON)
7373
message(STATUS "Enabling Python API")
7474
set(TTLANG_PYTHON_PACKAGES_DIR "${CMAKE_CURRENT_BINARY_DIR}/python_packages")
7575
add_subdirectory(python)
76+
include(CopyTTNNPythonPackage)
7677
endif()
7778

7879
add_subdirectory(test)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# SPDX-FileCopyrightText: (c) 2025 Tenstorrent AI ULC
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
# Copy tt-metal's ttnn Python package to build tree.
6+
# TTNN is required for compiling and running tt-lang kernels.
7+
8+
# Find ttnn source directory from TT_METAL_HOME or tt-mlir's third_party.
9+
# _TTMLIR_CMAKE_HOME_DIRECTORY is loaded from the tt-mlir build cache when
10+
# using TTMLIR_BUILD_DIR (see ttlang_setup_ttmlir_build_tree in TTLangUtils.cmake).
11+
if(DEFINED TT_METAL_HOME AND EXISTS "${TT_METAL_HOME}/ttnn/ttnn")
12+
set(_TTNN_SOURCE_DIR "${TT_METAL_HOME}/ttnn/ttnn")
13+
elseif(DEFINED _TTMLIR_CMAKE_HOME_DIRECTORY AND EXISTS "${_TTMLIR_CMAKE_HOME_DIRECTORY}/third_party/tt-metal/src/tt-metal/ttnn/ttnn")
14+
set(_TTNN_SOURCE_DIR "${_TTMLIR_CMAKE_HOME_DIRECTORY}/third_party/tt-metal/src/tt-metal/ttnn/ttnn")
15+
endif()
16+
17+
if(DEFINED _TTNN_SOURCE_DIR)
18+
message(STATUS "Found ttnn Python package at: ${_TTNN_SOURCE_DIR}")
19+
20+
add_custom_command(
21+
OUTPUT ${CMAKE_BINARY_DIR}/python_packages/ttnn/__init__.py
22+
COMMAND ${CMAKE_COMMAND} -E copy_directory
23+
${_TTNN_SOURCE_DIR}
24+
${CMAKE_BINARY_DIR}/python_packages/ttnn
25+
COMMENT "Copying tt-metal ttnn Python package to build tree"
26+
)
27+
28+
add_custom_target(copy-ttnn-python-package ALL
29+
DEPENDS ${CMAKE_BINARY_DIR}/python_packages/ttnn/__init__.py
30+
)
31+
else()
32+
message(STATUS "ttnn Python package not found - Python lit tests may be skipped")
33+
endif()

cmake/modules/TTLangUtils.cmake

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -200,25 +200,6 @@ macro(ttlang_setup_ttmlir_build_tree BUILD_DIR)
200200
endif()
201201
endmacro()
202202

203-
# ttlang_check_ttnn_available(OUTPUT_VAR)
204-
# Checks if the TTNN Python package is available at configure time.
205-
# Sets the variable named by OUTPUT_VAR to TRUE if available, FALSE otherwise.
206-
function(ttlang_check_ttnn_available OUTPUT_VAR)
207-
execute_process(
208-
COMMAND ${Python3_EXECUTABLE} -c "import ttnn"
209-
RESULT_VARIABLE _ttnn_import_result
210-
OUTPUT_QUIET
211-
ERROR_QUIET
212-
)
213-
if(_ttnn_import_result EQUAL 0)
214-
set(${OUTPUT_VAR} TRUE PARENT_SCOPE)
215-
message(STATUS "TTNN Python package available")
216-
else()
217-
set(${OUTPUT_VAR} FALSE PARENT_SCOPE)
218-
message(STATUS "TTNN Python package not available")
219-
endif()
220-
endfunction()
221-
222203
# ttlang_check_device_available(OUTPUT_VAR)
223204
# Checks if a Tenstorrent device is available at configure time by looking for
224205
# /dev/tenstorrent* files. This is faster than calling ttnn.GetNumAvailableDevices().

python/CMakeLists.txt

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,11 @@ set(_TTMLIR_PYTHON_CAPI_HINTS)
142142
if(DEFINED TTMLIR_BUILD_DIR)
143143
list(APPEND _TTMLIR_PYTHON_CAPI_HINTS
144144
"${TTMLIR_BUILD_DIR}/python_packages/ttmlir/_mlir_libs")
145-
endif()
146-
if(DEFINED TTMLIR_PATH)
147-
list(APPEND _TTMLIR_PYTHON_CAPI_HINTS
148-
"${TTMLIR_PATH}/python_packages/ttmlir/_mlir_libs")
145+
else()
146+
if(DEFINED TTMLIR_PATH)
147+
list(APPEND _TTMLIR_PYTHON_CAPI_HINTS
148+
"${TTMLIR_PATH}/python_packages/ttmlir/_mlir_libs")
149+
endif()
149150
endif()
150151
list(APPEND _TTMLIR_PYTHON_CAPI_HINTS
151152
"${TTMLIR_TOOLCHAIN_DIR}/python_packages/ttmlir/_mlir_libs"
@@ -190,6 +191,22 @@ target_link_libraries(TTLangPythonModules.extension._ttlang.dso PRIVATE
190191
${TTMLIR_PYTHON_CAPI_LIB}
191192
)
192193

194+
# Ensure we do not accidentally resolve libTTMLIRPythonCAPI.so from a different
195+
# tt-mlir install tree at runtime. Mixed CAPI builds in one Python process can
196+
# lead to MLIRContext registry mismatches.
197+
#
198+
# Constrain RUNPATH to the configured tt-mlir build tree.
199+
if(DEFINED TTMLIR_BUILD_DIR)
200+
set(_TTMLIR_PYTHON_LIBDIR "${TTMLIR_BUILD_DIR}/python_packages/ttmlir/_mlir_libs")
201+
set(_TTMLIR_LIBDIR "${TTMLIR_BUILD_DIR}/lib")
202+
if(EXISTS "${_TTMLIR_PYTHON_LIBDIR}" AND EXISTS "${_TTMLIR_LIBDIR}")
203+
set_target_properties(TTLangPythonModules.extension._ttlang.dso PROPERTIES
204+
BUILD_RPATH "\$ORIGIN:${_TTMLIR_LIBDIR}:${_TTMLIR_PYTHON_LIBDIR}"
205+
INSTALL_RPATH "\$ORIGIN:${_TTMLIR_LIBDIR}:${_TTMLIR_PYTHON_LIBDIR}"
206+
)
207+
endif()
208+
endif()
209+
193210
# ###############################################################################
194211
# Install
195212
# ###############################################################################

python/ttlang/ttl_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ def _compile_ttnn_kernel(
455455
)
456456

457457
if verbose:
458-
print(f"\nCompiled kernel ready (will execute on {len(kernel_paths)} kernels)")
458+
print(f"\nCompiled kernel ready (compiled {len(kernel_paths)} threads)")
459459
print("=" * 60)
460460

461461
return compiled_kernel

test/CMakeLists.txt

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ configure_lit_site_cfg(
77
${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py
88
)
99

10-
# Check for TTNN Python package and Tenstorrent device at configure time
11-
ttlang_check_ttnn_available(TTLANG_HAS_TTNN)
10+
# Check for Tenstorrent device at configure time
1211
ttlang_check_device_available(TTLANG_HAS_DEVICE)
1312

1413
if(TTLANG_HAS_DEVICE)
@@ -53,21 +52,14 @@ add_lit_testsuite(check-ttlang-mlir "Running ttlang MLIR tests"
5352
)
5453
set_target_properties(check-ttlang-mlir PROPERTIES FOLDER "Tests")
5554

56-
# Python lit tests require TTNN. Run sequentially to avoid TLB resource contention
55+
# Python lit tests. Run sequentially to avoid TLB resource contention
5756
# when multiple tests open Tenstorrent devices.
58-
if(TTLANG_HAS_TTNN)
59-
add_lit_testsuite(check-ttlang-python-lit "Running ttlang Python lit tests"
60-
${CMAKE_CURRENT_BINARY_DIR}/python
61-
-j1 ${TTLANG_LIT_COMMON_ARGS} --xunit-xml-output python-lit-report.xml
62-
DEPENDS ${TTLANG_PYTHON_TEST_DEPENDS}
63-
)
64-
set_target_properties(check-ttlang-python-lit PROPERTIES FOLDER "Tests")
65-
else()
66-
add_custom_target(check-ttlang-python-lit
67-
COMMAND ${CMAKE_COMMAND} -E echo "TTNN not available - skipping Python lit tests"
68-
COMMENT "Skipping ttlang Python lit tests (TTNN not available)"
69-
)
70-
endif()
57+
add_lit_testsuite(check-ttlang-python-lit "Running ttlang Python lit tests"
58+
${CMAKE_CURRENT_BINARY_DIR}/python
59+
-j1 ${TTLANG_LIT_COMMON_ARGS} --xunit-xml-output python-lit-report.xml
60+
DEPENDS ${TTLANG_PYTHON_TEST_DEPENDS}
61+
)
62+
set_target_properties(check-ttlang-python-lit PROPERTIES FOLDER "Tests")
7163

7264
# Python bindings tests only (no TT device required).
7365
add_lit_testsuite(check-ttlang-python-bindings "Running ttlang Python binding tests"
@@ -83,8 +75,20 @@ add_custom_target(check-ttlang
8375
COMMENT "Running ttlang lit tests that never require a TT device"
8476
)
8577

78+
# Pytest tests (test_*.py files).
79+
add_custom_target(check-ttlang-pytest
80+
COMMAND ${Python3_EXECUTABLE} -m pytest
81+
${CMAKE_CURRENT_SOURCE_DIR}/python
82+
-v --tb=short
83+
--junitxml=${CMAKE_CURRENT_BINARY_DIR}/pytest-report.xml
84+
DEPENDS ${TTLANG_PYTHON_TEST_DEPENDS}
85+
COMMENT "Running ttlang pytest tests"
86+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/python
87+
)
88+
set_target_properties(check-ttlang-pytest PROPERTIES FOLDER "Tests")
89+
8690
# All lit tests (including Python tests that require a TT device).
8791
add_custom_target(check-ttlang-all
88-
DEPENDS check-ttlang-mlir check-ttlang-python-bindings check-ttlang-python-lit
89-
COMMENT "Running all ttlang lit tests (including Python tests that require a TT device)"
92+
DEPENDS check-ttlang-mlir check-ttlang-python-bindings check-ttlang-python-lit check-ttlang-pytest
93+
COMMENT "Running all ttlang tests (lit and pytest)"
9094
)

test/lit.cfg.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"Inputs",
5353
"lit.cfg.py",
5454
"sim",
55+
"conftest.py",
56+
"utils.py",
5557
]
5658

5759
# Exclude pytest-style tests (test_*.py) from lit collection.
@@ -137,10 +139,19 @@
137139
"TT_METAL_RUNTIME_ROOT",
138140
"TT_MLIR_HOME",
139141
"SYSTEM_DESC_PATH",
142+
"TTLANG_COMPILE_ONLY",
140143
]:
141144
if env_var in os.environ:
142145
config.environment[env_var] = os.environ[env_var]
143146

144147
# Add system platform feature for UNSUPPORTED directives
145148
if platform.system() == "Darwin":
146149
config.available_features.add("system-darwin")
150+
151+
# Add TTNN feature if available
152+
try:
153+
import ttnn
154+
155+
config.available_features.add("ttnn")
156+
except ImportError:
157+
pass

test/python/conftest.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# SPDX-FileCopyrightText: (c) 2025 Tenstorrent AI ULC
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
"""Pytest configuration and fixtures for tt-lang Python tests."""
6+
7+
import glob
8+
import importlib.util
9+
import os
10+
11+
import pytest
12+
13+
# Lit tests that should not be collected by pytest (they have # RUN: directives)
14+
collect_ignore = [
15+
"conftest.py",
16+
"test_ttnn_interop_add.py",
17+
"test_dram_interleaved_add.py",
18+
"utils.py",
19+
]
20+
21+
# =============================================================================
22+
# Feature detection
23+
# =============================================================================
24+
25+
_ttnn_available = False
26+
if importlib.util.find_spec("ttnn") is not None:
27+
_ttnn_available = True
28+
29+
_hardware_available = bool(glob.glob("/dev/tenstorrent*"))
30+
31+
# Set compile-only mode if no hardware
32+
if not _hardware_available:
33+
os.environ["TTLANG_COMPILE_ONLY"] = "1"
34+
35+
36+
# =============================================================================
37+
# Pytest markers
38+
# =============================================================================
39+
40+
41+
def pytest_configure(config):
42+
"""Register custom markers."""
43+
config.addinivalue_line(
44+
"markers", "requires_ttnn: skip test if ttnn is not available"
45+
)
46+
config.addinivalue_line(
47+
"markers", "requires_device: skip test if no TT device is available"
48+
)
49+
50+
51+
def pytest_collection_modifyitems(config, items):
52+
"""Skip tests based on available features."""
53+
skip_ttnn = pytest.mark.skip(reason="TTNN not available")
54+
skip_device = pytest.mark.skip(reason="No Tenstorrent device available")
55+
56+
for item in items:
57+
if "requires_ttnn" in item.keywords and not _ttnn_available:
58+
item.add_marker(skip_ttnn)
59+
if "requires_device" in item.keywords and not _hardware_available:
60+
item.add_marker(skip_device)
61+
62+
63+
# =============================================================================
64+
# Fixtures
65+
# =============================================================================
66+
67+
68+
@pytest.fixture
69+
def ttnn_device():
70+
"""Fixture that provides a TTNN device, skipping if unavailable."""
71+
if not _ttnn_available:
72+
pytest.skip("TTNN not available")
73+
if not _hardware_available:
74+
pytest.skip("No Tenstorrent device available")
75+
76+
import ttnn
77+
78+
device = ttnn.open_device(device_id=0)
79+
yield device
80+
ttnn.close_device(device)

test/python/invalid/invalid_3d_grid.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5+
# REQUIRES: ttnn
56
# RUN: not %python %s 2>&1 | FileCheck %s
67

78
"""
@@ -14,21 +15,16 @@
1415

1516
os.environ["TTLANG_COMPILE_ONLY"] = "1"
1617

17-
from ttlang import ttl, make_circular_buffer_like
18-
from ttlang.ttl_api import Program
18+
import ttnn
19+
from ttlang import make_circular_buffer_like, ttl
1920
from ttlang.operators import copy
20-
21-
try:
22-
import ttnn
23-
except ImportError:
24-
print("TTNN not available - exiting")
25-
exit(0)
21+
from ttlang.ttl_api import Program
2622

2723

2824
# CHECK: ValueError: TTNN interop only supports single-core grid (1, 1), got (1, 1, 1)
29-
# CHECK-NEXT: --> {{.*}}invalid_3d_grid.py:34:1
25+
# CHECK-NEXT: --> {{.*}}invalid_3d_grid.py:[[LINE:[0-9]+]]:1
3026
# CHECK-NEXT: |
31-
# CHECK-NEXT: 34 | @ttl.kernel(grid=(1, 1, 1))
27+
# CHECK-NEXT: [[LINE]] | @ttl.kernel(grid=(1, 1, 1))
3228
# CHECK-NEXT: | ^
3329
# CHECK-NEXT: |
3430
@ttl.kernel(grid=(1, 1, 1))

0 commit comments

Comments
 (0)