Deep Learning / Representation and acceptance evidence / Lesson 16
Precision, memory, and efficiency
Distinguish mathematical counts, numerical formats, and measurements from an actual runtime.
On this page
Lesson goals
- Explain rounding, range, and loss scaling.
- Build an explicit training-memory estimate.
- Evaluate efficiency changes under a fixed correctness contract.
A numeric format has range and resolution
Floating-point formats encode a sign, exponent, and significand. Exponent width affects the range of representable magnitudes, while significand precision affects spacing near a value. A smaller format can save storage but lose small differences or overflow at magnitudes a wider format represents. Different sixteen-bit formats have different tradeoffs, so bit width alone does not specify behavior. Numerical stability also depends on which operations accumulate in wider precision. Record parameter, activation, reduction, gradient, and optimizer-state formats rather than describing the entire procedure with one vague precision label.
Inspect a representable-update failure
The first fixture packs and unpacks a scalar using Python struct binary16 storage. Adding 0.0001 to one is visible in ordinary Python floating-point arithmetic, but rounding the result to binary16 loses that increment. This illustrates why a method can need wider update accumulation even when some forward values use a smaller format. The program does not execute a half-precision matrix kernel, mixed-precision training, or a GPU operation. Its result establishes a storage-format fact for the stated scalar, not a speed comparison or a claim about any framework automatic-casting policy.
import struct
def half(value):
return struct.unpack("e", struct.pack("e", value))[0]
w, delta = 1.0, 0.0001
print(f"full update={w+delta:.7f}")
print(f"half update={half(w+delta):.7f}")
print("half bytes", struct.calcsize("e"))
full update=1.0001000
half update=1.0000000
half bytes 2
Binary16 rounding loses this small increment near one; this is a storage-format demonstration, not accelerator arithmetic.
Scaling gradients has a strict algebraic boundary
Multiplying a loss by a positive scale multiplies its derivatives by that scale in exact arithmetic. Before an ordinary optimizer step, gradients must be divided back by the scale unless the algorithm intentionally defines another update. Loss scaling can help preserve small gradients in certain limited-range calculations, but it cannot repair invalid inputs or an unstable forward formula. Nonfinite gradients require a defined skip or recovery policy. Clipping a scaled gradient before unscaling changes the effective clipping bound. Adaptive scaling state and whether an update occurred also matter when advancing schedules or resuming training.
Count every named memory category
The second fixture counts weights, gradients, two assumed optimizer arrays, and one saved activation. It deliberately names omissions: actual training can store many activations, temporary workspaces, framework objects, allocator reservations, and extra precision copies. Parameter count alone therefore cannot predict peak memory. Increasing batch size generally grows many activation terms while parameter storage stays fixed. Attention score storage can grow with sequence length squared under a conventional dense implementation. Activation checkpointing trades recomputation for stored intermediates and must preserve stochastic or stateful behavior according to the chosen implementation. These are analytical tradeoffs, not measurements made by this course.
parameters = 1000
weight_bytes = parameters * 4
gradient_bytes = parameters * 4
optimizer_bytes = parameters * 2 * 4
activation_bytes = 8 * 16 * 4
print("weights", weight_bytes)
print("assumed training total", weight_bytes+gradient_bytes+optimizer_bytes+activation_bytes)
print("double batch activations", 2*activation_bytes)
weights 4000
assumed training total 16512
double batch activations 1024
The count assumes float32 weights, gradients, two optimizer arrays, and one saved activation; it omits allocator and other runtime storage.
Measure improvements with the same task boundary
A lower precision, smaller batch, different kernel, or changed architecture should be checked against the required output tolerances and evaluation behavior. Measure peak memory and latency on the actual device using an appropriate synchronized procedure, and include preprocessing and transfer costs if they belong to the user-visible path. Report shapes, batch size, warmup policy, hardware, software, and the relevant distribution of times. This course performs no hardware benchmarking. Its small counts and rounding examples teach how to formulate a defensible experiment while leaving framework runtime profiling to dedicated tracks.
Common errors and fixes
A small update disappears in low-precision storage
- Why it happens
- The increment is smaller than the local representable spacing.
- How to fix it
- Use an appropriate wider accumulation policy and verify its update semantics.
- Prevent it next time
- Test representative parameter magnitudes and increments.
Clipping becomes stronger after enabling loss scaling
- Why it happens
- Scaled gradients were clipped before the intended unscale.
- How to fix it
- Unscale at the documented point before applying the original bound.
- Prevent it next time
- Check the combined precision, clipping, and step policy.
Memory usage greatly exceeds weight size
- Why it happens
- Activations, gradients, optimizer state, and temporary storage were omitted.
- How to fix it
- Build and measure a complete category budget.
- Prevent it next time
- Record assumptions and compare estimated versus observed peaks.
Check your understanding
1. If a loss is multiplied by 128, how should its gradient enter the same ordinary optimizer update?
Show worked solution
Divide the computed scaled gradient by 128 before that update. In exact arithmetic this restores the original derivative. Finite precision and nonfinite checks still need handling. Do not multiply the learning rate blindly without considering clipping, optimizer state, or the scaling policy.
2. Why does halving stored weight bytes not guarantee halving peak training memory?
Show worked solution
Weights are one category among gradients, optimizer arrays, saved activations, workspaces, and runtime overhead. Some remain in wider precision and some scale with batch or sequence length. Estimate each category and measure the actual runtime; a weight-file-size ratio is not a peak-memory measurement.
Key takeaways
- Format range and precision are different properties.
- Scaling, clipping, and update order must remain consistent.
- Analytical counts require runtime measurements before performance claims.