A Python library for performing operations on tensors with infinite dimensions.
Project description
Infinite Tensors
A Python library for operating on theoretically infinite tensors using a sliding-window approach. Process tensors that do not fit in memory (or that are genuinely unbounded) by computing and caching only the windows you actually read.
Installation
pip install infinite-tensor
Persistent HDF5 storage is optional:
pip install "infinite-tensor[hdf5]"
What is an Infinite Tensor?
An InfiniteTensor is an immutable tensor defined by a deterministic function f. Any subset of its dimensions can be None (unbounded); the rest are finite. Fully-finite shapes are legal too, but the interesting case is at least one infinite dim.
When you slice it:
- The library enumerates every output window that intersects the requested region.
fis called on each window (and on any upstream dependencies that feed it).- Results are handed to a
TileStore, which caches them so repeat reads are free. - The store assembles the requested pixels and returns a
torch.Tensor.
Overlapping output windows are summed in the region they overlap. That is the only blending rule.
Key Concepts
TensorWindow: output window specification. Fixedsize, optionalstride(defaults tosize, non-overlapping) andoffset(defaults to zeros). Yourfmust return a tensor of exactlysize.InfiniteTensor: immutable tensor with a shape (any number ofNonedims), a deterministicf, a declareddtypeanddevice, and a backingTileStore. Can depend on otherInfiniteTensors throughargs/args_windows.TileStore: owns the processed-window set and the storage. Pick one:MemoryTileStore: in-memory, single LRU cache shared by every tensor registered against it.HDF5TileStore: persistent, accumulates window outputs into fixed-size tiles in an HDF5 file on disk. Requiresh5py(install via thehdf5extra).
Subclass PersistentTileStore (from infinite_tensor.tilestore.persistent import PersistentTileStore) to add other durable backends; see Extending with a custom persistent backend.
Getting Started
1. Creating a tensor
import torch
from infinite_tensor import InfiniteTensor, TensorWindow, MemoryTileStore
tile_store = MemoryTileStore()
def your_processing_function(ctx):
# ctx is the window index (e.g. (wy, wx) for 2D), NOT pixel coordinates.
return torch.ones(512, 512)
tensor = InfiniteTensor(
shape=(None, None),
f=your_processing_function,
output_window=TensorWindow((512, 512)),
tile_store=tile_store,
tensor_id="my_infinite_tensor",
)
tile_store is optional; if omitted, a fresh MemoryTileStore with an unbounded cache is created for you.
tensor_id defaults to a random UUID4. Pass an explicit id if you want to reconnect to stored data on a later run (for persistent stores) or across tensor re-creations in the same session. On re-construction with the same id, the store validates that the new tensor's metadata matches the old one.
2. Slicing
Index like a normal tensor. Computation happens on demand:
result = tensor[0:1024, 0:1024]
__getitem__ supports integer indices, slices, and negative indices. Integer indices squeeze the dimension; slices preserve it.
3. Device and dtype
Pass device= and/or dtype= at construction. f must return tensors matching both exactly (DeviceMismatchError / DtypeMismatchError otherwise). Defaults are cpu / torch.float32. Upstream arg slices are not auto-transferred; if an upstream lives elsewhere, move/cast it inside f.
tensor.to(...) mirrors torch.Tensor.to (device, dtype, or another tensor) and migrates cached state in place. MemoryTileStore supports both device and dtype changes; HDF5TileStore (and any PersistentTileStore) supports device changes but raises ValidationError on dtype changes, so clear_tensor and rebuild if you need to re-cast on disk.
4. Cache size and eviction
MemoryTileStore holds one LRU cache shared by every tensor registered against it:
tile_store = MemoryTileStore(
cache_size_bytes=200 * 1024 * 1024, # 200 MB
cache_size_windows=None, # no window-count limit
)
Either (or both) of cache_size_bytes / cache_size_windows may be None (default, unbounded on that axis) or a positive int.
Eviction is deferred while any tensor is inside a __getitem__ call. Upstream caches are also pinned for the duration of a downstream read, so a single operation that temporarily overruns the cache still succeeds. Once the outermost access returns, the cache trims back to its limits.
HDF5TileStore has its own separate tile LRU; see Persistent storage with HDF5.
5. Clearing state
tensor.clear_cache()
tile_store.clear_tensor(tensor.uuid)
tensor.clear_cache()drops regeneratable cached state for this tensor. OnMemoryTileStoreit wipes the tensor's windows so they recompute on next access. OnHDF5TileStore(and anyPersistentTileStore) it flushes dirty tiles to disk and then drops the tensor's in-memory tile cache; persisted tiles and the processed-window record are untouched.tile_store.clear_tensor(tensor_id)wipes everything for the tensor: registration, metadata, cached windows, and (on persistent stores) the on-disk tile data and processed-window record. This is the only way to un-process a window on a persistent store.
Advanced Features
6. Dependency chaining
Build pipelines by making one infinite tensor depend on another. f is called as f(ctx, *args_sliced), where args_sliced[i] is the upstream args[i] sliced by args_windows[i].get_bounds(ctx).
Keep the output window separate from the arg window; the arg window typically carries the padding/offset:
import torch
import torchvision.transforms.v2.functional as F
from infinite_tensor import InfiniteTensor, TensorWindow, MemoryTileStore
tile_store = MemoryTileStore()
WINDOW = 512
PADDING = 2
PADDED = WINDOW + 2 * PADDING
def random_image(ctx):
return torch.rand(3, WINDOW, WINDOW)
base = InfiniteTensor(
shape=(3, None, None),
f=random_image,
output_window=TensorWindow((3, WINDOW, WINDOW)),
tile_store=tile_store,
tensor_id="base",
)
padded_input_window = TensorWindow(
size=(3, PADDED, PADDED),
stride=(3, WINDOW, WINDOW),
offset=(0, -PADDING, -PADDING),
)
def blur(ctx, padded_input):
blurred = F.gaussian_blur(padded_input, kernel_size=5, sigma=1.0)
return blurred[:, PADDING:-PADDING, PADDING:-PADDING]
blurred = InfiniteTensor(
shape=(3, None, None),
f=blur,
output_window=TensorWindow((3, WINDOW, WINDOW)),
args=(base,),
args_windows=(padded_input_window,),
tile_store=tile_store,
tensor_id="blurred",
)
out = blurred[:, 0:1024, 0:1024]
Do not manually index base inside f. Use args / args_windows; the framework manages slicing, batching, and cross-store cache pinning.
7. Batching
Setting batch_size=N changes the contract of f:
ctxbecomes alist[tuple[int, ...]]of up toNwindow indices.- Each positional arg becomes a
list[torch.Tensor]of the same length (one sliced upstream tensor per window in the batch). fmust return alist[torch.Tensor]of the same length.
def blur_batched(ctxs, padded_inputs):
stack = torch.stack(padded_inputs) # (B, 3, PADDED, PADDED)
blurred = F.gaussian_blur(stack, kernel_size=5, sigma=1.0)
cropped = blurred[:, :, PADDING:-PADDING, PADDING:-PADDING]
return [cropped[i] for i in range(cropped.shape[0])]
blurred = InfiniteTensor(
shape=(3, None, None),
f=blur_batched,
output_window=TensorWindow((3, WINDOW, WINDOW)),
args=(base,),
args_windows=(padded_input_window,),
tile_store=tile_store,
tensor_id="blurred_batched",
batch_size=4,
)
Batch size is a per-tensor knob. base (with batch_size=None) is still called one window at a time.
8. dimension_map on TensorWindow
TensorWindow.dimension_map is an optional per-output-dim remap applied by get_bounds. None in an entry turns that output dim into a singleton (slice(0, 1)); an integer entry says which window-space dim feeds that output dim. It is useful when two output dims must step in lockstep, or when an output dim should never slide.
# Singleton leading dim: output window always covers dim 0 as slice(0, 1).
window = TensorWindow(
size=(1, 64, 64),
stride=(1, 64, 64),
dimension_map=(None, 1, 2),
)
For most pipelines you can leave dimension_map=None (the default); size/stride/offset on the output dim itself already handle fixed-size dims.
9. Persistent storage with HDF5
from infinite_tensor import HDF5TileStore, InfiniteTensor, TensorWindow
with HDF5TileStore("tensor_data.h5", tile_size=512) as tile_store:
tensor = InfiniteTensor(
shape=(None, None),
f=your_processing_function,
output_window=TensorWindow((512, 512)),
tile_store=tile_store,
tensor_id="my_infinite_tensor",
)
result = tensor[0:2048, 0:2048]
# leaving the `with` block flushes dirty tiles and closes the file
tile_size controls how HDF5 accumulates overlapping window outputs into on-disk tiles. It may be an int (applied uniformly to every infinite dim) or a tuple sized to the tensor's infinite-dim count. Reopening the same file with the same tensor_id must use matching metadata and tile_size, or register_tensor raises ValidationError.
HDF5TileStore uses a write-back tile cache. Dirty tiles live in memory until:
- they are evicted from the LRU,
- you call
tile_store.flush()(persist everything, keep the file open), - or you call
tile_store.close()/ exit thewithblock.
Crashing before any of those loses the dirty tiles. Always wrap long-running persistent workloads in with HDF5TileStore(...) as store:, or call flush() at checkpoints.
HDF5 tile caching is separate from the MemoryTileStore cache:
HDF5TileStore(
"tensor_data.h5",
tile_size=512,
cache_size_bytes=256 * 1024 * 1024, # 256 MiB tile LRU; default 100 MiB
cache_size_tiles=None, # no tile-count limit
)
Reconnecting in a later process:
with HDF5TileStore("tensor_data.h5", tile_size=512) as tile_store:
tensor = InfiniteTensor(
shape=(None, None),
f=your_processing_function, # same f, or a determinism-equivalent one
output_window=TensorWindow((512, 512)),
tile_store=tile_store,
tensor_id="my_infinite_tensor", # same id as before
)
result = tensor[0:2048, 0:2048] # served from disk; f is only called for new windows
Once a window is processed on a persistent store it cannot be un-processed. clear_cache only drops in-memory buffers; use tile_store.clear_tensor(tensor.uuid) to wipe a tensor's durable state.
Extending with a custom persistent backend
PersistentTileStore implements the tile-accumulation scaffolding (tile math, LRU, window accumulation, write-back) generically. To plug in a different on-disk format, subclass it and implement the seven storage primitives:
from infinite_tensor.tilestore.persistent import PersistentTileStore
See infinite_tensor/tilestore/persistent.py for the full contract (and hdf5_tilestore.py for a worked example).
Important Notes
- Deterministic
f: stored outputs assumefis pure. Non-determinism silently breaks the "already processed, skip" optimization and can corrupt dependent tensors. - Exact output shape:
f's return must matchTensorWindow.sizeexactly (ShapeMismatchErrorotherwise). - Device and dtype: every
InfiniteTensordeclares adeviceanddtype(defaults:cpu,torch.float32).fmust return tensors on that exact device and with that exact dtype (DeviceMismatchError/DtypeMismatchErrorotherwise). Upstream arg slices are not auto-transferred; move or cast them insidefif they come from a different device/dtype. - Finite dimensions must fit in memory: non-
Nonedims are materialized whole whenever a window touches them. - Reconnecting: to reuse stored data, re-construct the tensor with the same
tensor_idagainst the same store. The store validates thatto_json()matches what it saw before and raisesValidationErroron mismatch. - Avoid manual slicing of dependencies: use
args/args_windows. Manual indexing insidefdefeats batching and future memory-management optimizations. - Exploding compute:
_ensure_processedrecurses into every upstream's window grid with the arg window's pixel bounds. Each chained tensor expands the "cone of interest" by its padding/offset. A deep chain with even modest overlap can detonate the amount of work a single slice triggers. Keep chains shallow and prefer a single fusedfover many thin layers. - Immutability of persistent stores: a processed window on
HDF5TileStorecannot be un-processed withouttile_store.clear_tensor(uuid).
Public exceptions
All live at infinite_tensor top level:
| Exception | Raised when |
|---|---|
InfiniteTensorError |
base class for everything below |
ValidationError |
constructor param validation, metadata mismatch on re-registration, unsupported dtype migration |
ShapeMismatchError |
f returned a tensor whose shape differs from TensorWindow.size |
DeviceMismatchError |
f returned a tensor on a different device than the tensor was declared with |
DtypeMismatchError |
f returned a tensor with a different dtype than the tensor was declared with |
TileAccessError |
internal: a tile was requested for read but never processed (indicates a store-consistency bug) |
Example
See examples/blur.py for a full worked example: a random-image base tensor blurred twice via two chained InfiniteTensors with a padded arg window.
License
MIT License. See LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file infinite_tensor-0.3.0.tar.gz.
File metadata
- Download URL: infinite_tensor-0.3.0.tar.gz
- Upload date:
- Size: 41.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e3e829b7c3ca0e8f33335d1108751db64356e6624a9580eebae715c395fb09c
|
|
| MD5 |
892ced2001a25c21e308ad5f165927bf
|
|
| BLAKE2b-256 |
9b74f2d77b5d34937d439cf0eba9c95cdf28c6f97c5fa197918bd754e8152cf5
|
Provenance
The following attestation bundles were made for infinite_tensor-0.3.0.tar.gz:
Publisher:
release.yml on xandergos/infinite-tensor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
infinite_tensor-0.3.0.tar.gz -
Subject digest:
6e3e829b7c3ca0e8f33335d1108751db64356e6624a9580eebae715c395fb09c - Sigstore transparency entry: 1339654805
- Sigstore integration time:
-
Permalink:
xandergos/infinite-tensor@7f1a33d1076580b0be3268b537b02d61eaaf0ec2 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/xandergos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7f1a33d1076580b0be3268b537b02d61eaaf0ec2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file infinite_tensor-0.3.0-py3-none-any.whl.
File metadata
- Download URL: infinite_tensor-0.3.0-py3-none-any.whl
- Upload date:
- Size: 32.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c7c9e2bd45ba7cb431462ce72e495e822fca1928af182075cc1878243671dff
|
|
| MD5 |
572e96fbdb01800f1f60ba623c728abe
|
|
| BLAKE2b-256 |
2290dbf1e43deca67eb9f1e572e25275566f9bcb303e06282a62ec9aa3f20faf
|
Provenance
The following attestation bundles were made for infinite_tensor-0.3.0-py3-none-any.whl:
Publisher:
release.yml on xandergos/infinite-tensor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
infinite_tensor-0.3.0-py3-none-any.whl -
Subject digest:
8c7c9e2bd45ba7cb431462ce72e495e822fca1928af182075cc1878243671dff - Sigstore transparency entry: 1339654810
- Sigstore integration time:
-
Permalink:
xandergos/infinite-tensor@7f1a33d1076580b0be3268b537b02d61eaaf0ec2 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/xandergos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7f1a33d1076580b0be3268b537b02d61eaaf0ec2 -
Trigger Event:
release
-
Statement type: