267 lines
12 KiB
C
267 lines
12 KiB
C
/*
|
||
StarForth — Steady-State Virtual Machine Runtime
|
||
|
||
Copyright (c) 2023–2025 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) 2023–2025 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 */ |