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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
#pragma once
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/// Coarse error-code categories returned by `baseRT_get_error_code()`.
///
/// `baseRT_get_error()` always carries the full human-readable message;
/// the code lets callers branch on failure mode without parsing strings.
/// Categories are intentionally coarse — adding members is a minor
/// version bump, repurposing a value is a major bump. Untriaged failures
/// land in `BASERT_ERR_UNKNOWN`.
typedef enum {
BASERT_OK = 0,
BASERT_ERR_UNKNOWN = 1,
BASERT_ERR_INVALID_ARGUMENT = 2, // caller-side bug (null, out-of-range, bad shape)
BASERT_ERR_FILE_NOT_FOUND = 3, // model_path / kernel_library_path missing
BASERT_ERR_INVALID_FORMAT = 4, // file present but failed format checks
BASERT_ERR_UNSUPPORTED = 5, // recognized but not yet implemented
BASERT_ERR_OUT_OF_MEMORY = 6,
BASERT_ERR_GPU = 7, // Metal device / pipeline failure
BASERT_ERR_GENERATION_FAILED = 8, // mid-run failure (NaN, dispatch error, …)
} BaseRTErrorCode;
/// Model configuration extracted from weight file metadata.
///
/// ABI NOTE (0.2.4): this struct's layout CHANGED. New family support landed
/// its fields beside the ones they belong with (`ssm_*` next to `gdn_*`, MLA
/// and gpt-oss fields beside the attention block) rather than appended, so
/// every offset after the first insertion moved, and the struct grew. That is
/// a deliberate readability choice under the pre-1.0 clause in baseRT.h ("the
/// above is intent, not contract"), not an oversight — but it means a caller
/// built against 0.2.3 MUST be recompiled against this header. Note that
/// appending would not have been sufficient on its own either:
/// `baseRT_get_config` returns this struct BY VALUE, so growth alone
/// overruns an older caller's return slot whatever the field order.
///
/// Mismatches are detectable rather than silent: `baseRT_model_config_sizeof()`
/// reports the library's own `sizeof`, and the language bindings compare it
/// against their mirrored definition at load time. Check it if you bind to
/// this struct from outside the tree.
typedef struct {
// Decoder (or decoder-only LLM) parameters
uint32_t dim; // embedding dimension
uint32_t n_layers; // transformer layers (decoder layers for enc-dec)
uint32_t n_heads; // query heads
uint32_t n_kv_heads; // key/value heads (GQA)
uint32_t head_dim; // dim per head
uint32_t q_dim; // total Q projection output (n_heads * head_dim)
uint32_t kv_dim; // total KV projection output (n_kv_heads * head_dim)
uint32_t ffn_dim; // feed-forward intermediate dimension
uint32_t vocab_size; // vocabulary size
uint32_t max_seq_len; // maximum context length
float norm_eps; // RMSNorm/LayerNorm epsilon
float rope_theta; // RoPE base frequency (0 = no RoPE)
uint32_t sliding_window_pattern; // 0 = all global or use swa_layers bitfield, 6 = every 6th layer global (Gemma 3)
uint32_t sliding_window; // 0 = full context, >0 = sliding window size for SWA layers
float rope_local_theta; // RoPE theta for local/sliding-window layers (0 = same as rope_theta)
char architecture[32]; // "llama", "qwen3", "gemma", "gemma3", "gemma4", "whisper"
// Encoder parameters (0 = decoder-only model)
uint32_t enc_n_layers; // encoder transformer layers
uint32_t enc_n_heads; // encoder attention heads
uint32_t enc_dim; // encoder embedding dimension (may equal dim)
uint32_t enc_ffn_dim; // encoder feed-forward dimension
uint32_t n_mels; // mel spectrogram bins (80/128, 0 = not audio model)
uint32_t enc_max_seq_len; // encoder max positions (1500 for 30s Whisper)
// Gemma 4-specific fields (0 / empty = not applicable)
uint32_t n_embd_per_layer; // per-layer embedding dim (PLE), 0 = no PLE
uint32_t n_layer_kv_from_start; // first N layers own KV; [n_layer_kv_from_start, n_layers) reuse earlier cache. 0
// = all own KV
float logit_softcap; // final logit softcap: (x/cap).tanh()*cap, 0 = disabled
float attention_scale; // explicit attention scale (0 = derive 1/sqrt(head_dim))
uint32_t head_dim_swa; // per-layer head_dim for SWA layers (0 = uniform, use head_dim)
uint32_t head_dim_global; // per-layer head_dim for global (full-attention) layers
// Partial-rotary factor for global (full-attention) layers. 0 or 1 = full rotation.
// Gemma 4 26B-A4B / 31B: 0.25 — only the first 25% of head_dim_global pairs rotate,
// the remainder stay unchanged. On GGUF this is encoded via `rope_freqs.weight`
// (factors of 1.0 for the rotating pairs, ~1e30 for the rest). MLX safetensors don't
// ship that tensor, so we parse this field from the HF config and synthesize the
// divisor buffer at forward-pass time.
float global_rope_partial_factor;
// Per-layer SWA pattern as packed bitfield (bit=1 means SWA/local, bit=0 means global).
// Supports up to 512 layers (64 bytes). Bit index i = layer i.
// If sliding_window_pattern is nonzero, this bitfield is ignored (legacy Gemma 3 path).
uint8_t swa_layers[64];
// Per-layer FFN dimensions. Non-zero entries override cfg.ffn_dim for that layer.
// E2B uses 6144 for SWA layers and 12288 for global layers.
// When all zeros, the uniform cfg.ffn_dim applies to all layers.
uint32_t ffn_dims[128];
// Per-layer number of KV heads. Non-zero entries override cfg.n_kv_heads.
// Gemma 4 26B-A4B: 8 for SWA layers, 2 for Global layers.
// Gemma 4 31B: 16 for SWA layers, 4 for Global layers.
// E4B / E2B: all zeros (uniform cfg.n_kv_heads applies).
uint32_t n_kv_heads_per_layer[128];
// ── Qwen3.5 / 3.6 hybrid linear-attention (Gated DeltaNet) ───────
// 0 / empty = not a hybrid model. Qwen3.5 interleaves Gated-DeltaNet
// linear-attention layers with periodic full (softmax) attention
// (reuses the Qwen3-Next decoder). full_attention_interval / the
// linear_attn_layers bitfield select which layers are which.
// Full-attention layers additionally use an output gate + partial RoPE.
uint8_t attn_output_gate; // 1 = full-attn q_proj is doubled [q|gate]; attn *= sigmoid(gate) pre-o_proj
uint8_t _qwen35_pad[3]; // align to 4 bytes
float partial_rotary_factor; // full-attn: only first factor*head_dim dims rotate (Qwen3.5 = 0.25). 0/1 = full
uint32_t full_attention_interval; // every Nth layer is full attention (rest are GDN). 0 = not hybrid
// Per-layer GDN mask: bit i = 1 means layer i is a Gated-DeltaNet
// linear-attention layer; bit 0 = full (softmax) attention. Authoritative
// copy of the config `layer_types` schedule. Up to 512 layers (64 bytes).
uint8_t linear_attn_layers[64];
// Gated-DeltaNet shapes (0 = not hybrid). Consumed by the GDN kernel.
uint32_t gdn_num_k_heads; // linear_num_key_heads
uint32_t gdn_num_v_heads; // linear_num_value_heads
uint32_t gdn_key_head_dim; // linear_key_head_dim
uint32_t gdn_value_head_dim; // linear_value_head_dim
uint32_t gdn_conv_kernel; // linear_conv_kernel_dim (short causal conv width)
// ── Nemotron-H hybrid Mamba-2 SSM (0 = not a Mamba-2 hybrid) ─────
// Per-layer schedule: linear_attn_layers bit set = Mamba-2 layer,
// n_kv_heads_per_layer[il] > 0 = attention layer, otherwise (MoE) FFN.
// The gdn_* fields above are additionally aliased at load so the shared
// GDNStateCache sizes the SSM state pool: nk=ssm_num_groups,
// khd=ssm_state_size, nv=ssm_num_heads, vhd=ssm_inner/ssm_heads.
uint32_t ssm_state_size; // per-head state width N (Nemotron 3 Nano: 128)
uint32_t ssm_conv_kernel; // depthwise causal conv taps (4)
uint32_t ssm_num_groups; // B/C groups (8)
uint32_t ssm_inner_size; // heads * head_dim (4096)
uint32_t ssm_num_heads; // SSM heads (64)
// Mixture-of-Experts (0 = dense model).
// Gemma 4 26B-A4B: n_experts=128, n_experts_used=8, n_experts_shared=1 (via dense ffn.*), expert_gating=0
// (softmax), norm_topk_prob=0 Qwen3.6-35B-A3B (qwen35moe): n_experts=128, n_experts_used=8, n_experts_shared=0,
// expert_gating=0 (softmax), norm_topk_prob=1
uint32_t n_experts; // total routed experts; 0 = dense model (MoE disabled)
uint32_t n_experts_used; // top-k experts per token
uint32_t n_experts_shared; // shared (always-on) experts; Gemma 4 = 1 via dense ffn.*, Qwen = 0
uint32_t expert_ffn_dim; // per-expert FFN intermediate dim
uint8_t expert_gating; // 0 = softmax, 1 = sigmoid
uint8_t norm_topk_prob; // 1 = renormalize top-k weights to sum to 1 (Qwen), 0 = leave as-is (Gemma)
uint8_t _moe_pad[2]; // align to 4 bytes
// Routed-expert output scale (DeepSeek/Nemotron routed_scaling_factor;
// applied to the renormalized top-k weights). 0 = disabled (treat as 1).
float expert_weights_scale;
// Vision tower (Gemma 4, PaliGemma, Llava-style multimodal).
// All zero = no vision tower.
uint32_t vision_n_layers; // ViT encoder layers
uint32_t vision_dim; // ViT hidden size (e.g. 768)
uint32_t vision_n_heads; // ViT attention heads
uint32_t vision_head_dim; // per-head dim
uint32_t vision_ffn_dim; // ViT FFN intermediate
uint32_t vision_patch_size; // patch side length in pixels (e.g. 16)
uint32_t vision_image_size; // canonical input image side (e.g. 896)
uint32_t vision_pooling_kernel; // square pooling kernel after encoder (1 = no pool)
uint32_t vision_soft_tokens; // image tokens emitted per image (e.g. 280)
float vision_norm_eps;
float vision_rope_theta; // 0 = no RoPE in ViT
uint32_t vision_pos_embed_size; // learned positional embedding size (0 = none)
uint32_t image_token_id; // text-side token id used as a placeholder for image features
uint32_t boi_token_id; // begin-of-image token (0 = none)
uint32_t eoi_token_id; // end-of-image token (0 = none)
// ── Vision-tower family selector + Qwen3-VL-style extras ─────────
// vision_arch: 0 = SigLIP/Gemma-4 (pooled ViT + factorized pos-embed);
// 1 = Qwen3.5 / Qwen3-VL ViT (LayerNorm blocks, fused QKV,
// learned pos-embed bilinear interp, 2D M-RoPE, spatial
// merger instead of avg-pool). Populated from mmproj.arch.
uint32_t vision_arch; // 0 = siglip/gemma4, 1 = qwen3_5 / qwen3_vl
uint32_t vision_spatial_merge; // Qwen: spatial_merge_size (2 → 2x2 patch merge). 0 = n/a
uint32_t vision_temporal_patch; // Qwen: temporal_patch_size (frame is tiled this many times)
uint32_t vision_out_dim; // Qwen: merger out_hidden_size (== text dim). 0 = use cfg.dim
// Audio tower (Gemma 4 Conformer encoder). All zero = no audio tower.
uint32_t audio_n_layers; // Conformer blocks (e.g. 12)
uint32_t audio_dim; // hidden size (e.g. 1024)
uint32_t audio_n_heads; // attention heads (e.g. 8)
uint32_t audio_head_dim; // per-head dim (audio_dim / audio_n_heads)
uint32_t audio_ffn_dim; // FFN intermediate (4 * audio_dim)
uint32_t audio_output_proj_dim; // output proj to this dim (e.g. 1536), 0 = no proj
uint32_t audio_chunk_size; // chunked attention block size (e.g. 12)
uint32_t audio_left_context; // left context frames for attention (e.g. 13)
uint32_t audio_conv_kernel; // LightConv1d kernel size (e.g. 5)
uint32_t audio_soft_tokens; // max audio tokens per clip (e.g. 750)
float audio_logit_softcap; // attention logit cap (e.g. 50.0)
float audio_norm_eps; // RMSNorm epsilon
float audio_gradient_clip; // gradient clipping bound (e.g. 1e10)
float audio_residual_weight; // FFW residual scale (e.g. 0.5)
float audio_ms_per_token; // ms per output token (e.g. 40.0)
uint32_t audio_sscp_channels[2]; // subsampling conv channels (e.g. {128, 32})
uint32_t audio_token_id; // placeholder token for audio features
uint32_t boa_token_id; // begin-of-audio token (0 = none)
uint32_t eoa_token_id; // end-of-audio token (0 = none)
// ── Multimodal RoPE (Qwen3.5-VL interleaved M-RoPE) ──────────────
// Rotary-pair split over the [temporal, height, width] axes for the
// full-attention layers of VL models (Qwen3.5/3.6-VL: [11, 11, 10],
// summing to n_rot/2). All-zero = plain 1-D RoPE (text-only model or a
// .base converted before the converter recorded the key — the runtime
// falls back to [11, 11, 10] on the image path then).
uint32_t mrope_section[3];
uint8_t mrope_interleaved; // 1 = interleaved axis map THWTHW…TT (Qwen3.5), 0 = sectioned/absent
uint8_t _mrope_pad[3]; // align to 4 bytes
// RoPE frequency scaling (HF `rope_scaling`, "llama3" type). factor <= 1
// means none. The low/high freq factors and original max position
// parameterize the piecewise per-frequency rescale; when factor > 1 and
// the detail fields are 0 the runtime falls back to the Llama-3-family
// defaults (low 1.0, high 4.0, original 8192).
float rope_scaling_factor; // e.g. 32 on Llama-3.2 (1 or 0 = unscaled)
float rope_low_freq_factor; // wavelengths above orig_max/low rescale by 1/factor
float rope_high_freq_factor; // wavelengths below orig_max/high stay unscaled
uint32_t rope_orig_max_pos; // original_max_position_embeddings (e.g. 8192)
// rope_scaling type: 0 = none/legacy header (llama arch + factor > 1
// defaults to llama3 — correct for every published llama-family .base),
// 1 = llama3 (piecewise), 2 = linear (uniform divisor), 3 = other /
// unsupported (skipped with a warning rather than mis-applied).
uint32_t rope_scaling_type;
// ── Muse Glimmer (0 / empty = not applicable) ────────────────────
// Dense SWA/global decoder with a perception (ViT) tower. Shaped like
// Gemma 3 (four zero-centered per-layer norms, final logit softcap) with
// four twists the fields below carry.
//
// Multiplier applied to Q AFTER a scaleless (weightless) per-head
// RMSNorm, ON TOP OF the usual 1/sqrt(head_dim) — unlike
// `attention_scale`, which REPLACES that term. 0 = no QK-norm/scale.
// Because the norm is weightless there are no q_norm/k_norm tensors.
float qk_scale_factor;
// Scale applied to the logits BEFORE the final tanh softcap
// (`logit_softcap`). 0 = no multiplier.
float output_multiplier;
// Epsilon for the post-attention / post-FFN norms, which differs from
// `norm_eps` here (1e-8 vs 1e-5). 0 = reuse `norm_eps` everywhere.
float post_norm_eps;
// Per-layer NoPE mask: bit i = 1 means layer i applies NO rotary at all
// (the reference passes `position_embeddings=None` there). On Muse
// Glimmer these are exactly the global/full-attention layers, so the
// mask is the complement of `swa_layers` — but it is stored
// independently because the two are separate config keys upstream
// (`layer_types` vs `layer_rope_theta`) and need not agree.
// Up to 512 layers (64 bytes). All zero = every layer gets RoPE.
uint8_t nope_layers[64];
// Epsilon for a weightless RMSNorm the runtime applies to the token
// embedding right after lookup. 0 = already folded into the embedding
// rows at convert time (the HF path does this exactly), or no such norm.
// Only the GGUF path sets it: k-quant passthrough keeps the rows packed,
// so folding would mean dequantizing them.
float embed_norm_eps;
// Perception-tower extras (vision_arch == 2). The tower is a LayerNorm
// ViT with SEPARATE biased q/k/v projections, alternating window/full
// attention, a bias-free Linear patch embed over flattened pixels, and a
// bilinearly-resampled learned position grid.
// Per-layer window-attention mask: bit i = 1 means layer i attends only
// within its window; 0 = full attention across all patches.
uint8_t vision_window_layers[64];
uint32_t vision_window_size; // window side in PIXELS (pos_emb_height * patch_size, e.g. 448)
uint32_t vision_pos_embed_h; // learned position grid height (e.g. 32)
uint32_t vision_pos_embed_w; // learned position grid width (e.g. 32)
uint32_t vision_adapter_dim; // projector hidden width (`projector_hidden_size`, e.g. 4096)
uint32_t video_token_id; // text-side placeholder token for video features (0 = none)
// ── GLM 5.2 / glm-dsa: Multi-head Latent Attention + MoE ─────────
// DeepSeek-V3.2-style decoder. All zero = not an MLA model. The
// struct's uniform head_dim can't express MLA's compressed/asymmetric
// Q/K/V dims, so the MLA encoder (src/core/models/glm_dsa.cpp) reads
// these explicit fields instead. See arch_descriptor.h is_mla flag.
uint32_t q_lora_rank; // MLA query compression rank (attn_q_a output); 0 = not MLA
uint32_t kv_lora_rank; // MLA KV latent rank (attn_kv_a_mqa kv part); K cache row = kv_lora_rank + qk_rope_head_dim
uint32_t qk_nope_head_dim; // per-head non-positional Q/K dim (attends in latent space via k_b)
uint32_t qk_rope_head_dim; // per-head decoupled-RoPE Q/K dim (the only rotated part; NORM/GPT-J rope)
uint32_t v_head_dim; // per-head value dim after v_b up-projection
float routed_scaling_factor; // routed-expert output scale (DeepSeek routed_scaling_factor); 0 = none
uint32_t first_k_dense_replace; // first N layers use a dense SwiGLU FFN instead of MoE (GLM 5.2 = 3)
uint32_t nextn_predict_layers; // Multi-Token-Prediction head layers (dropped at convert; recorded only)
// DSA lightning-indexer geometry. Loaded but UNUSED by the dense MLA
// path (llama.cpp's glm-dsa never runs the indexer at any context
// length). Kept for a future true-sparse-attention path. 0 = none.
uint32_t indexer_head_count;
uint32_t indexer_key_length;
uint32_t indexer_top_k;
// ── gpt-oss (0 / empty = not applicable) ─────────────────────────
// YaRN correction-range betas (HF `rope_scaling.beta_fast` / `beta_slow`);
// used with rope_scaling_type == 4 (yarn). 0 = the YaRN defaults (32 / 1).
float rope_yarn_beta_fast;
float rope_yarn_beta_slow;
// Clamped SwiGLU: gate = min(gate, limit), up = clamp(up, -limit, limit),
// act = (up + 1) * gate * sigmoid(alpha * gate). limit 0 = plain SwiGLU.
float swiglu_limit;
float swiglu_alpha;
uint8_t attention_sinks; // 1 = learned per-head sink logits (`layers.N.attention.sinks`)
uint8_t attention_bias; // 1 = q/k/v/o projections carry biases
uint8_t rope_yarn_truncate; // 1 = floor/ceil the YaRN correction range (HF default); gpt-oss: 0
uint8_t _gptoss_pad; // align to 4 bytes
} BaseRTModelConfig;
/// Transcription result statistics.
typedef struct {
int n_tokens; // output tokens generated
float audio_ms; // mel spectrogram computation time
float encode_ms; // encoder forward pass time
float decode_ms; // decoder generation time
float total_ms; // total wall time
} BaseRTTranscribeStats;
/// Sampling configuration for text generation.
///
/// Fields are appended at the end; older callers that brace-init only the
/// first five values still build (new fields zero-init to "disabled"). The
/// extended fields below mirror OpenAI's chat-completion params:
///
/// * presence_penalty — subtract this from logits of tokens that have
/// appeared at least once in the trailing history window. Disabled at 0.
/// * frequency_penalty — subtract `freq * count(token)` for each token's
/// occurrence count in the trailing window. Disabled at 0.
/// * seed — RNG seed for sampling. 0 = wall-clock-seeded
/// (non-deterministic); non-zero re-seeds the thread-local RNG before
/// generation begins.
/// * logit_bias_* — additive per-token logit bias. Parallel arrays:
/// `logit_bias_tokens[i]` gets `+ logit_bias_values[i]` added to its
/// logit each step. Pointers must remain valid for the duration of the
/// generate call; `n_logit_bias = 0` disables.
typedef struct {
float temperature; // 0 = greedy
int top_k;
float top_p;
float min_p;
float repeat_penalty;
float presence_penalty;
float frequency_penalty;
uint32_t seed;
int32_t n_logit_bias;
const int32_t *logit_bias_tokens;
const float *logit_bias_values;
} BaseRTSamplingConfig;
/// Generation result statistics.
typedef struct {
int prompt_tokens;
int generated_tokens;
float prefill_time_ms;
float decode_time_ms;
float prefill_tokens_per_sec;
float decode_tokens_per_sec;
} BaseRTGenerationStats;
/// Per-load configuration for baseRT_load_model_ex. Replaces the process-wide
/// baseRT_set_* pre-load setters with values scoped to one load call, so a
/// lazily-reloading host (e.g. a server's model registry) doesn't depend on
/// globals set once at startup. Zero-initialize (`BaseRTLoadOptions opts = {0}`
/// / value-init) and set `struct_size = sizeof(BaseRTLoadOptions)`; every
/// zero field keeps today's default, so a zeroed struct behaves exactly like
/// plain baseRT_load_model with no setters called.
typedef struct {
size_t struct_size; ///< must be sizeof(BaseRTLoadOptions); ABI guard
int kv_bits; ///< 0 default, or 4 / 8 / 16 / 84 (84 = K@Q8, V@Q4)
int paged_kv; ///< 0 default (off), 1 on
int max_batch_size; ///< 0 default (1), else >= 1 decode lanes
int prefix_cache; ///< 0 default (off), 1 on (needs paged_kv)
int prefill_chunk; ///< 0 per-chip default, else tokens per prefill pass
int paged_weights_mode; ///< 0 auto-retry on GPU-OOM, 1 force paged, 2 fail hard
} BaseRTLoadOptions;
#ifdef __cplusplus
}
#endif