Bvicii commited on
Commit
ff17647
·
verified ·
1 Parent(s): 2e71fa8

Reuse native mask backends and unify protected initialization

Browse files
modeling_paddleocr_vl.py CHANGED
@@ -16,6 +16,7 @@ import inspect
16
  import math
17
  import warnings
18
  from dataclasses import dataclass
 
19
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
20
 
21
  import numpy as np
@@ -66,7 +67,81 @@ else:
66
  from .configuration_paddleocr_vl import PaddleOCRVisionConfig, PaddleOCRVLConfig
67
 
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  def _create_causal_mask(config, inputs_embeds, attention_mask, past_key_values, cache_position):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  # Keep mask semantics local: Transformers renamed inputs and removed
71
  # cache_position across releases. M-RoPE coordinates are handled separately.
72
  if attention_mask is not None and attention_mask.ndim == 4:
@@ -106,7 +181,7 @@ def _init_derived_buffers(module):
106
  inv_freq, module.attention_scaling = module.rope_init_fn(
107
  module.config, device=module.inv_freq.device
108
  )
109
- module.inv_freq.copy_(inv_freq)
110
  module.original_inv_freq = module.inv_freq
111
  elif isinstance(module, SigLIPRotaryEmbedding):
112
  inv_freq = 1.0 / (
@@ -115,9 +190,9 @@ def _init_derived_buffers(module):
115
  / module.dim
116
  )
117
  )
118
- module.inv_freq.copy_(inv_freq)
119
  elif isinstance(module, PaddleOCRVisionEmbeddings):
120
- module.position_ids.copy_(
121
  torch.arange(module.num_positions, device=module.position_ids.device).expand(1, -1)
122
  )
123
 
@@ -781,9 +856,8 @@ class Ernie4_5ForCausalLM(Ernie4_5PreTrainedModel, GenerationMixin):
781
  )
782
 
783
 
 
784
  def _trunc_normal_(tensor, mean, std, a, b):
785
- if getattr(tensor, "_is_hf_initialized", False):
786
- return tensor
787
  # Cut & paste from PyTorch official master until it's in a few official releases - RW
788
  # Method based on https://people.sc.fsu.edu/~jburkardt/presentations/truncated_normal.pdf
789
  def norm_cdf(x):
@@ -819,6 +893,7 @@ def _trunc_normal_(tensor, mean, std, a, b):
819
  tensor.clamp_(min=a, max=b)
820
 
