Repository navigation
Description
Summary
Fine-tuned PP-DocLayoutV3 weights converted via Global.mode=pdparams2safetensors load successfully in Hugging Face / PaddleX engine="transformers", but produce no detections (all scores ≈ 0.05). The same checkpoint works correctly with paddle static inference (best_model/inference). The official hub model PaddlePaddle/PP-DocLayoutV3_safetensors also works under HF.
This points to a pdparams → safetensors Linear weight layout bug (reshape vs transpose / incomplete transpose), not a training or post-process issue.
Environment
- Model:
PP-DocLayoutV3(fine-tuned, n classes) - Conversion:
python main.py -c paddlex/configs/modules/layout_analysis/PP-DocLayoutV3.yaml -o Global.mode=pdparams2safetensors ... - Inference engines compared:
- ✅ PaddleX paddle static (
model_dir=.../best_model/inference) - ✅ HF hub
PaddlePaddle/PP-DocLayoutV3_safetensors - ❌ Converted local
model.safetensors+engine="transformers"/AutoModelForObjectDetection.from_pretrained
- ✅ PaddleX paddle static (
Repro
1) Convert
python main.py -c paddlex/configs/modules/layout_analysis/PP-DocLayoutV3.yaml \
-o Global.mode=pdparams2safetensors \
-o Pdparams2safetensors.input_path=./output/best_model/best_model.pdparams \
-o Pdparams2safetensors.output_dir=./output/safetensors/(Also set config.json / inference.yml label_list / id2label / num_labels to match the fine-tuned class count — exporter currently writes the default 25-class list.)
2) Infer with transformers engine
from paddlex import create_model
model = create_model(
model_name="PP-DocLayoutV3",
model_dir="./output/safetensors",
engine="transformers",
)
for res in model.predict("page.png", threshold=0.25):
res.print() # boxes: []Or via Hugging Face:
from transformers import AutoImageProcessor, AutoModelForObjectDetection
from PIL import Image
model = AutoModelForObjectDetection.from_pretrained("./output/safetensors")
processor = AutoImageProcessor.from_pretrained("./output/safetensors")
image = Image.open("page.png").convert("RGB")
inputs = processor(images=image, return_tensors="pt")
outputs = model(**inputs)
results = processor.post_process_object_detection(
outputs, threshold=0.25, target_sizes=[image.size[::-1]]
)
print(len(results[0]["scores"])) # 03) Control: paddle static on same page
model = create_model(
model_name="PP-DocLayoutV3",
model_dir="./output/best_model/inference",
)
# → real boxes with scores 0.3–0.97Observed symptoms
| Source | Logits max (approx) | Max sigmoid score | Boxes @ 0.25 |
|---|---|---|---|
Hub PP-DocLayoutV3_safetensors |
~+1.7 | ~0.84 | many |
| Converted fine-tuned safetensors | ~-3.0 | ~0.05 | 0 |
Paddle static best_model/inference |
n/a | high | many |
Loading reports 0 missing / unexpected keys; shapes match. Failures are silent.
Root-cause evidence (weight layout)
Compared best_model.pdparams vs converted model.safetensors:
-
transformer.score_head.weight[256, 7]→model.enc_score_head.weight[7, 256]- File matches
np.reshape(7, 256), notnp.transpose. - Correct HF
nn.Linearlayout should be transpose ([in, out]→[out, in]). - Current
WeightConverter._preprocess_tensorsintends to transpose (_should_transpose), but the written file for this head matched reshape.
- File matches
-
Attention
out_proj.weight(square[256, 256])- File matches paddle layout without transpose, while
_should_transpose(...out_proj.weight)isTrue— inconsistent with intended HF layout.
- File matches paddle layout without transpose, while
-
Fused
in_proj→q/k/vsplit- Reconstructing via transpose-then-split does not match tensors stored in the converted safetensors (large maxdiff).
decoder.class_embed being absent from the file is expected (HF _tied_weights_keys: decoder.class_embed ↔ enc_score_head). That is not the root cause.
Related code
paddlex/modules/base/weight_converter.py—WeightConverter,_preprocess_tensors,_should_transposepaddlex/modules/base/utils/pdparams2safetensors/mapping.py—RTDETR_MAPPING(registered forPP-DocLayoutV3)paddlex/modules/base/utils/pdparams2safetensors/model_config.py— default 25-classid2labelwritten into exportconfig.json
Expected
- Converted
model.safetensorsshould be numerically equivalent (after documented paddle→HF transforms) to the source.pdparams, and produce comparable detections undertransformers/ HF as paddle static. - Export should either:
- derive
num_labels/id2label/inference.ymllabel_listfrom the training checkpoint / dataset config, or - document clearly that users must rewrite the default 25-class metadata for fine-tunes.
- derive
- Prefer failing loud (checksum / shape / smoke-test forward) over silent empty predictions when Linear layout is wrong.
Impact
Fine-tuned PP-DocLayoutV3 cannot be used via HF / engine="transformers" after pdparams2safetensors without a manual weight fix or sticking to paddle static export (inference dir).
Workaround
Use paddle static inference weights (.../best_model/inference) for production until conversion is fixed. Official hub safetensors remain OK for the pretrained 25-class model.
Activity