File syn_nn.c¶
File List > src > syntropic > util > syn_nn.c
Go to the documentation of this file
#include "syn_nn.h"
#include <math.h>
static q7_t apply_activation_quant(q16_t acc, SYN_NN_Activation act, const syn_nn_quant_t *quant)
{
if (quant != NULL && quant->multiplier > 0) {
int64_t scaled = (int64_t)acc * quant->multiplier;
acc = (q16_t)(scaled >> (16 + quant->shift));
if (quant->zero_point != 0) {
acc += (q16_t)quant->zero_point << 8;
}
}
switch (act) {
case SYN_NN_ACT_RELU:
if (acc < 0)
acc = 0;
break;
case SYN_NN_ACT_LEAKY_RELU:
if (acc < 0)
acc = acc >> 4;
break;
case SYN_NN_ACT_SIGMOID:
if (acc <= Q16_FROM_INT(-4)) {
acc = 0;
} else if (acc >= Q16_FROM_INT(4)) {
acc = Q16_ONE;
} else {
acc = Q16_HALF + (acc >> 3);
}
break;
case SYN_NN_ACT_TANH:
if (acc <= Q16_FROM_INT(-2)) {
acc = -Q16_ONE;
} else if (acc >= Q16_FROM_INT(2)) {
acc = Q16_ONE;
} else {
acc = acc >> 1;
}
break;
case SYN_NN_ACT_NONE:
default:
break;
}
return q16_to_q7(acc);
}
static q7_t apply_activation_q7(q16_t acc, SYN_NN_Activation act, uint8_t out_shift)
{
if (out_shift > 0 && out_shift <= 16) {
acc = acc >> out_shift;
}
switch (act) {
case SYN_NN_ACT_RELU:
if (acc < 0)
acc = 0;
break;
case SYN_NN_ACT_LEAKY_RELU:
if (acc < 0)
acc = acc >> 4; /* Leaky slope = 0.0625 */
break;
case SYN_NN_ACT_SIGMOID:
/* Fixed-point piecewise linear Sigmoid approximation: [0.0, 1.0] in Q16 */
if (acc <= Q16_FROM_INT(-4)) {
acc = 0;
} else if (acc >= Q16_FROM_INT(4)) {
acc = Q16_ONE;
} else {
acc = Q16_HALF + (acc >> 3);
}
break;
case SYN_NN_ACT_TANH:
/* Fixed-point piecewise linear TanH approximation: [-1.0, 1.0] in Q16 */
if (acc <= Q16_FROM_INT(-2)) {
acc = -Q16_ONE;
} else if (acc >= Q16_FROM_INT(2)) {
acc = Q16_ONE;
} else {
acc = acc >> 1;
}
break;
case SYN_NN_ACT_NONE:
default:
break;
}
return q16_to_q7(acc);
}
SYN_Status syn_nn_dense_q7(const q7_t *inputs, size_t num_inputs, const q7_t *weights,
const q16_t *biases, q7_t *outputs, size_t num_outputs,
SYN_NN_Activation act, uint8_t out_shift)
{
if (!inputs || !weights || !outputs || num_inputs == 0 || num_outputs == 0) {
return SYN_INVALID_PARAM;
}
for (size_t i = 0; i < num_outputs; i++) {
q16_t acc = (biases != NULL) ? biases[i] : 0;
const q7_t *w_row = &weights[i * num_inputs];
for (size_t j = 0; j < num_inputs; j++) {
acc = q7_mac(acc, inputs[j], w_row[j]);
}
outputs[i] = apply_activation_q7(acc, act, out_shift);
}
return SYN_OK;
}
SYN_PT_Status syn_nn_dense_pt(SYN_PT *pt, const q7_t *inputs, size_t num_inputs,
const q7_t *weights, const q16_t *biases, q7_t *outputs,
size_t num_outputs, SYN_NN_Activation act, uint8_t out_shift,
size_t *current_neuron, size_t chunk_size)
{
PT_BEGIN(pt);
if (!inputs || !weights || !outputs || !current_neuron || num_inputs == 0 || num_outputs == 0) {
PT_EXIT(pt);
}
if (chunk_size == 0) {
chunk_size = num_outputs;
}
*current_neuron = 0;
while (*current_neuron < num_outputs) {
size_t end = *current_neuron + chunk_size;
if (end > num_outputs) {
end = num_outputs;
}
for (size_t i = *current_neuron; i < end; i++) {
q16_t acc = (biases != NULL) ? biases[i] : 0;
const q7_t *w_row = &weights[i * num_inputs];
for (size_t j = 0; j < num_inputs; j++) {
acc = q7_mac(acc, inputs[j], w_row[j]);
}
outputs[i] = apply_activation_q7(acc, act, out_shift);
}
*current_neuron = end;
if (*current_neuron < num_outputs) {
PT_YIELD(pt);
}
}
PT_END(pt);
}
SYN_Status syn_nn_softmax_q7(const q7_t *inputs, q7_t *outputs, size_t num_inputs)
{
if (!inputs || !outputs || num_inputs == 0) {
return SYN_INVALID_PARAM;
}
/* Stack buffer is fixed at 64 entries — reject larger inputs */
if (num_inputs > 64) {
return SYN_INVALID_PARAM;
}
/* Find max input for numerical stability */
q7_t max_val = inputs[0];
for (size_t i = 1; i < num_inputs; i++) {
if (inputs[i] > max_val) {
max_val = inputs[i];
}
}
/* Compute exp(x - max) approximation in Q16.16 */
double sum = 0.0;
double exp_vals[64];
for (size_t i = 0; i < num_inputs; i++) {
double diff = (double)(inputs[i] - max_val) / 128.0;
exp_vals[i] = exp(diff);
sum += exp_vals[i];
}
if (sum <= 0.0) {
sum = 1.0; /* LCOV_EXCL_LINE: Defensive bounds check / hardware port fallback */
}
/* Normalize so sum equals 127 in Q7 (+1.0) */
for (size_t i = 0; i < num_inputs; i++) {
double prob = exp_vals[i] / sum;
outputs[i] = Q7_FROM_FLOAT((float)prob);
}
return SYN_OK;
}
SYN_Status syn_nn_attention_q7(const q7_t *q, const q7_t *k, const q7_t *v, size_t seq_len,
size_t d_k, size_t d_v, q7_t *out, uint8_t attn_shift)
{
if (!q || !k || !v || !out || seq_len == 0 || d_k == 0 || d_v == 0 || seq_len > 32) {
return SYN_INVALID_PARAM;
}
/* Process attention for each query token */
for (size_t i = 0; i < seq_len; i++) {
const q7_t *q_i = &q[i * d_k];
q7_t attn_scores[32];
/* Compute dot-product Q_i * K_j^T for all key tokens */
for (size_t j = 0; j < seq_len; j++) {
const q7_t *k_j = &k[j * d_k];
q16_t acc = 0;
for (size_t m = 0; m < d_k; m++) {
acc = q7_mac(acc, q_i[m], k_j[m]);
}
if (attn_shift > 0) {
acc = acc >> attn_shift;
}
attn_scores[j] = q16_to_q7(acc);
}
/* Apply Softmax over attention scores */
q7_t attn_weights[32];
syn_nn_softmax_q7(attn_scores, attn_weights, seq_len);
/* Compute weighted sum over Values V */
for (size_t m = 0; m < d_v; m++) {
q16_t acc = 0;
for (size_t j = 0; j < seq_len; j++) {
acc = q7_mac(acc, attn_weights[j], v[j * d_v + m]);
}
out[i * d_v + m] = q16_to_q7(acc);
}
}
return SYN_OK;
}
SYN_Status syn_nn_conv1d_q7(const q7_t *inputs, size_t seq_len, size_t num_channels,
const q7_t *weights, const q16_t *biases, q7_t *outputs,
size_t num_filters, size_t kernel_size, size_t stride,
SYN_NN_Activation act, uint8_t out_shift)
{
if (!inputs || !weights || !outputs || seq_len == 0 || num_channels == 0 || num_filters == 0 ||
kernel_size == 0 || stride == 0 || kernel_size > seq_len) {
return SYN_INVALID_PARAM;
}
size_t out_steps = (seq_len - kernel_size) / stride + 1;
for (size_t s = 0; s < out_steps; s++) {
size_t t_start = s * stride;
for (size_t f = 0; f < num_filters; f++) {
q16_t acc = (biases != NULL) ? biases[f] : 0;
const q7_t *w_filter = &weights[f * kernel_size * num_channels];
for (size_t k = 0; k < kernel_size; k++) {
const q7_t *in_step = &inputs[(t_start + k) * num_channels];
const q7_t *w_k = &w_filter[k * num_channels];
for (size_t c = 0; c < num_channels; c++) {
acc = q7_mac(acc, in_step[c], w_k[c]);
}
}
outputs[s * num_filters + f] = apply_activation_q7(acc, act, out_shift);
}
}
return SYN_OK;
}
SYN_PT_Status syn_nn_conv1d_pt(SYN_PT *pt, const q7_t *inputs, size_t seq_len, size_t num_channels,
const q7_t *weights, const q16_t *biases, q7_t *outputs,
size_t num_filters, size_t kernel_size, size_t stride,
SYN_NN_Activation act, uint8_t out_shift, size_t *current_step,
size_t chunk_size)
{
PT_BEGIN(pt);
if (!inputs || !weights || !outputs || !current_step || seq_len == 0 || num_channels == 0 ||
num_filters == 0 || kernel_size == 0 || stride == 0 || kernel_size > seq_len) {
PT_EXIT(pt);
}
*current_step = 0;
while (*current_step < ((seq_len - kernel_size) / stride + 1)) {
size_t out_steps = (seq_len - kernel_size) / stride + 1;
size_t step_chunk = (chunk_size == 0) ? out_steps : chunk_size;
size_t end = *current_step + step_chunk;
if (end > out_steps) {
end = out_steps;
}
for (size_t s = *current_step; s < end; s++) {
size_t t_start = s * stride;
for (size_t f = 0; f < num_filters; f++) {
q16_t acc = (biases != NULL) ? biases[f] : 0;
const q7_t *w_filter = &weights[f * kernel_size * num_channels];
for (size_t k = 0; k < kernel_size; k++) {
const q7_t *in_step = &inputs[(t_start + k) * num_channels];
const q7_t *w_k = &w_filter[k * num_channels];
for (size_t c = 0; c < num_channels; c++) {
acc = q7_mac(acc, in_step[c], w_k[c]);
}
}
outputs[s * num_filters + f] = apply_activation_q7(acc, act, out_shift);
}
}
*current_step = end;
if (*current_step < out_steps) {
PT_YIELD(pt);
}
}
PT_END(pt);
}
size_t syn_nn_argmax_q7(const q7_t *outputs, size_t num_outputs)
{
if (!outputs || num_outputs == 0) {
return 0;
}
size_t max_idx = 0;
q7_t max_val = outputs[0];
for (size_t i = 1; i < num_outputs; i++) {
if (outputs[i] > max_val) {
max_val = outputs[i];
max_idx = i;
}
}
return max_idx;
}
SYN_Status syn_nn_conv1d_quant_q7(const q7_t *inputs, size_t seq_len, size_t num_channels,
const q7_t *weights, const q16_t *biases, q7_t *outputs,
size_t num_filters, size_t kernel_size, size_t stride,
SYN_NN_Activation act, const syn_nn_quant_t *quant)
{
if (!inputs || !weights || !outputs || seq_len == 0 || num_channels == 0 || num_filters == 0 ||
kernel_size == 0 || stride == 0 || kernel_size > seq_len) {
return SYN_INVALID_PARAM;
}
size_t out_steps = (seq_len - kernel_size) / stride + 1;
for (size_t s = 0; s < out_steps; s++) {
size_t t_start = s * stride;
for (size_t f = 0; f < num_filters; f++) {
q16_t acc = (biases != NULL) ? biases[f] : 0;
const q7_t *w_filter = &weights[f * kernel_size * num_channels];
for (size_t k = 0; k < kernel_size; k++) {
const q7_t *in_step = &inputs[(t_start + k) * num_channels];
const q7_t *w_k = &w_filter[k * num_channels];
for (size_t c = 0; c < num_channels; c++) {
acc = q7_mac(acc, in_step[c], w_k[c]);
}
}
outputs[s * num_filters + f] = apply_activation_quant(acc, act, quant);
}
}
return SYN_OK;
}
SYN_Status syn_nn_dense_quant_q7(const q7_t *inputs, size_t num_inputs, const q7_t *weights,
const q16_t *biases, q7_t *outputs, size_t num_outputs,
SYN_NN_Activation act, const syn_nn_quant_t *quant)
{
if (!inputs || !weights || !outputs || num_inputs == 0 || num_outputs == 0) {
return SYN_INVALID_PARAM;
}
for (size_t i = 0; i < num_outputs; i++) {
q16_t acc = (biases != NULL) ? biases[i] : 0;
const q7_t *w_row = &weights[i * num_inputs];
for (size_t j = 0; j < num_inputs; j++) {
acc = q7_mac(acc, inputs[j], w_row[j]);
}
outputs[i] = apply_activation_quant(acc, act, quant);
}
return SYN_OK;
}
SYN_Status syn_nn_maxpool1d_q7(const q7_t *inputs, size_t seq_len, size_t num_channels,
q7_t *outputs, size_t pool_size, size_t stride)
{
if (!inputs || !outputs || seq_len == 0 || num_channels == 0 || pool_size == 0 || stride == 0 ||
pool_size > seq_len) {
return SYN_INVALID_PARAM;
}
size_t out_steps = (seq_len - pool_size) / stride + 1;
for (size_t s = 0; s < out_steps; s++) {
size_t t_start = s * stride;
for (size_t c = 0; c < num_channels; c++) {
q7_t max_val = inputs[t_start * num_channels + c];
for (size_t p = 1; p < pool_size; p++) {
q7_t val = inputs[(t_start + p) * num_channels + c];
if (val > max_val) {
max_val = val;
}
}
outputs[s * num_channels + c] = max_val;
}
}
return SYN_OK;
}
SYN_Status syn_nn_avgpool1d_q7(const q7_t *inputs, size_t seq_len, size_t num_channels,
q7_t *outputs, size_t pool_size, size_t stride)
{
if (!inputs || !outputs || seq_len == 0 || num_channels == 0 || pool_size == 0 || stride == 0 ||
pool_size > seq_len) {
return SYN_INVALID_PARAM;
}
size_t out_steps = (seq_len - pool_size) / stride + 1;
for (size_t s = 0; s < out_steps; s++) {
size_t t_start = s * stride;
for (size_t c = 0; c < num_channels; c++) {
int32_t sum = 0;
for (size_t p = 0; p < pool_size; p++) {
sum += inputs[(t_start + p) * num_channels + c];
}
int32_t avg = sum / (int32_t)pool_size;
if (avg > 127)
avg = 127; /* LCOV_EXCL_LINE: Defensive bounds check / hardware port fallback */
if (avg < -128)
avg = -128; /* LCOV_EXCL_LINE: Defensive bounds check / hardware port fallback */
outputs[s * num_channels + c] = (q7_t)avg;
}
}
return SYN_OK;
}