821
 
 
822
  def trunc_normal_tf_(
823
  tensor: torch.Tensor,
824
  mean: float = 0.0,
@@ -844,16 +919,13 @@ def trunc_normal_tf_(
844
  a: the minimum cutoff value
845
  b: the maximum cutoff value
846
  """
847
- if getattr(tensor, "_is_hf_initialized", False):
848
- return tensor
849
  with torch.no_grad():
850
  _trunc_normal_(tensor, 0, 1.0, a, b)
851
  tensor.mul_(std).add_(mean)
852
 
853
 
 
854
  def variance_scaling_(tensor, scale=1.0, mode="fan_in", distribution="normal"):
855
- if getattr(tensor, "_is_hf_initialized", False):
856
- return tensor
857
  fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor)
858
  if mode == "fan_in":
859
  denom = fan_in
@@ -1369,34 +1441,34 @@ class PaddleOCRPreTrainedModel(PreTrainedModel):
1369
  if isinstance(self.config, PaddleOCRVLConfig)
1370
  else self.config.hidden_size
1371
  )
1372
- nn.init.normal_(module.position_embedding.weight, std=1 / np.sqrt(width))
1373
  elif isinstance(module, nn.Embedding):
1374
  default_flax_embed_init(module.weight)
1375
  elif isinstance(module, PaddleOCRAttention):
1376
- nn.init.xavier_uniform_(module.q_proj.weight)
1377
- nn.init.xavier_uniform_(module.k_proj.weight)
1378
- nn.init.xavier_uniform_(module.v_proj.weight)
1379
- nn.init.xavier_uniform_(module.out_proj.weight)
1380
- nn.init.zeros_(module.q_proj.bias)
1381
- nn.init.zeros_(module.k_proj.bias)
1382
- nn.init.zeros_(module.v_proj.bias)
1383
- nn.init.zeros_(module.out_proj.bias)
1384
  elif isinstance(module, PaddleOCRMLP):
1385
- nn.init.xavier_uniform_(module.fc1.weight)
1386
- nn.init.xavier_uniform_(module.fc2.weight)
1387
- nn.init.normal_(module.fc1.bias, std=1e-6)
1388
- nn.init.normal_(module.fc2.bias, std=1e-6)
1389
  elif isinstance(module, PaddleOCRMultiheadAttentionPoolingHead):
1390
- nn.init.xavier_uniform_(module.probe)
1391
- nn.init.xavier_uniform_(module.attention.in_proj_weight)
1392
- nn.init.zeros_(module.attention.in_proj_bias)
1393
  elif isinstance(module, (nn.Linear, nn.Conv2d)):
1394
  lecun_normal_(module.weight)
1395
  if module.bias is not None:
1396
- nn.init.zeros_(module.bias)
1397
  elif isinstance(module, nn.LayerNorm):
1398
- nn.init.zeros_(module.bias)
1399
- nn.init.ones_(module.weight)
1400
 
1401
 
1402
  # Copied from transformers.models.altclip.modeling_altclip.AltCLIPEncoder with AltCLIP->PaddleOCR
 
16
  import math
17
  import warnings
18
  from dataclasses import dataclass
19
+ from functools import wraps
20
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
21
 
22
  import numpy as np
 
67
  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
+ try:
82
+ _HF_MASK_PARAMETERS = frozenset(inspect.signature(_hf_create_causal_mask).parameters)
83
+ except (TypeError, ValueError):
84
+ _HF_MASK_PARAMETERS = frozenset()
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):
92
+ @wraps(function)
93
+ def initialize(tensor, *args, **kwargs):
94
+ if getattr(tensor, "_is_hf_initialized", False):
95
+ return tensor
96
+ with torch.no_grad():
97
+ return function(tensor, *args, **kwargs)
98
+ return initialize
99
+
100
+
101
+ @_guard_initializer
102
+ def _init_tensor(tensor, operation, *args, **kwargs):
103
+ # Standard primitives use the framework's protected implementation when
104
+ # present. Keep model-specific TF/LeCun sampling formulas below unchanged.
105
+ function = getattr(_hf_initialization, operation, None)
106
+ if function is not None:
107
+ return function(tensor, *args, **kwargs)
108
+ if operation == "copy_":
109
+ return tensor.copy_(*args, **kwargs)
110
+ return getattr(nn.init, operation)(tensor, *args, **kwargs)
111
+
112
+
113
  def _create_causal_mask(config, inputs_embeds, attention_mask, past_key_values, cache_position):
114
+ if attention_mask is not None and attention_mask.ndim == 4:
115
+ mask = attention_mask.to(device=inputs_embeds.device)
116
+ if mask.dtype == torch.bool:
117
+ additive = torch.zeros(mask.shape, device=mask.device, dtype=inputs_embeds.dtype)
118
+ return additive.masked_fill(~mask, torch.finfo(inputs_embeds.dtype).min)
119
+ return mask.to(dtype=inputs_embeds.dtype)
120
+
121
+ cache_supported = past_key_values is None or (
122
+ hasattr(past_key_values, "get_mask_sizes")
123
+ and ("cache_position" in _HF_MASK_PARAMETERS or hasattr(past_key_values, "get_query_offset"))
124
+ )
125
+ if _hf_create_causal_mask is not None and _HF_MASK_INPUT_NAME is not None and cache_supported:
126
+ mask_kwargs = {
127
+ "config": config,
128
+ _HF_MASK_INPUT_NAME: inputs_embeds,
129
+ "attention_mask": attention_mask,
130
+ "past_key_values": past_key_values,
131
+ }
132
+ if "cache_position" in _HF_MASK_PARAMETERS:
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:
 
181
  inv_freq, module.attention_scaling = module.rope_init_fn(
182
  module.config, device=module.inv_freq.device
183
  )
184
+ _init_tensor(module.inv_freq, "copy_", inv_freq)
185
  module.original_inv_freq = module.inv_freq
186
  elif isinstance(module, SigLIPRotaryEmbedding):
187
  inv_freq = 1.0 / (
 
190
  / module.dim
191
  )
192
  )
193
+ _init_tensor(module.inv_freq, "copy_", inv_freq)
194
  elif isinstance(module, PaddleOCRVisionEmbeddings):
195
+ _init_tensor(module.position_ids, "copy_",
196
  torch.arange(module.num_positions, device=module.position_ids.device).expand(1, -1)
197
  )
198
 
 
856
  )
857
 
858
 
859
+ @_guard_initializer
860
  def _trunc_normal_(tensor, mean, std, a, b):
 
 
861
  # Cut & paste from PyTorch official master until it's in a few official releases - RW
862
  # Method based on https://people.sc.fsu.edu/~jburkardt/presentations/truncated_normal.pdf
863
  def norm_cdf(x):
 
893
  tensor.clamp_(min=a, max=b)
894
 
895
 
896
+ @_guard_initializer
897
  def trunc_normal_tf_(
898
  tensor: torch.Tensor,
899
  mean: float = 0.0,
 
919
  a: the minimum cutoff value
920
  b: the maximum cutoff value
921
  """
 
 
922
  with torch.no_grad():
923
  _trunc_normal_(tensor, 0, 1.0, a, b)
924
  tensor.mul_(std).add_(mean)
925
 
926
 
927
+ @_guard_initializer
928
  def variance_scaling_(tensor, scale=1.0, mode="fan_in", distribution="normal"):
 
 
929
  fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor)
930
  if mode == "fan_in":
931
  denom = fan_in
 
1441
  if isinstance(self.config, PaddleOCRVLConfig)
1442
  else self.config.hidden_size
1443
  )
