Repository navigation
fix: run LLM autocast and the activation buffer on the model's device - #722
Merged
chanind merged 1 commit intoAug 5, 2026
Conversation
chanind
approved these changes
Aug 5, 2026
chanind
left a comment
Collaborator
There was a problem hiding this comment.
Great find, thank you for this fix!
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.
Description
ActivationsStore.get_activationsassumes CUDA in two places. The multi-hook sibling in the same class,get_multi_hook_activations, already avoids both.The first is the autocast device, which is hardcoded:
On a non-CUDA device PyTorch does not raise here. It warns and turns autocast off:
So on MPS, CPU and XPU,
autocast_lm=Truesilently runs the model in full precision. Ontiny-stories-1Mthe activations come back bit-identical toautocast_lm=False. I've changed this to_get_input_token_device(self.model).type, which is the same expressionget_multi_hook_activationsalready uses, and the activations then differ by 1.3e-3, which is bfloat16 rounding.The second is the staging buffer, which is allocated with no
device:It lands on the default device no matter where the model ran, so the slice-assignments below it copy the activations off the accelerator, and
get_raw_llm_batchmoves them straight back with.to(self.device). On an A100 withact_store_device="cuda",get_activationsreturns its result oncputoday and oncuda:0with this change. Allocating onself.deviceremoves that round trip and makes the.to(self.device)inget_raw_llm_batcha no-op. It is also what the tests already expect:test_activations_store.py:174and:206assertactivations.device == store.device, which passes onmainonly because they run on CPU. Timing just the buffer step on MPS, a 537 MB batch goes from 68.8 ms to 21.4 ms.The values are unchanged there. With the store on CUDA and again on CPU, autocast off and on, the activations are bit-identical before and after this change, and
autocast_lmstill engages on CUDA either way. I also rantests/training/test_activations_store.pyon that machine unpatched and then patched and compared the results test by test. The same seven were already failing in both runs and nothing new breaks.This is a real change for anyone already setting
autocast_lm=Trueon MPS or CPU. Those runs now get the bfloat16 forward pass the flag promises instead of a silent float32 one, so their activations shift by bfloat16 rounding and their memory use drops. That's the intent, but it isn't a no-op for existing runs. The flag defaults toFalse, so nothing changes for anyone who hasn't opted in.Alternatives I ruled out
Allocating on
layerwise_activations.devicerather thanself.devicelooks equivalent but is worse. Withact_store_device="cpu"and the model on the GPU it builds the buffer in VRAM and then ships all of it to the host anyway. At 537 MB that measured 141 ms against 55 ms on main, and it holds an extra copy in VRAM while it does so, which hurts exactly the people who picked that setting to save memory.Dropping the staging buffer altogether, so this path matches the multi-hook one, would change behavior. The buffer pins the output to
float32, and its fixed shape turns a hook whose width disagrees withd_ininto aRuntimeErrorinstead of a silently wrong result.Type of change
Checklist:
You have tested formatting, typing and tests
make check-cito check format and linting. (you can runmake formatto format code if needed.)