padoc-document-parser / padoc /preprocess.py
multimodalart's picture
multimodalart HF Staff
Upload folder using huggingface_hub
414b4fe verified
Raw
History Blame Contribute Delete
5.37 kB
"""Create a PaDoc-ready checkpoint from a standard image-text model."""
from __future__ import annotations
import argparse
import json
import logging
from pathlib import Path
import torch
from transformers import AutoModelForImageTextToText, AutoProcessor
from .constants import (
DEFAULT_FORK_TOKEN_MAP,
DEFAULT_SPECIAL_TOKENS,
PADOC_CONFIG_KEY,
PADOC_FORK_MAP_KEY,
PADOC_SPECIAL_TOKENS_KEY,
)
logger = logging.getLogger(__name__)
def get_padoc_metadata(model_or_config) -> dict:
config = getattr(model_or_config, "config", model_or_config)
metadata = getattr(config, PADOC_CONFIG_KEY, None)
if metadata is None and hasattr(config, "text_config"):
metadata = getattr(config.text_config, PADOC_CONFIG_KEY, None)
if not isinstance(metadata, dict) or not metadata.get(PADOC_FORK_MAP_KEY):
raise ValueError("Checkpoint has no padoc.fork_token_map metadata.")
return metadata
def get_fork_token_map(model_or_config) -> dict[str, str]:
return dict(get_padoc_metadata(model_or_config)[PADOC_FORK_MAP_KEY])
def _initialize_new_rows(model, token_ids: list[int], old_vocab_size: int, seed: int) -> None:
if not token_ids:
return
with torch.no_grad(), torch.random.fork_rng():
torch.manual_seed(seed)
input_weights = model.get_input_embeddings().weight
old_input = input_weights[:old_vocab_size].float()
input_mean = old_input.mean(0)
input_std = old_input.std(0)
for token_id in token_ids:
row = input_mean + torch.randn_like(input_mean) * input_std
input_weights[token_id].copy_(row.to(input_weights.dtype))
output = model.get_output_embeddings()
if output is not None and output.weight is not input_weights:
output_weights = output.weight
old_output = output_weights[:old_vocab_size].float()
output_mean = old_output.mean(0)
output_std = old_output.std(0)
for token_id in token_ids:
row = output_mean + torch.randn_like(output_mean) * output_std
output_weights[token_id].copy_(row.to(output_weights.dtype))
def preprocess_model(
base_model: str | Path,
output_dir: str | Path,
*,
special_tokens: list[str] | None = None,
fork_token_map: dict[str, str] | None = None,
dtype: torch.dtype = torch.bfloat16,
seed: int = 42,
) -> Path:
"""Register atomic fork tokens and persist their mapping in config.json."""
special_tokens = list(special_tokens or DEFAULT_SPECIAL_TOKENS)
fork_token_map = dict(fork_token_map or DEFAULT_FORK_TOKEN_MAP)
referenced = set(fork_token_map) | set(fork_token_map.values())
if not referenced <= set(special_tokens):
missing = sorted(referenced - set(special_tokens))
raise ValueError(f"Fork map references tokens absent from special_tokens: {missing}")
model = AutoModelForImageTextToText.from_pretrained(str(base_model), dtype=dtype)
processor = AutoProcessor.from_pretrained(str(base_model))
tokenizer = processor.tokenizer
old_vocab_size = len(tokenizer)
new_tokens = [
token
for token in special_tokens
if len(tokenizer.encode(token, add_special_tokens=False)) != 1
]
if new_tokens:
tokenizer.add_special_tokens({"additional_special_tokens": new_tokens})
model.resize_token_embeddings(len(tokenizer))
new_ids = [tokenizer.encode(token, add_special_tokens=False)[0] for token in new_tokens]
_initialize_new_rows(model, new_ids, old_vocab_size, seed)
for token in special_tokens:
ids = tokenizer.encode(token, add_special_tokens=False)
if len(ids) != 1:
raise ValueError(f"Special token {token!r} is not atomic: {ids}")
metadata = {
PADOC_SPECIAL_TOKENS_KEY: special_tokens,
PADOC_FORK_MAP_KEY: fork_token_map,
}
setattr(model.config, PADOC_CONFIG_KEY, metadata)
if hasattr(model.config, "text_config"):
setattr(model.config.text_config, PADOC_CONFIG_KEY, metadata)
output_path = Path(output_dir).expanduser().resolve()
output_path.mkdir(parents=True, exist_ok=True)
model.save_pretrained(output_path)
processor.save_pretrained(output_path)
config_path = output_path / "config.json"
with config_path.open(encoding="utf-8") as handle:
config = json.load(handle)
config[PADOC_CONFIG_KEY] = metadata
with config_path.open("w", encoding="utf-8") as handle:
json.dump(config, handle, indent=2, ensure_ascii=False)
handle.write("\n")
logger.info("Saved PaDoc-ready checkpoint to %s", output_path)
return output_path
def main(argv: list[str] | None = None) -> None:
parser = argparse.ArgumentParser(description="Create a PaDoc-ready checkpoint.")
parser.add_argument("--base-model", required=True)
parser.add_argument("--output", required=True)
parser.add_argument("--seed", type=int, default=42)
parser.add_argument("--dtype", choices=("bfloat16", "float32"), default="bfloat16")
args = parser.parse_args(argv)
logging.basicConfig(level=logging.INFO)
preprocess_model(
args.base_model,
args.output,
seed=args.seed,
dtype=torch.bfloat16 if args.dtype == "bfloat16" else torch.float32,
)
if __name__ == "__main__":
main()