neuroalpha 0.2.0 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +17 -0
- package/README.md +9 -3
- package/bin/cli.js +69 -69
- package/config/seed-run.json +1 -0
- package/config/strategy.json +4 -10
- package/deploy/install.sh +128 -0
- package/docs//320/224/320/225/320/237/320/233/320/236/320/231.md +103 -0
- package/docs//320/230/320/235/320/241/320/242/320/240/320/243/320/232/320/246/320/230/320/257.md +1 -1
- package/docs//320/234/320/225/320/245/320/220/320/235/320/230/320/232/320/220_/320/230_/320/237/320/220/320/240/320/220/320/234/320/225/320/242/320/240/320/253.md +171 -0
- package/package.json +15 -5
- package/public/admin.html +449 -0
- package/public/app.js +1182 -379
- package/public/index.html +87 -22
- package/public/landing.html +1217 -870
- package/public/legal.html +51 -8
- package/public/styles.css +71 -16
- package/server/api/routes.js +220 -35
- package/server/auth/index.js +633 -340
- package/server/billing/yookassa.js +1 -0
- package/server/brokers/index.js +1369 -645
- package/server/config.js +6 -4
- package/server/data/moex.js +1 -0
- package/server/db.js +69 -4
- package/server/hub.js +1 -0
- package/server/index.js +10 -2
- package/server/llm/client.js +1 -0
- package/server/llm/providers.js +50 -3
- package/server/model/dataset.js +362 -352
- package/server/model/forecast.js +202 -37
- package/server/model/linalg.js +1 -0
- package/server/model/live.js +476 -486
- package/server/model/metrics.js +1 -0
- package/server/model/models.js +31 -21
- package/server/model/profiles.js +1 -0
- package/server/model/research.js +128 -205
- package/server/model/vocab.js +1 -0
- package/server/news/index.js +1 -0
- package/server/notify/index.js +304 -168
- package/server/params-meta.js +22 -26
- package/server/settings.js +1 -0
- package/server/strategist/analytics.js +2 -1
- package/server/strategist/index.js +7 -7
- package/server/strategist/tools.js +2 -1
- package/server/util.js +1 -0
- package/server/model/gpu-worker.html +0 -11
- package/server/model/gpu-worker.js +0 -60
- package/server/model/tf-node.js +0 -26
- package/server/model/transformer-core.js +0 -330
- package/server/model/transformer-gpu.js +0 -221
- package/server/model/transformer-pool.js +0 -200
- package/server/model/transformer-worker.js +0 -28
- package/server/model/transformer.js +0 -57
package/server/model/forecast.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roman A. Perepelkin. All rights reserved. Proprietary software.
|
|
1
2
|
import { normalizeParams } from '../settings.js';
|
|
2
3
|
import { round, DAY, fmtBarMsk } from '../util.js';
|
|
3
|
-
import { loadDataset, barsFromHours, BAR_MS
|
|
4
|
-
import { buildFold, MODELS, HEADS, LIQ_MIN, FEATURE_WARMUP, explainAttention, mapSnapshot } from './models.js';
|
|
4
|
+
import { loadDataset, barsFromHours, BAR_MS } from './dataset.js';
|
|
5
|
+
import { buildFold, MODELS, HEADS, SCALES, BETAS, LIQ_MIN, FEATURE_WARMUP, explainAttention, mapSnapshot } from './models.js';
|
|
5
6
|
import { loadProfiles } from './profiles.js';
|
|
6
7
|
import { isMarketDown } from './research.js';
|
|
7
8
|
import { newsForSymbols } from '../news/index.js';
|
|
8
9
|
|
|
9
|
-
let
|
|
10
|
+
let baseState = null; // { key, trainedAt, ds, last, r, snap, prevExpMap, prevRankMap, lastTickResult, lastTickAt }
|
|
10
11
|
const inflight = new Map();
|
|
11
12
|
|
|
12
13
|
export const lastClosedIndex = (ds, now = Date.now()) => {
|
|
@@ -15,42 +16,131 @@ export const lastClosedIndex = (ds, now = Date.now()) => {
|
|
|
15
16
|
return last;
|
|
16
17
|
};
|
|
17
18
|
|
|
19
|
+
// Быстрое получение живых цен Мосбиржи (TQBR + IMOEX) за 1 лёгкий запрос (~80 мс)
|
|
20
|
+
async function fetchLiveMoexSnapshot() {
|
|
21
|
+
try {
|
|
22
|
+
const [rShares, rIdx] = await Promise.all([
|
|
23
|
+
fetch('https://iss.moex.com/iss/engines/stock/markets/shares/boards/TQBR/securities.json?iss.meta=off&iss.only=marketdata&marketdata.columns=SECID,LAST,MARKETPRICE,LCURRENTPRICE,VALTODAY', { signal: AbortSignal.timeout(6000) }),
|
|
24
|
+
fetch('https://iss.moex.com/iss/engines/stock/markets/index/boards/SNDX/securities/IMOEX.json?iss.meta=off&iss.only=marketdata&marketdata.columns=SECID,CURRENTVALUE,LASTVALUE', { signal: AbortSignal.timeout(6000) }).catch(() => null),
|
|
25
|
+
]);
|
|
26
|
+
if (!rShares.ok) return null;
|
|
27
|
+
const j = await rShares.json();
|
|
28
|
+
const cols = j.marketdata?.columns || [];
|
|
29
|
+
const iSec = cols.indexOf('SECID');
|
|
30
|
+
const iLast = cols.indexOf('LAST');
|
|
31
|
+
const iMkt = cols.indexOf('MARKETPRICE');
|
|
32
|
+
const iCur = cols.indexOf('LCURRENTPRICE');
|
|
33
|
+
const prices = new Map();
|
|
34
|
+
for (const row of j.marketdata?.data || []) {
|
|
35
|
+
const sym = row[iSec];
|
|
36
|
+
const px = Number(row[iLast] || row[iMkt] || row[iCur] || 0);
|
|
37
|
+
if (sym && px > 0) prices.set(sym, px);
|
|
38
|
+
}
|
|
39
|
+
let imoexPrice = 0;
|
|
40
|
+
if (rIdx?.ok) {
|
|
41
|
+
const ji = await rIdx.json().catch(() => null);
|
|
42
|
+
const ic = ji?.marketdata?.columns || [];
|
|
43
|
+
const ir = ji?.marketdata?.data?.[0];
|
|
44
|
+
if (ir) imoexPrice = Number(ir[ic.indexOf('CURRENTVALUE')] || ir[ic.indexOf('LASTVALUE')] || 0);
|
|
45
|
+
}
|
|
46
|
+
return { prices, imoexPrice, ts: Date.now() };
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Мгновенное обновление последней точки контекста u в уже обученной модели по живым котировкам (за ~2 мс)
|
|
53
|
+
function applyLivePricesToFold(ds, f, u, fitted, liveSnap) {
|
|
54
|
+
if (!liveSnap?.prices?.size) return;
|
|
55
|
+
const idx = f.lo + u;
|
|
56
|
+
if (idx < 1 || idx >= ds.T) return;
|
|
57
|
+
const N = f.N;
|
|
58
|
+
const mPrev = ds.mclose?.[idx - 1] || 0;
|
|
59
|
+
const mLive = liveSnap.imoexPrice > 0 && mPrev > 0 ? Math.log(liveSnap.imoexPrice / mPrev) : ds.mret[idx];
|
|
60
|
+
|
|
61
|
+
// 1) пересчитываем z[u*N + i] по живой цене акции относительно закрытия предыдущей свечи
|
|
62
|
+
for (let i = 0; i < N; i++) {
|
|
63
|
+
const tk = ds.tokens[i];
|
|
64
|
+
const prevPx = tk.close[idx - 1];
|
|
65
|
+
const livePx = liveSnap.prices.get(tk.symbol);
|
|
66
|
+
if (livePx > 0 && prevPx > 0) {
|
|
67
|
+
const ret = Math.log(livePx / prevPx);
|
|
68
|
+
const e = ret - f.beta[i] * mLive;
|
|
69
|
+
f.z[u * N + i] = Math.max(-8, Math.min(8, e / f.sigma[i]));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 2) пересчитываем признаки масштаба 0 (последний час) в точке u
|
|
74
|
+
const sc0 = SCALES[0];
|
|
75
|
+
const invP = 1 / Math.sqrt(sc0.len), invF = invP / 1.5;
|
|
76
|
+
const Xp0 = f.X[0][0], Xf0 = f.X[1][0];
|
|
77
|
+
for (let i = 0; i < N; i++) {
|
|
78
|
+
let sp = 0, sf = 0;
|
|
79
|
+
for (let k = 0; k < sc0.len; k++) {
|
|
80
|
+
const uk = u - k;
|
|
81
|
+
if (uk < 0) break;
|
|
82
|
+
const zk = f.z[uk * N + i];
|
|
83
|
+
sp += zk;
|
|
84
|
+
sf += zk * Math.min(4, ds.tokens[i].valRatio[f.lo + uk]);
|
|
85
|
+
}
|
|
86
|
+
Xp0[u * N + i] = sp * invP;
|
|
87
|
+
Xf0[u * N + i] = sf * invF;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 3) пересчитываем головы внимания F[hi][u*N + j] в точке u
|
|
91
|
+
const att = fitted.attention;
|
|
92
|
+
if (att?.As && att?.F) {
|
|
93
|
+
const off = u * N;
|
|
94
|
+
for (let hi = 0; hi < HEADS.length; hi++) {
|
|
95
|
+
const h = HEADS[hi];
|
|
96
|
+
const bIdx = BETAS.findIndex((b) => b.v === h.beta);
|
|
97
|
+
const A = att.As[bIdx];
|
|
98
|
+
const X = f.X[h.kind === 'price' ? 0 : 1][h.scale];
|
|
99
|
+
const Fh = att.F[hi];
|
|
100
|
+
for (let j = 0; j < N; j++) {
|
|
101
|
+
let s = 0;
|
|
102
|
+
const row = j * N;
|
|
103
|
+
for (let i = 0; i < N; i++) s += A[row + i] * X[off + i];
|
|
104
|
+
Fh[off + j] = s;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
18
110
|
// Обучение на окне, заканчивающемся свечой idx, и прогноз по этой свече.
|
|
19
111
|
// Возвращает строки по всем акциям: ожидаемое движение (относительно рынка), сигнал покупки, кто влияет.
|
|
20
|
-
export async function predictAt(ds, p, idx, { explain = true } = {}) {
|
|
21
|
-
const
|
|
112
|
+
export async function predictAt(ds, p, idx, { explain = true, liveSnap = null } = {}) {
|
|
113
|
+
const autoAdapt = p.modelAutoAdapt !== false;
|
|
114
|
+
const effHorizon = autoAdapt ? 2.5 : p.modelHorizon;
|
|
115
|
+
const effMinEdge = autoAdapt ? 2.5 : p.modelMinEdge;
|
|
116
|
+
const H = barsFromHours(effHorizon);
|
|
117
|
+
const maxH = autoAdapt ? Math.round(H * 1.4) : H;
|
|
22
118
|
const trainLen = Math.round(p.modelTrainDays * ds.barsPerDay);
|
|
23
|
-
const trainStart = Math.max(FEATURE_WARMUP +
|
|
119
|
+
const trainStart = Math.max(FEATURE_WARMUP + maxH + 2, idx - trainLen);
|
|
24
120
|
// «текстовая» часть карты: устойчивые описания бизнеса (профили готовятся отдельно, здесь только читаем)
|
|
25
121
|
const profiles = p.modelTextWeight > 0 ? loadProfiles() : null;
|
|
26
|
-
const f = buildFold(ds, { trainStart, testStart: idx, testEnd: idx + 1, H, embedDim: p.modelEmbedDim, textWeight: p.modelTextWeight, profiles });
|
|
122
|
+
const f = buildFold(ds, { trainStart, testStart: idx, testEnd: idx + 1, H, embedDim: p.modelEmbedDim, textWeight: p.modelTextWeight, profiles, multiHorizon: autoAdapt });
|
|
27
123
|
const u = idx - f.lo;
|
|
28
124
|
const chosen = p.modelType in MODELS ? p.modelType : 'attention';
|
|
29
|
-
const
|
|
30
|
-
// трансформер обучается здесь, только если торгует он (для сравнения — только в исследовании)
|
|
31
|
-
const list = [...new Set(explain ? [...Object.keys(MODELS).filter((n) => !MODELS[n].heavy), chosen] : need)];
|
|
125
|
+
const list = [...new Set(explain ? Object.keys(MODELS) : ['attention', chosen])];
|
|
32
126
|
const fitted = {};
|
|
33
|
-
const preds = {};
|
|
34
127
|
for (const n of list) {
|
|
35
|
-
if (n === 'transformer') {
|
|
36
|
-
// ~минута обучения — в отдельном потоке (или на видеокарте), чтобы сервер не замирал
|
|
37
|
-
const tm = await import('./transformer.js');
|
|
38
|
-
const { fitPredictIsolated } = await import('./transformer-pool.js');
|
|
39
|
-
const r = await fitPredictIsolated(tm.transformerInput(f), [u], tm.transformerCfg(p), { gpu: p.transformerGpu });
|
|
40
|
-
fitted[n] = { name: n, info: r.info, where: r.where };
|
|
41
|
-
preds[n] = r.preds.get(u);
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
128
|
fitted[n] = await MODELS[n].fit(f);
|
|
129
|
+
}
|
|
130
|
+
if (liveSnap) applyLivePricesToFold(ds, f, u, fitted, liveSnap);
|
|
131
|
+
return buildPredictionRows(ds, p, idx, f, u, fitted, chosen, H, effMinEdge, autoAdapt, explain, liveSnap);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildPredictionRows(ds, p, idx, f, u, fitted, chosen, H, effMinEdge, autoAdapt, explain, liveSnap = null) {
|
|
135
|
+
const preds = {};
|
|
136
|
+
for (const n of Object.keys(fitted)) {
|
|
45
137
|
preds[n] = MODELS[n].predict(fitted[n], f, u);
|
|
46
|
-
if (fitted[n].dispose) fitted[n].dispose();
|
|
47
138
|
}
|
|
48
139
|
const cost = (p.commissionPct + p.slippagePct) / 100;
|
|
49
|
-
const threshold =
|
|
140
|
+
const threshold = effMinEdge * 2 * cost;
|
|
50
141
|
const bear = isMarketDown(ds, idx);
|
|
51
|
-
const autoAdapt = p.modelAutoAdapt !== false;
|
|
52
142
|
const dir = p.modelDirection || 'long';
|
|
53
|
-
const needL = autoAdapt && bear
|
|
143
|
+
const needL = autoAdapt && bear ? threshold * 1.25 : threshold;
|
|
54
144
|
const needS = autoAdapt && dir === 'longshort' ? (bear ? threshold * 0.90 : threshold * 1.25) : threshold;
|
|
55
145
|
let sInv = 0, nInv = 0;
|
|
56
146
|
for (let i = 0; i < ds.tokens.length; i++) if (f.sigma[i] > 0 && ds.tokens[i].liq[idx] >= LIQ_MIN) { sInv += 1 / f.sigma[i]; nInv++; }
|
|
@@ -58,8 +148,10 @@ export async function predictAt(ds, p, idx, { explain = true } = {}) {
|
|
|
58
148
|
const rows = ds.tokens.map((tk, i) => {
|
|
59
149
|
const exp = preds[chosen][i] * f.sigma[i] * Math.sqrt(H);
|
|
60
150
|
const weight = avgInv > 0 && f.sigma[i] > 0 ? Math.max(0.4, Math.min(1.8, (1 / f.sigma[i]) / avgInv)) : 1;
|
|
151
|
+
const livePx = liveSnap?.prices?.get(tk.symbol) || tk.close[idx] || 0;
|
|
61
152
|
const row = {
|
|
62
153
|
i, symbol: tk.symbol, name: tk.name,
|
|
154
|
+
price: round(livePx, 4),
|
|
63
155
|
liquid: tk.liq[idx] >= LIQ_MIN,
|
|
64
156
|
expected: exp,
|
|
65
157
|
expectedPct: round(exp * 100, 3),
|
|
@@ -78,9 +170,7 @@ export async function predictAt(ds, p, idx, { explain = true } = {}) {
|
|
|
78
170
|
return { f, u, fitted, chosen, threshold, marketDown: bear, rows };
|
|
79
171
|
}
|
|
80
172
|
|
|
81
|
-
// Новости — только в прогнозе
|
|
82
|
-
// и ИИ, знающий будущее, исказил бы проверку). Строкам с новостями за последние 24 ч добавляем
|
|
83
|
-
// news: { count, tone — средний взвешенный по важности, top — суть самой важной }
|
|
173
|
+
// Новости — только в прогнозе «сейчас»
|
|
84
174
|
function withNews(result, p) {
|
|
85
175
|
const nm = p.newsEnabled ? newsForSymbols(24) : null;
|
|
86
176
|
for (const r of result.rows) {
|
|
@@ -91,29 +181,104 @@ function withNews(result, p) {
|
|
|
91
181
|
return result;
|
|
92
182
|
}
|
|
93
183
|
|
|
94
|
-
//
|
|
184
|
+
// Добавляет к каждой строке дельту ожидания (deltaPct) и смещение в рейтинге (rankDelta) относительно предыдущего замера
|
|
185
|
+
function attachDeltas(rows, stateObj) {
|
|
186
|
+
const prevExp = stateObj.prevExpMap;
|
|
187
|
+
const prevRank = stateObj.prevRankMap;
|
|
188
|
+
const nextExp = new Map();
|
|
189
|
+
const nextRank = new Map();
|
|
190
|
+
let changed = false;
|
|
191
|
+
|
|
192
|
+
rows.forEach((r, idx) => {
|
|
193
|
+
const rank = idx + 1;
|
|
194
|
+
nextExp.set(r.symbol, r.expectedPct);
|
|
195
|
+
nextRank.set(r.symbol, rank);
|
|
196
|
+
if (prevExp && prevExp.has(r.symbol)) {
|
|
197
|
+
const d = round(r.expectedPct - prevExp.get(r.symbol), 3);
|
|
198
|
+
if (Math.abs(d) >= 0.001) changed = true;
|
|
199
|
+
r.deltaPct = d !== 0 ? d : (r.deltaPct || 0);
|
|
200
|
+
r.rankDelta = prevRank?.has(r.symbol) ? (prevRank.get(r.symbol) - rank) : 0;
|
|
201
|
+
} else {
|
|
202
|
+
r.deltaPct = 0;
|
|
203
|
+
r.rankDelta = 0;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
if (!prevExp || changed) {
|
|
208
|
+
stateObj.prevExpMap = nextExp;
|
|
209
|
+
stateObj.prevRankMap = nextRank;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Прогноз на ближайшие H часов с живым поминутным обновлением котировок (без переобучения тяжёлого окна каждую минуту)
|
|
95
214
|
export async function forecastNow(input, { force = false, onProgress } = {}) {
|
|
96
215
|
const p = normalizeParams(input);
|
|
97
|
-
const
|
|
98
|
-
|
|
216
|
+
const autoAdapt = p.modelAutoAdapt !== false;
|
|
217
|
+
const effHorizon = autoAdapt ? 2.5 : p.modelHorizon;
|
|
218
|
+
const effMinEdge = autoAdapt ? 2.5 : p.modelMinEdge;
|
|
219
|
+
const key = JSON.stringify([autoAdapt, p.modelType, effHorizon, p.modelTrainDays, p.modelEmbedDim, effMinEdge, p.modelDirection, p.modelUseMacro, p.modelUseDividends, p.modelTextWeight, p.commissionPct, p.slippagePct]);
|
|
220
|
+
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
// 1) Если с последнего живого тика прошло меньше 15 секунд и нет force — отдаём мгновенно из памяти
|
|
223
|
+
if (!force && baseState && baseState.key === key && baseState.lastTickResult && now - baseState.lastTickAt < 15000) {
|
|
224
|
+
return withNews(baseState.lastTickResult, p);
|
|
225
|
+
}
|
|
226
|
+
// 2) Если базовая модель обучена менее 10 минут назад и нет force — делаем быстрый живой пересчёт (~80 мс запрос + 2 мс математика)
|
|
227
|
+
if (!force && baseState && baseState.key === key && now - baseState.trainedAt < 10 * 60000) {
|
|
228
|
+
const liveSnap = await fetchLiveMoexSnapshot();
|
|
229
|
+
if (liveSnap) applyLivePricesToFold(baseState.ds, baseState.f, baseState.u, baseState.fitted, liveSnap);
|
|
230
|
+
const H = barsFromHours(effHorizon);
|
|
231
|
+
const r = buildPredictionRows(baseState.ds, p, baseState.last, baseState.f, baseState.u, baseState.fitted, baseState.chosen, H, effMinEdge, autoAdapt, true, liveSnap);
|
|
232
|
+
const sortedRows = r.rows.map(({ i, expected, ...x }) => ({ ...x, cluster: baseState.snap.clusters[i] })).sort((a, b) => b.expectedPct - a.expectedPct);
|
|
233
|
+
attachDeltas(sortedRows, baseState);
|
|
234
|
+
const result = {
|
|
235
|
+
...baseState.lastTickResult,
|
|
236
|
+
at: now,
|
|
237
|
+
liveTs: liveSnap?.ts || now,
|
|
238
|
+
rows: sortedRows,
|
|
239
|
+
};
|
|
240
|
+
baseState.lastTickResult = result;
|
|
241
|
+
baseState.lastTickAt = now;
|
|
242
|
+
return withNews(result, p);
|
|
243
|
+
}
|
|
244
|
+
|
|
99
245
|
if (!force && inflight.has(key)) return withNews(await inflight.get(key), p);
|
|
100
246
|
const task = (async () => {
|
|
101
|
-
const
|
|
102
|
-
|
|
247
|
+
const [ds, liveSnap] = await Promise.all([
|
|
248
|
+
loadDataset({ fromTs: now - Math.ceil(p.modelTrainDays * 1.5 + 25) * DAY, tillTs: now, useMacro: p.modelUseMacro, useDividends: p.modelUseDividends, onProgress }),
|
|
249
|
+
fetchLiveMoexSnapshot(),
|
|
250
|
+
]);
|
|
103
251
|
const last = lastClosedIndex(ds, now);
|
|
104
|
-
const r = await predictAt(ds, p, last);
|
|
252
|
+
const r = await predictAt(ds, p, last, { explain: true, liveSnap });
|
|
105
253
|
const snap = mapSnapshot(r.f);
|
|
254
|
+
const sortedRows = r.rows.map(({ i, expected, ...x }) => ({ ...x, cluster: snap.clusters[i] })).sort((a, b) => b.expectedPct - a.expectedPct);
|
|
255
|
+
const stateObj = baseState && baseState.key === key ? baseState : { prevExpMap: null, prevRankMap: null };
|
|
256
|
+
attachDeltas(sortedRows, stateObj);
|
|
106
257
|
const result = {
|
|
107
258
|
at: now,
|
|
259
|
+
liveTs: liveSnap?.ts || now,
|
|
108
260
|
barTs: ds.timeline[last],
|
|
109
261
|
bar: fmtBarMsk(ds.timeline[last]),
|
|
110
|
-
horizon:
|
|
111
|
-
rows:
|
|
262
|
+
horizon: effHorizon, direction: p.modelDirection, model: r.chosen, thresholdPct: round(r.threshold * 100, 3), marketDown: r.marketDown,
|
|
263
|
+
rows: sortedRows,
|
|
112
264
|
map: { symbols: ds.tokens.map((t) => t.symbol), names: ds.tokens.map((t) => t.name), pts: snap.pts.map(([x, y]) => [round(x, 3), round(y, 3)]), cl: snap.clusters },
|
|
113
265
|
heads: HEADS.map((h, i) => ({ label: h.label, coef: round(r.fitted.attention.coef[i], 4) })),
|
|
114
|
-
...(r.fitted.transformer ? { transformer: { device: r.fitted.transformer.where, bestEpoch: r.fitted.transformer.info.bestEpoch, epochs: r.fitted.transformer.info.epochs } } : {}),
|
|
115
266
|
};
|
|
116
|
-
|
|
267
|
+
baseState = {
|
|
268
|
+
key,
|
|
269
|
+
trainedAt: now,
|
|
270
|
+
ds,
|
|
271
|
+
last,
|
|
272
|
+
f: r.f,
|
|
273
|
+
u: r.u,
|
|
274
|
+
fitted: r.fitted,
|
|
275
|
+
chosen: r.chosen,
|
|
276
|
+
snap,
|
|
277
|
+
prevExpMap: stateObj.prevExpMap,
|
|
278
|
+
prevRankMap: stateObj.prevRankMap,
|
|
279
|
+
lastTickResult: result,
|
|
280
|
+
lastTickAt: now,
|
|
281
|
+
};
|
|
117
282
|
return result;
|
|
118
283
|
})().finally(() => inflight.delete(key));
|
|
119
284
|
inflight.set(key, task);
|
package/server/model/linalg.js
CHANGED