trntensor 0.17.0__tar.gz → 0.19.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.
- {trntensor-0.17.0 → trntensor-0.19.0}/.github/workflows/ci.yml +6 -4
- {trntensor-0.17.0 → trntensor-0.19.0}/CHANGELOG.md +127 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/CLAUDE.md +4 -1
- {trntensor-0.17.0/trntensor.egg-info → trntensor-0.19.0}/PKG-INFO +35 -3
- trntensor-0.17.0/PKG-INFO → trntensor-0.19.0/README.md +26 -32
- trntensor-0.19.0/docs/api/nki.md +187 -0
- trntensor-0.19.0/docs/api/quantum.md +152 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/contraction_patterns.md +12 -10
- {trntensor-0.17.0 → trntensor-0.19.0}/pyproject.toml +25 -7
- {trntensor-0.17.0 → trntensor-0.19.0}/scripts/autotune_dispatch.py +162 -12
- trntensor-0.19.0/scripts/bench_native_einsum.py +396 -0
- trntensor-0.19.0/scripts/bench_native_kernels.py +199 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/tests/conftest.py +6 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/tests/test_einsum.py +2 -2
- trntensor-0.19.0/tests/test_native_dispatch.py +946 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/tests/test_nki_compile.py +28 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/tests/test_nki_kernels.py +74 -45
- trntensor-0.19.0/tests/test_nki_native.py +238 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/tests/test_plan.py +17 -1
- {trntensor-0.17.0 → trntensor-0.19.0}/tests/test_quantum.py +24 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/tests/test_sr_runtime.py +3 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor/__init__.py +4 -2
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor/einsum.py +26 -14
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor/nki/_kernels.py +224 -0
- trntensor-0.19.0/trntensor/nki/_native.py +114 -0
- trntensor-0.19.0/trntensor/nki/dispatch.py +1273 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor/plan.py +17 -6
- trntensor-0.19.0/trntensor/quantum.py +236 -0
- trntensor-0.17.0/README.md → trntensor-0.19.0/trntensor.egg-info/PKG-INFO +64 -1
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor.egg-info/SOURCES.txt +5 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor.egg-info/requires.txt +10 -1
- {trntensor-0.17.0 → trntensor-0.19.0}/uv.lock +1 -1
- trntensor-0.17.0/docs/api/nki.md +0 -78
- trntensor-0.17.0/docs/api/quantum.md +0 -99
- trntensor-0.17.0/trntensor/nki/dispatch.py +0 -547
- trntensor-0.17.0/trntensor/quantum.py +0 -173
- {trntensor-0.17.0 → trntensor-0.19.0}/.github/workflows/notify-umbrella.yml +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/.github/workflows/publish.yml +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/.gitignore +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/.pre-commit-config.yaml +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/CODE_OF_CONDUCT.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/CONTRIBUTING.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/LICENSE +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/benchmarks/__init__.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/benchmarks/bench_einsum.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/api/decompose.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/api/einsum.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/api/index.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/api/plan.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/architecture.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/aws_setup.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/benchmarks.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/design/matmul_dispatch_findings.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/design/matmul_kernel_profile.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/developing_kernels.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/index.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/installation.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/quickstart.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/docs/tutorial_df_mp2.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/examples/df_mp2_einsum.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/infra/terraform/README.md +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/infra/terraform/main.tf +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/mkdocs.yml +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/scripts/bench_to_md.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/scripts/run_benchmarks.sh +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/scripts/run_neuron_profile.sh +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/scripts/run_neuron_tests.sh +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/scripts/run_reduce_parallel_multirank.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/scripts/run_simulator_tests.sh +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/setup.cfg +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/tests/test_decompose.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/tests/test_nki.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/tests/test_nki_sim.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/tests/test_parallel.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor/decompose.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor/nki/__init__.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor/nki/collectives.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor/parallel.py +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor/py.typed +0 -0
- {trntensor-0.17.0 → trntensor-0.19.0}/trntensor.egg-info/dependency_links.txt +0 -0
- {trntensor-0.17.0 → trntensor-0.19.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
|
-
|
|
34
|
-
|
|
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
|
|
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,139 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.19.0] — 2026-10-31
|
|
11
|
+
|
|
12
|
+
Validated on Trn2 (trn2.3xlarge, Neuron SDK 2.33.0, native TorchNeuron) — see trnsci/trnsci#35.
|
|
13
|
+
|
|
14
|
+
Headline: new native MP2-energy and AO→MO kernels beat aten by 1.25–1.9× and 1.3–1.6× in the `auto` region; native NKI matmul/bmm kernels deprecated (#52).
|
|
15
|
+
|
|
16
|
+
Resolves #51: on the native TorchNeuron path (Trn2/Trn3), the AO→MO and MP2-energy kernels are rewritten for Trn2 and now beat aten, and the NKI matmul/bmm kernels are retired in favour of aten.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`mp2_energy_row_kernel`, the native MP2-energy kernel (#51).**
|
|
21
|
+
- One launch per occupied row, using the pair symmetry `e_ij = e_ji`.
|
|
22
|
+
- Per pair, one GEMM fills `T` in SBUF; the `Tᵀ` blocks are Tensor Engine transposes. `T` never reaches device memory.
|
|
23
|
+
- Gridless launch: at LNC=2 each physical core takes half of the `j` window (`num_programs()` / `program_id`), and it also runs at LNC=1.
|
|
24
|
+
- The design follows trnblas `pair_energy_sym` (trnblas#55, #58).
|
|
25
|
+
- Native `"auto"` uses it for device-resident `B` once each row launch carries at least 3 GFLOP. On Trn2 it measured 1.25–1.9× faster than aten: for example 0.69 s against 1.30 s at `nocc=96, nvir=672, naux=2304`.
|
|
26
|
+
- Near a full device the kernel's input staging (about 1.5× the padded `B`) needs more memory than aten's 64 MiB chunks. `"auto"` keeps aten when live allocations plus that staging would leave less than 3 GiB of the core free, and remembers shapes whose launch failed.
|
|
27
|
+
- `TRNTENSOR_MP2_ENERGY=auto|rows|aten|legacy` overrides the choice.
|
|
28
|
+
- **`ao_to_mo_trn2_kernel`, the native AO→MO kernel (#51).**
|
|
29
|
+
- Two plain GEMM stages with the contraction index on the partition axis, so there are no DMA transposes.
|
|
30
|
+
- The auxiliary index is split across both physical cores at LNC=2.
|
|
31
|
+
- Native `"auto"` uses it for device-resident `eri` with `nocc ≤ 128` and stage-1 work of at least 5e8 multiply-adds (about nbasis ≥ 256). There it measured 1.3–1.6× faster than the two aten einsums on Trn2, at nbasis 256–768. Smaller problems stay on aten.
|
|
32
|
+
- `TRNTENSOR_AO_TO_MO=auto|trn2|aten|legacy` overrides the choice.
|
|
33
|
+
- `scripts/bench_native_kernels.py`: a kernel-level A/B harness (aten against the original and the Trn2 kernels), with `--hold-gb` for the memory-pressure regime of aws-neuron-sdk#1447.
|
|
34
|
+
|
|
35
|
+
### Deprecated
|
|
36
|
+
|
|
37
|
+
- **On the native path, the NKI `matmul` / `batched_matmul` kernels and the original `mp2_energy` / `ao_to_mo_transform` kernels (#51).** `set_backend("nki")` still runs the matmul/bmm kernels there, with a one-time `FutureWarning`; a later release will route them to aten.
|
|
38
|
+
- A Trn2 rewrite of matmul used both cores, Tensor Engine transposes and B-tile reuse. It was 1.2–4.3× faster than the original kernel, but still slower than aten at every size (4096³ fp32: 4.10 ms against 3.80 ms; bf16: 1.61 ms against 1.43 ms). It was not kept.
|
|
39
|
+
- Native `"auto"` already used aten for these.
|
|
40
|
+
- The XLA path (Trn1) keeps all four original kernels unchanged and does not warn.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- Under `set_backend("nki")` on the native path, `mp2_energy` and `ao_to_mo_transform` run the Trn2 kernels instead of the original ones.
|
|
45
|
+
|
|
46
|
+
## [0.18.0] — 2026-10-30
|
|
47
|
+
|
|
48
|
+
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.
|
|
49
|
+
|
|
50
|
+
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).
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **Native dispatch thresholds are now measured (#41).** They were measured
|
|
55
|
+
on trn2.3xlarge (SDK 2.33, NKI 0.7.0) with `scripts/bench_native_einsum.py`
|
|
56
|
+
and `scripts/autotune_dispatch.py`.
|
|
57
|
+
- On device-resident operands, aten `torch.matmul` / `torch.bmm` beat the
|
|
58
|
+
NKI matmul and batched-matmul kernels at every size (128³ to 4096³, fp32
|
|
59
|
+
and bf16). The native pinned threshold now defaults to `never`, so
|
|
60
|
+
device work runs as aten on `neuron`.
|
|
61
|
+
- Host operands at or above `TRNTENSOR_MIN_NKI_FLOPS_NATIVE` (now
|
|
62
|
+
500 M multiply-adds, previously a provisional 1 G) are offloaded to
|
|
63
|
+
aten on the device instead of to the NKI kernel.
|
|
64
|
+
- Thresholds accept `never` / `inf`.
|
|
65
|
+
- The XLA path is unchanged.
|
|
66
|
+
- **Native `"auto"` runs the fused quantum primitives as aten.** This covers
|
|
67
|
+
`mp2_energy` and `ao_to_mo_transform`. On Trn2 the AO→MO kernel measured
|
|
68
|
+
6–20× slower than aten, and the MP2 kernel 1.0–1.5× slower.
|
|
69
|
+
`set_backend("nki")` still runs both kernels, and the XLA path is
|
|
70
|
+
unchanged.
|
|
71
|
+
- **`mp2_energy` reference path is vectorized.** Without NKI it now uses
|
|
72
|
+
aten ops chunked over `i` instead of a nocc² Python loop. That is 10–40×
|
|
73
|
+
faster on device. On an Apple M-series CPU it is 3× faster at small sizes
|
|
74
|
+
and about even (±10%) at `nocc=32, nvir=128, naux=512`.
|
|
75
|
+
- **`ao_to_mo_transform` reference path** is now two explicit binary
|
|
76
|
+
contractions.
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- **Greedy contraction path no longer starts with an outer product.** For
|
|
81
|
+
example, `einsum("mi,na,mnP->iaP")` used to contract `mi,na` first,
|
|
82
|
+
building a 4-index intermediate. Pairs that share an index are now
|
|
83
|
+
preferred.
|
|
84
|
+
- **`scripts/autotune_dispatch.py` now reports M·K·N**, the quantity dispatch
|
|
85
|
+
compares, where it used to report 2·M·K·N. On the native path it now
|
|
86
|
+
derives both thresholds: host vs device offload, and NKI vs aten on device.
|
|
87
|
+
|
|
10
88
|
## [0.17.0] — 2026-10-28
|
|
11
89
|
|
|
12
90
|
Validated on Trn2 (trn2.3xlarge, Neuron SDK 2.33.0, native TorchNeuron device) — see trnsci/trnsci#35.
|
|
13
91
|
|
|
14
92
|
### Added
|
|
15
93
|
|
|
94
|
+
- **Native PyTorch dispatch on TorchNeuron (#41, trnsci/trnsci#35).** On
|
|
95
|
+
TorchNeuron (Neuron SDK 2.33+, `torch-neuronx` 2.14, PyTorch 2.14,
|
|
96
|
+
Trn2/Trn3) every host-wrapper NKI kernel is a PyTorch custom op registered
|
|
97
|
+
with `torch_neuronx.nki_op` and launched on `device="neuron"`, with no
|
|
98
|
+
`torch_xla` import: `trntensor::matmul`, `batched_matmul`, `mp2_energy`,
|
|
99
|
+
`ao_to_mo_transform`. Kernel bodies are unchanged. The PyTorch/XLA path
|
|
100
|
+
(Trn1) is unchanged.
|
|
101
|
+
- The dispatch path is detected on the first kernel launch, not at
|
|
102
|
+
import: native when `torch_neuronx` exports `nki_op` and
|
|
103
|
+
`torch_neuronx.device_count() > 0`, XLA when `torch_xla` is installed,
|
|
104
|
+
otherwise none. `TRNSCI_NKI_DISPATCH=auto|native|xla` (suite-wide)
|
|
105
|
+
forces a path; forcing an unavailable one raises.
|
|
106
|
+
- The ops are registered lazily (`trntensor/nki/_native.py` is imported
|
|
107
|
+
only on the native path). Every launch goes through one helper,
|
|
108
|
+
`dispatch._launch`. That covers `einsum` (matmul/bmm/path steps),
|
|
109
|
+
`multi_einsum` (batched and per-contraction), `ao_to_mo_transform`,
|
|
110
|
+
`mp2_energy`, and `precision="sr"` (which passes `use_sr=True` to the op).
|
|
111
|
+
`precision="kahan"` stays a host fp64 contraction, and `"dd"` still
|
|
112
|
+
raises on NKI hosts (trnblas#22). The reduce-parallel `nki.collectives`
|
|
113
|
+
all-reduce from Phase 4 is unchanged.
|
|
114
|
+
- New `to_device` / `from_device` place tensors on the active path's
|
|
115
|
+
device (`neuron` or XLA). `to_xla` / `from_xla` stay public. On the
|
|
116
|
+
native path `to_xla` places on `neuron`, so existing residency code keeps
|
|
117
|
+
working. `multi_einsum` pre-pins shared operands on either device.
|
|
118
|
+
- FLOP dispatch thresholds are path-aware. The XLA values are unchanged
|
|
119
|
+
(2 GFLOPs unpinned, 1 GFLOPs pinned). The native path uses provisional
|
|
120
|
+
1 GFLOPs / 128 MFLOPs, because eager native dispatch costs about 0.2 ms
|
|
121
|
+
per launch. Override them with `TRNTENSOR_MIN_NKI_FLOPS_NATIVE[_PINNED]`
|
|
122
|
+
or the `trntensor_min_nki_flops_native[_pinned]` autotune cache keys.
|
|
123
|
+
`scripts/autotune_dispatch.py` measures and writes the native keys on a
|
|
124
|
+
TorchNeuron host.
|
|
125
|
+
- Dormant deprecation hook: with `TRNSCI_XLA_DEPRECATION=1` (off by
|
|
126
|
+
default), selecting the XLA path and calling `to_xla` / `from_xla` each
|
|
127
|
+
emit a one-time `DeprecationWarning`. Migration: replace `to_xla` with
|
|
128
|
+
`to_device` and `from_xla` with `from_device` (docs/api/nki.md).
|
|
129
|
+
- The `mp2_energy` CPU fallback copies `neuron`-resident operands to the
|
|
130
|
+
host before its scalar loop, then moves the result back.
|
|
131
|
+
- New extras: `[torchneuron]` (`torch>=2.14`, `torch-neuronx>=2.14`,
|
|
132
|
+
`nki>=0.6`) and `[neuron]` for the XLA lineage (`torch-neuronx>=2.9`,
|
|
133
|
+
`nki>=0.3.0`).
|
|
134
|
+
- Tests: `tests/conftest.py` sets `NKI_ENABLE_TRACE_CACHE=0` by default.
|
|
135
|
+
New CPU tests in `tests/test_native_dispatch.py` cover path selection,
|
|
136
|
+
launch routing for every entry point, lazy registration, op schemas,
|
|
137
|
+
fake shapes, thresholds, and the deprecation hook. New
|
|
138
|
+
`@pytest.mark.neuron` tests in `tests/test_nki_native.py` have not yet
|
|
139
|
+
run on hardware. `TestXlaResidency::test_matmul_stays_on_xla` is now
|
|
140
|
+
path-aware: it expects `neuron` on TorchNeuron and `xla` on Trn1, and
|
|
141
|
+
still checks residency.
|
|
142
|
+
|
|
16
143
|
- **Reduce-parallel sharding on real multi-rank all-reduce (Phase 4 prototype).**
|
|
17
144
|
Before this change, reduce-parallel and mixed output/reduce-parallel einsums
|
|
18
145
|
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
|
-
│
|
|
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.
|
|
3
|
+
Version: 0.19.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
|
|
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]
|
|
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
|
[](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]
|
|
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,187 @@
|
|
|
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_MP2_ENERGY=auto\|rows\|aten\|legacy` | Native path: force the `mp2_energy` executor (`rows` = the Trn2 row kernel, `legacy` = the original kernel, deprecated there). |
|
|
122
|
+
| `TRNTENSOR_AO_TO_MO=auto\|trn2\|aten\|legacy` | Native path: force the `ao_to_mo_transform` executor (`trn2` = the Trn2 kernel, `legacy` = the original kernel, deprecated there). |
|
|
123
|
+
| `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. |
|
|
124
|
+
| `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. |
|
|
125
|
+
|
|
126
|
+
### Native thresholds: measured on Trn2
|
|
127
|
+
|
|
128
|
+
These were measured on a trn2.3xlarge (Neuron SDK 2.33, NKI 0.7.0), using one
|
|
129
|
+
NeuronCore and two host threads, with `scripts/bench_native_einsum.py` and
|
|
130
|
+
`scripts/autotune_dispatch.py` (#41):
|
|
131
|
+
|
|
132
|
+
- **Pinned operands: NKI never beats aten.** On device-resident operands,
|
|
133
|
+
aten `torch.matmul` / `torch.bmm` beat the trntensor NKI matmul and
|
|
134
|
+
batched-matmul kernels at every size from 128³ to 4096³, in both fp32 and
|
|
135
|
+
bf16. Examples:
|
|
136
|
+
- 4096³ bf16: 1.38 ms with aten, 6.82 ms with NKI.
|
|
137
|
+
- 1024³ fp32: 0.20 ms with aten, 0.55 ms with NKI.
|
|
138
|
+
|
|
139
|
+
The eager launch floor is about 0.13 ms for an aten op and about 0.24 ms for
|
|
140
|
+
an NKI custom op.
|
|
141
|
+
- **Host operands: offload to aten from about 0.5 G multiply-adds.**
|
|
142
|
+
Offloading to aten (upload, run on device, download) beats offloading
|
|
143
|
+
through the NKI kernel at every size. It also beats host torch:
|
|
144
|
+
- fp32: from about 134 M multiply-adds (512³) when the host has two
|
|
145
|
+
threads.
|
|
146
|
+
- bf16: from about 0.5 to 1 G multiply-adds.
|
|
147
|
+
|
|
148
|
+
The default of 500 M sits between these. A host with more threads moves the
|
|
149
|
+
crossover up. Re-measure on your host with `scripts/autotune_dispatch.py`.
|
|
150
|
+
|
|
151
|
+
Kernel status on the native path after #51:
|
|
152
|
+
|
|
153
|
+
- **matmul / bmm: retired in favour of aten.** A Trn2 rewrite used both
|
|
154
|
+
physical cores through the `program_id` split, Tensor Engine transposes of
|
|
155
|
+
A and B-tile reuse across four stationaries. It was 1.2–4.3× faster than
|
|
156
|
+
the original kernel and still slower than aten at every size: 4096³ fp32
|
|
157
|
+
took 4.10 ms against 3.80 ms, and 4096³ bf16 1.61 ms against 1.43 ms.
|
|
158
|
+
It was not kept. `set_backend("nki")` still runs the original kernels on
|
|
159
|
+
the native path, with a one-time `FutureWarning`. A later release will
|
|
160
|
+
route them to aten.
|
|
161
|
+
- **AO→MO: kept, rewritten for Trn2** (`ao_to_mo_trn2_kernel`). It is
|
|
162
|
+
1.3–1.6× faster than the two aten einsums at nbasis 256–768, and native
|
|
163
|
+
`"auto"` uses it there for device-resident `eri`. Below nbasis ≈ 256
|
|
164
|
+
`"auto"` keeps aten.
|
|
165
|
+
- **MP2 energy: kept, rewritten for Trn2** (`mp2_energy_row_kernel`). It is
|
|
166
|
+
1.25–1.9× faster than aten once each row launch carries at least 3 GFLOP,
|
|
167
|
+
and native `"auto"` uses it there.
|
|
168
|
+
|
|
169
|
+
The original AO→MO and MP2 kernels remain the XLA-path (Trn1) kernels. See
|
|
170
|
+
[Quantum-chemistry primitives](quantum.md) for the tables.
|
|
171
|
+
|
|
172
|
+
## Kernels (internal)
|
|
173
|
+
|
|
174
|
+
- `matmul_kernel(a, b)` — 2-index matmul with stationary-A tile reuse.
|
|
175
|
+
Tile constants: `TILE_M=128`, `TILE_K=128`, `TILE_N=512`.
|
|
176
|
+
- `batched_matmul_kernel(a, b)` — per-batch-slice matmul. Batch dim
|
|
177
|
+
iterated via `nl.affine_range`; each slice reuses the stationary-A
|
|
178
|
+
tile layout.
|
|
179
|
+
|
|
180
|
+
The DF-MP2 pair pattern `einsum("ap,bp->ab")` is served by
|
|
181
|
+
`matmul_kernel` through the planner's `transB=True` route — no
|
|
182
|
+
dedicated kernel needed. Fused energy-denominator kernel is tracked
|
|
183
|
+
in [#13][i13].
|
|
184
|
+
|
|
185
|
+
[i13]: https://github.com/trnsci/trntensor/issues/13
|
|
186
|
+
|
|
187
|
+
[m2]: https://github.com/trnsci/trntensor/milestone/2
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Quantum-chemistry primitives
|
|
2
|
+
|
|
3
|
+
Domain-specific fused kernels where the whole computation — contraction, elementwise shaping, reduction — lives in a single NKI program. The architectural core of trntensor (see [Architecture](../architecture.md)).
|
|
4
|
+
|
|
5
|
+
## `trntensor.mp2_energy(B, eps_occ, eps_vir) -> scalar`
|
|
6
|
+
|
|
7
|
+
Density-fitted second-order Møller–Plesset correlation energy.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
E_MP2 = Σ_{i,j,a,b} T_{i,j,a,b} (2 T_{i,j,a,b} - T_{i,j,b,a}) / Δ_{i,j,a,b}
|
|
11
|
+
|
|
12
|
+
T_{i,j,a,b} = Σ_P B[i, a, P] B[j, b, P]
|
|
13
|
+
Δ_{i,j,a,b} = ε_i + ε_j - ε_a - ε_b
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Arguments
|
|
17
|
+
|
|
18
|
+
- `B: (nocc, nvir, naux) tensor` — density-fitted ERI coefficients
|
|
19
|
+
- `eps_occ: (nocc,) tensor` — occupied orbital energies
|
|
20
|
+
- `eps_vir: (nvir,) tensor` — virtual orbital energies
|
|
21
|
+
|
|
22
|
+
### Returns
|
|
23
|
+
|
|
24
|
+
A 0-D tensor containing the correlation energy.
|
|
25
|
+
|
|
26
|
+
### Example
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import torch
|
|
30
|
+
import trntensor
|
|
31
|
+
|
|
32
|
+
nocc, nvir, naux = 5, 19, 72
|
|
33
|
+
B = torch.randn(nocc, nvir, naux) * 0.1
|
|
34
|
+
eps_occ = -torch.sort(torch.rand(nocc))[0] - 0.5
|
|
35
|
+
eps_vir = torch.sort(torch.rand(nvir))[0] + 0.1
|
|
36
|
+
|
|
37
|
+
E = trntensor.mp2_energy(B, eps_occ, eps_vir)
|
|
38
|
+
print(f"E_MP2 = {E.item():.6f}")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Backend behaviour
|
|
42
|
+
|
|
43
|
+
- **CPU, and small problems on native TorchNeuron**: aten ops on the
|
|
44
|
+
operands' device. `T[i, j, a, b]` is contracted with `torch.einsum` in
|
|
45
|
+
chunks over `i`, which bounds peak memory to about 64 MiB of `T` at a time.
|
|
46
|
+
The `T`-weighted sum is vectorized.
|
|
47
|
+
- **Native TorchNeuron (Trn2/Trn3): the row kernel.** `mp2_energy_row_kernel`
|
|
48
|
+
is launched once per occupied row `i`. It covers `j = i + d (mod nocc)` for
|
|
49
|
+
`d = 0..nocc/2` and uses the pair symmetry `e_ij = e_ji`. Per pair, one
|
|
50
|
+
GEMM fills `T = B[i] B[j]ᵀ` in SBUF, and the `Tᵀ` blocks come from Tensor
|
|
51
|
+
Engine transposes. `T` never reaches device memory. At LNC=2 each physical
|
|
52
|
+
core takes half of the `j` window. The design follows trnblas
|
|
53
|
+
`pair_energy_sym` (trnblas#55, #58).
|
|
54
|
+
- `"auto"` uses the row kernel when `B` is on the device and each row
|
|
55
|
+
launch carries at least 3 GFLOP, counted on the padded sizes as
|
|
56
|
+
`2·(nocc//2+1)·nvir²·naux`. Below that, the ~nocc custom-op launch
|
|
57
|
+
floors lose to aten.
|
|
58
|
+
- `set_backend("nki")` uses it whenever its SBUF working set fits.
|
|
59
|
+
- `TRNTENSOR_MP2_ENERGY=auto|rows|aten|legacy` overrides the choice.
|
|
60
|
+
`legacy` is the original single-launch kernel, which is deprecated on
|
|
61
|
+
the native path.
|
|
62
|
+
|
|
63
|
+
Measured on Trn2 (fp32, one LNC=2 core, device-resident, warm, #51):
|
|
64
|
+
|
|
65
|
+
| nocc, nvir, naux | aten | row kernel |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| 16, 128, 512 | 1.5 ms | 5.1 ms |
|
|
68
|
+
| 32, 256, 768 | 12.1 ms | 11.6 ms |
|
|
69
|
+
| 32, 320, 1024 | 24.9 ms | 19.8 ms |
|
|
70
|
+
| 48, 384, 1152 | 75.7 ms | 39.2 ms |
|
|
71
|
+
| 64, 448, 1536 | 240 ms | 120 ms |
|
|
72
|
+
| 96, 672, 2304 | 1.30 s | 0.69 s |
|
|
73
|
+
|
|
74
|
+
Holding 12 or 18 GB of the core's 24 GB on the device did not slow either
|
|
75
|
+
path. The aten path's 64 MiB `T` chunks stay clear of the slowdown in
|
|
76
|
+
aws-neuron-sdk#1447. The row kernel's input staging is about 1.5× the
|
|
77
|
+
padded `B`, so near a full device it needs more memory than aten does.
|
|
78
|
+
`"auto"` therefore keeps aten when this process's live device allocations
|
|
79
|
+
plus that staging would leave less than 3 GiB free. A shape whose kernel
|
|
80
|
+
launch fails anyway falls back to aten and is remembered for the rest of
|
|
81
|
+
the process.
|
|
82
|
+
- **PyTorch/XLA path (Trn1)**: the original single-launch `mp2_energy_kernel`.
|
|
83
|
+
It accumulates `T` in PSUM, builds `Δ` on the Vector Engine, and writes one
|
|
84
|
+
partial per `(i, j)` pair.
|
|
85
|
+
|
|
86
|
+
## `trntensor.ao_to_mo_transform(eri, C_occ, C_vir) -> Tensor`
|
|
87
|
+
|
|
88
|
+
Fused 4-index AO→MO integral transform. The canonical quantum-chemistry primitive.
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
B[i, a, P] = Σ_{μ, ν} C_occ[μ, i] · C_vir[ν, a] · eri[μ, ν, P]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Arguments
|
|
95
|
+
|
|
96
|
+
- `eri: (nbasis, nbasis, naux) tensor` — density-fitted two-electron integrals in the AO basis
|
|
97
|
+
- `C_occ: (nbasis, nocc) tensor` — occupied molecular orbital coefficients
|
|
98
|
+
- `C_vir: (nbasis, nvir) tensor` — virtual molecular orbital coefficients
|
|
99
|
+
|
|
100
|
+
### Returns
|
|
101
|
+
|
|
102
|
+
A `(nocc, nvir, naux)` tensor of transformed DF coefficients B_ia^P.
|
|
103
|
+
|
|
104
|
+
### Example — full DF-MP2 pipeline
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import trntensor
|
|
108
|
+
|
|
109
|
+
B = trntensor.ao_to_mo_transform(eri, C_occ, C_vir)
|
|
110
|
+
E = trntensor.mp2_energy(B, eps_occ, eps_vir)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Backend behaviour
|
|
114
|
+
|
|
115
|
+
- **CPU**: two explicit aten steps on the operands' device:
|
|
116
|
+
`einsum("mi,mnP->inP")` then `einsum("na,inP->iaP")`.
|
|
117
|
+
- **Native TorchNeuron (Trn2/Trn3): `ao_to_mo_trn2_kernel`.** Both stages are
|
|
118
|
+
plain GEMMs with the contraction index on the partition axis, so there are
|
|
119
|
+
no DMA transposes.
|
|
120
|
+
- Stage 1: `X[i, ν, P] = Σ_μ C_occ[μ, i] eri[μ, ν, P]`.
|
|
121
|
+
- Stage 2: `B[i, a, P] = Σ_ν C_vir[ν, a] X[i, ν, P]`.
|
|
122
|
+
- The auxiliary index is split across both physical cores at LNC=2.
|
|
123
|
+
- `"auto"` uses the kernel for device-resident `eri` with `nocc ≤ 128` when
|
|
124
|
+
its SBUF working set fits and the stage-1 work `nocc·nbasis²·naux` is at
|
|
125
|
+
least 5e8 multiply-adds (about nbasis ≥ 256). Smaller problems lose to
|
|
126
|
+
the launch floor and the padding copies; otherwise it runs the two aten
|
|
127
|
+
steps.
|
|
128
|
+
`TRNTENSOR_AO_TO_MO=auto|trn2|aten|legacy` overrides the choice.
|
|
129
|
+
|
|
130
|
+
Measured on Trn2 (fp32, one LNC=2 core, device-resident, warm, #51):
|
|
131
|
+
|
|
132
|
+
| nbasis, nocc, naux | two aten einsums | Trn2 kernel (op) | `ao_to_mo_transform`, `"auto"` | original kernel |
|
|
133
|
+
|---|---|---|---|---|
|
|
134
|
+
| 64, 8, 128 | 0.41 ms | — | 0.41 ms (aten) | 6.0 ms (#49) |
|
|
135
|
+
| 128, 16, 256 | 0.49 ms | 0.43 ms | 0.49 ms (aten) | 16.1 ms |
|
|
136
|
+
| 256, 32, 512 | 1.23 ms | 1.11 ms | 0.92 ms | 112 ms (#49) |
|
|
137
|
+
| 512, 32, 1024 | 7.30 ms | 4.56 ms | 4.72 ms | — |
|
|
138
|
+
| 768, 48, 1536 | 18.3 ms | 11.8 ms | 12.0 ms | — |
|
|
139
|
+
|
|
140
|
+
The op column takes tile-multiple inputs (the raw kernel). The `"auto"`
|
|
141
|
+
column includes the padding of `nvir` to 128 and is the shipped route. At
|
|
142
|
+
nbasis 64 and 128 it measured 0.79 and 0.57 ms with the kernel forced,
|
|
143
|
+
which is why `"auto"` keeps aten there.
|
|
144
|
+
|
|
145
|
+
- **PyTorch/XLA path (Trn1)**: the original kernel. It loops over P, with the
|
|
146
|
+
`(i, ν)` intermediate round-tripping through kernel-scratch HBM. On the
|
|
147
|
+
native path it is deprecated.
|
|
148
|
+
|
|
149
|
+
### Current limitations
|
|
150
|
+
|
|
151
|
+
- Both NKI kernels need `nocc ≤ 128`; larger `nocc` runs the aten steps (native) or raises `NotImplementedError` (XLA).
|
|
152
|
+
- The `(nocc, nbasis, naux)` intermediate goes through kernel-scratch HBM between the two stages, as it does between the two aten einsums. Stage 1 leaves `i` on the partition axis, and stage 2 needs `ν` there.
|