|
| 1 | +--- |
| 2 | +description: How to port a new model to keras_cv_attention_models |
| 3 | +--- |
| 4 | + |
| 5 | +# Porting a New Model |
| 6 | + |
| 7 | +This workflow covers adding a new model architecture (vision or LLM) to `keras_cv_attention_models`. |
| 8 | + |
| 9 | +## 1. Research the Source Model |
| 10 | + |
| 11 | +1. Read the paper and reference implementation (usually PyTorch from GitHub). |
| 12 | +2. Identify which **existing model family** it's closest to: |
| 13 | + - **Vision**: ViT-5 → `Beit`, EVA02 → `Beit`, DINOv2 → `Beit`, etc. |
| 14 | + - **LLM**: LLaMA3 → `LLaMA2`, etc. |
| 15 | +3. Note key architectural differences: new layers, attention mechanisms, normalization, positional encoding. |
| 16 | +4. Check if required building blocks already exist in `attention_layers/` (e.g., `RMSNorm`, `PositionalEncodingFourierRot`, `ClassToken`, `CausalMask`). |
| 17 | + |
| 18 | +## 2. Implement New Components |
| 19 | + |
| 20 | +1. If new layers are needed, add them to the appropriate file in `attention_layers/`. |
| 21 | +2. **Reuse existing layers** whenever possible — check `attention_layers/__init__.py` for available components. |
| 22 | +3. If modifying existing layers (e.g., adding parameters to `PositionalEncodingFourierRot`): |
| 23 | + - Ensure backward compatibility — existing models using the layer must still work. |
| 24 | + - Add new parameters with defaults matching the old behavior. |
| 25 | + - Update `get_config()` to include new parameters. |
| 26 | + |
| 27 | +> [!IMPORTANT] |
| 28 | +> **PyTorch backend compatibility**: Any layer that stores tensors as plain attributes (not via `add_weight`) must call `self.register_buffer()` in `__init__` for PyTorch backend support. Without this, the layer becomes a no-op identity in the PyTorch computation graph. See the PyTorch backend workflow for details. |
| 29 | +
|
| 30 | +## 3. Create the Model Variant File |
| 31 | + |
| 32 | +Models that share a base architecture should be **thin wrappers**: |
| 33 | + |
| 34 | +**Vision model** (see `beit/vit5.py`, `beit/eva02.py`): |
| 35 | +```python |
| 36 | +from keras_cv_attention_models.beit.beit import Beit |
| 37 | +from keras_cv_attention_models.models import register_model |
| 38 | + |
| 39 | +def ModelFamily(model_name="model_family", **kwargs): |
| 40 | + kwargs.pop("kwargs", None) |
| 41 | + return Beit(**locals(), **kwargs) |
| 42 | + |
| 43 | +@register_model |
| 44 | +def ModelFamily_Small(input_shape=(224, 224, 3), num_classes=1000, activation="gelu", |
| 45 | + classifier_activation="softmax", pretrained="imagenet", **kwargs): |
| 46 | + embed_dim = 384 |
| 47 | + num_heads = 6 |
| 48 | + return ModelFamily(**locals(), model_name="model_family_small", **kwargs) |
| 49 | +``` |
| 50 | + |
| 51 | +**LLM model** (see `llama2/llama2.py`): |
| 52 | +```python |
| 53 | +from keras_cv_attention_models.llama2.llama2 import LLaMA2 |
| 54 | +from keras_cv_attention_models.models import register_model |
| 55 | + |
| 56 | +@register_model |
| 57 | +def LLaMA3_8B(max_block_size=8192, vocab_size=128256, include_top=True, |
| 58 | + activation="swish", pretrained=None, **kwargs): |
| 59 | + num_blocks = 32 |
| 60 | + embedding_size = 4096 |
| 61 | + num_heads = 32 |
| 62 | + num_kv_heads = 8 |
| 63 | + return LLaMA2(**locals(), model_name="llama3_8b", **kwargs) |
| 64 | +``` |
| 65 | + |
| 66 | +Key rules: |
| 67 | +- Use `@register_model` decorator for each variant. |
| 68 | +- Set `pretrained` default appropriately once weights are available. |
| 69 | +- Use `kwargs.pop("kwargs", None)` to handle nested kwargs. |
| 70 | +- Model names use **snake_case** (e.g., `vit5_small_patch16`, `llama2_42m`). |
| 71 | + |
| 72 | +## 4. Register in `__init__.py` |
| 73 | + |
| 74 | +1. Import the new model and variant functions. |
| 75 | +2. Add a `__head_doc__` string with paper link and GitHub link. |
| 76 | +3. Add a `__tail_doc__` for common parameters: |
| 77 | + - **Vision**: `input_shape`, `num_classes`, `classifier_activation`, `pretrained`, plus model-specific params. |
| 78 | + - **LLM**: `vocab_size`, `max_block_size`, `include_top`, `dropout`, `activation`, `pretrained`. |
| 79 | +4. Add model architecture table (Params, FLOPs, and model-specific columns). |
| 80 | + |
| 81 | +## 5. Weight Conversion |
| 82 | + |
| 83 | +1. Add entries to `PRETRAINED_DICT` in the base model file: |
| 84 | + ```python |
| 85 | + # Vision: {model_name: {pretrained_tag: {resolution: md5}}} |
| 86 | + "vit5_small_patch16": {"imagenet": {224: "md5_hash_here"}}, |
| 87 | + # LLM: {model_name: {pretrained_tag: md5}} (no resolution key) |
| 88 | + "llama2_42m": {"tiny_stories": "md5_hash_here"}, |
| 89 | + ``` |
| 90 | +2. Convert weights using the appropriate method: |
| 91 | + - **Vision**: `download_and_load.keras_reload_from_torch_model()` or a model-specific helper. |
| 92 | + - **LLM**: `convert_huggingface_weights_to_h5()` or direct state_dict loading. |
| 93 | +3. After conversion, verify: |
| 94 | + ```python |
| 95 | + # Vision |
| 96 | + mm = kecam.beit.ViT5_Small_Patch16(pretrained="path/to/converted.h5") |
| 97 | + pred = mm(mm.preprocess_input(kecam.test_images.cat())) |
| 98 | + print(mm.decode_predictions(pred)[0][0]) # ('n02124075', 'Egyptian_cat', 0.84...) |
| 99 | + |
| 100 | + # LLM |
| 101 | + mm = kecam.llama2.LLaMA2_42M(pretrained="path/to/converted.h5") |
| 102 | + print(mm.run_prediction("A long time ago,", top_k=1, max_new_tokens=5)) |
| 103 | + ``` |
| 104 | +4. Compute the md5 hash: `md5sum converted_model.h5` |
| 105 | +5. Upload to GitHub releases under the appropriate sub-release tag. Notice user if cannot upload directly. |
| 106 | + |
| 107 | +## 6. Documentation |
| 108 | + |
| 109 | +1. **Model-specific `README.md`** (e.g., `beit/README.md`, `llama2/README.md`): |
| 110 | + - Add paper reference in the Summary section. |
| 111 | + - Add model table with Params, FLOPs, and model-specific metrics. |
| 112 | + - Download links: `[filename.h5](https://github.com/leondgarse/keras_cv_attention_models/releases/download/{sub_release}/{filename}.h5)` |
| 113 | +2. **Root `README.md`**: Add entry to Table of Contents in alphabetical order. |
| 114 | +3. **`__init__.py`**: Ensure all parameters are documented with model-specific defaults noted. |
| 115 | + |
| 116 | +## 7. Testing |
| 117 | + |
| 118 | +Add tests in `tests/test_models.py`: |
| 119 | + |
| 120 | +**Vision model**: |
| 121 | +```python |
| 122 | +def test_ModelFamily_defination(): |
| 123 | + mm = keras_cv_attention_models.beit.ModelFamily_Small(pretrained=None) |
| 124 | + assert isinstance(mm, models.Model) |
| 125 | + mm = keras_cv_attention_models.beit.ModelFamily_Small(pretrained=None, num_classes=0) |
| 126 | + assert isinstance(mm, models.Model) |
| 127 | + |
| 128 | +def test_ModelFamily_predict(): |
| 129 | + mm = keras_cv_attention_models.beit.ModelFamily_Small(pretrained="imagenet") |
| 130 | + pred = mm(mm.preprocess_input(cat())) |
| 131 | + out = mm.decode_predictions(pred)[0][0] |
| 132 | + assert out[1] == "Egyptian_cat" |
| 133 | +``` |
| 134 | + |
| 135 | +**LLM model**: |
| 136 | +```python |
| 137 | +def test_LLM_run_prediction(): |
| 138 | + mm = keras_cv_attention_models.llama2.LLaMA2_42M(pretrained="tiny_stories") |
| 139 | + generated = mm.run_prediction("A long time ago,", top_k=1, max_new_tokens=5) |
| 140 | + assert generated == " there was a little girl" |
| 141 | +``` |
| 142 | + |
| 143 | +If new layers were added, add layer tests in `tests/test_layers.py`. |
| 144 | + |
| 145 | +## 8. Format and Verify |
| 146 | + |
| 147 | +// turbo-all |
| 148 | + |
| 149 | +1. Run black formatter: |
| 150 | + ```sh |
| 151 | + find ./* -name "*.py" | grep -v __init__ | xargs -I {} black -l 160 {} |
| 152 | + ``` |
| 153 | +2. Run TF test suite: |
| 154 | + ```sh |
| 155 | + CUDA_VISIBLE_DEVICES='-1' pytest -vv --durations=0 ./tests |
| 156 | + ``` |
| 157 | +3. Run PyTorch test suite: |
| 158 | + ```sh |
| 159 | + CUDA_VISIBLE_DEVICES='-1' KECAM_BACKEND='torch' pytest -vv --durations=0 ./tests/test_models.py |
| 160 | + ``` |
0 commit comments