advanced27 min studyLesson 514 in Engineering

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

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.

o(i,j)=o0+is0+js1,a(i,j)=a0+d o(i,j)o(i,j)=o_0+i s_0+j s_1,\qquad a(i,j)=a_0+d\,o(i,j)
o is an element offset, d is bytes per element, and a is a byte address relative to storage base a0. The formula assumes valid in-bounds indices and a supported strided layout. It does not prove bounds, alignment, lifetime, or lack of overlapping views.↔ Scroll wide equations horizontally.

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.

G=Gshape∧Gstride∧Gdtype∧Gdevice∧GstaticG=G_{\rm shape}\land G_{\rm stride}\land G_{\rm dtype}\land G_{\rm device}\land G_{\rm static}
This schematic conjunction includes the conditions actually needed by a chosen specialization; a more general implementation can omit conditions it does not require. G is Boolean and dimensionless. It is not a complete list of all runtime assumptions, such as aliasing or relevant ambient state.↔ Scroll wide equations horizontally.

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.

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.

  1. The correct element offset is 0×1+1×2=2. It lies eight bytes from the storage base and contains the value two.

  2. 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.

  3. 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 result

The wrong dispatch produces one where the tensor semantics require two. Valid bounds and a matching shape do not repair the incorrect stride assumption.

Explore the model

Interactive lab

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.

Separate variant reuse from variant validityA 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. Current readings: Hits / requests: 0 / 16; Hit rate: 0%; Address-stream period: 16 addresses. All parameters are adjustable with the labeled controls below.Fully associative LRU cache after 16 deterministic requests: 0 hits and 16 misses.Most recent 16 requestsaddress = i × 3 mod 160M3M6M9M12M15M2M5M8M11M14M1M4M7M10M13MCurrent cache order · oldest → newest471013Latest address 13 · miss: inserted as newestToy address stream · period 16 · starts empty
Cached addressMiss / evictionMost recent request
Hits / requests
0 / 16
Hit rate
0%
Address-stream period
16 addresses
t = 0 s

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.

Check your understanding

A [2,3] float32 input changes from contiguous strides [3,1] to valid strides [1,2]. What must a contiguous-only artifact do?

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.

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.