Skip to content

fix(gguf): parse v2 counts/lengths as uint64 (spec-correct) - #365

Open
jamesburton wants to merge 1 commit into
kkokosa:mainfrom
jamesburton:fix/gguf-v2-uint64-parsing
Open

fix(gguf): parse v2 counts/lengths as uint64 (spec-correct)#365
jamesburton wants to merge 1 commit into
kkokosa:mainfrom
jamesburton:fix/gguf-v2-uint64-parsing

Conversation

@jamesburton

Copy link
Copy Markdown

Closes #364

Problem

Every real GGUF v2 file fails to load with a bogus InvalidDataException: Tensor '' data extends beyond file boundary from GgufFile.Open (observed on TheBloke/Llama-2-13B-GGUF, a v2 file).

Root cause

GgufReader read v2 tensor/metadata counts, string lengths, and array lengths as uint32, treating v2 like the (unsupported) v1 format. Per the GGUF spec the uint32 -> uint64 switch happened at v1 -> v2: v2 and v3 are byte-identical on the wire and both use uint64. The buggy pattern in three sites was version == 2 ? reader.ReadUInt32() : reader.ReadUInt64() (and the equivalent if (version == 2) branch in ReadHeader).

Reading 4 bytes where the file wrote 8 misaligns the whole metadata + tensor-info parse, yielding garbage/empty tensor names and offsets — surfacing as the spurious "extends beyond file boundary".

The bug stayed latent because every tested model was v3, and the test fixture GgufTestData itself wrote v2 with uint32 — writer and reader shared the same wrong assumption and agreed with each other while both disagreeing with the spec.

Fix

  • src/DotLLM.Models/Gguf/GgufReader.cs — read uint64 for counts, string lengths, and array lengths across all supported versions (v1 is already rejected by header validation), plus accurate doc comments.
  • tests/DotLLM.Tests.Unit/Models/Gguf/GgufTestData.cs — write spec-correct uint64 for v2 so v2 tests exercise the real wire format.
  • tests/.../GgufReaderTests.cs — add Reads_V2_FullFile_ArrayMetadataThenTensors_Exactly (header -> string-array metadata -> tensor infos, where misalignment compounds).
  • tests/.../GgufFileTests.cs — add Open_V2File_WithArrayMetadataAndTensors_Succeeds (end-to-end, mirrors the Llama-2-13B failure).

Verification

  • dotnet test --filter "FullyQualifiedName~Models.Gguf" -> all 73 GGUF tests pass.
  • Cross-checked against the gguf reference library, which parses the same v2 files with uint64, and confirmed a real Llama-2-13B v2 file loads with the fix.

🤖 Generated with Claude Code

GgufReader treated GGUF v2 as if it used uint32 tensor/metadata counts,
string lengths and array lengths. Per the GGUF spec the uint32->uint64
switch happened at v1->v2: v2 and v3 are identical on the wire and both
use uint64. (v1, the only uint32 form, is rejected by header validation.)

Reading a uint32 (4 bytes) where a real v2 file writes a uint64 (8 bytes)
misaligns the entire metadata + tensor-info parse. On TheBloke's
Llama-2-13B-GGUF (a v2 file) this produced garbage tensor names and
offsets, surfacing as a bogus "Tensor '' data extends beyond file
boundary" in GgufFile.Open. The bug stayed latent because every model we
had tested with was v3, and GgufTestData itself wrote v2 with uint32 — so
the writer and reader shared the same wrong assumption and agreed with
each other while disagreeing with the spec.

Fix: read uint64 for counts, string lengths and array lengths (all
supported versions). Fix GgufTestData to write spec-correct uint64 v2 so
v2 tests actually exercise the real wire format. Add discriminating
regression tests: a full v2 parse (header -> string-array metadata ->
tensor infos, where misalignment compounds) and an end-to-end
GgufFile.Open v2 case mirroring the Llama-2-13B failure. Verified against
the gguf reference library, which parses the same file with uint64.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jamesburton
jamesburton marked this pull request as ready for review June 30, 2026 23:56
@jamesburton

Copy link
Copy Markdown
Author

Polite fortnightly ping 🙂 — status unchanged since opening: the fix parses GGUF v2 counts/string/array lengths as uint64 per the spec (only v1 is uint32), with discriminating v2 regression tests included (the test fixture was co-bugged, which is why this stayed latent). No CI is configured on this branch; the full unit suite passes locally. Happy to rebase or adjust scope if you'd prefer it packaged differently.

@jamesburton

Copy link
Copy Markdown
Author

Fortnightly ping 🙂 — still open, no changes needed on my side. Recap: GGUF v2 counts / string / array lengths are parsed as uint64 per the spec (only v1 is uint32); every v2 file currently fails to parse. Discriminating v2 regression tests are included — the previous fixture was co-bugged with the reader, which is why this stayed latent while all v3 test models passed.

No CI is configured on this branch; the full unit suite passes locally. Happy to rebase, split, or re-scope if that would make it easier to review.

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.

GGUF v2 files fail to load: counts/lengths parsed as uint32 instead of uint64

1 participant