1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! The NTK-alpha RoPE base rescale, for the architectures that apply it.
//!
//! `{arch}.rope.scaling.alpha` (`LLM_KV_ROPE_SCALING_ALPHA`) is read for
//! EVERY architecture in llama.cpp's generic hparams loader
//! (`llama-model.cpp:1186`, optional, default `0.0f`), but only two
//! architectures do anything with it: `hunyuan-vl` and the
//! `hunyuan-dense` that inherits its `load_arch_hparams`
//! (`models.h:1830-1832`). `src/models/hunyuan-vl.cpp:8-12` is the whole
//! of it:
//!
//! ```text
//! // XDRoPE / NTK-aware scaling: base = rope_theta * alpha^(dim / (dim - 2))
//! if (hparams.rope_scaling_alpha > 0.0f) {
//! const int dim = hparams.n_embd_head_k();
//! hparams.rope_freq_base_train = hparams.rope_freq_base_train
//! * powf(hparams.rope_scaling_alpha, (float)dim / (float)(dim - 2));
//! }
//! ```
//!
//! **Why this is a list and not a generic rule.** The key is generic and
//! the behaviour is not. Applying the rescale wherever the key appears
//! would change the RoPE base of every other architecture that carries
//! it, which is the "two structures that must agree about one thing"
//! shape this repo keeps paying for -- llama.cpp reads the key in one
//! place and applies it in another, and only the second place is
//! per-architecture.
//!
//! **What a real `hunyuan-dense` checkpoint actually carries.** Not this
//! key. `conversion/hunyuan.py:254-281` (`HunYuanModel`, the
//! `HUNYUAN_DENSE` converter) does the same arithmetic in Python --
//! `scaled_base = base * (alpha ** (dim / (dim - 2)))` at :270 -- and
//! writes the ALREADY-SCALED value through `add_rope_freq_base`, with no
//! alpha key at all. The converter line that writes
//! `add_rope_scaling_alpha` is :356, and it is in `HunyuanVLTextModel`,
//! whose `model_arch` is `HUNYUAN_VL`, a different GGUF architecture
//! string and a different (still-refusing) ferrox row.
//!
//! So for every converter-produced `hunyuan-dense` file this function
//! returns the base unchanged, and the rescale exists for the case
//! llama.cpp will still honour: a file that does carry the key. That is
//! not a gate that cannot fire -- it is arithmetic that must agree with
//! llama.cpp's when the key is there, and
//! `tests/one_match_arm_graphs.rs` drives it out of a fixture that
//! carries `hunyuan-dense.rope.scaling.alpha` and compares against
//! libllama's own logits.
/// Architectures whose `load_arch_hparams` applies
/// `{arch}.rope.scaling.alpha` to the trained RoPE base.
///
/// `hunyuan-vl` is here for completeness of the reading even though it
/// resolves to a deferred multimodal row rather than the generic
/// decoder: leaving it out would make the list disagree with
/// `models.h:1830`, where `hunyuan-dense` inherits the behaviour FROM
/// it.
pub const NTK_ALPHA_RESCALED_ROPE_BASE: & = &;
/// llama.cpp's `hunyuan-vl.cpp:8-12`, for the base this architecture
/// should rotate at.
///
/// Returns `base` unchanged when the architecture does not apply the
/// rescale, when the key is absent, or when the declared alpha is not
/// positive -- llama.cpp's own `> 0.0f` guard, and the reason a default
/// of `0.0` is a no-op rather than a collapse to zero frequency.
///
/// `head_dim` is `hparams.n_embd_head_k()`, which ferrox carries as
/// `ModelConfig::head_dim`. A head_dim of 2 or less would divide by zero
/// or negate the exponent; llama.cpp has no guard because no attention
/// head is that narrow, and this returns the base unchanged rather than
/// producing an infinity.