Metadata-Version: 2.4
Name: lanczos-triton
Version: 0.1.0
Summary: Antialiased separable Lanczos image resizing in Triton
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: torch>=2.6
Requires-Dist: triton>=3.2
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: numpy>=1.24; extra == "test"
Requires-Dist: Pillow>=10; extra == "test"
Provides-Extra: figures
Requires-Dist: numpy>=1.24; extra == "figures"
Requires-Dist: Pillow>=10; extra == "figures"
Requires-Dist: matplotlib>=3.8; extra == "figures"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: numpy>=1.24; extra == "dev"
Requires-Dist: Pillow>=10; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=6; extra == "dev"
Requires-Dist: ruff>=0.11; extra == "dev"
Dynamic: license-file

# lanczos-triton

[简体中文](README.zh-CN.md) · [Installation](#get-started) · [API](#python) · [Algorithm](#how-it-works) · [Benchmark](#run-your-own-benchmarks)

Lanczos Triton implements antialiased Lanczos-2 and Lanczos-3 image resizing
as Triton GPU kernels. It provides a PyTorch tensor API and a reusable resizer
for fixed-resolution image and video pipelines.

Both axes support integer and fractional scale ratios, including reduction,
enlargement and mixed directions. Kernels retain the full Lanczos support and
are compiled by Triton on first use. There are no pretrained weights or
project-specific C++/CUDA extensions to build.

| Capability | What you get |
|---|---|
| Tensor API | NCHW input, batch support, independently processed channels |
| Resampling | Lanczos-2/3, half-pixel coordinates, antialiased reduction |
| Repeated processing | Reusable geometry weights and optional device autotuning |
| Integration | Contiguous float32 output on the input CUDA device |
| Inspectable implementation | Triton source, independent numerical tests, benchmark tools |

## Qualitative illustration

![Common input, Pillow float reference and actual Triton outputs for reduction and enlargement](assets/resampling.png)

**Figure 1.** A common input resized by independent per-channel Pillow float
Lanczos and this implementation. The upper row reduces resolution; the lower
row enlarges it. Dimensions are given as height × width. Outputs use Lanczos-3
and antialiased reduction, without sharpening or learned postprocessing.

Floating outputs are compared before display clipping. Panels use
nearest-neighbor rendering and fit their respective axes; compare the
reference and Triton panels at the same output dimensions. The illustration
is not a ground-truth reconstruction benchmark.

## Get started

Use Python 3.10+, Linux, an NVIDIA GPU and a compatible CUDA-enabled PyTorch
build. Install PyTorch using its
[installation selector](https://pytorch.org/get-started/locally/), then run
this from the checkout:

```bash
python -m pip install -e .
```

The package requires PyTorch ≥2.6 and Triton ≥3.2. Keep their versions
compatible; check `torch.cuda.is_available()` before use. The first call for
a new kernel specialization includes JIT compilation.

### Python

```python
import torch
from lanczos_triton import resize

# Replace with your own finite floating-point NCHW image.
image = torch.rand(1, 3, 2160, 3840, device="cuda")
output = resize(image, (1537, 2731), a=3, antialias=True)
assert output.shape == (1, 3, 1537, 2731)
assert torch.isfinite(output).all()
```

| Argument / contract | Behavior |
|---|---|
| `image` | Nonempty NCHW NVIDIA CUDA tensor; float16, bfloat16 or float32 |
| `size` | Positive integer `(height, width)` target dimensions |
| `a=3` | Lanczos support parameter; choose `2` or `3` |
| `antialias=True` | Widen the filter support on each shrinking axis |
| Output | Same device, target size, contiguous float32; input unchanged |

Noncontiguous inputs are accepted and copied to contiguous storage. Equal
input/output dimensions return an independent float32 copy. Finite values
are required, but the API does not scan for NaN/Inf and synchronize the GPU.
Inputs requiring gradients are rejected; detach explicitly for inference.

Input and output values are not clipped or normalized. Lanczos has negative
lobes and can overshoot near sharp edges; use `output.clamp(0, 1)` when that
is appropriate for display. Each channel is processed independently,
including alpha. Color-space conversion and alpha premultiplication belong
to the calling pipeline.

### Reuse weights across frames

```python
from lanczos_triton import Resizer

resizer = Resizer(
    (2160, 3840),
    (1537, 2731),
    a=3,
    antialias=True,
    device=image.device,
    autotune=True,
)
output = resizer(image)
```

`Resizer` fixes the input/output height, width and device, while allowing
different batches, channel counts and supported dtypes. It shares geometry
weights, not mutable image buffers. `autotune=False` is the default;
`True` benchmarks several equivalent launch configurations on first use.
Report this startup cost separately from steady-state inference.

Calls support different CUDA streams; input production and output consumption
still follow PyTorch stream synchronization rules. For CUDA Graphs, construct
the resizer outside capture, warm up the exact shape/dtype and synchronize
before capture. Keep the resizer and input alive for the graph's lifetime.
This class is a callable utility, not an `nn.Module` or serialized model.

## How it works

![Lanczos-2/3 kernels and support widening for antialiased downsampling](assets/kernels.png)

**Figure 2.** (a) Lanczos-2 and Lanczos-3 reconstruction kernels.
(b) Continuous Lanczos-3 kernels `L(x/s)/s` for scales `s=1,3` illustrate
support widening. Actual discrete weights are normalized separately at each
output position, including image boundaries.

The one-dimensional kernel is `L(t) = sinc(t) * sinc(t/a)` for `|t| < a`,
and zero elsewhere, where `sinc(t) = sin(pi*t)/(pi*t)` and `sinc(0)=1`.
Two one-dimensional passes implement separable two-dimensional resampling.

| Convention | Definition |
|---|---|
| Pixel centers | Output index `j` maps to `(j + 0.5) * input_size / output_size - 0.5` |
| Antialias scale | `s = max(input_size/output_size, 1)`; `s=1` when disabled |
| Sample weight | `L((i-center)/s)`, normalized over valid input indices |
| Boundaries | Exclude samples outside the image and renormalize remaining weights |
| Arithmetic | Float32 weights, accumulation and intermediate images |

Reduction expands the full sampling window. The implementation does not
replace it with repeated smaller resizes or an area/bilinear prefilter.
Coordinate calculation uses integer quotient/remainder decomposition to
preserve local fractional positions at large image coordinates. Fused
multiply-add and parallel reductions can change final rounding; bitwise
agreement with other implementations is not promised.

The sampling conventions correspond to Pillow's floating single-channel
Lanczos path. Pillow's uint8 path includes intermediate quantization/clipping
and is not a strict floating-point reference. Disabling antialiasing changes
the reduction filter and can produce aliasing.

### Scale-aware execution

| Workload | Execution strategy |
|---|---|
| Identity / one unchanged axis | Copy / filter only the changing axis |
| Windows up to 32 samples | Unrolled sampling, horizontal row reuse, shared vertical row weights |
| Wider reduction windows | Parallel, chunked reductions over every valid sample |
| Extreme reduction | Bound support to available input; process it in chunks |
| Singleton width | Reuse contiguous horizontal filtering for the vertical axis |
| Mixed / unequal axis scales | Independent weights; choose the smaller intermediate image |
| Repeated geometry | Reuse weights; optional autotuning changes layout, not the filter |

Weights are stored by sample offset and output position for contiguous
horizontal access. With horizontal-first execution, work is approximately
`N*C*(IH*OW*Kx + OH*OW*Ky)`. Memory includes the float32 intermediate and
weight tables; noncontiguous input adds a contiguous copy. There is no global
GPU weight cache. A `Resizer` owns its tables, whose reuse/freeing follows
CUDA stream lifetimes and PyTorch's allocator.

Image sizes are limited by device memory and int32 element indexing capacity.

## Run your own benchmarks

```bash
python -m pip install -e ".[test]"

# Validate 14 scale families from a 2160×3840 input, without timing.
python benchmarks/benchmark.py --suite --check-only

# Time ordinary calls and prepared/autotuned calls separately.
python benchmarks/benchmark.py --suite --autotune
python benchmarks/benchmark.py --input 2160 3840 --output 1537 2731 --dtype float16

# Warm CUDA Graph execution, excluding per-call Python dispatch.
python benchmarks/benchmark.py --input 1080 1920 --output 2160 3840 --autotune --timing graph

# Optional trusted local comparator exposing the same resize API.
python benchmarks/benchmark.py --compare /path/to/reference_lanczos.py
```

The suite covers integer/fractional enlargement and reduction, near-identity
sizes, strong reduction, one-axis changes, both mixed directions and 1×1
output. Defaults are batch=1, RGB and float32. Use `--help` for batch,
channels, dtype, antialiasing and timing options. Results print to stdout;
no dataset is downloaded or result file written automatically.

Every Lanczos method first checks output shape and finiteness. Boundary and
interior samples from the first batch/channel are compared against independent
CPU float64 direct convolution. The test suite additionally checks complete
small multichannel outputs, Pillow float references, large coordinates,
constants, checkerboards, impulses, edges and chirps.

Timing reports warm median device latency. `resize` includes weight generation;
`prepared` excludes construction and autotuning. Neither includes first JIT,
image I/O or CPU/GPU transfers. CUDA event/graph timing is not end-to-end
wall-clock latency. Keep GPU load and inputs consistent, and report cold-start
costs separately. `--alternatives` adds PyTorch bicubic/bilinear as different
filters; their timings do not establish a speedup over another Lanczos implementation.

## Tests

```bash
python -m pip install -e ".[test]"
pytest -q tests/test_api.py  # CPU contracts
pytest -q                  # includes actual GPU tests when available
```

Independent floating references use `atol=rtol=2e-5`; tests also cover
noncontiguous input, supported dtypes, streams, CUDA Graphs and autotuning.
GPU tests skip on CPU-only machines.

## License

MIT. See [LICENSE](LICENSE). The documentation photograph has separate
provenance in [assets/README.md](assets/README.md).

## Publishing

Version tags automatically build and publish to PyPI after CI passes.
See [RELEASING.md](RELEASING.md) for one-time Trusted Publisher setup and release steps.
