Spaces:
Running on Zero
Running on Zero
Commit ·
4ed3b70
1
Parent(s): c9744fe
feat: add explanation on `hidden_size = num_attention_heads * dim_per_head`
Browse files- README.md +2 -1
- app.py +31 -12
- src/extract.py +19 -6
README.md
CHANGED
|
@@ -27,7 +27,8 @@ learning notes only and are **not** imported by the app.
|
|
| 27 |
1. **Random matrix** — sample even-width Q (and K) tensors, apply numpy RoPE,
|
| 28 |
inspect heatmaps, pairwise 2D rotation, `QK^T`, and additive sinusoidal PE.
|
| 29 |
2. **Real model** — lazy-load an ungated Llama-like checkpoint (default
|
| 30 |
-
`HuggingFaceTB/SmolLM2-135M`
|
|
|
|
| 31 |
`k_proj` (GQA-aware), and compare educational numpy RoPE (`llama` pairing)
|
| 32 |
to the model's `rotary_emb`. No Hugging Face token is required. Gated models
|
| 33 |
are not used.
|
|
|
|
| 27 |
1. **Random matrix** — sample even-width Q (and K) tensors, apply numpy RoPE,
|
| 28 |
inspect heatmaps, pairwise 2D rotation, `QK^T`, and additive sinusoidal PE.
|
| 29 |
2. **Real model** — lazy-load an ungated Llama-like checkpoint (default
|
| 30 |
+
`HuggingFaceTB/SmolLM2-135M`; `HuggingFaceM4/tiny-random-LlamaForCausalLM`
|
| 31 |
+
is included for a very small test model), take `embed_tokens`, first-layer `q_proj` /
|
| 32 |
`k_proj` (GQA-aware), and compare educational numpy RoPE (`llama` pairing)
|
| 33 |
to the model's `rotary_emb`. No Hugging Face token is required. Gated models
|
| 34 |
are not used.
|
app.py
CHANGED
|
@@ -14,7 +14,7 @@ from src.extract import (
|
|
| 14 |
MODEL_CHOICES,
|
| 15 |
expand_kv_heads,
|
| 16 |
extract_from_model,
|
| 17 |
-
|
| 18 |
random_qk,
|
| 19 |
select_head,
|
| 20 |
)
|
|
@@ -93,18 +93,28 @@ def _safe_slider_max(n: int) -> int:
|
|
| 93 |
|
| 94 |
def update_dimension(source: str, model_name: str):
|
| 95 |
if source.startswith("Random"):
|
| 96 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
try:
|
| 98 |
-
|
| 99 |
-
return
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
)
|
| 106 |
except Exception:
|
| 107 |
-
return gr.update()
|
| 108 |
|
| 109 |
|
| 110 |
def compute(
|
|
@@ -306,6 +316,15 @@ with gr.Blocks(title="RoPE Explorer") as demo:
|
|
| 306 |
seq_len = gr.Slider(2, MAX_SEQ_LEN, value=16, step=1, label="Sequence length")
|
| 307 |
dim = gr.Slider(4, 128, value=32, step=2, label="Dimension (even; per attention head)")
|
| 308 |
seed = gr.Number(value=42, label="Seed", precision=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
base = gr.Number(
|
| 310 |
value=10000,
|
| 311 |
label="RoPE base (overridden by config.rope_theta for real models)",
|
|
@@ -355,12 +374,12 @@ with gr.Blocks(title="RoPE Explorer") as demo:
|
|
| 355 |
source.change(
|
| 356 |
update_dimension,
|
| 357 |
inputs=[source, model_name],
|
| 358 |
-
outputs=[dim],
|
| 359 |
)
|
| 360 |
model_name.change(
|
| 361 |
update_dimension,
|
| 362 |
inputs=[source, model_name],
|
| 363 |
-
outputs=[dim],
|
| 364 |
)
|
| 365 |
|
| 366 |
bulk_inputs = [state, which, head, mod_2pi]
|
|
|
|
| 14 |
MODEL_CHOICES,
|
| 15 |
expand_kv_heads,
|
| 16 |
extract_from_model,
|
| 17 |
+
get_model_dimensions,
|
| 18 |
random_qk,
|
| 19 |
select_head,
|
| 20 |
)
|
|
|
|
| 93 |
|
| 94 |
def update_dimension(source: str, model_name: str):
|
| 95 |
if source.startswith("Random"):
|
| 96 |
+
return (
|
| 97 |
+
gr.update(minimum=4, maximum=128, value=32, step=2, interactive=True),
|
| 98 |
+
gr.update(value=32),
|
| 99 |
+
gr.update(value=1),
|
| 100 |
+
gr.update(value=32),
|
| 101 |
+
)
|
| 102 |
try:
|
| 103 |
+
total_dim, n_heads, head_dim = get_model_dimensions(model_name)
|
| 104 |
+
return (
|
| 105 |
+
gr.update(
|
| 106 |
+
minimum=4,
|
| 107 |
+
maximum=max(128, head_dim),
|
| 108 |
+
value=head_dim,
|
| 109 |
+
step=2,
|
| 110 |
+
interactive=False,
|
| 111 |
+
),
|
| 112 |
+
gr.update(value=total_dim),
|
| 113 |
+
gr.update(value=n_heads),
|
| 114 |
+
gr.update(value=head_dim),
|
| 115 |
)
|
| 116 |
except Exception:
|
| 117 |
+
return gr.update(), gr.update(), gr.update(), gr.update()
|
| 118 |
|
| 119 |
|
| 120 |
def compute(
|
|
|
|
| 316 |
seq_len = gr.Slider(2, MAX_SEQ_LEN, value=16, step=1, label="Sequence length")
|
| 317 |
dim = gr.Slider(4, 128, value=32, step=2, label="Dimension (even; per attention head)")
|
| 318 |
seed = gr.Number(value=42, label="Seed", precision=0)
|
| 319 |
+
with gr.Row():
|
| 320 |
+
total_dim = gr.Number(value=32, label="Total dimension", precision=0, interactive=False)
|
| 321 |
+
attention_heads = gr.Number(value=1, label="Attention heads", precision=0, interactive=False)
|
| 322 |
+
head_dim = gr.Number(value=32, label="Dimension per attention head", precision=0, interactive=False)
|
| 323 |
+
gr.Markdown(
|
| 324 |
+
"**Why these numbers differ:** `total dimension = attention heads × dimension per head`. "
|
| 325 |
+
"RoPE rotates each query/key head separately, so its Dimension slider uses "
|
| 326 |
+
"the per-head value, not the model's total dimension."
|
| 327 |
+
)
|
| 328 |
base = gr.Number(
|
| 329 |
value=10000,
|
| 330 |
label="RoPE base (overridden by config.rope_theta for real models)",
|
|
|
|
| 374 |
source.change(
|
| 375 |
update_dimension,
|
| 376 |
inputs=[source, model_name],
|
| 377 |
+
outputs=[dim, total_dim, attention_heads, head_dim],
|
| 378 |
)
|
| 379 |
model_name.change(
|
| 380 |
update_dimension,
|
| 381 |
inputs=[source, model_name],
|
| 382 |
+
outputs=[dim, total_dim, attention_heads, head_dim],
|
| 383 |
)
|
| 384 |
|
| 385 |
bulk_inputs = [state, which, head, mod_2pi]
|
src/extract.py
CHANGED
|
@@ -108,20 +108,33 @@ def _require_hf():
|
|
| 108 |
return torch, AutoModelForCausalLM, AutoTokenizer
|
| 109 |
|
| 110 |
|
| 111 |
-
def
|
| 112 |
-
"""Return the Q/K dimension per attention head without loading model weights."""
|
| 113 |
if model_name not in _config_cache:
|
| 114 |
try:
|
| 115 |
from transformers import AutoConfig
|
| 116 |
except ImportError as exc:
|
| 117 |
raise RuntimeError("Real-model mode needs `transformers`.") from exc
|
| 118 |
_config_cache[model_name] = AutoConfig.from_pretrained(model_name)
|
|
|
|
| 119 |
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
configured_head_dim = getattr(config, "head_dim", None)
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
|
| 127 |
def get_model(model_name: str):
|
|
|
|
| 108 |
return torch, AutoModelForCausalLM, AutoTokenizer
|
| 109 |
|
| 110 |
|
| 111 |
+
def _get_model_config(model_name: str):
|
|
|
|
| 112 |
if model_name not in _config_cache:
|
| 113 |
try:
|
| 114 |
from transformers import AutoConfig
|
| 115 |
except ImportError as exc:
|
| 116 |
raise RuntimeError("Real-model mode needs `transformers`.") from exc
|
| 117 |
_config_cache[model_name] = AutoConfig.from_pretrained(model_name)
|
| 118 |
+
return _config_cache[model_name]
|
| 119 |
|
| 120 |
+
|
| 121 |
+
def get_model_dimensions(model_name: str) -> tuple[int, int, int]:
|
| 122 |
+
"""Return total hidden size, attention heads, and per-head Q/K size."""
|
| 123 |
+
config = _get_model_config(model_name)
|
| 124 |
+
total_dim = int(config.hidden_size)
|
| 125 |
+
n_heads = int(config.num_attention_heads)
|
| 126 |
configured_head_dim = getattr(config, "head_dim", None)
|
| 127 |
+
head_dim = (
|
| 128 |
+
int(configured_head_dim)
|
| 129 |
+
if configured_head_dim is not None
|
| 130 |
+
else total_dim // n_heads
|
| 131 |
+
)
|
| 132 |
+
return total_dim, n_heads, head_dim
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def get_model_head_dim(model_name: str) -> int:
|
| 136 |
+
"""Return the Q/K dimension per attention head without loading model weights."""
|
| 137 |
+
return get_model_dimensions(model_name)[2]
|
| 138 |
|
| 139 |
|
| 140 |
def get_model(model_name: str):
|