1444
+ _init_tensor(module.position_embedding.weight, 'normal_', std=1 / np.sqrt(width))
1445
  elif isinstance(module, nn.Embedding):
1446
  default_flax_embed_init(module.weight)
1447
  elif isinstance(module, PaddleOCRAttention):
1448
+ _init_tensor(module.q_proj.weight, 'xavier_uniform_')
1449
+ _init_tensor(module.k_proj.weight, 'xavier_uniform_')
1450
+ _init_tensor(module.v_proj.weight, 'xavier_uniform_')
1451
+ _init_tensor(module.out_proj.weight, 'xavier_uniform_')
1452
+ _init_tensor(module.q_proj.bias, 'zeros_')
1453
+ _init_tensor(module.k_proj.bias, 'zeros_')
1454
+ _init_tensor(module.v_proj.bias, 'zeros_')
1455
+ _init_tensor(module.out_proj.bias, 'zeros_')
1456
  elif isinstance(module, PaddleOCRMLP):
1457
+ _init_tensor(module.fc1.weight, 'xavier_uniform_')
1458
+ _init_tensor(module.fc2.weight, 'xavier_uniform_')
1459
+ _init_tensor(module.fc1.bias, 'normal_', std=1e-6)
1460
+ _init_tensor(module.fc2.bias, 'normal_', std=1e-6)
1461
  elif isinstance(module, PaddleOCRMultiheadAttentionPoolingHead):
1462
+ _init_tensor(module.probe, 'xavier_uniform_')
1463
+ _init_tensor(module.attention.in_proj_weight, 'xavier_uniform_')
1464
+ _init_tensor(module.attention.in_proj_bias, 'zeros_')
1465
  elif isinstance(module, (nn.Linear, nn.Conv2d)):
1466
  lecun_normal_(module.weight)
1467
  if module.bias is not None:
1468
+ _init_tensor(module.bias, 'zeros_')
1469
  elif isinstance(module, nn.LayerNorm):
1470
+ _init_tensor(module.bias, 'zeros_')
1471
+ _init_tensor(module.weight, 'ones_')
1472
 
1473
 
1474
  # Copied from transformers.models.altclip.modeling_altclip.AltCLIPEncoder with AltCLIP->PaddleOCR
tests/README.md CHANGED
@@ -1,20 +1,36 @@
1
  # Remote-code compatibility regression
2
 
3
- Run from a model checkout containing the checkpoint and tokenizer files:
4
 
5
  ```bash
6
  python -m pip install einops sentencepiece protobuf
7
  python tests/test_remote_code_compat.py --full --model . --inputs tests/data --output /tmp/paddleocr-vl-compat
8
  ```
9
 
10
- Use an environment with PyTorch, torchvision, Pillow, NumPy, safetensors, and the Transformers version under test. The script always uses `trust_remote_code=True`, local checkpoint files, CPU float32, and an isolated dynamic-module cache below the output directory. Each version is a separate process/environment; framework versions are not switched inside one Python process.
 
 
11
 
12
- The default suite checks checkpoint parameter equality, derived RoPE buffers, mask semantics, English generation, an uncached prefix, and batch-prefill isolation. `--full` additionally checks a complete uncached English generation, Chinese OCR, equality of individual/batched decoded text, and table recognition.
 
 
 
13
 
14
- `compatibility_results.json` records the three executed boundary versions for this model and their environment scope. It does not claim continuous-range or future-version coverage.
 
 
 
 
15
 
16
- Fixtures:
 
 
 
 
 
 
 
17
 
18
- - `paddle_general_ocr_001.png`: official synthetic Chinese OCR test image from https://huggingface.co/datasets/hf-internal-testing/transformers-synthetic-assets/resolve/main/images/paddle_general_ocr_001.png .
19
- - `invoice_en.png`: synthetic three-line English invoice with known expected text.
20
- - `table_en.png`: synthetic Item/Qty/Price table containing Apple and Orange rows.
 
1
  # Remote-code compatibility regression
2
 
3
+ Use a model checkout containing its official checkpoint and tokenizer files:
4
 
5
  ```bash
6
  python -m pip install einops sentencepiece protobuf
7
  python tests/test_remote_code_compat.py --full --model . --inputs tests/data --output /tmp/paddleocr-vl-compat
8
  ```
9
 
10
+ The script uses AutoModelForCausalLM / AutoProcessor with trust_remote_code=True,
11
+ CPU float32, local files, and an isolated dynamic-module cache. Use a separate
12
+ process/environment for each Transformers release. No Transformers pin is added.
13
 
