Download code/shared_character_recognizer.py from ampixa/deva-ocr-75m-research: direct link, hf CLI and curl.
- Browser
- Download file 5.77 kB
-
https://huggingface.co/ampixa/deva-ocr-75m-research/resolve/main/code/shared_character_recognizer.py
- Command line
-
hf download hf://ampixa/deva-ocr-75m-research/code/shared_character_recognizer.py
-
curl -L -o shared_character_recognizer.py https://huggingface.co/ampixa/deva-ocr-75m-research/resolve/main/code/shared_character_recognizer.py
5.77 kB
| """Research recognizer sharing one English encoder across two explicit branches. | |
| The character branch applies saved low-rank corrections through functional_call | |
| and supplies precomputed encoder outputs to a decoder-only VED container. No | |
| second learned encoder is registered or saved. Functional parameter overrides | |
| are serial CPU research operations, not a concurrent/native serving interface. | |
| """ | |
| import copy | |
| import hashlib | |
| import json | |
| from pathlib import Path | |
| import torch | |
| from torch import nn | |
| from safetensors.torch import load_file | |
| from transformers import DeiTModel, GenerationConfig, VisionEncoderDecoderConfig, VisionEncoderDecoderModel | |
| def sha(path): | |
| value = hashlib.sha256() | |
| with Path(path).open('rb') as stream: | |
| for block in iter(lambda: stream.read(1024 * 1024), b''): value.update(block) | |
| return value.hexdigest() | |
| def eager(config): | |
| config._attn_implementation = 'eager' | |
| config.encoder._attn_implementation = 'eager' | |
| config.decoder._attn_implementation = 'eager' | |
| return config | |
| def load_english(folder): | |
| config = eager(VisionEncoderDecoderConfig.from_pretrained(folder, local_files_only=True)) | |
| model, report = VisionEncoderDecoderModel.from_pretrained(folder, config=config, | |
| encoder=DeiTModel(config.encoder, add_pooling_layer=False), local_files_only=True, | |
| dtype=torch.float32, attn_implementation='eager', output_loading_info=True) | |
| assert not any(report.get(k) for k in ['missing_keys', 'unexpected_keys', 'mismatched_keys', 'error_msgs']) | |
| assert sum(p.numel() for p in model.parameters()) == 61448832 | |
| return model.eval().requires_grad_(False) | |
| class PrecomputedOnlyEncoder(nn.Module): | |
| main_input_name = 'pixel_values' | |
| def __init__(self, config): | |
| super().__init__(); self.config = copy.deepcopy(config) | |
| def get_output_embeddings(self): | |
| return None | |
| def forward(self, pixel_values=None, **kwargs): | |
| raise RuntimeError('Character decoding requires the shared encoder outputs') | |
| class Correction(nn.Module): | |
| def __init__(self, spec): | |
| super().__init__(); self.spec = spec | |
| if spec['kind'] == 'factor': | |
| self.left = nn.Parameter(torch.empty(spec['matrix_shape'][0], spec['rank'])) | |
| self.right = nn.Parameter(torch.empty(spec['rank'], spec['matrix_shape'][1])) | |
| else: | |
| assert spec['kind'] == 'replacement' | |
| self.value = nn.Parameter(torch.empty(spec['shape'])) | |
| def apply(self, base): | |
| if self.spec['kind'] == 'replacement': return self.value | |
| return base + (self.left @ self.right).reshape(self.spec['shape']) | |
| class SharedCharacterRecognizer(nn.Module): | |
| def __init__(self, original, character_decoder, character_projection, character_config, | |
| english_generation, character_generation, specifications): | |
| super().__init__() | |
| self.original = original | |
| cfg = eager(copy.deepcopy(character_config)) | |
| self.character = VisionEncoderDecoderModel(config=cfg, | |
| encoder=PrecomputedOnlyEncoder(cfg.encoder), decoder=character_decoder) | |
| if character_projection is not None: self.character.enc_to_dec_proj = character_projection | |
| self.original.generation_config = copy.deepcopy(english_generation) | |
| self.character.generation_config = copy.deepcopy(character_generation) | |
| self.specifications = specifications | |
| self.corrections = nn.ModuleDict({spec['key']: Correction(spec) for spec in specifications}) | |
| def generate_pixels(self, pixels, branch, generation): | |
| if self.training: raise ValueError('Explicit eval mode is required') | |
| if branch == 'english': return self.original.generate(pixel_values=pixels, **generation) | |
| if branch != 'character': raise ValueError('Require an explicit English or character branch') | |
| base = self.original.encoder.state_dict() | |
| overrides = {spec['name']: self.corrections[spec['key']].apply(base[spec['name']]) | |
| for spec in self.specifications} | |
| output = torch.func.functional_call(self.original.encoder, overrides, (), | |
| dict(pixel_values=pixels, return_dict=True), strict=False) | |
| return self.character.generate(encoder_outputs=output, **generation) | |
| def from_bundle(cls, folder): | |
| folder = Path(folder); manifest = json.loads((folder / 'bundle.json').read_text()) | |
| for name, expected in manifest['files_sha256'].items(): assert sha(folder / name) == expected | |
| configs = json.loads((folder / 'model-configs.json').read_text()) | |
| english = eager(VisionEncoderDecoderConfig.from_dict(configs['english'])) | |
| character = eager(VisionEncoderDecoderConfig.from_dict(configs['character'])) | |
| original = VisionEncoderDecoderModel(config=english, | |
| encoder=DeiTModel(english.encoder, add_pooling_layer=False)) | |
| private = VisionEncoderDecoderModel(config=character, | |
| encoder=PrecomputedOnlyEncoder(character.encoder)) | |
| model = cls(original, private.decoder, getattr(private, 'enc_to_dec_proj', None), character, | |
| GenerationConfig.from_pretrained(folder, config_file_name='english-generation.json', local_files_only=True), | |
| GenerationConfig.from_pretrained(folder, config_file_name='character-generation.json', local_files_only=True), | |
| manifest['corrections']) | |
| result = model.load_state_dict(load_file(str(folder / 'weights.safetensors')), strict=True) | |
| assert not result.missing_keys and not result.unexpected_keys | |
| assert sum(p.numel() for p in model.parameters()) == manifest['parameters'] == 74842752 | |
| assert len(model.character.encoder.state_dict()) == 0 | |
| return model.eval().requires_grad_(False) | |