Files

267 lines
12 KiB
C
Raw Permalink 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.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef SSM_JACQUARD_H
#define SSM_JACQUARD_H
#include <stdint.h>
#include "compudynamics.h"
#include "starforth_config.h"
/* ============================================================================
* SSM L8: Jacquard Mode Selector (Data-Driven Architecture)
* ============================================================================
*
* Based on 2^7 DoE with 300 reps (128 configurations, 38,400 total runs).
* Top 5% analysis reveals optimal loop combinations for speed + stability.
*
* Architecture (Experimentally Validated):
* - L1 (heat_tracking): DISABLED (harmful in 86% of top configs)
* - L4 (pipelining_metrics): DISABLED (harmful in 100% of top configs)
* - L7 (adaptive_heartrate): ALWAYS ON (beneficial in 71% of top configs)
* - L2, L3, L5, L6: Runtime-controlled by L8 (workload-dependent)
* - L8 (Jacquard): 4-bit selector (16 modes) controlling L2/L3/L5/L6
*
* L8 operates as a multi-dimensional classifier:
* L2 (window): ON if entropy > 0.75 (diversity tracking)
* L3 (decay): ON if temporal_decay > 0.5 (temporal locality)
* L5 (window_inf): ON if cv > 0.15 (variance adaptation)
* L6 (decay_inf): ON if cv > 0.15 AND temporal_decay > 0.3
*
* Top 5% Validated Modes:
* C4 (0100): L2=0, L3=1, L5=0, L6=0 - Temporal locality
* C7 (0111): L2=0, L3=1, L5=1, L6=1 - Full inference
* C9 (1001): L2=1, L3=0, L5=0, L6=1 - Diverse + decay_inf
* C11 (1011): L2=1, L3=0, L5=1, L6=1 - Diverse + inference
* C12 (1100): L2=1, L3=1, L5=0, L6=0 - Diverse + temporal
*/
/* ============================================================================
* L8 Mode Definitions (4-bit: L2/L3/L5/L6)
* ============================================================================
*/
typedef enum {
/* Bits: L2 L3 L5 L6 */
SSM_MODE_C0 = 0x0, /* 0000: Minimal (stable/predictable workloads) */
SSM_MODE_C1 = 0x1, /* 0001: L6 only (decay inference) */
SSM_MODE_C2 = 0x2, /* 0010: L5 only (window inference) */
SSM_MODE_C3 = 0x3, /* 0011: L5+L6 (volatile workloads) */
SSM_MODE_C4 = 0x4, /* 0100: L3 only (temporal locality) ✅ TOP 5% */
SSM_MODE_C5 = 0x5, /* 0101: L3+L6 (temporal + decay_inf) */
SSM_MODE_C6 = 0x6, /* 0110: L3+L5 (temporal + window_inf) */
SSM_MODE_C7 = 0x7, /* 0111: L3+L5+L6 (full inference) ✅ TOP 5% */
SSM_MODE_C8 = 0x8, /* 1000: L2 only (high diversity) */
SSM_MODE_C9 = 0x9, /* 1001: L2+L6 (diverse + decay_inf) ✅ TOP 5% */
SSM_MODE_C10 = 0xA, /* 1010: L2+L5 (diverse + window_inf) */
SSM_MODE_C11 = 0xB, /* 1011: L2+L5+L6 (diverse + inference) ✅ TOP 5% */
SSM_MODE_C12 = 0xC, /* 1100: L2+L3 (diverse + temporal) ✅ TOP 5% */
SSM_MODE_C13 = 0xD, /* 1101: L2+L3+L6 (complex workload) */
SSM_MODE_C14 = 0xE, /* 1110: L2+L3+L5 (full adaptive, no decay_inf) */
SSM_MODE_C15 = 0xF /* 1111: L2+L3+L5+L6 (full adaptive, all on) */
} ssm_l8_mode_t;
/* ============================================================================
* L8 Metrics (Input to Mode Selection)
* ============================================================================
*/
typedef struct {
double entropy; /* Rolling-window entropy/diversity (0.0-1.0) */
double cv; /* Coefficient of variation (short-term volatility) */
double temporal_decay; /* Temporal locality strength (0.0-1.0) */
double stability_score; /* Combined stability metric for hysteresis */
int inference_ran_this_tick; /* 1 if inference engine ran this tick */
int inference_early_exited; /* 1 if inference early-exited (ANOVA stable) */
} ssm_l8_metrics_t;
/* ============================================================================
* SSM Configuration (L1-L7 Mode Bits)
* ============================================================================
*
* L1/L4/L7 were runtime-inert until this field expansion: the adaptive
* table (SsmConfigTable, below) always selected across the full 128-config
* (7-bit) space, but ssm_apply_mode_from_table() and its per-tick scoring
* counterpart only ever wrote 4 of those 7 bits out here, so L1/L4/L7 were computed by
* the bandit's UCB selection and then silently discarded before reaching
* any real gate -- those three loops were governed purely by compile-time
* macros (ENABLE_HOTWORDS_CACHE, ENABLE_PIPELINING, HEARTBEAT_THREAD_ENABLED)
* for the whole process lifetime. Adding these fields, and wiring them at
* their four call sites alongside (not instead of) the compile-time macros,
* makes the bandit's full selection actually take effect. */
typedef struct {
int L1_heat_tracking; /* 1 = hotwords cache active, 0 = off */
int L2_rolling_window; /* 1 = window tracking active, 0 = off */
int L3_linear_decay; /* 1 = decay active, 0 = off */
int L4_pipelining; /* 1 = pipelining metrics active, 0 = off */
int L5_window_inference; /* 1 = window inference active, 0 = off */
int L6_decay_inference; /* 1 = decay inference active, 0 = off */
int L7_adaptive_heartrate; /* 1 = heartbeat thread active, 0 = off */
} ssm_config_t;
/* ============================================================================
* L8 Configuration Thresholds (Data-Driven from DoE)
* ============================================================================
* Defaults live in starforth_config.h (included above), the single source
* of truth for VM build-time defaults; overridable via -D exactly as before.
*/
/* ============================================================================
* Adaptive Table Constants (L8 Heat-Ranked 128-Config Selector)
* One clock only: the heartbeat tick (rev r). Scoring and UCB reselection
* both run every tick -- no derived multi-tick batching unit exists
* anywhere in this mechanism. The score/UCB/reward/weight constants live
* in cd_tuning_word() (compudynamics.h/.c) now, not here -- this is the
* word-level instantiation of the generic compudynamics module (rev s).
* ============================================================================
*/
/* ============================================================================
* 7-bit Config Bit Positions (b6=L1, b5=L2, b4=L3, b3=L4, b2=L5, b1=L6, b0=L7)
* ============================================================================
*/
#define SSM_CFG_L1 0x40u
#define SSM_CFG_L2 0x20u
#define SSM_CFG_L3 0x10u
#define SSM_CFG_L4 0x08u
#define SSM_CFG_L5 0x04u
#define SSM_CFG_L6 0x02u
#define SSM_CFG_L7 0x01u
/* ============================================================================
* L8 State (extended with adaptive table pointer)
* ============================================================================
*
* The adaptive table itself (CDConfigEntry/CDConfigTable) is the generic
* compudynamics bandit (compudynamics.h), sized to 128 configs x 8 regimes
* via cd_tuning_word() -- see ssm_l8_init_table().
*/
typedef struct {
ssm_l8_mode_t current_mode; /* Mirrored from table for diagnostics */
uint32_t hysteresis_counter; /* Legacy threshold mode hysteresis */
ssm_l8_mode_t pending_mode; /* Legacy threshold mode pending */
CDConfigTable *table; /* NULL = legacy threshold; non-NULL = adaptive */
} ssm_l8_state_t;
/* ============================================================================
* L8 API
* ============================================================================
*/
/**
* @brief Initialize L8 state (legacy fields only; call ssm_l8_init_table separately)
*/
void ssm_l8_init(ssm_l8_state_t *state, ssm_l8_mode_t initial_mode);
/**
* @brief Allocate and seed the adaptive config table
* Sets state->table; must be called after ssm_l8_init().
* On allocation failure, state->table remains NULL (legacy mode is used).
*/
void ssm_l8_init_table(ssm_l8_state_t *state);
/**
* @brief Free the adaptive config table
*/
void ssm_l8_free_table(ssm_l8_state_t *state);
/*
* @brief Per-tick update for the adaptive table path
*
* Classifies the current regime, scores the current config from this
* tick's outcome, and UCB-reselects -- every call, no batching -- and
* applies the selected config to ssm_config_t.
*
* The locality component of the regime classification (and of the
* joint-convergence reward signal) is computed here from recent_word_ids
* via cd_classify_ids() -- purely execution-count-derived, no wall-clock
* input. This replaced a decay-slope-derived temporal_decay signal (rev
* r) that made L8's regime classification wall-clock-tainted once it ran
* every tick instead of once per ~2000 ticks, breaking cross-architecture
* dict_hash determinism under QEMU/TCG's differing real emulation speed
* per ISA (rev s).
*
* @param state L8 state (must have state->table != NULL)
* @param metrics Current runtime metrics (entropy, cv, inference status)
* @param config SSM config updated this tick
* @param current_window VM's effective_window_size this tick
* @param recent_word_ids Recent word-execution-ID history, oldest first
* (e.g. from rolling_window_get_recent_sequence())
* @param recent_word_ids_count Number of valid entries in recent_word_ids
*/
void ssm_l8_update_table(ssm_l8_state_t *state, const ssm_l8_metrics_t *metrics,
ssm_config_t *config,
uint32_t current_window,
const uint32_t *recent_word_ids, uint32_t recent_word_ids_count);
/**
* @brief Apply the current table config to ssm_config_t
* Used on first tick to set the initial DoE-seeded config immediately.
*/
void ssm_apply_mode_from_table(const ssm_l8_state_t *state, ssm_config_t *config);
/**
* @brief Force the adaptive table onto an externally-chosen config
* (e.g. a DoE campaign driving a specific tick's config rather than
* letting the bandit's own UCB selection choose it). See definition for
* details on why this stays coherent with the bandit's own reward loop.
*/
void ssm_l8_force_config(ssm_l8_state_t *state, ssm_config_t *config, uint8_t config_idx);
/**
* @brief Update L8 state based on current metrics (legacy threshold path)
*/
void ssm_l8_update(const ssm_l8_metrics_t *metrics, ssm_l8_state_t *state);
/**
* @brief Apply L8 mode to SSM configuration (legacy threshold path)
*/
void ssm_apply_mode(const ssm_l8_state_t *state, ssm_config_t *config);
/**
* @brief Get human-readable mode name
*/
const char* ssm_l8_mode_name(ssm_l8_mode_t mode);
#endif /* SSM_JACQUARD_H */