Skip to content

fix(loading/paddleocr-vl): remap real language_model.* / visual.* checkpoint keys - #607

Merged
inureyes merged 2 commits into
mainfrom
fix/issue-526-paddleocr-remap
Jul 2, 2026
Merged

fix(loading/paddleocr-vl): remap real language_model.* / visual.* checkpoint keys#607
inureyes merged 2 commits into
mainfrom
fix/issue-526-paddleocr-remap

Conversation

@inureyes

@inureyes inureyes commented Jul 2, 2026

Copy link
Copy Markdown
Member

Follow-up hardening for #526: the merged PaddleOCR-VL port cannot load its real checkpoint. mlxcel generate -m /home/inureyes/models/paddleocr-vl-bfloat16 ... fails with Failed to load PaddleOCR-VL text model: Weight not found: model.embed_tokens.weight.

Root cause

The published checkpoint (PaddleOCRVLForConditionalGeneration) has exactly two top-level weight prefixes, language_model.* and visual.*:

  • text: language_model.model.embed_tokens.weight, language_model.model.layers.{i}.*, language_model.model.norm.weight, language_model.lm_head.weight
  • vision: visual.embeddings.*, visual.layers.{i}.* (attention already fused as self_attn.qkv), visual.post_layernorm.*, visual.projector.*

The loader's remap_key was written for the reference Model.sanitize layout (visual.vision_model.*, split q_proj/k_proj/v_proj, top-level mlp_AR, bare model.* / lm_head.*). None of those patterns appear in the real checkpoint (verified read-only: zero keys match them), so the text keys passed through unstripped and the ERNIE-4.5 backbone could not find model.embed_tokens.weight.

Fix

Strip the language_model. wrapper in remap_key so language_model.model.* becomes model.* and language_model.lm_head.* becomes lm_head.*. The vision tower is already sanitized and qkv-fused under visual.*, matching exactly what PaddleOcrVisionEncoder::from_weights(prefix = "visual") and PaddleOcrProjector::from_weights(prefix = "visual.projector") consume, so it passes through unchanged. The reference-layout branches (visual.vision_model.* to visual.*, split-qkv fusion, mlp_AR to visual.projector) are preserved for split exports.

What changed

  • src/loading/vlm_paddleocr.rs: remap_key strips the language_model. prefix; the remap_paddleocr_weights doc comment now describes both the published and reference layouts.
  • src/loading/vlm_paddleocr_tests.rs (new): feeds the real checkpoint key set (18 text layers, 27 vision layers, as read read-only from model.safetensors.index.json) through the remap and asserts the produced keys match exactly what the text model, vision encoder, and connector request; asserts no language_model. wrapper survives and that the remap is lossless; plus focused remap_key cases for the wrapper strip, sanitized-vision pass-through, and the legacy reference layout.

Test plan

  • cargo test --lib paddleocr (5 passed)
  • cargo clippy --lib --tests -- -D warnings (clean)
  • cargo check --lib --tests (clean)
  • cargo fmt --check (clean)

Checkpoint keys were inspected read-only from model.safetensors.index.json; the on-GPU real-model load is re-validated by the orchestrator.

The published PaddleOCR-VL checkpoint (PaddleOCRVLForConditionalGeneration) wraps the ERNIE-4.5 text backbone under `language_model` (`language_model.model.*`, `language_model.lm_head.*`), so the loader's remap never produced the `model.*` / `lm_head.*` keys the text model requests. Loading the real checkpoint failed with `Failed to load PaddleOCR-VL text model: Weight not found: model.embed_tokens.weight`.

Strip the `language_model.` wrapper in `remap_key` so the ERNIE-4.5 backbone sees `model.*` / `lm_head.*`. The published vision tower is already sanitized and qkv-fused under `visual.*` (embeddings, layers, post_layernorm, projector), which the encoder and connector already consume as pass-through, so no vision-side change is needed. The existing reference `Model.sanitize` handling (`visual.vision_model.*` to `visual.*`, split `q_proj`/`k_proj`/`v_proj` to fused `qkv`, `mlp_AR` to `visual.projector`) is preserved for split exports.

Add `vlm_paddleocr_tests.rs`: feed the real checkpoint key set (read read-only from `model.safetensors.index.json`) through the remap and assert the produced keys match exactly what the text model, vision encoder, and connector request, that no `language_model.` wrapper survives, and that the remap is lossless. Also cover `remap_key` for the wrapper strip, sanitized-vision pass-through, and the legacy reference layout.
@inureyes inureyes added type:bug Bug fixes, error corrections, or issue resolutions area:models Model architectures, weights, loading, metadata status:review Under review labels Jul 2, 2026
@inureyes
inureyes merged commit 7fd4e1f into main Jul 2, 2026
@inureyes
inureyes deleted the fix/issue-526-paddleocr-remap branch July 2, 2026 13:49
@inureyes inureyes self-assigned this Aug 31, 2026
@inureyes inureyes added status:done Completed and removed status:review Under review labels Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:models Model architectures, weights, loading, metadata status:done Completed type:bug Bug fixes, error corrections, or issue resolutions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant