torchmatch 1.0.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 (38) hide show
  1. torchmatch-1.0.0/LICENSE +21 -0
  2. torchmatch-1.0.0/PKG-INFO +186 -0
  3. torchmatch-1.0.0/README.md +156 -0
  4. torchmatch-1.0.0/pyproject.toml +174 -0
  5. torchmatch-1.0.0/setup.cfg +4 -0
  6. torchmatch-1.0.0/setup.py +97 -0
  7. torchmatch-1.0.0/sources/torchmatch/__init__.py +119 -0
  8. torchmatch-1.0.0/sources/torchmatch/_cpu.py +74 -0
  9. torchmatch-1.0.0/sources/torchmatch/_cuda.py +73 -0
  10. torchmatch-1.0.0/sources/torchmatch/_loader.py +102 -0
  11. torchmatch-1.0.0/sources/torchmatch/cpu/jv_compact_avx2.h +71 -0
  12. torchmatch-1.0.0/sources/torchmatch/cpu/jv_compact_core.h +303 -0
  13. torchmatch-1.0.0/sources/torchmatch/cpu/jv_dense_avx2.h +35 -0
  14. torchmatch-1.0.0/sources/torchmatch/cpu/jv_dense_core.cpp +62 -0
  15. torchmatch-1.0.0/sources/torchmatch/cpu/jv_dense_core.h +19 -0
  16. torchmatch-1.0.0/sources/torchmatch/cpu/jv_dense_flat.h +276 -0
  17. torchmatch-1.0.0/sources/torchmatch/cpu/jv_scalar.cpp +198 -0
  18. torchmatch-1.0.0/sources/torchmatch/cpu/jv_scalar.h +11 -0
  19. torchmatch-1.0.0/sources/torchmatch/cpu/ops.cpp +496 -0
  20. torchmatch-1.0.0/sources/torchmatch/cuda/control.cuh +64 -0
  21. torchmatch-1.0.0/sources/torchmatch/cuda/dispatch.cu +114 -0
  22. torchmatch-1.0.0/sources/torchmatch/cuda/jv_tiled.cuh +203 -0
  23. torchmatch-1.0.0/sources/torchmatch/cuda/munkres_classical.cu +703 -0
  24. torchmatch-1.0.0/sources/torchmatch/cuda/munkres_hybrid.cu +1297 -0
  25. torchmatch-1.0.0/sources/torchmatch/cuda/munkres_tree.cu +1161 -0
  26. torchmatch-1.0.0/sources/torchmatch/cuda/ops.cpp +35 -0
  27. torchmatch-1.0.0/sources/torchmatch/cuda/workspace.cuh +108 -0
  28. torchmatch-1.0.0/sources/torchmatch/py.typed +0 -0
  29. torchmatch-1.0.0/sources/torchmatch.egg-info/PKG-INFO +186 -0
  30. torchmatch-1.0.0/sources/torchmatch.egg-info/SOURCES.txt +36 -0
  31. torchmatch-1.0.0/sources/torchmatch.egg-info/dependency_links.txt +1 -0
  32. torchmatch-1.0.0/sources/torchmatch.egg-info/requires.txt +13 -0
  33. torchmatch-1.0.0/sources/torchmatch.egg-info/top_level.txt +1 -0
  34. torchmatch-1.0.0/tests/test_lap_jv_batch.py +26 -0
  35. torchmatch-1.0.0/tests/test_lap_jv_float32.py +24 -0
  36. torchmatch-1.0.0/tests/test_lap_jv_scalar.py +76 -0
  37. torchmatch-1.0.0/tests/test_lap_jv_single.py +24 -0
  38. torchmatch-1.0.0/tests/test_lap_opcheck.py +92 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Kurt Stolle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,186 @@
