textopt 0.0.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/LICENSE +21 -0
- package/README.md +509 -0
- package/dist/bootstrap-search/index.cjs +308 -0
- package/dist/bootstrap-search/index.d.cts +162 -0
- package/dist/bootstrap-search/index.d.mts +162 -0
- package/dist/bootstrap-search/index.mjs +307 -0
- package/dist/cache-CuSo0NJ8.d.cts +24 -0
- package/dist/cache-CuSo0NJ8.d.mts +24 -0
- package/dist/concurrency-C-cFzWW2.cjs +44 -0
- package/dist/concurrency-D58PWeSk.mjs +39 -0
- package/dist/demos-B0pVQjYC.d.mts +88 -0
- package/dist/demos-B9BJiNKz.cjs +143 -0
- package/dist/demos-BTuzFNsp.d.cts +88 -0
- package/dist/demos-Degx6UmP.mjs +126 -0
- package/dist/evaluation-BV0nSZVx.mjs +521 -0
- package/dist/evaluation-OZOp6TB7.cjs +598 -0
- package/dist/file-cache.cjs +70 -0
- package/dist/file-cache.d.cts +21 -0
- package/dist/file-cache.d.mts +21 -0
- package/dist/file-cache.mjs +69 -0
- package/dist/gepa/index.cjs +1671 -0
- package/dist/gepa/index.d.cts +385 -0
- package/dist/gepa/index.d.mts +385 -0
- package/dist/gepa/index.mjs +1652 -0
- package/dist/index.cjs +266 -0
- package/dist/index.d.cts +221 -0
- package/dist/index.d.mts +221 -0
- package/dist/index.mjs +245 -0
- package/dist/math-COOofUyv.cjs +101 -0
- package/dist/math-DhrDmpFS.mjs +78 -0
- package/dist/mipro/index.cjs +739 -0
- package/dist/mipro/index.d.cts +372 -0
- package/dist/mipro/index.d.mts +372 -0
- package/dist/mipro/index.mjs +736 -0
- package/dist/opro/index.cjs +487 -0
- package/dist/opro/index.d.cts +230 -0
- package/dist/opro/index.d.mts +230 -0
- package/dist/opro/index.mjs +485 -0
- package/dist/optimizer-B7SpRwl7.d.cts +288 -0
- package/dist/optimizer-DqCoth_w.d.mts +288 -0
- package/dist/random-search/index.cjs +321 -0
- package/dist/random-search/index.d.cts +156 -0
- package/dist/random-search/index.d.mts +156 -0
- package/dist/random-search/index.mjs +319 -0
- package/dist/reflection-CQToe-5B.d.cts +283 -0
- package/dist/reflection-Cr_upzU0.d.mts +283 -0
- package/dist/reflection-DRfbk6hu.cjs +249 -0
- package/dist/reflection-mwMhrjs_.mjs +214 -0
- package/dist/rng-BR5MOedA.d.cts +22 -0
- package/dist/rng-BR5MOedA.d.mts +22 -0
- package/dist/rng-DbA_rPIo.cjs +67 -0
- package/dist/rng-Dtc5eZ_W.mjs +62 -0
- package/dist/sampling-CfHt7Gue.mjs +59 -0
- package/dist/sampling-DFo_7RNJ.d.mts +23 -0
- package/dist/sampling-Dars7ctR.cjs +64 -0
- package/dist/sampling-axOwfZf5.d.cts +23 -0
- package/dist/simba/index.cjs +709 -0
- package/dist/simba/index.d.cts +289 -0
- package/dist/simba/index.d.mts +289 -0
- package/dist/simba/index.mjs +700 -0
- package/dist/testing.cjs +155 -0
- package/dist/testing.d.cts +53 -0
- package/dist/testing.d.mts +53 -0
- package/dist/testing.mjs +148 -0
- package/dist/text--v4Ffbus.mjs +21 -0
- package/dist/text-CK_HB3su.cjs +26 -0
- package/dist/types-CWv4IQFF.d.cts +129 -0
- package/dist/types-CWv4IQFF.d.mts +129 -0
- package/package.json +135 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { t as parseProposedText } from "./text--v4Ffbus.mjs";
|
|
2
|
+
//#region src/gepa/reflection.ts
|
|
3
|
+
const TRUNCATION_MARKER = "… [truncated]";
|
|
4
|
+
const MIN_STRING_BUDGET = 40;
|
|
5
|
+
/**
|
|
6
|
+
* Adapted from the reflection prompt in the GEPA paper (Agrawal et al., 2025).
|
|
7
|
+
* The instruction to mine domain facts out of the traces matters as much as the
|
|
8
|
+
* instruction to fix failures — most of the lift comes from the model writing
|
|
9
|
+
* down knowledge the traces revealed.
|
|
10
|
+
*/
|
|
11
|
+
function buildReflectionPrompt(args) {
|
|
12
|
+
const { componentName, currentText, records, rejected = [] } = args;
|
|
13
|
+
return [
|
|
14
|
+
`I gave an assistant the following instruction for the "${componentName}" component of a larger system:`,
|
|
15
|
+
"",
|
|
16
|
+
"<current_instruction>",
|
|
17
|
+
currentText,
|
|
18
|
+
"</current_instruction>",
|
|
19
|
+
"",
|
|
20
|
+
"Below are task inputs the assistant received, the outputs it produced, and feedback on how each output could be better:",
|
|
21
|
+
"",
|
|
22
|
+
"<examples>",
|
|
23
|
+
serializeRecords(records),
|
|
24
|
+
"</examples>",
|
|
25
|
+
...rejected.length === 0 ? [] : [
|
|
26
|
+
"",
|
|
27
|
+
"These instructions have already been tried for this component and scored worse than the one they replaced. Do not propose them again, and do not propose a variation that repeats the idea that made them fail:",
|
|
28
|
+
"",
|
|
29
|
+
"<rejected_instructions>",
|
|
30
|
+
JSON.stringify(rejected, jsonSafeReplacer, 2),
|
|
31
|
+
"</rejected_instructions>"
|
|
32
|
+
],
|
|
33
|
+
"",
|
|
34
|
+
"Write a new instruction for this component.",
|
|
35
|
+
"Read the inputs carefully and infer a detailed description of the task the component is solving, including its input format.",
|
|
36
|
+
"Read every output and its feedback. Identify all niche or domain-specific factual information the task depends on and state it explicitly in the instruction — the assistant will not have access to these examples in future.",
|
|
37
|
+
"If the assistant used a generalizable strategy that worked, describe that strategy.",
|
|
38
|
+
"If the feedback shows a recurring failure, add a precise rule that prevents it.",
|
|
39
|
+
"",
|
|
40
|
+
"Return only the new instruction, inside a ``` block."
|
|
41
|
+
].join("\n");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Cut rather than add. Reflective evolution only ever appends — every
|
|
45
|
+
* iteration diagnoses a failure and writes a rule preventing it — so an
|
|
46
|
+
* instruction grows monotonically until it is mostly edge cases that no longer
|
|
47
|
+
* fire. Nothing else in the loop ever removes one.
|
|
48
|
+
*/
|
|
49
|
+
function buildSimplifyPrompt(args) {
|
|
50
|
+
const { componentName, currentText, records } = args;
|
|
51
|
+
return [
|
|
52
|
+
`The instruction below drives the "${componentName}" component of a larger system. It has been edited many times and has accumulated rules.`,
|
|
53
|
+
"",
|
|
54
|
+
"<current_instruction>",
|
|
55
|
+
currentText,
|
|
56
|
+
"</current_instruction>",
|
|
57
|
+
"",
|
|
58
|
+
"Here is how it behaved on recent inputs, with feedback on each output:",
|
|
59
|
+
"",
|
|
60
|
+
"<examples>",
|
|
61
|
+
serializeRecords(records),
|
|
62
|
+
"</examples>",
|
|
63
|
+
"",
|
|
64
|
+
"Write a shorter instruction.",
|
|
65
|
+
"Remove any rule that is redundant, that restates something already said, that contradicts another rule, or that no longer earns the space it takes.",
|
|
66
|
+
"Keep every rule the examples show is load-bearing. The component must still behave the same way on the inputs above.",
|
|
67
|
+
"Do not add new rules. If nothing can be removed, say the same thing in fewer words.",
|
|
68
|
+
"",
|
|
69
|
+
"Return only the new instruction, inside a ``` block."
|
|
70
|
+
].join("\n");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Replace a rule that fits the instances it was written from with the
|
|
74
|
+
* principle behind it. Feedback is drawn from minibatches, so a rule written
|
|
75
|
+
* to fix three examples routinely encodes those three examples.
|
|
76
|
+
*/
|
|
77
|
+
function buildGeneralizePrompt(args) {
|
|
78
|
+
const { componentName, currentText, records } = args;
|
|
79
|
+
return [
|
|
80
|
+
`The instruction below drives the "${componentName}" component of a larger system.`,
|
|
81
|
+
"",
|
|
82
|
+
"<current_instruction>",
|
|
83
|
+
currentText,
|
|
84
|
+
"</current_instruction>",
|
|
85
|
+
"",
|
|
86
|
+
"It was written from a small sample of inputs, so parts of it may describe those specific inputs rather than the task. Here is how it behaved on recent inputs, with feedback on each output:",
|
|
87
|
+
"",
|
|
88
|
+
"<examples>",
|
|
89
|
+
serializeRecords(records),
|
|
90
|
+
"</examples>",
|
|
91
|
+
"",
|
|
92
|
+
"Write a new instruction that states the underlying principle instead of the special cases.",
|
|
93
|
+
"Where a rule names a specific input, value or phrasing, ask what general property that case is an instance of, and write that property.",
|
|
94
|
+
"Keep concrete domain facts that are genuinely fixed — names, thresholds, formats. Those are knowledge, not overfitting.",
|
|
95
|
+
"",
|
|
96
|
+
"Return only the new instruction, inside a ``` block."
|
|
97
|
+
].join("\n");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Start from the evidence rather than from the incumbent. Every other strategy
|
|
101
|
+
* edits the current text, which anchors each proposal to whatever the search
|
|
102
|
+
* happened to reach first; this one is the only escape from a bad opening.
|
|
103
|
+
*/
|
|
104
|
+
function buildRewritePrompt(args) {
|
|
105
|
+
const { componentName, records } = args;
|
|
106
|
+
return [
|
|
107
|
+
`Write the instruction for the "${componentName}" component of a larger system, from scratch.`,
|
|
108
|
+
"",
|
|
109
|
+
"Below are task inputs the component received, the outputs it produced, and feedback on how each output could be better:",
|
|
110
|
+
"",
|
|
111
|
+
"<examples>",
|
|
112
|
+
serializeRecords(records),
|
|
113
|
+
"</examples>",
|
|
114
|
+
"",
|
|
115
|
+
"You are deliberately not being shown the instruction currently in use. Work out what the component is for from the inputs and outputs alone.",
|
|
116
|
+
"Infer the task, its input format, and what a correct output looks like.",
|
|
117
|
+
"State explicitly any domain-specific facts the task depends on — the component will not have access to these examples in future.",
|
|
118
|
+
"",
|
|
119
|
+
"Return only the new instruction, inside a ``` block."
|
|
120
|
+
].join("\n");
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* A rotation covering the four directions a proposal can move in: fix what is
|
|
124
|
+
* broken, cut what is dead, widen what is too narrow, and start over.
|
|
125
|
+
*
|
|
126
|
+
* Drawing a proposal k times from one template samples one direction k times.
|
|
127
|
+
* Rotating costs nothing extra — same call count, same rollouts — and is the
|
|
128
|
+
* cheapest diversity available. Opt in via `reflection.strategies`; the
|
|
129
|
+
* default stays the published single prompt.
|
|
130
|
+
*/
|
|
131
|
+
function diverseReflectionStrategies() {
|
|
132
|
+
return [
|
|
133
|
+
buildReflectionPrompt,
|
|
134
|
+
buildSimplifyPrompt,
|
|
135
|
+
buildGeneralizePrompt,
|
|
136
|
+
buildRewritePrompt
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Trims a reflective dataset down to what one prompt should carry: the worst
|
|
141
|
+
* scoring records first, since reflection is about diagnosing failures, and
|
|
142
|
+
* long strings cut to a share of the character budget.
|
|
143
|
+
*/
|
|
144
|
+
function limitReflectiveRecords(args) {
|
|
145
|
+
const { records, maxRecords, maxCharacters } = args;
|
|
146
|
+
let kept = [...records];
|
|
147
|
+
if (maxRecords !== void 0 && kept.length > maxRecords) kept = kept.map((record, position) => ({
|
|
148
|
+
record,
|
|
149
|
+
position
|
|
150
|
+
})).sort((a, b) => (a.record.score ?? Number.POSITIVE_INFINITY) - (b.record.score ?? Number.POSITIVE_INFINITY)).slice(0, maxRecords).sort((a, b) => a.position - b.position).map((entry) => entry.record);
|
|
151
|
+
if (maxCharacters === void 0 || kept.length === 0) return kept;
|
|
152
|
+
const perRecord = Math.max(MIN_STRING_BUDGET, Math.floor(maxCharacters / kept.length));
|
|
153
|
+
kept = kept.map((record) => truncateStrings(record, perRecord));
|
|
154
|
+
while (kept.length > 1 && serializeRecords(kept).length > maxCharacters) kept.pop();
|
|
155
|
+
return kept;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Default instruction proposer: one reflection call per component being
|
|
159
|
+
* updated. Adapters override this via `proposeNewTexts` when components need
|
|
160
|
+
* coupled updates or a structured proposal format; `buildPrompt` is the
|
|
161
|
+
* lighter seam for changing only the wording.
|
|
162
|
+
*/
|
|
163
|
+
function createDefaultProposer(options = {}) {
|
|
164
|
+
const { buildPrompt, strategies, limits = {} } = options;
|
|
165
|
+
if (buildPrompt !== void 0 && strategies !== void 0) throw new Error("createDefaultProposer takes buildPrompt or strategies, not both");
|
|
166
|
+
if (strategies !== void 0 && strategies.length === 0) throw new Error("createDefaultProposer requires a non-empty strategies list");
|
|
167
|
+
const rotation = strategies ?? [buildPrompt ?? buildReflectionPrompt];
|
|
168
|
+
return async (args) => {
|
|
169
|
+
const { candidate, reflectiveDataset, componentsToUpdate, rejectedProposals, attempt = 0, reflect, signal } = args;
|
|
170
|
+
const proposed = {};
|
|
171
|
+
const strategy = rotation[attempt % rotation.length];
|
|
172
|
+
for (const componentName of componentsToUpdate) {
|
|
173
|
+
const records = reflectiveDataset[componentName];
|
|
174
|
+
if (records === void 0 || records.length === 0) continue;
|
|
175
|
+
const currentText = candidate[componentName] ?? "";
|
|
176
|
+
const response = await reflect({
|
|
177
|
+
prompt: strategy({
|
|
178
|
+
componentName,
|
|
179
|
+
currentText,
|
|
180
|
+
records: limitReflectiveRecords({
|
|
181
|
+
records,
|
|
182
|
+
...limits
|
|
183
|
+
}),
|
|
184
|
+
rejected: rejectedProposals?.[componentName]
|
|
185
|
+
}),
|
|
186
|
+
signal
|
|
187
|
+
});
|
|
188
|
+
const newText = parseProposedText(response);
|
|
189
|
+
if (newText.length > 0 && newText !== currentText) proposed[componentName] = newText;
|
|
190
|
+
}
|
|
191
|
+
return proposed;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function serializeRecords(records) {
|
|
195
|
+
return JSON.stringify(records, jsonSafeReplacer, 2);
|
|
196
|
+
}
|
|
197
|
+
/** Recursive so a long trace buried in a nested output is cut too. */
|
|
198
|
+
function truncateStrings(value, budget) {
|
|
199
|
+
if (typeof value === "string") return value.length <= budget ? value : `${value.slice(0, budget)}${TRUNCATION_MARKER}`;
|
|
200
|
+
if (Array.isArray(value)) return value.map((item) => truncateStrings(item, budget));
|
|
201
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, truncateStrings(item, budget)]));
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
function jsonSafeReplacer(_key, value) {
|
|
205
|
+
if (value instanceof Error) return {
|
|
206
|
+
name: value.name,
|
|
207
|
+
message: value.message
|
|
208
|
+
};
|
|
209
|
+
if (value instanceof Map) return Object.fromEntries(value);
|
|
210
|
+
if (value instanceof Set) return [...value];
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
export { createDefaultProposer as a, buildSimplifyPrompt as i, buildReflectionPrompt as n, diverseReflectionStrategies as o, buildRewritePrompt as r, buildGeneralizePrompt as t };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/rng.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Seeded pseudo-random number generator.
|
|
4
|
+
*
|
|
5
|
+
* Optimization runs are expensive and long-lived, so every stochastic decision
|
|
6
|
+
* in the engine flows through an explicit Rng instance rather than Math.random.
|
|
7
|
+
* That makes runs reproducible and checkpoints replayable.
|
|
8
|
+
*/
|
|
9
|
+
interface Rng {
|
|
10
|
+
next(): number;
|
|
11
|
+
nextInt(maxExclusive: number): number;
|
|
12
|
+
pick<T>(items: readonly T[]): T;
|
|
13
|
+
shuffle<T>(items: readonly T[]): T[];
|
|
14
|
+
/** `k` distinct items, or every item when `k` exceeds the input length. */
|
|
15
|
+
sample<T>(items: readonly T[], k: number): T[];
|
|
16
|
+
/** Picks proportionally to `weights`; falls back to uniform when all are 0. */
|
|
17
|
+
weighted<T>(items: readonly T[], weights: readonly number[]): T;
|
|
18
|
+
/** Position in the stream, for checkpointing. Restore via `createSeededRng`. */
|
|
19
|
+
state(): number;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { Rng as t };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/rng.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Seeded pseudo-random number generator.
|
|
4
|
+
*
|
|
5
|
+
* Optimization runs are expensive and long-lived, so every stochastic decision
|
|
6
|
+
* in the engine flows through an explicit Rng instance rather than Math.random.
|
|
7
|
+
* That makes runs reproducible and checkpoints replayable.
|
|
8
|
+
*/
|
|
9
|
+
interface Rng {
|
|
10
|
+
next(): number;
|
|
11
|
+
nextInt(maxExclusive: number): number;
|
|
12
|
+
pick<T>(items: readonly T[]): T;
|
|
13
|
+
shuffle<T>(items: readonly T[]): T[];
|
|
14
|
+
/** `k` distinct items, or every item when `k` exceeds the input length. */
|
|
15
|
+
sample<T>(items: readonly T[], k: number): T[];
|
|
16
|
+
/** Picks proportionally to `weights`; falls back to uniform when all are 0. */
|
|
17
|
+
weighted<T>(items: readonly T[], weights: readonly number[]): T;
|
|
18
|
+
/** Position in the stream, for checkpointing. Restore via `createSeededRng`. */
|
|
19
|
+
state(): number;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { Rng as t };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/rng.ts
|
|
2
|
+
const DEFAULT_SEED = 2654435769;
|
|
3
|
+
function createSeededRng(seed, resumeState) {
|
|
4
|
+
let state = resumeState ?? normalizeSeed(seed);
|
|
5
|
+
function next() {
|
|
6
|
+
state = state + 1831565813 >>> 0;
|
|
7
|
+
let t = state;
|
|
8
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
9
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
10
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
11
|
+
}
|
|
12
|
+
function nextInt(maxExclusive) {
|
|
13
|
+
if (!Number.isFinite(maxExclusive) || maxExclusive <= 0) throw new Error(`nextInt requires a positive bound, received ${maxExclusive}`);
|
|
14
|
+
return Math.floor(next() * maxExclusive);
|
|
15
|
+
}
|
|
16
|
+
function pick(items) {
|
|
17
|
+
if (items.length === 0) throw new Error("Cannot pick from an empty array");
|
|
18
|
+
return items[nextInt(items.length)];
|
|
19
|
+
}
|
|
20
|
+
function shuffle(items) {
|
|
21
|
+
const copy = [...items];
|
|
22
|
+
for (let i = copy.length - 1; i > 0; i -= 1) {
|
|
23
|
+
const j = nextInt(i + 1);
|
|
24
|
+
const swap = copy[i];
|
|
25
|
+
copy[i] = copy[j];
|
|
26
|
+
copy[j] = swap;
|
|
27
|
+
}
|
|
28
|
+
return copy;
|
|
29
|
+
}
|
|
30
|
+
function sample(items, k) {
|
|
31
|
+
if (k <= 0) return [];
|
|
32
|
+
return shuffle(items).slice(0, k);
|
|
33
|
+
}
|
|
34
|
+
function weighted(items, weights) {
|
|
35
|
+
if (items.length === 0) throw new Error("Cannot pick from an empty array");
|
|
36
|
+
const positive = items.map((_, index) => Math.max(0, weights[index] ?? 0));
|
|
37
|
+
const total = positive.reduce((sum, weight) => sum + weight, 0);
|
|
38
|
+
if (total <= 0) return pick(items);
|
|
39
|
+
let threshold = next() * total;
|
|
40
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
41
|
+
threshold -= positive[index];
|
|
42
|
+
if (threshold < 0) return items[index];
|
|
43
|
+
}
|
|
44
|
+
return items[items.length - 1];
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
next,
|
|
48
|
+
nextInt,
|
|
49
|
+
pick,
|
|
50
|
+
shuffle,
|
|
51
|
+
sample,
|
|
52
|
+
weighted,
|
|
53
|
+
state: () => state
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function normalizeSeed(seed) {
|
|
57
|
+
if (!Number.isFinite(seed)) return DEFAULT_SEED;
|
|
58
|
+
const normalized = Math.floor(seed) >>> 0;
|
|
59
|
+
return normalized === 0 ? DEFAULT_SEED : normalized;
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
Object.defineProperty(exports, "createSeededRng", {
|
|
63
|
+
enumerable: true,
|
|
64
|
+
get: function() {
|
|
65
|
+
return createSeededRng;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/rng.ts
|
|
2
|
+
const DEFAULT_SEED = 2654435769;
|
|
3
|
+
function createSeededRng(seed, resumeState) {
|
|
4
|
+
let state = resumeState ?? normalizeSeed(seed);
|
|
5
|
+
function next() {
|
|
6
|
+
state = state + 1831565813 >>> 0;
|
|
7
|
+
let t = state;
|
|
8
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
9
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
10
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
11
|
+
}
|
|
12
|
+
function nextInt(maxExclusive) {
|
|
13
|
+
if (!Number.isFinite(maxExclusive) || maxExclusive <= 0) throw new Error(`nextInt requires a positive bound, received ${maxExclusive}`);
|
|
14
|
+
return Math.floor(next() * maxExclusive);
|
|
15
|
+
}
|
|
16
|
+
function pick(items) {
|
|
17
|
+
if (items.length === 0) throw new Error("Cannot pick from an empty array");
|
|
18
|
+
return items[nextInt(items.length)];
|
|
19
|
+
}
|
|
20
|
+
function shuffle(items) {
|
|
21
|
+
const copy = [...items];
|
|
22
|
+
for (let i = copy.length - 1; i > 0; i -= 1) {
|
|
23
|
+
const j = nextInt(i + 1);
|
|
24
|
+
const swap = copy[i];
|
|
25
|
+
copy[i] = copy[j];
|
|
26
|
+
copy[j] = swap;
|
|
27
|
+
}
|
|
28
|
+
return copy;
|
|
29
|
+
}
|
|
30
|
+
function sample(items, k) {
|
|
31
|
+
if (k <= 0) return [];
|
|
32
|
+
return shuffle(items).slice(0, k);
|
|
33
|
+
}
|
|
34
|
+
function weighted(items, weights) {
|
|
35
|
+
if (items.length === 0) throw new Error("Cannot pick from an empty array");
|
|
36
|
+
const positive = items.map((_, index) => Math.max(0, weights[index] ?? 0));
|
|
37
|
+
const total = positive.reduce((sum, weight) => sum + weight, 0);
|
|
38
|
+
if (total <= 0) return pick(items);
|
|
39
|
+
let threshold = next() * total;
|
|
40
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
41
|
+
threshold -= positive[index];
|
|
42
|
+
if (threshold < 0) return items[index];
|
|
43
|
+
}
|
|
44
|
+
return items[items.length - 1];
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
next,
|
|
48
|
+
nextInt,
|
|
49
|
+
pick,
|
|
50
|
+
shuffle,
|
|
51
|
+
sample,
|
|
52
|
+
weighted,
|
|
53
|
+
state: () => state
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function normalizeSeed(seed) {
|
|
57
|
+
if (!Number.isFinite(seed)) return DEFAULT_SEED;
|
|
58
|
+
const normalized = Math.floor(seed) >>> 0;
|
|
59
|
+
return normalized === 0 ? DEFAULT_SEED : normalized;
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { createSeededRng as t };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/sampling.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shuffles the training set once per epoch and walks it in fixed-size chunks, so
|
|
4
|
+
* every training example is seen once before any is seen twice.
|
|
5
|
+
*/
|
|
6
|
+
function createEpochShuffledSampler(args) {
|
|
7
|
+
const { minibatchSize } = args;
|
|
8
|
+
let shuffled = [];
|
|
9
|
+
let epoch = -1;
|
|
10
|
+
let lastTrainsetSize = -1;
|
|
11
|
+
const sampler = ({ trainingSet, iteration, rng }) => {
|
|
12
|
+
if (trainingSet.length === 0) throw new Error("Cannot sample a minibatch from an empty trainingSet");
|
|
13
|
+
const baseIndex = iteration * minibatchSize;
|
|
14
|
+
const currentEpoch = shuffled.length === 0 ? 0 : Math.floor(baseIndex / shuffled.length);
|
|
15
|
+
if (shuffled.length === 0 || trainingSet.length !== lastTrainsetSize || currentEpoch > epoch) {
|
|
16
|
+
epoch = currentEpoch;
|
|
17
|
+
lastTrainsetSize = trainingSet.length;
|
|
18
|
+
shuffled = buildPaddedShuffle({
|
|
19
|
+
size: trainingSet.length,
|
|
20
|
+
minibatchSize,
|
|
21
|
+
rng
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const start = baseIndex % shuffled.length;
|
|
25
|
+
return shuffled.slice(start, start + minibatchSize);
|
|
26
|
+
};
|
|
27
|
+
sampler.state = () => ({
|
|
28
|
+
shuffled: [...shuffled],
|
|
29
|
+
epoch,
|
|
30
|
+
lastTrainsetSize
|
|
31
|
+
});
|
|
32
|
+
sampler.restore = (state) => {
|
|
33
|
+
if (!isSamplerState(state)) return;
|
|
34
|
+
shuffled = [...state.shuffled];
|
|
35
|
+
epoch = state.epoch;
|
|
36
|
+
lastTrainsetSize = state.lastTrainsetSize;
|
|
37
|
+
};
|
|
38
|
+
return sampler;
|
|
39
|
+
}
|
|
40
|
+
function isSamplerState(state) {
|
|
41
|
+
if (state === null || typeof state !== "object") return false;
|
|
42
|
+
const candidate = state;
|
|
43
|
+
return Array.isArray(candidate.shuffled) && typeof candidate.epoch === "number" && typeof candidate.lastTrainsetSize === "number";
|
|
44
|
+
}
|
|
45
|
+
function buildPaddedShuffle(args) {
|
|
46
|
+
const { size, minibatchSize, rng } = args;
|
|
47
|
+
const indices = rng.shuffle(Array.from({ length: size }, (_, i) => i));
|
|
48
|
+
const remainder = indices.length % minibatchSize;
|
|
49
|
+
const padding = remainder === 0 ? 0 : minibatchSize - remainder;
|
|
50
|
+
const frequencies = new Map(indices.map((index) => [index, 1]));
|
|
51
|
+
for (let i = 0; i < padding; i += 1) {
|
|
52
|
+
const leastUsed = indices.reduce((best, index) => frequencies.get(index) < frequencies.get(best) ? index : best);
|
|
53
|
+
indices.push(leastUsed);
|
|
54
|
+
frequencies.set(leastUsed, frequencies.get(leastUsed) + 1);
|
|
55
|
+
}
|
|
56
|
+
return indices;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { createEpochShuffledSampler as t };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { t as Rng } from "./rng-BR5MOedA.mjs";
|
|
2
|
+
//#region src/sampling.d.ts
|
|
3
|
+
type BatchSampler<Datum> = ((args: {
|
|
4
|
+
trainingSet: readonly Datum[];
|
|
5
|
+
/**
|
|
6
|
+
* Position in the sequence of minibatches drawn so far, not the loop
|
|
7
|
+
* iteration. Draws made concurrently within one iteration arrive as
|
|
8
|
+
* consecutive positions, so siblings diagnose different failures instead of
|
|
9
|
+
* sharing a batch.
|
|
10
|
+
*/
|
|
11
|
+
iteration: number;
|
|
12
|
+
rng: Rng;
|
|
13
|
+
}) => number[]) & {
|
|
14
|
+
/**
|
|
15
|
+
* Position within the sampler's own schedule, checkpointed alongside the
|
|
16
|
+
* random stream. Without it a resumed run restarts its epoch and re-walks
|
|
17
|
+
* minibatches the interrupted run had already spent.
|
|
18
|
+
*/
|
|
19
|
+
state?: () => unknown;
|
|
20
|
+
restore?: (state: unknown) => void;
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export { BatchSampler as t };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/sampling.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shuffles the training set once per epoch and walks it in fixed-size chunks, so
|
|
4
|
+
* every training example is seen once before any is seen twice.
|
|
5
|
+
*/
|
|
6
|
+
function createEpochShuffledSampler(args) {
|
|
7
|
+
const { minibatchSize } = args;
|
|
8
|
+
let shuffled = [];
|
|
9
|
+
let epoch = -1;
|
|
10
|
+
let lastTrainsetSize = -1;
|
|
11
|
+
const sampler = ({ trainingSet, iteration, rng }) => {
|
|
12
|
+
if (trainingSet.length === 0) throw new Error("Cannot sample a minibatch from an empty trainingSet");
|
|
13
|
+
const baseIndex = iteration * minibatchSize;
|
|
14
|
+
const currentEpoch = shuffled.length === 0 ? 0 : Math.floor(baseIndex / shuffled.length);
|
|
15
|
+
if (shuffled.length === 0 || trainingSet.length !== lastTrainsetSize || currentEpoch > epoch) {
|
|
16
|
+
epoch = currentEpoch;
|
|
17
|
+
lastTrainsetSize = trainingSet.length;
|
|
18
|
+
shuffled = buildPaddedShuffle({
|
|
19
|
+
size: trainingSet.length,
|
|
20
|
+
minibatchSize,
|
|
21
|
+
rng
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const start = baseIndex % shuffled.length;
|
|
25
|
+
return shuffled.slice(start, start + minibatchSize);
|
|
26
|
+
};
|
|
27
|
+
sampler.state = () => ({
|
|
28
|
+
shuffled: [...shuffled],
|
|
29
|
+
epoch,
|
|
30
|
+
lastTrainsetSize
|
|
31
|
+
});
|
|
32
|
+
sampler.restore = (state) => {
|
|
33
|
+
if (!isSamplerState(state)) return;
|
|
34
|
+
shuffled = [...state.shuffled];
|
|
35
|
+
epoch = state.epoch;
|
|
36
|
+
lastTrainsetSize = state.lastTrainsetSize;
|
|
37
|
+
};
|
|
38
|
+
return sampler;
|
|
39
|
+
}
|
|
40
|
+
function isSamplerState(state) {
|
|
41
|
+
if (state === null || typeof state !== "object") return false;
|
|
42
|
+
const candidate = state;
|
|
43
|
+
return Array.isArray(candidate.shuffled) && typeof candidate.epoch === "number" && typeof candidate.lastTrainsetSize === "number";
|
|
44
|
+
}
|
|
45
|
+
function buildPaddedShuffle(args) {
|
|
46
|
+
const { size, minibatchSize, rng } = args;
|
|
47
|
+
const indices = rng.shuffle(Array.from({ length: size }, (_, i) => i));
|
|
48
|
+
const remainder = indices.length % minibatchSize;
|
|
49
|
+
const padding = remainder === 0 ? 0 : minibatchSize - remainder;
|
|
50
|
+
const frequencies = new Map(indices.map((index) => [index, 1]));
|
|
51
|
+
for (let i = 0; i < padding; i += 1) {
|
|
52
|
+
const leastUsed = indices.reduce((best, index) => frequencies.get(index) < frequencies.get(best) ? index : best);
|
|
53
|
+
indices.push(leastUsed);
|
|
54
|
+
frequencies.set(leastUsed, frequencies.get(leastUsed) + 1);
|
|
55
|
+
}
|
|
56
|
+
return indices;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
Object.defineProperty(exports, "createEpochShuffledSampler", {
|
|
60
|
+
enumerable: true,
|
|
61
|
+
get: function() {
|
|
62
|
+
return createEpochShuffledSampler;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { t as Rng } from "./rng-BR5MOedA.cjs";
|
|
2
|
+
//#region src/sampling.d.ts
|
|
3
|
+
type BatchSampler<Datum> = ((args: {
|
|
4
|
+
trainingSet: readonly Datum[];
|
|
5
|
+
/**
|
|
6
|
+
* Position in the sequence of minibatches drawn so far, not the loop
|
|
7
|
+
* iteration. Draws made concurrently within one iteration arrive as
|
|
8
|
+
* consecutive positions, so siblings diagnose different failures instead of
|
|
9
|
+
* sharing a batch.
|
|
10
|
+
*/
|
|
11
|
+
iteration: number;
|
|
12
|
+
rng: Rng;
|
|
13
|
+
}) => number[]) & {
|
|
14
|
+
/**
|
|
15
|
+
* Position within the sampler's own schedule, checkpointed alongside the
|
|
16
|
+
* random stream. Without it a resumed run restarts its epoch and re-walks
|
|
17
|
+
* minibatches the interrupted run had already spent.
|
|
18
|
+
*/
|
|
19
|
+
state?: () => unknown;
|
|
20
|
+
restore?: (state: unknown) => void;
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export { BatchSampler as t };
|