Skip to main content

sc_neurocore_engine/bindings/
mixed_dense.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Commercial license available
3// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4// © Code 2020–2026 Miroslav Šotek. All rights reserved.
5// ORCID: 0009-0009-3560-0851
6// Contact: www.anulum.li | protoscience@anulum.li
7// SC-NeuroCore — mixed-precision dense PyO3 binding
8
9//! Python binding for the bit-true Q8.8 by Q16.16 dense contraction.
10
11use numpy::{IntoPyArray, PyReadonlyArray1};
12use pyo3::exceptions::PyValueError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16/// Register the mixed-precision dense contraction with the extension module.
17pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
18    module.add_function(wrap_pyfunction!(
19        py_mixed_dense_forward_batch_q88_q1616,
20        module
21    )?)?;
22    Ok(())
23}
24
25/// Batched integer mixed-precision Q8.8 × Q16.16 dense MAC.
26///
27/// Parity contract with `sc_neurocore.compiler.mixed_dense_kernel`: this Rust
28/// path and the Julia, Go, Mojo and Python backends return bit-identical arrays
29/// because the integer branch (divisor equal to the Q8.8 weight scale) is exact.
30///
31/// `weights_q88` is row-major `n_outputs * n_inputs`; `inputs_q1616` is row-major
32/// `n_batch * n_inputs`. Returns a dict with `outputs_q1616` (int32), `overflow`
33/// (bool) and `underflow` (bool), each a 1-D array of length `n_batch * n_outputs`.
34#[pyfunction]
35#[pyo3(signature = (weights_q88, inputs_q1616, n_outputs, n_inputs))]
36fn py_mixed_dense_forward_batch_q88_q1616<'py>(
37    py: Python<'py>,
38    weights_q88: PyReadonlyArray1<'py, i16>,
39    inputs_q1616: PyReadonlyArray1<'py, i32>,
40    n_outputs: usize,
41    n_inputs: usize,
42) -> PyResult<Py<PyAny>> {
43    let result = crate::ir::qformat::mixed_dense_forward_batch_q88_q1616(
44        weights_q88.as_slice()?,
45        inputs_q1616.as_slice()?,
46        n_outputs,
47        n_inputs,
48    )
49    .map_err(|e| PyValueError::new_err(e.to_string()))?;
50    let d = PyDict::new(py);
51    d.set_item("outputs_q1616", result.outputs_q1616.into_pyarray(py))?;
52    d.set_item("overflow", result.overflow.into_pyarray(py))?;
53    d.set_item("underflow", result.underflow.into_pyarray(py))?;
54    Ok(d.into_any().unbind())
55}