trntensor 0.17.0__tar.gz → 0.18.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {trntensor-0.17.0 → trntensor-0.18.0}/.github/workflows/ci.yml +6 -4
  2. {trntensor-0.17.0 → trntensor-0.18.0}/CHANGELOG.md +91 -0
  3. {trntensor-0.17.0 → trntensor-0.18.0}/CLAUDE.md +4 -1
  4. {trntensor-0.17.0/trntensor.egg-info → trntensor-0.18.0}/PKG-INFO +35 -3
  5. trntensor-0.17.0/PKG-INFO → trntensor-0.18.0/README.md +26 -32
  6. trntensor-0.18.0/docs/api/nki.md +174 -0
  7. {trntensor-0.17.0 → trntensor-0.18.0}/docs/api/quantum.md +15 -5
  8. {trntensor-0.17.0 → trntensor-0.18.0}/docs/contraction_patterns.md +12 -10
  9. {trntensor-0.17.0 → trntensor-0.18.0}/pyproject.toml +25 -7
  10. {trntensor-0.17.0 → trntensor-0.18.0}/scripts/autotune_dispatch.py +162 -12
  11. trntensor-0.18.0/scripts/bench_native_einsum.py +396 -0
  12. {trntensor-0.17.0 → trntensor-0.18.0}/tests/conftest.py +6 -0
  13. {trntensor-0.17.0 → trntensor-0.18.0}/tests/test_einsum.py +2 -2
  14. trntensor-0.18.0/tests/test_native_dispatch.py +732 -0
  15. {trntensor-0.17.0 → trntensor-0.18.0}/tests/test_nki_kernels.py +74 -45
  16. trntensor-0.18.0/tests/test_nki_native.py +176 -0
  17. {trntensor-0.17.0 → trntensor-0.18.0}/tests/test_plan.py +17 -1
  18. {trntensor-0.17.0 → trntensor-0.18.0}/tests/test_quantum.py +24 -0
  19. {trntensor-0.17.0 → trntensor-0.18.0}/tests/test_sr_runtime.py +3 -0
  20. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor/__init__.py +4 -2
  21. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor/einsum.py +26 -14
  22. trntensor-0.18.0/trntensor/nki/_native.py +85 -0
  23. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor/nki/dispatch.py +413 -49
  24. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor/plan.py +17 -6
  25. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor/quantum.py +48 -20
  26. trntensor-0.17.0/README.md → trntensor-0.18.0/trntensor.egg-info/PKG-INFO +64 -1
  27. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor.egg-info/SOURCES.txt +4 -0
  28. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor.egg-info/requires.txt +10 -1
  29. {trntensor-0.17.0 → trntensor-0.18.0}/uv.lock +1 -1
  30. trntensor-0.17.0/docs/api/nki.md +0 -78
  31. {trntensor-0.17.0 → trntensor-0.18.0}/.github/workflows/notify-umbrella.yml +0 -0
  32. {trntensor-0.17.0 → trntensor-0.18.0}/.github/workflows/publish.yml +0 -0
  33. {trntensor-0.17.0 → trntensor-0.18.0}/.gitignore +0 -0
  34. {trntensor-0.17.0 → trntensor-0.18.0}/.pre-commit-config.yaml +0 -0
  35. {trntensor-0.17.0 → trntensor-0.18.0}/CODE_OF_CONDUCT.md +0 -0
  36. {trntensor-0.17.0 → trntensor-0.18.0}/CONTRIBUTING.md +0 -0
  37. {trntensor-0.17.0 → trntensor-0.18.0}/LICENSE +0 -0
  38. {trntensor-0.17.0 → trntensor-0.18.0}/benchmarks/__init__.py +0 -0
  39. {trntensor-0.17.0 → trntensor-0.18.0}/benchmarks/bench_einsum.py +0 -0
  40. {trntensor-0.17.0 → trntensor-0.18.0}/docs/api/decompose.md +0 -0
  41. {trntensor-0.17.0 → trntensor-0.18.0}/docs/api/einsum.md +0 -0
  42. {trntensor-0.17.0 → trntensor-0.18.0}/docs/api/index.md +0 -0
  43. {trntensor-0.17.0 → trntensor-0.18.0}/docs/api/plan.md +0 -0
  44. {trntensor-0.17.0 → trntensor-0.18.0}/docs/architecture.md +0 -0
  45. {trntensor-0.17.0 → trntensor-0.18.0}/docs/aws_setup.md +0 -0
  46. {trntensor-0.17.0 → trntensor-0.18.0}/docs/benchmarks.md +0 -0
  47. {trntensor-0.17.0 → trntensor-0.18.0}/docs/design/matmul_dispatch_findings.md +0 -0
  48. {trntensor-0.17.0 → trntensor-0.18.0}/docs/design/matmul_kernel_profile.md +0 -0
  49. {trntensor-0.17.0 → trntensor-0.18.0}/docs/developing_kernels.md +0 -0
  50. {trntensor-0.17.0 → trntensor-0.18.0}/docs/index.md +0 -0
  51. {trntensor-0.17.0 → trntensor-0.18.0}/docs/installation.md +0 -0
  52. {trntensor-0.17.0 → trntensor-0.18.0}/docs/quickstart.md +0 -0
  53. {trntensor-0.17.0 → trntensor-0.18.0}/docs/tutorial_df_mp2.md +0 -0
  54. {trntensor-0.17.0 → trntensor-0.18.0}/examples/df_mp2_einsum.py +0 -0
  55. {trntensor-0.17.0 → trntensor-0.18.0}/infra/terraform/README.md +0 -0
  56. {trntensor-0.17.0 → trntensor-0.18.0}/infra/terraform/main.tf +0 -0
  57. {trntensor-0.17.0 → trntensor-0.18.0}/mkdocs.yml +0 -0
  58. {trntensor-0.17.0 → trntensor-0.18.0}/scripts/bench_to_md.py +0 -0
  59. {trntensor-0.17.0 → trntensor-0.18.0}/scripts/run_benchmarks.sh +0 -0
  60. {trntensor-0.17.0 → trntensor-0.18.0}/scripts/run_neuron_profile.sh +0 -0
  61. {trntensor-0.17.0 → trntensor-0.18.0}/scripts/run_neuron_tests.sh +0 -0
  62. {trntensor-0.17.0 → trntensor-0.18.0}/scripts/run_reduce_parallel_multirank.py +0 -0
  63. {trntensor-0.17.0 → trntensor-0.18.0}/scripts/run_simulator_tests.sh +0 -0
  64. {trntensor-0.17.0 → trntensor-0.18.0}/setup.cfg +0 -0
  65. {trntensor-0.17.0 → trntensor-0.18.0}/tests/test_decompose.py +0 -0
  66. {trntensor-0.17.0 → trntensor-0.18.0}/tests/test_nki.py +0 -0
  67. {trntensor-0.17.0 → trntensor-0.18.0}/tests/test_nki_compile.py +0 -0
  68. {trntensor-0.17.0 → trntensor-0.18.0}/tests/test_nki_sim.py +0 -0
  69. {trntensor-0.17.0 → trntensor-0.18.0}/tests/test_parallel.py +0 -0
  70. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor/decompose.py +0 -0
  71. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor/nki/__init__.py +0 -0
  72. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor/nki/_kernels.py +0 -0
  73. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor/nki/collectives.py +0 -0
  74. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor/parallel.py +0 -0
  75. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor/py.typed +0 -0
  76. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor.egg-info/dependency_links.txt +0 -0
  77. {trntensor-0.17.0 → trntensor-0.18.0}/trntensor.egg-info/top_level.txt +0 -0