14
+ Full checks compare every loaded parameter with the checkpoint; validate text
15
+ and vision RoPE and vision position-ID buffers; cover mask semantics, English
16
+ OCR/EOS, cached versus complete uncached token equality, Chinese/batched OCR,
17
+ batch-prefill isolation, and table recognition.
18
 
19
+ The backend contract checks cover eager/SDPA prefill and cached decode with and
20
+ without left padding, the unpadded SDPA no-mask fast path, the local fallback,
21
+ propagation of native backend exceptions, and StaticCache mask capacity/padding.
22
+ Initializer checks protect already-loaded tensors and preserve TF-style
23
+ truncation. They also run without checkpoint weights:
24
 
25
+ ```bash
26
+ python tests/test_remote_code_compat.py --helpers-only --model . --output /tmp/paddleocr-vl-helpers
27
+ ```
28
+
29
+ compatibility_results.json records this model's executed releases and scope.
30
+ StaticCache mask construction is covered; full-model StaticCache generation,
31
+ CUDA/MPS, low precision, FlashAttention, training, dataset-level accuracy and
32
+ performance, and every historical/intermediate/future release are not verified.
33
 
34
+ Fixtures: invoice_en.png and table_en.png are synthetic images. The Chinese
35
+ fixture paddle_general_ocr_001.png comes from the official synthetic assets:
36
+ https://huggingface.co/datasets/hf-internal-testing/transformers-synthetic-assets/resolve/main/images/paddle_general_ocr_001.png
tests/compatibility_results.json CHANGED
@@ -1,109 +1,144 @@
1
  {
2
  "model": "PaddlePaddle/PaddleOCR-VL",
3
  "revision": "7fa00a8c55b735ba51ba49a9058f3f9c57a99a11",
 
4
  "trust_remote_code": true,
5
  "validation_scope": "full checkpoint and generation",
6
- "full_checkpoint_pending": false,
7
  "device": "cpu",
8
  "dtype": "float32",
9
- "checkpoint_sha256": "3085f1042e184f68f8a412aa0f64f2c4b8562989598bbfba326aaa11fc685de8",
10
  "tested_versions": [
11
  "4.57.6",
12
  "5.0.0",
13
  "5.21.1"
14
  ],
15
  "same_source_sha256": {
16
- "modeling_paddleocr_vl.py": "d01a9847b5e4142e5aa35c28f2354fae920ab31b2f990216db39287d344758ce",
17
  "image_processing_paddleocr_vl.py": "a4fa521b9cb16e207f94b7f2d16427771776dfc634420d319fc4916ee58049ec",
18
  "processing_paddleocr_vl.py": "e29cb1e5f275f2bd3ce051bd5c9983a33894e693b2823a0e13d4c07c8c4f9e13",
19
  "configuration_paddleocr_vl.py": "753dd93654c3a9c8c85a3eaee1e3092dd12591b0f2dce0305e1abfb7a41ff160"
20
  },
21
  "all_tested_passed": true,
22
  "universal_historical_or_future_support_claimed": false,
23
- "original_reproduction_scope": "original source, meta construction/text forward; no checkpoint weights",
24
- "original_failures": [
25
- {
26
- "transformers": "4.57.6",
27
- "stage": "text_decoder_forward",
28
- "exception_type": "TypeError",
29
- "error": "create_causal_mask() got an unexpected keyword argument 'inputs_embeds'",
30
- "expected_failure": true
31
- },
32
- {
33
- "transformers": "5.0.0",
34
- "stage": null,
35
- "exception_type": "KeyError",
36
- "error": "'default'",
37
- "expected_failure": true
38
- },
39
- {
40
- "transformers": "5.21.1",
41
- "stage": null,
42
- "exception_type": "KeyError",
43
- "error": "'default'",
44
- "expected_failure": true
45
- }
46
- ],
47
  "versions": [
48
  {
49
  "version": "4.57.6",
50
  "passed": true,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  "stages": [
52
  "config",
53
  "processor",
54
  "model",
55
  "weights_and_buffers",
56
  "mask_semantics",
 
57
  "english_generate",
58
  "uncached_prefix",
59
  "batch_prefill_isolation",
60
  "uncached_full_english",
61
  "full_chinese_batch_table"
62
- ],
63
- "parameter_mismatches": 0,
64
- "batch_logits_max_abs_difference": 4.3392181396484375e-05,
65
- "uncached_full_english_matches_cached": true,
66
- "full_batch_matches_individual": true
67
  },
68
  {
69
  "version": "5.0.0",
70
  "passed": true,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  "stages": [
72
  "config",
73
  "processor",
74
  "model",
75
  "weights_and_buffers",
76
  "mask_semantics",
 
77
  "english_generate",
78
  "uncached_prefix",
79
  "batch_prefill_isolation",
80
  "uncached_full_english",
81
  "full_chinese_batch_table"
82
- ],
83
- "parameter_mismatches": 0,
84
- "batch_logits_max_abs_difference": 4.3392181396484375e-05,
85
- "uncached_full_english_matches_cached": true,
86
- "full_batch_matches_individual": true
87
  },
88
  {
89
  "version": "5.21.1",
90
  "passed": true,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  "stages": [
92
  "config",
93
  "processor",
94
  "model",
95
  "weights_and_buffers",
96
  "mask_semantics",
 
97
  "english_generate",
98
  "uncached_prefix",
99
  "batch_prefill_isolation",
100
  "uncached_full_english",
101
  "full_chinese_batch_table"
102
- ],
103
- "parameter_mismatches": 0,
104
- "batch_logits_max_abs_difference": 4.3392181396484375e-05,
105
- "uncached_full_english_matches_cached": true,
106
- "full_batch_matches_individual": true
107
  }
108
  ]
109
  }
 
