Skip to content

Fix EK-FAC gradient store overflow above ~537M tracked params#317

Open
davidoj wants to merge 2 commits into
mainfrom
fix/ekfac-gradient-itemsize-overflow
Open

Fix EK-FAC gradient store overflow above ~537M tracked params#317
davidoj wants to merge 2 commits into
mainfrom
fix/ekfac-gradient-itemsize-overflow

Conversation

@davidoj

@davidoj davidoj commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Fix EK-FAC gradient store overflow above ~537M tracked params

Problem

bergson ekfac (the EK-FAC influence pipeline) cannot attribute any model with
more than ~537M tracked-linear parameters. The apply-Hessian stage loads a structured memmap which contains unprojected grads so is beyond the structured memmap size limit and aborts with ValueError: integer won't fit into a C int.

So sum(grad_sizes.values()) equals
the full per-example gradient dimension, and at fp32 the record crosses 2**31
bytes once the model has more than 2**31 / 4 = 536,870,912 tracked params.

The exact failing calls are in the apply stage:

  • bergson/hessians/apply_hessian.py:79load_gradients(self.gradient_path)
    (structured) maps the unprojected query gradient → overflow.
  • bergson/hessians/apply_hessian.py:83create_index(...) (structured,
    default) allocates the transformed-gradient output → overflow.

Fix

You can avoid the crash by loading with the flag structured=False.

For oversized stores only, create_index / load_gradients now serve the data
as a flat 2D memmap wrapped in a small field-access view that preserves the
structured interface the rest of the codebase uses. Small models are completely
unaffected — they still get a genuine numpy structured memmap.

  • bergson/data.py — added FlatGradientView: exposes view[module_name]
    (returns the module's (num_grads, size) column block as a live, writable view
    into the memmap), view.dtype.names, len(view), and view.flush().
  • bergson/data.py — added _structured_dtype_fits(); create_index
    (with_structure=True) and load_gradients (structured=True) fall back to
    FlatGradientView when the record would overflow the C-int cap, and are
    otherwise unchanged. The persisted info.json is unchanged (still records the
    structured dtype, grad_sizes, and base_dtype), so existing stores and
    metadata readers keep working.
  • bergson/query/faiss_index.pygradients_loader now routes through
    load_gradients (so the approximate-query path is overflow-safe too), and the
    loader loop skips the structured_to_unstructured copy for an already-flat
    view.

No public API or signature changes. apply_hessian's field-access reads/writes
(mmap[k], grad_buffer[k][:] = ..., len, .flush) work unchanged against
FlatGradientView.

Test

tests/test_data.py (CPU-only, no CUDA, no model):

  • test_large_gradient_store_roundtrips — builds a store whose combined fp32
    record exceeds 2**31 bytes (> 2**31 / 4 float32 elements across two
    modules), writes sentinels at each module's start/end via structured field
    access, and asserts they round-trip through create_index +
    load_gradients(structured=True). It also cross-checks the flat
    (structured=False) view of the same bytes to confirm the byte-identical
    layout. The store is a sparse file (truncate only sets the size), so the
    test touches a handful of pages instead of materializing ~2 GB.
  • test_small_gradient_store_stays_structured — asserts sub-cap stores still
    return a genuine np.memmap structured array with unchanged behavior.

Before this fix the oversized case raises ValueError: integer won't fit into a C int in create_index; after, it round-trips. The previous test_data.py
(which asserted the failure as expected behavior) is replaced accordingly.

Verified in the pinned venv (numpy 2.5.1, torch 2.9.1+cu128):

pytest tests/test_data.py -v        # 2 passed
pytest tests/test_score.py -v       # 8 passed (incl. test_large_gradients_query)