@@ -30,8 +30,10 @@ jobs:
30
30
  - uses: astral-sh/setup-uv@v6
31
31
  with:
32
32
  python-version: ${{ matrix.python-version }}
33
- - run: uv sync --extra dev
34
- - run: uv run pytest tests/ -v -x --tb=short -m "not neuron and not nki_simulator" --cov=trntensor --cov-report=xml
33
+ # uv pip (not uv sync): sync resolves every extra, and [torchneuron]'s nki>=0.6
34
+ # lives only on the Neuron index.
35
+ - run: uv venv && uv pip install -e ".[dev]"
36
+ - run: uv run --no-sync pytest tests/ -v -x --tb=short -m "not neuron and not nki_simulator" --cov=trntensor --cov-report=xml
35
37
 
36
38
  - name: Upload coverage reports to Codecov
37
39
  uses: codecov/codecov-action@v5
@@ -50,10 +52,10 @@ jobs:
50
52
  - uses: astral-sh/setup-uv@v6
51
53
  with:
52
54
  python-version: "3.12"
53
- - run: uv sync --extra dev
55
+ - run: uv venv && uv pip install -e ".[dev]"
54
56
  - name: Install NKI simulator
55
57
  run: uv pip install --extra-index-url https://pip.repos.neuron.amazonaws.com "nki>=0.3.0"
56
58
  - name: Run simulator-backed kernel tests
57
59
  env:
58
60
  TRNTENSOR_USE_SIMULATOR: "1"