1
  {
2
  "model": "PaddlePaddle/PaddleOCR-VL",
3
  "revision": "7fa00a8c55b735ba51ba49a9058f3f9c57a99a11",
4
+ "checkpoint_sha256": "3085f1042e184f68f8a412aa0f64f2c4b8562989598bbfba326aaa11fc685de8",
5
  "trust_remote_code": true,
6
  "validation_scope": "full checkpoint and generation",
 
7
  "device": "cpu",
8
  "dtype": "float32",
9
+ "full_checkpoint_pending": false,
10
  "tested_versions": [
11
  "4.57.6",
12
  "5.0.0",
13
  "5.21.1"
14
  ],
15
  "same_source_sha256": {
16
+ "modeling_paddleocr_vl.py": "27099e51f7ab16df18273bea955b3357abf3e92562f95dc1b857b6dd10c7e2e8",
17
  "image_processing_paddleocr_vl.py": "a4fa521b9cb16e207f94b7f2d16427771776dfc634420d319fc4916ee58049ec",
18
  "processing_paddleocr_vl.py": "e29cb1e5f275f2bd3ce051bd5c9983a33894e693b2823a0e13d4c07c8c4f9e13",
19
  "configuration_paddleocr_vl.py": "753dd93654c3a9c8c85a3eaee1e3092dd12591b0f2dce0305e1abfb7a41ff160"
20
  },
21
  "all_tested_passed": true,
22
  "universal_historical_or_future_support_claimed": false,
23
+ "initializer_equivalence": {
24
+ "cases": 36,
25
+ "all_equal": true,
26
+ "reference_source_sha256": "d01a9847b5e4142e5aa35c28f2354fae920ab31b2f990216db39287d344758ce"
27
+ },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  "versions": [
29
  {
30
  "version": "4.57.6",
31
  "passed": true,
32
+ "parameter_mismatches": 0,
33
+ "batch_logits_max_abs_difference": 4.3392181396484375e-05,
34
+ "uncached_full_english_matches_cached": true,
35
+ "full_batch_matches_individual": true,
36
+ "backend_contracts": {
37
+ "mask_cases": 12,
38
+ "max_valid_token_difference": 0.0,
39
+ "sdpa_fast_path": true,
40
+ "fallback_exercised": true,
41
+ "backend_errors_propagate": true,
42
+ "static_cache_mask": true,
43
+ "loaded_initializer_guards": true,
44
+ "tf_truncation_preserved": true,
45
+ "native_mask_input_name": "input_embeds",
46
+ "native_mask_requires_cache_position": true
47
+ },
48
+ "previous_revision_comparison": {
49
+ "english_token_ids_unchanged": true,
50
+ "batch_text_unchanged": true,
51
+ "table_text_unchanged": true
52
+ },
53
  "stages": [
54
  "config",
55
  "processor",
56
  "model",
57
  "weights_and_buffers",
58
  "mask_semantics",
59
+ "backend_contracts",
60
  "english_generate",
61
  "uncached_prefix",
62
  "batch_prefill_isolation",
63
  "uncached_full_english",
64
  "full_chinese_batch_table"
65
+ ]
 
 
 
 
66
  },
67
  {
68
  "version": "5.0.0",
69
  "passed": true,
70
+ "parameter_mismatches": 0,
71
+ "batch_logits_max_abs_difference": 4.3392181396484375e-05,
72
+ "uncached_full_english_matches_cached": true,
73
+ "full_batch_matches_individual": true,
74
+ "backend_contracts": {
75
+ "mask_cases": 12,
76
+ "max_valid_token_difference": 0.0,
77
+ "sdpa_fast_path": true,
78
+ "fallback_exercised": true,
79
+ "backend_errors_propagate": true,
80
+ "static_cache_mask": true,
81
+ "loaded_initializer_guards": true,
82
+ "tf_truncation_preserved": true,
83
+ "native_mask_input_name": "input_embeds",
84
+ "native_mask_requires_cache_position": true
85
+ },
86
+ "previous_revision_comparison": {
87
+ "english_token_ids_unchanged": true,
88
+ "batch_text_unchanged": true,
89
+ "table_text_unchanged": true
90
+ },
91
  "stages": [
92
  "config",
93
  "processor",
94
  "model",
95
  "weights_and_buffers",
96
  "mask_semantics",
97
+ "backend_contracts",
98
  "english_generate",
99
  "uncached_prefix",
100
  "batch_prefill_isolation",
101
  "uncached_full_english",
102
  "full_chinese_batch_table"
103
+ ]
 
 
 
 
104
  },
105
  {
106
  "version": "5.21.1",
107
  "passed": true,
108
+ "parameter_mismatches": 0,
109
+ "batch_logits_max_abs_difference": 4.3392181396484375e-05,
110
+ "uncached_full_english_matches_cached": true,
111
+ "full_batch_matches_individual": true,
112
+ "backend_contracts": {
113
+ "mask_cases": 12,
114
+ "max_valid_token_difference": 0.0,
115
+ "sdpa_fast_path": true,
116
+ "fallback_exercised": true,
117
+ "backend_errors_propagate": true,
118
+ "static_cache_mask": true,
119
+ "loaded_initializer_guards": true,
120
+ "tf_truncation_preserved": true,
121
+ "native_mask_input_name": "inputs_embeds",
122
+ "native_mask_requires_cache_position": false
123
+ },
124
+ "previous_revision_comparison": {
125
+ "english_token_ids_unchanged": true,
126
+ "batch_text_unchanged": true,
127
+ "table_text_unchanged": true
128
+ },
129
  "stages": [
130
  "config",
131
  "processor",
132
  "model",
133
  "weights_and_buffers",
134
  "mask_semantics",
135
+ "backend_contracts",
136
  "english_generate",
137
  "uncached_prefix",
138
  "batch_prefill_isolation",
139
  "uncached_full_english",
140
  "full_chinese_batch_table"
141
+ ]
 
 
 
 
142
  }
143
  ]
144
  }