Scope and limitations

  • This raises the storage ceiling for the EK-FAC gradient store past
    ~537M params; it does not change results for models below the old ceiling.
  • EK-FAC influence has a separate, larger ceiling that this fix does not
    address: the query gradient is materialized unprojected in full model
    space
    on the GPU during apply_hessian (and again when reading it back for
    scoring). That is an architectural memory ceiling (order ~7B params on an 80 GB
    GPU, and it also implies a large host/disk footprint), independent of the
    numpy itemsize limit fixed here. Projecting or streaming the unprojected query
    gradient to lift that ceiling is out of scope for this bug fix.
  • FlatGradientView implements only the subset of the structured-array
    interface Bergson actually uses (field access, dtype.names, len, flush,
    and integer/slice row indexing on the underlying flat array). It is not a
    general drop-in for every numpy structured-array operation.

🤖 Generated with Claude Code

https://claude.ai/code/session_016rrSNbhfYPhfuxuBrdJeFG

The EK-FAC influence pipeline (`bergson ekfac`) stores the query gradient
unprojected in full model space as a numpy structured record, one field per
tracked module. numpy keeps a structured dtype's record itemsize in a C int,
so the record cannot exceed 2**31 - 1 bytes; at fp32 this overflows once a
model has more than 2**31 / 4 = ~537M tracked params, raising
"integer won't fit into a C int" in apply_hessian (load_gradients /
create_index) before any work is done. This is hardware-independent.

The structured record layout is byte-identical to a flat 2D
(num_grads, total_grad_dim) memmap, whose shape is intp-sized and therefore
has no such ceiling (the score path already reads the same bytes with
structured=False). For oversized stores only, create_index and load_gradients
now serve the data as a flat 2D memmap wrapped in FlatGradientView, a small
adapter that preserves the structured field-access interface the codebase
uses (view[module], view.dtype.names, len, flush). Sub-cap models are
unaffected and still get a genuine structured memmap. gradients_loader in the
FAISS path is routed through the same overflow-safe loader.

Adds CPU-only regression tests in tests/test_data.py: an oversized store
(> 2**31/4 fp32 elements, allocated sparsely) now round-trips through
create_index + load_gradients(structured=True), and a sub-cap store still
returns a structured np.memmap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01866exUnUq9z5uPTFofcmmj
@luciaquirke

luciaquirke commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

For oversized stores only, create_index / load_gradients now serve the data
as a flat 2D memmap wrapped in a small field-access view that preserves the
structured interface the rest of the codebase uses

Please merge whenever makes sense to you to maximize development speed. Long term it seems like we could maybe just pass structured=False here rather than writing a bunch of new code unless we need the structure for something.

@luciaquirke

Copy link
Copy Markdown
Collaborator

@claude are you still wired up here haha

@claude

claude Bot commented Jul 9, 2026

Copy link
Copy Markdown

Claude finished @luciaquirke's task in 15s —— View job


👋 Yep, still here!

Just confirming I'm wired up and watching this PR. Nothing to act on from your "haha" ping specifically — but happy to help with anything on this PR. A couple of things worth flagging while I'm here:

  • Lucia's earlier suggestion (just passing structured=False instead of adding FlatGradientView) is a reasonable simplification to consider long-term, but I won't implement it unless you explicitly ask — it wasn't a direct instruction to me.
  • Let me know if you'd like me to: review the current diff, run the test suite to re-verify, or implement any specific change (e.g., the structured=False simplification).

Just say the word (@claude ...) and I'll get to work.

@luciaquirke

Copy link
Copy Markdown
Collaborator

@claude I'm happy with this work, can you fix the typecheck so we can merge

@claude

claude Bot commented Jul 9, 2026

Copy link
Copy Markdown

Claude encountered an error after 2m 11s —— View job


Working on it

  • Gather context (PR diff, CLAUDE.md)
  • Run pyright to reproduce typecheck failures
  • Fix typecheck errors
  • Re-run pyright to confirm clean
  • Run test suite to confirm no regressions
  • Commit and push fix

Narrow the three unstructured-layout call sites (load_gradient_dataset,
builder, score) to np.memmap via isinstance asserts, since with_structure/
structured=False never takes the overflow path. Widen write_grads to accept
np.memmap | FlatGradientView, since gradient collection uses with_structure=
True and oversized models legitimately flow a FlatGradientView through it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@luciaquirke
luciaquirke marked this pull request as ready for review July 9, 2026 04:34
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