blob: 150244d9bbf0d16af5b2f8df7863fb034a528ead [file] [edit]
---
title: Getting Started
description: How to configure Pyrefly for tensor shape checking and set up your project.
---
{/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/}
# Getting Started with Pyrefly Tensor Shapes
This page walks you through configuring Pyrefly for tensor shape checking and
getting your first shape-annotated code running.
## Installation
Tensor shape checking requires shape-aware stubs to be available through normal
import resolution. Install them from PyPI:
```bash
pip install pyrefly-torch-stubs
```
`pyrefly-torch-stubs` is a PEP 561 stub-only package: it carries type
information for PyTorch, and leaves the runtime `torch` package alone. PyTorch's
own type stubs don't carry shape information, so these stubs take precedence
over them and provide shape-aware versions (e.g., `nn.Conv2d.__init__` that
captures kernel size, stride, and padding as type-level values, and a `forward`
that computes the output spatial dimensions).
Installing it also pulls in `pyrefly-shape-extensions`, which provides the
`shape_extensions` package. `shape_extensions` exports `Int` the bridge
between runtime integer values and type-level symbols. Both packages are
versioned in lockstep with Pyrefly.
Tensor shape support is enabled automatically when Pyrefly can resolve the
`shape_extensions` package, so nothing else needs configuring, as long as
Pyrefly resolves imports against the
[Python environment](../configuration#python-interpreter-path) you installed
into.
### Checking against a local copy of the stubs
The stubs also live in Pyrefly's source tree under
[`tensor-shapes/`](https://github.com/facebook/pyrefly/tree/main/tensor-shapes),
which is where to work if you want to read or modify them. Copy that directory
into your project and point `search-path` at the two package directories inside
it. The paths are relative to the location of your `pyrefly.toml` (or of your
`pyproject.toml`, if you configure Pyrefly under `[tool.pyrefly]`):
```toml
search-path = [
"tensor-shapes/pyrefly-torch-stubs",
"tensor-shapes/pyrefly-shape-extensions",
]
```
A copy that lives inside your project is also checked as project code. The stubs
use PEP 696 type parameter defaults (`class Conv1d[..., S: IntVar = 1]`), which
Pyrefly parses only under `python-version` 3.13 or later, so checking them under
an earlier version reports:
```
ERROR Cannot set default type for a type parameter on Python 3.12 (syntax was added in Python 3.13) [invalid-syntax]
```
Either raise the version Pyrefly checks against. `python-version` sets the
version your code is checked for; it does not change the interpreter your code
runs on:
```toml
python-version = "3.13"
```
Or, if your project targets an earlier version and you don't want to change how
your own code is checked, exclude the copy from checking instead. Imports still
resolve through `search-path`:
```toml
project-excludes = ["tensor-shapes/**"]
```
## Imports and runtime considerations
Python evaluates type annotations at runtime by default. This is a problem
for tensor shape annotations because Python's built-in `typing.TypeVar`
doesn't support arithmetic expressions like `D // NHead` in an annotation
will raise `TypeError` when the annotation is evaluated. There are two ways
to avoid this:
### Option 1: `from __future__ import annotations` (recommended)
Adding this import at the top of the file defers evaluation of all
annotations, so shape arithmetic never executes at runtime:
```python
from __future__ import annotations
import torch
import torch.nn as nn
from torch import Tensor
from shape_extensions import Int
```
This works with both old-style and new-style generics (PEP 695
`class Foo[T]` syntax).
**`assert_type` during development:** You can use `assert_type` while
porting to verify shapes via `pyrefly check`. Once you're done, remove the
`assert_type` calls — each one corresponds to an IDE inlay type hint that
shows the same information permanently. Pyrefly catches shape errors
through your function signatures and return types regardless.
Note that `assert_type` forces evaluation of its type argument, so the
file will crash if you try to *run* it with `assert_type` calls still
present. This is fine — just remove them when the port is complete.
You can also guard `Tensor` and `Int` under `TYPE_CHECKING` if you prefer
to keep shape imports invisible at runtime:
```python
from __future__ import annotations
from typing import TYPE_CHECKING
import torch
import torch.nn as nn
if TYPE_CHECKING:
from torch import Tensor
from shape_extensions import Int
```
### Option 2: `shape_extensions.IntVar` (runtime-compatible)
If you need annotations to evaluate at runtime (e.g., for runtime shape
validation or keeping `assert_type` in production code), import
`shape_extensions` directly. The package patches
`torch.Tensor`, `nn.Conv2d`, and other torch classes to accept subscript
syntax at runtime without crashing. It also provides a `IntVar` that
supports arithmetic (`N + 1` returns `self` instead of raising `TypeError`).
Use old-style generics with `shape_extensions.IntVar`:
```python
from typing import assert_type
import torch
import torch.nn as nn
from torch import Tensor
from shape_extensions import Int, IntVar
N = IntVar("N")
M = IntVar("M")
class Linear(nn.Module):
def __init__(self, n: Int[N], m: Int[M]):
...
```
PEP 695 new-style generics (`class Foo[T]`) automatically use
`typing.TypeVar` internally, which doesn't support arithmetic so this
option requires old-style generics.
```sandbox
dir: tensor-shapes-setup
description: Experiment with Int arithmetic, TYPE_CHECKING imports, and building typed tensors — no setup required.
```
## Hello world
Here's a minimal example to verify everything works. This example uses
Option 1 (`from __future__ import annotations`) since it's the simplest
setup. We skip `assert_type` instead, run `pyrefly check` and use your
IDE's inlay type hints to verify shapes.
Create a file `hello_shapes.py`:
```python
from __future__ import annotations
import torch
import torch.nn as nn
from torch import Tensor
from shape_extensions import Int, IntVar
class TwoLayerNet[InDim: IntVar, HidDim: IntVar, OutDim: IntVar](nn.Module):
def __init__(
self,
in_dim: Int[InDim],
hid_dim: Int[HidDim],
out_dim: Int[OutDim],
):
super().__init__()
self.fc1 = nn.Linear(in_dim, hid_dim)
self.fc2 = nn.Linear(hid_dim, out_dim)
def forward[B: IntVar](self, x: Tensor[[B, InDim]]) -> Tensor[[B, OutDim]]:
h = self.fc1(x) # pyrefly infers: Tensor[[B, HidDim]]
return self.fc2(torch.relu(h))
```
Run `pyrefly check hello_shapes.py`. You should see no errors — pyrefly
infers the shapes through the `nn.Linear` calls.
If you're using an IDE with Pyrefly's language server, you'll see inlay
type hints showing the inferred shape of `h` as `Tensor[[B, HidDim]]`
without needing any `assert_type` calls.
### Inlay hints in action
Here's what inlay hints look like on a real model (NanoGPT). The MLP
module shows shapes flowing through linear layers and activations:
<img src="/img/tensor-shapes/nanogpt-mlp.png" alt="NanoGPT MLP module with inlay type hints showing Tensor[B, T, 4 * NEmbedding] after the expansion layer" />
The forward method signature shows how `x.size()` unpacks into typed
dimensions:
<img src="/img/tensor-shapes/nanogpt-forward-signature.png" alt="NanoGPT forward signature with x.size() unpacking to b: Int[B], t: Int[T], c: Int[NEmbedding]" />
And the attention module, where view/transpose reshapes for multi-head
attention are fully tracked:
<img src="/img/tensor-shapes/nanogpt-attention-qkv.png" alt="NanoGPT attention QKV computation with inlay type hints showing Tensor[B, NHead, T, NEmbedding // NHead]" />
The full attention body, including both flash and manual paths:
<img src="/img/tensor-shapes/nanogpt-attention-body.png" alt="NanoGPT attention body with flash and manual attention paths fully shape-tracked" />