Memory Formats & Layouts¶
Overview¶
Memory format (contiguous vs channels_last vs blocksparse) determines how kernels read/write tensors in DRAM. The same logical tensor can be 2-4x faster to process— or to pass between layers— based purely on physical layout. GPUs love coalesced, aligned, large-block accesses.
- NCHW (default): channels innermost → channel-heavy ops (matmul, batchnorm) walk memory efficiently.
- NHWC / channels_last: spatial dims innermost → convolution/attention gather favorably;
torch.compile+channels_lastis the modern fast path. - memory_format is metadata on a tensor:
.to(memory_format=torch.channels_last)reshapes layout (copy), kernels choose paths by it. - Coalescing: neighboring threads read neighboring addresses → one 128-byte transaction.
Modern GPUs: increasing batch/linear dims contiguous is king;
permute/transposein hot paths silently triggers full copies.
Viewing the Format & Why It Matters¶
import torch
x = torch.randn(8, 3, 64, 64) # NCHW contiguous
print(x.is_contiguous(memory_format=torch.channels_last)) # False
x_cl = x.to(memory_format=torch.channels_last) # copy into NHWC
print(x_cl.is_contiguous(memory_format=torch.channels_last)) # True
print(x_cl.shape) # same logical shape!
The tensor is logically identical; only the physical order of elements changed.
-
NCHW vs NHWC in Layers¶
import torch, torch.nn as nn, time
def bench_conv(N, C, H, W, fmt, n=20):
m = nn.Conv2d(C, C, 3, padding=1).cuda()
m = m.to(memory_format=fmt)
x = torch.randn(N, C, H, W, device='cuda').to(memory_format=fmt)
for _ in range(3): m(x)
torch.cuda.synchronize()
t0 = time.perf_counter()
for _ in range(n): m(x)
torch.cuda.synchronize()
return (time.perf_counter() - t0) / n * 1e3
cf = torch.contiguous_format
cl = torch.channels_last
print("contiguous: %.2f ms" % bench_conv(16, 64, 64, 64, cf))
print("channels_last: %.2f ms" % bench_conv(16, 64, 64, 64, cl))
# On many GPUs channels_last is faster for convs (memory coalescing).
Results vary by GPU/version— always measure. The direction: convolutions + AMP favor channels_last; pure matmul/attention favors plain contiguous.
When Formats Matter Most¶
| Op | Best format | Why |
|---|---|---|
| Conv2d/3d | channels_last often | coalesced spatial reads |
| BatchNorm | channels_last w/ conv fused | layout preserved end-to-end |
| Linear/Matmul | contiguous | GEMM wants row-major |
| Attention (QKV gather) | contiguous (or fused kernels) | index arithmetic simple |
| AMP (fp16) BMMs | contiguous | alignment |
Format consistency across a model¶
model = model.to(memory_format=torch.channels_last)
# inputs too:
x = x.to(memory_format=torch.channels_last)
# avoid per-layer to() conversions in the hot path
Pitfalls¶
# 1. to() back and forth silently copies
y = x_cl.to(memory_format=torch.contiguous_format) # full copy
# 2. Weights and activations must match format for fusion
# torch.compile handles conversions automatically; eager doesn't.
# 3. Some ops don't support channels_last -> forced fallback copies
# check via
-
torch.compile Makes Most of This Automatic¶
torch.compile(model, mode="max-autotune")
# Inductor picks layouts per kernel; you mostly stop hand-tuning formats.
Prefer
torch.compiledefault path over manual channels_last surgery in eager code. Manual formats still matter for: custom kernels, ONNX/export paths, and memory-constrained inference.
Strategy Checklist¶
- Default: contiguous +
torch.compile(max-autotune) and let Inductor choose. - Conv-heavy eager nets: channels_last for model AND data.
- Never convert format inside a hot loop; convert once at the boundary.
- Profile with
torch.profiler— a "copy_" node next to a layer signals format mismatch. - Export (Ch 07): pin formats in the export inputs.
-
Key Takeaways¶
- Format = physical layout; changing it costs a full copy.
- channels_last helps conv-heavy and some AMP paths; contiguous is king for GEMMs.
torch.compilelargely automates layout choice— don't hand-tune before measuring.- Keep formats consistent end-to-end (weights + activations + inputs).
- Profile for
copy_/tonodes to detect stray conversions.
-
Related Topics¶
- Strides & Storage
- Torch.Compile
- [Profiling](/06-pytorch/04-performance-and-compilation/(04-profiling-benchmarking/)