tests/test_remote_code_compat.py CHANGED
@@ -11,6 +11,7 @@ from pathlib import Path
11
  args_parser=argparse.ArgumentParser()
12
  args_parser.add_argument('--imports-only',action='store_true')
13
  args_parser.add_argument('--full',action='store_true')
 
14
  args_parser.add_argument('--model',type=Path)
15
  args_parser.add_argument('--inputs',type=Path)
16
  args_parser.add_argument('--output',type=Path)
@@ -25,9 +26,9 @@ OUT.mkdir(parents=True,exist_ok=True)
25
  os.environ['HF_MODULES_CACHE']=str(OUT/'modules')
26
  os.environ['HF_HUB_OFFLINE']='1'
27
  os.environ['TOKENIZERS_PARALLELISM']='false'
28
- report={'transformers':VERSION,'trust_remote_code':True,'mode':'imports' if args.imports_only else 'full' if args.full else 'matrix','pid':os.getpid(),'started_at':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),'stages':{}}
29
  report['source_sha256']={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in MODEL.glob('*.py')}
30
- def save(): (OUT/('imports.json' if args.imports_only else 'full.json' if args.full else 'results.json')).write_text(json.dumps(report,ensure_ascii=False,indent=2,default=str)+'\n')
31
  def step(name,fn):
32
  started=time.perf_counter()
33
  try:
@@ -36,6 +37,111 @@ def step(name,fn):
36
  except Exception as exc:
37
  report['stages'][name]={'passed':False,'error':str(exc),'traceback':traceback.format_exc()}
38
  print(name,type(exc).__name__,str(exc),flush=True);save();raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  try:
40
  import torch,transformers,tokenizers,huggingface_hub
41
  from PIL import Image
@@ -51,8 +157,11 @@ try:
51
  def construct():
52
  with torch.device('meta'):
53
  return AutoModelForCausalLM.from_config(config,trust_remote_code=True)
54
- if args.imports_only:
55
  model=step('model_class',construct)
 
 
 
56
  else:
57
  def load():
58
  model,info=AutoModelForCausalLM.from_pretrained(MODEL,torch_dtype=torch.float32,output_loading_info=True,**common)
@@ -76,6 +185,8 @@ try:
76
  elif type(module).__name__=='SigLIPRotaryEmbedding':
77
  expected=1/(module.theta**(torch.arange(0,module.dim,2,dtype=torch.float32)/module.dim))
78
  assert torch.equal(expected,module.inv_freq)
 
 
79
  step('weights_and_buffers',weights)
80
  def mask_semantics():
81
  import sys
@@ -92,6 +203,8 @@ try:
92
  mask=create(c,torch.zeros(1,2,4),allowed,None,torch.arange(2))
93
  assert torch.equal(mask==0,allowed)
94
  step('mask_semantics',mask_semantics)
 
 
95
  processor.tokenizer.padding_side='left'
96
  def inputs(names,prompt='OCR:'):
97
  images=[];messages=[]
 
11
  args_parser=argparse.ArgumentParser()
12
  args_parser.add_argument('--imports-only',action='store_true')
