Add an auto mode for inference dtype - #10
Open
wrignj08 wants to merge 1 commit into
Open
Conversation
inference_dtype defaults to float32, and on hardware with a reduced-precision path that leaves throughput on the table - a Perth Sentinel-2 tile ran model inference in 27.5s at float32 against 21.1s at bfloat16, identical output to six decimal places. But reduced precision is not always faster: pass "auto" and the device is measured rather than assumed. A capability check cannot answer this. A device will accept bfloat16 tensors and run every kernel on them while emulating the arithmetic in software, which is far slower than the float32 it was meant to beat - on this machine's CPU an emulated convolution is ~100x slower. So the signal has to be a timed comparison on the device that will run the model. The measurement decides one thing only: whether to drop precision at all. Which dtype to drop to is decided by preference, because a single convolution turned out not to predict how a whole model ranks two dtypes sharing one hardware path - the probe put float16 ahead of bfloat16 here (0.6 ms against 1.0-2.0 ms) while the real model ranked them the other way (21.1s against 23.3s), and bfloat16's probe figure moved by 2x between runs. Letting it cast that vote returned float16, bfloat16 and float32 on alternate runs of the same machine. bfloat16 now wins on its numerics, keeping float32's exponent range where float16 overflows to inf, and float16 is used only where bfloat16 will not run. Two details the timing needs to get right: - Each measurement repeats the convolution until it has run for 50 ms and then divides, rather than timing one call. A single convolution is a few milliseconds on a GPU, where scheduling jitter is a large fraction of the total. Repeating is much cheaper than enlarging the tensor, which grows memory quadratically. - Candidates are screened with one small call first, and anything already slower than float32 is dropped rather than measured properly. Sizing the real probe from float32 and then running an emulated candidate at that size turned a sub-second selection into 212 s on CPU. A candidate must beat float32 by 10% to be used at all; below that the difference is noise and float32 is what the models were trained in. The result is cached per device, so a batch of scenes measures once rather than once per scene. Selection costs ~130 ms on MPS and ~380 ms on CPU. Explicit dtypes are passed through untouched and the default is unchanged, so this only takes effect when asked for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
inference_dtypedefaults totorch.float32, and on hardware with a reduced-precision path that leaves throughput on the table — a Perth Sentinel-2 tile ran model inference in 27.5s at float32 against 21.1s at bfloat16, identical output to six decimal places. The examples passinference_dtype="bf16"explicitly; anyone copying the README does not.Pass
inference_dtype="auto"and the device is measured rather than assumed.The default is unchanged, so this changes nothing until it is asked for.
Why measuring, not a capability check
A device will accept
bfloat16tensors and run every kernel on them while emulating the arithmetic in software — far slower than the float32 it was meant to beat. On this machine's CPU an emulated convolution is ~100× slower. A capability flag is not evidence of a speedup.What the measurement decides, and what it doesn't
Whether to drop precision at all is decided by measurement. That signal is large and consistent: ~100× on an emulating CPU, 1.5–5× on MPS.
Which dtype to drop to is decided by preference, not measurement. A single convolution turned out not to predict how a whole model ranks two dtypes sharing one hardware path — the probe put float16 ahead of bfloat16 here (0.6 ms against 1.0–2.0 ms) while the real model ranked them the other way (21.1s against 23.3s), and bfloat16's probe figure moved by 2× between runs. Letting it cast that vote returned float16, bfloat16 and float32 on alternate runs of the same machine. bfloat16 now wins on numerics — it keeps float32's exponent range where float16 overflows to inf — and float16 is used only where bfloat16 will not run.
Two details the timing has to get right
A candidate must beat float32 by 10% to be used at all; below that the difference is noise and float32 is what the models were trained in. The result is cached per device, so a batch of scenes measures once. Selection costs ~130 ms on MPS, ~380 ms on CPU.
Verification
🤖 Generated with Claude Code