@swapai/core 0.2.2 → 0.3.0
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/README.md +43 -0
- package/dist/chunk-AZVNSHXP.js +253 -0
- package/dist/chunk-AZVNSHXP.js.map +1 -0
- package/dist/classifiers-ui.d.ts +39 -0
- package/dist/classifiers-ui.js +9 -0
- package/dist/classifiers-ui.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +56 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +118 -1
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
package/dist/index.js
CHANGED
|
@@ -1120,6 +1120,8 @@ var SCHEMA = `
|
|
|
1120
1120
|
new_examples_since_training INTEGER NOT NULL DEFAULT 0,
|
|
1121
1121
|
training_attempts INTEGER NOT NULL DEFAULT 0,
|
|
1122
1122
|
examples_used_for_training INTEGER NOT NULL DEFAULT 0,
|
|
1123
|
+
last_evaluated_error REAL,
|
|
1124
|
+
last_evaluated_at INTEGER,
|
|
1123
1125
|
local_classifications_since_retest INTEGER NOT NULL DEFAULT 0,
|
|
1124
1126
|
total_local_classifications INTEGER NOT NULL DEFAULT 0,
|
|
1125
1127
|
total_retests INTEGER NOT NULL DEFAULT 0,
|
|
@@ -1165,6 +1167,19 @@ var SCHEMA = `
|
|
|
1165
1167
|
CREATE INDEX IF NOT EXISTS examples_by_split
|
|
1166
1168
|
ON examples(classifier_name, generation, split, id);
|
|
1167
1169
|
|
|
1170
|
+
CREATE TABLE IF NOT EXISTS classifier_runtimes (
|
|
1171
|
+
classifier_name TEXT NOT NULL,
|
|
1172
|
+
runtime_id TEXT NOT NULL,
|
|
1173
|
+
pid INTEGER NOT NULL,
|
|
1174
|
+
started_at INTEGER NOT NULL,
|
|
1175
|
+
heartbeat_at INTEGER NOT NULL,
|
|
1176
|
+
PRIMARY KEY (classifier_name, runtime_id),
|
|
1177
|
+
FOREIGN KEY (classifier_name) REFERENCES classifiers(name) ON DELETE CASCADE
|
|
1178
|
+
);
|
|
1179
|
+
|
|
1180
|
+
CREATE INDEX IF NOT EXISTS classifier_runtimes_by_heartbeat
|
|
1181
|
+
ON classifier_runtimes(classifier_name, heartbeat_at);
|
|
1182
|
+
|
|
1168
1183
|
CREATE TRIGGER IF NOT EXISTS swapai_v2_classifiers_insert
|
|
1169
1184
|
BEFORE INSERT ON classifiers
|
|
1170
1185
|
WHEN swapai_writer_version() < 2
|
|
@@ -1306,6 +1321,8 @@ function openStorage(options) {
|
|
|
1306
1321
|
c.new_examples_since_training,
|
|
1307
1322
|
c.training_attempts,
|
|
1308
1323
|
c.examples_used_for_training,
|
|
1324
|
+
c.last_evaluated_error,
|
|
1325
|
+
c.last_evaluated_at,
|
|
1309
1326
|
c.local_classifications_since_retest,
|
|
1310
1327
|
c.total_local_classifications,
|
|
1311
1328
|
c.total_retests,
|
|
@@ -1341,6 +1358,8 @@ function openStorage(options) {
|
|
|
1341
1358
|
newExamplesSinceTraining: row2.new_examples_since_training,
|
|
1342
1359
|
trainingAttempts: row2.training_attempts,
|
|
1343
1360
|
examplesUsedForTraining: row2.examples_used_for_training,
|
|
1361
|
+
lastEvaluatedError: row2.last_evaluated_error,
|
|
1362
|
+
lastEvaluatedAt: row2.last_evaluated_at,
|
|
1344
1363
|
localClassificationsSinceRetest: row2.local_classifications_since_retest,
|
|
1345
1364
|
totalLocalClassifications: row2.total_local_classifications,
|
|
1346
1365
|
totalRetests: row2.total_retests,
|
|
@@ -1439,6 +1458,21 @@ function openStorage(options) {
|
|
|
1439
1458
|
`).run(exampleCount, Date.now(), options.name, epoch);
|
|
1440
1459
|
return result.changes === 1;
|
|
1441
1460
|
},
|
|
1461
|
+
recordEvaluation(error, expectedDataEpoch) {
|
|
1462
|
+
assertOpen(closed);
|
|
1463
|
+
if (!Number.isFinite(error) || error < 0 || error > 1) {
|
|
1464
|
+
throw new TypeError("evaluation error must be between 0 and 1");
|
|
1465
|
+
}
|
|
1466
|
+
const epoch = expectedDataEpoch ?? storage.snapshot().dataEpoch;
|
|
1467
|
+
const result = database.prepare(`
|
|
1468
|
+
UPDATE classifiers
|
|
1469
|
+
SET last_evaluated_error = ?,
|
|
1470
|
+
last_evaluated_at = ?,
|
|
1471
|
+
updated_at = ?
|
|
1472
|
+
WHERE name = ? AND data_epoch = ? AND clear_pending = 0
|
|
1473
|
+
`).run(error, Date.now(), Date.now(), options.name, epoch);
|
|
1474
|
+
return result.changes === 1;
|
|
1475
|
+
},
|
|
1442
1476
|
promoteGeneration(model, expectedDataEpoch) {
|
|
1443
1477
|
assertOpen(closed);
|
|
1444
1478
|
let generation = 0;
|
|
@@ -1522,6 +1556,52 @@ function openStorage(options) {
|
|
|
1522
1556
|
WHERE name = ? AND training_lease_owner = ?
|
|
1523
1557
|
`).run(Date.now(), options.name, owner);
|
|
1524
1558
|
},
|
|
1559
|
+
registerRuntime(runtimeId, pid) {
|
|
1560
|
+
assertOpen(closed);
|
|
1561
|
+
if (runtimeId.trim() === "" || !Number.isSafeInteger(pid) || pid <= 0) {
|
|
1562
|
+
throw new TypeError("runtime needs an id and positive process id");
|
|
1563
|
+
}
|
|
1564
|
+
const now = Date.now();
|
|
1565
|
+
database.prepare(`
|
|
1566
|
+
INSERT INTO classifier_runtimes (
|
|
1567
|
+
classifier_name, runtime_id, pid, started_at, heartbeat_at
|
|
1568
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
1569
|
+
ON CONFLICT(classifier_name, runtime_id) DO UPDATE SET
|
|
1570
|
+
pid = excluded.pid,
|
|
1571
|
+
heartbeat_at = excluded.heartbeat_at
|
|
1572
|
+
`).run(options.name, runtimeId, pid, now, now);
|
|
1573
|
+
},
|
|
1574
|
+
heartbeatRuntime(runtimeId) {
|
|
1575
|
+
assertOpen(closed);
|
|
1576
|
+
database.prepare(`
|
|
1577
|
+
UPDATE classifier_runtimes
|
|
1578
|
+
SET heartbeat_at = ?
|
|
1579
|
+
WHERE classifier_name = ? AND runtime_id = ?
|
|
1580
|
+
`).run(Date.now(), options.name, runtimeId);
|
|
1581
|
+
},
|
|
1582
|
+
removeRuntime(runtimeId) {
|
|
1583
|
+
assertOpen(closed);
|
|
1584
|
+
database.prepare(`
|
|
1585
|
+
DELETE FROM classifier_runtimes
|
|
1586
|
+
WHERE classifier_name = ? AND runtime_id = ?
|
|
1587
|
+
`).run(options.name, runtimeId);
|
|
1588
|
+
},
|
|
1589
|
+
listLiveRuntimes(heartbeatAfter) {
|
|
1590
|
+
assertOpen(closed);
|
|
1591
|
+
if (!Number.isFinite(heartbeatAfter)) {
|
|
1592
|
+
throw new TypeError("heartbeat cutoff must be finite");
|
|
1593
|
+
}
|
|
1594
|
+
return database.prepare(`
|
|
1595
|
+
SELECT
|
|
1596
|
+
runtime_id AS runtimeId,
|
|
1597
|
+
pid,
|
|
1598
|
+
started_at AS startedAt,
|
|
1599
|
+
heartbeat_at AS heartbeatAt
|
|
1600
|
+
FROM classifier_runtimes
|
|
1601
|
+
WHERE classifier_name = ? AND heartbeat_at >= ?
|
|
1602
|
+
ORDER BY started_at, runtime_id
|
|
1603
|
+
`).all(options.name, heartbeatAfter).map((value) => row(value));
|
|
1604
|
+
},
|
|
1525
1605
|
archiveAndReset(expectedDataEpoch, resetOptions) {
|
|
1526
1606
|
assertOpen(closed);
|
|
1527
1607
|
let nextGeneration = 0;
|
|
@@ -1563,6 +1643,8 @@ function openStorage(options) {
|
|
|
1563
1643
|
new_examples_since_training = ?,
|
|
1564
1644
|
training_attempts = 0,
|
|
1565
1645
|
examples_used_for_training = 0,
|
|
1646
|
+
last_evaluated_error = NULL,
|
|
1647
|
+
last_evaluated_at = NULL,
|
|
1566
1648
|
training_lease_owner = NULL,
|
|
1567
1649
|
training_lease_until = NULL,
|
|
1568
1650
|
training_lease_epoch = NULL,
|
|
@@ -1630,6 +1712,8 @@ function openStorage(options) {
|
|
|
1630
1712
|
new_examples_since_training = 0,
|
|
1631
1713
|
training_attempts = 0,
|
|
1632
1714
|
examples_used_for_training = 0,
|
|
1715
|
+
last_evaluated_error = NULL,
|
|
1716
|
+
last_evaluated_at = NULL,
|
|
1633
1717
|
local_classifications_since_retest = 0,
|
|
1634
1718
|
total_local_classifications = 0,
|
|
1635
1719
|
total_retests = 0,
|
|
@@ -1792,6 +1876,16 @@ function migrateClassifierColumns(database) {
|
|
|
1792
1876
|
"ALTER TABLE classifiers ADD COLUMN training_lease_epoch INTEGER"
|
|
1793
1877
|
);
|
|
1794
1878
|
}
|
|
1879
|
+
if (!columns.has("last_evaluated_error")) {
|
|
1880
|
+
database.exec(
|
|
1881
|
+
"ALTER TABLE classifiers ADD COLUMN last_evaluated_error REAL"
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
if (!columns.has("last_evaluated_at")) {
|
|
1885
|
+
database.exec(
|
|
1886
|
+
"ALTER TABLE classifiers ADD COLUMN last_evaluated_at INTEGER"
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1795
1889
|
});
|
|
1796
1890
|
}
|
|
1797
1891
|
function generationByNumber(database, name, generation) {
|
|
@@ -1925,6 +2019,7 @@ function requiredRow(value) {
|
|
|
1925
2019
|
// src/classifier.ts
|
|
1926
2020
|
var TRAINING_LEASE_DURATION_MS = 5 * 60 * 1e3;
|
|
1927
2021
|
var TRAINING_LEASE_RENEWAL_MS = 60 * 1e3;
|
|
2022
|
+
var RUNTIME_HEARTBEAT_MS = 2e3;
|
|
1928
2023
|
var ARTIFACT_LOCK_WAIT_MS = 3e4;
|
|
1929
2024
|
function init(inputConfig) {
|
|
1930
2025
|
const config = normalizeConfig(inputConfig);
|
|
@@ -1974,6 +2069,18 @@ function createClassifier(config, storage, runtime) {
|
|
|
1974
2069
|
let trainingQueue = Promise.resolve();
|
|
1975
2070
|
let trainingScheduled = false;
|
|
1976
2071
|
const trainingLeaseOwner = `${process.pid}:${randomUUID2()}`;
|
|
2072
|
+
storage.registerRuntime(trainingLeaseOwner, process.pid);
|
|
2073
|
+
const runtimeHeartbeat = setInterval(() => {
|
|
2074
|
+
try {
|
|
2075
|
+
storage.heartbeatRuntime(trainingLeaseOwner);
|
|
2076
|
+
} catch (error) {
|
|
2077
|
+
reportBackgroundError(
|
|
2078
|
+
config,
|
|
2079
|
+
toSwapAIError(error, "storage_failed", "Could not update classifier status")
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
}, RUNTIME_HEARTBEAT_MS);
|
|
2083
|
+
runtimeHeartbeat.unref();
|
|
1977
2084
|
let classificationQueue = Promise.resolve();
|
|
1978
2085
|
let queuedFailure = null;
|
|
1979
2086
|
let clearFailure = null;
|
|
@@ -2199,7 +2306,15 @@ function createClassifier(config, storage, runtime) {
|
|
|
2199
2306
|
candidate: candidateResult
|
|
2200
2307
|
});
|
|
2201
2308
|
}
|
|
2202
|
-
|
|
2309
|
+
const measuredError = averageError(config.result, comparisons);
|
|
2310
|
+
if (!storage.recordEvaluation(measuredError, trainingEpoch)) {
|
|
2311
|
+
await runtime.clearClassifierGenerationArtifacts(
|
|
2312
|
+
config.name,
|
|
2313
|
+
snapshot.activeGeneration
|
|
2314
|
+
);
|
|
2315
|
+
return false;
|
|
2316
|
+
}
|
|
2317
|
+
if (measuredError > config.acceptableError) {
|
|
2203
2318
|
return true;
|
|
2204
2319
|
}
|
|
2205
2320
|
if (!renewTrainingLease()) {
|
|
@@ -2582,6 +2697,8 @@ function createClassifier(config, storage, runtime) {
|
|
|
2582
2697
|
failures.push(error);
|
|
2583
2698
|
}
|
|
2584
2699
|
try {
|
|
2700
|
+
clearInterval(runtimeHeartbeat);
|
|
2701
|
+
storage.removeRuntime(trainingLeaseOwner);
|
|
2585
2702
|
storage.close();
|
|
2586
2703
|
} catch (error) {
|
|
2587
2704
|
failures.push(error);
|