13
  args_parser.add_argument('--full',action='store_true')
14
+ args_parser.add_argument('--helpers-only',action='store_true')
15
  args_parser.add_argument('--model',type=Path)
16
  args_parser.add_argument('--inputs',type=Path)
17
  args_parser.add_argument('--output',type=Path)
 
26
  os.environ['HF_MODULES_CACHE']=str(OUT/'modules')
27
  os.environ['HF_HUB_OFFLINE']='1'
28
  os.environ['TOKENIZERS_PARALLELISM']='false'
29
+ report={'transformers':VERSION,'trust_remote_code':True,'mode':'helpers' if args.helpers_only else 'imports' if args.imports_only else 'full' if args.full else 'matrix','pid':os.getpid(),'started_at':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),'stages':{}}
30
  report['source_sha256']={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in MODEL.glob('*.py')}
31
+ def save(): (OUT/('helpers.json' if args.helpers_only else 'imports.json' if args.imports_only else 'full.json' if args.full else 'results.json')).write_text(json.dumps(report,ensure_ascii=False,indent=2,default=str)+'\n')
32
  def step(name,fn):
33
  started=time.perf_counter()
34
  try:
 
37
  except Exception as exc:
38
  report['stages'][name]={'passed':False,'error':str(exc),'traceback':traceback.format_exc()}
39
  print(name,type(exc).__name__,str(exc),flush=True);save();raise
40
+ """Backend contracts exercised without requiring model checkpoint weights."""
41
+
42
+ def check_backend_contracts(module, report):
43
+ import math
44
+ import torch
45
+ from transformers import PretrainedConfig
46
+ from transformers.cache_utils import DynamicCache, StaticCache
47
+
48
+ config=PretrainedConfig()
49
+ qkv_generator=torch.Generator().manual_seed(321)
50
+ checked=0
51
+ largest=0.0
52
+ for backend in ['eager','sdpa']:
53
+ config._attn_implementation=backend
54
+ for past_length,query_length in [(0,4),(3,1),(3,2)]:
55
+ for padded in [False,True]:
56
+ cache=None
57
+ if past_length:
58
+ cache=DynamicCache()
59
+ past=torch.zeros(1,2,past_length,4)
60
+ cache.update(past,past,0,{'cache_position':torch.arange(past_length)})
61
+ positions=torch.arange(past_length,past_length+query_length)
62
+ embeds=torch.zeros(1,query_length,8)
63
+ padding=torch.ones(1,past_length+query_length)
64
+ if padded:padding[:,:2]=0
65
+ native=module._create_causal_mask(config,embeds,padding,cache,positions)
66
+ fallback=module._create_causal_mask_fallback(config,embeds,padding,cache,positions)
67
+ q=torch.randn(1,2,query_length,4,generator=qkv_generator)
68
+ k=torch.randn(1,2,past_length+query_length,4,generator=qkv_generator)
69
+ v=torch.randn(k.shape,generator=qkv_generator)
70
+ actual=torch.nn.functional.scaled_dot_product_attention(q,k,v,attn_mask=native,is_causal=native is None and query_length>1)
71
+ expected=torch.nn.functional.scaled_dot_product_attention(q,k,v,attn_mask=fallback)
72
+ # Left-padding query rows are discarded; compare valid tokens.
73
+ valid=padding[0,past_length:].bool()
74
+ torch.testing.assert_close(actual[:,:,valid],expected[:,:,valid],atol=2e-6,rtol=2e-6)
75
+ assert torch.isfinite(actual).all()
76
+ largest=max(largest,(actual[:,:,valid]-expected[:,:,valid]).abs().max().item())
77
+ checked+=1
78
+
79
+ config._attn_implementation='sdpa'
80
+ x=torch.zeros(1,4,8)
81
+ positions=torch.arange(4)
82
+ mask=module._create_causal_mask(config,x,torch.ones(1,4),None,positions)
83
+ native_available=module._hf_create_causal_mask is not None and module._HF_MASK_INPUT_NAME is not None
84
+ if native_available:
85
+ assert mask is None,'Unpadded SDPA should preserve the native is_causal fast path'
86
+ fast_path=mask is None
87
+ native_function=module._hf_create_causal_mask
88
+ try:
89
+ module._hf_create_causal_mask=None
90
+ mask=module._create_causal_mask(config,x,torch.ones(1,4),None,positions)
91
+ assert torch.equal(mask[0,0]==0,torch.ones(4,4,dtype=torch.bool).tril())
92
+ def broken_helper(**kwargs):raise TypeError('backend-error-must-propagate')
93
+ if native_available:
94
+ module._hf_create_causal_mask=broken_helper
95
+ try:module._create_causal_mask(config,x,None,None,positions)
96
+ except TypeError as exc:assert str(exc)=='backend-error-must-propagate'
97
+ else:raise AssertionError('Backend computation errors must not be swallowed')
98
+ finally:module._hf_create_causal_mask=native_function
99
+
100
+ static_config=PretrainedConfig(num_hidden_layers=1,hidden_size=8,num_attention_heads=2,
101
+ num_key_value_heads=2,head_dim=4)
102
+ static_config._attn_implementation='eager'
103
+ cache=StaticCache(config=static_config,max_cache_len=8,batch_size=1)
104
+ past=torch.zeros(1,2,3,4)
105
+ cache.update(past,past,0,{'cache_position':torch.arange(3)})
106
+ static_mask=module._create_causal_mask(static_config,torch.zeros(1,1,8),
107
+ torch.tensor([[0,1,1,1]]),cache,torch.tensor([3]))
108
+ assert torch.equal(static_mask[0,0,0]==0,torch.tensor([False,True,True,True,False,False,False,False]))
109
+
110
+ for operation,args,kwargs in [
111
+ ('normal_',(),{'std':0.1}),('zeros_',(),{}),('ones_',(),{}),
112
+ ('xavier_uniform_',(),{}),('copy_',(torch.ones(4,4),),{})]:
113
+ tensor=torch.nn.Parameter(torch.full((4,4),0.123))
114
+ tensor._is_hf_initialized=True
115
+ expected=tensor.detach().clone()
116
+ assert module._init_tensor(tensor,operation,*args,**kwargs) is tensor
117
+ assert torch.equal(tensor,expected)
118
+ for name in ['_trunc_normal_','trunc_normal_tf_','variance_scaling_','lecun_normal_','default_flax_embed_init']:
119
+ tensor=torch.nn.Parameter(torch.full((4,4),0.123))
120
+ tensor._is_hf_initialized=True
121
+ expected=tensor.detach().clone()
122
+ arguments=(0.0,1.0,-2.0,2.0) if name=='_trunc_normal_' else ()
123
+ getattr(module,name)(tensor,*arguments)
124
+ assert torch.equal(tensor,expected)
125
+ fresh=torch.nn.Parameter(torch.empty(4,4))
126
+ module._init_tensor(fresh,'ones_')
127
+ assert torch.equal(fresh,torch.ones(4,4))
128
+ # TF-style truncation bounds are applied before scaling, unlike a direct
129
+ # replacement with torch.nn.init.trunc_normal_(std=...).
130
+ with torch.random.fork_rng():
131
+ torch.manual_seed(123)
132
+ fresh=torch.nn.Parameter(torch.empty(1024,64))
133
+ module.lecun_normal_(fresh)
134
+ bound=2*math.sqrt(1/64)/0.87962566103423978
135
+ assert fresh.abs().max()<=bound
136
+ assert abs(fresh.std().item()-1/math.sqrt(64))<0.005
137
+ report['backend_contracts']={'mask_cases':checked,'max_valid_token_difference':largest,
138
+ 'sdpa_fast_path':fast_path,'fallback_exercised':True,'backend_errors_propagate':native_available,
139
+ 'static_cache_mask':True,
140
+ 'loaded_initializer_guards':True,'tf_truncation_preserved':True,
141
+ 'native_mask_input_name':module._HF_MASK_INPUT_NAME,
142
+ 'native_mask_requires_cache_position':'cache_position' in module._HF_MASK_PARAMETERS}
143
+
144
+
145
  try:
