Skip to content

convert-h5-to-ggml.py writes n_text_ctx from max_length even when it is present and is not the architecture (follow-up to #2477, may explain #2778) #3986

Description

@hqudsi

models/convert-h5-to-ggml.py on master (line 125, unchanged since before #2477) writes the ggml header's n_text_ctx from hparams["max_length"]:

# lines 87-95 — #2477 and #2840: max_target_positions as a FALLBACK for when max_length is MISSING
if "max_length" not in hparams or hparams["max_length"] is None:
    hparams["max_length"] = hparams.get("max_target_positions", 448)
...
# line 125 — but whenever max_length EXISTS, it is what goes into the header
fout.write(struct.pack("i", hparams["max_length"]))     # n_text_ctx

max_length is a generation default. max_target_positions is the architecture — the row count of model.decoder.embed_positions.weight, the tensor this same script writes a few lines later. Fine-tuning frequently sets max_length to something else, and then the header and the tensor disagree:

whisper_model_load: tensor 'decoder.positional_embedding' has wrong size in model file
whisper_model_load: shape: [512, 448, 1], expected: [512, 1024, 1]
whisper_init_with_params_no_state: failed to load model
error: failed to initialize whisper context

The conversion succeeds and the model will not load.

Why it has never shown up in testing

On OpenAI's own checkpoints max_length == max_target_positions == 448, so reading the wrong field returns the right number. The bug is invisible on every model the script is normally tested against, and appears only on a fine-tune whose training run set a different max_length.

Prior issues — all of them the missing case, which is why this one is still open

Both reach for max_target_positions as a default when max_length is missing, rather than as the source. When max_length is present and wrong for this purpose, neither path fires.

🎯 This may be the cause of #2778 (open)

#2778 reports "it generates the output, however, when trying to load it with whisper.cpp it fails loading" for a fine-tuned non-English checkpoint — the same symptom, from the same population (fine-tunes are where max_length gets set). I cannot confirm it: that issue does not include config.json, the loader's output, or a link to the model.

If @the-reporter-of-2778 is still around, this is a one-command check:

python -c "import json;c=json.load(open('config.json'));print(c.get('max_length'), c.get('max_target_positions'))"

If the two numbers differ, this is the cause.

Minimal reproduction

python - <<'PY'
import json, urllib.request
repo = "tarteel-ai/whisper-base-ar-quran"     # public, Apache-2.0; any fine-tune where the fields differ
c = json.load(urllib.request.urlopen(f"https://huggingface.co/{repo}/resolve/main/config.json"))
print(repo, "max_length =", c.get("max_length"), "| max_target_positions =", c.get("max_target_positions"))
PY
# -> max_length = 1024 | max_target_positions = 448

git clone https://huggingface.co/tarteel-ai/whisper-base-ar-quran
python models/convert-h5-to-ggml.py ./whisper-base-ar-quran/ ./whisper .
./build/bin/whisper-cli -m ggml-model.bin -f samples/jfk.wav      # fails to load

Suggested fix — the small one

n_text_ctx = hparams.get("max_target_positions") or hparams.get("max_length") or 448
fout.write(struct.pack("i", n_text_ctx))

Reverse the precedence left by #2477 and #2840: prefer the architecture field, keep max_length as the fallback. One line, and it keeps #2477's, #2840's and #1520's cases working.

And a larger one, offered as a suggestion rather than a condition

Read the shape of the tensor being written — model.model.decoder.embed_positions.weight.shape[0] — and assert it equals the header value. That makes the header a derived number instead of a declared one, so the class of bug disappears rather than being fixed once. The small fix above is complete on its own; this is only if you would rather the header could never disagree with the tensor again.

Workaround for anyone who finds this first

Convert from a copy of the model directory whose config.json has max_length set to max_target_positions. Do not edit the published checkpoint — the value is correct for what it means; it is only being read as something else.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions