Skip to main content

baseRT_sys/
lib.rs

1//! Raw FFI bindings for the BaseRT C API.
2//!
3//! These are unsafe, low-level bindings. Prefer the `baseRT` crate for safe usage.
4
5#![allow(non_camel_case_types)]
6
7use std::os::raw::{c_char, c_float, c_int, c_void};
8
9/// Opaque model handle.
10pub type baseRT_model_t = *mut c_void;
11
12/// Model configuration extracted from weight file metadata.
13///
14/// This mirrors the FULL `BaseRTModelConfig` in include/baseRT/types.h. The
15/// struct is returned BY VALUE from `baseRT_get_config`, so it MUST match the C
16/// layout exactly — a truncated copy makes the C side write past the Rust
17/// allocation (memory corruption). Earlier revisions of this binding dropped
18/// `sliding_window` and every field after the encoder block; do not reintroduce
19/// that. New fields in C are appended at the end (`#[repr(C)]` keeps offsets).
20#[repr(C)]
21#[derive(Debug, Clone, Copy)]
22pub struct BaseRTModelConfig {
23    // Decoder (or decoder-only LLM) parameters
24    pub dim: u32,
25    pub n_layers: u32,
26    pub n_heads: u32,
27    pub n_kv_heads: u32,
28    pub head_dim: u32,
29    pub q_dim: u32,
30    pub kv_dim: u32,
31    pub ffn_dim: u32,
32    pub vocab_size: u32,
33    pub max_seq_len: u32,
34    pub norm_eps: c_float,
35    pub rope_theta: c_float,
36    pub sliding_window_pattern: u32,
37    pub sliding_window: u32,
38    pub rope_local_theta: c_float,
39    pub architecture: [c_char; 32],
40
41    // Encoder parameters (0 = decoder-only model)
42    pub enc_n_layers: u32,
43    pub enc_n_heads: u32,
44    pub enc_dim: u32,
45    pub enc_ffn_dim: u32,
46    pub n_mels: u32,
47    pub enc_max_seq_len: u32,
48
49    // Gemma 4-specific fields
50    pub n_embd_per_layer: u32,
51    pub n_layer_kv_from_start: u32,
52    pub logit_softcap: c_float,
53    pub attention_scale: c_float,
54    pub head_dim_swa: u32,
55    pub head_dim_global: u32,
56    pub global_rope_partial_factor: c_float,
57    pub swa_layers: [u8; 64],
58    pub ffn_dims: [u32; 128],
59    pub n_kv_heads_per_layer: [u32; 128],
60
61    // Qwen3.5 / 3.6 hybrid linear-attention (Gated DeltaNet)
62    pub attn_output_gate: u8,
63    pub _qwen35_pad: [u8; 3],
64    pub partial_rotary_factor: c_float,
65    pub full_attention_interval: u32,
66    pub linear_attn_layers: [u8; 64],
67    pub gdn_num_k_heads: u32,
68    pub gdn_num_v_heads: u32,
69    pub gdn_key_head_dim: u32,
70    pub gdn_value_head_dim: u32,
71    pub gdn_conv_kernel: u32,
72
73    // Nemotron-H hybrid Mamba-2 SSM
74    pub ssm_state_size: u32,
75    pub ssm_conv_kernel: u32,
76    pub ssm_num_groups: u32,
77    pub ssm_inner_size: u32,
78    pub ssm_num_heads: u32,
79
80    // Mixture-of-Experts (0 = dense)
81    pub n_experts: u32,
82    pub n_experts_used: u32,
83    pub n_experts_shared: u32,
84    pub expert_ffn_dim: u32,
85    pub expert_gating: u8,
86    pub norm_topk_prob: u8,
87    pub _moe_pad: [u8; 2],
88    pub expert_weights_scale: c_float,
89
90    // Vision tower (all zero = none)
91    pub vision_n_layers: u32,
92    pub vision_dim: u32,
93    pub vision_n_heads: u32,
94    pub vision_head_dim: u32,
95    pub vision_ffn_dim: u32,
96    pub vision_patch_size: u32,
97    pub vision_image_size: u32,
98    pub vision_pooling_kernel: u32,
99    pub vision_soft_tokens: u32,
100    pub vision_norm_eps: c_float,
101    pub vision_rope_theta: c_float,
102    pub vision_pos_embed_size: u32,
103    pub image_token_id: u32,
104    pub boi_token_id: u32,
105    pub eoi_token_id: u32,
106
107    // Vision-tower family selector + Qwen3-VL-style extras
108    pub vision_arch: u32,
109    pub vision_spatial_merge: u32,
110    pub vision_temporal_patch: u32,
111    pub vision_out_dim: u32,
112
113    // Audio tower (all zero = none)
114    pub audio_n_layers: u32,
115    pub audio_dim: u32,
116    pub audio_n_heads: u32,
117    pub audio_head_dim: u32,
118    pub audio_ffn_dim: u32,
119    pub audio_output_proj_dim: u32,
120    pub audio_chunk_size: u32,
121    pub audio_left_context: u32,
122    pub audio_conv_kernel: u32,
123    pub audio_soft_tokens: u32,
124    pub audio_logit_softcap: c_float,
125    pub audio_norm_eps: c_float,
126    pub audio_gradient_clip: c_float,
127    pub audio_residual_weight: c_float,
128    pub audio_ms_per_token: c_float,
129    pub audio_sscp_channels: [u32; 2],
130    pub audio_token_id: u32,
131    pub boa_token_id: u32,
132    pub eoa_token_id: u32,
133    pub mrope_section: [u32; 3],
134    pub mrope_interleaved: u8,
135    pub _mrope_pad: [u8; 3],
136    pub rope_scaling_factor: c_float,
137    pub rope_low_freq_factor: c_float,
138    pub rope_high_freq_factor: c_float,
139    pub rope_orig_max_pos: u32,
140    pub rope_scaling_type: u32,
141    // Muse Glimmer (0 / empty = not applicable)
142    pub qk_scale_factor: c_float,
143    pub output_multiplier: c_float,
144    pub post_norm_eps: c_float,
145    pub nope_layers: [u8; 64],
146    pub embed_norm_eps: c_float,
147    pub vision_window_layers: [u8; 64],
148    pub vision_window_size: u32,
149    pub vision_pos_embed_h: u32,
150    pub vision_pos_embed_w: u32,
151    pub vision_adapter_dim: u32,
152    pub video_token_id: u32,
153    // GLM 5.2 / glm-dsa
154    pub q_lora_rank: u32,
155    pub kv_lora_rank: u32,
156    pub qk_nope_head_dim: u32,
157    pub qk_rope_head_dim: u32,
158    pub v_head_dim: u32,
159    pub routed_scaling_factor: c_float,
160    pub first_k_dense_replace: u32,
161    pub nextn_predict_layers: u32,
162    pub indexer_head_count: u32,
163    pub indexer_key_length: u32,
164    pub indexer_top_k: u32,
165    // gpt-oss
166    pub rope_yarn_beta_fast: f32,
167    pub rope_yarn_beta_slow: f32,
168    pub swiglu_limit: f32,
169    pub swiglu_alpha: f32,
170    pub attention_sinks: u8,
171    pub attention_bias: u8,
172    pub rope_yarn_truncate: u8,
173    pub _gptoss_pad: u8,
174}
175
176/// Transcription result statistics.
177#[repr(C)]
178#[derive(Debug, Clone, Copy, Default)]
179pub struct BaseRTTranscribeStats {
180    pub n_tokens: c_int,
181    pub audio_ms: c_float,
182    pub encode_ms: c_float,
183    pub decode_ms: c_float,
184    pub total_ms: c_float,
185}
186
187/// One segment of the last transcription (verbose_json surface).
188/// `text` is borrowed from the engine — valid until the next transcription
189/// or model free.
190#[repr(C)]
191#[derive(Debug, Clone, Copy)]
192pub struct BaseRTTranscribeSegment {
193    pub start_ms: c_int,
194    pub end_ms: c_int,
195    pub text: *const c_char,
196    pub avg_logprob: c_float,
197    pub no_speech_prob: c_float,
198    pub compression_ratio: c_float,
199    pub temperature: c_float,
200}
201
202impl Default for BaseRTTranscribeSegment {
203    fn default() -> Self {
204        Self {
205            start_ms: 0,
206            end_ms: 0,
207            text: std::ptr::null(),
208            avg_logprob: 0.0,
209            no_speech_prob: 0.0,
210            compression_ratio: 0.0,
211            temperature: 0.0,
212        }
213    }
214}
215
216/// Sampling configuration for text generation.
217///
218/// Extended in baseRT 0.2 with OpenAI-compat presence / frequency penalties,
219/// a deterministic-sample `seed`, and a per-token `logit_bias` map. New
220/// fields are appended; older callers using `Default::default()` keep the
221/// classic five values plus zeroed extensions (disabled).
222#[repr(C)]
223#[derive(Debug, Clone, Copy)]
224pub struct BaseRTSamplingConfig {
225    pub temperature: c_float,
226    pub top_k: c_int,
227    pub top_p: c_float,
228    pub min_p: c_float,
229    pub repeat_penalty: c_float,
230    pub presence_penalty: c_float,
231    pub frequency_penalty: c_float,
232    pub seed: u32,
233    pub n_logit_bias: i32,
234    pub logit_bias_tokens: *const i32,
235    pub logit_bias_values: *const c_float,
236}
237
238impl Default for BaseRTSamplingConfig {
239    fn default() -> Self {
240        Self {
241            temperature: 0.0,
242            top_k: 40,
243            top_p: 0.9,
244            min_p: 0.0,
245            repeat_penalty: 1.0,
246            presence_penalty: 0.0,
247            frequency_penalty: 0.0,
248            seed: 0,
249            n_logit_bias: 0,
250            logit_bias_tokens: std::ptr::null(),
251            logit_bias_values: std::ptr::null(),
252        }
253    }
254}
255
256/// Generation result statistics.
257#[repr(C)]
258#[derive(Debug, Clone, Copy, Default)]
259pub struct BaseRTGenerationStats {
260    pub prompt_tokens: c_int,
261    pub generated_tokens: c_int,
262    pub prefill_time_ms: c_float,
263    pub decode_time_ms: c_float,
264    pub prefill_tokens_per_sec: c_float,
265    pub decode_tokens_per_sec: c_float,
266}
267
268/// Opaque per-sequence handle (independent KV state on the model's shared
269/// paged-KV pool). Mirrors `baseRT_sequence_t` in baseRT.h.
270pub type baseRT_sequence_t = *mut c_void;
271
272/// Result of a prefix-cache lookup (`baseRT_prefix_match`). `blocks` points
273/// into engine-owned storage valid until `baseRT_prefix_unlock(handle)` /
274/// `baseRT_prefix_release(handle)`. `handle == 0` means miss / cache
275/// disabled — nothing to unlock.
276#[repr(C)]
277#[derive(Debug, Clone, Copy)]
278pub struct BaseRTPrefixMatch {
279    pub matched_tokens: c_int,
280    pub n_blocks: c_int,
281    pub blocks: *const c_int,
282    pub handle: u64,
283}
284
285/// Callback for streaming token output. Return `false` to stop generation.
286pub type baseRT_token_callback = Option<
287    unsafe extern "C" fn(token_id: u32, text: *const c_char, user_data: *mut c_void) -> bool,
288>;
289
290/// Callback for streaming transcription segments. Return `false` to stop transcription.
291pub type baseRT_segment_callback = Option<
292    unsafe extern "C" fn(
293        start_ms: c_int,
294        end_ms: c_int,
295        text: *const c_char,
296        user_data: *mut c_void,
297    ) -> bool,
298>;
299
300extern "C" {
301    // === Model lifecycle ===
302
303    pub fn baseRT_load_model(
304        model_path: *const c_char,
305        kernel_library_path: *const c_char,
306        max_context: c_int,
307    ) -> baseRT_model_t;
308
309    pub fn baseRT_free_model(model: baseRT_model_t);
310    /// Tokenizer-only load: metadata + vocab, no GPU tensors. Only the
311    /// tokenizer surface is valid on the returned handle.
312    pub fn baseRT_load_tokenizer_only(model_path: *const c_char) -> baseRT_model_t;
313
314    // === Model info ===
315
316    pub fn baseRT_get_config(model: baseRT_model_t) -> BaseRTModelConfig;
317    pub fn baseRT_model_config_sizeof() -> usize;
318    pub fn baseRT_model_memory(model: baseRT_model_t) -> usize;
319    /// Device memory budget in bytes (Metal: recommended working set; CUDA:
320    /// total device memory). 0 when no supported device is present.
321    pub fn baseRT_device_memory_budget() -> usize;
322    /// A `max_context` sized for this device and this bundle. Metadata-only,
323    /// so it is safe to call before loading. 0 = could not size.
324    pub fn baseRT_suggest_max_context(
325        model_path: *const c_char,
326        max_batch: c_int,
327        kv_bits: c_int,
328        paged_kv: c_int,
329    ) -> c_int;
330    /// As above for a set of models that will be resident at the same time:
331    /// weights and KV pools add up, and the trained-window cap is the
332    /// shortest among them.
333    pub fn baseRT_suggest_max_context_multi(
334        model_paths: *const *const c_char,
335        n_models: c_int,
336        max_batch: c_int,
337        kv_bits: c_int,
338        paged_kv: c_int,
339    ) -> c_int;
340    /// As above, plus the speculators loading beside those models:
341    /// `speculator_embedded[i]` non-zero is the target bundle's embedded head
342    /// (its KV pool only); zero (or a null array) a sidecar / draft model
343    /// (weights and KV pool), even when also served. `speculator_target[i]`
344    /// >= 0 is the index of the model a sidecar's baseRT_load_drafter reopens
345    /// for its embedding / lm_head (charged where the backend copies wrapped
346    /// weights); -1 (or a null array) for none. Speculators never cap the
347    /// window.
348    pub fn baseRT_suggest_max_context_spec(
349        model_paths: *const *const c_char,
350        n_models: c_int,
351        speculator_paths: *const *const c_char,
352        speculator_embedded: *const c_int,
353        speculator_target: *const c_int,
354        n_speculators: c_int,
355        max_batch: c_int,
356        kv_bits: c_int,
357        paged_kv: c_int,
358    ) -> c_int;
359    /// 1 = the window fits the co-resident set, 0 = it does not, -1 = unknown.
360    pub fn baseRT_context_window_fits(
361        model_paths: *const *const c_char,
362        n_models: c_int,
363        max_batch: c_int,
364        kv_bits: c_int,
365        paged_kv: c_int,
366        window: c_int,
367    ) -> c_int;
368    pub fn baseRT_get_error() -> *const c_char;
369
370    // === Tokenization ===
371
372    pub fn baseRT_encode(
373        model: baseRT_model_t,
374        text: *const c_char,
375        out_tokens: *mut u32,
376        max_tokens: c_int,
377    ) -> c_int;
378    /// Special-token strings in `text` are ordinary text (client content),
379    /// never control ids. See the header.
380    pub fn baseRT_encode_plain(
381        model: baseRT_model_t,
382        text: *const c_char,
383        out_tokens: *mut u32,
384        max_tokens: c_int,
385    ) -> c_int;
386    /// `n_pieces` texts encoded as one: piece `i` parses marker strings
387    /// when `plain[i]` is zero and keeps them as text otherwise, with the
388    /// pre-tokenizer seeing the concatenation. See the header.
389    pub fn baseRT_encode_pieces(
390        model: baseRT_model_t,
391        texts: *const *const c_char,
392        plain: *const c_int,
393        n_pieces: c_int,
394        out_tokens: *mut u32,
395        max_tokens: c_int,
396    ) -> c_int;
397
398    pub fn baseRT_decode_token(model: baseRT_model_t, token_id: u32) -> *const c_char;
399    /// Length-preserving stateless decode (byte-level tokens can contain
400    /// NUL); returns the true byte length, which may exceed `max_bytes`.
401    pub fn baseRT_decode_token_raw(
402        model: baseRT_model_t,
403        token_id: u32,
404        out: *mut c_char,
405        max_bytes: c_int,
406    ) -> c_int;
407
408    // === Generation ===
409
410    pub fn baseRT_generate(
411        model: baseRT_model_t,
412        prompt_tokens: *const u32,
413        n_prompt: c_int,
414        max_tokens: c_int,
415        sampling: BaseRTSamplingConfig,
416        callback: baseRT_token_callback,
417        user_data: *mut c_void,
418    ) -> BaseRTGenerationStats;
419
420    pub fn baseRT_generate_continue(
421        model: baseRT_model_t,
422        new_tokens: *const u32,
423        n_new: c_int,
424        max_tokens: c_int,
425        sampling: BaseRTSamplingConfig,
426        callback: baseRT_token_callback,
427        user_data: *mut c_void,
428    ) -> BaseRTGenerationStats;
429
430    // === Low-level API ===
431
432    pub fn baseRT_prefill(model: baseRT_model_t, tokens: *const u32, n_tokens: c_int) -> u32;
433
434    pub fn baseRT_decode_step(model: baseRT_model_t, token_id: u32, position: c_int) -> u32;
435
436    pub fn baseRT_chain_decode(
437        model: baseRT_model_t,
438        first_token: u32,
439        start_position: c_int,
440        count: c_int,
441        out_tokens: *mut u32,
442    ) -> c_int;
443
444    pub fn baseRT_get_position(model: baseRT_model_t) -> c_int;
445
446    pub fn baseRT_set_speculation(model: baseRT_model_t, enabled: bool);
447
448    pub fn baseRT_reset(model: baseRT_model_t);
449
450    // === Whisper transcription ===
451
452    pub fn baseRT_transcribe(
453        model: baseRT_model_t,
454        wav_path: *const c_char,
455        language: *const c_char,
456        stats_out: *mut BaseRTTranscribeStats,
457    ) -> *const c_char;
458
459    pub fn baseRT_transcribe_pcm(
460        model: baseRT_model_t,
461        samples: *const c_float,
462        n_samples: c_int,
463        language: *const c_char,
464        stats_out: *mut BaseRTTranscribeStats,
465    ) -> *const c_char;
466
467    pub fn baseRT_set_timestamps(model: baseRT_model_t, enabled: bool);
468
469    pub fn baseRT_set_task(model: baseRT_model_t, task: *const c_char) -> bool;
470
471    pub fn baseRT_set_initial_prompt(model: baseRT_model_t, text: *const c_char);
472
473    pub fn baseRT_set_condition_on_previous_text(model: baseRT_model_t, enabled: bool);
474
475    pub fn baseRT_transcribe_language(model: baseRT_model_t) -> *const c_char;
476
477    pub fn baseRT_transcribe_audio_duration_ms(model: baseRT_model_t) -> c_int;
478
479    pub fn baseRT_transcribe_segment_count(model: baseRT_model_t) -> c_int;
480
481    pub fn baseRT_transcribe_segment(
482        model: baseRT_model_t,
483        index: c_int,
484        out: *mut BaseRTTranscribeSegment,
485    ) -> bool;
486
487    pub fn baseRT_is_whisper(model: baseRT_model_t) -> bool;
488
489    // === Streaming transcription ===
490
491    pub fn baseRT_transcribe_pcm_stream(
492        model: baseRT_model_t,
493        samples: *const c_float,
494        n_samples: c_int,
495        language: *const c_char,
496        stats_out: *mut BaseRTTranscribeStats,
497        callback: baseRT_segment_callback,
498        user_data: *mut c_void,
499    ) -> *const c_char;
500
501    pub fn baseRT_transcribe_stream(
502        model: baseRT_model_t,
503        wav_path: *const c_char,
504        language: *const c_char,
505        stats_out: *mut BaseRTTranscribeStats,
506        callback: baseRT_segment_callback,
507        user_data: *mut c_void,
508    ) -> *const c_char;
509
510    // === Embeddings ===
511
512    pub fn baseRT_embed(
513        model: baseRT_model_t,
514        tokens: *const u32,
515        n_tokens: c_int,
516        out_embedding: *mut c_float,
517        max_dims: c_int,
518    ) -> c_int;
519
520    pub fn baseRT_embed_text(
521        model: baseRT_model_t,
522        text: *const c_char,
523        out_embedding: *mut c_float,
524        max_dims: c_int,
525    ) -> c_int;
526
527    pub fn baseRT_embedding_dim(model: baseRT_model_t) -> c_int;
528    pub fn baseRT_is_embedding_model(model: baseRT_model_t) -> bool;
529
530    // === Chat templates ===
531
532    pub fn baseRT_format_chat(
533        model: baseRT_model_t,
534        system_prompt: *const c_char,
535        user_message: *const c_char,
536    ) -> *const c_char;
537
538    pub fn baseRT_chat_template(model: baseRT_model_t) -> *const c_char;
539
540    /// Raw Jinja `chat_template` source folded in from the HF tokenizer
541    /// (empty if the bundle carries none).
542    pub fn baseRT_chat_template_jinja(model: baseRT_model_t) -> *const c_char;
543
544    /// Create a grammar from a JSON Schema (converted to GBNF internally);
545    /// NULL on error. Used to validate that a codec's tool-call grammar
546    /// compiles in the engine (the runtime constrained-decode path).
547    pub fn baseRT_grammar_create_from_schema(
548        model: baseRT_model_t,
549        json_schema: *const c_char,
550    ) -> *mut std::os::raw::c_void;
551
552    /// Create a grammar from an xgrammar STRUCTURAL TAG document (JSON, not a
553    /// grammar string); NULL on error. The tag's content formats reach past
554    /// JSON Schema — `sequence`, `const_string`, `regex`, `or` — which is how
555    /// a dialect whose call body is not JSON (Gemma 4's `call:fn{k:v}`) still
556    /// gets an enforceable `tool_choice`.
557    pub fn baseRT_grammar_create_from_structural_tag(
558        model: baseRT_model_t,
559        tag_json: *const c_char,
560    ) -> *mut std::os::raw::c_void;
561
562    /// Create a grammar from a GBNF string; NULL on parse error.
563    pub fn baseRT_grammar_create(
564        model: baseRT_model_t,
565        gbnf: *const c_char,
566    ) -> *mut std::os::raw::c_void;
567
568    /// Step a grammar by one token; 0 = the grammar REJECTS it. Lets a test
569    /// assert what a compiled grammar actually admits, not merely that it
570    /// compiled — the difference between "the tag parses" and "the tag
571    /// permits the calls this dialect must be able to make".
572    pub fn baseRT_grammar_accept_token(
573        grammar: *mut std::os::raw::c_void,
574        token_id: u32,
575    ) -> std::os::raw::c_int;
576
577    /// Reset a grammar's acceptance state to its initial stacks, so one
578    /// handle can check several candidate strings.
579    pub fn baseRT_grammar_reset(grammar: *mut std::os::raw::c_void);
580
581    /// Free a grammar.
582    pub fn baseRT_grammar_free(grammar: *mut std::os::raw::c_void);
583
584    /// Number of added/special tokens the model declares.
585    pub fn baseRT_special_token_count(model: baseRT_model_t) -> c_int;
586
587    /// The `index`-th special token string; writes its id to `id_out`.
588    pub fn baseRT_special_token(
589        model: baseRT_model_t,
590        index: c_int,
591        id_out: *mut u32,
592    ) -> *const c_char;
593
594    // === Token counting ===
595
596    pub fn baseRT_token_count(model: baseRT_model_t, text: *const c_char) -> c_int;
597
598    // === Model inspection ===
599
600    pub fn baseRT_tensor_count(model: baseRT_model_t) -> c_int;
601    pub fn baseRT_tensor_name(model: baseRT_model_t, index: c_int) -> *const c_char;
602    pub fn baseRT_tensor_dtype(model: baseRT_model_t, index: c_int) -> u32;
603    pub fn baseRT_tensor_raw_dtype(model: baseRT_model_t, index: c_int) -> *const c_char;
604
605    // === Profiling ===
606
607    pub fn baseRT_profile_decode_step(
608        model: baseRT_model_t,
609        token_id: u32,
610        position: c_int,
611        timing_out: *mut c_float,
612        max_entries: c_int,
613    ) -> c_int;
614
615    pub fn baseRT_profile_label(model: baseRT_model_t, index: c_int) -> *const c_char;
616
617    // === GPU sampling ===
618
619    pub fn baseRT_gpu_temperature_scale(model: baseRT_model_t, temperature: c_float);
620
621    pub fn baseRT_gpu_repetition_penalty(
622        model: baseRT_model_t,
623        token_ids: *const u32,
624        n_tokens: c_int,
625        penalty: c_float,
626    );
627
628    // === Pre-load configuration (call BEFORE baseRT_load_model) ===
629
630    pub fn baseRT_set_paged_kv(enable: c_int);
631    pub fn baseRT_set_baked_decode(enable: c_int);
632    pub fn baseRT_set_max_batch_size(n: c_int);
633    pub fn baseRT_set_prefix_cache(enable: c_int);
634    pub fn baseRT_set_prefill_chunk(n: c_int);
635    pub fn baseRT_set_kv_bits(bits: c_int);
636    pub fn baseRT_set_verbose(on: c_int);
637
638    // === Error / capability introspection ===
639
640    pub fn baseRT_get_error_code() -> c_int;
641    pub fn baseRT_strerror(code: c_int) -> *const c_char;
642    pub fn baseRT_capabilities(model: baseRT_model_t) -> u32;
643    pub fn baseRT_eos_token_id(model: baseRT_model_t) -> u32;
644    pub fn baseRT_is_eos_token(model: baseRT_model_t, token: u32) -> i32;
645    pub fn baseRT_weights_identity(model: baseRT_model_t) -> u64;
646    pub fn baseRT_kv_bits_effective(model: baseRT_model_t) -> c_int;
647    pub fn baseRT_bos_id(model: baseRT_model_t) -> u32;
648
649    // === Multi-sequence generation (paged-KV only) ===
650
651    pub fn baseRT_sequence_create(model: baseRT_model_t) -> baseRT_sequence_t;
652    pub fn baseRT_sequence_free(seq: baseRT_sequence_t);
653    pub fn baseRT_sequence_rollback(seq: baseRT_sequence_t, length: c_int) -> c_int;
654
655    // === Batched prefill/decode ===
656
657    pub fn baseRT_batch_step(
658        model: baseRT_model_t,
659        seqs: *mut baseRT_sequence_t,
660        n_seqs: c_int,
661        in_tokens: *const u32,
662        in_token_counts: *const c_int,
663        out_tokens: *mut u32,
664    ) -> c_int;
665    pub fn baseRT_batch_step_fused(
666        model: baseRT_model_t,
667        seqs: *mut baseRT_sequence_t,
668        n_seqs: c_int,
669        in_tokens: *const u32,
670        in_token_counts: *const c_int,
671        out_tokens: *mut u32,
672    ) -> c_int;
673    pub fn baseRT_batch_step_fused_logits(
674        model: baseRT_model_t,
675        seqs: *mut baseRT_sequence_t,
676        n_seqs: c_int,
677        in_tokens: *const u32,
678        in_token_counts: *const c_int,
679    ) -> c_int;
680    pub fn baseRT_batch_warmup(model: baseRT_model_t, max_batch: c_int) -> c_int;
681    pub fn baseRT_max_prefill_chunk(model: baseRT_model_t) -> c_int;
682
683    // === Host-side logits-row operations ===
684
685    pub fn baseRT_read_batch_logits(
686        model: baseRT_model_t,
687        n_seqs: c_int,
688        out_logits_f16: *mut c_void,
689    ) -> c_int;
690    pub fn baseRT_batch_logits_stride(model: baseRT_model_t) -> usize;
691    pub fn baseRT_mask_logits_row(
692        model: baseRT_model_t,
693        row: *mut c_void,
694        bitmask: *const i32,
695    ) -> c_int;
696    pub fn baseRT_sample_logits_row(
697        model: baseRT_model_t,
698        row: *const c_void,
699        cfg: *const BaseRTSamplingConfig,
700        prev_tokens: *const u32,
701        n_prev: c_int,
702        repeat_window: c_int,
703        seed_offset: u32,
704    ) -> u32;
705    pub fn baseRT_argmax_logits_row(model: baseRT_model_t, row: *const c_void) -> u32;
706    pub fn baseRT_logits_row_logprobs(
707        model: baseRT_model_t,
708        row: *const c_void,
709        token: u32,
710        top_k: c_int,
711        out_token_logprob: *mut c_float,
712        out_ids: *mut u32,
713        out_logprobs: *mut c_float,
714    ) -> c_int;
715
716    // === Prefix cache — scheduler-driven primitives ===
717
718    pub fn baseRT_prefix_match(
719        model: baseRT_model_t,
720        tokens: *const u32,
721        n_tokens: c_int,
722    ) -> BaseRTPrefixMatch;
723    pub fn baseRT_sequence_seed_prefix(
724        seq: baseRT_sequence_t,
725        blocks: *const c_int,
726        n_blocks: c_int,
727        n_tokens: c_int,
728    ) -> c_int;
729    pub fn baseRT_page_size(model: baseRT_model_t) -> c_int;
730    pub fn baseRT_prefix_insert(
731        model: baseRT_model_t,
732        tokens: *const u32,
733        n_tokens: c_int,
734        seq: baseRT_sequence_t,
735    ) -> c_int;
736    pub fn baseRT_prefix_unlock(model: baseRT_model_t, handle: u64);
737    pub fn baseRT_prefix_release(model: baseRT_model_t, handle: u64);
738    pub fn baseRT_prefix_discard(model: baseRT_model_t, handle: u64);
739    pub fn baseRT_prefix_evict(model: baseRT_model_t, n_blocks: c_int) -> c_int;
740    pub fn baseRT_prefix_cache_stats(
741        model: baseRT_model_t,
742        out_hits: *mut u64,
743        out_misses: *mut u64,
744        out_reused_tokens: *mut u64,
745        out_blocks_cached: *mut c_int,
746    );
747
748    // === Recurrent (GDN) state snapshots — hybrid models only ===
749
750    pub fn baseRT_gdn_snapshot_size(model: baseRT_model_t) -> c_int;
751    pub fn baseRT_sequence_gdn_capture(seq: baseRT_sequence_t, out: *mut u8, cap: c_int) -> c_int;
752    pub fn baseRT_sequence_gdn_restore(
753        seq: baseRT_sequence_t,
754        blob: *const u8,
755        len: c_int,
756    ) -> c_int;
757}
758
759#[cfg(test)]
760mod tests {
761    use super::*;
762    use std::mem;
763
764    // -----------------------------------------------------------------------
765    // Struct size tests — must match the C struct layout in types.h
766    // -----------------------------------------------------------------------
767
768    #[test]
769    fn model_config_size() {
770        // Full struct: decoder + encoder + Gemma4 + Qwen3.5-GDN + MoE +
771        // vision + audio.
772        // Must be far larger than the old 112-byte truncation; returning a
773        // 112-byte struct by value from baseRT_get_config corrupted memory.
774        assert!(
775            mem::size_of::<BaseRTModelConfig>() > 1000,
776            "config struct unexpectedly small ({})",
777            mem::size_of::<BaseRTModelConfig>()
778        );
779        assert_eq!(mem::size_of::<BaseRTModelConfig>(), 1792);
780    }
781
782    #[test]
783    fn model_config_size_matches_library() {
784        // The authoritative drift check: compare this hand-written mirror
785        // against sizeof(BaseRTModelConfig) as compiled into libbaseRT. A
786        // mismatch means every field after the divergence point decodes as
787        // garbage through baseRT_get_config.
788        assert_eq!(
789            unsafe { baseRT_model_config_sizeof() },
790            mem::size_of::<BaseRTModelConfig>(),
791            "BaseRTModelConfig mirror drifted from include/baseRT/types.h"
792        );
793    }
794
795    #[test]
796    fn model_config_alignment() {
797        assert_eq!(mem::align_of::<BaseRTModelConfig>(), 4);
798    }
799
800    #[test]
801    fn sampling_config_size() {
802        // 5 floats/ints (20) + 2 floats (presence/freq, 28) + uint32 seed (32)
803        // + int32 n_logit_bias (36) + 4-byte pad to 8-byte align (40)
804        // + 2 pointers (8 bytes each on 64-bit) = 56 bytes
805        assert_eq!(mem::size_of::<BaseRTSamplingConfig>(), 56);
806    }
807
808    #[test]
809    fn sampling_config_alignment() {
810        // Two pointer fields force 8-byte alignment on 64-bit.
811        assert_eq!(mem::align_of::<BaseRTSamplingConfig>(), 8);
812    }
813
814    #[test]
815    fn generation_stats_size() {
816        // int + int + float + float + float + float = 24 bytes
817        assert_eq!(mem::size_of::<BaseRTGenerationStats>(), 24);
818    }
819
820    #[test]
821    fn generation_stats_alignment() {
822        assert_eq!(mem::align_of::<BaseRTGenerationStats>(), 4);
823    }
824
825    #[test]
826    fn transcribe_stats_size() {
827        // int + float + float + float + float = 20 bytes
828        assert_eq!(mem::size_of::<BaseRTTranscribeStats>(), 20);
829    }
830
831    #[test]
832    fn transcribe_stats_alignment() {
833        assert_eq!(mem::align_of::<BaseRTTranscribeStats>(), 4);
834    }
835
836    // -----------------------------------------------------------------------
837    // Field offset tests — verify C-compatible field ordering
838    // -----------------------------------------------------------------------
839
840    #[test]
841    fn model_config_field_offsets() {
842        // SAFETY: zeroed bytes are a valid bit pattern for this all-POD struct.
843        let base: BaseRTModelConfig = unsafe { mem::zeroed() };
844        let base_ptr = &base as *const _ as usize;
845
846        assert_eq!(&base.dim as *const _ as usize - base_ptr, 0);
847        assert_eq!(&base.n_layers as *const _ as usize - base_ptr, 4);
848        assert_eq!(&base.norm_eps as *const _ as usize - base_ptr, 40);
849        assert_eq!(&base.rope_theta as *const _ as usize - base_ptr, 44);
850        assert_eq!(
851            &base.sliding_window_pattern as *const _ as usize - base_ptr,
852            48
853        );
854        // The field the old binding dropped — everything after shifts by 4.
855        assert_eq!(&base.sliding_window as *const _ as usize - base_ptr, 52);
856        assert_eq!(&base.rope_local_theta as *const _ as usize - base_ptr, 56);
857        assert_eq!(&base.architecture as *const _ as usize - base_ptr, 60);
858        assert_eq!(&base.enc_n_layers as *const _ as usize - base_ptr, 92);
859        assert_eq!(&base.enc_max_seq_len as *const _ as usize - base_ptr, 112);
860        // Spot-check the tail blocks to confirm the full layout.
861        assert_eq!(&base.swa_layers as *const _ as usize - base_ptr, 144);
862        assert_eq!(&base.ffn_dims as *const _ as usize - base_ptr, 208);
863        assert_eq!(&base.attn_output_gate as *const _ as usize - base_ptr, 1232);
864        assert_eq!(
865            &base.linear_attn_layers as *const _ as usize - base_ptr,
866            1244
867        );
868        assert_eq!(&base.gdn_num_k_heads as *const _ as usize - base_ptr, 1308);
869        // Nemotron-H SSM block, inserted between gdn_* and the MoE block —
870        // 20 bytes that shifted every field below it.
871        assert_eq!(&base.ssm_state_size as *const _ as usize - base_ptr, 1328);
872        assert_eq!(&base.ssm_num_heads as *const _ as usize - base_ptr, 1344);
873        assert_eq!(&base.n_experts as *const _ as usize - base_ptr, 1348);
874        assert_eq!(
875            &base.expert_weights_scale as *const _ as usize - base_ptr,
876            1368
877        );
878        assert_eq!(&base.vision_n_layers as *const _ as usize - base_ptr, 1372);
879        assert_eq!(&base.vision_arch as *const _ as usize - base_ptr, 1432);
880        assert_eq!(&base.audio_n_layers as *const _ as usize - base_ptr, 1448);
881        assert_eq!(&base.eoa_token_id as *const _ as usize - base_ptr, 1524);
882        assert_eq!(&base.mrope_section as *const _ as usize - base_ptr, 1528);
883        assert_eq!(
884            &base.mrope_interleaved as *const _ as usize - base_ptr,
885            1540
886        );
887        assert_eq!(
888            &base.rope_scaling_factor as *const _ as usize - base_ptr,
889            1544
890        );
891        assert_eq!(
892            &base.rope_orig_max_pos as *const _ as usize - base_ptr,
893            1556
894        );
895        assert_eq!(
896            &base.rope_scaling_type as *const _ as usize - base_ptr,
897            1560
898        );
899        assert_eq!(&base.qk_scale_factor as *const _ as usize - base_ptr, 1564);
900        assert_eq!(&base.nope_layers as *const _ as usize - base_ptr, 1576);
901        assert_eq!(&base.embed_norm_eps as *const _ as usize - base_ptr, 1640);
902        assert_eq!(
903            &base.vision_window_layers as *const _ as usize - base_ptr,
904            1644
905        );
906        assert_eq!(&base.video_token_id as *const _ as usize - base_ptr, 1724);
907        assert_eq!(&base.q_lora_rank as *const _ as usize - base_ptr, 1728);
908        assert_eq!(&base.indexer_top_k as *const _ as usize - base_ptr, 1768);
909        assert_eq!(
910            &base.rope_yarn_beta_fast as *const _ as usize - base_ptr,
911            1772
912        );
913    }
914
915    #[test]
916    fn sampling_config_field_offsets() {
917        let base = BaseRTSamplingConfig::default();
918        let base_ptr = &base as *const _ as usize;
919
920        assert_eq!(&base.temperature as *const _ as usize - base_ptr, 0);
921        assert_eq!(&base.top_k as *const _ as usize - base_ptr, 4);
922        assert_eq!(&base.top_p as *const _ as usize - base_ptr, 8);
923        assert_eq!(&base.min_p as *const _ as usize - base_ptr, 12);
924        assert_eq!(&base.repeat_penalty as *const _ as usize - base_ptr, 16);
925        assert_eq!(&base.presence_penalty as *const _ as usize - base_ptr, 20);
926        assert_eq!(&base.frequency_penalty as *const _ as usize - base_ptr, 24);
927        assert_eq!(&base.seed as *const _ as usize - base_ptr, 28);
928        assert_eq!(&base.n_logit_bias as *const _ as usize - base_ptr, 32);
929        // 4-byte padding before pointer alignment to 8.
930        assert_eq!(&base.logit_bias_tokens as *const _ as usize - base_ptr, 40);
931        assert_eq!(&base.logit_bias_values as *const _ as usize - base_ptr, 48);
932    }
933
934    #[test]
935    fn generation_stats_field_offsets() {
936        let base = BaseRTGenerationStats::default();
937        let base_ptr = &base as *const _ as usize;
938
939        assert_eq!(&base.prompt_tokens as *const _ as usize - base_ptr, 0);
940        assert_eq!(&base.generated_tokens as *const _ as usize - base_ptr, 4);
941        assert_eq!(&base.prefill_time_ms as *const _ as usize - base_ptr, 8);
942        assert_eq!(&base.decode_time_ms as *const _ as usize - base_ptr, 12);
943        assert_eq!(
944            &base.prefill_tokens_per_sec as *const _ as usize - base_ptr,
945            16
946        );
947        assert_eq!(
948            &base.decode_tokens_per_sec as *const _ as usize - base_ptr,
949            20
950        );
951    }
952
953    // -----------------------------------------------------------------------
954    // Default trait tests
955    // -----------------------------------------------------------------------
956
957    #[test]
958    fn sampling_config_default_values() {
959        let cfg = BaseRTSamplingConfig::default();
960        assert_eq!(cfg.temperature, 0.0);
961        assert_eq!(cfg.top_k, 40);
962        assert!((cfg.top_p - 0.9).abs() < f32::EPSILON);
963        assert_eq!(cfg.min_p, 0.0);
964        assert_eq!(cfg.repeat_penalty, 1.0);
965        assert_eq!(cfg.presence_penalty, 0.0);
966        assert_eq!(cfg.frequency_penalty, 0.0);
967        assert_eq!(cfg.seed, 0);
968        assert_eq!(cfg.n_logit_bias, 0);
969        assert!(cfg.logit_bias_tokens.is_null());
970        assert!(cfg.logit_bias_values.is_null());
971    }
972
973    #[test]
974    fn prefix_match_layout() {
975        // int matched_tokens (4) + int n_blocks (8) + const int *blocks
976        // (16) + uint64_t handle (24). Returned BY VALUE from
977        // baseRT_prefix_match, so the mirror must match the C layout
978        // exactly.
979        assert_eq!(mem::size_of::<BaseRTPrefixMatch>(), 24);
980        assert_eq!(mem::offset_of!(BaseRTPrefixMatch, matched_tokens), 0);
981        assert_eq!(mem::offset_of!(BaseRTPrefixMatch, n_blocks), 4);
982        assert_eq!(mem::offset_of!(BaseRTPrefixMatch, blocks), 8);
983        assert_eq!(mem::offset_of!(BaseRTPrefixMatch, handle), 16);
984    }
985
986    #[test]
987    fn transcribe_stats_default_values() {
988        let stats = BaseRTTranscribeStats::default();
989        assert_eq!(stats.n_tokens, 0);
990        assert_eq!(stats.audio_ms, 0.0);
991        assert_eq!(stats.encode_ms, 0.0);
992        assert_eq!(stats.decode_ms, 0.0);
993        assert_eq!(stats.total_ms, 0.0);
994    }
995
996    #[test]
997    fn generation_stats_default_values() {
998        let stats = BaseRTGenerationStats::default();
999        assert_eq!(stats.prompt_tokens, 0);
1000        assert_eq!(stats.generated_tokens, 0);
1001        assert_eq!(stats.prefill_time_ms, 0.0);
1002        assert_eq!(stats.decode_time_ms, 0.0);
1003        assert_eq!(stats.prefill_tokens_per_sec, 0.0);
1004        assert_eq!(stats.decode_tokens_per_sec, 0.0);
1005    }
1006}