59
- run: uv run pytest tests/ -v -m nki_simulator --tb=short
61
+ run: uv run --no-sync pytest tests/ -v -m nki_simulator --tb=short
@@ -7,12 +7,103 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.18.0] — 2026-10-30
11
+
12
+ Native TorchNeuron dispatch (Trn2/Trn3) alongside the PyTorch/XLA path (Trn1, maintenance); validated on Trn2 (trn2.3xlarge, Neuron SDK 2.33.0) — see trnsci/trnsci#35.
13
+
14
+ Headline performance: native `"auto"` routes to aten on the device, and the planner contraction-order fix takes the DF-MP2 AO→MO transform (`mi,na,mnP->iaP`, nb=256) from 70 to 1.16 ms (#49).
15
+
16
+ ### Changed
17
+
18
+ - **Native dispatch thresholds are now measured (#41).** They were measured
19
+ on trn2.3xlarge (SDK 2.33, NKI 0.7.0) with `scripts/bench_native_einsum.py`
20
+ and `scripts/autotune_dispatch.py`.
21
+ - On device-resident operands, aten `torch.matmul` / `torch.bmm` beat the
22
+ NKI matmul and batched-matmul kernels at every size (128³ to 4096³, fp32
23
+ and bf16). The native pinned threshold now defaults to `never`, so
24
+ device work runs as aten on `neuron`.
25
+ - Host operands at or above `TRNTENSOR_MIN_NKI_FLOPS_NATIVE` (now
26
+ 500 M multiply-adds, previously a provisional 1 G) are offloaded to
27
+ aten on the device instead of to the NKI kernel.
28
+ - Thresholds accept `never` / `inf`.
29
+ - The XLA path is unchanged.
30
+ - **Native `"auto"` runs the fused quantum primitives as aten.** This covers
31
+ `mp2_energy` and `ao_to_mo_transform`. On Trn2 the AO→MO kernel measured
32
+ 6–20× slower than aten, and the MP2 kernel 1.0–1.5× slower.
33
+ `set_backend("nki")` still runs both kernels, and the XLA path is
34
+ unchanged.
35
+ - **`mp2_energy` reference path is vectorized.** Without NKI it now uses
36
+ aten ops chunked over `i` instead of a nocc² Python loop. That is 10–40×
37
+ faster on device. On an Apple M-series CPU it is 3× faster at small sizes
38
+ and about even (±10%) at `nocc=32, nvir=128, naux=512`.
39
+ - **`ao_to_mo_transform` reference path** is now two explicit binary
40
+ contractions.
41
+
42
+ ### Fixed
43
+
44
+ - **Greedy contraction path no longer starts with an outer product.** For
45
+ example, `einsum("mi,na,mnP->iaP")` used to contract `mi,na` first,
46
+ building a 4-index intermediate. Pairs that share an index are now
47
+ preferred.
48
+ - **`scripts/autotune_dispatch.py` now reports M·K·N**, the quantity dispatch
49
+ compares, where it used to report 2·M·K·N. On the native path it now
50
+ derives both thresholds: host vs device offload, and NKI vs aten on device.
51
+
10
52
  ## [0.17.0] — 2026-10-28
11
53
 
12
54
  Validated on Trn2 (trn2.3xlarge, Neuron SDK 2.33.0, native TorchNeuron device) — see trnsci/trnsci#35.
13
55
 
14
56
  ### Added
15
57
 
58
+ - **Native PyTorch dispatch on TorchNeuron (#41, trnsci/trnsci#35).** On
59
+ TorchNeuron (Neuron SDK 2.33+, `torch-neuronx` 2.14, PyTorch 2.14,
60
+ Trn2/Trn3) every host-wrapper NKI kernel is a PyTorch custom op registered
61
+ with `torch_neuronx.nki_op` and launched on `device="neuron"`, with no
62
+ `torch_xla` import: `trntensor::matmul`, `batched_matmul`, `mp2_energy`,
63
+ `ao_to_mo_transform`. Kernel bodies are unchanged. The PyTorch/XLA path
64
+ (Trn1) is unchanged.
65
+ - The dispatch path is detected on the first kernel launch, not at
66
+ import: native when `torch_neuronx` exports `nki_op` and
67
+ `torch_neuronx.device_count() > 0`, XLA when `torch_xla` is installed,
68
+ otherwise none. `TRNSCI_NKI_DISPATCH=auto|native|xla` (suite-wide)
69
+ forces a path; forcing an unavailable one raises.
70
+ - The ops are registered lazily (`trntensor/nki/_native.py` is imported
71
+ only on the native path). Every launch goes through one helper,
72
+ `dispatch._launch`. That covers `einsum` (matmul/bmm/path steps),
73
+ `multi_einsum` (batched and per-contraction), `ao_to_mo_transform`,
74
+ `mp2_energy`, and `precision="sr"` (which passes `use_sr=True` to the op).
75
+ `precision="kahan"` stays a host fp64 contraction, and `"dd"` still
76
+ raises on NKI hosts (trnblas#22). The reduce-parallel `nki.collectives`
77
+ all-reduce from Phase 4 is unchanged.
78
+ - New `to_device` / `from_device` place tensors on the active path's
79
+ device (`neuron` or XLA). `to_xla` / `from_xla` stay public. On the
80
+ native path `to_xla` places on `neuron`, so existing residency code keeps
81
+ working. `multi_einsum` pre-pins shared operands on either device.
82
+ - FLOP dispatch thresholds are path-aware. The XLA values are unchanged
83
+ (2 GFLOPs unpinned, 1 GFLOPs pinned). The native path uses provisional
84
+ 1 GFLOPs / 128 MFLOPs, because eager native dispatch costs about 0.2 ms
85
+ per launch. Override them with `TRNTENSOR_MIN_NKI_FLOPS_NATIVE[_PINNED]`
86
+ or the `trntensor_min_nki_flops_native[_pinned]` autotune cache keys.
87
+ `scripts/autotune_dispatch.py` measures and writes the native keys on a
88
+ TorchNeuron host.
89
+ - Dormant deprecation hook: with `TRNSCI_XLA_DEPRECATION=1` (off by
90
+ default), selecting the XLA path and calling `to_xla` / `from_xla` each
91
+ emit a one-time `DeprecationWarning`. Migration: replace `to_xla` with
92
+ `to_device` and `from_xla` with `from_device` (docs/api/nki.md).
93
+ - The `mp2_energy` CPU fallback copies `neuron`-resident operands to the
94
+ host before its scalar loop, then moves the result back.
95
+ - New extras: `[torchneuron]` (`torch>=2.14`, `torch-neuronx>=2.14`,
96
+ `nki>=0.6`) and `[neuron]` for the XLA lineage (`torch-neuronx>=2.9`,
97
+ `nki>=0.3.0`).
98
+ - Tests: `tests/conftest.py` sets `NKI_ENABLE_TRACE_CACHE=0` by default.
99
+ New CPU tests in `tests/test_native_dispatch.py` cover path selection,
100
+ launch routing for every entry point, lazy registration, op schemas,
101
+ fake shapes, thresholds, and the deprecation hook. New
102
+ `@pytest.mark.neuron` tests in `tests/test_nki_native.py` have not yet
103
+ run on hardware. `TestXlaResidency::test_matmul_stays_on_xla` is now
104
+ path-aware: it expects `neuron` on TorchNeuron and `xla` on Trn1, and
105
+ still checks residency.
106
+
16
107
  - **Reduce-parallel sharding on real multi-rank all-reduce (Phase 4 prototype).**
17
108
  Before this change, reduce-parallel and mixed output/reduce-parallel einsums
18
109
  raised `NotImplementedError` whenever `HAS_NKI` was true.
@@ -24,7 +24,10 @@ trntensor/
24
24
  │ ├── decompose.py # cp_decompose, tucker_decompose (HOSVD)
25
25
  │ └── nki/
26
26
  │ ├── __init__.py
27
- │ └── dispatch.py # Fused contraction kernels
27
+ │ ├── _kernels.py # @nki.jit kernels
28
+ │ ├── _native.py # TorchNeuron custom ops (nki_op), loaded lazily
29
+ │ ├── collectives.py # nki.collectives all-reduce (reduce-parallel)
30
+ │ └── dispatch.py # host wrappers, dispatch-path selection, _launch
28
31
  ├── tests/
29
32
  │ ├── test_einsum.py # Matmul, bmm, 4-index transform, DF-MP2 pattern
30
33
  │ └── test_decompose.py # CP rank-1/low-rank, Tucker orthogonality
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trntensor
3
- Version: 0.17.0
3
+ Version: 0.18.0
4
4
  Summary: Tensor contractions for AWS Trainium via NKI
5
5
  Author-email: Scott Friedman <scttfrdmn@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -18,8 +18,15 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: torch>=2.1
20
20
  Requires-Dist: numpy>=1.24
21
+ Provides-Extra: neuron
22
+ Requires-Dist: nki>=0.3.0; extra == "neuron"
23
+ Requires-Dist: torch-neuronx>=2.9; extra == "neuron"
24
+ Provides-Extra: torchneuron
25
+ Requires-Dist: torch>=2.14; extra == "torchneuron"
26
+ Requires-Dist: torch-neuronx>=2.14; extra == "torchneuron"
27
+ Requires-Dist: nki>=0.6; extra == "torchneuron"
21
28
  Provides-Extra: dev
22
- Requires-Dist: pytest>=7.0; extra == "dev"
29
+ Requires-Dist: pytest<9,>=7.0; extra == "dev"
23
30
  Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
24
31
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
25
32
  Requires-Dist: ruff>=0.6; extra == "dev"
@@ -59,8 +66,32 @@ Suite-wide tracker: [trnsci/trnsci#1](https://github.com/trnsci/trnsci/issues/1)
59
66
 
60
67
  ```bash
61
68
  pip install trntensor
69
+
70
+ # Trn2/Trn3 with TorchNeuron (native PyTorch, Neuron SDK 2.33+)
71
+ pip install "trntensor[torchneuron]" --extra-index-url https://pip.repos.neuron.amazonaws.com
72
+
73
+ # Trn1 / PyTorch-XLA lineage
74
+ pip install "trntensor[neuron]" --extra-index-url https://pip.repos.neuron.amazonaws.com
62
75
  ```
63
76
 
77
+ Kernels dispatch on whichever Neuron PyTorch lineage is installed:
78
+
79
+ - **TorchNeuron (native)**: Trn2/Trn3, Neuron SDK 2.33+, Python ≥ 3.12,
80
+ `torch` 2.14, `torch-neuronx` 2.14.*. Each kernel is a PyTorch custom op
81
+ (`torch.ops.trntensor.*`) on the `neuron` device; no `torch_xla`.
82
+ - **PyTorch/XLA**: Trn1 (and any host on the `torch-neuronx` 2.9 lineage).
83
+ Kernels dispatch through `torch_xla`, unchanged.
84
+
85
+ trntensor picks the native path when the native `torch_neuronx` and a
86
+ `neuron` device are present, and the XLA path when `torch_xla` is
87
+ installed; otherwise it runs on PyTorch. Set `TRNSCI_NKI_DISPATCH=native`
88
+ or `xla` (shared by all trnsci libraries) to force a path. Pin operands on
89
+ the device with `to_device` / `from_device`; the older `to_xla` /
90
+ `from_xla` still work on both lineages (see the
91
+ [migration note](docs/api/nki.md#migrating-from-to_xla-from_xla)). With an
92
+ editable install (`pip install -e`), set `NKI_ENABLE_TRACE_CACHE=0`: the NKI
93
+ kernel cache only versions installed wheels and can serve stale kernels.
94
+
64
95
  ## Usage
65
96
 
66
97
  ```python
@@ -103,7 +134,8 @@ core, factors = trntensor.tucker_decompose(tensor, ranks=(5, 5, 5))
103
134
  - [x] Tucker decomposition (HOSVD)
104
135
  - [x] DF-MP2 einsum example
105
136
  - [x] NKI fused contraction kernels (`mp2_energy`, `ao_to_mo_transform`)
106
- - [x] XLA operand residency (`to_xla` / `from_xla`)
137
+ - [x] Device operand residency (`to_device` / `from_device`; `to_xla` / `from_xla` kept)
138
+ - [x] Native TorchNeuron dispatch (Trn2/Trn3, `torch.ops.trntensor.*`) alongside PyTorch/XLA (Trn1)
107
139
  - [x] NKI CPU simulator + `nki-simulator` CI gate
108
140
  - [x] Optimal contraction ordering — greedy path search for 3+ operands
109
141
  - [x] Multi-contraction shared-operand XLA pre-pinning
@@ -1,34 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: trntensor
3
- Version: 0.17.0
4
- Summary: Tensor contractions for AWS Trainium via NKI
5
- Author-email: Scott Friedman <scttfrdmn@gmail.com>
6
- License-Expression: Apache-2.0
7
- Project-URL: Homepage, https://github.com/trnsci/trntensor
8
- Project-URL: Documentation, https://trnsci.dev/trntensor/
9
- Project-URL: Repository, https://github.com/trnsci/trntensor
10
- Project-URL: Issues, https://github.com/trnsci/trntensor/issues
11
- Classifier: Development Status :: 3 - Alpha
12
- Classifier: Intended Audience :: Science/Research
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Topic :: Scientific/Engineering
15
- Classifier: Topic :: Scientific/Engineering :: Mathematics
16
- Requires-Python: >=3.10
17
- Description-Content-Type: text/markdown
18
- License-File: LICENSE
19
- Requires-Dist: torch>=2.1
20
- Requires-Dist: numpy>=1.24
21
- Provides-Extra: dev
22
- Requires-Dist: pytest>=7.0; extra == "dev"
23
- Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
24
- Requires-Dist: pytest-cov>=4.0; extra == "dev"
25
- Requires-Dist: ruff>=0.6; extra == "dev"
26
- Requires-Dist: pre-commit>=3.7; extra == "dev"
27
- Requires-Dist: scipy>=1.11; extra == "dev"
28
- Requires-Dist: mkdocs>=1.5; extra == "dev"
29
- Requires-Dist: mkdocs-material>=9.0; extra == "dev"
30
- Dynamic: license-file
31
-
32
1
  # trntensor
33
2
 
34
3
  [![CI](https://github.com/trnsci/trntensor/actions/workflows/ci.yml/badge.svg)](https://github.com/trnsci/trntensor/actions/workflows/ci.yml)
@@ -59,8 +28,32 @@ Suite-wide tracker: [trnsci/trnsci#1](https://github.com/trnsci/trnsci/issues/1)
59
28
 
60
29
  ```bash
61
30
  pip install trntensor
31
+
32
+ # Trn2/Trn3 with TorchNeuron (native PyTorch, Neuron SDK 2.33+)
33
+ pip install "trntensor[torchneuron]" --extra-index-url https://pip.repos.neuron.amazonaws.com
34
+
35
+ # Trn1 / PyTorch-XLA lineage
36
+ pip install "trntensor[neuron]" --extra-index-url https://pip.repos.neuron.amazonaws.com
62
37
  ```
63
38
 
39
+ Kernels dispatch on whichever Neuron PyTorch lineage is installed:
40
+
41
+ - **TorchNeuron (native)**: Trn2/Trn3, Neuron SDK 2.33+, Python ≥ 3.12,
42
+ `torch` 2.14, `torch-neuronx` 2.14.*. Each kernel is a PyTorch custom op
43
+ (`torch.ops.trntensor.*`) on the `neuron` device; no `torch_xla`.
44
+ - **PyTorch/XLA**: Trn1 (and any host on the `torch-neuronx` 2.9 lineage).
45
+ Kernels dispatch through `torch_xla`, unchanged.
46
+
47
+ trntensor picks the native path when the native `torch_neuronx` and a
48
+ `neuron` device are present, and the XLA path when `torch_xla` is
49
+ installed; otherwise it runs on PyTorch. Set `TRNSCI_NKI_DISPATCH=native`
50
+ or `xla` (shared by all trnsci libraries) to force a path. Pin operands on
51
+ the device with `to_device` / `from_device`; the older `to_xla` /
52
+ `from_xla` still work on both lineages (see the
53
+ [migration note](docs/api/nki.md#migrating-from-to_xla-from_xla)). With an
54
+ editable install (`pip install -e`), set `NKI_ENABLE_TRACE_CACHE=0`: the NKI
55
+ kernel cache only versions installed wheels and can serve stale kernels.
56
+
64
57
  ## Usage
65
58
 
66
59
  ```python
@@ -103,7 +96,8 @@ core, factors = trntensor.tucker_decompose(tensor, ranks=(5, 5, 5))
103
96
  - [x] Tucker decomposition (HOSVD)
104
97
  - [x] DF-MP2 einsum example
105
98
  - [x] NKI fused contraction kernels (`mp2_energy`, `ao_to_mo_transform`)
106
- - [x] XLA operand residency (`to_xla` / `from_xla`)
99
+ - [x] Device operand residency (`to_device` / `from_device`; `to_xla` / `from_xla` kept)
100
+ - [x] Native TorchNeuron dispatch (Trn2/Trn3, `torch.ops.trntensor.*`) alongside PyTorch/XLA (Trn1)
107
101
  - [x] NKI CPU simulator + `nki-simulator` CI gate
108
102
  - [x] Optimal contraction ordering — greedy path search for 3+ operands
109
103
  - [x] Multi-contraction shared-operand XLA pre-pinning
@@ -0,0 +1,174 @@
1
+ # NKI dispatch
2
+
3
+ Backend selection between the PyTorch fallback and the NKI-accelerated
4
+ path on Trainium. Kernels live in `trntensor.nki._kernels`; the
5
+ public-facing dispatch is in `trntensor.nki.dispatch`.
6
+
7
+ ## `set_backend(backend: str)`
8
+
9
+ - `"auto"` — use NKI if `nki` is importable, else PyTorch
10
+ - `"pytorch"` — always use `torch.matmul` / `torch.einsum`
11
+ - `"nki"` — require NKI; raises `RuntimeError` on non-Neuron hosts
12
+
13
+ ## `get_backend() -> str`
14
+
15
+ Return the current backend selection.
16
+
17
+ ## `HAS_NKI: bool`
18
+
19
+ Module-level flag set at import time. `True` when `nki` (0.3.0+) is
20
+ importable, otherwise `False`.
21
+
22
+ ## Dispatch path
23
+
24
+ On Neuron hardware, kernels run on one of two `torch-neuronx` lineages,
25
+ detected on the first kernel launch (never at import):
26
+
27
+ - **Native (TorchNeuron, Trn2/Trn3, Neuron SDK 2.33+)** — chosen when
28
+ `torch_neuronx` exports `nki_op` and a `neuron` device is available
29
+ (`torch_neuronx.device_count() > 0`). Each kernel is a PyTorch custom op
30
+ on `device="neuron"`, also callable directly and inside
31
+ `torch.compile(backend="neuron")`: `torch.ops.trntensor.matmul`,
32
+ `batched_matmul`, `mp2_energy`, `ao_to_mo_transform`. The ops are
33
+ registered only once this path is selected, and this path never imports
34
+ `torch_xla`.
35
+ - **XLA (Trn1)** — chosen when `torch_xla` is installed and the native
36
+ path is unavailable. Kernels run on the `torch_xla` device, unchanged.
37
+ - Otherwise there is no device path: the host wrappers fall back to
38
+ `torch` (or raise under `TRNTENSOR_REQUIRE_NKI=1`), as before.
39
+
40
+ `set_backend` is unaffected: it still chooses NKI vs PyTorch. Set the
41
+ suite-wide `TRNSCI_NKI_DISPATCH=native` or `xla` to force a path; forcing an
42
+ unavailable path raises `RuntimeError`.
43
+
44
+ The reduce-parallel all-reduce kernel (`trntensor.nki.collectives`) is
45
+ native-only already; it keeps its `torch_neuronx.wrap_nki` launch because
46
+ its `ReplicaGroup` argument is a compile-time constant that a custom-op
47
+ schema cannot carry.
48
+
49
+ Precision modes follow the same launch: `precision="sr"` passes
50
+ `use_sr=True` to the `matmul` / `batched_matmul` op (stochastic rounding
51
+ still needs `NEURON_RT_STOCHASTIC_ROUNDING_EN=1` at process start and warns
52
+ once otherwise); `precision="kahan"` contracts in true fp64 on the host
53
+ (Trainium has no fp64 datapath) on both paths; `precision="dd"` still
54
+ raises on NKI hosts pending trnblas#22.
55
+
56
+ ## `to_device(tensor) -> Tensor` / `from_device(tensor) -> Tensor`
57
+
58
+ Pin an operand on the Trainium device of the active dispatch path —
59
+ `neuron` on TorchNeuron, the XLA device on Trn1 — so repeated trntensor
60
+ calls skip the host↔device transfer that otherwise dominates dispatch
61
+ overhead.
62
+
63
+ ```python
64
+ import trntensor
65
+
66
+ # One-time transfer onto the accelerator.
67
+ eri_d = trntensor.to_device(eri)
68
+ C_occ_d = trntensor.to_device(C_occ)
69
+ C_vir_d = trntensor.to_device(C_vir)
70
+ eps_occ_d = trntensor.to_device(eps_occ)
71
+ eps_vir_d = trntensor.to_device(eps_vir)
72
+
73
+ # Full DF-MP2 pipeline — B_d never leaves the device.
74
+ B_d = trntensor.ao_to_mo_transform(eri_d, C_occ_d, C_vir_d)
75
+ E_d = trntensor.mp2_energy(B_d, eps_occ_d, eps_vir_d)
76
+
77
+ # Pull the scalar back when we actually need it in Python.
78
+ E = trntensor.from_device(E_d)
79
+ ```
80
+
81
+ - `to_device`: no-op when the tensor is already on a Neuron device.
82
+ Raises `RuntimeError` on hosts without the NKI runtime.
83
+ - `from_device`: no-op when the tensor is already on CPU.
84
+
85
+ When every operand is already on the device, dispatch skips the per-call
86
+ transfer, uses the lower "pinned" FLOP threshold, and returns the result on
87
+ the device — the caller controls when to pull back.
88
+
89
+ ## `to_xla(tensor) -> Tensor` / `from_xla(tensor) -> Tensor`
90
+
91
+ The original names, kept for compatibility. On Trn1 they behave exactly as
92
+ before. On TorchNeuron there is no XLA device, so `to_xla` places the
93
+ tensor on `neuron` instead (residency works the same way) and `from_xla`
94
+ moves it back to CPU.
95
+
96
+ ### Migrating from `to_xla` / `from_xla`
97
+
98
+ Replace `to_xla` with `to_device` and `from_xla` with `from_device`. Same
99
+ behaviour on Trn1; on Trn2/Trn3 the new names say what they do. Checks such
100
+ as `t.device.type == "xla"` become `t.device.type in ("xla", "neuron")`.
101
+
102
+ `to_xla` / `from_xla` stay available. When the suite-wide
103
+ `TRNSCI_XLA_DEPRECATION=1` is set (off by default; trnsci turns it on at
104
+ TorchNeuron GA), each emits a one-time `DeprecationWarning` pointing at the
105
+ new names, and selecting the XLA dispatch path emits: "the PyTorch/XLA path
106
+ is deprecated on Trn2/Trn3; use TorchNeuron (native). Trn1/Inf2 remain
107
+ supported in maintenance mode."
108
+
109
+ ## Environment variables
110
+
111
+ | Variable | Effect |
112
+ |---|---|
113
+ | `TRNTENSOR_REQUIRE_NKI=1` | Re-raise kernel exceptions instead of falling back to PyTorch. Useful in the validation loop to surface silent kernel breakage. |
114
+ | `TRNTENSOR_MIN_NKI_FLOPS=<int>` | XLA path: override the FLOP threshold (M·K·N) below which dispatch skips NKI and uses `torch.matmul` / `torch.bmm` directly. Default: `2_000_000_000`. Set to `0` to force NKI for kernel validation. |
115
+ | `TRNTENSOR_MIN_NKI_FLOPS_PINNED=<int>` | XLA path, operands already on the device. Default: `1_000_000_000`. |
116
+ | `TRNTENSOR_MIN_NKI_FLOPS_NATIVE=<int>` | Native (TorchNeuron) path, operands on CPU: M·K·N at which the work leaves the host for the `neuron` device. Below it, `torch.matmul` / `torch.bmm` run on the host. Above it, the operands are offloaded. The offload runs aten unless the pinned threshold below selects NKI. Default: `500_000_000` (measured on Trn2, see below). |
117
+ | `TRNTENSOR_MIN_NKI_FLOPS_NATIVE_PINNED=<int>\|never` | Native path: M·K·N at which the NKI kernel replaces aten on the device. Below it, device-resident operands run `torch.matmul` / `torch.bmm` (aten) on `neuron`. Default: `never`, because on Trn2 aten beat the NKI kernels at every measured size. |
118
+ | `TRNSCI_NKI_DISPATCH=auto\|native\|xla` | Suite-wide. Force the native or XLA dispatch path; `auto` (default) detects. Forcing an unavailable path raises. |
119
+ | `TRNSCI_XLA_DEPRECATION=1` | Suite-wide, off by default. Emit the one-time XLA-path and `to_xla` / `from_xla` deprecation warnings. |
120
+ | `NKI_ENABLE_TRACE_CACHE=0` | TorchNeuron setting (not trntensor's). Turns off the cross-process NKI kernel cache, which cannot version editable installs and may serve a stale kernel after a source edit. Set it with `pip install -e`; the test suite sets it by default. |
121
+ | `TRNTENSOR_FORCE_BACKEND=pytorch\|nki\|auto` | Override the backend selection at runtime without calling `set_backend()`. Used by benchmarks to sweep both paths on the same machine. |
122
+ | `TRNTENSOR_USE_SIMULATOR=1` | Route kernel dispatch through `nki.simulate(kernel)(numpy_args)` on CPU instead of XLA → NEFF → hardware. Catches Python-trace-level errors without AWS round-trips. MLIR verifier errors remain hardware-only. |
123
+
124
+ ### Native thresholds: measured on Trn2
125
+
126
+ These were measured on a trn2.3xlarge (Neuron SDK 2.33, NKI 0.7.0), using one
127
+ NeuronCore and two host threads, with `scripts/bench_native_einsum.py` and
128
+ `scripts/autotune_dispatch.py` (#41):
129
+
130
+ - **Pinned operands: NKI never beats aten.** On device-resident operands,
131
+ aten `torch.matmul` / `torch.bmm` beat the trntensor NKI matmul and
132
+ batched-matmul kernels at every size from 128³ to 4096³, in both fp32 and
133
+ bf16. Examples:
134
+ - 4096³ bf16: 1.38 ms with aten, 6.82 ms with NKI.
135
+ - 1024³ fp32: 0.20 ms with aten, 0.55 ms with NKI.
136
+
137
+ The eager launch floor is about 0.13 ms for an aten op and about 0.24 ms for
138
+ an NKI custom op.
139
+ - **Host operands: offload to aten from about 0.5 G multiply-adds.**
140
+ Offloading to aten (upload, run on device, download) beats offloading
141
+ through the NKI kernel at every size. It also beats host torch:
142
+ - fp32: from about 134 M multiply-adds (512³) when the host has two
143
+ threads.
144
+ - bf16: from about 0.5 to 1 G multiply-adds.
145
+
146
+ The default of 500 M sits between these. A host with more threads moves the
147
+ crossover up. Re-measure on your host with `scripts/autotune_dispatch.py`.
148
+
149
+ Under `"auto"` on the native path, the fused quantum kernels (`mp2_energy`,
150
+ `ao_to_mo_transform`) also run as aten, because both measured slower than
151
+ aten on the device:
152
+
153
+ - AO→MO was 6–20× slower.
154
+ - MP2 energy was 1.0–1.5× slower, reaching parity at best.
155
+
156
+ `set_backend("nki")` still runs the NKI kernels. The kernels are unchanged,
157
+ and the XLA path keeps them under `"auto"`.
158
+
159
+ ## Kernels (internal)
160
+
161
+ - `matmul_kernel(a, b)` — 2-index matmul with stationary-A tile reuse.
162
+ Tile constants: `TILE_M=128`, `TILE_K=128`, `TILE_N=512`.
163
+ - `batched_matmul_kernel(a, b)` — per-batch-slice matmul. Batch dim
164
+ iterated via `nl.affine_range`; each slice reuses the stationary-A
165
+ tile layout.
166
+
167
+ The DF-MP2 pair pattern `einsum("ap,bp->ab")` is served by
168
+ `matmul_kernel` through the planner's `transB=True` route — no
169
+ dedicated kernel needed. Fused energy-denominator kernel is tracked
170
+ in [#13][i13].
171
+
172
+ [i13]: https://github.com/trnsci/trntensor/issues/13
173
+
174
+ [m2]: https://github.com/trnsci/trntensor/milestone/2
@@ -40,9 +40,15 @@ print(f"E_MP2 = {E.item():.6f}")
40
40
 
41
41
  ### Backend behaviour
42
42
 
43
- - **CPU**: falls back to a Python loop over `(i, j)` pairs composing `torch.einsum`
44
- and element-wise ops. Same as `examples/df_mp2_einsum.py`.
45
- - **Trainium (NKI)**: dispatches a single `@nki.jit` program that
43
+ - **CPU, and native TorchNeuron under `"auto"`**: aten ops on the operands' device.
44
+ `T[i, j, a, b]` is contracted with `torch.einsum` in chunks over `i`, which
45
+ bounds peak memory to about 64 MiB of `T` at a time. The `T`-weighted sum is
46
+ vectorized. This replaces the former Python loop over `(i, j)` pairs, which
47
+ ran 10–40× slower on a Trainium device.
48
+ On Trn2 the aten form matched or beat the fused kernel. For example, with
49
+ `nocc=32, nvir=128, naux=512` it took 3.1 ms, against 4.5 ms for the
50
+ kernel (#41).
51
+ - **Trainium (NKI): the PyTorch/XLA path, or `set_backend("nki")`**: dispatches a single `@nki.jit` program that
46
52
  - accumulates `T` in PSUM via `nisa.nc_matmul`,
47
53
  - builds `Δ` on the Vector Engine from SBUF-resident `ε` tiles,
48
54
  - folds the energy into a scalar accumulator in SBUF,
@@ -85,8 +91,12 @@ E = trntensor.mp2_energy(B, eps_occ, eps_vir)
85
91
 
86
92
  ### Backend behaviour
87
93
 
88
- - **CPU**: `torch.einsum("mi,na,mnP->iaP", C_occ, C_vir, eri)` — a single three-operand einsum.
89
- - **Trainium (NKI)**: dispatches a single `@nki.jit` program that
94
+ - **CPU, and native TorchNeuron under `"auto"`**: two explicit aten steps,
95
+ `einsum("mi,mnP->inP")` then `einsum("na,inP->iaP")`, on the operands'
96
+ device. On Trn2 this was 6–20× faster than the fused kernel. For example,
97
+ with `nbasis=128, nocc=16, naux=256` it took 0.82 ms, against 16.4 ms for
98
+ the kernel (#41).
99
+ - **Trainium (NKI): the PyTorch/XLA path, or `set_backend("nki")`**: dispatches a single `@nki.jit` program that
90
100
  - loads `C_occ` and `C_vir` once, SBUF-resident across all P iterations,
91
101
  - for each auxiliary index P: two sequential `nisa.nc_matmul` calls with the intermediate `(i, ν)` tile round-tripping through kernel-scratch HBM to handle the partition-dim change,
92
102
  - writes one `(nocc, nvir)` slice per P to the output HBM tensor.
@@ -15,7 +15,7 @@ as the primary design surface.
15
15
  | Multiple `Plan` objects with HBM intermediates | `multi_einsum()` with shared-operand pre-pinning |
16
16
  | `cutensorDecompositionCreate` (Tucker HOSVD) | `tucker_decompose()` |
17
17
  | Custom THC / Cholesky-factored ERIs | `cp_decompose()` (tensor hypercontraction) |
18
- | CUDA stream + `cudaMemcpy` to pin device memory | `to_xla(tensor)` / `from_xla(tensor)` |
18
+ | CUDA stream + `cudaMemcpy` to pin device memory | `to_device(tensor)` / `from_device(tensor)` (formerly `to_xla` / `from_xla`) |
19
19
 
20
20
  ## 2-Operand contractions — the generic path
21
21
 
@@ -119,12 +119,14 @@ XLA-resident copy is used for all subsequent contractions in the batch.
119
119
 
120
120
  On CPU (no NKI), `multi_einsum` behaves exactly like a loop over `einsum()`.
121
121
 
122
- ## XLA residency: `to_xla` / `from_xla`
122
+ ## Device residency: `to_device` / `from_device`
123
123
 
124
- The NKI dispatch path always transfers operands to the Trainium XLA device
125
- before kernel launch. For a single large matmul this cost is negligible; for a
126
- tight loop of small contractions it dominates. `to_xla` / `from_xla` expose the
127
- transfer explicitly so callers can control when it happens:
124
+ The NKI dispatch path always transfers operands to the Trainium device
125
+ (`neuron` on TorchNeuron, the XLA device on Trn1) before kernel launch. For a
126
+ single large matmul this cost is negligible; for a tight loop of small
127
+ contractions it dominates. `to_device` / `from_device` expose the transfer
128
+ explicitly so callers can control when it happens. `to_xla` / `from_xla` are
129
+ the older names for the same thing and still work on both lineages:
128
130
 
129
131
  ```python
130
132
  # Cold path: B transferred on every call (3 transfers for 3 calls)
@@ -132,17 +134,17 @@ for _ in range(3):
132
134
  result = trntensor.einsum("ij,jk->ik", A, B)
133
135
 
134
136
  # Residency path: B transferred once, result stays on device until needed
135
- A_x = trntensor.to_xla(A)
136
- B_x = trntensor.to_xla(B)
137
+ A_x = trntensor.to_device(A)
138
+ B_x = trntensor.to_device(B)
137
139
  for _ in range(3):
138
140
  result_x = trntensor.einsum("ij,jk->ik", A_x, B_x) # zero transfer cost
139
- result = trntensor.from_xla(result_x)
141
+ result = trntensor.from_device(result_x)
140
142
  ```
141
143
 
142
144
  The residency API maps to the CUDA concept of a device-pinned buffer, but with
143
145
  explicit Python-level transfer rather than stream-implicit staging.
144
146
 
145
- `to_xla` raises `RuntimeError` on hosts without `nki` installed.
147
+ `to_device` (like `to_xla`) raises `RuntimeError` on hosts without `nki` installed.
146
148
 
147
149
  ## 3+ operand einsums: contraction-path search
148
150
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "trntensor"
7
- version = "0.17.0"
7
+ version = "0.18.0"
8
8
  description = "Tensor contractions for AWS Trainium via NKI"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -25,8 +25,24 @@ dependencies = [
25
25
  ]
26
26
 
27
27
  [project.optional-dependencies]
28
+ neuron = [
29
+ # PyTorch/XLA lineage (Trn1, and Trn2 on PyTorch 2.9). NKI 0.3.0 Stable
30
+ # (Neuron SDK 2.29) promotes `nki.*` as the canonical namespace. Ships with
31
+ # the Deep Learning AMI Neuron or via https://pip.repos.neuron.amazonaws.com.
32
+ "nki>=0.3.0",
33
+ "torch-neuronx>=2.9",
34
+ ]
35
+ torchneuron = [
36
+ # TorchNeuron / native PyTorch (Neuron SDK 2.33+, Trn2/Trn3; torch-neuronx
37
+ # 2.14 itself requires Python >= 3.12). Install with
38
+ # --extra-index-url https://pip.repos.neuron.amazonaws.com. The nki floor
39
+ # also keeps pip off the unrelated `nki` 0.0.1 on public PyPI.
40
+ "torch>=2.14",
41
+ "torch-neuronx>=2.14",
42
+ "nki>=0.6",
43
+ ]
28
44
  dev = [
29
- "pytest>=7.0",
45
+ "pytest>=7.0,<9", # pytest-benchmark 5.3 breaks on pytest 9 (Function._nodeid)
30
46
  "pytest-benchmark>=4.0",
31
47
  "pytest-cov>=4.0",
32
48
  "ruff>=0.6",
@@ -47,11 +63,13 @@ include = ["trntensor*"]
47
63
 
48
64
  # Neuron SDK note: on-hardware runs use the AWS Deep Learning AMI's
49
65
  # pre-built venv at /opt/aws_neuronx_venv_pytorch_*, which bundles
50
- # neuronxcc and the NKI runtime. There is no pip-install step for Neuron —
51
- # the DLAMI's terraform `most_recent = true` pulls the current image and
52
- # its venv tracks the current SDK (2.29 as of 2026-04-09, with NKI 0.3.0
53
- # Stable and the CPU simulator). trntensor does not declare neuronxcc in
54
- # its dep set; CPU-only installs resolve cleanly from PyPI.
66
+ # the NKI runtime. The DLAMI's terraform `most_recent = true` pulls the
67
+ # current image and its venv tracks the current SDK. Outside the DLAMI, use
68
+ # the `[torchneuron]` (Trn2/Trn3, native) or `[neuron]` (Trn1, PyTorch/XLA)
69
+ # extra with the Neuron pip repo. Both lineages publish a package named
70
+ # `torch-neuronx`, so pip markers cannot pick between them; choose the extra
71
+ # that matches the instance. The base dependency set stays CPU-only and
72
+ # resolves cleanly from PyPI.
55
73
 
56
74
  [tool.ruff]
57
75
  # Match the rest of the trnsci suite. Line length 100 is a compromise