Guard every assumption that changes indexing or semantics
Derive a stride counterexample and connect artifact dispatch to shape, layout, dtype, device, and explicitly specialized values.
On this page
The question for today
What you’ll understand
- Explain why equal shapes do not imply equal memory layouts.
- Calculate element and byte offsets from explicit strides.
- Design guard-positive and guard-negative tests for a specialized artifact.
A specialized artifact is a program plus conditions under which its assumptions hold. Shape is only one part of that agreement. Strides can change how an index reaches storage, dtype changes element representation and arithmetic, and device changes which executable and memory space are valid. A captured Python constant can also influence the result even though no tensor shape changes. Guards belong to the correctness boundary: a fast implementation that dispatches under an incomplete condition can return plausible but incorrect numbers.
Start with the address expression the kernel assumes
For a two-dimensional strided view with element strides s0 and s1, the storage element offset of index [i,j] is offset0+i·s0+j·s1. Contiguous row-major shape [2,3] usually has strides [3,1], but the transpose of a contiguous [3,2] tensor has the same [2,3] shape and strides [1,2]. A kernel that silently substitutes contiguous indexing changes which elements it reads. Shape-only testing can miss this if every benchmark input is created with the same layout constructor.
Use deliberately distinctive contents to expose the mismatch. Storage [0,1,2,3,4,5] viewed with shape [2,3], strides [1,2], and zero storage offset gives rows [0,2,4] and [1,3,5]. At index [0,1], the correct value is two while assumed contiguous indexing reads one. Both are finite, ordinary numbers, so a crash detector will not catch this bug. A layout guard or a genuinely stride-general implementation is necessary; a convenient allocation pattern is not a substitute.
Specialization includes more than sizes
A dtype assumption can select different instructions, accumulation precision, and byte offsets. A device assumption identifies where data resides and which generated code can access it. A static scalar option may select an activation, axis, or training branch. If compilation treats that option as fixed, later changes need an appropriate dispatch condition or a dynamic representation. Do not guard an entire weight tensor's identity merely because that was easy unless identity is the actual required condition; changing weight contents and replacing weight objects are different events.
PyTorch 2.9's documented guards and recompilation behavior illustrate these distinctions. A failed condition can cause a new variant or eager execution under the runtime's policy, with existing valid variants still potentially reusable. Cache limits and names of internal configuration settings are version-specific, so this lesson does not prescribe a universal limit. Audit the generated assumptions for the chosen release. Then test that each deliberately violated assumption routes safely, rather than accepting that warm calls on the initial example succeed.
Vary one assumption at a time and inspect the outcome
Build paired fixtures that hold contents and shape stable while changing layout, or hold shape and layout stable while changing a static option. Expected behavior should identify whether the call reuses a compatible artifact, selects another artifact, recaptures, or falls back. A guard check must precede any operation that relies on it. Testing only that the final numerical result is sometimes close can conceal an invalid dispatch whose wrong address happens to contain a similar value.
The cache lab helps separate artifact diversity from correctness of artifact guards. Reset and set both C=4,s=4 to inspect four repeating identifiers with twelve initial hits; Reset and set both C=4,s=1 to inspect sixteen identifiers with zero initial hits. Predict before Play or Step. Time-zero already includes sixteen requests and playback caps at 4,096. Identifiers can stand for distinct fictional variants, but this fully associative LRU stream does not evaluate shape, layout, device, or value conditions and cannot validate any real dispatch policy.
Work it through
Find a shape-only dispatch error
Use float32 storage [0,1,2,3,4,5], zero storage offset, and the valid shape [2,3] view with strides [1,2]. A specialized kernel assumes strides [3,1]. Compare reads at index [0,1], with four bytes per element.
The correct element offset is 0×1+1×2=2. It lies eight bytes from the storage base and contains the value two.
The assumed contiguous offset is 0×3+1×1=1. It lies four bytes from the same base and contains one, despite the shape matching exactly.
The fixture disproves the shape-only dispatch condition for this kernel. Add the necessary layout condition or implement correct general-stride addressing; then verify that the transposed view is rejected, redirected, or handled correctly.
The wrong dispatch produces one where the tensor semantics require two. Valid bounds and a matching shape do not repair the incorrect stride assumption.
The living notebook
Explore the model
Interactive lab · cache
Separate variant reuse from variant validity
Compare fresh Reset → set both C=4,s=4 and Reset → set both C=4,s=1. Predict twelve versus zero hits over the initial sixteen requests, then Play or Step. The 4,096-request-capped LRU trace contains identifiers only and does not execute compiler guards.
- Hits / requests
- 0 / 16
- Hit rate
- 0%
- Address-stream period
- 16 addresses
A fully associative LRU cache starts empty and requests address (i·s) mod 16. Hits move an address to the newest position; misses evict the oldest when full. Playback starts with 16 requests and adds two per second, capped at 4096 explicitly simulated requests. This toy stream is not a processor-cache benchmark. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forA cache-hit model can explain reuse pressure, while guard correctness needs metadata-specific tests.
Pause & reason
Check your understanding
Your turn
Try it yourself
A captured function uses a static Boolean normalize=True. The same-shape call later passes False, and the generated artifact has deleted that branch. Which test is missing if all existing tests use True, and what is the required dispatch obligation?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
A non-tensor argument can change semantics without changing strides, dtype, shape, or device.
Compare with the solution
Add a same-tensor, changed-option fixture with normalize=False and a reference result that distinguishes the branches. If the artifact specializes True, dispatch must establish that value before reuse or select another correct path. The test should check both the numerical behavior and the expected reuse or fallback event. Treating every metadata match as sufficient would leave a value specialization unguarded, even though all tensor shapes remained identical.
Keep the idea
Take it with you
- Strides determine element addresses and cannot be inferred from shape alone.
- Dtype, device, static values, and other relevant assumptions belong to the artifact contract.
- Guard-negative fixtures must verify safe behavior before specialization-dependent execution.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.