@holoscript/framework 6.0.3 → 6.0.4
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/CHANGELOG.md +1 -2
- package/ROADMAP.md +68 -66
- package/dist/{InvisibleWallet-BB6tFvRA.d.cts → InvisibleWallet-EFiuaLn3.d.cts} +1 -1
- package/dist/{OrchestratorAgent-BvWgf9uw.d.cts → OrchestratorAgent-CrLDGNL6.d.cts} +1 -1
- package/dist/agents/index.cjs +11 -10
- package/dist/agents/index.d.cts +4 -16
- package/dist/ai/index.cjs +2 -2
- package/dist/behavior.cjs +10 -0
- package/dist/economy/index.cjs +4 -4
- package/dist/economy/index.d.cts +2 -2
- package/dist/index.cjs +33 -11
- package/dist/index.d.cts +3 -3
- package/dist/swarm/index.cjs +3 -0
- package/package.json +14 -9
- package/src/__tests__/bounty-marketplace.test.ts +53 -21
- package/src/__tests__/delegation.test.ts +1 -4
- package/src/__tests__/done-log-audit.test.ts +38 -46
- package/src/__tests__/framework.test.ts +172 -53
- package/src/__tests__/goal-synthesizer.test.ts +9 -6
- package/src/__tests__/presence.test.ts +1 -1
- package/src/__tests__/protocol-agent.test.ts +12 -11
- package/src/__tests__/revenue-splitter.test.ts +22 -15
- package/src/__tests__/scenario-driven-todo.test.ts +55 -35
- package/src/__tests__/self-improve.test.ts +28 -9
- package/src/__tests__/service-lifecycle.test.ts +9 -3
- package/src/__tests__/skill-router.test.ts +3 -3
- package/src/agents/CulturalMemory.ts +6 -6
- package/src/agents/DelegationTraceHooks.ts +560 -0
- package/src/agents/FederatedRegistryAdapter.ts +1 -1
- package/src/agents/NormEngine.ts +3 -8
- package/src/agents/OrchestratorAgent.ts +1 -1
- package/src/agents/TaskDelegationService.ts +5 -9
- package/src/agents/__tests__/AgentWalletRegistry.test.ts +5 -4
- package/src/agents/__tests__/CrossRealityHandoff.test.ts +9 -3
- package/src/agents/__tests__/DelegationTraceHooks.test.ts +390 -0
- package/src/agents/__tests__/TaskDelegationService.test.ts +4 -2
- package/src/agents/spatial-comms/Layer1RealTime.ts +36 -19
- package/src/agents/spatial-comms/Layer2A2A.ts +1 -3
- package/src/agents/spatial-comms/Layer3MCP.ts +13 -4
- package/src/agents/spatial-comms/ProtocolTypes.ts +5 -2
- package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +2 -2
- package/src/ai/HoloScriptGenerator.ts +2 -2
- package/src/ai/__tests__/PerceptionSystem.prod.test.ts +1 -1
- package/src/ai/__tests__/PerceptionSystem.test.ts +14 -14
- package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +1 -1
- package/src/ai/index.ts +5 -1
- package/src/board/audit.ts +17 -6
- package/src/board/board-ops.ts +45 -15
- package/src/board/board-types.ts +94 -20
- package/src/delegation.ts +5 -3
- package/src/distributed-claimer.ts +13 -2
- package/src/economy/BountyManager.ts +40 -18
- package/src/economy/KnowledgeMarketplace.ts +27 -8
- package/src/economy/PaymentWebhookService.ts +0 -1
- package/src/economy/RevenueSplitter.ts +2 -4
- package/src/economy/UnifiedBudgetOptimizer.ts +8 -9
- package/src/economy/_core-stubs.ts +1 -1
- package/src/economy/x402-facilitator.ts +17 -8
- package/src/index.ts +16 -12
- package/src/knowledge/__tests__/knowledge-consolidator.test.ts +138 -89
- package/src/knowledge/__tests__/knowledge-store-vector.test.ts +59 -16
- package/src/knowledge/brain.ts +7 -7
- package/src/knowledge/consolidation.ts +16 -16
- package/src/knowledge/knowledge-consolidator.ts +60 -30
- package/src/knowledge/knowledge-store.ts +83 -45
- package/src/learning/ProceduralCompiler.ts +6 -1
- package/src/learning/learning/MemoryConsolidator.ts +102 -0
- package/src/learning/learning/MemoryScorer.ts +69 -0
- package/src/learning/learning/ProceduralCompiler.ts +45 -0
- package/src/learning/learning/SemanticClusterer.ts +66 -0
- package/src/llm/llm-adapter.ts +24 -10
- package/src/mesh/index.ts +37 -17
- package/src/protocol/goal-synthesizer.ts +24 -34
- package/src/protocol/implementations.ts +91 -22
- package/src/protocol/micro-phase-decomposer.ts +25 -17
- package/src/protocol/micro-step-decomposer.test.ts +104 -39
- package/src/protocol-agent.test.ts +17 -7
- package/src/protocol-agent.ts +45 -42
- package/src/self-improve/absorb-scanner.ts +9 -6
- package/src/self-improve/evolution-engine.ts +36 -18
- package/src/self-improve/framework-absorber.ts +21 -16
- package/src/self-improve/index.ts +2 -10
- package/src/self-improve/prompt-optimizer.ts +31 -19
- package/src/self-improve/test-generator.ts +16 -12
- package/src/skill-router.ts +7 -6
- package/src/swarm/messaging/GossipProtocol.ts +1 -1
- package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +31 -9
- package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +21 -7
- package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +24 -8
- package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +6 -2
- package/src/team.ts +277 -122
- package/src/training/scripts/generate-spatial-dataset.ts +1 -1
- package/src/training/training/LRScheduler.ts +377 -0
- package/src/training/training/QualityScoringPipeline.ts +139 -0
- package/src/training/training/SoftDedup.ts +461 -0
- package/src/training/training/SparsityMonitor.ts +685 -0
- package/src/training/training/SparsityMonitorTypes.ts +209 -0
- package/src/training/training/SpatialTrainingDataGenerator.ts +1526 -0
- package/src/training/training/SpatialTrainingDataTypes.ts +216 -0
- package/src/training/training/TrainingPipelineConfig.ts +215 -0
- package/src/training/training/__tests__/CorpusValidation.test.ts +87 -0
- package/src/training/training/__tests__/LRScheduler.test.ts +592 -0
- package/src/training/training/__tests__/SoftDedup.test.ts +415 -0
- package/src/training/training/__tests__/SparsityMonitor.test.ts +1623 -0
- package/src/training/training/__tests__/SpatialCorpusValidation.test.ts +72 -0
- package/src/training/training/__tests__/SpatialTrainingDataGenerator.test.ts +1244 -0
- package/src/training/training/__tests__/TrainingMonkeyIntegration.test.ts +897 -0
- package/src/training/training/__tests__/TrainingPipelineConfig.test.ts +202 -0
- package/src/training/training/__tests__/schema.test.ts +72 -0
- package/src/training/training/__tests__/training-constants.test.ts +106 -0
- package/src/training/training/__tests__/trait-mappings.test.ts +81 -0
- package/src/training/training/constants.ts +94 -0
- package/src/training/training/index.ts +17 -0
- package/src/training/training/schema.ts +147 -0
- package/src/training/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
- package/src/training/training/scripts/generate-spatial-dataset.ts +521 -0
- package/src/training/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
- package/src/training/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
- package/src/training/training/trainingmonkey/index.ts +26 -0
- package/src/training/training/trait-mappings.ts +157 -0
- package/src/types.ts +2 -7
- package/ALL-test-results.json +0 -1
- package/LICENSE +0 -21
- package/dist/AgentManifest-CB4xM-Ma.d.ts +0 -704
- package/dist/BehaviorTree-BrBFECv5.d.ts +0 -103
- package/dist/InvisibleWallet-rtRrBOA8.d.ts +0 -1732
- package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +0 -798
- package/dist/agents/index.d.ts +0 -1788
- package/dist/agents/index.js +0 -4695
- package/dist/ai/index.d.ts +0 -1753
- package/dist/ai/index.js +0 -5244
- package/dist/behavior.d.ts +0 -130
- package/dist/behavior.js +0 -407
- package/dist/economy/index.d.ts +0 -747
- package/dist/economy/index.js +0 -3617
- package/dist/implementations-D9T3un9D.d.ts +0 -236
- package/dist/index.d.ts +0 -1729
- package/dist/index.js +0 -24277
- package/dist/learning/index.d.ts +0 -104
- package/dist/learning/index.js +0 -189
- package/dist/negotiation/index.d.ts +0 -610
- package/dist/negotiation/index.js +0 -931
- package/dist/skills/index.d.ts +0 -289
- package/dist/skills/index.js +0 -1079
- package/dist/swarm/index.d.ts +0 -2433
- package/dist/swarm/index.js +0 -5221
- package/dist/training/index.d.ts +0 -1734
- package/dist/training/index.js +0 -2687
- package/extract-failures.js +0 -10
- package/src/training/training/data/novel-use-cases.jsonl +0 -153
- package/src/training/training/data/spatial-reasoning-10k.jsonl +0 -9354
- package/src/types/core-stubs.d.ts +0 -113
- package/test-output.txt +0 -0
- package/test-result.json +0 -1
- package/tsc-errors.txt +0 -4
- package/tsc_output.txt +0 -0
- package/typescript-errors-2.txt +0 -0
- package/typescript-errors.txt +0 -22
- package/vitest-log-utf8.txt +0 -268
- package/vitest-log.txt +0 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
LRScheduler,
|
|
4
|
+
createSFTScheduler,
|
|
5
|
+
createGRPOScheduler,
|
|
6
|
+
DEFAULT_LR_SCHEDULER_CONFIG,
|
|
7
|
+
GRPO_LR_SCHEDULER_CONFIG,
|
|
8
|
+
} from '../LRScheduler';
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// TESTS
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
describe('LRScheduler', () => {
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// CONSTRUCTION & CONFIGURATION
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
describe('constructor', () => {
|
|
20
|
+
it('uses default config when no overrides provided', () => {
|
|
21
|
+
const scheduler = new LRScheduler();
|
|
22
|
+
const config = scheduler.getConfig();
|
|
23
|
+
expect(config).toEqual(DEFAULT_LR_SCHEDULER_CONFIG);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('merges partial config with defaults', () => {
|
|
27
|
+
const scheduler = new LRScheduler({ baseLR: 1e-3, totalSteps: 5000 });
|
|
28
|
+
const config = scheduler.getConfig();
|
|
29
|
+
expect(config.baseLR).toBe(1e-3);
|
|
30
|
+
expect(config.totalSteps).toBe(5000);
|
|
31
|
+
expect(config.warmupRatio).toBe(DEFAULT_LR_SCHEDULER_CONFIG.warmupRatio);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('throws on invalid baseLR (<= 0)', () => {
|
|
35
|
+
expect(() => new LRScheduler({ baseLR: 0 })).toThrow('baseLR');
|
|
36
|
+
expect(() => new LRScheduler({ baseLR: -1e-4 })).toThrow('baseLR');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('throws on invalid totalSteps (negative)', () => {
|
|
40
|
+
expect(() => new LRScheduler({ totalSteps: -1 })).toThrow('totalSteps');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('throws on invalid totalSteps (non-integer)', () => {
|
|
44
|
+
expect(() => new LRScheduler({ totalSteps: 100.5 })).toThrow('totalSteps');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('throws on invalid warmupRatio (< 0)', () => {
|
|
48
|
+
expect(() => new LRScheduler({ warmupRatio: -0.1 })).toThrow('warmupRatio');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('throws on invalid warmupRatio (>= 1)', () => {
|
|
52
|
+
expect(() => new LRScheduler({ warmupRatio: 1.0 })).toThrow('warmupRatio');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('throws on invalid minLR (negative)', () => {
|
|
56
|
+
expect(() => new LRScheduler({ minLR: -1 })).toThrow('minLR');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('throws on invalid minLR (>= baseLR)', () => {
|
|
60
|
+
expect(() => new LRScheduler({ baseLR: 1e-4, minLR: 1e-4 })).toThrow('minLR');
|
|
61
|
+
expect(() => new LRScheduler({ baseLR: 1e-4, minLR: 2e-4 })).toThrow('minLR');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('throws on invalid numCycles (< 1)', () => {
|
|
65
|
+
expect(() => new LRScheduler({ numCycles: 0 })).toThrow('numCycles');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('throws on invalid numCycles (non-integer)', () => {
|
|
69
|
+
expect(() => new LRScheduler({ numCycles: 1.5 })).toThrow('numCycles');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('accepts totalSteps of 0', () => {
|
|
73
|
+
const scheduler = new LRScheduler({ totalSteps: 0 });
|
|
74
|
+
expect(scheduler.getConfig().totalSteps).toBe(0);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// WARMUP PHASE
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
describe('warmup phase', () => {
|
|
83
|
+
it('starts at LR = 0 at step 0', () => {
|
|
84
|
+
const scheduler = new LRScheduler({
|
|
85
|
+
baseLR: 2e-4,
|
|
86
|
+
totalSteps: 1000,
|
|
87
|
+
warmupRatio: 0.1,
|
|
88
|
+
});
|
|
89
|
+
expect(scheduler.getLR(0)).toBe(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('ramps linearly to baseLR during warmup', () => {
|
|
93
|
+
const scheduler = new LRScheduler({
|
|
94
|
+
baseLR: 2e-4,
|
|
95
|
+
totalSteps: 1000,
|
|
96
|
+
warmupRatio: 0.1, // 100 warmup steps
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// At step 50 (halfway through warmup), LR should be ~1e-4
|
|
100
|
+
const lr50 = scheduler.getLR(50);
|
|
101
|
+
expect(lr50).toBeCloseTo(1e-4, 6);
|
|
102
|
+
|
|
103
|
+
// At step 100 (end of warmup), LR should be 2e-4
|
|
104
|
+
const lr100 = scheduler.getLR(100);
|
|
105
|
+
expect(lr100).toBeCloseTo(2e-4, 6);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('warmup is linear (monotonically increasing)', () => {
|
|
109
|
+
const scheduler = new LRScheduler({
|
|
110
|
+
baseLR: 2e-4,
|
|
111
|
+
totalSteps: 1000,
|
|
112
|
+
warmupRatio: 0.1,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
let prevLR = -1;
|
|
116
|
+
for (let step = 0; step <= 100; step++) {
|
|
117
|
+
const lr = scheduler.getLR(step);
|
|
118
|
+
expect(lr).toBeGreaterThanOrEqual(prevLR);
|
|
119
|
+
prevLR = lr;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('computes correct warmup steps', () => {
|
|
124
|
+
const scheduler = new LRScheduler({
|
|
125
|
+
totalSteps: 10000,
|
|
126
|
+
warmupRatio: 0.1,
|
|
127
|
+
});
|
|
128
|
+
expect(scheduler.getWarmupSteps()).toBe(1000);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('warmup steps rounds down', () => {
|
|
132
|
+
const scheduler = new LRScheduler({
|
|
133
|
+
totalSteps: 1000,
|
|
134
|
+
warmupRatio: 0.15, // 150 steps
|
|
135
|
+
});
|
|
136
|
+
expect(scheduler.getWarmupSteps()).toBe(150);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// COSINE DECAY PHASE
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
describe('cosine decay phase', () => {
|
|
145
|
+
it('starts at baseLR after warmup', () => {
|
|
146
|
+
const scheduler = new LRScheduler({
|
|
147
|
+
baseLR: 2e-4,
|
|
148
|
+
totalSteps: 1000,
|
|
149
|
+
warmupRatio: 0.1,
|
|
150
|
+
minLR: 0,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Right at the boundary of warmup -> decay
|
|
154
|
+
const lr = scheduler.getLR(100);
|
|
155
|
+
expect(lr).toBeCloseTo(2e-4, 6);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('ends at minLR at totalSteps', () => {
|
|
159
|
+
const scheduler = new LRScheduler({
|
|
160
|
+
baseLR: 2e-4,
|
|
161
|
+
totalSteps: 1000,
|
|
162
|
+
warmupRatio: 0.1,
|
|
163
|
+
minLR: 1e-7,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const lr = scheduler.getLR(1000);
|
|
167
|
+
expect(lr).toBeCloseTo(1e-7, 9);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('ends at 0 when minLR is 0', () => {
|
|
171
|
+
const scheduler = new LRScheduler({
|
|
172
|
+
baseLR: 2e-4,
|
|
173
|
+
totalSteps: 1000,
|
|
174
|
+
warmupRatio: 0.1,
|
|
175
|
+
minLR: 0,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const lr = scheduler.getLR(1000);
|
|
179
|
+
expect(lr).toBeCloseTo(0, 10);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('decays monotonically during single-cycle cosine', () => {
|
|
183
|
+
const scheduler = new LRScheduler({
|
|
184
|
+
baseLR: 2e-4,
|
|
185
|
+
totalSteps: 1000,
|
|
186
|
+
warmupRatio: 0.1,
|
|
187
|
+
minLR: 0,
|
|
188
|
+
numCycles: 1,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
let prevLR = Infinity;
|
|
192
|
+
for (let step = 100; step <= 1000; step += 10) {
|
|
193
|
+
const lr = scheduler.getLR(step);
|
|
194
|
+
expect(lr).toBeLessThanOrEqual(prevLR + 1e-12); // Allow tiny float errors
|
|
195
|
+
prevLR = lr;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('midpoint of decay is approximately (baseLR + minLR) / 2', () => {
|
|
200
|
+
const scheduler = new LRScheduler({
|
|
201
|
+
baseLR: 2e-4,
|
|
202
|
+
totalSteps: 1000,
|
|
203
|
+
warmupRatio: 0.1,
|
|
204
|
+
minLR: 0,
|
|
205
|
+
numCycles: 1,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Midpoint of decay: step 550 (halfway between 100 and 1000)
|
|
209
|
+
const lr = scheduler.getLR(550);
|
|
210
|
+
const expectedMidpoint = (2e-4 + 0) / 2;
|
|
211
|
+
expect(lr).toBeCloseTo(expectedMidpoint, 5);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// COSINE ANNEALING WITH WARM RESTARTS
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
describe('multiple cycles (warm restarts)', () => {
|
|
220
|
+
it('with 2 cycles, LR returns to peak mid-training', () => {
|
|
221
|
+
const scheduler = new LRScheduler({
|
|
222
|
+
baseLR: 2e-4,
|
|
223
|
+
totalSteps: 1000,
|
|
224
|
+
warmupRatio: 0.1,
|
|
225
|
+
minLR: 0,
|
|
226
|
+
numCycles: 2,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// At step 550, we should be halfway through cycle 2's rise back
|
|
230
|
+
// The cosine with 2 cycles completes a full period across the decay phase
|
|
231
|
+
// At step 550 (midpoint of decay), cos(2*pi*0.5) = cos(pi) = -1
|
|
232
|
+
// (1 + (-1)) / 2 = 0 -> minLR
|
|
233
|
+
const lrMid = scheduler.getLR(550);
|
|
234
|
+
expect(lrMid).toBeCloseTo(0, 5);
|
|
235
|
+
|
|
236
|
+
// At step 325 (quarter of decay), cos(2*pi*0.25) = cos(pi/2) = 0
|
|
237
|
+
// (1 + 0) / 2 = 0.5 -> baseLR * 0.5
|
|
238
|
+
const lrQuarter = scheduler.getLR(325);
|
|
239
|
+
expect(lrQuarter).toBeCloseTo(1e-4, 5);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// EDGE CASES
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
describe('edge cases', () => {
|
|
248
|
+
it('handles totalSteps = 0', () => {
|
|
249
|
+
const scheduler = new LRScheduler({
|
|
250
|
+
baseLR: 2e-4,
|
|
251
|
+
totalSteps: 0,
|
|
252
|
+
warmupRatio: 0.1,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Step 0 should return baseLR since warmupSteps = 0
|
|
256
|
+
const lr = scheduler.getLR(0);
|
|
257
|
+
expect(lr).toBe(2e-4);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('handles warmupRatio = 0 (no warmup)', () => {
|
|
261
|
+
const scheduler = new LRScheduler({
|
|
262
|
+
baseLR: 2e-4,
|
|
263
|
+
totalSteps: 1000,
|
|
264
|
+
warmupRatio: 0,
|
|
265
|
+
minLR: 0,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
expect(scheduler.getWarmupSteps()).toBe(0);
|
|
269
|
+
// Step 0 should already be at baseLR
|
|
270
|
+
expect(scheduler.getLR(0)).toBeCloseTo(2e-4, 6);
|
|
271
|
+
// End should be at minLR
|
|
272
|
+
expect(scheduler.getLR(1000)).toBeCloseTo(0, 10);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('clamps negative step to 0', () => {
|
|
276
|
+
const scheduler = new LRScheduler({
|
|
277
|
+
baseLR: 2e-4,
|
|
278
|
+
totalSteps: 1000,
|
|
279
|
+
warmupRatio: 0.1,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(scheduler.getLR(-10)).toBe(scheduler.getLR(0));
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('clamps step beyond totalSteps', () => {
|
|
286
|
+
const scheduler = new LRScheduler({
|
|
287
|
+
baseLR: 2e-4,
|
|
288
|
+
totalSteps: 1000,
|
|
289
|
+
warmupRatio: 0.1,
|
|
290
|
+
minLR: 1e-7,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
expect(scheduler.getLR(2000)).toBe(scheduler.getLR(1000));
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('handles very small totalSteps', () => {
|
|
297
|
+
const scheduler = new LRScheduler({
|
|
298
|
+
baseLR: 2e-4,
|
|
299
|
+
totalSteps: 1,
|
|
300
|
+
warmupRatio: 0.1,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// With 1 step and 10% warmup -> 0 warmup steps
|
|
304
|
+
expect(scheduler.getWarmupSteps()).toBe(0);
|
|
305
|
+
const lr0 = scheduler.getLR(0);
|
|
306
|
+
const lr1 = scheduler.getLR(1);
|
|
307
|
+
expect(lr0).toBeGreaterThan(0);
|
|
308
|
+
expect(lr1).toBeLessThanOrEqual(lr0);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('handles very large totalSteps', () => {
|
|
312
|
+
const scheduler = new LRScheduler({
|
|
313
|
+
baseLR: 2e-4,
|
|
314
|
+
totalSteps: 1_000_000,
|
|
315
|
+
warmupRatio: 0.1,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
expect(scheduler.getWarmupSteps()).toBe(100_000);
|
|
319
|
+
expect(scheduler.getLR(0)).toBe(0);
|
|
320
|
+
expect(scheduler.getLR(100_000)).toBeCloseTo(2e-4, 6);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
// SNAPSHOTS
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
describe('getSnapshot', () => {
|
|
329
|
+
it('returns warmup phase during warmup', () => {
|
|
330
|
+
const scheduler = new LRScheduler({
|
|
331
|
+
baseLR: 2e-4,
|
|
332
|
+
totalSteps: 1000,
|
|
333
|
+
warmupRatio: 0.1,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const snapshot = scheduler.getSnapshot(50);
|
|
337
|
+
expect(snapshot.phase).toBe('warmup');
|
|
338
|
+
expect(snapshot.step).toBe(50);
|
|
339
|
+
expect(snapshot.phaseProgress).toBeCloseTo(0.5, 5);
|
|
340
|
+
expect(snapshot.overallProgress).toBeCloseTo(0.05, 5);
|
|
341
|
+
expect(snapshot.learningRate).toBeCloseTo(1e-4, 6);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('returns decay phase after warmup', () => {
|
|
345
|
+
const scheduler = new LRScheduler({
|
|
346
|
+
baseLR: 2e-4,
|
|
347
|
+
totalSteps: 1000,
|
|
348
|
+
warmupRatio: 0.1,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const snapshot = scheduler.getSnapshot(550);
|
|
352
|
+
expect(snapshot.phase).toBe('decay');
|
|
353
|
+
expect(snapshot.step).toBe(550);
|
|
354
|
+
expect(snapshot.phaseProgress).toBe(0.5); // (550-100)/900 = 0.5
|
|
355
|
+
expect(snapshot.overallProgress).toBe(0.55);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('clamps step in snapshot', () => {
|
|
359
|
+
const scheduler = new LRScheduler({
|
|
360
|
+
baseLR: 2e-4,
|
|
361
|
+
totalSteps: 1000,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const snapshot = scheduler.getSnapshot(2000);
|
|
365
|
+
expect(snapshot.step).toBe(1000);
|
|
366
|
+
expect(snapshot.overallProgress).toBe(1);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// ---------------------------------------------------------------------------
|
|
371
|
+
// STATS
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
describe('getStats', () => {
|
|
375
|
+
it('returns correct peak and min LR', () => {
|
|
376
|
+
const scheduler = new LRScheduler({
|
|
377
|
+
baseLR: 2e-4,
|
|
378
|
+
totalSteps: 1000,
|
|
379
|
+
warmupRatio: 0.1,
|
|
380
|
+
minLR: 1e-7,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const stats = scheduler.getStats();
|
|
384
|
+
expect(stats.peakLR).toBe(2e-4);
|
|
385
|
+
expect(stats.minLR).toBe(1e-7);
|
|
386
|
+
expect(stats.warmupSteps).toBe(100);
|
|
387
|
+
expect(stats.decaySteps).toBe(900);
|
|
388
|
+
expect(stats.totalSteps).toBe(1000);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('computes reasonable average LR', () => {
|
|
392
|
+
const scheduler = new LRScheduler({
|
|
393
|
+
baseLR: 2e-4,
|
|
394
|
+
totalSteps: 1000,
|
|
395
|
+
warmupRatio: 0.1,
|
|
396
|
+
minLR: 0,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const stats = scheduler.getStats();
|
|
400
|
+
// Average should be between 0 and baseLR
|
|
401
|
+
expect(stats.avgLR).toBeGreaterThan(0);
|
|
402
|
+
expect(stats.avgLR).toBeLessThan(2e-4);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('handles totalSteps = 0', () => {
|
|
406
|
+
const scheduler = new LRScheduler({
|
|
407
|
+
baseLR: 2e-4,
|
|
408
|
+
totalSteps: 0,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const stats = scheduler.getStats();
|
|
412
|
+
expect(stats.totalSteps).toBe(0);
|
|
413
|
+
expect(stats.warmupSteps).toBe(0);
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
// SCHEDULE GENERATION
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
|
|
421
|
+
describe('getSchedule', () => {
|
|
422
|
+
it('generates requested number of points', () => {
|
|
423
|
+
const scheduler = new LRScheduler({
|
|
424
|
+
baseLR: 2e-4,
|
|
425
|
+
totalSteps: 1000,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const schedule = scheduler.getSchedule(50);
|
|
429
|
+
expect(schedule).toHaveLength(50);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('starts at step 0 and ends at totalSteps', () => {
|
|
433
|
+
const scheduler = new LRScheduler({
|
|
434
|
+
baseLR: 2e-4,
|
|
435
|
+
totalSteps: 1000,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const schedule = scheduler.getSchedule(100);
|
|
439
|
+
expect(schedule[0][0]).toBe(0);
|
|
440
|
+
expect(schedule[schedule.length - 1][0]).toBe(1000);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('each entry is [step, lr] pair', () => {
|
|
444
|
+
const scheduler = new LRScheduler({
|
|
445
|
+
baseLR: 2e-4,
|
|
446
|
+
totalSteps: 1000,
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const schedule = scheduler.getSchedule(10);
|
|
450
|
+
for (const [step, lr] of schedule) {
|
|
451
|
+
expect(typeof step).toBe('number');
|
|
452
|
+
expect(typeof lr).toBe('number');
|
|
453
|
+
expect(step).toBeGreaterThanOrEqual(0);
|
|
454
|
+
expect(step).toBeLessThanOrEqual(1000);
|
|
455
|
+
expect(lr).toBeGreaterThanOrEqual(0);
|
|
456
|
+
expect(lr).toBeLessThanOrEqual(2e-4 + 1e-10);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('enforces minimum of 2 points', () => {
|
|
461
|
+
const scheduler = new LRScheduler({
|
|
462
|
+
baseLR: 2e-4,
|
|
463
|
+
totalSteps: 1000,
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
const schedule = scheduler.getSchedule(1);
|
|
467
|
+
expect(schedule.length).toBeGreaterThanOrEqual(2);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// ---------------------------------------------------------------------------
|
|
472
|
+
// FACTORY FUNCTIONS
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
describe('createSFTScheduler', () => {
|
|
476
|
+
it('creates scheduler with SFT defaults', () => {
|
|
477
|
+
const scheduler = createSFTScheduler();
|
|
478
|
+
expect(scheduler.getConfig().baseLR).toBe(2e-4);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('allows overrides on SFT defaults', () => {
|
|
482
|
+
const scheduler = createSFTScheduler({ totalSteps: 5000 });
|
|
483
|
+
expect(scheduler.getConfig().baseLR).toBe(2e-4);
|
|
484
|
+
expect(scheduler.getConfig().totalSteps).toBe(5000);
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
describe('createGRPOScheduler', () => {
|
|
489
|
+
it('creates scheduler with GRPO defaults', () => {
|
|
490
|
+
const scheduler = createGRPOScheduler();
|
|
491
|
+
expect(scheduler.getConfig().baseLR).toBe(1e-6);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('allows overrides on GRPO defaults', () => {
|
|
495
|
+
const scheduler = createGRPOScheduler({ totalSteps: 2000 });
|
|
496
|
+
expect(scheduler.getConfig().baseLR).toBe(1e-6);
|
|
497
|
+
expect(scheduler.getConfig().totalSteps).toBe(2000);
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// ---------------------------------------------------------------------------
|
|
502
|
+
// DEFAULT CONFIGS
|
|
503
|
+
// ---------------------------------------------------------------------------
|
|
504
|
+
|
|
505
|
+
describe('DEFAULT_LR_SCHEDULER_CONFIG', () => {
|
|
506
|
+
it('has expected values per W.006 and W.009', () => {
|
|
507
|
+
expect(DEFAULT_LR_SCHEDULER_CONFIG.baseLR).toBe(2e-4);
|
|
508
|
+
expect(DEFAULT_LR_SCHEDULER_CONFIG.warmupRatio).toBe(0.1);
|
|
509
|
+
expect(DEFAULT_LR_SCHEDULER_CONFIG.minLR).toBe(0);
|
|
510
|
+
expect(DEFAULT_LR_SCHEDULER_CONFIG.numCycles).toBe(1);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
describe('GRPO_LR_SCHEDULER_CONFIG', () => {
|
|
515
|
+
it('has lower baseLR for GRPO', () => {
|
|
516
|
+
expect(GRPO_LR_SCHEDULER_CONFIG.baseLR).toBe(1e-6);
|
|
517
|
+
expect(GRPO_LR_SCHEDULER_CONFIG.warmupRatio).toBe(0.1);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// ---------------------------------------------------------------------------
|
|
522
|
+
// MATHEMATICAL PROPERTIES
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
|
|
525
|
+
describe('mathematical properties', () => {
|
|
526
|
+
it('LR never exceeds baseLR', () => {
|
|
527
|
+
const scheduler = new LRScheduler({
|
|
528
|
+
baseLR: 2e-4,
|
|
529
|
+
totalSteps: 1000,
|
|
530
|
+
warmupRatio: 0.1,
|
|
531
|
+
minLR: 0,
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
for (let step = 0; step <= 1000; step += 10) {
|
|
535
|
+
expect(scheduler.getLR(step)).toBeLessThanOrEqual(2e-4 + 1e-12);
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('LR never goes below minLR (for single cycle)', () => {
|
|
540
|
+
const scheduler = new LRScheduler({
|
|
541
|
+
baseLR: 2e-4,
|
|
542
|
+
totalSteps: 1000,
|
|
543
|
+
warmupRatio: 0.1,
|
|
544
|
+
minLR: 1e-7,
|
|
545
|
+
numCycles: 1,
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
for (let step = 0; step <= 1000; step += 10) {
|
|
549
|
+
// During warmup, LR can be below minLR (it starts at 0)
|
|
550
|
+
if (step >= scheduler.getWarmupSteps()) {
|
|
551
|
+
expect(scheduler.getLR(step)).toBeGreaterThanOrEqual(1e-7 - 1e-12);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it('warmup is exactly linear', () => {
|
|
557
|
+
const scheduler = new LRScheduler({
|
|
558
|
+
baseLR: 2e-4,
|
|
559
|
+
totalSteps: 1000,
|
|
560
|
+
warmupRatio: 0.1,
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// Check linearity: LR at step k = baseLR * k / warmupSteps
|
|
564
|
+
for (let step = 0; step <= 100; step += 10) {
|
|
565
|
+
const expected = 2e-4 * (step / 100);
|
|
566
|
+
expect(scheduler.getLR(step)).toBeCloseTo(expected, 10);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('cosine decay follows cos((pi * progress) / 2) shape', () => {
|
|
571
|
+
const baseLR = 2e-4;
|
|
572
|
+
const minLR = 0;
|
|
573
|
+
const scheduler = new LRScheduler({
|
|
574
|
+
baseLR,
|
|
575
|
+
totalSteps: 1000,
|
|
576
|
+
warmupRatio: 0.1,
|
|
577
|
+
minLR,
|
|
578
|
+
numCycles: 1,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// Sample a few points and verify cosine formula
|
|
582
|
+
const warmupSteps = 100;
|
|
583
|
+
const decaySteps = 900;
|
|
584
|
+
|
|
585
|
+
for (const step of [200, 400, 600, 800, 1000]) {
|
|
586
|
+
const progress = (step - warmupSteps) / decaySteps;
|
|
587
|
+
const expected = minLR + (baseLR - minLR) * ((1 + Math.cos(Math.PI * progress)) / 2);
|
|
588
|
+
expect(scheduler.getLR(step)).toBeCloseTo(expected, 10);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
});
|