PyDotNet.Torch
A typed PyTorch plugin for PyDotNet — autograd, device movement, and zero-copy DLPack interop for torch.Tensor from .NET.
Installation
dotnet add package PyDotNet.Torch
PyTorch must be installed in the active Python environment:
pip install torch
Quick start
using PyDotNet.Torch;
using PyDotNet.Runtime;
using PyDotNet.Types;
PyRuntime.Initialize(new PyRuntimeOptions { ReleaseGilAfterInit = true });
using var interp = PyRuntime.CreateInterpreter();
// Create tensors
using var a = PyTorchTensor.Ones(interp, new[] { 3, 4 });
using var b = PyTorchTensor.Zeros(interp, new[] { 3, 4 });
// Arithmetic
using var c = a + b;
using var d = a * 2f + b;
// Shape ops
using var flat = a.Reshape(12); // 1-D view
using var col = flat.Unsqueeze(1); // (12, 1)
// Reductions
using var mean = a.Mean();
using var sum = a.Sum(dim: 1);
PyRuntime.Shutdown();
Zero-copy interop
FromArray<T> — .NET array → torch tensor
FromArray<T> pins the source array and hands a DLPack capsule to torch.from_dlpack. No data is copied — the tensor shares the same memory as the .NET array. The GC handle is released automatically when the tensor is disposed.
float[] weights = new float[3 * 4];
Random.Shared.NextBytes(MemoryMarshal.AsBytes(weights.AsSpan()));
using var tensor = PyTorchTensor.FromArray(interp, weights, new[] { 3, 4 });
// tensor.Shape → [3, 4], tensor.Device → Cpu, tensor.DataType → Float32
ToDLPack() — tensor → DLPackTensor
Export a CPU tensor back to a DLPackTensor (zero-copy). The tensor must not have requires_grad = true; call Detach() first if needed.
using var dlpack = tensor.ToDLPack();
ReadOnlySpan<float> span = dlpack.AsReadOnlySpan<float>();
AsTensorBuffer() — buffer protocol
AsTensorBuffer() acquires a PyBuffer from the tensor via detach().numpy() (zero-copy for contiguous CPU tensors). Use this when you need Span<T> access to tensor data in-place.
using var buf = tensor.AsTensorBuffer(writable: true);
Span<float> span = buf.AsSpan<float>();
span.Fill(0f); // writes directly into the tensor's storage
ToArray<T> — managed copy
When you just need a snapshot of the data:
float[] data = tensor.ToArray<float>();
If RequiresGrad is true, the tensor is detached automatically before copying.
Autograd
using var x = PyTorchTensor.Ones(interp, new[] { 3 }, requiresGrad: true);
using var y = x * x; // y = x²
using var z = y.Sum(); // scalar
z.Backward(); // ∂z/∂x = 2x
using var grad = x.Grad!; // grad.Shape == [3]
float[] g = grad.ToArray<float>();
// g == [2, 2, 2]
Backward() propagates gradients from a scalar tensor. Grad returns null until Backward() has been called on a downstream scalar. HasGrad is a cheaper check that avoids constructing the gradient tensor object.
API reference
PyTorchTensor
All methods return a new PyTorchTensor (owned reference, IDisposable) unless stated otherwise.
Metadata
| Member | Type | Description |
|---|---|---|
Shape |
IReadOnlyList<long> |
Tensor dimensions. |
Rank |
int |
Number of dimensions. |
ElementCount |
long |
Total number of elements. |
DataType |
TensorDataType |
Element type (Float32, Float64, Int32, Int64, …). |
Device |
TensorDevice |
Compute device (Cpu, Cuda). |
Autograd
| Member | Description |
|---|---|
RequiresGrad |
Gets or sets gradient tracking. |
HasGrad |
true after Backward() has been called on a downstream scalar. |
Grad |
Returns the accumulated gradient tensor, or null. |
Backward() |
Runs back-propagation (scalar tensors only). |
Detach() |
New tensor detached from the autograd graph (shared storage). |
Device movement
| Member | Description |
|---|---|
To(string device) |
Copy to named device ("cpu", "cuda", "cuda:0"). |
Cpu() |
Copy to CPU. |
Cuda(int deviceIndex = 0) |
Copy to CUDA device. |
Arithmetic
| Member | Python equivalent | Description |
|---|---|---|
Add(other) / + |
a + b |
Element-wise addition. |
Subtract(other) / - |
a - b |
Element-wise subtraction. |
Multiply(other) / * |
a * b |
Element-wise multiplication. |
Divide(other) / / |
a / b |
Element-wise true division. |
MatMul(other) / @ |
a @ b |
Matrix multiplication. |
Negate() / unary - |
-a |
Negates all elements. |
Shape
| Member | Python equivalent | Description |
|---|---|---|
Reshape(params int[]) |
reshape(...) |
New shape (may copy if non-contiguous). |
View(params int[]) |
view(...) |
New shape; tensor must be contiguous. |
Transpose(int, int) |
transpose(d0, d1) |
Swap two dimensions. |
Transposed |
.T |
2-D transpose (rank-2 only). |
Squeeze() |
squeeze() |
Remove all size-1 dimensions. |
Squeeze(int) |
squeeze(dim) |
Remove specified size-1 dimension. |
Unsqueeze(int) |
unsqueeze(dim) |
Insert a size-1 dimension. |
Permute(params int[] dims) |
permute(dims) |
Permute tensor dimensions in the specified order. |
Flatten() |
flatten() |
Flatten to a 1-D tensor. |
Clone() |
clone() |
Deep copy with its own storage. |
Contiguous() |
contiguous() |
Return a C-contiguous tensor (copies if needed). |
Reductions
| Member | Python equivalent | Description |
|---|---|---|
Mean() |
mean() |
Mean of all elements. |
Mean(int dim) |
mean(dim) |
Mean along one dimension. |
Sum() |
sum() |
Sum of all elements. |
Sum(int dim) |
sum(dim) |
Sum along one dimension. |
Min() |
min() |
Minimum of all elements (0-D tensor). |
Min(int dim) |
min(dim) |
Min along one dimension; returns a named-tuple tensor (values, indices). |
Max() |
max() |
Maximum of all elements (0-D tensor). |
Max(int dim) |
max(dim) |
Max along one dimension; returns a named-tuple tensor (values, indices). |
ArgMin(int dim) |
argmin(dim) |
Index of minimum along one dimension (0-D tensor). |
ArgMax(int dim) |
argmax(dim) |
Index of maximum along one dimension (0-D tensor). |
Var() |
var() |
Variance of all elements (Bessel-corrected, ddof=1). |
Std() |
std() |
Standard deviation of all elements. |
Norm(float p = 2f) |
norm(p) |
Lp norm of all elements. |
Element-wise math
| Member | Python equivalent |
|---|---|
Abs() |
abs() |
Exp() |
exp() |
Log() |
log() |
Log2() |
log2() |
Log10() |
log10() |
Sqrt() |
sqrt() |
Pow(double exponent) |
pow(exp) |
Clamp(double min, double max) |
clamp(min, max) |
Activations
| Member | Python equivalent |
|---|---|
Relu() |
relu() |
Sigmoid() |
sigmoid() |
Tanh() |
tanh() |
Softmax(int dim) |
softmax(dim) |
Data access
| Member | Description |
|---|---|
Item<T>() |
Extracts the scalar value from a 0-D tensor. Use after Min(), Max(), Var(), Std(), Norm(), ArgMin(), ArgMax(). |
ToArray<T>() |
Copies all elements to a managed array. |
ToDLPack() |
Zero-copy DLPackTensor (CPU, no grad). |
AsTensorBuffer(bool writable) |
Zero-copy PyBuffer via detach().numpy() (CPU only). |
Factory methods
All factory methods are static and return a new owned tensor.
| Member | Python equivalent | Description |
|---|---|---|
Zeros(interp, shape, dtype, requiresGrad) |
torch.zeros(...) |
Tensor of zeros. |
Ones(interp, shape, dtype, requiresGrad) |
torch.ones(...) |
Tensor of ones. |
Empty(interp, shape, dtype, requiresGrad) |
torch.empty(...) |
Uninitialized tensor. |
Full(interp, shape, fillValue, dtype) |
torch.full(...) |
Constant-filled tensor. |
Arange(interp, start, end, step) |
torch.arange(...) |
Range tensor with given step (float). |
Linspace(interp, start, end, steps) |
torch.linspace(...) |
Linearly spaced tensor with steps elements. |
Cat(interp, tensors[], dim) |
torch.cat(...) |
Concatenate tensors along an existing dimension. |
Stack(interp, tensors[], dim) |
torch.stack(...) |
Stack tensors along a new dimension. |
FromArray<T>(interp, data, shape) |
torch.from_dlpack(...) |
Zero-copy from .NET array via DLPack. |
From(PyObject) |
— | Wraps an existing Python torch.Tensor object. |
TensorDataType
| Value | torch dtype |
|---|---|
Float32 |
torch.float32 |
Float64 |
torch.float64 |
Int32 |
torch.int32 |
Int64 |
torch.int64 |
Int16 |
torch.int16 |
Int8 |
torch.int8 |
UInt8 |
torch.uint8 |
Bool |
torch.bool |
TensorDevice
| Value | Description |
|---|---|
Cpu |
Host memory. |
Cuda |
NVIDIA GPU memory. |
Python API coverage
~55 of the ~700 methods on torch.Tensor are wrapped.
Notable gaps: index/slice access (tensor[i, j]), in-place (_-suffixed) variants, topk, gather, scatter, color bars, and sparse/complex tensor operations.
Supported frameworks
net8.0 · net9.0 · net10.0