Skip to content

Bound the weight-gradient temporary in FusedEncoder.backward - #141

Open
alepot55 wants to merge 1 commit into
EleutherAI:mainfrom
alepot55:pr-encoder-chunk
Open

Bound the weight-gradient temporary in FusedEncoder.backward#141
alepot55 wants to merge 1 commit into
EleutherAI:mainfrom
alepot55:pr-encoder-chunk

Conversation

@alepot55

@alepot55 alepot55 commented Aug 23, 2026

Copy link
Copy Markdown

What

FusedEncoder.backward builds grad_values[:, :, None] * input[:, None, :], an [N, k, D] tensor holding every top-k contribution at once, and then folds it into grad_weight with a single index_add_. This consumes it in row-blocks sized to a fixed byte budget instead.

Why

It is the largest single allocation in SAE training. Some sizes, all measured on an A100:

shape temporary
batch 8, ctx 512, d_model 576, k 32 (repo defaults) 288 MiB
N=8192, D=1024, k=32 (what tests/test_encode.py already runs) 1 GiB

It is linear in k, so the --k 192 configuration the README recommends for Llama 3 8B pays six times the first row.

Effect

Peak allocated per rank, SmolLM2-135M, 30 hookpoints, 2x A100-40GB, batch 8:

mode before after
DDP 9.19 GiB 9.04 GiB
--distribute_modules 4.24 GiB 3.86 GiB

Median step time is unchanged within run-to-run variance, which I measured at about 25% on this setup, so I would not read a throughput difference either way from these runs.

Correctness

The arithmetic is unchanged. Only the accumulation order inside index_add_ differs, and that was already unspecified on CUDA. tests/test_encode.py compares the encoder gradient against a naive autograd reference at N=8192, D=1024, k=32 and passes.

Full suite on an A100, this branch against main: same results, with the one pre-existing test_auxk_loss failure on both (that one is #142).

The backward pass builds `grad_values[:, :, None] * input[:, None, :]`, an
`[N, k, D]` tensor holding every top-k contribution at once. It is the largest
single allocation in SAE training: 288 MiB at batch 8, ctx 512, d_model 576,
k 32, and 1 GiB in the shape `tests/test_encode.py` already exercises. It
scales with k, so the `--k 192` configuration the README recommends pays six
times that.

Consume it in row-blocks sized to a fixed byte budget instead. The arithmetic
is unchanged; only the accumulation order inside `index_add_` differs, and that
was already unspecified on CUDA.

Peak allocated per rank on SmolLM2-135M with 30 hookpoints at 2 GPUs:
9.19 -> 9.04 GiB under DDP, 4.24 -> 3.86 GiB under --distribute_modules. Step
time is unchanged within run-to-run variance.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants