#include "brain.h" #include "mtmd.h" #include "mtmd-helper.h" #include #include #include #include #include #include #include "ggml-backend.h" #include "ggml.h" #include "llama.h" #include "sbuf.h" // Nomi dei tensori nel grafo di calcolo (suffisso "-"). #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ì // ("\n" diventa "\n\n" 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 ? "\n\n\n\n" : "", img && i == last_user ? mtmd_default_marker() : "", msgs[i].text); // Il formato di conversazione del cervello: "\n" avvia il ragionamento, un blocco vuoto lo salta. sb_puts(&p, o->thinking ? "<|im_start|>assistant\n\n" : "<|im_start|>assistant\n\n\n\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, "")) { 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 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, "")) { b->last_stop = STOP_END; break; } // il resto lo scrive lo strumento if (!(o->tools && strstr(answer, "")) && 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\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; }