torch-fps
Farthest point sampling (FPS) for PyTorch, with fused FPS + kNN and FPS +
nearest-centroid assignment. On CUDA it runs Triton kernels that are compiled
just-in-time for the local GPU. On CPU it runs C++ kernels that are compiled
just-in-time on first use. Elsewhere (and for CUDA precision=torch.float64)
it falls back to a pure-PyTorch implementation with the same semantics.
Install
pip install torch-fps
This is a pure-Python package. Nothing is compiled at install time, and the
only dependency is torch>=2.7. The CUDA path also needs triton>=3.3, which
Linux CUDA builds of PyTorch 2.7+ already ship. If CUDA is available but
Triton is not, you get one RuntimeWarning and the reference backend.
A C++ compiler is optional and makes CPU calls much faster. With one on the
path, the first CPU call builds the C++ backend (FPS and FPS + kNN) through
torch.utils.cpp_extension, which takes about 15 s once and is then cached in
~/.cache/torch_extensions (or $TORCH_EXTENSIONS_DIR). Without a compiler
you get one RuntimeWarning and the pure-PyTorch backend. OpenMP is used on
Linux and Windows; on macOS it is opt-in with TORCH_FPS_OPENMP=1, and
TORCH_FPS_OPENMP=0 turns it off everywhere.
API
from torch_fps import (
farthest_point_sampling, # -> idx [B, k]
farthest_point_sampling_with_knn, # -> idx [B, k], neighbours [B, k, k_neighbors]
farthest_point_sampling_with_assign, # -> idx [B, k], assign [B, N] in [0, k)
nearest_assign, # flat segmented points -> [N] in [0, k)
)
points = torch.randn(4, 1000, 3, device="cuda") # [B, N, D]
mask = torch.ones(4, 1000, dtype=torch.bool, device="cuda")
idx = farthest_point_sampling(points, mask, 128)
idx, nbr = farthest_point_sampling_with_knn(points, mask, 128, 16)
idx, assign = farthest_point_sampling_with_assign(points, mask, 128)
The three FPS functions share these keyword-only options:
| Option | Default | Effect |
|---|---|---|
start_idx |
None |
Explicit [B] first index per row. |
random_start |
True |
Random valid first index. When False, the first valid index is used. |
generator |
None |
Generator for the random start. |
precision |
None |
Input dtype. None means float32. Distances always accumulate in fp32, or fp64 with precision=torch.float64. |
validate |
True |
Checks k <= valid count, which costs one host sync. With False the call is sync-free and short rows pad by repeating their last pick. |
assume_finite |
False |
Skips the finiteness pass over the points. |
Other semantics:
- A point is valid when its mask entry is true and all its coordinates are finite.
- Ties go to the lowest index.
- kNN neighbours are sorted closest-first and include the centroid. Rows with fewer than
k_neighborsvalid points pad with the centroid index.
All backends agree up to fp32 rounding. Triton (and C++, where the compiler fuses multiply-adds) can occasionally resolve exact near-ties differently.
Breaking change from 0.5: the sample count argument is now k, not K.
Performance
Measured on an RTX 5090 with torch 2.12 + cu130 and triton 3.7, using K = 128,
k_nn = 8 and float32. The script is benchmarks/bench_fps.py and the raw
numbers are in benchmarks/results_rtx5090_v3.json.
Op-level median time in ms (extra peak memory in MB), pure-PyTorch reference vs Triton:
| B | N | assign | knn |
|---|---|---|---|
| 128 | 192 | 10.5 (48) vs 0.13 (0.3) | 10.9 (49) vs 0.36 (1) |
| 128 | 3000 | 13.9 (753) vs 0.20 (3) | 15.9 (754) vs 0.56 (3) |
| 256 | 3000 | 18.0 (1505) vs 0.35 (6) | 22.0 (1505) vs 1.07 (6) |
The reference loops over the K samples, so its cost is mostly a fixed per-call overhead rather than growing with N.
End to end in a Neptune model (IceCube-sized events, up to 192 pulses), the pure-PyTorch reference makes a training step 15–42% slower, less for larger batches and models, and inference 71–94% slower.
On CPU (Threadripper 7970X, 32 torch threads, K = 128, k_nn = 8, float32;
bench_fps.py --sections cpu, raw numbers in benchmarks/results_cpu_v1.json),
median ms for the pure-PyTorch reference vs C++:
| B | N | assign | knn |
|---|---|---|---|
| 16 | 192 | 9.0 vs 0.41 | 11.3 vs 0.11 |
| 16 | 3000 | 56.9 vs 13.6 | 87.6 vs 1.7 |
| 128 | 192 | 31.1 vs 1.4 | 40.5 vs 0.89 |
| 128 | 3000 | 328 vs 269 | 378 vs 7.5 |
C++ covers FPS and FPS + kNN. The assignment step after FPS (and
nearest_assign) still runs the reference, which dominates assign at large
B × N.
Metadata
Release files for torch-fps 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| torch_fps-0.6.0.tar.gz | 25.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| torch_fps-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 45.5 kB
Release files / torch_fps-0.6.0.tar.gz
| Download URL | torch_fps-0.6.0.tar.gz |
|---|---|
| Size | 25.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8006683eed8dc0fecde2cf9eaaad0cc3e61c4722f0ce14431dd1eaae6615e7cb
|
|
BLAKE2b-256 checksum How to use checksums |
d0ff741a4dc6596d18278720c991ae027e26ebb4f756e2e2eaedebe8a224d3f0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|
Release files / torch_fps-0.6.0-py3-none-any.whl
| Download URL | torch_fps-0.6.0-py3-none-any.whl |
|---|---|
| Size | 19.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e48bf2d24c27c791102287e772644dc523253a1c324c45ee88bf2f62c482c61d
|
|
BLAKE2b-256 checksum How to use checksums |
43d749d910c63a148040e7c108954afeed72cf253cce41d5d7a9b245e51ac6b5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|
Security for torch-fps 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.