Skip to content

Devices

SILVA Networks uses standard PyTorch device semantics.

from silva_networks import resolve_device

device = resolve_device("auto")

The resolver returns CUDA when available, then MPS when available, otherwise CPU.

Tensor Placement

Every tensor participating in a forward pass should live on the same device:

\[ \operatorname{device}(x) = \operatorname{device}(\texttt{edge\_index}) = \operatorname{device}(\theta). \]

Use move_to_device for nested dictionaries, tuples, and lists:

from silva_networks import move_to_device

batch = move_to_device(batch, device)
model = model.to(device)

Solvers allocate residual workspaces, identity matrices, and aggregation buffers on the device of the current state.

Operational Contract

This API surface connects device and dtype propagation to the same SILVA experiment contract used by the learning pages and notebooks. Its central relation is

\[ T_\theta:\mathbb R^{B\times N\times D}_{(d,q)}\rightarrow\mathbb R^{B\times N\times D}_{(d,q)} \]
Part What must remain inspectable
State the same state layout on the selected device and floating dtype.
Condition inputs, parameters, temporary tensors, solver history, and outputs must agree on device and dtype.
Diagnostic shape, finite values, gradient availability, and residual.
Replacement point the automatic device selection with an explicit device passed by the experiment runner.
Scale axes batch size, precision, device count, and data-loader workers.

The relevant method lineage is recorded in the SILVA construction [1] and implicit-layer foundation [4]. Those references define the source mechanisms; this API exposes them through SILVA objects so a reader can inspect, replace, solve, differentiate, and scale the construction.

Complete Compact Study

Run the complete repository program below from the project root. The page uses the same file that is exercised by the test suite, so the displayed call is not an isolated fragment.

from __future__ import annotations

import torch

from silva_networks import SILVAGraphLayer, SolverConfig, stability_report


def main() -> None:
    torch.manual_seed(7)
    x = torch.randn(8, 5)
    y = (x[:, 0] + x[:, 1] > 0).long()
    edge_index = torch.tensor(
        [[0, 1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7, 0]],
        dtype=torch.long,
    )

    layer = SILVAGraphLayer(5, 12, config=SolverConfig(max_iter=18, alpha=0.45))
    head = torch.nn.Linear(12, 2)
    z = layer(x, edge_index=edge_index)
    loss = torch.nn.functional.cross_entropy(head(torch.tanh(z)), y)
    loss.backward()
    report = stability_report(lambda zz: layer.f(zz, x, edge_index=edge_index), z, samples=2, iters=10)

    print("state_shape", tuple(z.shape))
    print("loss", float(loss.detach()))
    print("residual", report.residual)
    print("spectral_radius", report.spectral_radius)


if __name__ == "__main__":
    main()
python examples/graph_silva.py

Measured Compact Output

state_shape (8, 12)
loss 0.7801069021224976
residual 0.07725001126527786
spectral_radius 0.7778381109237671

Interpret the Output

The printed shape confirms the graph state contract, and the finite loss, residual, and spectral-radius estimate are computed on the same selected device. Device equivalence still requires a separate CPU/accelerator comparison with fixed seeds.

For a controlled experiment, retain the compact call as a regression case and change one scale axis at a time. Record the resolved constructor, data source and split, preprocessing, seed, forward and backward solver settings, task metric, normalized residual, iteration count, runtime, peak memory, and any failed convergence case. A larger run becomes evidence only when its own resolved configuration and outputs are archived; the compact output above is evidence for the executable mechanism and its stated invariants.

available_devices

available_devices()

Return the PyTorch device backends currently available.

Source code in src/silva_networks/device.py
def available_devices() -> list[str]:
    """Return the PyTorch device backends currently available."""

    devices = ["cpu"]
    if torch.cuda.is_available():
        devices.append("cuda")
    mps = getattr(torch.backends, "mps", None)
    if mps is not None and mps.is_available():
        devices.append("mps")
    return devices

module_device

module_device(module)

Return the first parameter or buffer device for a module.

Source code in src/silva_networks/device.py
def module_device(module: torch.nn.Module) -> torch.device:
    """Return the first parameter or buffer device for a module."""

    for parameter in module.parameters(recurse=True):
        return parameter.device
    for buffer in module.buffers(recurse=True):
        return buffer.device
    return torch.device("cpu")

move_to_device

move_to_device(value, device='auto', non_blocking=True)

Recursively move tensors in common batch containers to a device.

Source code in src/silva_networks/device.py
def move_to_device(value: Any, device: str | torch.device | None = "auto", non_blocking: bool = True) -> Any:
    """Recursively move tensors in common batch containers to a device."""

    resolved = resolve_device(device)
    if torch.is_tensor(value):
        return value.to(resolved, non_blocking=non_blocking)
    if isinstance(value, Mapping):
        return type(value)((key, move_to_device(item, resolved, non_blocking)) for key, item in value.items())
    if isinstance(value, tuple) and hasattr(value, "_fields"):
        return type(value)(*(move_to_device(item, resolved, non_blocking) for item in value))
    if isinstance(value, tuple):
        return tuple(move_to_device(item, resolved, non_blocking) for item in value)
    if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
        return type(value)(move_to_device(item, resolved, non_blocking) for item in value)
    return value

resolve_device

resolve_device(device='auto')

Resolve "auto", "cuda", "mps", or "cpu" to a PyTorch device.

Source code in src/silva_networks/device.py
def resolve_device(device: str | torch.device | None = "auto") -> torch.device:
    """Resolve ``"auto"``, ``"cuda"``, ``"mps"``, or ``"cpu"`` to a PyTorch device."""

    if device is None or str(device) == "auto":
        if torch.cuda.is_available():
            return torch.device("cuda")
        mps = getattr(torch.backends, "mps", None)
        if mps is not None and mps.is_available():
            return torch.device("mps")
        return torch.device("cpu")

    resolved = torch.device(device)
    if resolved.type == "cuda" and not torch.cuda.is_available():
        raise RuntimeError("CUDA was requested, but torch.cuda.is_available() is False")
    if resolved.type == "mps":
        mps = getattr(torch.backends, "mps", None)
        if mps is None or not mps.is_available():
            raise RuntimeError("MPS was requested, but torch.backends.mps.is_available() is False")
    return resolved

Where to Go Next

Question Page
How are several points placed across devices? Stacking and Devices
Which optional backends can be installed? Installation
Which model containers use these helpers? Architectures API