GitLab Repo

amachine.am_transformers.am_gpt2_generator

  1import json
  2from pathlib import Path
  3from typing import Literal
  4
  5from .am_models import get_tokenizer, get_text_tokenizer
  6
  7try:
  8    from transformers import GPT2Config, AutoModelForCausalLM
  9    _HAVE_TRANSFORMERS = True
 10except ImportError:
 11    _HAVE_TRANSFORMERS = False
 12
 13def _gpt2_model_name(width: int, depth: int, head_dim: int, opt: str | None = None, id_str: str | None = None) -> str:
 14    """Generates a consistent naming convention for the GPT-2 models."""
 15    parts = [f"gpt2-w{width}-d{depth}-h{head_dim}"]
 16    if opt is not None:
 17        parts.append(f"o{opt}")
 18    if id_str is not None:
 19        parts.append(f"id{id_str}")
 20    return "-".join(parts)
 21
 22def generate_gpt2_ensemble(
 23    output_dir: str,
 24    core_params: list[dict],
 25    seq_len: int = 1024,
 26    tokenizer_type: Literal["symbolic", "text"] = "symbolic",
 27    tokenizer_id : str = "ibm-granite/granite-4.0-350m",
 28    activation_function : str = "gelu_new",
 29    dropout : float = 0.1
 30) -> dict:
 31    """Generate a sweep of standard GPT-2 models with varying widths and depths."""
 32
 33    if not _HAVE_TRANSFORMERS:
 34        raise ImportError("The 'generate_gpt2_ensemble' function requires 'transformers'.")
 35
 36    output_path = Path(output_dir)
 37    output_path.mkdir(parents=True, exist_ok=True)
 38
 39    manifest = {"models": []}
 40
 41    # 1. Setup the Tokenizer dynamically just like the Granite builder
 42    if tokenizer_type == "symbolic":
 43        raw_tokenizer, vocabulary, hf_tokenizer, gen_config = get_tokenizer()
 44    elif tokenizer_type == "text":
 45        raw_tokenizer, vocabulary, hf_tokenizer, gen_config = get_text_tokenizer( tokenizer_id )
 46    else:
 47        raise ValueError(f"Unsupported tokenizer_type {tokenizer_type}. Options are symbolic or text")
 48
 49    # assert isinstance(hf_tokenizer.pad_token_id, int), "pad_token_id is not an integer!"
 50    # assert isinstance(hf_tokenizer.bos_token_id, int), "bos_token_id is not an integer!"
 51    # assert isinstance(hf_tokenizer.eos_token_id, int), "eos_token_id is not an integer!"
 52
 53    vocab_size = len(hf_tokenizer)
 54    pad_token_id = hf_tokenizer.pad_token_id
 55    bos_token_id = hf_tokenizer.bos_token_id
 56    eos_token_id = hf_tokenizer.eos_token_id
 57
 58    for idx, p in enumerate(core_params):
 59
 60        if not "width" in p or not "depth" in p or not "head_dim" in p:
 61            raise ValueError("core params must contain width, depth, and head_dim")
 62
 63        width = int(p["width"])
 64        depth = int(p["depth"])
 65        head_dim = int(p["head_dim"])
 66
 67        opt = p.get("opt", None)
 68        id_str = p.get("id", str(idx))
 69        target_seq_len = int(p.get("seq_len", seq_len))
 70
 71        if width % head_dim != 0:
 72            raise ValueError(f"Width ({width}) must be divisible by head_dim ({head_dim})")
 73        
 74        n_head = width // head_dim
 75        
 76        name = _gpt2_model_name(width=width, depth=depth, head_dim=head_dim, opt=opt, id_str=id_str)
 77        model_dir = str(output_path / name)
 78        model_path = Path(model_dir)
 79        model_path.mkdir(parents=True, exist_ok=True)
 80
 81        print(f"\n{'=' * 60}")
 82        print(f"Generating {name}")
 83        print(f"  Architecture: GPT-2 | width (n_embd)={width} | depth (n_layer)={depth} | heads={n_head}")
 84
 85        config = GPT2Config(
 86            vocab_size=vocab_size,
 87            n_positions=target_seq_len+1, # +1 for safety, huggingface shifts internally, -> 1025 model inputs/labels to predict 1024
 88            n_embd=width,
 89            n_layer=depth,
 90            n_head=n_head,
 91            bos_token_id=bos_token_id,
 92            eos_token_id=eos_token_id,
 93            pad_token_id=pad_token_id,
 94            activation_function=activation_function,
 95            resid_pdrop=dropout,
 96            embd_pdrop=dropout,
 97            attn_pdrop=dropout,
 98            use_cache=True
 99        )