1
+ Metadata-Version: 2.4
2
+ Name: torchmatch
3
+ Version: 1.0.0
4
+ Summary: Linear assignment problem solvers for PyTorch.
5
+ Author-email: Kurt Stolle <kurt@computer.org>
6
+ License-Expression: MIT
7
+ Keywords: pytorch,linear assignment problem,lap,lapjv,hungarian algorithm,bipartite matching,cuda
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
12
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Requires-Python: <3.14,>=3.13
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: torch>=2.11
21
+ Provides-Extra: cpu
22
+ Requires-Dist: torch>=2.11; extra == "cpu"
23
+ Provides-Extra: cu126
24
+ Requires-Dist: torch>=2.11; extra == "cu126"
25
+ Provides-Extra: cu128
26
+ Requires-Dist: torch>=2.11; extra == "cu128"
27
+ Provides-Extra: cu130
28
+ Requires-Dist: torch>=2.12; extra == "cu130"
29
+ Dynamic: license-file
30
+
31
+ # torchmatch
32
+
33
+ Linear assignment problem (LAP) solvers for PyTorch, registered under
34
+ `torch.ops.matching`. Inputs are `torch.Tensor`; outputs are integer
35
+ row-to-column assignments.
36
+
37
+ 📖 **Documentation**: <https://khwstolle.github.io/torchmatch/>. Tutorials, API reference, and the full benchmark report.
38
+
39
+ Two algorithmic families share the op surface:
40
+
41
+ - **Munkres** (Kuhn-Munkres 1957): the primed/starred-zeros 6-step
42
+ augmenting-path procedure. CUDA only; three parallelization
43
+ strategies.
44
+ - **Jonker-Volgenant / successive-shortest-path (SSP)**: the
45
+ Dijkstra-like family.
46
+
47
+ Both families return optimal assignments. They differ in the
48
+ augmentation routine, the parallelization story, and constant
49
+ factors.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install torchmatch
55
+ ```
56
+
57
+ The Munkres CUDA ops and the CUDA backend of `jv_dense_batch` need a
58
+ CUDA-capable PyTorch build. CPU ops run on any platform; the
59
+ SIMD-optimized variants need AVX2/FMA at build time (sdist falls back
60
+ to the AVX2/FMA-aware JIT path, and `jv_scalar` works without SIMD).
61
+
62
+ ## Usage
63
+
64
+ ```python
65
+ import torch
66
+ import torchmatch # extensions load eagerly at import
67
+
68
+ cost = torch.randn(8, 8, device="cuda")
69
+ row_to_col = torchmatch.munkres_hybrid(cost)
70
+
71
+ cost_cpu = torch.randn(8, 8)
72
+ row_to_col_cpu = torchmatch.jv_scalar(cost_cpu)
73
+ ```
74
+
75
+ The same ops live at `torch.ops.matching.<op>`, which is useful inside
76
+ `torch.compile` regions or when dispatching by name.
77
+
78
+ For control over extension loading (preforked workers, latency-sensitive
79
+ serving), the loaders stay exported and idempotent:
80
+
81
+ ```python
82
+ torchmatch.load_cpu() # already ran at import; no-op
83
+ torchmatch.load_cuda()
84
+ ```
85
+
86
+ Every op returns an `int64` row→col mapping of length `N` (the number
87
+ of rows). A `-1` entry marks an unmatched row.
88
+
89
+ ## Ops
90
+
91
+ ### Munkres family (CUDA only)
92
+
93
+ Primed/starred-zeros Hungarian/Munkres algorithm; three GPU
94
+ parallelization strategies. All carry the `cudagraph_unsafe` tag
95
+ (host-side syncs read managed-memory flags).
96
+
97
+ | Op | dtype (internal) | Variant |
98
+ | ------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------- |
99
+ | `munkres_classical` | float32, column-major | Single-path augmentation per outer iteration; CUB segmented reductions for column-min |
100
+ | `munkres_hybrid` | float32, row-major | Adaptive: starts single-path, switches to tree augmentation when single-path stalls |
101
+ | `munkres_tree` | float64, row-major | Always-tree: parallel BFS finds all vertex-disjoint augmenting paths per outer iteration; cooperative-groups + Thrust scan |
102
+
103
+ ### Jonker-Volgenant family, CPU (single problem)
104
+
105
+ Successive-shortest-path (Dijkstra-like over reduced costs). All accept
106
+ rectangular `(N, M)` cost matrices in float32 or float64.
107
+
108
+ | Op | Variant |
109
+ | ------------ | --------------------------------------------------------------------------------------------------------------------- |
110
+ | `jv_scalar` | Sequential reference; no SIMD. Implements Crouse 2016. Rejects NaN / -inf; treats +inf as an infeasible edge. |
111
+ | `jv_dense` | AVX2 flat-pointer inner loop; rectangular-capable |
112
+ | `jv_compact` | AVX2-gather inner loop; square-only internal kernel (rectangular inputs padded by the wrapper) |
113
+
114
+ ### Jonker-Volgenant family, batched
115
+
116
+ | Op | Devices | Constraints |
117
+ | --------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
118
+ | `jv_dense_batch` | CPU + CUDA | CPU: `(B, N, M)`, any size, `at::parallel_for` over per-problem `jv_dense`. CUDA: shared-memory tiled kernel, one block per problem; requires `(B, K, K)` square with `K ≤ MAX_TILE = 64`. |
119
+ | `jv_compact_batch` | CPU | `at::parallel_for` over per-problem `jv_compact` |
120
+ | `jv_dense_batch_unpacked` | CPU | Returns `(matches, unmatched_rows, unmatched_cols, n_matched)`, which saves a per-problem Python unpack |
121
+ | `jv_compact_batch_unpacked` | CPU | Same shape, compact variant |
122
+
123
+ ## torch.compile / torch.export
124
+
125
+ Every op carries a FakeTensor kernel. The Munkres CUDA ops carry the
126
+ `cudagraph_unsafe` tag (host-side syncs); the CUDA backend of
127
+ `jv_dense_batch` is fully capturable.
128
+
129
+ ## Build modes
130
+
131
+ The per-device loaders prefer a prebuilt `.so` shipped in the wheel and
132
+ fall back to JIT-compiling the C++/CUDA sources via
133
+ `torch.utils.cpp_extension.load`. Both paths register the same
134
+ `torch.ops.matching.*` ops; the choice is transparent to callers.
135
+
136
+ ### Building wheels
137
+
138
+ ```bash
139
+ # default: builds CPU extension; builds CUDA extension if a CUDA
140
+ # toolchain is detected (torch.utils.cpp_extension.CUDA_HOME)
141
+ pip wheel . -w dist/
142
+
143
+ # CPU-only wheel
144
+ TORCHMATCH_SKIP_CUDA=1 pip wheel . -w dist/
145
+
146
+ # CUDA SM targets (default: PyTorch's current-device default)
147
+ TORCH_CUDA_ARCH_LIST="8.0;8.6;8.9;9.0" pip wheel . -w dist/
148
+ ```
149
+
150
+ The build system pairs `setuptools` with `torch.utils.cpp_extension.BuildExtension`.
151
+ Sdists ship the full `` tree so the JIT path works on any
152
+ CUDA-capable machine, even without a matching wheel.
153
+
154
+ ### Runtime overrides
155
+
156
+ - `TORCHMATCH_FORCE_JIT=1`: skip the prebuilt `.so` and recompile from
157
+ source via `cpp_extension.load`. Useful for development and for
158
+ diagnosing ABI mismatches.
159
+
160
+ ## Development environment
161
+
162
+ A self-contained `flake.nix` provides Python 3.13, uv, and a chosen
163
+ CUDA toolkit (default 12.8) without touching your global system. With
164
+ direnv:
165
+
166
+ ```bash
167
+ direnv allow # picks devShells.default = cu128
168
+ NIX_DEVSHELL_NAME=cpu direnv reload
169
+ NIX_DEVSHELL_NAME=cu130 direnv reload
170
+ ```
171
+
172
+ Without direnv:
173
+
174
+ ```bash
175
+ nix develop # default = cu128
176
+ nix develop .#cpu # CPU-only (sets TORCHMATCH_SKIP_CUDA=1)
177
+ nix develop .#cu126
178
+ nix develop .#cu130
179
+ ```
180
+
181
+ Once inside the shell:
182
+
183
+ ```bash
184
+ uv sync --all-groups # populate .venv from pyproject.toml
185
+ uv run python -m pytest tests/ # run the test suite
186
+ ```
@@ -0,0 +1,156 @@
1
+ # torchmatch
2
+
3
+ Linear assignment problem (LAP) solvers for PyTorch, registered under
4
+ `torch.ops.matching`. Inputs are `torch.Tensor`; outputs are integer
5
+ row-to-column assignments.
6
+
7
+ 📖 **Documentation**: <https://khwstolle.github.io/torchmatch/>. Tutorials, API reference, and the full benchmark report.
8
+
9
+ Two algorithmic families share the op surface:
10
+
11
+ - **Munkres** (Kuhn-Munkres 1957): the primed/starred-zeros 6-step
12
+ augmenting-path procedure. CUDA only; three parallelization
13
+ strategies.
14
+ - **Jonker-Volgenant / successive-shortest-path (SSP)**: the
15
+ Dijkstra-like family.
16
+
17
+ Both families return optimal assignments. They differ in the
18
+ augmentation routine, the parallelization story, and constant
19
+ factors.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install torchmatch
25
+ ```
26
+
27
+ The Munkres CUDA ops and the CUDA backend of `jv_dense_batch` need a
28
+ CUDA-capable PyTorch build. CPU ops run on any platform; the
29
+ SIMD-optimized variants need AVX2/FMA at build time (sdist falls back
30
+ to the AVX2/FMA-aware JIT path, and `jv_scalar` works without SIMD).
31
+
32
+ ## Usage
33
+
34
+ ```python
35
+ import torch
36
+ import torchmatch # extensions load eagerly at import
37
+
38
+ cost = torch.randn(8, 8, device="cuda")
39
+ row_to_col = torchmatch.munkres_hybrid(cost)
40
+
41
+ cost_cpu = torch.randn(8, 8)
42
+ row_to_col_cpu = torchmatch.jv_scalar(cost_cpu)
43
+ ```
44
+
45
+ The same ops live at `torch.ops.matching.<op>`, which is useful inside
46
+ `torch.compile` regions or when dispatching by name.
47
+
48
+ For control over extension loading (preforked workers, latency-sensitive
49
+ serving), the loaders stay exported and idempotent:
50
+
51
+ ```python
52
+ torchmatch.load_cpu() # already ran at import; no-op
53
+ torchmatch.load_cuda()
54
+ ```
55
+
56
+ Every op returns an `int64` row→col mapping of length `N` (the number
57
+ of rows). A `-1` entry marks an unmatched row.
58
+
59
+ ## Ops
60
+
61
+ ### Munkres family (CUDA only)
62
+
63
+ Primed/starred-zeros Hungarian/Munkres algorithm; three GPU
64
+ parallelization strategies. All carry the `cudagraph_unsafe` tag
65
+ (host-side syncs read managed-memory flags).
66
+
67
+ | Op | dtype (internal) | Variant |
68
+ | ------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------- |
69
+ | `munkres_classical` | float32, column-major | Single-path augmentation per outer iteration; CUB segmented reductions for column-min |
70
+ | `munkres_hybrid` | float32, row-major | Adaptive: starts single-path, switches to tree augmentation when single-path stalls |
71
+ | `munkres_tree` | float64, row-major | Always-tree: parallel BFS finds all vertex-disjoint augmenting paths per outer iteration; cooperative-groups + Thrust scan |
72
+
73
+ ### Jonker-Volgenant family, CPU (single problem)
74
+
75
+ Successive-shortest-path (Dijkstra-like over reduced costs). All accept
76
+ rectangular `(N, M)` cost matrices in float32 or float64.
77
+
78
+ | Op | Variant |
79
+ | ------------ | --------------------------------------------------------------------------------------------------------------------- |
80
+ | `jv_scalar` | Sequential reference; no SIMD. Implements Crouse 2016. Rejects NaN / -inf; treats +inf as an infeasible edge. |
81
+ | `jv_dense` | AVX2 flat-pointer inner loop; rectangular-capable |
82
+ | `jv_compact` | AVX2-gather inner loop; square-only internal kernel (rectangular inputs padded by the wrapper) |
83
+
84
+ ### Jonker-Volgenant family, batched
85
+
86
+ | Op | Devices | Constraints |
87
+ | --------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
88
+ | `jv_dense_batch` | CPU + CUDA | CPU: `(B, N, M)`, any size, `at::parallel_for` over per-problem `jv_dense`. CUDA: shared-memory tiled kernel, one block per problem; requires `(B, K, K)` square with `K ≤ MAX_TILE = 64`. |
89
+ | `jv_compact_batch` | CPU | `at::parallel_for` over per-problem `jv_compact` |
90
+ | `jv_dense_batch_unpacked` | CPU | Returns `(matches, unmatched_rows, unmatched_cols, n_matched)`, which saves a per-problem Python unpack |
91
+ | `jv_compact_batch_unpacked` | CPU | Same shape, compact variant |
92
+
93
+ ## torch.compile / torch.export
94
+
95
+ Every op carries a FakeTensor kernel. The Munkres CUDA ops carry the
96
+ `cudagraph_unsafe` tag (host-side syncs); the CUDA backend of
97
+ `jv_dense_batch` is fully capturable.
98
+
99
+ ## Build modes
100
+
101
+ The per-device loaders prefer a prebuilt `.so` shipped in the wheel and
102
+ fall back to JIT-compiling the C++/CUDA sources via
103
+ `torch.utils.cpp_extension.load`. Both paths register the same
104
+ `torch.ops.matching.*` ops; the choice is transparent to callers.
105
+
106
+ ### Building wheels
107
+
108
+ ```bash
109
+ # default: builds CPU extension; builds CUDA extension if a CUDA
110
+ # toolchain is detected (torch.utils.cpp_extension.CUDA_HOME)
111
+ pip wheel . -w dist/
112
+
113
+ # CPU-only wheel
114
+ TORCHMATCH_SKIP_CUDA=1 pip wheel . -w dist/
115
+
116
+ # CUDA SM targets (default: PyTorch's current-device default)
117
+ TORCH_CUDA_ARCH_LIST="8.0;8.6;8.9;9.0" pip wheel . -w dist/
118
+ ```
119
+
120
+ The build system pairs `setuptools` with `torch.utils.cpp_extension.BuildExtension`.
121
+ Sdists ship the full `` tree so the JIT path works on any
122
+ CUDA-capable machine, even without a matching wheel.
123
+
124
+ ### Runtime overrides
125
+
126
+ - `TORCHMATCH_FORCE_JIT=1`: skip the prebuilt `.so` and recompile from
127
+ source via `cpp_extension.load`. Useful for development and for
128
+ diagnosing ABI mismatches.
129
+
130
+ ## Development environment
131
+
132
+ A self-contained `flake.nix` provides Python 3.13, uv, and a chosen
133
+ CUDA toolkit (default 12.8) without touching your global system. With
134
+ direnv:
135
+
136
+ ```bash
137
+ direnv allow # picks devShells.default = cu128
138
+ NIX_DEVSHELL_NAME=cpu direnv reload
139
+ NIX_DEVSHELL_NAME=cu130 direnv reload
140
+ ```
141
+
142
+ Without direnv:
143
+
144
+ ```bash
145
+ nix develop # default = cu128
146
+ nix develop .#cpu # CPU-only (sets TORCHMATCH_SKIP_CUDA=1)
147
+ nix develop .#cu126
148
+ nix develop .#cu130
149
+ ```
150
+
151
+ Once inside the shell:
152
+
153
+ ```bash
154
+ uv sync --all-groups # populate .venv from pyproject.toml
155
+ uv run python -m pytest tests/ # run the test suite
156
+ ```
@@ -0,0 +1,174 @@
1
+ [build-system]
2
+ # Pinned to the same range as [project.dependencies]: a wider build-system
3
+ # constraint lets the isolated build env resolve a torch whose libtorch ABI
4
+ # does not match the runtime torch, producing a .so that fails to load with
5
+ # `undefined symbol: torch::Library::_def(...)` style errors.
6
+ requires = ["setuptools>=70", "wheel", "torch>=2.11,<2.13"]
7
+ build-backend = "setuptools.build_meta"
8
+
9
+ [project]
10
+ name = "torchmatch"
11
+ description = "Linear assignment problem solvers for PyTorch."
12
+ readme = "README.md"
13
+ version = "1.0.0"
14
+ requires-python = ">=3.13, <3.14"
15
+ keywords = [
16
+ "pytorch",
17
+ "linear assignment problem",
18
+ "lap",
19
+ "lapjv",
20
+ "hungarian algorithm",
21
+ "bipartite matching",
22
+ "cuda",
23
+ ]
24
+ authors = [{ name = "Kurt Stolle", email = "kurt@computer.org" }]
25
+ license = "MIT"
26
+ license-files = ["LICENSE"]
27
+ classifiers = [
28
+ "Development Status :: 3 - Alpha",
29
+ "Intended Audience :: Developers",
30
+ "Intended Audience :: Science/Research",
31
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
32
+ "Topic :: Scientific/Engineering :: Mathematics",
33
+ "Programming Language :: Python",
34
+ "Programming Language :: Python :: 3",
35
+ "Programming Language :: Python :: 3.13",
36
+ "Operating System :: POSIX :: Linux",
37
+ ]
38
+ dependencies = ["torch>=2.11"]
39
+
40
+ [project.optional-dependencies]
41
+ cpu = ["torch>=2.11"]
42
+ cu126 = ["torch>=2.11"]
43
+ cu128 = ["torch>=2.11"]
44
+ cu130 = ["torch>=2.12"]
45
+
46
+ [dependency-groups]
47
+ dev = [
48
+ "pytest>=8.4.2",
49
+ "pytest-benchmark>=5.1.0",
50
+ "pytest-timeout>=2.4.0",
51
+ "ruff>=0.9.0",
52
+ "scipy>=1.10",
53
+ ]
54
+ docs = [
55
+ "mkdocs-material>=9.5",
56
+ "mkdocstrings[python]>=0.27",
57
+ "pymdown-extensions>=10.0",
58
+ ]
59
+
60
+ [[tool.uv.index]]
61
+ name = "pytorch-cpu"
62
+ url = "https://download.pytorch.org/whl/cpu"
63
+ explicit = true
64
+
65
+ [[tool.uv.index]]
66
+ name = "pytorch-cu126"
67
+ url = "https://download.pytorch.org/whl/cu126"
68
+ explicit = true
69
+
70
+ [[tool.uv.index]]
71
+ name = "pytorch-cu128"
72
+ url = "https://download.pytorch.org/whl/cu128"
73
+ explicit = true
74
+
75
+ [[tool.uv.index]]
76
+ name = "pytorch-cu130"
77
+ url = "https://download.pytorch.org/whl/cu130"
78
+ explicit = true
79
+
80
+ [tool.uv.sources]
81
+ torch = [
82
+ { index = "pytorch-cpu", extra = "cpu" },
83
+ { index = "pytorch-cu126", extra = "cu126" },
84
+ { index = "pytorch-cu128", extra = "cu128" },
85
+ { index = "pytorch-cu130", extra = "cu130" },
86
+ ]
87
+
88
+ [tool.uv]
89
+ # [build-system].requires uses a separate isolated env that does NOT see
90
+ # [tool.uv.sources], so pip would pull torch from PyPI's default index
91
+ # (which bundles CUDA 13 in current releases) and produce a .so whose
92
+ # torch ABI / CUDA version does not match the venv torch. Disable build
93
+ # isolation for torchmatch so the build reuses the venv's pinned torch
94
+ # (cu126 / cu128 / cu130, picked by the user-selected extra).
95
+ no-build-isolation-package = ["torchmatch"]
96
+
97
+ conflicts = [
98
+ [
99
+ { extra = "cpu" },
100
+ { extra = "cu126" },
101
+ { extra = "cu128" },
102
+ { extra = "cu130" },
103
+ ],
104
+ ]
105
+
106
+ [tool.setuptools]
107
+ include-package-data = true
108
+
109
+ [tool.setuptools.packages.find]
110
+ where = ["sources"]
111
+
112
+ [tool.setuptools.package-dir]
113
+ "" = "sources"
114
+
115
+ [tool.setuptools.package-data]
116
+ "torchmatch" = [
117
+ "py.typed",
118
+ "cuda/*.cpp",
119
+ "cuda/*.cu",
120
+ "cuda/*.cuh",
121
+ "cpu/*.cpp",
122
+ "cpu/*.h",
123
+ ]
124
+
125
+ [tool.ruff]
126
+ target-version = 'py313'
127
+ line-length = 88
128
+
129
+ [tool.ruff.lint]
130
+ select = [
131
+ "ARG",
132
+ "B",
133
+ "C",
134
+ "C4",
135
+ "D",
136
+ "E",
137
+ "EM",
138
+ "F",
139
+ "FURB",
140
+ "G",
141
+ "I",
142
+ "ICN",
143
+ "N",
144
+ "NPY",
145
+ "PERF",
146
+ "PGH",
147
+ "PIE",
148
+ "PL",
149
+ "PT",
150
+ "PTH",
151
+ "PYI",
152
+ "RUF",
153
+ "RET",
154
+ "SIM",
155
+ "T20",
156
+ "TRY",
157
+ "UP",
158
+ ]
159
+ ignore = ["PLR2004", "PLW2901", "D212", "D203", "D107"]
160
+
161
+ [tool.ruff.lint.per-file-ignores]
162
+ # pytest.mark.parametrize functions take one parameter per fixture, so
163
+ # PLR0913's 5-argument cap fires on every multi-parametrize test.
164
+ "tests/*" = ["D", "T", "PLC0415", "PLR0913", "PLR1714"]
165
+ # Utility scripts: print is expected (CLI tools), docstrings are optional,
166
+ # RUF002 ambiguous-unicode rules are docstring-only false positives.
167
+ "scripts/*" = ["D", "T", "RUF002", "C901", "PERF401"]
168
+
169
+ [tool.ruff.format]
170
+ docstring-code-format = true
171
+ docstring-code-line-length = "dynamic"
172
+
173
+ [tool.pytest.ini_options]
174
+ timeout = 10
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,97 @@
1
+ """
2
+ Build the C++/CUDA extensions for torchmatch.
3
+
4
+ Project metadata lives in ``pyproject.toml``; this file defines only
5
+ ``ext_modules`` so :class:`torch.utils.cpp_extension.BuildExtension` can
6
+ compile the native sources into precompiled ``.so`` files.
7
+
8
+ Build-time behaviour
9
+ --------------------
10
+ - ``TORCHMATCH_SKIP_CUDA=1`` skips the CUDA extension entirely.
11
+ - ``TORCHMATCH_SKIP_CPU=1`` skips the CPU extension.
12
+ - When no CUDA toolchain is detected (``torch.utils.cpp_extension.CUDA_HOME``
13
+ is None), the CUDA extension drops out silently, so an sdist install
14
+ on a CPU-only box still produces a usable wheel with just the CPU
15
+ extension; CUDA ops then fall back to JIT at runtime on machines that
16
+ do have a toolchain.
17
+ - ``TORCH_CUDA_ARCH_LIST`` controls which SM versions ``nvcc`` targets;
18
+ honor it when set, otherwise PyTorch's default kicks in.
19
+ - CPU compile flags default to ``-O3 -mavx2 -mfma``. Older CPUs without
20
+ AVX2 install from sdist (runtime JIT detects flags from
21
+ ``/proc/cpuinfo``).
22
+
23
+ Runtime behaviour
24
+ -----------------
25
+ ``torchmatch.load_cpu()`` / ``torchmatch.load_cuda()`` look for a
26
+ prebuilt ``_cpu_impl*.so`` / ``_cuda_impl*.so`` next to the package and
27
+ ``dlopen`` it via ``torch.ops.load_library``. When no prebuilt is
28
+ present, they JIT-compile via ``torch.utils.cpp_extension.load``. Set
29
+ ``TORCHMATCH_FORCE_JIT=1`` to bypass the prebuilt path.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import os
35
+ from pathlib import Path
36
+
37
+ from setuptools import Extension, setup
38
+ from torch.utils.cpp_extension import (
39
+ CUDA_HOME,
40
+ BuildExtension,
41
+ CppExtension,
42
+ CUDAExtension,
43
+ )
44
+
45
+ CSRC = Path("sources/torchmatch")
46
+ CUDA_CSRC = CSRC / "cuda"
47
+ CPU_CSRC = CSRC / "cpu"
48
+
49
+ SKIP_CUDA = os.environ.get("TORCHMATCH_SKIP_CUDA") == "1"
50
+ SKIP_CPU = os.environ.get("TORCHMATCH_SKIP_CPU") == "1"
51
+
52
+
53
+ def _ext_modules() -> list[Extension]:
54
+ mods: list[Extension] = []
55
+
56
+ if not SKIP_CPU:
57
+ mods.append(
58
+ CppExtension(
59
+ name="torchmatch._cpu_impl",
60
+ sources=[
61
+ str(CPU_CSRC / "jv_scalar.cpp"),
62
+ str(CPU_CSRC / "jv_dense_core.cpp"),
63
+ str(CPU_CSRC / "ops.cpp"),
64
+ ],
65
+ include_dirs=[str(CPU_CSRC)],
66
+ extra_compile_args={
67
+ "cxx": ["-O3", "-std=c++17", "-mavx2", "-mfma"],
68
+ },
69
+ )
70
+ )
71
+
72
+ if not SKIP_CUDA and CUDA_HOME is not None:
73
+ mods.append(
74
+ CUDAExtension(
75
+ name="torchmatch._cuda_impl",
76
+ sources=[
77
+ str(CUDA_CSRC / "ops.cpp"),
78
+ str(CUDA_CSRC / "dispatch.cu"),
79
+ str(CUDA_CSRC / "munkres_classical.cu"),
80
+ str(CUDA_CSRC / "munkres_hybrid.cu"),
81
+ str(CUDA_CSRC / "munkres_tree.cu"),
82
+ ],
83
+ include_dirs=[str(CUDA_CSRC)],
84
+ extra_compile_args={
85
+ "cxx": ["-O3"],
86
+ "nvcc": ["-O3", "-std=c++17"],
87
+ },
88
+ )
89
+ )
90
+
91
+ return mods
92
+
93
+
94
+ setup(
95
+ ext_modules=_ext_modules(),
96
+ cmdclass={"build_ext": BuildExtension},
97
+ )