neuronal-gpu / src /brain.c
amogaddy's picture
Neuronal: modello su ZeroGPU
862f461 verified
Raw History Blame Contribute Delete
68.6 kB
#include "brain.h"
#include "mtmd.h"
#include "mtmd-helper.h"
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "ggml-backend.h"
#include "ggml.h"
#include "llama.h"
#include "sbuf.h"
// Nomi dei tensori nel grafo di calcolo (suffisso "-<strato>").
#define NEURON_TENSOR "ffn_swiglu" // attivazioni FFN dopo SwiGLU: i "neuroni" del modello
#define FFN_IN_TENSOR "attn_post_norm" // ingresso del blocco FFN
#define FFN_OUT_TENSOR "ffn_out" // uscita del blocco FFN, prima della somma al residuo
#define LAYER_OUT_TENSOR "l_out" // flusso residuo all'uscita dello strato
typedef struct {
int layer, n;
int *idx;
float *old; // fattori precedenti, per l'annullamento
} Op;
typedef struct {
int id;
BrainArea a; // copia di proprietà della brain
float *act; // [n] attivazione dell'ultimo token
float *inf; // [n] esercitazione di infezione: 1 = sano
double *acc; // [n] somma delle attivazioni durante l'accumulo (per i presidi)
} Area;
struct Brain {
struct llama_model *model;
struct llama_context *ctx;
const struct llama_vocab *vocab;
int n_layer, n_ff, n_batch, n_embd;
// cattura per la neurogenesi
bool capture_on;
double *cap_x, *cap_out; // [n_layer][n_embd]
float *cap_last; // [n_layer][n_embd] ultimo token
long cap_tokens;
// aree create (protette da mu)
Area *areas;
int n_areas, cap_areas;
int *layer_live; // neuroni creati abilitati per strato
unsigned areas_version; // cambia a ogni modifica: evita di mescolare ingresso e uscita
float *area_a; // attivazioni per token dei neuroni dello strato in corso
size_t area_a_cap;
int area_a_layer;
int64_t area_a_ntok;
unsigned area_a_version;
float *xbuf; // copia host di un tensore [n_embd][n_tok]
size_t xbuf_cap;
float *last; // [n_layer][n_ff] attivazione dell'ultimo token
double *accum; // [n_layer][n_ff] somma di |a| (mappatura)
long accum_tokens;
bool accum_on;
bool observe; // questo blocco di token va osservato (l'ultimo del prompt, o un token generato)
mtmd_context *mtmd; // visione (NULL finché non serve)
bool explicit_pos; // dopo un'immagine le posizioni si contano a mano (M-RoPE)
llama_pos n_past;
unsigned mods; // cambia a ogni modifica dei neuroni: lo stato salvato non vale più
// Stato salvato alla fine dell'ultimo prompt: il prossimo che inizia uguale riparte da qui.
llama_token *snap_toks;
int n_snap;
unsigned snap_mods;
uint8_t *snap;
size_t snap_size, snap_cap;
float *scratch; // copia host del tensore corrente
size_t scratch_cap; // in float
// Interventi: protetti da mu perché arrivano dal thread di rete mentre il modello calcola.
pthread_mutex_t mu;
float *factor; // [n_layer][n_ff], 1 = normale
int *n_mod; // neuroni con fattore != 1, per strato
bool interv_on; // interventi manuali attivi (l'autodiagnosi può sospenderli)
BrainStop last_stop; // perché si è fermata l'ultima generazione
float *sfactor; // [n_layer][n_ff] modifiche strutturali (macroaree): fuori da annulla/ripristina
int *n_smod;
float *ifactor; // [n_layer][n_ff] esercitazione di infezione: neuroni "febbricitanti"
int *n_imod;
Op *ops;
int n_ops, cap_ops;
// Calcolo delegato a un cervello remoto (vedi in fondo). link_mu: un solo scambio alla volta.
BrainLink link;
pthread_mutex_t link_mu;
unsigned r_epoch; // cervello remoto con cui stiamo parlando (0 = nessuno)
unsigned r_idle_epoch;
unsigned r_mods; // stato dei neuroni che gli abbiamo mandato
bool r_state_ok;
unsigned areas_struct, r_areas_struct; // cambia quando si aggiunge o toglie un'area: solo allora viaggiano i pesi
bool r_areas_ok;
volatile bool r_busy; // uno scambio è in corso: i dati rimasti di là non si possono chiedere adesso
unsigned r_dirty; // dati ancora solo sul cervello remoto (DIRTY_*)
bool r_accum_reset, r_capture_reset;
double r_last_use;
bool dg_on; // riassunto dell'ultimo token arrivato dal cervello remoto
float *dg_mean, *dg_val; // [n_layer], [n_layer][DIGEST_K]
int *dg_idx;
};
#define DIGEST_K 32 // neuroni più attivi per strato che viaggiano a ogni token
enum { DIRTY_LAST = 1, DIRTY_ACCUM = 2, DIRTY_CAPTURE = 4 };
static void fresh(const Brain *b, unsigned mask);
static int layer_of(const char *name, const char *prefix) {
size_t n = strlen(prefix);
if (strncmp(name, prefix, n) != 0 || name[n] != '-') return -1;
return atoi(name + n + 1);
}
// Copia un tensore [n_embd][n_tok] dalla GPU in b->xbuf.
static float *read_embd(Brain *b, struct ggml_tensor *t, int64_t n_tok) {
size_t need = (size_t)(n_tok * b->n_embd);
if (need > b->xbuf_cap) { b->xbuf_cap = need; b->xbuf = realloc(b->xbuf, need * sizeof(float)); }
size_t row = (size_t)b->n_embd * sizeof(float);
if (t->nb[1] == row) ggml_backend_tensor_get(t, b->xbuf, 0, row * n_tok);
else for (int64_t r = 0; r < n_tok; r++) ggml_backend_tensor_get(t, b->xbuf + r * b->n_embd, r * t->nb[1], row);
return b->xbuf;
}
static void write_embd(Brain *b, struct ggml_tensor *t, int64_t n_tok) {
size_t row = (size_t)b->n_embd * sizeof(float);
if (t->nb[1] == row) ggml_backend_tensor_set(t, b->xbuf, 0, row * n_tok);
else for (int64_t r = 0; r < n_tok; r++) ggml_backend_tensor_set(t, b->xbuf + r * b->n_embd, r * t->nb[1], row);
}
static void add_rows(double *acc, const float *x, int64_t n_tok, int n) {
for (int64_t r = 0; r < n_tok; r++) {
const float *v = x + r * n;
for (int i = 0; i < n; i++) acc[i] += v[i];
}
}
static float dot(const float *a, const float *b, int n) {
float s = 0;
for (int i = 0; i < n; i++) s += a[i] * b[i];
return s;
}
// Ingresso FFN: cattura e calcolo dei neuroni creati per questo strato.
static void on_ffn_in(Brain *b, struct ggml_tensor *t, int L) {
int64_t n_tok = t->ne[1];
float *x = read_embd(b, t, n_tok);
if (b->capture_on) {
add_rows(b->cap_x + (size_t)L * b->n_embd, x, n_tok, b->n_embd);
memcpy(b->cap_last + (size_t)L * b->n_embd, x + (n_tok - 1) * b->n_embd, sizeof(float) * (size_t)b->n_embd);
if (L == 0) b->cap_tokens += n_tok;
}
pthread_mutex_lock(&b->mu);
int live = b->layer_live[L];
if (live > 0) {
if ((size_t)(n_tok * live) > b->area_a_cap) {
b->area_a_cap = (size_t)(n_tok * live);
b->area_a = realloc(b->area_a, b->area_a_cap * sizeof(float));
}
int k = 0;
for (int ai = 0; ai < b->n_areas; ai++) {
Area *A = &b->areas[ai];
if (!A->a.enabled) continue;
for (int j = 0; j < A->a.n; j++) {
if (A->a.layer[j] != L) continue;
const float *w = A->a.w_in + (size_t)j * b->n_embd;
for (int64_t r = 0; r < n_tok; r++) {
float v = 1.0f;
if (!A->a.tonic) {
v = (dot(w, x + r * b->n_embd, b->n_embd) - A->a.bias[j]) * A->a.scale[j];
v = v < 0 ? 0 : v > 1 ? 1 : v;
}
// febbre: il neurone infetto si accende anche senza stimolo e spinge più forte
if (A->inf[j] != 1.0f) v = (v < 0.3f ? 0.3f : v) * A->inf[j];
if (b->accum_on) A->acc[j] += v;
b->area_a[r * live + k] = v;
}
A->act[j] = b->area_a[(n_tok - 1) * live + k];
k++;
}
}
b->area_a_layer = L;
b->area_a_ntok = n_tok;
b->area_a_version = b->areas_version;
}
pthread_mutex_unlock(&b->mu);
}
// Uscita FFN: i neuroni creati attivi scrivono il loro contributo prima della somma al residuo.
static void on_ffn_out(Brain *b, struct ggml_tensor *t, int L) {
pthread_mutex_lock(&b->mu);
int live = b->layer_live[L];
int64_t n_tok = t->ne[1];
if (live > 0 && b->area_a_layer == L && b->area_a_ntok == n_tok && b->area_a_version == b->areas_version) {
float *o = read_embd(b, t, n_tok);
int k = 0;
for (int ai = 0; ai < b->n_areas; ai++) {
Area *A = &b->areas[ai];
if (!A->a.enabled) continue;
for (int j = 0; j < A->a.n; j++) {
if (A->a.layer[j] != L) continue;
const float *w = A->a.w_out + (size_t)j * b->n_embd;
for (int64_t r = 0; r < n_tok; r++) {
float g = A->a.gain * b->area_a[r * live + k];
if (g == 0) continue;
float *y = o + r * b->n_embd;
for (int i = 0; i < b->n_embd; i++) y[i] += g * w[i];
}
k++;
}
}
write_embd(b, t, n_tok);
}
b->area_a_layer = -1;
pthread_mutex_unlock(&b->mu);
}
static bool layer_has_areas(Brain *b, int L) {
pthread_mutex_lock(&b->mu);
bool r = b->layer_live[L] > 0;
pthread_mutex_unlock(&b->mu);
return r;
}
// Neuroni originali: interventi, osservazione e accumulo per la mappatura.
static void on_neurons(Brain *b, struct ggml_tensor *t, int L) {
if (t->type != GGML_TYPE_F32 || t->ne[0] != b->n_ff) return;
const int64_t n_tok = t->ne[1];
const size_t row = (size_t)b->n_ff * sizeof(float);
const bool dense = t->nb[1] == row;
if ((size_t)(n_tok * b->n_ff) > b->scratch_cap) {
b->scratch_cap = (size_t)(n_tok * b->n_ff);
b->scratch = realloc(b->scratch, b->scratch_cap * sizeof(float));
}
float *a = b->scratch;
if (dense) ggml_backend_tensor_get(t, a, 0, row * n_tok);
else for (int64_t r = 0; r < n_tok; r++) ggml_backend_tensor_get(t, a + r * b->n_ff, r * t->nb[1], row);
pthread_mutex_lock(&b->mu);
bool use_interv = b->interv_on && b->n_mod[L] > 0;
if (use_interv || b->n_smod[L] > 0 || b->n_imod[L] > 0) {
const float *f = b->factor + (size_t)L * b->n_ff, *sf = b->sfactor + (size_t)L * b->n_ff;
const float *inf = b->ifactor + (size_t)L * b->n_ff;
for (int64_t r = 0; r < n_tok; r++) {
float *x = a + r * b->n_ff;
for (int i = 0; i < b->n_ff; i++) {
float k = (use_interv ? f[i] : 1.0f) * sf[i] * inf[i];
if (k != 1.0f) x[i] *= k;
}
}
if (dense) ggml_backend_tensor_set(t, a, 0, row * n_tok);
else for (int64_t r = 0; r < n_tok; r++) ggml_backend_tensor_set(t, a + r * b->n_ff, r * t->nb[1], row);
}
pthread_mutex_unlock(&b->mu);
memcpy(b->last + (size_t)L * b->n_ff, a + (n_tok - 1) * b->n_ff, row);
if (b->accum_on) {
double *acc = b->accum + (size_t)L * b->n_ff;
for (int64_t r = 0; r < n_tok; r++) {
const float *x = a + r * b->n_ff;
for (int i = 0; i < b->n_ff; i++) acc[i] += fabsf(x[i]);
}
if (L == 0) b->accum_tokens += n_tok;
}
}
// Chiamato da llama.cpp per ogni nodo del grafo: con ask=true decide se vogliamo il tensore,
// con ask=false il tensore è calcolato e possiamo leggerlo e riscriverlo prima che venga usato.
static bool on_tensor(struct ggml_tensor *t, bool ask, void *ud) {
Brain *b = ud;
int L;
if ((L = layer_of(t->name, NEURON_TENSOR)) >= 0 && L < b->n_layer) {
// copiare 17408 neuroni x 512 token per strato dalla GPU costa: solo se servono davvero
if (ask) return b->observe || b->accum_on || (b->interv_on && b->n_mod[L] > 0) || b->n_smod[L] > 0 || b->n_imod[L] > 0;
on_neurons(b, t, L);
return true;
}
bool embd = t->type == GGML_TYPE_F32 && t->ne[0] == b->n_embd;
if ((L = layer_of(t->name, FFN_IN_TENSOR)) >= 0 && L < b->n_layer) {
if (ask) return b->capture_on || layer_has_areas(b, L);
if (embd) on_ffn_in(b, t, L);
return true;
}
if ((L = layer_of(t->name, FFN_OUT_TENSOR)) >= 0 && L < b->n_layer) {
if (ask) return layer_has_areas(b, L);
if (embd) on_ffn_out(b, t, L);
return true;
}
if ((L = layer_of(t->name, LAYER_OUT_TENSOR)) >= 0 && L < b->n_layer) {
if (ask) return b->capture_on;
if (embd && b->capture_on) add_rows(b->cap_out + (size_t)L * b->n_embd, read_embd(b, t, t->ne[1]), t->ne[1], b->n_embd);
return true;
}
return !ask;
}
// Solo avvisi ed errori di llama.cpp: il resto è rumore per chi usa Neuronal.
static void on_log(enum ggml_log_level level, const char *text, void *ud) {
if (level >= GGML_LOG_LEVEL_WARN) fputs(text, stderr);
}
Brain *brain_open(const char *model_path, int n_ctx, const char *backend_dir, int n_gpu_layers) {
llama_log_set(on_log, NULL);
if (backend_dir) ggml_backend_load_all_from_path(backend_dir);
else ggml_backend_load_all();
llama_backend_init();
struct llama_model_params mp = llama_model_default_params();
mp.n_gpu_layers = n_gpu_layers;
struct llama_model *model = llama_model_load_from_file(model_path, mp);
if (!model) return NULL;
char buf[64] = {0};
const char *arch_keys[] = {"qwen35.feed_forward_length", "llama.feed_forward_length", "qwen3.feed_forward_length"};
for (size_t i = 0; i < sizeof arch_keys / sizeof *arch_keys && !buf[0]; i++)
if (llama_model_meta_val_str(model, arch_keys[i], buf, sizeof buf) < 0) buf[0] = 0;
if (!buf[0]) { fprintf(stderr, "brain: feed_forward_length non trovato nei metadati\n"); llama_model_free(model); return NULL; }
Brain *b = calloc(1, sizeof *b);
b->model = model;
b->vocab = llama_model_get_vocab(model);
b->n_layer = llama_model_n_layer(model);
b->n_ff = atoi(buf);
b->n_embd = llama_model_n_embd(model);
size_t n = (size_t)b->n_layer * b->n_ff;
b->cap_x = calloc((size_t)b->n_layer * b->n_embd, sizeof(double));
b->cap_out = calloc((size_t)b->n_layer * b->n_embd, sizeof(double));
b->cap_last = calloc((size_t)b->n_layer * b->n_embd, sizeof(float));
b->layer_live = calloc((size_t)b->n_layer, sizeof(int));
b->area_a_layer = -1;
b->last = calloc(n, sizeof(float));
b->accum = calloc(n, sizeof(double));
b->factor = malloc(n * sizeof(float));
for (size_t i = 0; i < n; i++) b->factor[i] = 1.0f;
b->n_mod = calloc((size_t)b->n_layer, sizeof(int));
b->interv_on = true;
b->observe = true;
b->sfactor = malloc(n * sizeof(float));
for (size_t i = 0; i < n; i++) b->sfactor[i] = 1.0f;
b->n_smod = calloc((size_t)b->n_layer, sizeof(int));
b->ifactor = malloc(n * sizeof(float));
for (size_t i = 0; i < n; i++) b->ifactor[i] = 1.0f;
b->n_imod = calloc((size_t)b->n_layer, sizeof(int));
b->dg_mean = calloc((size_t)b->n_layer, sizeof(float));
b->dg_val = calloc((size_t)b->n_layer * DIGEST_K, sizeof(float));
b->dg_idx = calloc((size_t)b->n_layer * DIGEST_K, sizeof(int));
pthread_mutex_init(&b->mu, NULL);
pthread_mutex_init(&b->link_mu, NULL);
struct llama_context_params cp = llama_context_default_params();
cp.n_ctx = (uint32_t)n_ctx;
cp.n_batch = 512;
cp.n_seq_max = 1;
cp.cb_eval = on_tensor;
cp.cb_eval_user_data = b;
// Solo 1 strato su 4 ha l'attenzione piena (gli altri hanno uno stato fisso): la cache del contesto è piccola.
// In 8 bit occupa 32 KB per token, così il contesto può essere lungo senza uscire dalla VRAM.
cp.flash_attn_type = LLAMA_FLASH_ATTN_TYPE_ENABLED;
cp.type_k = GGML_TYPE_Q8_0;
cp.type_v = GGML_TYPE_Q8_0;
b->ctx = llama_init_from_model(model, cp);
if (!b->ctx) { // backend senza flash attention: cache a 16 bit, contesto ridotto
cp.flash_attn_type = LLAMA_FLASH_ATTN_TYPE_AUTO;
cp.type_k = cp.type_v = GGML_TYPE_F16;
cp.n_ctx = (uint32_t)(n_ctx > 16384 ? 16384 : n_ctx);
b->ctx = llama_init_from_model(model, cp);
}
if (!b->ctx) { brain_close(b); return NULL; }
printf("Contesto: %u token\n", llama_n_ctx(b->ctx));
b->n_batch = (int)llama_n_batch(b->ctx);
return b;
}
void brain_close(Brain *b) {
if (!b) return;
if (b->ctx) llama_free(b->ctx);
if (b->model) llama_model_free(b->model);
llama_backend_free();
for (int i = 0; i < b->n_ops; i++) { free(b->ops[i].idx); free(b->ops[i].old); }
free(b->ops); free(b->last); free(b->accum); free(b->factor); free(b->n_mod); free(b->scratch);
free(b->sfactor); free(b->n_smod); free(b->ifactor); free(b->n_imod);
free(b->snap); free(b->snap_toks); free(b->dg_mean); free(b->dg_val); free(b->dg_idx);
pthread_mutex_destroy(&b->link_mu);
if (b->mtmd) mtmd_free(b->mtmd);
for (int i = 0; i < b->n_areas; i++) {
BrainArea *a = &b->areas[i].a;
free(a->layer); free(a->w_in); free(a->bias); free(a->scale); free(a->w_out); free(b->areas[i].act);
}
free(b->areas); free(b->layer_live); free(b->area_a); free(b->xbuf); free(b->cap_x); free(b->cap_out); free(b->cap_last);
pthread_mutex_destroy(&b->mu);
free(b);
}
BrainInfo brain_info(const Brain *b) {
return (BrainInfo){b->n_layer, b->n_ff, (long)b->n_layer * b->n_ff, b->n_embd};
}
const float *brain_last_activation(const Brain *b) { fresh(b, DIRTY_LAST); return b->last; }
void brain_accum_begin(Brain *b) {
memset(b->accum, 0, (size_t)b->n_layer * b->n_ff * sizeof(double));
pthread_mutex_lock(&b->mu);
for (int i = 0; i < b->n_areas; i++) memset(b->areas[i].acc, 0, sizeof(double) * (size_t)b->areas[i].a.n);
pthread_mutex_unlock(&b->mu);
b->accum_tokens = 0;
b->accum_on = true;
b->r_dirty &= ~(unsigned)DIRTY_ACCUM; // quello accumulato di là fin qui non serve più
b->r_accum_reset = true;
}
const double *brain_accum(const Brain *b, long *n_tokens) {
fresh(b, DIRTY_ACCUM);
if (n_tokens) *n_tokens = b->accum_tokens;
return b->accum;
}
void brain_accum_end(Brain *b) { b->accum_on = false; }
// ---- token ----------------------------------------------------------------
static llama_token *tokenize(const Brain *b, const char *text, bool special, int *n_out) {
int len = (int)strlen(text);
int n = -llama_tokenize(b->vocab, text, len, NULL, 0, false, special);
llama_token *toks = malloc(sizeof(llama_token) * (size_t)(n > 0 ? n : 1));
*n_out = llama_tokenize(b->vocab, text, len, toks, n, false, special);
return toks;
}
static int decode_all(Brain *b, llama_token *toks, int n) {
for (int i = 0; i < n; i += b->n_batch) {
int m = n - i < b->n_batch ? n - i : b->n_batch;
b->observe = i + m >= n; // l'attività mostrata è quella dell'ultimo token
int r = llama_decode(b->ctx, llama_batch_get_one(toks + i, m));
b->observe = true;
if (r) return -1;
}
return n;
}
// Decodifica durante la generazione: con un'immagine nel prompt le posizioni sono esplicite.
static int decode_gen(Brain *b, llama_token *toks, int n) {
if (!b->explicit_pos) return decode_all(b, toks, n);
llama_batch bt = llama_batch_init(n, 0, 1);
for (int i = 0; i < n; i++) {
bt.token[i] = toks[i];
bt.pos[i] = b->n_past + i;
bt.n_seq_id[i] = 1;
bt.seq_id[i][0] = 0;
bt.logits[i] = i == n - 1;
}
bt.n_tokens = n;
int r = llama_decode(b->ctx, bt);
llama_batch_free(bt);
if (r) return -1;
b->n_past += n;
return n;
}
bool brain_vision_open(Brain *b, const char *mmproj_path) {
if (b->mtmd) return true;
struct mtmd_context_params mp = mtmd_context_params_default();
mp.use_gpu = true;
mp.print_timings = false;
mp.n_threads = 8;
mp.warmup = false;
mp.image_max_tokens = 1024; // basta per una foto; limita memoria e tempo
b->mtmd = mtmd_init_from_file(mmproj_path, b->model, mp);
if (!b->mtmd) { // VRAM insufficiente: il proiettore gira sulla CPU
mp.use_gpu = false;
b->mtmd = mtmd_init_from_file(mmproj_path, b->model, mp);
}
return b->mtmd && mtmd_support_vision(b->mtmd);
}
bool brain_has_vision(const Brain *b) { return b->mtmd != NULL; }
// Prompt con un'immagine: niente stato salvato, si legge tutto con il proiettore.
static int prefill_image(Brain *b, const char *prompt, const GenOpts *o) {
llama_memory_clear(llama_get_memory(b->ctx), true);
b->n_snap = 0;
mtmd_bitmap *bm = mtmd_bitmap_init((uint32_t)o->image_w, (uint32_t)o->image_h, o->image_rgb);
mtmd_input_chunks *ch = mtmd_input_chunks_init();
mtmd_input_text it = {prompt, strlen(prompt), false, true};
const mtmd_bitmap *bms[1] = {bm};
int r = mtmd_tokenize(b->mtmd, ch, &it, bms, 1);
llama_pos np = 0;
if (r == 0) r = mtmd_helper_eval_chunks(b->mtmd, b->ctx, ch, 0, 0, b->n_batch, true, &np);
mtmd_input_chunks_free(ch);
mtmd_bitmap_free(bm);
if (r) return -1;
b->n_past = np;
b->explicit_pos = true;
return 0;
}
static int local_feed(Brain *b, const char *text) {
b->dg_on = false;
b->r_dirty &= ~(unsigned)DIRTY_LAST;
int n;
llama_token *toks = tokenize(b, text, false, &n);
llama_memory_clear(llama_get_memory(b->ctx), true);
int r = n > 0 ? decode_all(b, toks, n) : 0;
free(toks);
return r;
}
// ---- conversazione --------------------------------------------------------
static const char *role_name(Role r) {
return r == ROLE_SYSTEM ? "system" : r == ROLE_USER ? "user" : "assistant";
}
// Byte finali che iniziano un carattere UTF-8 non ancora completo (0 se il testo finisce pulito).
static size_t utf8_incomplete_tail(const char *s) {
size_t n = strlen(s);
for (size_t k = 1; k <= 3 && k <= n; k++) {
unsigned char c = (unsigned char)s[n - k];
if ((c & 0xC0) == 0x80) continue; // byte di continuazione: guarda più indietro
int need = c >= 0xF0 ? 4 : c >= 0xE0 ? 3 : c >= 0xC0 ? 2 : 1;
return need > (int)k ? k : 0;
}
return 0;
}
// Vero se la coda del testo è lo stesso pezzo ripetuto tre volte di fila (loop di generazione).
static bool repeating(const char *s, size_t n) {
for (size_t p = 24; p <= 200 && 3 * p <= n; p++)
if (!memcmp(s + n - p, s + n - 2 * p, p) && !memcmp(s + n - p, s + n - 3 * p, p)) return true;
return false;
}
// Legge il prompt. Se inizia come l'ultimo letto, riparte dallo stato salvato e legge solo la parte nuova.
// Lo stato si salva un token prima della fine: il prompt successivo di solito cambia proprio lì
// ("<think>\n" diventa "<think>\n\n</think>" nei messaggi passati).
static int prefill(Brain *b, const llama_token *toks, int n) {
llama_memory_t mem = llama_get_memory(b->ctx);
int start = 0;
if (b->n_snap > 0 && b->n_snap < n && b->snap_mods == b->mods &&
!memcmp(b->snap_toks, toks, sizeof(llama_token) * (size_t)b->n_snap)) {
llama_memory_clear(mem, true);
if (llama_state_seq_set_data(b->ctx, b->snap, b->snap_size, 0) == b->snap_size) start = b->n_snap;
}
if (!start) llama_memory_clear(mem, true);
int body = n - 1;
if (body > start) {
if (decode_all(b, (llama_token *)toks + start, body - start) < 0) { b->n_snap = 0; return -1; }
size_t sz = llama_state_seq_get_size(b->ctx, 0);
if (sz > b->snap_cap) { free(b->snap); b->snap = malloc(sz); b->snap_cap = b->snap ? sz : 0; }
if (b->snap && llama_state_seq_get_data(b->ctx, b->snap, sz, 0) == sz) {
b->snap_size = sz;
b->snap_toks = realloc(b->snap_toks, sizeof(llama_token) * (size_t)body);
memcpy(b->snap_toks, toks, sizeof(llama_token) * (size_t)body);
b->n_snap = body;
b->snap_mods = b->mods;
} else {
b->n_snap = 0;
}
}
return decode_all(b, (llama_token *)toks + body, 1);
}
static int local_chat(Brain *b, const ChatMsg *msgs, int n_msgs, const GenOpts *o,
brain_token_fn fn, void *ud, char *answer, size_t cap) {
b->dg_on = false;
b->r_dirty &= ~(unsigned)DIRTY_LAST;
SBuf p = {0};
bool img = o->image_rgb && o->image_w > 0 && o->image_h > 0 && b->mtmd;
int last_user = -1;
for (int i = 0; i < n_msgs; i++) if (msgs[i].role == ROLE_USER) last_user = i;
for (int i = 0; i < n_msgs; i++)
sb_printf(&p, "<|im_start|>%s\n%s%s%s<|im_end|>\n", role_name(msgs[i].role),
msgs[i].role == ROLE_ASSISTANT ? "<think>\n\n</think>\n\n" : "",
img && i == last_user ? mtmd_default_marker() : "", msgs[i].text);
// Il formato di conversazione del cervello: "<think>\n" avvia il ragionamento, un blocco vuoto lo salta.
sb_puts(&p, o->thinking ? "<|im_start|>assistant\n<think>\n" : "<|im_start|>assistant\n<think>\n\n</think>\n\n");
b->explicit_pos = false;
if (img) {
int r = prefill_image(b, p.p, o);
sb_free(&p);
if (r < 0) return -1;
} else {
int n_prompt;
llama_token *toks = tokenize(b, p.p, true, &n_prompt);
sb_free(&p);
if (prefill(b, toks, n_prompt) < 0) { free(toks); return -1; }
free(toks);
}
struct llama_sampler *smp = llama_sampler_chain_init(llama_sampler_chain_default_params());
// Il modello a 1 bit tende a ripetere la stessa frase all'infinito: penalizza i token già usati di recente.
if (o->repeat_penalty > 1.0f)
llama_sampler_chain_add(smp, llama_sampler_init_penalties(llama_vocab_n_tokens(b->vocab), 256, o->repeat_penalty, 0.0f, 0.0f));
if (o->temperature <= 0) {
llama_sampler_chain_add(smp, llama_sampler_init_greedy());
} else {
llama_sampler_chain_add(smp, llama_sampler_init_top_k(20));
llama_sampler_chain_add(smp, llama_sampler_init_top_p(0.95f, 1));
llama_sampler_chain_add(smp, llama_sampler_init_temp(o->temperature));
llama_sampler_chain_add(smp, llama_sampler_init_dist(o->seed));
}
b->last_stop = STOP_MAX;
char pend[8] = "";
bool in_thought = o->thinking;
int n_thought = 0, produced = 0;
size_t len = 0;
if (cap) answer[0] = 0;
for (; produced < o->max_tokens; produced++) {
llama_token tok = llama_sampler_sample(smp, b->ctx, -1);
if (llama_vocab_is_eog(b->vocab, tok)) { b->last_stop = STOP_END; break; }
// un carattere multi-byte può essere diviso tra due token: si trattiene la parte incompleta
char piece[272];
size_t pl = strlen(pend);
memcpy(piece, pend, pl);
int m = llama_token_to_piece(b->vocab, tok, piece + pl, (int)(sizeof piece - pl - 1), 0, true);
piece[pl + (m > 0 ? (size_t)m : 0)] = 0;
pend[0] = 0;
size_t tail = utf8_incomplete_tail(piece);
if (tail) { memcpy(pend, piece + strlen(piece) - tail, tail); pend[tail] = 0; piece[strlen(piece) - tail] = 0; }
if (in_thought && strstr(piece, "</think>")) {
in_thought = false;
} else if (in_thought) {
n_thought++;
if (fn && !fn(ud, piece, true)) { b->last_stop = STOP_USER; break; }
} else {
// salta gli a capo iniziali che il modello mette dopo </think>
const char *s = piece;
if (len == 0) while (*s == '\n') s++;
size_t sl = strlen(s);
if (sl && len + sl < cap) { memcpy(answer + len, s, sl); len += sl; answer[len] = 0; }
if (sl && fn && !fn(ud, s, false)) { b->last_stop = STOP_USER; break; }
if (o->tools && strstr(answer, "</tool_call>")) { b->last_stop = STOP_END; break; } // il resto lo scrive lo strumento
if (!(o->tools && strstr(answer, "<tool_call>")) && repeating(answer, len)) { b->last_stop = STOP_LOOP; break; }
}
if (decode_gen(b, &tok, 1) < 0) break;
// Budget di ragionamento esaurito: chiudiamo noi il pensiero e costringiamo a rispondere.
if (in_thought && o->think_budget > 0 && n_thought >= o->think_budget) {
int n_close;
llama_token *close = tokenize(b, "\n\nHo abbastanza elementi, rispondo ora.\n</think>\n\n", true, &n_close);
int ok = decode_gen(b, close, n_close);
free(close);
if (ok < 0) break;
in_thought = false;
}
}
llama_sampler_free(smp);
return produced;
}
// ---- interventi -----------------------------------------------------------
int brain_set_neurons(Brain *b, int layer, const int *idx, int n, float factor) {
if (layer < 0 || layer >= b->n_layer || n <= 0 || !isfinite(factor)) return -1;
Op op = {layer, 0, malloc(sizeof(int) * (size_t)n), malloc(sizeof(float) * (size_t)n)};
pthread_mutex_lock(&b->mu);
b->mods++;
float *f = b->factor + (size_t)layer * b->n_ff;
for (int i = 0; i < n; i++) {
int j = idx[i];
if (j < 0 || j >= b->n_ff) continue;
op.idx[op.n] = j;
op.old[op.n] = f[j];
op.n++;
if (f[j] == 1.0f && factor != 1.0f) b->n_mod[layer]++;
if (f[j] != 1.0f && factor == 1.0f) b->n_mod[layer]--;
f[j] = factor;
}
if (b->n_ops == b->cap_ops) {
b->cap_ops = b->cap_ops ? b->cap_ops * 2 : 16;
b->ops = realloc(b->ops, sizeof(Op) * (size_t)b->cap_ops);
}
b->ops[b->n_ops++] = op;
pthread_mutex_unlock(&b->mu);
return op.n;
}
bool brain_undo(Brain *b) {
pthread_mutex_lock(&b->mu);
b->mods++;
bool done = b->n_ops > 0;
if (done) {
Op op = b->ops[--b->n_ops];
float *f = b->factor + (size_t)op.layer * b->n_ff;
// all'indietro: se un neurone compare più volte torna al valore più vecchio
for (int i = op.n - 1; i >= 0; i--) {
int j = op.idx[i];
if (f[j] == 1.0f && op.old[i] != 1.0f) b->n_mod[op.layer]++;
if (f[j] != 1.0f && op.old[i] == 1.0f) b->n_mod[op.layer]--;
f[j] = op.old[i];
}
free(op.idx); free(op.old);
}
pthread_mutex_unlock(&b->mu);
return done;
}
void brain_reset(Brain *b) {
pthread_mutex_lock(&b->mu);
b->mods++;
size_t n = (size_t)b->n_layer * b->n_ff;
for (size_t i = 0; i < n; i++) b->factor[i] = 1.0f;
memset(b->n_mod, 0, sizeof(int) * (size_t)b->n_layer);
for (int i = 0; i < b->n_ops; i++) { free(b->ops[i].idx); free(b->ops[i].old); }
b->n_ops = 0;
pthread_mutex_unlock(&b->mu);
}
int top_abs(const float *a, int n, int k, int *out) {
if (k > n) k = n;
int m = 0;
for (int j = 0; j < n; j++) { // inserimento in lista ordinata: k è piccolo
float v = fabsf(a[j]);
if (m == k && v <= fabsf(a[out[m - 1]])) continue;
int i = m < k ? m++ : k - 1;
while (i > 0 && fabsf(a[out[i - 1]]) < v) { out[i] = out[i - 1]; i--; }
out[i] = j;
}
return m;
}
int brain_top_neurons(const Brain *b, int layer, int k, int *out) {
if (layer < 0 || layer >= b->n_layer || k <= 0) return 0;
fresh(b, DIRTY_LAST);
return top_abs(b->last + (size_t)layer * b->n_ff, b->n_ff, k, out);
}
long brain_intervention_counts(Brain *b, int *per_layer) {
long tot = 0;
pthread_mutex_lock(&b->mu);
for (int L = 0; L < b->n_layer; L++) { per_layer[L] = b->n_mod[L]; tot += b->n_mod[L]; }
pthread_mutex_unlock(&b->mu);
return tot;
}
float brain_neuron_factor(Brain *b, int layer, int idx) {
if (layer < 0 || layer >= b->n_layer || idx < 0 || idx >= b->n_ff) return 1.0f;
pthread_mutex_lock(&b->mu);
float f = b->factor[(size_t)layer * b->n_ff + idx];
pthread_mutex_unlock(&b->mu);
return f;
}
int brain_intervention_list(Brain *b, int *layer, int *idx, float *factor, int cap) {
int n = 0;
pthread_mutex_lock(&b->mu);
for (int L = 0; L < b->n_layer && n < cap; L++) {
if (!b->n_mod[L]) continue;
const float *f = b->factor + (size_t)L * b->n_ff;
for (int j = 0; j < b->n_ff && n < cap; j++)
if (f[j] != 1.0f) { layer[n] = L; idx[n] = j; factor[n] = f[j]; n++; }
}
pthread_mutex_unlock(&b->mu);
return n;
}
// ---- cattura --------------------------------------------------------------
void brain_capture_begin(Brain *b) {
size_t n = (size_t)b->n_layer * b->n_embd;
memset(b->cap_x, 0, n * sizeof(double));
memset(b->cap_out, 0, n * sizeof(double));
b->cap_tokens = 0;
b->capture_on = true;
b->r_dirty &= ~(unsigned)DIRTY_CAPTURE;
b->r_capture_reset = true;
}
const double *brain_capture_x(const Brain *b, long *n_tokens) {
fresh(b, DIRTY_CAPTURE);
if (n_tokens) *n_tokens = b->cap_tokens;
return b->cap_x;
}
const double *brain_capture_out(const Brain *b) { fresh(b, DIRTY_CAPTURE); return b->cap_out; }
const float *brain_capture_last(const Brain *b) { fresh(b, DIRTY_CAPTURE); return b->cap_last; }
void brain_capture_end(Brain *b) { b->capture_on = false; }
// ---- aree create ----------------------------------------------------------
static void recount_live(Brain *b) { // con mu preso
memset(b->layer_live, 0, sizeof(int) * (size_t)b->n_layer);
for (int i = 0; i < b->n_areas; i++) {
const BrainArea *a = &b->areas[i].a;
if (!a->enabled) continue;
for (int j = 0; j < a->n; j++) if (a->layer[j] >= 0 && a->layer[j] < b->n_layer) b->layer_live[a->layer[j]]++;
}
b->areas_version++;
}
static Area *find_area(Brain *b, int id) {
for (int i = 0; i < b->n_areas; i++) if (b->areas[i].id == id) return &b->areas[i];
return NULL;
}
static void *dup_mem(const void *p, size_t n) { void *q = malloc(n); memcpy(q, p, n); return q; }
static void free_area(Area *A) {
free(A->act); free(A->inf); free(A->acc); free(A->a.layer); free(A->a.w_in); free(A->a.w_out); free(A->a.bias); free(A->a.scale);
}
bool brain_area_add(Brain *b, int id, const BrainArea *src) {
if (src->n <= 0) return false;
size_t n = (size_t)src->n, E = (size_t)b->n_embd;
Area A = {id, *src, calloc(n, sizeof(float)), malloc(n * sizeof(float)), calloc(n, sizeof(double))};
for (size_t k = 0; k < n; k++) A.inf[k] = 1.0f;
A.a.layer = dup_mem(src->layer, n * sizeof(int));
A.a.w_in = dup_mem(src->w_in, n * E * sizeof(float));
A.a.w_out = dup_mem(src->w_out, n * E * sizeof(float));
A.a.bias = dup_mem(src->bias, n * sizeof(float));
A.a.scale = dup_mem(src->scale, n * sizeof(float));
pthread_mutex_lock(&b->mu);
b->mods++;
bool ok = find_area(b, id) == NULL;
if (ok) {
if (b->n_areas == b->cap_areas) {
b->cap_areas = b->cap_areas ? b->cap_areas * 2 : 8;
b->areas = realloc(b->areas, sizeof(Area) * (size_t)b->cap_areas);
}
b->areas[b->n_areas++] = A;
b->areas_struct++;
recount_live(b);
}
pthread_mutex_unlock(&b->mu);
if (!ok) free_area(&A);
return ok;
}
bool brain_area_update(Brain *b, int id, bool enabled, float gain) {
pthread_mutex_lock(&b->mu);
b->mods++;
Area *A = find_area(b, id);
if (A) {
A->a.enabled = enabled;
A->a.gain = gain;
recount_live(b);
}
pthread_mutex_unlock(&b->mu);
return A != NULL;
}
bool brain_area_remove(Brain *b, int id) {
pthread_mutex_lock(&b->mu);
b->mods++;
Area *A = find_area(b, id);
bool found = A != NULL;
if (found) {
free_area(A);
*A = b->areas[--b->n_areas];
b->areas_struct++;
recount_live(b);
}
pthread_mutex_unlock(&b->mu);
return found;
}
int brain_area_activation(Brain *b, int id, float *out, int cap) {
fresh(b, DIRTY_LAST);
pthread_mutex_lock(&b->mu);
Area *A = find_area(b, id);
int n = -1;
if (A) {
n = A->a.n < cap ? A->a.n : cap;
memcpy(out, A->act, sizeof(float) * (size_t)n);
}
pthread_mutex_unlock(&b->mu);
return n;
}
// ---- modifiche strutturali -------------------------------------------------------
void brain_struct_clear(Brain *b) {
pthread_mutex_lock(&b->mu);
b->mods++;
size_t n = (size_t)b->n_layer * b->n_ff;
for (size_t i = 0; i < n; i++) b->sfactor[i] = 1.0f;
memset(b->n_smod, 0, sizeof(int) * (size_t)b->n_layer);
pthread_mutex_unlock(&b->mu);
}
int brain_struct_set(Brain *b, int layer, const int *idx, int n, float factor) {
if (layer < 0 || layer >= b->n_layer || !isfinite(factor)) return -1;
pthread_mutex_lock(&b->mu);
b->mods++;
float *f = b->sfactor + (size_t)layer * b->n_ff;
int done = 0;
for (int i = 0; i < (idx ? n : b->n_ff); i++) {
int j = idx ? idx[i] : i;
if (j < 0 || j >= b->n_ff) continue;
if (f[j] == 1.0f && factor != 1.0f) b->n_smod[layer]++;
if (f[j] != 1.0f && factor == 1.0f) b->n_smod[layer]--;
f[j] = factor;
done++;
}
pthread_mutex_unlock(&b->mu);
return done;
}
void brain_interventions_enable(Brain *b, bool on) {
pthread_mutex_lock(&b->mu);
b->mods++;
b->interv_on = on;
pthread_mutex_unlock(&b->mu);
}
BrainStop brain_last_stop(const Brain *b) { return b->last_stop; }
// ---- esercitazione di infezione ------------------------------------------------------------------------
bool brain_infect_set(Brain *b, int layer, int idx, float factor) {
if (layer < 0 || layer >= b->n_layer || idx < 0 || idx >= b->n_ff || !isfinite(factor)) return false;
pthread_mutex_lock(&b->mu);
b->mods++;
float *f = b->ifactor + (size_t)layer * b->n_ff + idx;
bool changed = *f != factor;
if (*f == 1.0f && factor != 1.0f) b->n_imod[layer]++;
if (*f != 1.0f && factor == 1.0f) b->n_imod[layer]--;
*f = factor;
pthread_mutex_unlock(&b->mu);
return changed;
}
float brain_infect_get(Brain *b, int layer, int idx) {
if (layer < 0 || layer >= b->n_layer || idx < 0 || idx >= b->n_ff) return 1.0f;
pthread_mutex_lock(&b->mu);
float f = b->ifactor[(size_t)layer * b->n_ff + idx];
pthread_mutex_unlock(&b->mu);
return f;
}
void brain_infect_clear(Brain *b) {
pthread_mutex_lock(&b->mu);
for (int i = 0; i < b->n_areas; i++) for (int k = 0; k < b->areas[i].a.n; k++) b->areas[i].inf[k] = 1.0f;
b->mods++;
size_t n = (size_t)b->n_layer * b->n_ff;
for (size_t i = 0; i < n; i++) b->ifactor[i] = 1.0f;
memset(b->n_imod, 0, sizeof(int) * (size_t)b->n_layer);
pthread_mutex_unlock(&b->mu);
}
bool brain_area_infect(Brain *b, int id, int k, float factor) {
if (!isfinite(factor)) return false;
pthread_mutex_lock(&b->mu);
Area *A = find_area(b, id);
bool changed = A && k >= 0 && k < A->a.n && A->inf[k] != factor;
if (changed) { A->inf[k] = factor; b->mods++; }
pthread_mutex_unlock(&b->mu);
return changed;
}
int brain_area_accum(Brain *b, int id, float *out, int cap) {
fresh(b, DIRTY_ACCUM);
pthread_mutex_lock(&b->mu);
Area *A = find_area(b, id);
int n = -1;
if (A) {
n = A->a.n < cap ? A->a.n : cap;
for (int k = 0; k < n; k++) out[k] = b->accum_tokens > 0 ? (float)(A->acc[k] / (double)b->accum_tokens) : 0;
}
pthread_mutex_unlock(&b->mu);
return n;
}
// ---- calcolo delegato -------------------------------------------------------------------------------------
// Il modello può girare anche in un altro processo, su una GPU presa in prestito per pochi secondi: là si
// fanno solo lettura e generazione (brain_feed, brain_chat). Interventi, aree e ogni dato letto dal resto del
// programma restano qui; prima di ogni calcolo lo stato dei neuroni viaggia verso il cervello remoto, dopo
// tornano le attivazioni. Se il collegamento manca o cade, il calcolo si fa qui come sempre.
//
// Messaggi (little endian), spezzati in blocchi [tipo][1 = ne seguono altri][dati]:
// 'H' remoto -> qui saluto: strati, neuroni per strato, dimensione del flusso
// 'S' qui -> remoto stato: interventi, modifiche strutturali, infezione, aree create
// 'F' / 'C' lettura di un testo / conversazione; 'X' interrompi la generazione
// 'T' remoto -> qui un pezzo generato con il riassunto dell'attività di quel token
// 'R' remoto -> qui esito
// 'P' / 'D' richiesta dei dati rimasti di là / i dati; 'Q' congedo
#define WIRE_CHUNK (256u * 1024u)
#define REMOTE_NONE (-100) // non eseguito di là: si calcola qui
enum { OPF_ACCUM = 1, OPF_ACCUM_RESET = 2, OPF_CAPTURE = 4, OPF_CAPTURE_RESET = 8, OPF_QUIET = 16 };
static double mono_s(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (double)t.tv_sec + (double)t.tv_nsec * 1e-9;
}
static bool wire_send(const BrainLink *l, char type, const void *data, size_t len) {
uint8_t *buf = malloc(2 + (len < WIRE_CHUNK ? len : WIRE_CHUNK));
size_t off = 0;
bool ok;
do {
size_t m = len - off < WIRE_CHUNK ? len - off : WIRE_CHUNK;
buf[0] = (uint8_t)type;
buf[1] = off + m < len;
if (m) memcpy(buf + 2, (const uint8_t *)data + off, m);
ok = l->send(l->ud, buf, 2 + m);
off += m;
} while (ok && off < len);
free(buf);
return ok;
}
// timeout_ms < 0: senza limite. Ritorna false se il collegamento è caduto o il tempo è scaduto.
static bool wire_recv(const BrainLink *l, char *type, SBuf *out, int timeout_ms) {
out->n = 0;
for (;;) {
size_t n = 0;
uint8_t *f = l->recv(l->ud, &n, timeout_ms);
if (!f) return false;
if (n < 2) { free(f); return false; }
*type = (char)f[0];
bool more = f[1] != 0;
sb_putn(out, (const char *)f + 2, n - 2);
free(f);
if (!more) return true;
}
}
#define PUT(s, v) sb_putn((s), (const char *)&(v), sizeof(v))
#define PUTN(s, p, n) sb_putn((s), (const char *)(p), (n))
typedef struct { const uint8_t *p, *end; bool bad; } Rd;
static void rd_get(Rd *r, void *out, size_t n) {
if ((size_t)(r->end - r->p) < n) { r->bad = true; memset(out, 0, n); return; }
memcpy(out, r->p, n);
r->p += n;
}
static const uint8_t *rd_ptr(Rd *r, size_t n) {
if ((size_t)(r->end - r->p) < n) { r->bad = true; return NULL; }
const uint8_t *q = r->p;
r->p += n;
return q;
}
#define GET(r, v) rd_get((r), &(v), sizeof(v))
// ---- stato dei neuroni ----
static void put_sparse(SBuf *s, const float *f, size_t n) {
size_t at = s->n;
uint32_t cnt = 0;
PUT(s, cnt);
for (size_t i = 0; i < n; i++) {
if (f[i] == 1.0f) continue;
uint32_t k = (uint32_t)i;
PUT(s, k);
PUT(s, f[i]);
cnt++;
}
memcpy(s->p + at, &cnt, 4);
}
static void state_export(Brain *b, SBuf *s, bool full_areas, unsigned *mods, unsigned *areas_struct) {
size_t n = (size_t)b->n_layer * b->n_ff, E = (size_t)b->n_embd;
pthread_mutex_lock(&b->mu);
uint8_t on = b->interv_on, full = full_areas;
PUT(s, on);
put_sparse(s, b->factor, n);
put_sparse(s, b->sfactor, n);
put_sparse(s, b->ifactor, n);
int32_t na = b->n_areas;
PUT(s, full);
PUT(s, na);
for (int i = 0; i < b->n_areas; i++) {
const Area *A = &b->areas[i];
int32_t id = A->id, an = A->a.n;
uint8_t en = A->a.enabled, tonic = A->a.tonic;
PUT(s, id); PUT(s, an); PUT(s, A->a.gain); PUT(s, en); PUT(s, tonic);
PUTN(s, A->inf, sizeof(float) * (size_t)an);
if (!full) continue;
PUTN(s, A->a.layer, sizeof(int) * (size_t)an);
PUTN(s, A->a.bias, sizeof(float) * (size_t)an);
PUTN(s, A->a.scale, sizeof(float) * (size_t)an);
PUTN(s, A->a.w_in, sizeof(float) * (size_t)an * E);
PUTN(s, A->a.w_out, sizeof(float) * (size_t)an * E);
}
*mods = b->mods;
*areas_struct = b->areas_struct;
pthread_mutex_unlock(&b->mu);
}
static bool get_sparse(Rd *r, float *f, int *count, int n_layer, int n_ff) {
size_t n = (size_t)n_layer * n_ff;
for (size_t i = 0; i < n; i++) f[i] = 1.0f;
memset(count, 0, sizeof(int) * (size_t)n_layer);
uint32_t cnt = 0;
GET(r, cnt);
for (uint32_t i = 0; i < cnt && !r->bad; i++) {
uint32_t k = 0;
float v = 1.0f;
GET(r, k); GET(r, v);
if (k >= n || !isfinite(v)) return false;
if (f[k] == 1.0f && v != 1.0f) count[k / (uint32_t)n_ff]++;
f[k] = v;
}
return !r->bad;
}
static bool state_import(Brain *b, const uint8_t *data, size_t len) {
Rd r = {data, data + len, false};
size_t E = (size_t)b->n_embd;
uint8_t on = 1, full = 0;
int32_t na = 0;
pthread_mutex_lock(&b->mu);
b->mods++;
GET(&r, on);
b->interv_on = on != 0;
bool ok = get_sparse(&r, b->factor, b->n_mod, b->n_layer, b->n_ff) &&
get_sparse(&r, b->sfactor, b->n_smod, b->n_layer, b->n_ff) &&
get_sparse(&r, b->ifactor, b->n_imod, b->n_layer, b->n_ff);
GET(&r, full); GET(&r, na);
if (ok && full) {
for (int i = 0; i < b->n_areas; i++) free_area(&b->areas[i]);
b->n_areas = 0;
recount_live(b);
}
pthread_mutex_unlock(&b->mu);
for (int i = 0; ok && i < na; i++) {
int32_t id = 0, an = 0;
float gain = 0;
uint8_t en = 0, tonic = 0;
GET(&r, id); GET(&r, an); GET(&r, gain); GET(&r, en); GET(&r, tonic);
if (r.bad || an <= 0 || an > 1000000) { ok = false; break; }
const uint8_t *inf = rd_ptr(&r, sizeof(float) * (size_t)an);
if (full) {
// i dati del messaggio non sono allineati: brain_area_add li copia byte per byte
BrainArea a = {an, NULL, NULL, NULL, NULL, NULL, gain, en != 0, tonic != 0};
a.layer = (int *)rd_ptr(&r, sizeof(int) * (size_t)an);
a.bias = (float *)rd_ptr(&r, sizeof(float) * (size_t)an);
a.scale = (float *)rd_ptr(&r, sizeof(float) * (size_t)an);
a.w_in = (float *)rd_ptr(&r, sizeof(float) * (size_t)an * E);
a.w_out = (float *)rd_ptr(&r, sizeof(float) * (size_t)an * E);
if (r.bad || !brain_area_add(b, id, &a)) { ok = false; break; }
}
if (r.bad) { ok = false; break; }
pthread_mutex_lock(&b->mu);
Area *A = find_area(b, id);
if (A && A->a.n == an) {
A->a.enabled = en != 0;
A->a.gain = gain;
memcpy(A->inf, inf, sizeof(float) * (size_t)an);
recount_live(b);
} else {
ok = false;
}
pthread_mutex_unlock(&b->mu);
}
long nm = 0, ns = 0, ni = 0;
for (int L = 0; L < b->n_layer; L++) { nm += b->n_mod[L]; ns += b->n_smod[L]; ni += b->n_imod[L]; }
fprintf(stderr, "stato dei neuroni: %ld interventi%s, %ld strutturali, %ld infetti, %d aree%s\n", nm,
b->interv_on ? "" : " (sospesi)", ns, ni, b->n_areas, ok && !r.bad ? "" : " - NON VALIDO");
return ok && !r.bad;
}
// ---- dati che tornano dal cervello remoto ----
static void data_export(Brain *b, unsigned mask, SBuf *s) {
size_t n = (size_t)b->n_layer * b->n_ff, m = (size_t)b->n_layer * b->n_embd;
uint8_t mk = (uint8_t)mask;
PUT(s, mk);
int32_t na = b->n_areas;
if (mask & DIRTY_LAST) {
PUTN(s, b->last, n * sizeof(float));
PUT(s, na);
for (int i = 0; i < b->n_areas; i++) {
int32_t id = b->areas[i].id, an = b->areas[i].a.n;
PUT(s, id); PUT(s, an);
PUTN(s, b->areas[i].act, sizeof(float) * (size_t)an);
}
}
if (mask & DIRTY_ACCUM) {
int64_t tok = b->accum_tokens;
PUT(s, tok);
sb_reserve(s, n * sizeof(float));
for (size_t i = 0; i < n; i++) { float v = (float)b->accum[i]; memcpy(s->p + s->n + i * sizeof(float), &v, sizeof v); }
s->n += n * sizeof(float);
s->p[s->n] = 0;
PUT(s, na);
for (int i = 0; i < b->n_areas; i++) {
int32_t id = b->areas[i].id, an = b->areas[i].a.n;
PUT(s, id); PUT(s, an);
PUTN(s, b->areas[i].acc, sizeof(double) * (size_t)an);
memset(b->areas[i].acc, 0, sizeof(double) * (size_t)an);
}
memset(b->accum, 0, n * sizeof(double)); // consegnato: di là si riparte da zero
b->accum_tokens = 0;
}
if (mask & DIRTY_CAPTURE) {
int64_t tok = b->cap_tokens;
PUT(s, tok);
PUTN(s, b->cap_x, m * sizeof(double));
PUTN(s, b->cap_out, m * sizeof(double));
PUTN(s, b->cap_last, m * sizeof(float));
memset(b->cap_x, 0, m * sizeof(double));
memset(b->cap_out, 0, m * sizeof(double));
b->cap_tokens = 0;
}
}
static bool data_import(Brain *b, const uint8_t *data, size_t len) {
Rd r = {data, data + len, false};
size_t n = (size_t)b->n_layer * b->n_ff, m = (size_t)b->n_layer * b->n_embd;
uint8_t mask = 0;
GET(&r, mask);
if (mask & DIRTY_LAST) {
const uint8_t *p = rd_ptr(&r, n * sizeof(float));
if (!p) return false;
memcpy(b->last, p, n * sizeof(float));
b->dg_on = false;
int32_t na = 0;
GET(&r, na);
for (int i = 0; i < na && !r.bad; i++) {
int32_t id = 0, an = 0;
GET(&r, id); GET(&r, an);
if (an < 0) return false;
const uint8_t *q = rd_ptr(&r, sizeof(float) * (size_t)an);
pthread_mutex_lock(&b->mu);
Area *A = find_area(b, id);
if (A && q && A->a.n == an) memcpy(A->act, q, sizeof(float) * (size_t)an);
pthread_mutex_unlock(&b->mu);
}
}
if (mask & DIRTY_ACCUM) {
int64_t tok = 0;
GET(&r, tok);
const uint8_t *p = rd_ptr(&r, n * sizeof(float));
if (!p) return false;
for (size_t i = 0; i < n; i++) { float v; memcpy(&v, p + i * sizeof(float), sizeof v); b->accum[i] += v; }
b->accum_tokens += (long)tok;
int32_t na = 0;
GET(&r, na);
for (int i = 0; i < na && !r.bad; i++) {
int32_t id = 0, an = 0;
GET(&r, id); GET(&r, an);
if (an < 0) return false;
const uint8_t *q = rd_ptr(&r, sizeof(double) * (size_t)an);
pthread_mutex_lock(&b->mu);
Area *A = find_area(b, id);
if (A && q && A->a.n == an)
for (int k = 0; k < an; k++) { double v; memcpy(&v, q + (size_t)k * sizeof(double), sizeof v); A->acc[k] += v; }
pthread_mutex_unlock(&b->mu);
}
}
if (mask & DIRTY_CAPTURE) {
int64_t tok = 0;
GET(&r, tok);
const uint8_t *x = rd_ptr(&r, m * sizeof(double)), *o = rd_ptr(&r, m * sizeof(double)), *l = rd_ptr(&r, m * sizeof(float));
if (!x || !o || !l) return false;
for (size_t i = 0; i < m; i++) {
double v;
memcpy(&v, x + i * sizeof(double), sizeof v); b->cap_x[i] += v;
memcpy(&v, o + i * sizeof(double), sizeof v); b->cap_out[i] += v;
}
memcpy(b->cap_last, l, m * sizeof(float));
b->cap_tokens += (long)tok;
}
return !r.bad;
}
// ---- lato che delega (il motore) ----
void brain_link_set(Brain *b, const BrainLink *l) {
pthread_mutex_lock(&b->link_mu);
b->link = l ? *l : (BrainLink){0};
b->r_epoch = 0;
b->r_dirty = 0;
pthread_mutex_unlock(&b->link_mu);
}
// Il cervello remoto non risponde più: quello che era rimasto di là è perso.
static void remote_lost(Brain *b) {
if (b->r_dirty & (DIRTY_ACCUM | DIRTY_CAPTURE))
fprintf(stderr, "GPU in prestito: collegamento caduto con una misura in corso (dati persi).\n");
b->r_dirty = 0;
b->r_epoch = 0;
if (b->link.drop) b->link.drop(b->link.ud);
}
// Con link_mu preso: fa arrivare qui i dati rimasti sul cervello remoto.
static bool remote_pull(Brain *b, unsigned mask) {
mask &= b->r_dirty;
if (!mask) return true;
uint8_t mk = (uint8_t)mask;
SBuf in = {0};
char t = 0;
bool ok = wire_send(&b->link, 'P', &mk, 1) && wire_recv(&b->link, &t, &in, 30000) && t == 'D' &&
data_import(b, (const uint8_t *)in.p, in.n);
sb_free(&in);
if (ok) b->r_dirty &= ~mask;
else remote_lost(b);
return ok;
}
// Con link_mu preso: recupera tutto e congeda il cervello remoto (la GPU torna libera).
static void remote_release(Brain *b) {
if (!b->r_epoch) { if (b->link.drop) b->link.drop(b->link.ud); return; }
if (remote_pull(b, b->r_dirty) && b->r_epoch) {
wire_send(&b->link, 'Q', NULL, 0);
b->r_epoch = 0;
b->link.drop(b->link.ud);
}
}
// Dati richiesti dal programma: se sono ancora di là li va a prendere (non durante uno scambio).
static void fresh(const Brain *cb, unsigned mask) {
Brain *b = (Brain *)cb;
if (!(b->r_dirty & mask) || b->r_busy) return;
pthread_mutex_lock(&b->link_mu);
remote_pull(b, mask);
pthread_mutex_unlock(&b->link_mu);
}
// Prende il cervello remoto per un calcolo di circa need_s secondi. Se ritorna true link_mu resta preso.
static bool remote_begin(Brain *b, int need_s) {
if (!b->link.acquire) return false;
pthread_mutex_lock(&b->link_mu);
int a = b->link.acquire(b->link.ud, need_s, true);
if (a < 0) { // c'è, ma il tempo che gli resta non basta: se ne chiede un altro
remote_release(b);
a = b->link.acquire(b->link.ud, need_s, true);
}
if (a <= 0) { pthread_mutex_unlock(&b->link_mu); return false; }
unsigned ep = b->link.epoch(b->link.ud);
SBuf m = {0};
bool ok = true;
if (ep != b->r_epoch) { // un cervello remoto nuovo: non sa ancora niente
if (b->r_epoch) remote_lost(b);
char t = 0;
int32_t h[3] = {0};
ok = wire_recv(&b->link, &t, &m, 20000) && t == 'H' && m.n >= sizeof h;
if (ok) memcpy(h, m.p, sizeof h);
ok = ok && h[0] == b->n_layer && h[1] == b->n_ff && h[2] == b->n_embd;
if (!ok) fprintf(stderr, "GPU in prestito: il cervello remoto non corrisponde a questo modello.\n");
b->r_epoch = ep;
b->r_state_ok = b->r_areas_ok = false;
b->r_dirty = 0;
}
if (ok && (!b->r_state_ok || b->r_mods != b->mods)) {
unsigned mods, as;
m.n = 0;
pthread_mutex_lock(&b->mu);
bool full = !b->r_areas_ok || b->r_areas_struct != b->areas_struct;
pthread_mutex_unlock(&b->mu);
state_export(b, &m, full, &mods, &as);
ok = wire_send(&b->link, 'S', m.p, m.n);
if (ok) { b->r_mods = mods; b->r_state_ok = true; if (full) { b->r_areas_struct = as; b->r_areas_ok = true; } }
}
sb_free(&m);
if (!ok) { remote_lost(b); pthread_mutex_unlock(&b->link_mu); return false; }
b->r_busy = true;
return true;
}
static void remote_end(Brain *b) {
b->r_busy = false;
b->r_last_use = mono_s();
pthread_mutex_unlock(&b->link_mu);
}
static uint8_t op_flags(Brain *b) {
uint8_t f = (uint8_t)((b->accum_on ? OPF_ACCUM : 0) | (b->r_accum_reset ? OPF_ACCUM_RESET : 0) |
(b->capture_on ? OPF_CAPTURE : 0) | (b->r_capture_reset ? OPF_CAPTURE_RESET : 0));
b->r_accum_reset = b->r_capture_reset = false;
return f;
}
static void op_done(Brain *b) {
b->r_dirty |= DIRTY_LAST | (b->accum_on ? DIRTY_ACCUM : 0) | (b->capture_on ? DIRTY_CAPTURE : 0);
}
static int remote_feed(Brain *b, const char *text) {
if (!remote_begin(b, 4)) return REMOTE_NONE;
bool measuring = b->accum_on || b->capture_on;
SBuf m = {0};
uint8_t fl = op_flags(b);
PUT(&m, fl);
PUTN(&m, text, strlen(text) + 1);
char t = 0;
int32_t res[2] = {-1, 0};
bool ok = wire_send(&b->link, 'F', m.p, m.n) && wire_recv(&b->link, &t, &m, 60000) && t == 'R' && m.n >= sizeof res;
if (ok) memcpy(res, m.p, sizeof res);
sb_free(&m);
int ret = res[0];
if (ok) {
b->dg_on = false;
op_done(b);
} else {
remote_lost(b);
ret = measuring ? -1 : REMOTE_NONE; // una misura a metà non si può completare qui
}
remote_end(b);
return ret;
}
// Riassunto dell'attività di un token (arriva con ogni pezzo generato).
static bool digest_import(Brain *b, Rd *r) {
for (int L = 0; L < b->n_layer; L++) {
uint16_t ii[DIGEST_K];
GET(r, b->dg_mean[L]);
GET(r, ii);
rd_get(r, b->dg_val + (size_t)L * DIGEST_K, sizeof(float) * DIGEST_K);
for (int k = 0; k < DIGEST_K; k++) b->dg_idx[L * DIGEST_K + k] = ii[k];
}
uint16_t na = 0;
GET(r, na);
for (int i = 0; i < na && !r->bad; i++) {
int32_t id = 0;
uint16_t an = 0;
GET(r, id); GET(r, an);
const uint8_t *q = rd_ptr(r, sizeof(float) * (size_t)an);
pthread_mutex_lock(&b->mu);
Area *A = find_area(b, id);
if (A && q && A->a.n == an) memcpy(A->act, q, sizeof(float) * (size_t)an);
pthread_mutex_unlock(&b->mu);
}
b->dg_on = !r->bad;
return !r->bad;
}
static int remote_chat(Brain *b, const ChatMsg *msgs, int n_msgs, const GenOpts *o,
brain_token_fn fn, void *ud, char *answer, size_t cap) {
if (!remote_begin(b, fn ? 15 : 8)) return REMOTE_NONE;
SBuf m = {0};
uint8_t fl = (uint8_t)(op_flags(b) | (fn ? 0 : OPF_QUIET)), thinking = o->thinking, tools = o->tools;
bool img = o->image_rgb && o->image_w > 0 && o->image_h > 0;
int32_t iw = img ? o->image_w : 0, ih = img ? o->image_h : 0, nm = n_msgs;
int32_t budget = o->think_budget, max_tokens = o->max_tokens;
uint32_t seed = o->seed, acap = (uint32_t)cap;
PUT(&m, fl); PUT(&m, thinking); PUT(&m, tools); PUT(&m, budget); PUT(&m, max_tokens);
PUT(&m, o->temperature); PUT(&m, seed); PUT(&m, o->repeat_penalty); PUT(&m, acap); PUT(&m, iw); PUT(&m, ih);
if (img) PUTN(&m, o->image_rgb, (size_t)iw * ih * 3);
PUT(&m, nm);
for (int i = 0; i < n_msgs; i++) {
uint8_t role = (uint8_t)msgs[i].role;
uint32_t len = (uint32_t)strlen(msgs[i].text) + 1;
PUT(&m, role); PUT(&m, len);
PUTN(&m, msgs[i].text, len);
}
bool ok = wire_send(&b->link, 'C', m.p, m.n);
size_t len = 0;
int ret = REMOTE_NONE, pieces = 0;
bool cancelled = false, finished = false;
if (cap) answer[0] = 0;
double t_start = mono_s(), t_wait = 0, t_fn = 0;
while (ok) {
char t = 0;
double tw = mono_s();
if (!wire_recv(&b->link, &t, &m, fn ? 45000 : 180000)) { ok = false; break; }
t_wait += mono_s() - tw;
Rd r = {(const uint8_t *)m.p, (const uint8_t *)m.p + m.n, false};
if (t == 'T') {
uint8_t thought = 0;
uint16_t pl = 0;
GET(&r, thought); GET(&r, pl);
const char *piece = (const char *)rd_ptr(&r, (size_t)pl + 1);
if (!piece || piece[pl] || !digest_import(b, &r)) { ok = false; break; }
pieces++;
if (!thought && pl && len + pl < cap) { memcpy(answer + len, piece, pl); len += pl; answer[len] = 0; }
double tf = mono_s();
if (fn && !cancelled && !fn(ud, piece, thought != 0)) {
cancelled = true;
ok = wire_send(&b->link, 'X', NULL, 0);
}
t_fn += mono_s() - tf;
} else if (t == 'R') {
int32_t res[2] = {-1, STOP_MAX};
uint32_t al = 0;
GET(&r, res); GET(&r, al);
const char *a = (const char *)rd_ptr(&r, al);
if (r.bad) { ok = false; break; }
ret = res[0];
b->last_stop = (BrainStop)res[1];
if (cap && a) { size_t k = al < cap - 1 ? al : cap - 1; memcpy(answer, a, k); answer[k] = 0; }
finished = true;
break;
}
}
sb_free(&m);
if (pieces)
printf("GPU in prestito: %d pezzi in %.1f s (attesa dalla rete %.1f s, lavoro qui %.1f s)\n", pieces, mono_s() - t_start, t_wait, t_fn);
if (finished) {
op_done(b);
} else {
remote_lost(b);
if (pieces) { ret = pieces; b->last_stop = cancelled ? STOP_USER : STOP_MAX; } // risposta troncata: resta quello che è arrivato
else { ret = REMOTE_NONE; if (cap) answer[0] = 0; } // niente ancora mostrato: si rifà qui
}
remote_end(b);
return ret;
}
int brain_feed(Brain *b, const char *text) {
int r = remote_feed(b, text);
return r != REMOTE_NONE ? r : local_feed(b, text);
}
int brain_chat(Brain *b, const ChatMsg *msgs, int n_msgs, const GenOpts *o,
brain_token_fn fn, void *ud, char *answer, size_t cap) {
int r = remote_chat(b, msgs, n_msgs, o, fn, ud, answer, cap);
return r != REMOTE_NONE ? r : local_chat(b, msgs, n_msgs, o, fn, ud, answer, cap);
}
void brain_link_idle(Brain *b, double idle_s) {
if (!b->link.acquire || pthread_mutex_trylock(&b->link_mu) != 0) return;
int a = b->link.acquire(b->link.ud, 3, false);
unsigned ep = a ? b->link.epoch(b->link.ud) : 0;
if (a && ep != b->r_idle_epoch) { b->r_idle_epoch = ep; b->r_last_use = mono_s(); } // appena arrivato: gli si dà tempo
if (a < 0 || (a > 0 && mono_s() - b->r_last_use > idle_s)) remote_release(b);
pthread_mutex_unlock(&b->link_mu);
}
int brain_layer_top(Brain *b, int layer, int k, int *idx, float *val, float *mean) {
if (layer < 0 || layer >= b->n_layer || k <= 0) return 0;
if (b->dg_on && k <= DIGEST_K) {
memcpy(idx, b->dg_idx + (size_t)layer * DIGEST_K, sizeof(int) * (size_t)k);
memcpy(val, b->dg_val + (size_t)layer * DIGEST_K, sizeof(float) * (size_t)k);
*mean = b->dg_mean[layer];
return k;
}
fresh(b, DIRTY_LAST);
const float *a = b->last + (size_t)layer * b->n_ff;
float s = 0;
for (int j = 0; j < b->n_ff; j++) s += fabsf(a[j]);
*mean = s / (float)b->n_ff;
int n = top_abs(a, b->n_ff, k, idx);
for (int t = 0; t < n; t++) val[t] = a[idx[t]];
return n;
}
// ---- lato remoto (il processo sulla GPU) ----
typedef struct { Brain *b; const BrainLink *l; SBuf s; double t_digest, t_send; } ServeCtx;
static bool serve_token(void *ud, const char *piece, bool thought) {
ServeCtx *c = ud;
Brain *b = c->b;
SBuf *s = &c->s;
double t0 = mono_s();
s->n = 0;
uint8_t th = thought;
uint16_t pl = (uint16_t)strlen(piece);
PUT(s, th); PUT(s, pl);
PUTN(s, piece, (size_t)pl + 1);
for (int L = 0; L < b->n_layer; L++) {
int idx[DIGEST_K];
float mean = 0, vv[DIGEST_K] = {0};
uint16_t ii[DIGEST_K] = {0};
int n = brain_layer_top(b, L, DIGEST_K, idx, vv, &mean);
for (int t = 0; t < n; t++) ii[t] = (uint16_t)idx[t];
PUT(s, mean); PUT(s, ii); PUT(s, vv);
}
pthread_mutex_lock(&b->mu);
uint16_t na = (uint16_t)b->n_areas;
PUT(s, na);
for (int i = 0; i < b->n_areas; i++) {
int32_t id = b->areas[i].id;
uint16_t an = (uint16_t)b->areas[i].a.n;
PUT(s, id); PUT(s, an);
PUTN(s, b->areas[i].act, sizeof(float) * (size_t)an);
}
pthread_mutex_unlock(&b->mu);
double t1 = mono_s();
bool sent = wire_send(c->l, 'T', s->p, s->n);
c->t_digest += t1 - t0;
c->t_send += mono_s() - t1;
if (!sent) return false;
size_t n = 0;
uint8_t *x = c->l->recv(c->l->ud, &n, 0); // è arrivato l'ordine di fermarsi?
bool stop = x && n >= 1 && x[0] == 'X';
free(x);
return !stop;
}
static void serve_flags(Brain *b, uint8_t fl) {
if (fl & OPF_ACCUM_RESET) brain_accum_begin(b);
if (fl & OPF_CAPTURE_RESET) brain_capture_begin(b);
b->accum_on = (fl & OPF_ACCUM) != 0;
b->capture_on = (fl & OPF_CAPTURE) != 0;
}
void brain_serve(Brain *b, const BrainLink *l, const char *mmproj_path) {
int32_t h[3] = {b->n_layer, b->n_ff, b->n_embd};
if (!wire_send(l, 'H', h, sizeof h)) return;
ServeCtx c = {b, l, {0}, 0, 0};
SBuf in = {0}, out = {0};
char t = 0;
while (wire_recv(l, &t, &in, -1)) {
Rd r = {(const uint8_t *)in.p, (const uint8_t *)in.p + in.n, false};
out.n = 0;
if (t == 'Q') break;
if (t == 'S') {
if (!state_import(b, r.p, in.n)) { fprintf(stderr, "stato dei neuroni non valido\n"); break; }
} else if (t == 'P') {
uint8_t mask = 0;
GET(&r, mask);
data_export(b, mask, &out);
if (!wire_send(l, 'D', out.p, out.n)) break;
} else if (t == 'F') {
uint8_t fl = 0;
GET(&r, fl);
if (r.bad || in.p[in.n - 1]) break;
serve_flags(b, fl);
int32_t res[2] = {local_feed(b, (const char *)r.p), 0};
if (!wire_send(l, 'R', res, sizeof res)) break;
} else if (t == 'C') {
uint8_t fl = 0, thinking = 0, tools = 0;
int32_t budget = 0, max_tokens = 0, iw = 0, ih = 0, nm = 0;
uint32_t seed = 0, acap = 0;
float temperature = 0, penalty = 0;
GET(&r, fl); GET(&r, thinking); GET(&r, tools); GET(&r, budget); GET(&r, max_tokens);
GET(&r, temperature); GET(&r, seed); GET(&r, penalty); GET(&r, acap); GET(&r, iw); GET(&r, ih);
const uint8_t *rgb = NULL;
if (iw > 0 && ih > 0 && iw <= 4096 && ih <= 4096) rgb = rd_ptr(&r, (size_t)iw * ih * 3);
GET(&r, nm);
if (r.bad || nm <= 0 || nm > 4096) break;
ChatMsg *msgs = calloc((size_t)nm, sizeof *msgs);
for (int i = 0; i < nm && !r.bad; i++) {
uint8_t role = 0;
uint32_t len = 0;
GET(&r, role); GET(&r, len);
const char *text = (const char *)rd_ptr(&r, len);
if (!text || !len || text[len - 1]) { r.bad = true; break; }
msgs[i] = (ChatMsg){(Role)role, text};
}
if (r.bad) { free(msgs); break; }
if (rgb && mmproj_path && !b->mtmd && !brain_vision_open(b, mmproj_path)) fprintf(stderr, "visione non disponibile sulla GPU\n");
serve_flags(b, fl);
GenOpts o = {thinking != 0, budget, max_tokens, temperature, seed, penalty, tools != 0, rgb, iw, ih};
if (acap < 256) acap = 256;
if (acap > (1u << 22)) acap = 1u << 22;
char *answer = malloc(acap);
int32_t res[2];
double t0 = mono_s();
c.t_digest = c.t_send = 0;
res[0] = local_chat(b, msgs, nm, &o, (fl & OPF_QUIET) ? NULL : serve_token, &c, answer, acap);
fprintf(stderr, "generazione: %d token in %.2f s (riassunto dei neuroni %.2f s, invio %.2f s)\n", res[0], mono_s() - t0, c.t_digest, c.t_send);
res[1] = (int32_t)b->last_stop;
uint32_t al = res[0] >= 0 ? (uint32_t)strlen(answer) + 1 : 0;
PUT(&out, res); PUT(&out, al);
if (al) PUTN(&out, answer, al);
free(answer);
free(msgs);
if (!wire_send(l, 'R', out.p, out.n)) break;
}
}
sb_free(&in); sb_free(&out); sb_free(&c.s);
}
// ---- OCR ------------------------------------------------------------------------------------------------
int brain_ocr(Brain *b, const unsigned char *file, size_t len, char *out, size_t cap) {
if (!b->mtmd || !mtmd_support_vision(b->mtmd)) return -2;
mtmd_helper_init_opt opt = mtmd_helper_init_opt_default();
struct mtmd_helper_bitmap_wrapper w = mtmd_helper_bitmap_init_from_buf(b->mtmd, file, len, false, opt);
if (!w.bitmap || w.video_ctx) { if (w.bitmap) mtmd_bitmap_free(w.bitmap); return -3; }
int nx = (int)mtmd_bitmap_get_nx(w.bitmap), ny = (int)mtmd_bitmap_get_ny(w.bitmap);
ChatMsg m[2] = {
{ROLE_SYSTEM, "Sei un lettore di testo (OCR). Trascrivi esattamente il testo che vedi nell'immagine, riga per riga, "
"nell'ordine in cui compare, senza tradurre, correggere o commentare. Le tabelle riga per riga con le colonne "
"separate da \" | \". Se nell'immagine non c'è testo scrivi solo: NESSUN TESTO."},
{ROLE_USER, "Trascrivi tutto il testo di questa immagine."}};
GenOpts o = {.thinking = false, .think_budget = 0, .max_tokens = 3000, .temperature = 0, .seed = 0, .repeat_penalty = 1.05f,
.tools = false, .image_rgb = mtmd_bitmap_get_data(w.bitmap), .image_w = nx, .image_h = ny};
int n = brain_chat(b, m, 2, &o, NULL, NULL, out, cap);
mtmd_bitmap_free(w.bitmap);
return n;
}