146
  import torch,transformers,tokenizers,huggingface_hub
147
  from PIL import Image
 
157
  def construct():
158
  with torch.device('meta'):
159
  return AutoModelForCausalLM.from_config(config,trust_remote_code=True)
160
+ if args.imports_only or args.helpers_only:
161
  model=step('model_class',construct)
162
+ if args.helpers_only:
163
+ import sys
164
+ step('backend_contracts',lambda:check_backend_contracts(sys.modules[type(model).__module__],report))
165
  else:
166
  def load():
167
  model,info=AutoModelForCausalLM.from_pretrained(MODEL,torch_dtype=torch.float32,output_loading_info=True,**common)
 
185
  elif type(module).__name__=='SigLIPRotaryEmbedding':
186
  expected=1/(module.theta**(torch.arange(0,module.dim,2,dtype=torch.float32)/module.dim))
187
  assert torch.equal(expected,module.inv_freq)
188
+ elif type(module).__name__=='PaddleOCRVisionEmbeddings':
189
+ assert torch.equal(module.position_ids,torch.arange(module.num_positions).expand(1,-1))
190
  step('weights_and_buffers',weights)
191
  def mask_semantics():
192
  import sys
 
203
  mask=create(c,torch.zeros(1,2,4),allowed,None,torch.arange(2))
204
  assert torch.equal(mask==0,allowed)
205
  step('mask_semantics',mask_semantics)
206
+ import sys
207
+ step('backend_contracts',lambda:check_backend_contracts(sys.modules[type(model).__module__],report))
208
  processor.tokenizer.padding_side='left'
209
  def inputs(names,prompt='OCR:'):
210
  images=[];messages=[]