Instructions to use PaddlePaddle/PaddleOCR-VL with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PaddleOCR
How to use PaddlePaddle/PaddleOCR-VL with PaddleOCR:
# See https://www.paddleocr.ai/latest/version3.x/pipeline_usage/PaddleOCR-VL.html to installation from paddleocr import PaddleOCRVL pipeline = PaddleOCRVL(pipeline_version="v1") output = pipeline.predict("path/to/document_image.png") for res in output: res.print() res.save_to_json(save_path="output") res.save_to_markdown(save_path="output") - Notebooks
- Google Colab
- Kaggle
Always use native create_causal_mask; fix FA2 and save_pretrained on v5
Browse files- Drop the cache gate and `_create_causal_mask_fallback`. This file already needs transformers >= 4.54, where `masking_utils.create_causal_mask` exists. The gate pushed every cached call on 5.6-5.16 onto the fallback, which lost the SDPA `is_causal` fast path and the flex BlockMask, and gave FA3/FA4 a 4D mask. The signature differences (`input_embeds` -> `inputs_embeds`, removal of `cache_position`) are now resolved once from `inspect.signature`, and `position_ids` is passed as None because 3D M-RoPE coordinates are not packed-sequence positions.
- Declare `_supports_flash_attn = True` on `PaddleOCRPreTrainedModel`. v5 reads only this name, so `attn_implementation="flash_attention_2"` raised "PaddleOCRVisionModel does not support Flash Attention 2 yet". 4.x accepts both names.
- Use dict-style `_tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}`. v5 calls `.items()` on it, so the list form made `save_pretrained` raise `AttributeError: 'list' object has no attribute 'keys'`. 4.x only iterates it, which yields the same key. `tie_word_embeddings` is false in config.json, so lm_head stays untied.
Same change as PaddlePaddle/PaddleOCR-VL-1.6#15 (commits 91669010 and 0f4ece18); the modeling, configuration and config.json files of this PR are identical to that one before the change.
Tested on a tiny random model with transformers 4.57.6, 5.12.1 and 5.22.1, on CPU fp32 (eager, sdpa) and A100 bf16 (eager, sdpa, flash_attention_2): generation matches the previous PR head, cache == no cache, a left-padded row matches the same row unpadded, packed multi-image vision encoding matches per-image encoding, and save_pretrained + from_pretrained restores the rotary/position buffers, keeps lm_head tying as configured, and reproduces pre-save generation.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- modeling_paddleocr_vl.py +22 -70
|
@@ -30,6 +30,7 @@ from transformers.cache_utils import (
|
|
| 30 |
Cache,
|
| 31 |
DynamicCache,
|
| 32 |
)
|
|
|
|
| 33 |
from transformers.generation import GenerationMixin
|
| 34 |
from transformers.integrations import use_kernel_forward_from_hub
|
| 35 |
from transformers.modeling_attn_mask_utils import AttentionMaskConverter
|
|
@@ -68,24 +69,16 @@ from .configuration_paddleocr_vl import PaddleOCRVisionConfig, PaddleOCRVLConfig
|
|
| 68 |
|
| 69 |
|
| 70 |
# Resolve optional framework interfaces once, outside the forward path.
|
| 71 |
-
try:
|
| 72 |
-
from transformers.masking_utils import create_causal_mask as _hf_create_causal_mask
|
| 73 |
-
except ImportError:
|
| 74 |
-
_hf_create_causal_mask = None
|
| 75 |
-
|
| 76 |
try:
|
| 77 |
from transformers import initialization as _hf_initialization
|
| 78 |
except ImportError:
|
| 79 |
_hf_initialization = None
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
_HF_MASK_INPUT_NAME = next(
|
| 87 |
-
(name for name in ("inputs_embeds", "input_embeds") if name in _HF_MASK_PARAMETERS), None
|
| 88 |
-
)
|
| 89 |
|
| 90 |
|
| 91 |
def _guard_initializer(function):
|
|
@@ -118,60 +111,18 @@ def _create_causal_mask(config, inputs_embeds, attention_mask, past_key_values,
|
|
| 118 |
return additive.masked_fill(~mask, torch.finfo(inputs_embeds.dtype).min)
|
| 119 |
return mask.to(dtype=inputs_embeds.dtype)
|
| 120 |
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
mask_kwargs["cache_position"] = cache_position
|
| 134 |
-
if "position_ids" in _HF_MASK_PARAMETERS:
|
| 135 |
-
# 3D M-RoPE coordinates are not packed text-sequence positions.
|
| 136 |
-
mask_kwargs["position_ids"] = None
|
| 137 |
-
return _hf_create_causal_mask(**mask_kwargs)
|
| 138 |
-
|
| 139 |
-
return _create_causal_mask_fallback(
|
| 140 |
-
config, inputs_embeds, attention_mask, past_key_values, cache_position
|
| 141 |
-
)
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
def _create_causal_mask_fallback(config, inputs_embeds, attention_mask, past_key_values, cache_position):
|
| 145 |
-
# Keep mask semantics local: Transformers renamed inputs and removed
|
| 146 |
-
# cache_position across releases. M-RoPE coordinates are handled separately.
|
| 147 |
-
if attention_mask is not None and attention_mask.ndim == 4:
|
| 148 |
-
mask = attention_mask.to(device=inputs_embeds.device)
|
| 149 |
-
if mask.dtype == torch.bool:
|
| 150 |
-
additive = torch.zeros(mask.shape, device=mask.device, dtype=inputs_embeds.dtype)
|
| 151 |
-
return additive.masked_fill(~mask, torch.finfo(inputs_embeds.dtype).min)
|
| 152 |
-
return mask.to(dtype=inputs_embeds.dtype)
|
| 153 |
-
if config._attn_implementation == "flash_attention_2":
|
| 154 |
-
return attention_mask if attention_mask is not None and (attention_mask == 0).any() else None
|
| 155 |
-
batch_size, query_length = inputs_embeds.shape[:2]
|
| 156 |
-
past_length = past_key_values.get_seq_length() if past_key_values is not None else 0
|
| 157 |
-
key_length = past_length + query_length
|
| 158 |
-
if attention_mask is not None:
|
| 159 |
-
key_length = max(key_length, attention_mask.shape[-1])
|
| 160 |
-
if past_key_values is not None and getattr(past_key_values, "is_compileable", False):
|
| 161 |
-
maximum = past_key_values.get_max_cache_shape()
|
| 162 |
-
if isinstance(maximum, int):
|
| 163 |
-
key_length = max(key_length, maximum)
|
| 164 |
-
query_positions = cache_position[-query_length:].to(inputs_embeds.device)
|
| 165 |
-
key_positions = torch.arange(key_length, device=inputs_embeds.device)
|
| 166 |
-
allowed = key_positions[None, :] <= query_positions[:, None]
|
| 167 |
-
allowed = allowed[None, None, :, :].expand(batch_size, 1, query_length, key_length)
|
| 168 |
-
if attention_mask is not None:
|
| 169 |
-
padding = attention_mask[:, :key_length].to(device=inputs_embeds.device, dtype=torch.bool)
|
| 170 |
-
if padding.shape[-1] < key_length:
|
| 171 |
-
padding = F.pad(padding, (0, key_length - padding.shape[-1]), value=False)
|
| 172 |
-
allowed = allowed & padding[:, None, None, :]
|
| 173 |
-
mask = torch.zeros(allowed.shape, dtype=inputs_embeds.dtype, device=inputs_embeds.device)
|
| 174 |
-
return mask.masked_fill(~allowed, torch.finfo(inputs_embeds.dtype).min)
|
| 175 |
|
| 176 |
|
| 177 |
def _init_derived_buffers(module):
|
|
@@ -779,7 +730,7 @@ class Ernie4_5Model(Ernie4_5PreTrainedModel):
|
|
| 779 |
|
| 780 |
|
| 781 |
class Ernie4_5ForCausalLM(Ernie4_5PreTrainedModel, GenerationMixin):
|
| 782 |
-
_tied_weights_keys =
|
| 783 |
_tp_plan = {"lm_head": "colwise_rep"}
|
| 784 |
_pp_plan = {"lm_head": (["hidden_states"], ["logits"])}
|
| 785 |
|
|
@@ -1429,7 +1380,8 @@ class PaddleOCRPreTrainedModel(PreTrainedModel):
|
|
| 1429 |
"PaddleOCRVisionEmbeddings",
|
| 1430 |
"PaddleOCRMultiheadAttentionPoolingHead",
|
| 1431 |
]
|
| 1432 |
-
|
|
|
|
| 1433 |
_supports_sdpa = True
|
| 1434 |
|
| 1435 |
def _init_weights(self, module):
|
|
@@ -2033,7 +1985,7 @@ class PaddleOCRVLCausalLMOutputWithPast(ModelOutput):
|
|
| 2033 |
|
| 2034 |
|
| 2035 |
class PaddleOCRVLForConditionalGeneration(Ernie4_5PreTrainedModel, GenerationMixin):
|
| 2036 |
-
_tied_weights_keys =
|
| 2037 |
config_class = PaddleOCRVLConfig
|
| 2038 |
_no_split_modules = ["Ernie4_5_DecoderLayer", "PaddleOCREncoderLayer"]
|
| 2039 |
|
|
|
|
| 30 |
Cache,
|
| 31 |
DynamicCache,
|
| 32 |
)
|
| 33 |
+
from transformers.masking_utils import create_causal_mask as _hf_create_causal_mask
|
| 34 |
from transformers.generation import GenerationMixin
|
| 35 |
from transformers.integrations import use_kernel_forward_from_hub
|
| 36 |
from transformers.modeling_attn_mask_utils import AttentionMaskConverter
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
# Resolve optional framework interfaces once, outside the forward path.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
try:
|
| 73 |
from transformers import initialization as _hf_initialization
|
| 74 |
except ImportError:
|
| 75 |
_hf_initialization = None
|
| 76 |
|
| 77 |
+
# create_causal_mask exists in every release this file can import on (>= 4.54),
|
| 78 |
+
# but its signature changed: `input_embeds` -> `inputs_embeds`, and
|
| 79 |
+
# `cache_position` was later removed in favor of reading offsets from the cache.
|
| 80 |
+
_HF_MASK_PARAMETERS = frozenset(inspect.signature(_hf_create_causal_mask).parameters)
|
| 81 |
+
_HF_MASK_INPUT_NAME = "inputs_embeds" if "inputs_embeds" in _HF_MASK_PARAMETERS else "input_embeds"
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
|
| 84 |
def _guard_initializer(function):
|
|
|
|
| 111 |
return additive.masked_fill(~mask, torch.finfo(inputs_embeds.dtype).min)
|
| 112 |
return mask.to(dtype=inputs_embeds.dtype)
|
| 113 |
|
| 114 |
+
mask_kwargs = {
|
| 115 |
+
"config": config,
|
| 116 |
+
_HF_MASK_INPUT_NAME: inputs_embeds,
|
| 117 |
+
"attention_mask": attention_mask,
|
| 118 |
+
"past_key_values": past_key_values,
|
| 119 |
+
}
|
| 120 |
+
if "cache_position" in _HF_MASK_PARAMETERS:
|
| 121 |
+
mask_kwargs["cache_position"] = cache_position
|
| 122 |
+
if "position_ids" in _HF_MASK_PARAMETERS:
|
| 123 |
+
# 3D M-RoPE coordinates are not packed text-sequence positions.
|
| 124 |
+
mask_kwargs["position_ids"] = None
|
| 125 |
+
return _hf_create_causal_mask(**mask_kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
|
| 128 |
def _init_derived_buffers(module):
|
|
|
|
| 730 |
|
| 731 |
|
| 732 |
class Ernie4_5ForCausalLM(Ernie4_5PreTrainedModel, GenerationMixin):
|
| 733 |
+
_tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
|
| 734 |
_tp_plan = {"lm_head": "colwise_rep"}
|
| 735 |
_pp_plan = {"lm_head": (["hidden_states"], ["logits"])}
|
| 736 |
|
|
|
|
| 1380 |
"PaddleOCRVisionEmbeddings",
|
| 1381 |
"PaddleOCRMultiheadAttentionPoolingHead",
|
| 1382 |
]
|
| 1383 |
+
# v5 only reads `_supports_flash_attn`; 4.x accepts both names.
|
| 1384 |
+
_supports_flash_attn = True
|
| 1385 |
_supports_sdpa = True
|
| 1386 |
|
| 1387 |
def _init_weights(self, module):
|
|
|
|
| 1985 |
|
| 1986 |
|
| 1987 |
class PaddleOCRVLForConditionalGeneration(Ernie4_5PreTrainedModel, GenerationMixin):
|
| 1988 |
+
_tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
|
| 1989 |
config_class = PaddleOCRVLConfig
|
| 1990 |
_no_split_modules = ["Ernie4_5_DecoderLayer", "PaddleOCREncoderLayer"]
|
| 1991 |
|