Skip to content

Commit 206d85d

Browse files
Migrate dpctl.tensor into dpnp.tensor (#2856)
This PR migrates the tensor implementation from `dpctl.tensor` into `dpnp.tensor` making dpnp the primary owner of the Array API-compliant tensor layer Major changes: - Move compiled C++/SYCL extensions (`_tensor_impl, _tensor_elementwise_impl, _tensor_reductions_impl, _tensor_sorting_impl, _tensor_accumulation_impl, tensor linalg`) into `dpnp.tensor` - Move `usm_ndarray`, `compute-follows-data utilities` and tensor `tests` from dpctl - Replace all `dpctl.tensor` references with `dpnp.tensor` in docstrings, error messages and comments - Remove redundant dpctl.tensor C-API interface - Add `tensor.rst` documentation page describing the module, its relationship to `dpnp.ndarray` and `dpctl` and linking to the `dpctl 0.21.1 API` reference This simplifies maintenance, reduces cross-project dependencies and enables independent development and release cycles
1 parent 71d3c23 commit 206d85d

731 files changed

Lines changed: 136364 additions & 2080 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-sphinx.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
name: Build and Deploy Docs
2727

2828
runs-on: ubuntu-22.04
29-
timeout-minutes: 60
29+
timeout-minutes: 90
3030

3131
permissions:
3232
# Needed to cancel any previous runs that are not completed for a given workflow

.github/workflows/check-onemath.yaml

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ jobs:
7474
os: [ubuntu-22.04] # windows-2022 - no DFT support for Windows in oneMKL
7575

7676
runs-on: ${{ matrix.os }}
77-
timeout-minutes: 60
77+
timeout-minutes: 120
7878

7979
defaults:
8080
run:
@@ -133,6 +133,14 @@ jobs:
133133
if: env.rerun-tests-on-failure != 'true'
134134
run: |
135135
python -m pytest -ra --pyargs dpnp.tests
136+
env:
137+
SKIP_TENSOR_TESTS: 1
138+
SYCL_CACHE_PERSISTENT: 1
139+
140+
- name: Run tensor tests
141+
if: env.rerun-tests-on-failure != 'true'
142+
run: |
143+
python -m pytest -ra --pyargs dpnp.tests.tensor
136144
env:
137145
SYCL_CACHE_PERSISTENT: 1
138146

@@ -150,6 +158,24 @@ jobs:
150158
mamba activate ${{ env.test-env-name }}
151159
152160
python -m pytest -ra --pyargs dpnp.tests
161+
env:
162+
SKIP_TENSOR_TESTS: 1
163+
SYCL_CACHE_PERSISTENT: 1
164+
165+
- name: ReRun tensor tests on Linux
166+
if: env.rerun-tests-on-failure == 'true'
167+
id: run_tensor_tests
168+
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
169+
with:
170+
timeout_minutes: ${{ env.rerun-tests-timeout }}
171+
max_attempts: ${{ env.rerun-tests-max-attempts }}
172+
retry_on: any
173+
command: |
174+
. $CONDA/etc/profile.d/conda.sh
175+
. $CONDA/etc/profile.d/mamba.sh
176+
mamba activate ${{ env.test-env-name }}
177+
178+
python -m pytest -ra --pyargs dpnp.tests.tensor
153179
env:
154180
SYCL_CACHE_PERSISTENT: 1
155181

@@ -239,6 +265,14 @@ jobs:
239265
if: env.rerun-tests-on-failure != 'true'
240266
run: |
241267
python -m pytest -ra --pyargs dpnp.tests
268+
env:
269+
SKIP_TENSOR_TESTS: 1
270+
SYCL_CACHE_PERSISTENT: 1
271+
272+
- name: Run tensor tests
273+
if: env.rerun-tests-on-failure != 'true'
274+
run: |
275+
python -m pytest -ra --pyargs dpnp.tests.tensor
242276
env:
243277
SYCL_CACHE_PERSISTENT: 1
244278

@@ -256,5 +290,23 @@ jobs:
256290
mamba activate ${{ env.test-env-name }}
257291
258292
python -m pytest -ra --pyargs dpnp.tests
293+
env:
294+
SKIP_TENSOR_TESTS: 1
295+
SYCL_CACHE_PERSISTENT: 1
296+
297+
- name: ReRun tensor tests on Linux
298+
if: env.rerun-tests-on-failure == 'true'
299+
id: run_tensor_tests_branch
300+
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
301+
with:
302+
timeout_minutes: ${{ env.rerun-tests-timeout }}
303+
max_attempts: ${{ env.rerun-tests-max-attempts }}
304+
retry_on: any
305+
command: |
306+
. $CONDA/etc/profile.d/conda.sh
307+
. $CONDA/etc/profile.d/mamba.sh
308+
mamba activate ${{ env.test-env-name }}
309+
310+
python -m pytest -ra --pyargs dpnp.tests.tensor
259311
env:
260312
SYCL_CACHE_PERSISTENT: 1

.github/workflows/conda-package.yml

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
actions: write
3838

3939
runs-on: ${{ matrix.os }}
40-
timeout-minutes: 60
40+
timeout-minutes: 90
4141

4242
defaults:
4343
run:
@@ -220,6 +220,7 @@ jobs:
220220
- name: Run tests
221221
if: env.rerun-tests-on-failure != 'true'
222222
run: |
223+
export SKIP_TENSOR_TESTS=1
223224
if [[ "${{ matrix.python }}" == "${{ env.python-ver-test-all-dtypes }}" ]]; then
224225
export DPNP_TEST_ALL_INT_TYPES=1
225226
python -m pytest -ra --pyargs ${{ env.package-name }}.tests
@@ -239,6 +240,7 @@ jobs:
239240
. $CONDA/etc/profile.d/conda.sh
240241
. $CONDA/etc/profile.d/mamba.sh
241242
mamba activate ${{ env.test-env-name }}
243+
export SKIP_TENSOR_TESTS=1
242244
243245
if [[ "${{ matrix.python }}" == "${{ env.python-ver-test-all-dtypes }}" ]]; then
244246
export DPNP_TEST_ALL_INT_TYPES=1
@@ -247,6 +249,26 @@ jobs:
247249
python -m pytest -n auto -ra --pyargs ${{ env.package-name }}.tests
248250
fi
249251
252+
- name: Run tensor tests
253+
if: env.rerun-tests-on-failure != 'true'
254+
run: |
255+
python -m pytest -n auto -ra --pyargs dpnp.tests.tensor
256+
257+
- name: Run tensor tests
258+
if: env.rerun-tests-on-failure == 'true'
259+
id: run_tests_tensor_linux
260+
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
261+
with:
262+
timeout_minutes: ${{ env.rerun-tests-timeout }}
263+
max_attempts: ${{ env.rerun-tests-max-attempts }}
264+
retry_on: any
265+
command: |
266+
. $CONDA/etc/profile.d/conda.sh
267+
. $CONDA/etc/profile.d/mamba.sh
268+
mamba activate ${{ env.test-env-name }}
269+
270+
python -m pytest -n auto -ra --pyargs dpnp.tests.tensor
271+
250272
test_windows:
251273
name: Test
252274

@@ -382,6 +404,7 @@ jobs:
382404
if: env.rerun-tests-on-failure != 'true'
383405
shell: pwsh
384406
run: |
407+
$env:SKIP_TENSOR_TESTS=1
385408
if (${{ matrix.python }} -eq ${{ env.python-ver-test-all-dtypes }}) {
386409
$env:DPNP_TEST_ALL_INT_TYPES=1
387410
python -m pytest -ra --pyargs ${{ env.package-name }}.tests
@@ -399,13 +422,32 @@ jobs:
399422
retry_on: any
400423
shell: pwsh
401424
command: |
425+
$env:SKIP_TENSOR_TESTS=1
402426
if ( ${{ matrix.python }} -eq ${{ env.python-ver-test-all-dtypes }} ) {
403427
$env:DPNP_TEST_ALL_INT_TYPES=1
404428
python -m pytest -ra --pyargs ${{ env.package-name }}.tests
405429
} else {
406430
python -m pytest -n auto -ra --pyargs ${{ env.package-name }}.tests
407431
}
408432
433+
- name: Run tensor tests
434+
if: env.rerun-tests-on-failure != 'true'
435+
shell: pwsh
436+
run: |
437+
python -m pytest -n auto -ra --pyargs dpnp.tests.tensor
438+
439+
- name: Run tensor tests
440+
if: env.rerun-tests-on-failure == 'true'
441+
id: run_tests_tensor_win
442+
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
443+
with:
444+
timeout_minutes: ${{ env.rerun-tests-timeout }}
445+
max_attempts: ${{ env.rerun-tests-max-attempts }}
446+
retry_on: any
447+
shell: pwsh
448+
command: |
449+
python -m pytest -n auto -ra --pyargs dpnp.tests.tensor
450+
409451
upload:
410452
name: Upload
411453

.github/workflows/generate_coverage.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
name: Generate coverage and push to Coveralls.io
1212

1313
runs-on: ubuntu-latest
14-
timeout-minutes: 120
14+
timeout-minutes: 150
1515

1616
permissions:
1717
# Needed to cancel any previous runs that are not completed for a given workflow
@@ -122,14 +122,15 @@ jobs:
122122
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
123123
with:
124124
shell: bash
125-
timeout_minutes: 60
125+
timeout_minutes: 120
126126
max_attempts: 5
127127
retry_on: error
128128
command: |
129129
. $CONDA/etc/profile.d/conda.sh
130130
conda activate coverage
131131
[ -f /opt/intel/oneapi/setvars.sh ] && source /opt/intel/oneapi/setvars.sh
132132
git clean -fxd
133+
export SKIP_TENSOR_TESTS=1
133134
python scripts/gen_coverage.py
134135
135136
- name: Total number of coverage attempts

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dpnp_pytest.*
2828
example3
2929

3030
*dpnp_backend*
31+
dpnp/include/dpnp/tensor/*.h
3132
dpnp/**/*.cpython*.so
3233
dpnp/**/*.pyd
3334
*~

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [0.20.0] - MM/DD/2026
88

9+
This release introduces a major architectural change: the Array API-compliant tensor implementation has been migrated from `dpctl.tensor` into `dpnp.tensor`, simplifying maintenance, reducing cross-project dependencies, and allows the tensor implementation to evolve within `dpnp`.
910
This release changes the license from `BSD-2-Clause` to `BSD-3-Clause`.
1011
This release achieves `dpnp` compatibility with Python 3.14 and enables distributing `dpnp` packages with the latest Python version.
1112
Also, that release drops support for Python 3.9, making Python 3.10 the minimum required version.
@@ -28,6 +29,7 @@ Also, that release drops support for Python 3.9, making Python 3.10 the minimum
2829
* Added implementation of `dpnp.isin` function [#2595](https://github.com/IntelPython/dpnp/pull/2595)
2930
* Added implementation of `dpnp.scipy.linalg.lu` (SciPy-compatible) [#2787](https://github.com/IntelPython/dpnp/pull/2787)
3031
* Added support for ndarray subclassing via `dpnp.ndarray.view` method with `type` parameter [#2815](https://github.com/IntelPython/dpnp/issues/2815)
32+
* Migrated tensor implementation from `dpctl.tensor` into `dpnp.tensor`, making `dpnp` the primary owner of the Array API-compliant tensor layer [#2856](https://github.com/IntelPython/dpnp/pull/2856)
3133

3234
### Changed
3335

@@ -57,6 +59,7 @@ Also, that release drops support for Python 3.9, making Python 3.10 the minimum
5759
* Updated QR tests to avoid element-wise comparisons for `raw` and `r` modes [#2785](https://github.com/IntelPython/dpnp/pull/2785)
5860
* Moved all SYCL kernel functors from `backend/extensions/` to a unified `backend/kernels/` directory hierarchy [#2816](https://github.com/IntelPython/dpnp/pull/2816)
5961
* `dpnp` uses pybind11 3.0.3 [#2834](https://github.com/IntelPython/dpnp/pull/2834)
62+
* Disabled `dpnp.tensor` tests by default in `conda build --test` to prevent OOM failures during package testing. Set `SKIP_TENSOR_TESTS=0` to re-enable them on systems with enough memory [#2860](https://github.com/IntelPython/dpnp/pull/2860)
6063

6164
### Deprecated
6265

@@ -84,6 +87,7 @@ Also, that release drops support for Python 3.9, making Python 3.10 the minimum
8487
* Resolved an issue with strides calculation in `dpnp.diagonal` to return correct values for empty diagonals [#2814](https://github.com/IntelPython/dpnp/pull/2814)
8588
* Fixed test tolerance issues for float16 intermediate precision that became visible when testing against conda-forge's NumPy [#2828](https://github.com/IntelPython/dpnp/pull/2828)
8689
* Ensured device aware dtype handling in `dpnp.identity` and `dpnp.gradient` [#2835](https://github.com/IntelPython/dpnp/pull/2835)
90+
* Fixed `dpnp.tensor.round` to use device-aware output dtype for boolean input [#2851](https://github.com/IntelPython/dpnp/pull/2851)
8791

8892
### Security
8993

CMakeLists.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,23 @@ project(
3737
)
3838

3939
option(DPNP_GENERATE_COVERAGE "Enable build DPNP with coverage instrumentation" OFF)
40+
option(
41+
DPNP_TENSOR_GENERATE_COVERAGE_FOR_PYBIND11_EXTENSIONS
42+
"Build dpnp tensor pybind11 offloading extensions with coverage instrumentation"
43+
OFF
44+
)
4045
option(DPNP_BACKEND_TESTS "Enable building of DPNP backend test suite" OFF)
4146
option(
4247
DPNP_WITH_REDIST
4348
"Build DPNP assuming DPC++ redistributable is installed into Python prefix"
4449
OFF
4550
)
51+
option(
52+
DPNP_TENSOR_OFFLOAD_COMPRESS
53+
"Build dpnp tensor using offload section compression feature of DPC++ to reduce \
54+
size of shared object with offloading sections"
55+
OFF
56+
)
4657

4758
set(CMAKE_CXX_STANDARD 17)
4859
set(CMAKE_CXX_STANDARD_REQUIRED True)
@@ -106,7 +117,6 @@ find_package(Cython REQUIRED)
106117

107118
find_package(Dpctl REQUIRED)
108119
message(STATUS "Dpctl_INCLUDE_DIR=" ${Dpctl_INCLUDE_DIR})
109-
message(STATUS "Dpctl_TENSOR_INCLUDE_DIR=" ${Dpctl_TENSOR_INCLUDE_DIR})
110120

111121
option(DPNP_USE_ONEMATH "Build DPNP with oneMath" OFF)
112122
set(DPNP_TARGET_CUDA

conda-recipe/run_test.bat

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ if not defined PYTHON (
3131
)
3232

3333

34+
REM Skip tensor tests by default to avoid OOM in conda builds.
35+
REM Set SKIP_TENSOR_TESTS=0 to run them on machines with enough memory.
36+
if not defined SKIP_TENSOR_TESTS (
37+
set "SKIP_TENSOR_TESTS=1"
38+
)
39+
3440
"%PYTHON%" -c "import dpnp; print(dpnp.__version__)"
3541
if %errorlevel% neq 0 exit 1
3642

conda-recipe/run_test.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ if [ -z "${PYTHON}" ]; then
3333
PYTHON=$PREFIX/bin/python
3434
fi
3535

36+
# Skip tensor tests by default to avoid OOM in conda builds.
37+
# Set SKIP_TENSOR_TESTS=0 to run them on machines with enough memory.
38+
if [ -z "${SKIP_TENSOR_TESTS}" ]; then
39+
export SKIP_TENSOR_TESTS=1
40+
fi
41+
3642
set -e
3743

3844
$PYTHON -c "import dpnp; print(dpnp.__version__)"

doc/conf.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# http://www.sphinx-doc.org/en/master/config
77

88
from datetime import datetime
9+
from urllib.parse import urljoin
910

1011
from sphinx.ext.autodoc import FunctionDocumenter
1112
from sphinx.ext.napoleon import NumpyDocstring, docstring
@@ -231,6 +232,9 @@ def _can_document_member(member, *args, **kwargs):
231232

232233
autosummary_generate = True
233234

235+
_DPCTL_021_BASE = "https://intelpython.github.io/dpctl/0.21.1/"
236+
_DPCTL_021_INV = urljoin(_DPCTL_021_BASE, "objects.inv")
237+
234238
intersphinx_mapping = {
235239
"python": ("https://docs.python.org/3/", None),
236240
"numpy": ("https://numpy.org/doc/stable/", None),
@@ -302,3 +306,65 @@ def _parse_returns_section_patched(self, section: str) -> list[str]:
302306

303307

304308
NumpyDocstring._parse_returns_section = _parse_returns_section_patched
309+
310+
311+
# TODO: Remove once dpnp.tensor docs are generated in dpnp
312+
def _load_dpctl_tensor_inventory(app):
313+
"""Load dpctl 0.21.1 inventory for dpnp.tensor fallback only."""
314+
from sphinx.ext.intersphinx import fetch_inventory
315+
from sphinx.util import logging
316+
317+
logger = logging.getLogger(__name__)
318+
319+
try:
320+
inv = fetch_inventory(app, _DPCTL_021_BASE, _DPCTL_021_INV)
321+
except Exception as exc:
322+
logger.warning(
323+
"Failed to load dpctl 0.21.1 inventory from %s: %s",
324+
_DPCTL_021_INV,
325+
exc,
326+
)
327+
inv = {}
328+
329+
app.builder.env._dpctl_tensor_021_inventory = inv
330+
331+
332+
# TODO: Remove once dpnp.tensor docs are generated in dpnp
333+
def _resolve_dpnp_tensor_refs(app, env, node, contnode):
334+
"""Resolve dpnp.tensor.* references to dpctl 0.21.1 documentation.
335+
336+
This temporary workaround is needed because dpnp.tensor documentation
337+
is not generated yet, while the corresponding API is still documented
338+
in dpctl 0.21.1.
339+
"""
340+
from docutils import nodes as docutils_nodes
341+
342+
target = node.get("reftarget", "")
343+
if not target.startswith("dpnp.tensor"):
344+
return None
345+
346+
dpctl_target = target.replace("dpnp.tensor", "dpctl.tensor", 1)
347+
dpctl_tensor_inv = getattr(env, "_dpctl_tensor_021_inventory", {})
348+
349+
for _objtype, objects in dpctl_tensor_inv.items():
350+
if dpctl_target not in objects:
351+
continue
352+
353+
item = objects[dpctl_target]
354+
location = item.uri
355+
if location.endswith("$"):
356+
location = location[:-1] + dpctl_target
357+
358+
refuri = urljoin(_DPCTL_021_BASE, location)
359+
newnode = docutils_nodes.reference(
360+
"", "", internal=False, refuri=refuri
361+
)
362+
newnode += contnode.deepcopy()
363+
return newnode
364+
365+
return None
366+
367+
368+
def setup(app):
369+
app.connect("builder-inited", _load_dpctl_tensor_inventory, priority=400)
370+
app.connect("missing-reference", _resolve_dpnp_tensor_refs, priority=400)

0 commit comments

Comments
 (0)