[GH-ISSUE #6787] [Bug] GGUF inspection rejects unpadded vocab-only output from the released converter #4140

Closed
opened 2026-10-31 00:47:06 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 23, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6787

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

The production ModelArtifactService rejects a 105-byte GGUF v3 header with zero tensors and two string metadata entries as BinaryTargetError, public code target_unavailable, category unsupported_target, constraint file_format_rejected, reason GGUF tensor data section begins outside the file. The same header plus 23 zero padding bytes succeeds and returns both entries and zero tensors.

Observed: the source service and real regular-file reader were executed with reduced inert fixtures. Source-derived: the released llama.cpp vocab-only converter emits this unpadded header pattern. The converter itself and a real vocabulary export were not executed.

Reproduction

From the tested checkout with locked dependencies installed, save the following executed probe as $WORK/repro.mts, with WORK an existing disposable directory:

import { writeFile } from 'node:fs/promises';
import { pathToFileURL } from 'node:url';
const [repo,work]=process.argv.slice(2);
const load=(p:string)=>import(pathToFileURL(`${repo}/${p}`).href);
const {ModelArtifactProvider}=await load('src/model/ModelArtifactProvider.ts');
const {ModelArtifactService}=await load('src/application/model/ModelArtifactService.ts');
const bytes=Buffer.from('4747554603000000000000000000000002000000000000000c0000000000000067656e6572616c2e74797065080000000500000000000000766f6361621400000000000000746f6b656e697a65722e67676d6c2e6d6f64656c08000000040000000000000067707432','hex');
const svc=new ModelArtifactService(new ModelArtifactProvider());
try {
 for (const [name,data] of [['vocab',bytes],['control',Buffer.concat([bytes,Buffer.alloc(23)])]] as const) {
  const path=`${work}/${name}.gguf`; await writeFile(path,data);
  const r=await svc.inspect({path,detail:'complete'});
  console.log(JSON.stringify(r.ok?{name,bytes:data.length,ok:true,tensors:r.value.normalized_result.tensors.length}:{name,bytes:data.length,ok:false,reason:r.error.reason}));
 }
} finally {await svc.close();}

Executed from the checkout root:

node --import ./node_modules/tsx/dist/loader.mjs "$WORK/repro.mts" "$PWD" "$WORK"

The shortest probe ran once; an earlier expanded service probe independently observed the same failure and passing control. Both runs exited 0 with empty stderr. Neither input was executed as code.

Expected behavior

Inspect metadata-only/vocab-only GGUF declarations without demanding a nonexistent tensor-data section. The released converter's TextModel.write_vocab writes the header and key/value data and closes the writer. Its key/value write method does not add alignment padding. The released upstream reader seeks over padding only when n_tensors > 0.

There is a contract conflict: the written GGUF layout and REA's existing empty-directory tests demand padding unconditionally, while the released converter and reader permit unpadded zero-tensor files. This report concerns interoperability with that ordinary producer path, rather than claiming the written specification expressly exempts it.

Impact and scope

Unpadded vocab-only exports whose header length is not already an alignment multiple cannot produce a completed REA model report, even though their metadata parses. The two reduced service probes reproduced the bounded refusal. Producer reach is source-derived; real vocabulary files, prevalence, CLI/MCP transports, and inference compatibility were not tested. Adding padding to a copy succeeds but changes the selected artifact bytes and identity.

REA version

Package version 6.7.0; source revision a7180370332350bc0f998fbd643888221759570e. The parent confirmed npm latest as 6.7.0. The source revision was executed; the published compiled package was not executed.

Runtime and platform

Node.js v24.22.1; Linux x86_64; repository-locked tsx loader. No model runtime, GPU, Python model package, Hopper, or Ghidra was used.

Target and artifact context

Generated little-endian GGUF v3, zero tensors, string metadata general.type = vocab and tokenizer.ggml.model = gpt2, default alignment 32. Header-only fixture: 105 bytes; control: the identical header plus 23 zero bytes, 128 bytes total.

Interface

Other

Earliest failure stage

Capture or observation

Relevant output or evidence

Exact shortest-probe stdout:

{"name":"vocab","bytes":105,"ok":false,"reason":"GGUF tensor data section begins outside the file"}
{"name":"control","bytes":128,"ok":true,"tensors":0}

The earlier expanded source-service probe additionally observed the public error fields listed above, and the control's two metadata values with complete header/storage/range coverage.

Evidence provenance: observed REA source-service failure and control; producer behavior is source-derived from the published llama.cpp v0.7.0 revision above. Local verification: reproduced. Reproduction completeness: complete for the reduced fixtures; NOT_ENOUGH_INFO for an actual converter export or public transport execution.

Typed outcome or recovery expected

Return successful metadata Evidence for this zero-tensor producer output; retain the original artifact identity. A tensor-data offset/alignment check must not prevent inspection solely because the vocab-only producer omitted unused tensor-section padding.

Analysis or suspected cause

Source-derived: GgufHeader.ts rounds the end of every tensor directory up to the declared alignment, including an empty directory, then rejects when that rounded location exceeds the file. The current tests deliberately encode unconditional padding and need to be reconciled with the released vocab-only path.

Additional context

The supplied open-issue inventory was searched for GGUF, empty directories, zero tensors, padding, and vocab-only exports. No matching open report was found. Related #6772 rejects hyphenated architecture keys; this fixture uses no architecture key and both metadata keys parse before the later padding refusal. Open and closed GGUF reports were searched before filing. Closed #6636 concerns a wrong offset on an already padded zero-tensor file; this report concerns rejecting the unpadded output of the released vocab-only converter. NOT_ENOUGH_INFO: first affected version and real-export frequency. Prepared with AI assistance.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The actual source service refuses an inert metadata-only GGUF pattern used by the released converter, while the identical padded control succeeds. This is a bounded format-compatibility defect that blocks completed declaration inspection for affected files.
  • Workaround status: unverified
  • Workaround basis: Padding a copy succeeds but changes the selected artifact and its identity. The upstream reader is source-inspected only; no independent workflow preserving and inspecting the original file was executed.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6787 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Native analysis or decompilation ## Observed behavior The production `ModelArtifactService` rejects a 105-byte GGUF v3 header with zero tensors and two string metadata entries as `BinaryTargetError`, public code `target_unavailable`, category `unsupported_target`, constraint `file_format_rejected`, reason `GGUF tensor data section begins outside the file`. The same header plus 23 zero padding bytes succeeds and returns both entries and zero tensors. Observed: the source service and real regular-file reader were executed with reduced inert fixtures. Source-derived: the released llama.cpp vocab-only converter emits this unpadded header pattern. The converter itself and a real vocabulary export were not executed. ## Reproduction From the tested checkout with locked dependencies installed, save the following executed probe as `$WORK/repro.mts`, with `WORK` an existing disposable directory: ```ts import { writeFile } from 'node:fs/promises'; import { pathToFileURL } from 'node:url'; const [repo,work]=process.argv.slice(2); const load=(p:string)=>import(pathToFileURL(`${repo}/${p}`).href); const {ModelArtifactProvider}=await load('src/model/ModelArtifactProvider.ts'); const {ModelArtifactService}=await load('src/application/model/ModelArtifactService.ts'); const bytes=Buffer.from('4747554603000000000000000000000002000000000000000c0000000000000067656e6572616c2e74797065080000000500000000000000766f6361621400000000000000746f6b656e697a65722e67676d6c2e6d6f64656c08000000040000000000000067707432','hex'); const svc=new ModelArtifactService(new ModelArtifactProvider()); try { for (const [name,data] of [['vocab',bytes],['control',Buffer.concat([bytes,Buffer.alloc(23)])]] as const) { const path=`${work}/${name}.gguf`; await writeFile(path,data); const r=await svc.inspect({path,detail:'complete'}); console.log(JSON.stringify(r.ok?{name,bytes:data.length,ok:true,tensors:r.value.normalized_result.tensors.length}:{name,bytes:data.length,ok:false,reason:r.error.reason})); } } finally {await svc.close();} ``` Executed from the checkout root: ```sh node --import ./node_modules/tsx/dist/loader.mjs "$WORK/repro.mts" "$PWD" "$WORK" ``` The shortest probe ran once; an earlier expanded service probe independently observed the same failure and passing control. Both runs exited 0 with empty stderr. Neither input was executed as code. ## Expected behavior Inspect metadata-only/vocab-only GGUF declarations without demanding a nonexistent tensor-data section. The [released converter's `TextModel.write_vocab`](https://github.com/ggml-org/llama.cpp/blob/620d2225cc436ab0b001e3200e590e3b286ef9ab/conversion/base.py#L1721-L1728) writes the header and key/value data and closes the writer. Its [key/value write method](https://github.com/ggml-org/llama.cpp/blob/620d2225cc436ab0b001e3200e590e3b286ef9ab/gguf-py/gguf/gguf_writer.py#L238-L253) does not add alignment padding. The [released upstream reader](https://github.com/ggml-org/llama.cpp/blob/620d2225cc436ab0b001e3200e590e3b286ef9ab/ggml/src/gguf.cpp#L778-L788) seeks over padding only when `n_tensors > 0`. There is a contract conflict: the written GGUF layout and REA's existing empty-directory tests demand padding unconditionally, while the released converter and reader permit unpadded zero-tensor files. This report concerns interoperability with that ordinary producer path, rather than claiming the written specification expressly exempts it. ## Impact and scope Unpadded vocab-only exports whose header length is not already an alignment multiple cannot produce a completed REA model report, even though their metadata parses. The two reduced service probes reproduced the bounded refusal. Producer reach is source-derived; real vocabulary files, prevalence, CLI/MCP transports, and inference compatibility were not tested. Adding padding to a copy succeeds but changes the selected artifact bytes and identity. ## REA version Package version 6.7.0; source revision `a7180370332350bc0f998fbd643888221759570e`. The parent confirmed npm latest as 6.7.0. The source revision was executed; the published compiled package was not executed. ## Runtime and platform Node.js v24.22.1; Linux x86_64; repository-locked tsx loader. No model runtime, GPU, Python model package, Hopper, or Ghidra was used. ## Target and artifact context Generated little-endian GGUF v3, zero tensors, string metadata `general.type = vocab` and `tokenizer.ggml.model = gpt2`, default alignment 32. Header-only fixture: 105 bytes; control: the identical header plus 23 zero bytes, 128 bytes total. ## Interface Other ## Earliest failure stage Capture or observation ## Relevant output or evidence Exact shortest-probe stdout: ```jsonl {"name":"vocab","bytes":105,"ok":false,"reason":"GGUF tensor data section begins outside the file"} {"name":"control","bytes":128,"ok":true,"tensors":0} ``` The earlier expanded source-service probe additionally observed the public error fields listed above, and the control's two metadata values with complete header/storage/range coverage. Evidence provenance: observed REA source-service failure and control; producer behavior is source-derived from the published llama.cpp v0.7.0 revision above. Local verification: reproduced. Reproduction completeness: complete for the reduced fixtures; NOT_ENOUGH_INFO for an actual converter export or public transport execution. ## Typed outcome or recovery expected Return successful metadata Evidence for this zero-tensor producer output; retain the original artifact identity. A tensor-data offset/alignment check must not prevent inspection solely because the vocab-only producer omitted unused tensor-section padding. ## Analysis or suspected cause Source-derived: [GgufHeader.ts](https://github.com/morluto/rea/blob/a7180370332350bc0f998fbd643888221759570e/src/model/GgufHeader.ts#L386-L389) rounds the end of every tensor directory up to the declared alignment, including an empty directory, then rejects when that rounded location exceeds the file. The [current tests](https://github.com/morluto/rea/blob/a7180370332350bc0f998fbd643888221759570e/tests/boundary/model/modelArtifact.test.ts#L289) deliberately encode unconditional padding and need to be reconciled with the released vocab-only path. ## Additional context The supplied open-issue inventory was searched for GGUF, empty directories, zero tensors, padding, and vocab-only exports. No matching open report was found. Related #6772 rejects hyphenated architecture keys; this fixture uses no architecture key and both metadata keys parse before the later padding refusal. Open and closed GGUF reports were searched before filing. Closed #6636 concerns a wrong offset on an already padded zero-tensor file; this report concerns rejecting the unpadded output of the released vocab-only converter. NOT_ENOUGH_INFO: first affected version and real-export frequency. Prepared with AI assistance. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The actual source service refuses an inert metadata-only GGUF pattern used by the released converter, while the identical padded control succeeds. This is a bounded format-compatibility defect that blocks completed declaration inspection for affected files. - Workaround status: unverified - Workaround basis: Padding a copy succeeds but changes the selected artifact and its identity. The upstream reader is source-inspected only; no independent workflow preserving and inspecting the original file was executed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4140
No description provided.