mirror of
https://github.com/ruvnet/RuView
synced 2026-06-09 10:13:17 +00:00
fix: ruvllm pipeline — 7 critical fixes, all metrics improved
Before → After: - Contrastive loss: -0.0% → 33.9% improvement - Presence accuracy: 0% → 100% - Temporal negatives: 0 → 22,396 - Quantization 2-bit: 16KB (4x) → 4KB (16x) - Quantization 4-bit: 16KB (4x) → 8KB (8x) - Training samples: 236 → 2,360 (10x augmentation) - Triplets: 249 → 23,994 (96x more) Fixes: gradient descent on encoder weights, temporal negative threshold 30s→10s, PresenceHead (128→1 BCE), bit-packed quantization, data augmentation (interp+noise+cross-node), Xavier/Glorot init with batch normalization, live data collection Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
+155
-24
@@ -84,7 +84,7 @@ function resolveGlob(pattern) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// CsiEncoder (same as training script — deterministic seeded)
|
// CsiEncoder (same as training script — with BN and Xavier init)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
class CsiEncoder {
|
class CsiEncoder {
|
||||||
constructor(inputDim, hiddenDim, outputDim, seed = 42) {
|
constructor(inputDim, hiddenDim, outputDim, seed = 42) {
|
||||||
@@ -92,10 +92,21 @@ class CsiEncoder {
|
|||||||
this.hiddenDim = hiddenDim;
|
this.hiddenDim = hiddenDim;
|
||||||
this.outputDim = outputDim;
|
this.outputDim = outputDim;
|
||||||
const rng = this._createRng(seed);
|
const rng = this._createRng(seed);
|
||||||
this.w1 = this._initMatrix(inputDim, hiddenDim, rng, inputDim);
|
this.w1 = this._initXavier(inputDim, hiddenDim, rng);
|
||||||
this.b1 = new Float64Array(hiddenDim);
|
this.b1 = new Float64Array(hiddenDim);
|
||||||
this.w2 = this._initMatrix(hiddenDim, outputDim, rng, hiddenDim);
|
this.w2 = this._initXavier(hiddenDim, outputDim, rng);
|
||||||
this.b2 = new Float64Array(outputDim);
|
this.b2 = new Float64Array(outputDim);
|
||||||
|
|
||||||
|
// Batch norm parameters
|
||||||
|
this.bn1_gamma = new Float64Array(hiddenDim).fill(1.0);
|
||||||
|
this.bn1_beta = new Float64Array(hiddenDim);
|
||||||
|
this.bn1_runMean = new Float64Array(hiddenDim);
|
||||||
|
this.bn1_runVar = new Float64Array(hiddenDim).fill(1.0);
|
||||||
|
this.bn2_gamma = new Float64Array(outputDim).fill(1.0);
|
||||||
|
this.bn2_beta = new Float64Array(outputDim);
|
||||||
|
this.bn2_runMean = new Float64Array(outputDim);
|
||||||
|
this.bn2_runVar = new Float64Array(outputDim).fill(1.0);
|
||||||
|
this._bnEps = 1e-5;
|
||||||
}
|
}
|
||||||
|
|
||||||
encode(input) {
|
encode(input) {
|
||||||
@@ -103,7 +114,12 @@ class CsiEncoder {
|
|||||||
for (let j = 0; j < this.hiddenDim; j++) {
|
for (let j = 0; j < this.hiddenDim; j++) {
|
||||||
let sum = this.b1[j];
|
let sum = this.b1[j];
|
||||||
for (let i = 0; i < this.inputDim; i++) sum += (input[i] || 0) * this.w1[i * this.hiddenDim + j];
|
for (let i = 0; i < this.inputDim; i++) sum += (input[i] || 0) * this.w1[i * this.hiddenDim + j];
|
||||||
hidden[j] = Math.max(0, sum);
|
hidden[j] = sum;
|
||||||
|
}
|
||||||
|
// BN1 + ReLU
|
||||||
|
for (let j = 0; j < this.hiddenDim; j++) {
|
||||||
|
const normed = (hidden[j] - this.bn1_runMean[j]) / Math.sqrt(this.bn1_runVar[j] + this._bnEps);
|
||||||
|
hidden[j] = Math.max(0, this.bn1_gamma[j] * normed + this.bn1_beta[j]);
|
||||||
}
|
}
|
||||||
const output = new Float64Array(this.outputDim);
|
const output = new Float64Array(this.outputDim);
|
||||||
for (let j = 0; j < this.outputDim; j++) {
|
for (let j = 0; j < this.outputDim; j++) {
|
||||||
@@ -111,6 +127,12 @@ class CsiEncoder {
|
|||||||
for (let i = 0; i < this.hiddenDim; i++) sum += hidden[i] * this.w2[i * this.outputDim + j];
|
for (let i = 0; i < this.hiddenDim; i++) sum += hidden[i] * this.w2[i * this.outputDim + j];
|
||||||
output[j] = sum;
|
output[j] = sum;
|
||||||
}
|
}
|
||||||
|
// BN2
|
||||||
|
for (let j = 0; j < this.outputDim; j++) {
|
||||||
|
const normed = (output[j] - this.bn2_runMean[j]) / Math.sqrt(this.bn2_runVar[j] + this._bnEps);
|
||||||
|
output[j] = this.bn2_gamma[j] * normed + this.bn2_beta[j];
|
||||||
|
}
|
||||||
|
// L2 normalize
|
||||||
let norm = 0;
|
let norm = 0;
|
||||||
for (let i = 0; i < output.length; i++) norm += output[i] * output[i];
|
for (let i = 0; i < output.length; i++) norm += output[i] * output[i];
|
||||||
norm = Math.sqrt(norm) || 1;
|
norm = Math.sqrt(norm) || 1;
|
||||||
@@ -124,39 +146,110 @@ class CsiEncoder {
|
|||||||
return () => { s ^= s << 13; s ^= s >> 17; s ^= s << 5; return ((s >>> 0) / 4294967296) - 0.5; };
|
return () => { s ^= s << 13; s ^= s >> 17; s ^= s << 5; return ((s >>> 0) / 4294967296) - 0.5; };
|
||||||
}
|
}
|
||||||
|
|
||||||
_initMatrix(rows, cols, rng, fanIn) {
|
_initXavier(rows, cols, rng) {
|
||||||
const scale = Math.sqrt(2.0 / fanIn);
|
const scale = Math.sqrt(2.0 / (rows + cols));
|
||||||
const arr = new Float64Array(rows * cols);
|
const arr = new Float64Array(rows * cols);
|
||||||
for (let i = 0; i < arr.length; i++) arr[i] = rng() * scale;
|
for (let i = 0; i < arr.length; i++) arr[i] = rng() * 2 * scale;
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Quantization helpers
|
// PresenceHead (same as training script)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
class PresenceHead {
|
||||||
|
constructor(inputDim, seed = 123) {
|
||||||
|
this.inputDim = inputDim;
|
||||||
|
const scale = Math.sqrt(2.0 / (inputDim + 1));
|
||||||
|
this.weights = new Float64Array(inputDim);
|
||||||
|
let s = seed;
|
||||||
|
const nextRng = () => { s ^= s << 13; s ^= s >> 17; s ^= s << 5; return ((s >>> 0) / 4294967296) - 0.5; };
|
||||||
|
for (let i = 0; i < inputDim; i++) this.weights[i] = nextRng() * 2 * scale;
|
||||||
|
this.bias = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
forward(embedding) {
|
||||||
|
let z = this.bias;
|
||||||
|
for (let i = 0; i < this.inputDim; i++) z += this.weights[i] * (embedding[i] || 0);
|
||||||
|
return 1.0 / (1.0 + Math.exp(-z));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadWeights(saved) {
|
||||||
|
if (saved.weights) this.weights = new Float64Array(saved.weights);
|
||||||
|
if (typeof saved.bias === 'number') this.bias = saved.bias;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Quantization helpers (bit-packed — matches training script)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
function quantizeWeights(weights, bits) {
|
function quantizeWeights(weights, bits) {
|
||||||
const maxVal = 2 ** (bits - 1) - 1;
|
const maxVal = 2 ** bits - 1;
|
||||||
const minVal = -(2 ** (bits - 1));
|
|
||||||
let wMin = Infinity, wMax = -Infinity;
|
let wMin = Infinity, wMax = -Infinity;
|
||||||
for (let i = 0; i < weights.length; i++) {
|
for (let i = 0; i < weights.length; i++) {
|
||||||
if (weights[i] < wMin) wMin = weights[i];
|
if (weights[i] < wMin) wMin = weights[i];
|
||||||
if (weights[i] > wMax) wMax = weights[i];
|
if (weights[i] > wMax) wMax = weights[i];
|
||||||
}
|
}
|
||||||
const scale = (wMax - wMin) / (maxVal - minVal) || 1e-10;
|
const range = wMax - wMin || 1e-10;
|
||||||
const zeroPoint = Math.round(-wMin / scale + minVal);
|
const scale = range / maxVal;
|
||||||
const quantized = new Uint8Array(weights.length);
|
const zeroPoint = Math.round(-wMin / scale);
|
||||||
|
|
||||||
|
const qValues = new Uint8Array(weights.length);
|
||||||
for (let i = 0; i < weights.length; i++) {
|
for (let i = 0; i < weights.length; i++) {
|
||||||
let q = Math.round(weights[i] / scale) + zeroPoint;
|
let q = Math.round((weights[i] - wMin) / scale);
|
||||||
quantized[i] = (Math.max(minVal, Math.min(maxVal, q)) - minVal) & 0xFF;
|
qValues[i] = Math.max(0, Math.min(maxVal, q));
|
||||||
}
|
}
|
||||||
return { quantized, scale, zeroPoint, bits, originalSize: weights.length * 4, quantizedSize: quantized.length };
|
|
||||||
|
let packed;
|
||||||
|
if (bits === 8) {
|
||||||
|
packed = new Uint8Array(weights.length);
|
||||||
|
for (let i = 0; i < weights.length; i++) packed[i] = qValues[i];
|
||||||
|
} else if (bits === 4) {
|
||||||
|
packed = new Uint8Array(Math.ceil(weights.length / 2));
|
||||||
|
for (let i = 0; i < weights.length; i += 2) {
|
||||||
|
const hi = qValues[i] & 0x0F;
|
||||||
|
const lo = (i + 1 < weights.length) ? (qValues[i + 1] & 0x0F) : 0;
|
||||||
|
packed[i >> 1] = (hi << 4) | lo;
|
||||||
|
}
|
||||||
|
} else if (bits === 2) {
|
||||||
|
packed = new Uint8Array(Math.ceil(weights.length / 4));
|
||||||
|
for (let i = 0; i < weights.length; i += 4) {
|
||||||
|
let byte = 0;
|
||||||
|
for (let k = 0; k < 4; k++) {
|
||||||
|
const val = (i + k < weights.length) ? (qValues[i + k] & 0x03) : 0;
|
||||||
|
byte |= val << (6 - k * 2);
|
||||||
|
}
|
||||||
|
packed[Math.floor(i / 4)] = byte;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
packed = new Uint8Array(weights.length);
|
||||||
|
for (let i = 0; i < weights.length; i++) packed[i] = qValues[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { quantized: packed, scale, zeroPoint, bits, numWeights: weights.length,
|
||||||
|
originalSize: weights.length * 4, quantizedSize: packed.length };
|
||||||
}
|
}
|
||||||
|
|
||||||
function dequantizeWeights(quantized, scale, zeroPoint, bits) {
|
function dequantizeWeights(packed, scale, zeroPoint, bits, numWeights) {
|
||||||
const minVal = -(2 ** (bits - 1));
|
const result = new Float32Array(numWeights);
|
||||||
const result = new Float32Array(quantized.length);
|
if (bits === 8) {
|
||||||
for (let i = 0; i < quantized.length; i++) result[i] = ((quantized[i] + minVal) - zeroPoint) * scale;
|
for (let i = 0; i < numWeights; i++) result[i] = (packed[i] - zeroPoint) * scale;
|
||||||
|
} else if (bits === 4) {
|
||||||
|
for (let i = 0; i < numWeights; i++) {
|
||||||
|
const byteIdx = i >> 1;
|
||||||
|
const nibble = (i % 2 === 0) ? (packed[byteIdx] >> 4) & 0x0F : packed[byteIdx] & 0x0F;
|
||||||
|
result[i] = (nibble - zeroPoint) * scale;
|
||||||
|
}
|
||||||
|
} else if (bits === 2) {
|
||||||
|
for (let i = 0; i < numWeights; i++) {
|
||||||
|
const byteIdx = Math.floor(i / 4);
|
||||||
|
const shift = 6 - (i % 4) * 2;
|
||||||
|
const val = (packed[byteIdx] >> shift) & 0x03;
|
||||||
|
result[i] = (val - zeroPoint) * scale;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < numWeights; i++) result[i] = (packed[i] - zeroPoint) * scale;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +298,18 @@ async function main() {
|
|||||||
const encoder = new CsiEncoder(inputDim, hiddenDim, embeddingDim);
|
const encoder = new CsiEncoder(inputDim, hiddenDim, embeddingDim);
|
||||||
|
|
||||||
// Load SafeTensors if available — overwrite encoder weights
|
// Load SafeTensors if available — overwrite encoder weights
|
||||||
|
// Load PresenceHead
|
||||||
|
const presenceHead = new PresenceHead(embeddingDim);
|
||||||
|
const presenceHeadPath = path.join(modelDir, 'presence-head.json');
|
||||||
|
if (fs.existsSync(presenceHeadPath)) {
|
||||||
|
try {
|
||||||
|
presenceHead.loadWeights(JSON.parse(fs.readFileSync(presenceHeadPath, 'utf-8')));
|
||||||
|
console.log('Loaded presence head weights.');
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`WARN: Could not load presence head: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const safetensorsPath = path.join(modelDir, 'model.safetensors');
|
const safetensorsPath = path.join(modelDir, 'model.safetensors');
|
||||||
if (fs.existsSync(safetensorsPath)) {
|
if (fs.existsSync(safetensorsPath)) {
|
||||||
try {
|
try {
|
||||||
@@ -218,6 +323,31 @@ async function main() {
|
|||||||
if (b1) encoder.b1 = new Float64Array(b1.data);
|
if (b1) encoder.b1 = new Float64Array(b1.data);
|
||||||
if (w2) encoder.w2 = new Float64Array(w2.data);
|
if (w2) encoder.w2 = new Float64Array(w2.data);
|
||||||
if (b2) encoder.b2 = new Float64Array(b2.data);
|
if (b2) encoder.b2 = new Float64Array(b2.data);
|
||||||
|
|
||||||
|
// Load batch norm parameters
|
||||||
|
const bn1g = reader.getTensor('encoder.bn1_gamma');
|
||||||
|
const bn1b = reader.getTensor('encoder.bn1_beta');
|
||||||
|
const bn1m = reader.getTensor('encoder.bn1_runMean');
|
||||||
|
const bn1v = reader.getTensor('encoder.bn1_runVar');
|
||||||
|
const bn2g = reader.getTensor('encoder.bn2_gamma');
|
||||||
|
const bn2b = reader.getTensor('encoder.bn2_beta');
|
||||||
|
const bn2m = reader.getTensor('encoder.bn2_runMean');
|
||||||
|
const bn2v = reader.getTensor('encoder.bn2_runVar');
|
||||||
|
if (bn1g) encoder.bn1_gamma = new Float64Array(bn1g.data);
|
||||||
|
if (bn1b) encoder.bn1_beta = new Float64Array(bn1b.data);
|
||||||
|
if (bn1m) encoder.bn1_runMean = new Float64Array(bn1m.data);
|
||||||
|
if (bn1v) encoder.bn1_runVar = new Float64Array(bn1v.data);
|
||||||
|
if (bn2g) encoder.bn2_gamma = new Float64Array(bn2g.data);
|
||||||
|
if (bn2b) encoder.bn2_beta = new Float64Array(bn2b.data);
|
||||||
|
if (bn2m) encoder.bn2_runMean = new Float64Array(bn2m.data);
|
||||||
|
if (bn2v) encoder.bn2_runVar = new Float64Array(bn2v.data);
|
||||||
|
|
||||||
|
// Load presence head from SafeTensors if available
|
||||||
|
const phW = reader.getTensor('presence_head.weights');
|
||||||
|
const phB = reader.getTensor('presence_head.bias');
|
||||||
|
if (phW) presenceHead.weights = new Float64Array(phW.data);
|
||||||
|
if (phB) presenceHead.bias = phB.data[0];
|
||||||
|
|
||||||
console.log('Loaded encoder weights from SafeTensors.');
|
console.log('Loaded encoder weights from SafeTensors.');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`WARN: Could not load SafeTensors: ${e.message}`);
|
console.log(`WARN: Could not load SafeTensors: ${e.message}`);
|
||||||
@@ -328,7 +458,7 @@ async function main() {
|
|||||||
|
|
||||||
for (const bits of [8, 4, 2]) {
|
for (const bits of [8, 4, 2]) {
|
||||||
const qr = quantizeWeights(flatWeights, bits);
|
const qr = quantizeWeights(flatWeights, bits);
|
||||||
const deq = dequantizeWeights(qr.quantized, qr.scale, qr.zeroPoint, bits);
|
const deq = dequantizeWeights(qr.quantized, qr.scale, qr.zeroPoint, bits, qr.numWeights);
|
||||||
|
|
||||||
let sumSqErr = 0;
|
let sumSqErr = 0;
|
||||||
for (let i = 0; i < flatWeights.length; i++) {
|
for (let i = 0; i < flatWeights.length; i++) {
|
||||||
@@ -375,7 +505,7 @@ async function main() {
|
|||||||
|
|
||||||
if (timeDiff <= 1.0 && f1.nodeId === f2.nodeId) {
|
if (timeDiff <= 1.0 && f1.nodeId === f2.nodeId) {
|
||||||
positivePairs.push(sim);
|
positivePairs.push(sim);
|
||||||
} else if (timeDiff >= 30.0) {
|
} else if (timeDiff >= 10.0) { // Reduced from 30s to match training threshold
|
||||||
negativePairs.push(sim);
|
negativePairs.push(sim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -426,8 +556,9 @@ async function main() {
|
|||||||
|
|
||||||
const groundTruth = nearestVitals.presenceScore > 0.3 ? 1 : 0;
|
const groundTruth = nearestVitals.presenceScore > 0.3 ? 1 : 0;
|
||||||
const emb = encoder.encode(f.features);
|
const emb = encoder.encode(f.features);
|
||||||
const out = adapter.forward(emb);
|
// Use trained PresenceHead for presence prediction instead of raw embedding[0]
|
||||||
const predicted = out[0] > 0.5 ? 1 : 0;
|
const presScore = presenceHead.forward(emb);
|
||||||
|
const predicted = presScore > 0.5 ? 1 : 0;
|
||||||
|
|
||||||
if (predicted === 1 && groundTruth === 1) tp++;
|
if (predicted === 1 && groundTruth === 1) tp++;
|
||||||
else if (predicted === 1 && groundTruth === 0) fp++;
|
else if (predicted === 1 && groundTruth === 0) fp++;
|
||||||
|
|||||||
+740
-70
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user