100
101        model = AutoModelForCausalLM.from_config(config)
102
103        total_params = sum(param.numel() for param in model.parameters())
104        print(f"  Total parameters: {total_params / 1e6:.2f}M")
105
106        model.save_pretrained(model_dir, safe_serialization=True)
107
108        raw_tokenizer.save(str(model_path / "tokenizer.json"))
109        
110        if tokenizer_type == "symbolic":
111            with open(model_path / "vocab.json", "w", encoding="utf-8") as f:
112                json.dump(vocabulary, f, indent=2, ensure_ascii=False)
113
114        hf_tokenizer.save_pretrained(model_dir)
115        gen_config.save_pretrained(model_dir)
116
117        manifest["models"].append({
118            "name": name,
119            "architecture": "gpt2",
120            "hidden_size": width,
121            "num_hidden_layers": depth,
122            "head_dim": head_dim,
123            "num_attention_heads": n_head,
124            "total_params": total_params,
125            "seq_len": target_seq_len,
126            "tokenizer_type": tokenizer_type,
127            "estimated_params" : total_params
128        })
129
130    manifest_path = output_path / "manifest_gpt2.json"
131    with open(manifest_path, "w") as f:
132        json.dump(manifest, f, indent=2)
133    
134    print(f"\nGPT-2 Manifest written to {manifest_path}")
135
136    return manifest
def generate_gpt2_ensemble( output_dir: str, core_params: list[dict], seq_len: int = 1024, tokenizer_type: Literal['symbolic', 'text'] = 'symbolic', tokenizer_id: str = 'ibm-granite/granite-4.0-350m', activation_function: str = 'gelu_new', dropout: float = 0.1) -> dict:
 23def generate_gpt2_ensemble(
 24    output_dir: str,
 25    core_params: list[dict],
 26    seq_len: int = 1024,
 27    tokenizer_type: Literal["symbolic", "text"] = "symbolic",
 28    tokenizer_id : str = "ibm-granite/granite-4.0-350m",
 29    activation_function : str = "gelu_new",
 30    dropout : float = 0.1
 31) -> dict:
 32    """Generate a sweep of standard GPT-2 models with varying widths and depths."""
 33
 34    if not _HAVE_TRANSFORMERS:
 35        raise ImportError("The 'generate_gpt2_ensemble' function requires 'transformers'.")
 36
 37    output_path = Path(output_dir)
 38    output_path.mkdir(parents=True, exist_ok=True)
 39
 40    manifest = {"models": []}
 41
 42    # 1. Setup the Tokenizer dynamically just like the Granite builder
 43    if tokenizer_type == "symbolic":
 44        raw_tokenizer, vocabulary, hf_tokenizer, gen_config = get_tokenizer()
 45    elif tokenizer_type == "text":
 46        raw_tokenizer, vocabulary, hf_tokenizer, gen_config = get_text_tokenizer( tokenizer_id )
 47    else:
 48        raise ValueError(f"Unsupported tokenizer_type {tokenizer_type}. Options are symbolic or text")
 49
 50    # assert isinstance(hf_tokenizer.pad_token_id, int), "pad_token_id is not an integer!"
 51    # assert isinstance(hf_tokenizer.bos_token_id, int), "bos_token_id is not an integer!"
 52    # assert isinstance(hf_tokenizer.eos_token_id, int), "eos_token_id is not an integer!"
 53
 54    vocab_size = len(hf_tokenizer)
 55    pad_token_id = hf_tokenizer.pad_token_id
 56    bos_token_id = hf_tokenizer.bos_token_id
 57    eos_token_id = hf_tokenizer.eos_token_id
 58
 59    for idx, p in enumerate(core_params):
 60
 61        if not "width" in p or not "depth" in p or not "head_dim" in p:
 62            raise ValueError("core params must contain width, depth, and head_dim")
 63
 64        width = int(p["width"])
 65        depth = int(p["depth"])
 66        head_dim = int(p["head_dim"])
 67
 68        opt = p.get("opt", None)
 69        id_str = p.get("id", str(idx))
 70        target_seq_len = int(p.get("seq_len", seq_len))
 71
 72        if width % head_dim != 0:
 73            raise ValueError(f"Width ({width}) must be divisible by head_dim ({head_dim})")
 74        
 75        n_head = width // head_dim
 76        
 77        name = _gpt2_model_name(width=width, depth=depth, head_dim=head_dim, opt=opt, id_str=id_str)
 78        model_dir = str(output_path / name)
 79        model_path = Path(model_dir)
 80        model_path.mkdir(parents=True, exist_ok=True)
 81
 82        print(f"\n{'=' * 60}")
 83        print(f"Generating {name}")
 84        print(f"  Architecture: GPT-2 | width (n_embd)={width} | depth (n_layer)={depth} | heads={n_head}")
 85
 86        config = GPT2Config(
 87            vocab_size=vocab_size,
 88            n_positions=target_seq_len+1, # +1 for safety, huggingface shifts internally, -> 1025 model inputs/labels to predict 1024
 89            n_embd=width,
 90            n_layer=depth,
 91            n_head=n_head,
 92            bos_token_id=bos_token_id,
 93            eos_token_id=eos_token_id,
 94            pad_token_id=pad_token_id,
 95            activation_function=activation_function,
 96            resid_pdrop=dropout,
 97            embd_pdrop=dropout,
 98            attn_pdrop=dropout,
 99            use_cache=True
100        )
101
102        model = AutoModelForCausalLM.from_config(config)
103
104        total_params = sum(param.numel() for param in model.parameters())
105        print(f"  Total parameters: {total_params / 1e6:.2f}M")
106
107        model.save_pretrained(model_dir, safe_serialization=True)
108
109        raw_tokenizer.save(str(model_path / "tokenizer.json"))
110        
111        if tokenizer_type == "symbolic":
112            with open(model_path / "vocab.json", "w", encoding="utf-8") as f:
113                json.dump(vocabulary, f, indent=2, ensure_ascii=False)
114
115        hf_tokenizer.save_pretrained(model_dir)
116        gen_config.save_pretrained(model_dir)
117
118        manifest["models"].append({
119            "name": name,
120            "architecture": "gpt2",
121            "hidden_size": width,
122            "num_hidden_layers": depth,
123            "head_dim": head_dim,
124            "num_attention_heads": n_head,
125            "total_params": total_params,
126            "seq_len": target_seq_len,
127            "tokenizer_type": tokenizer_type,
128            "estimated_params" : total_params
129        })
130
131    manifest_path = output_path / "manifest_gpt2.json"
132    with open(manifest_path, "w") as f:
133        json.dump(manifest, f, indent=2)
134    
135    print(f"\nGPT-2 Manifest written to {manifest_path}")
136
137    return manifest

Generate a sweep of standard GPT-2 models with varying widths and depths.