kronos-mql5/Scripts/Kronos/KronosVerifyKVCache.mq5
2026-07-05 05:06:39 +00:00

219 lines
9.4 KiB
MQL5

//+------------------------------------------------------------------+
//| KronosVerifyKVCache.mq5 |
//| MMQ — Muhammad Minhas Qamar |
//| www.mql5.com |
//+------------------------------------------------------------------+
//| Development scaffold for the decode_s1 KV-cache (grow phase). |
//| For the first few AR steps it runs BOTH paths and asserts they |
//| agree to floating-point round-off: |
//| - cached: PrimeCache once, then DecodeS1Step per new token |
//| - full: DecodeS1 over the growing window each step |
//| The last-row s1_logits must match to ~1e-9 (different summation |
//| order only). This localizes any RoPE-position, append-order, or |
//| block-threading bug at its source, before the integer-match |
//| harnesses (VerifyInference / VerifySlide). Greedy picks keep the |
//| generated token sequence deterministic across both paths. |
//| |
//| Remove this scaffold once green (the full DecodeS1 stays in the |
//| library as the slide-phase path and reference math). |
//| |
//| Placement: kronos_refs/{x_norm,x_stamp,y_stamp}.bin and |
//| kronos_weights/{tokenizer,predictor}/*.bin |
//+------------------------------------------------------------------+
#property copyright "MMQ — Muhammad Minhas Qamar"
#property link "https://www.mql5.com"
#property version "1.00"
#property script_show_inputs
#property strict
#include "KronosInference.mqh"
#define KR_TOK_DIR "kronos_weights\\tokenizer\\"
#define KR_TOK_DM 256
#define KR_TOK_HEADS 4
#define KR_TOK_ENC 4
#define KR_TOK_DEC 4
#define KR_TOK_FF 512
#define KR_PRED_DIR "kronos_weights\\predictor\\"
#define KR_PRED_DM 512
#define KR_PRED_HEADS 8
#define KR_PRED_LAYERS 8
#define KR_PRED_FF 1024
input string InpRefDir = "kronos_refs\\";
input int InpLookback = 256;
input int InpSteps = 6; // AR steps to cross-check
input double InpTol = 1e-7; // max abs logit delta tolerated
//+------------------------------------------------------------------+
//| Read a [rows,cols] float32 .bin into a matrix(double). |
//+------------------------------------------------------------------+
bool LoadF32Matrix(const string fname, ulong rows, ulong cols, matrix &out)
{
int h = FileOpen(fname, FILE_READ | FILE_BIN);
if(h == INVALID_HANDLE)
{ PrintFormat("open fail %s (%d)", fname, GetLastError()); return false; }
ulong n = rows * cols;
float buf[];
ArrayResize(buf, (int)n);
uint got = FileReadArray(h, buf, 0, (int)n);
FileClose(h);
if(got != n)
{ PrintFormat("short read %s", fname); return false; }
out = matrix::Zeros(rows, cols);
for(ulong r = 0; r < rows; r++)
for(ulong c = 0; c < cols; c++)
out[r][c] = (double)buf[r*cols+c];
return true;
}
//+------------------------------------------------------------------+
//| Greedy argmax of a logits row (matches SampleFromLogits greedy). |
//+------------------------------------------------------------------+
int ArgmaxRow(const matrix &logits, int row)
{
int best = 0;
double bv = logits[row][0];
for(int j = 1; j < (int)logits.Cols(); j++)
if(logits[row][j] > bv) { bv = logits[row][j]; best = j; }
return best;
}
//+------------------------------------------------------------------+
//| Max abs delta between the last row of two logit matrices. |
//+------------------------------------------------------------------+
double LastRowMaxDelta(const matrix &A, int ra, const matrix &B, int rb)
{
double m = 0.0;
for(int j = 0; j < (int)A.Cols(); j++)
{
double d = MathAbs(A[ra][j] - B[rb][j]);
if(d > m) m = d;
}
return m;
}
//+------------------------------------------------------------------+
//| Script entry point. |
//+------------------------------------------------------------------+
void OnStart()
{
Print("============ Kronos KV-cache cross-check (cached vs full decode_s1) ============");
const int L = InpLookback;
matrix x_norm, x_stamp, y_stamp;
if(!LoadF32Matrix(InpRefDir + "x_norm.bin", (ulong)L, KR_NFEAT, x_norm)) { Print("ABORT x_norm"); return; }
if(!LoadF32Matrix(InpRefDir + "x_stamp.bin", (ulong)L, 5, x_stamp)) { Print("ABORT x_stamp"); return; }
if(!LoadF32Matrix(InpRefDir + "y_stamp.bin", (ulong)InpSteps, 5, y_stamp)){ Print("ABORT y_stamp"); return; }
matrix full_stamp = matrix::Zeros((ulong)(L + InpSteps), 5);
for(int i = 0; i < L; i++)
for(int j = 0; j < 5; j++)
full_stamp[i][j] = x_stamp[i][j];
for(int i = 0; i < InpSteps; i++)
for(int j = 0; j < 5; j++)
full_stamp[L + i][j] = y_stamp[i][j];
// We need access to the predictor/encoder stages directly. CKronosModel
// wraps them privately, so build the encoder + predictor S1/S2 here.
CKronosEncoder enc;
CKronosPredictorS1 p1;
CKronosPredictorS2 p2;
bool ok = true;
ok &= enc.Init(KR_TOK_DIR, KR_TOK_ENC, KR_TOK_DM, KR_TOK_HEADS, KR_TOK_FF);
ok &= p1.Init(KR_PRED_DIR, KR_PRED_LAYERS, KR_PRED_DM, KR_PRED_HEADS, KR_PRED_FF);
ok &= p2.Init(KR_PRED_DIR, KR_PRED_DM);
if(!ok) { Print("ABORT: component init failed"); return; }
Print("Components loaded (encoder, predictor s1/s2).");
// encode context -> base tokens
int base_s1[], base_s2[];
if(!enc.Encode(x_norm, base_s1, base_s2)) { Print("ABORT: encode failed"); return; }
// ---- prime the cache over the L base tokens ----
int win0_s1[], win0_s2[];
ArrayResize(win0_s1, L); ArrayResize(win0_s2, L);
for(int t = 0; t < L; t++) { win0_s1[t] = base_s1[t]; win0_s2[t] = base_s2[t]; }
matrix stamp0 = matrix::Zeros((ulong)L, 5);
for(int t = 0; t < L; t++) for(int j = 0; j < 5; j++) stamp0[t][j] = full_stamp[t][j];
matrix prime_logits, prime_ctx;
if(!p1.PrimeCache(win0_s1, win0_s2, stamp0, prime_logits, prime_ctx))
{ Print("ABORT: PrimeCache failed"); return; }
// running token buffers for the FULL-path window
int pre[], post[];
ArrayCopy(pre, base_s1); ArrayCopy(post, base_s2);
double worst = 0.0;
int fails = 0;
for(int i = 0; i < InpSteps; i++)
{
int seqlen = L + i; // window length for the full path this step
// ----- cached path: step-0 reuses primed logits, else DecodeS1Step -----
matrix cached_logits, cached_ctx;
int cl_row;
if(i == 0)
{
cached_logits = prime_logits;
p1.GetContext(cached_ctx);
cl_row = (int)cached_logits.Rows() - 1;
}
else
{
vector srow = vector::Zeros(5);
for(int j = 0; j < 5; j++) srow[j] = full_stamp[L + i - 1][j];
if(!p1.DecodeS1Step(pre[ArraySize(pre)-1], post[ArraySize(post)-1], srow, cached_logits, cached_ctx))
{ Print("ABORT: DecodeS1Step failed"); return; }
cl_row = 0;
}
// ----- full path: DecodeS1 over the current window -----
int win_s1[], win_s2[];
ArrayResize(win_s1, seqlen); ArrayResize(win_s2, seqlen);
for(int t = 0; t < seqlen; t++) { win_s1[t] = pre[t]; win_s2[t] = post[t]; }
matrix stamp_win = matrix::Zeros((ulong)seqlen, 5);
for(int t = 0; t < seqlen; t++) for(int j = 0; j < 5; j++) stamp_win[t][j] = full_stamp[t][j];
matrix full_logits, full_ctx;
if(!p1.DecodeS1(win_s1, win_s2, stamp_win, full_logits, full_ctx))
{ Print("ABORT: DecodeS1 failed"); return; }
int fl_row = seqlen - 1;
// ----- compare last-row s1 logits -----
double d = LastRowMaxDelta(cached_logits, cl_row, full_logits, fl_row);
if(d > worst) worst = d;
int a_cached = ArgmaxRow(cached_logits, cl_row);
int a_full = ArgmaxRow(full_logits, fl_row);
bool argok = (a_cached == a_full);
bool tolok = (d <= InpTol);
if(!argok || !tolok) fails++;
PrintFormat("step %2d: max|dlogit|=%.3e argmax cached=%d full=%d %s",
i, d, a_cached, a_full,
(argok && tolok) ? "OK" : "FAIL");
// advance the FULL-path buffer by the greedy s1 pick (deterministic).
// s2 pick irrelevant to decode_s1, but we must append a token; use the
// s2 argmax conditioned via decode_s2 over the cached full context so
// both paths consume identical tokens.
int s1_pick = a_full; // greedy
int pick_arr[]; ArrayResize(pick_arr, 1); pick_arr[0] = s1_pick;
matrix s2_logits;
if(!p2.DecodeS2(full_ctx, pick_arr, s2_logits)) { Print("ABORT: DecodeS2"); return; }
int s2_pick = ArgmaxRow(s2_logits, (int)s2_logits.Rows() - 1);
int n = ArraySize(pre);
ArrayResize(pre, n + 1); pre[n] = s1_pick;
ArrayResize(post, n + 1); post[n] = s2_pick;
}
PrintFormat("worst max|dlogit| over %d steps = %.3e (tol %.1e)", InpSteps, worst, InpTol);
if(fails == 0)
Print(">>> KV-CACHE CROSS-CHECK PASSED: cached decode_s1 == full path within tolerance. <<<");
else
PrintFormat(">>> KV-CACHE CROSS-CHECK FAILED: %d step(s) diverged. <<<", fails);
Print("================================================================================");
}
//+------------------------------------------------------------------+