Files
LithosAnanake/src/word_source/inference_words.c
T

359 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/**
* inference_words.c — Module 26: SSM inference engine + Jacquard FORTH words
*
* Exposes the SSM physics engine to FORTH:
* - Variance / decay-slope / window-width inference on arbitrary arrays
* - Full inference engine run on this VM's rolling window
* - L8 Jacquard mode selector: update, query, apply
* - Bayesian latency posteriors for cache-hit and bucket-search latencies
* - Rolling window diversity (entropy metric)
* - Readable inference output fields from vm->last_inference_outputs
*
* All Q48.16 values pushed as cell_t (int64_t), reinterpreted as uint64_t.
*
* Words registered:
* Q.VARIANCE ( addr u -- q ) variance of u cells at addr (Q48.16)
* INFER-DECAY-SLOPE ( addr u -- q ) exponential decay slope (Q48.16)
* INFER-WINDOW-WIDTH ( addr u -- n ) optimal window width from inflection
* WINDOW-DIVERSITY ( -- u ) rolling window diversity (entropy)
* INFER-RUN ( -- ) run full inference, update vm->last_inference_outputs
* INFER-WINDOW@ ( -- u ) last adaptive_window_width
* INFER-DECAY@ ( -- q ) last adaptive_decay_slope (Q48.16)
* INFER-VARIANCE@ ( -- q ) last window_variance_q48
* INFER-FIT@ ( -- q ) last slope_fit_quality_q48
* INFER-EARLY-EXIT@ ( -- flag ) 1 if last INFER-RUN used ANOVA early-exit
* L8-MODE ( -- n ) current Jacquard mode (0-15)
* L8-UPDATE ( entropy_q cv_q temporal_q stability_q -- )
* L8-APPLY ( -- ) apply current mode to vm->ssm_config (legacy 16-mode)
* L8-TABLE-FORCE ( config_idx -- ) force the adaptive table onto config_idx (0-127)
* BAYES-CACHE-MEAN ( -- q ) Bayesian mean latency for cache hits (Q48.16)
* BAYES-CACHE-LOWER ( -- q ) 95% credible lower bound, cache hits
* BAYES-CACHE-UPPER ( -- q ) 95% credible upper bound, cache hits
* BAYES-BUCKET-MEAN ( -- q ) Bayesian mean latency for bucket searches
* BAYES-BUCKET-LOWER ( -- q ) 95% credible lower bound, bucket searches
* BAYES-BUCKET-UPPER ( -- q ) 95% credible upper bound, bucket searches
*/
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "vm.h"
#include "word_registry.h"
#include "q48_16.h"
#include "inference_engine.h"
#include "ssm_jacquard.h"
#include "rolling_window_of_truth.h"
#include "physics_hotwords_cache.h"
#include "platform_alloc.h"
#include "platform_lock.h"
/* ── helpers ──────────────────────────────────────────────────────────── */
static inline q48_16_t q48_pop_inf(VM *vm) { return (q48_16_t)(uint64_t)VM_POP(vm); }
static inline void q48_push_inf(VM *vm, q48_16_t q) { VM_PUSH(vm, (cell_t)(int64_t)q); }
/* Translate Q48.16 integer to double for ssm_l8_metrics_t (double-based). */
static inline double q48_to_dbl(q48_16_t q) { return (double)q / 65536.0; }
/* ── array-based inference primitives ───────────────────────────────── */
/*
* Validate a FORTH array reference: addr is a vaddr_t, u is cell count.
* Returns pointer to data or NULL on bounds error (sets vm->error).
*/
static const uint64_t *array_ptr(VM *vm, vaddr_t addr, cell_t u)
{
if (u <= 0 || addr >= (vaddr_t)VM_MEMORY_SIZE) {
vm->error = 1;
return NULL;
}
size_t bytes = (size_t)u * sizeof(cell_t);
if ((size_t)addr + bytes > VM_MEMORY_SIZE) {
vm->error = 1;
return NULL;
}
return (const uint64_t *)(vm->memory + addr);
}
/* Q.VARIANCE ( addr u -- q ) — variance of u uint64_t cells at addr */
static void infer_word_q_variance(VM *vm)
{
cell_t u = VM_POP(vm);
vaddr_t addr = (vaddr_t)VM_POP(vm);
const uint64_t *data = array_ptr(vm, addr, u);
if (!data) { q48_push_inf(vm, 0); return; }
q48_push_inf(vm, compute_variance_q48(data, (uint64_t)u));
}
/* INFER-DECAY-SLOPE ( addr u -- q ) — decay slope via linear regression */
static void infer_word_decay_slope(VM *vm)
{
cell_t u = VM_POP(vm);
vaddr_t addr = (vaddr_t)VM_POP(vm);
const uint64_t *data = array_ptr(vm, addr, u);
if (!data) { q48_push_inf(vm, 0); return; }
q48_push_inf(vm, (q48_16_t)infer_decay_slope_q48(data, (uint64_t)u));
}
/* INFER-WINDOW-WIDTH ( addr u -- n ) — optimal window width */
static void infer_word_window_width(VM *vm)
{
cell_t u = VM_POP(vm);
vaddr_t addr = (vaddr_t)VM_POP(vm);
const uint64_t *data = array_ptr(vm, addr, u);
if (!data) { VM_PUSH(vm, 0); return; }
q48_16_t var = compute_variance_q48(data, (uint64_t)u);
uint32_t w = find_variance_inflection(data, (uint64_t)u, var);
VM_PUSH(vm, (cell_t)(int64_t)w);
}
/* ── rolling-window stats ─────────────────────────────────────────────── */
/* WINDOW-DIVERSITY ( -- u ) */
static void infer_word_window_diversity(VM *vm)
{
uint64_t d = rolling_window_measure_diversity(&vm->rolling_window);
VM_PUSH(vm, (cell_t)(int64_t)d);
}
/* ── full inference run ────────────────────────────────────────────────── */
/*
* INFER-RUN ( -- )
* Runs the full inference engine on this VM's rolling window and dictionary
* heat, updating vm->last_inference_outputs. Allocates the outputs struct
* on first call (matches the pattern in vm_time.c).
*/
static void infer_word_run(VM *vm)
{
/* Allocate outputs struct if not yet done */
if (!vm->last_inference_outputs) {
vm->last_inference_outputs = (InferenceOutputs *)sf_malloc(sizeof(InferenceOutputs));
if (!vm->last_inference_outputs) { vm->error = 1; return; }
memset(vm->last_inference_outputs, 0, sizeof(InferenceOutputs));
}
/* Walk dictionary to collect heat stats (mirror of vm_time.c) */
uint64_t hot_word_count = 0;
uint64_t stale_word_count = 0;
uint64_t total_heat = 0;
uint32_t word_count = 0;
sf_mutex_lock(&vm->dict_lock);
DictEntry *e = vm->latest;
while (e) {
if (e->execution_heat > HOTWORDS_EXECUTION_HEAT_THRESHOLD)
hot_word_count++;
else if (e->execution_heat > 0 && e->execution_heat < 10)
stale_word_count++;
total_heat += e->execution_heat;
word_count++;
e = e->link;
}
sf_mutex_unlock(&vm->dict_lock);
uint64_t traj_len = (vm->rolling_window.window_pos > 0)
? vm->rolling_window.window_pos
: vm->rolling_window.total_executions;
InferenceInputs inputs;
memset(&inputs, 0, sizeof(inputs));
inputs.vm = vm;
inputs.window = &vm->rolling_window;
inputs.trajectory_length = traj_len;
inputs.prefetch_hits = vm->pipeline_metrics.prefetch_hits;
inputs.prefetch_attempts = vm->pipeline_metrics.prefetch_attempts;
inputs.hot_word_count = hot_word_count;
inputs.stale_word_count = stale_word_count;
inputs.total_heat = total_heat;
inputs.word_count = word_count;
inputs.last_total_heat = vm->total_heat_at_last_check;
inputs.last_stale_count = vm->stale_word_count_at_check;
inference_engine_run(&inputs, vm->last_inference_outputs);
}
/* ── inference output accessors ───────────────────────────────────────── */
/* INFER-WINDOW@ ( -- u ) */
static void infer_word_window_fetch(VM *vm)
{
uint32_t w = vm->last_inference_outputs
? vm->last_inference_outputs->adaptive_window_width : 0;
VM_PUSH(vm, (cell_t)(int64_t)w);
}
/* INFER-DECAY@ ( -- q ) */
static void infer_word_decay_fetch(VM *vm)
{
uint64_t d = vm->last_inference_outputs
? vm->last_inference_outputs->adaptive_decay_slope : 0;
q48_push_inf(vm, (q48_16_t)d);
}
/* INFER-VARIANCE@ ( -- q ) */
static void infer_word_variance_fetch(VM *vm)
{
uint64_t v = vm->last_inference_outputs
? vm->last_inference_outputs->window_variance_q48 : 0;
q48_push_inf(vm, (q48_16_t)v);
}
/* INFER-FIT@ ( -- q ) */
static void infer_word_fit_fetch(VM *vm)
{
uint64_t f = vm->last_inference_outputs
? vm->last_inference_outputs->slope_fit_quality_q48 : 0;
q48_push_inf(vm, (q48_16_t)f);
}
/* INFER-EARLY-EXIT@ ( -- flag ) */
static void infer_word_early_exit_fetch(VM *vm)
{
uint32_t ex = vm->last_inference_outputs
? vm->last_inference_outputs->early_exited : 0;
VM_PUSH(vm, (cell_t)(int64_t)ex);
}
/* ── L8 Jacquard ──────────────────────────────────────────────────────── */
/* L8-MODE ( -- n ) */
static void infer_word_l8_mode(VM *vm)
{
ssm_l8_state_t *l8 = (ssm_l8_state_t *)vm->ssm_l8_state;
int mode = l8 ? (int)l8->current_mode : 0;
VM_PUSH(vm, (cell_t)mode);
}
/*
* L8-UPDATE ( entropy_q cv_q temporal_q stability_q -- )
* Takes four Q48.16 values, converts to double, calls ssm_l8_update().
* Stack order: stability TOS, temporal, cv, entropy at bottom.
*/
static void infer_word_l8_update(VM *vm)
{
ssm_l8_state_t *l8 = (ssm_l8_state_t *)vm->ssm_l8_state;
if (!l8) { VM_POP(vm); VM_POP(vm); VM_POP(vm); VM_POP(vm); return; }
ssm_l8_metrics_t metrics;
metrics.stability_score = q48_to_dbl(q48_pop_inf(vm));
metrics.temporal_decay = q48_to_dbl(q48_pop_inf(vm));
metrics.cv = q48_to_dbl(q48_pop_inf(vm));
metrics.entropy = q48_to_dbl(q48_pop_inf(vm));
ssm_l8_update(&metrics, l8);
}
/* L8-APPLY ( -- ) */
static void infer_word_l8_apply(VM *vm)
{
ssm_l8_state_t *l8 = (ssm_l8_state_t *)vm->ssm_l8_state;
ssm_config_t *cfg = (ssm_config_t *)vm->ssm_config;
if (!l8 || !cfg) return;
ssm_apply_mode(l8, cfg);
}
/*
* L8-TABLE-FORCE ( config_idx -- )
* Forces the adaptive 128-config table onto config_idx (masked to 0-127)
* as if the bandit's own UCB selection had picked it, and applies its
* bits immediately. Unlike L8-UPDATE/L8-APPLY (the legacy 16-mode path,
* which the table's own periodic heartbeat tick ignores and will
* overwrite at its next trial boundary regardless), this drives the same
* mechanism the heartbeat itself uses, so an external choice (e.g. a DoE
* campaign) stays in effect and gets scored coherently by the bandit's
* own reward loop rather than being silently overwritten out from under
* it. See ssm_l8_force_config() for the full rationale.
*/
static void infer_word_l8_table_force(VM *vm)
{
ssm_l8_state_t *l8 = (ssm_l8_state_t *)vm->ssm_l8_state;
ssm_config_t *cfg = (ssm_config_t *)vm->ssm_config;
cell_t idx = VM_POP(vm);
if (!l8 || !cfg) return;
ssm_l8_force_config(l8, cfg, (uint8_t)idx);
}
/* ── Bayesian latency posteriors ──────────────────────────────────────── */
static BayesianLatencyPosterior cache_posterior(VM *vm)
{
BayesianLatencyPosterior zero;
memset(&zero, 0, sizeof(zero));
if (!vm->hotwords_cache) return zero;
return hotwords_posterior_cache_hits(&vm->hotwords_cache->stats);
}
static BayesianLatencyPosterior bucket_posterior(VM *vm)
{
BayesianLatencyPosterior zero;
memset(&zero, 0, sizeof(zero));
if (!vm->hotwords_cache) return zero;
return hotwords_posterior_bucket_searches(&vm->hotwords_cache->stats);
}
static void infer_word_bayes_cache_mean(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)cache_posterior(vm).mean_ns_q48);
}
static void infer_word_bayes_cache_lower(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)cache_posterior(vm).credible_lower_95);
}
static void infer_word_bayes_cache_upper(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)cache_posterior(vm).credible_upper_95);
}
static void infer_word_bayes_bucket_mean(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)bucket_posterior(vm).mean_ns_q48);
}
static void infer_word_bayes_bucket_lower(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)bucket_posterior(vm).credible_lower_95);
}
static void infer_word_bayes_bucket_upper(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)bucket_posterior(vm).credible_upper_95);
}
/* ── registration ─────────────────────────────────────────────────────── */
void register_inference_words(VM *vm)
{
register_word(vm, "Q.VARIANCE", infer_word_q_variance);
register_word(vm, "INFER-DECAY-SLOPE", infer_word_decay_slope);
register_word(vm, "INFER-WINDOW-WIDTH", infer_word_window_width);
register_word(vm, "WINDOW-DIVERSITY", infer_word_window_diversity);
register_word(vm, "INFER-RUN", infer_word_run);
register_word(vm, "INFER-WINDOW@", infer_word_window_fetch);
register_word(vm, "INFER-DECAY@", infer_word_decay_fetch);
register_word(vm, "INFER-VARIANCE@", infer_word_variance_fetch);
register_word(vm, "INFER-FIT@", infer_word_fit_fetch);
register_word(vm, "INFER-EARLY-EXIT@", infer_word_early_exit_fetch);
register_word(vm, "L8-MODE", infer_word_l8_mode);
register_word(vm, "L8-UPDATE", infer_word_l8_update);
register_word(vm, "L8-APPLY", infer_word_l8_apply);
register_word(vm, "L8-TABLE-FORCE", infer_word_l8_table_force);
register_word(vm, "BAYES-CACHE-MEAN", infer_word_bayes_cache_mean);
register_word(vm, "BAYES-CACHE-LOWER", infer_word_bayes_cache_lower);
register_word(vm, "BAYES-CACHE-UPPER", infer_word_bayes_cache_upper);
register_word(vm, "BAYES-BUCKET-MEAN", infer_word_bayes_bucket_mean);
register_word(vm, "BAYES-BUCKET-LOWER", infer_word_bayes_bucket_lower);
register_word(vm, "BAYES-BUCKET-UPPER", infer_word_bayes_bucket_upper);
}