After counting bytes and FLOPs, systems work asks how expensive those quantities are in time.
One timing is weak evidence. Repeated measurements are stronger. FLOPs divided by bytes gives arithmetic intensity. Bytes divided by bandwidth gives an estimated transfer time.
- explain why repeated measurements need a median
- compute arithmetic intensity from FLOPs and bytes
- compare the same byte movement through two memory tiers
- distinguish elapsed time from byte movement and bandwidth
- read memory hierarchy as a typed map, not hardware folklore
A timing trace should name:
what stage was measured
how long it took
how many FLOPs it represents
how many bytes moved
Arithmetic intensity asks:
How much arithmetic work happened per byte moved?
The example:
1024 FLOPs / 2240 bytes = 0.4571 FLOPs/byte
Memory hierarchy asks a different question:
If the same bytes move through a faster or slower tier, how does time change?
The same 16384 bytes can cost 3 ns through a high-bandwidth local path and
512 ns through a slower host-memory path.
Median timing:
StageMeasurements -> median elapsed time
Arithmetic intensity:
ArithmeticIntensity = Flops / Bytes
Transfer estimate:
ElapsedNanos = Bytes / BytesPerSecond
The composition is:
StageMeasurement -> ArithmeticIntensity
MemoryTransfer -> ElapsedNanos
use rust_ml_systems::{
Bytes, BytesPerSecond, ColumnCount, ElapsedNanos, ElementSize, MatrixVectorShape,
MemoryLevel, MemoryTransfer, RowCount, StageMeasurement, StageName,
};
fn main() -> Result<(), rust_ml_systems::Error> {
let shape = MatrixVectorShape::new(RowCount::try_from(16)?, ColumnCount::try_from(32)?);
let measurement = StageMeasurement::new(
StageName::try_from("matvec")?,
ElapsedNanos::try_from(90_000_u128)?,
shape.multiply_add_flops()?,
shape.bytes_moved(ElementSize::float32())?,
);
let transfer = MemoryTransfer::new(
MemoryLevel::HostMemory,
Bytes::try_from(16_384_u64)?,
BytesPerSecond::try_from(32_000_000_000_u128)?,
);
println!("stage = {}", measurement.name());
println!("intensity = {}", measurement.arithmetic_intensity()?);
println!(
"{} transfer -> {}",
transfer.level(),
transfer.estimated_elapsed()?
);
Ok(())
}The division operators are typed. Flops / Bytes produces
ArithmeticIntensity. Bytes / BytesPerSecond produces ElapsedNanos.
Optimization is not a magic label. It should point to a changed resource trace.
If a stage is bandwidth-heavy, reducing bytes moved may matter more than reducing arithmetic. If a stage is compute-heavy, reducing FLOPs or improving the compute schedule may matter more. Typed systems evidence makes those claims inspectable.
- Object/newtype:
StageMeasurement,StageMeasurements,ElapsedNanos,ArithmeticIntensity,BytesPerSecond,MemoryLevel, andMemoryTransfer. - Invariant: elapsed time, byte movement, bandwidth, and FLOPs-per-byte stay separate units.
- Map: repeated measurements -> median elapsed time, FLOPs/bytes -> intensity, and bytes/bandwidth -> transfer time.
- Runnable proof:
cargo run --manifest-path code/Cargo.toml -p rust_ml_systems --example 04_arithmetic_intensity. - Failure signal: you report speed without saying whether timing, FLOPs, bytes moved, or bandwidth changed.
- Why is a median stronger than a single timing?
- What does
0.4571 FLOPs/bytesay about the example stage? - Which map produces
ElapsedNanosfromBytesandBytesPerSecond? - Why can the same byte count have different transfer times?