htaf commited on
Commit
b2f1284
·
1 Parent(s): 04fcffb

Add CI, licences, samples, and benchmark scripts

Browse files
.github/workflows/ci.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, master ]
6
+ pull_request:
7
+ branches: [ main, master ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Setup Node
15
+ uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 20
18
+ cache: npm
19
+ - name: Install
20
+ run: npm ci
21
+ - name: Test
22
+ run: npm test
CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Changelog
2
+
3
+ ## 1.1.0 - 2025-11-27
4
+ - Add Apache-2.0 LICENSE.
5
+ - Add instruct pipeline toggle (`INSTRUCT_PIPELINE`, `INSTRUCT_GENERATOR_MODEL/PROVIDER`) with separate cache/output helpers.
6
+ - Provide continuous runner scripts for thinking/instruct pipelines.
7
+ - Improve cache reporting for thinking vs instruct caches.
8
+ - Expand docs and `.env.example` for new env vars, random walk, overnight runs, and instruct flows.
9
+ - Add sample gold JSONL and Hugging Face/GitHub distribution guidance.
10
+ - Add GitHub Actions CI (npm test).
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md CHANGED
@@ -1,3 +1,4 @@
 
1
  license: apache-2.0
2
  title: distill-pipeline 🌟
3
  tags:
 
1
+ ---
2
  license: apache-2.0
3
  title: distill-pipeline 🌟
4
  tags:
samples/pipeline_gold_sample.jsonl ADDED
@@ -0,0 +1 @@
 
 
1
+ {"question":"What is the meaning of life?","context":[{"id":"c1","content":"ctx content"}],"sample":{"answer":"42","thought":"reasoning goes here","raw":"42"},"verifier":{"ok":true,"score":0.9},"reward":{"ok":true,"score":0.8}}
scripts/bench_pipeline.mjs ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+ // scripts/bench_pipeline.mjs
3
+ // Quick micro-benchmark for the pipeline using mock providers.
4
+ // Measures throughput (questions/sec) over a limited run.
5
+
6
+ import { performance } from 'perf_hooks';
7
+ import path from 'path';
8
+ import os from 'os';
9
+ import { fileURLToPath } from 'url';
10
+ import { runPipelineBatch } from '../src/pipeline/pipeline.mjs';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ const PROJECT_ROOT = path.join(__dirname, '..');
15
+
16
+ function parseArgs(argv) {
17
+ const args = argv.slice(2);
18
+ let limit = 50;
19
+ let chunkLimit;
20
+ let cacheDir;
21
+ let randomWalk = false;
22
+ for (let i = 0; i < args.length; i++) {
23
+ const a = args[i];
24
+ if (a === '--limit' || a === '-n') {
25
+ const v = Number(args[i + 1]);
26
+ if (!Number.isNaN(v)) limit = v;
27
+ i++;
28
+ } else if (a === '--chunk-limit') {
29
+ const v = Number(args[i + 1]);
30
+ if (!Number.isNaN(v)) chunkLimit = v;
31
+ i++;
32
+ } else if (a === '--cache-dir') {
33
+ cacheDir = args[i + 1];
34
+ i++;
35
+ } else if (a === '--random-walk') {
36
+ randomWalk = true;
37
+ }
38
+ }
39
+ return { limit, chunkLimit, cacheDir, randomWalk };
40
+ }
41
+
42
+ function bar(label, fraction, width = 30) {
43
+ const clamped = Math.max(0, Math.min(1, fraction));
44
+ const filled = Math.round(clamped * width);
45
+ const empty = width - filled;
46
+ return `${label} [${'#'.repeat(filled)}${'.'.repeat(empty)}] ${(clamped * 100).toFixed(1)}%`;
47
+ }
48
+
49
+ async function main() {
50
+ const { limit, chunkLimit, cacheDir, randomWalk } = parseArgs(process.argv);
51
+
52
+ // Force mock providers for speed and determinism
53
+ process.env.GENERATOR_PROVIDER = 'mock';
54
+ process.env.VERIFIER_PROVIDER = 'mock';
55
+ process.env.REWARD_PROVIDER = 'mock';
56
+ process.env.QUESTION_PROVIDER = 'mock';
57
+ process.env.PROVIDER_TYPE = 'mock';
58
+
59
+ // Seed mode: question-first avoids ES by using rag chunks JSONL
60
+ process.env.PIPELINE_SEED_MODE = 'question-first';
61
+
62
+ // Optional random walk over chunks
63
+ if (randomWalk) process.env.PIPELINE_RANDOM_WALK = '1';
64
+
65
+ // Isolate cache/output
66
+ const cachePath =
67
+ cacheDir ||
68
+ path.join(os.tmpdir(), `distill-cache-bench-${Date.now()}`);
69
+ process.env.PIPELINE_CACHE_DIR = cachePath;
70
+ const outPath = path.join(
71
+ os.tmpdir(),
72
+ `pipeline_gold_bench_${Date.now()}.jsonl`,
73
+ );
74
+
75
+ console.log('🏎️ Benchmarking pipeline (mock providers)');
76
+ console.log(` limit: ${limit}`);
77
+ console.log(` chunkLimit: ${chunkLimit ?? 'default'}`);
78
+ console.log(` randomWalk: ${randomWalk ? 'yes' : 'no'}`);
79
+ console.log(` cache: ${cachePath}`);
80
+ console.log(` out: ${outPath}`);
81
+ console.log('');
82
+
83
+ const start = performance.now();
84
+ const silentLogger = { log: () => {}, error: console.error };
85
+ const result = await runPipelineBatch({
86
+ limit,
87
+ chunkLimit,
88
+ verbose: false,
89
+ outPath,
90
+ seedMode: 'question-first',
91
+ logger: silentLogger,
92
+ });
93
+ const end = performance.now();
94
+ const ms = end - start;
95
+
96
+ const qps = result.processed > 0 ? (result.processed / ms) * 1000 : 0;
97
+ const acceptRatio = result.processed > 0 ? result.accepted / result.processed : 0;
98
+
99
+ console.log('🎯 Benchmark complete');
100
+ console.log(` mode: ${result.mode}`);
101
+ console.log(` processed: ${result.processed}`);
102
+ console.log(` accepted: ${result.accepted}`);
103
+ console.log(` duration: ${ms.toFixed(1)} ms`);
104
+ console.log(` throughput: ${qps.toFixed(2)} q/s`);
105
+ console.log(` ${bar('accept rate ', acceptRatio)}`);
106
+ console.log(` ${bar('throughput ', Math.min(1, qps / 50))} (normalized vs 50 q/s)`);
107
+ console.log(` cache dir: ${cachePath}`);
108
+ console.log(` out file: ${outPath}`);
109
+ }
110
+
111
+ main().catch((err) => {
112
+ console.error('Benchmark error:', err);
113
+ process.exit(1);
114
+ });
scripts/live_bench.mjs ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+ // scripts/live_bench.mjs
3
+ // Live HUD for pipeline throughput/latency using readline (no deps).
4
+ // Defaults to mock providers for speed; can run real providers by env overrides.
5
+
6
+ import readline from 'readline';
7
+ import { performance } from 'perf_hooks';
8
+ import path from 'path';
9
+ import os from 'os';
10
+ import { runPipelineBatch } from '../src/pipeline/pipeline.mjs';
11
+
12
+ function parseArgs(argv) {
13
+ const args = argv.slice(2);
14
+ let limit;
15
+ let chunkLimit;
16
+ let randomWalk = false;
17
+ let mockMode = true;
18
+ for (let i = 0; i < args.length; i++) {
19
+ const a = args[i];
20
+ if (a === '--limit' || a === '-n') {
21
+ const v = Number(args[i + 1]);
22
+ if (!Number.isNaN(v)) limit = v;
23
+ i++;
24
+ } else if (a === '--chunk-limit') {
25
+ const v = Number(args[i + 1]);
26
+ if (!Number.isNaN(v)) chunkLimit = v;
27
+ i++;
28
+ } else if (a === '--random-walk') {
29
+ randomWalk = true;
30
+ } else if (a === '--real') {
31
+ mockMode = false;
32
+ }
33
+ }
34
+ return { limit, chunkLimit, randomWalk, mockMode };
35
+ }
36
+
37
+ function createHud() {
38
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
39
+ rl.pause();
40
+ function render(lines) {
41
+ readline.cursorTo(process.stdout, 0, 0);
42
+ readline.clearScreenDown(process.stdout);
43
+ process.stdout.write(lines.join('\n') + '\n');
44
+ }
45
+ return { render };
46
+ }
47
+
48
+ function formatBar(label, fraction, width = 30) {
49
+ const clamped = Math.max(0, Math.min(1, fraction));
50
+ const filled = Math.round(clamped * width);
51
+ const empty = width - filled;
52
+ return `${label.padEnd(12)} [${'#'.repeat(filled)}${'.'.repeat(empty)}] ${(clamped * 100).toFixed(1)}%`;
53
+ }
54
+
55
+ function humanMs(ms) {
56
+ if (ms < 1000) return `${ms.toFixed(1)} ms`;
57
+ return `${(ms / 1000).toFixed(2)} s`;
58
+ }
59
+
60
+ async function main() {
61
+ const { limit, chunkLimit, randomWalk, mockMode } = parseArgs(process.argv);
62
+
63
+ // Defaults: mock providers for speed/determinism
64
+ if (mockMode) {
65
+ process.env.GENERATOR_PROVIDER = 'mock';
66
+ process.env.VERIFIER_PROVIDER = 'mock';
67
+ process.env.REWARD_PROVIDER = 'mock';
68
+ process.env.QUESTION_PROVIDER = 'mock';
69
+ process.env.PROVIDER_TYPE = 'mock';
70
+ }
71
+
72
+ // question-first by default
73
+ process.env.PIPELINE_SEED_MODE = process.env.PIPELINE_SEED_MODE || 'question-first';
74
+ if (randomWalk) process.env.PIPELINE_RANDOM_WALK = '1';
75
+
76
+ const cacheDir =
77
+ process.env.PIPELINE_CACHE_DIR ||
78
+ path.join(os.tmpdir(), `distill-live-bench-cache-${Date.now()}`);
79
+ const outPath =
80
+ process.env.BENCH_OUT ||
81
+ path.join(os.tmpdir(), `pipeline_gold_live_bench_${Date.now()}.jsonl`);
82
+
83
+ const hud = createHud();
84
+ const t0 = performance.now();
85
+ const samples = [];
86
+ const statusCounts = {};
87
+ const stageTimes = { gen: [], ver: [], rew: [], end2end: [] };
88
+
89
+ function avg(arr) {
90
+ if (!arr.length) return 0;
91
+ return arr.reduce((a, b) => a + b, 0) / arr.length;
92
+ }
93
+
94
+ function throughput(windowMs = 60000) {
95
+ const now = performance.now();
96
+ const recent = samples.filter((s) => now - s.ts <= windowMs);
97
+ if (recent.length === 0) return 0;
98
+ const spanMs = Math.min(windowMs, now - recent[0].ts);
99
+ if (spanMs <= 0) return 0;
100
+ return recent.length / (spanMs / 1000);
101
+ }
102
+
103
+ function acceptRate() {
104
+ const accepted = statusCounts.accepted || 0;
105
+ const processed = samples.length;
106
+ return processed ? accepted / processed : 0;
107
+ }
108
+
109
+ function summarizeStatus() {
110
+ const keys = Object.keys(statusCounts);
111
+ return keys.map((k) => `${k}:${statusCounts[k]}`).join(' ');
112
+ }
113
+
114
+ const logger = {
115
+ log: () => {},
116
+ error: console.error,
117
+ };
118
+
119
+ const hudInterval = setInterval(() => {
120
+ const now = performance.now();
121
+ const totalMs = now - t0;
122
+ const proc = samples.length;
123
+ const qps = throughput();
124
+ const acc = statusCounts.accepted || 0;
125
+ const lines = [
126
+ '📊 Live Bench (minimal logging)',
127
+ `mode: ${process.env.PIPELINE_SEED_MODE} | mock: ${mockMode ? 'yes' : 'no'} | random walk: ${randomWalk ? 'yes' : 'no'}`,
128
+ `processed: ${proc} | accepted: ${acc} | elapsed: ${humanMs(totalMs)}`,
129
+ `throughput: ${qps.toFixed(2)} pipeline cycles/s (60s window)`,
130
+ `gen avg: ${humanMs(avg(stageTimes.gen))} | ver avg: ${humanMs(avg(stageTimes.ver))} | rew avg: ${humanMs(avg(stageTimes.rew))}`,
131
+ `end2end avg: ${humanMs(avg(stageTimes.end2end))}`,
132
+ `status: ${summarizeStatus() || 'n/a'}`,
133
+ `cache: ${cacheDir}`,
134
+ `out: ${outPath}`,
135
+ ];
136
+ hud.render(lines);
137
+ }, 750);
138
+
139
+ function onProgress({ status, elapsedMs }) {
140
+ const ts = performance.now();
141
+ statusCounts[status] = (statusCounts[status] || 0) + 1;
142
+ samples.push({ ts, status, end2endMs: elapsedMs });
143
+ if (samples.length > 2000) samples.shift();
144
+ if (elapsedMs != null) {
145
+ stageTimes.end2end.push(elapsedMs);
146
+ if (stageTimes.end2end.length > 500) stageTimes.end2end.shift();
147
+ }
148
+ }
149
+
150
+ const result = await runPipelineBatch({
151
+ limit,
152
+ chunkLimit,
153
+ verbose: false,
154
+ outPath,
155
+ seedMode: process.env.PIPELINE_SEED_MODE,
156
+ logger,
157
+ onProgress,
158
+ });
159
+
160
+ const totalMs = performance.now() - t0;
161
+ clearInterval(hudInterval);
162
+
163
+ // Final render
164
+ const proc = result.processed;
165
+ const acc = result.accepted;
166
+ const qps = proc > 0 ? (proc / totalMs) * 1000 : 0;
167
+ const lines = [
168
+ '✅ Bench complete',
169
+ `mode: ${result.mode} | mock: ${mockMode ? 'yes' : 'no'} | random walk: ${randomWalk ? 'yes' : 'no'}`,
170
+ `processed: ${proc} | accepted: ${acc} | elapsed: ${humanMs(totalMs)}`,
171
+ `throughput: ${qps.toFixed(2)} pipeline cycles/s overall`,
172
+ `status: ${summarizeStatus() || 'n/a'}`,
173
+ `cache: ${cacheDir}`,
174
+ `out: ${outPath}`,
175
+ ];
176
+ hud.render(lines);
177
+ }
178
+
179
+ main().catch((err) => {
180
+ console.error('Live bench error:', err);
181
+ process.exit(1);
182
+ });
src/pipeline/batch.mjs CHANGED
@@ -74,6 +74,7 @@ export async function runPipelineBatch({
74
  verbose = false,
75
  logger = console,
76
  seedMode = process.env.PIPELINE_SEED_MODE || 'question-first',
 
77
  } = {}) {
78
  const log = logger?.log?.bind(logger) || console.log;
79
  const errLog = logger?.error?.bind(logger) || console.error;
@@ -105,6 +106,7 @@ export async function runPipelineBatch({
105
 
106
  log(`→ ${label} Running pipeline for: "${question}"`);
107
 
 
108
  try {
109
  const result = await runPipelineStep({
110
  question,
@@ -116,6 +118,15 @@ export async function runPipelineBatch({
116
  statusCounts[result.status] =
117
  (statusCounts[result.status] || 0) + 1;
118
 
 
 
 
 
 
 
 
 
 
119
  if (verbose) {
120
  log(` ↳ status: ${result.status}`);
121
  }
@@ -145,6 +156,14 @@ export async function runPipelineBatch({
145
  processed += 1;
146
  statusCounts.pipeline_error =
147
  (statusCounts.pipeline_error || 0) + 1;
 
 
 
 
 
 
 
 
148
  errLog(' [pipeline] ERROR:', msg);
149
  }
150
  }
@@ -349,6 +368,14 @@ export async function runPipelineBatch({
349
  accepted += 1;
350
  statusCounts.cached_reward =
351
  (statusCounts.cached_reward || 0) + 1;
 
 
 
 
 
 
 
 
352
  if (verbose)
353
  log(
354
  ` → [q ${processed}] using cached reward, skipping stages`,
@@ -363,6 +390,7 @@ export async function runPipelineBatch({
363
  ` → ${qLabel} Running pipeline for generated question: "${q}"`,
364
  );
365
 
 
366
  try {
367
  const result = await runPipelineStep({
368
  question: q,
@@ -376,6 +404,15 @@ export async function runPipelineBatch({
376
  statusCounts[result.status] =
377
  (statusCounts[result.status] || 0) + 1;
378
 
 
 
 
 
 
 
 
 
 
379
  if (verbose) {
380
  log(` ↳ status: ${result.status}`);
381
  }
@@ -443,6 +480,14 @@ export async function runPipelineBatch({
443
  processed += 1;
444
  statusCounts.pipeline_error =
445
  (statusCounts.pipeline_error || 0) + 1;
 
 
 
 
 
 
 
 
446
  errLog(' [pipeline] ERROR:', msg);
447
  }
448
  }
 
74
  verbose = false,
75
  logger = console,
76
  seedMode = process.env.PIPELINE_SEED_MODE || 'question-first',
77
+ onProgress = () => {},
78
  } = {}) {
79
  const log = logger?.log?.bind(logger) || console.log;
80
  const errLog = logger?.error?.bind(logger) || console.error;
 
106
 
107
  log(`→ ${label} Running pipeline for: "${question}"`);
108
 
109
+ const tStart = Date.now();
110
  try {
111
  const result = await runPipelineStep({
112
  question,
 
118
  statusCounts[result.status] =
119
  (statusCounts[result.status] || 0) + 1;
120
 
121
+ onProgress({
122
+ processed,
123
+ accepted,
124
+ status: result.status,
125
+ elapsedMs: Date.now() - tStart,
126
+ question,
127
+ mode: seedMode,
128
+ });
129
+
130
  if (verbose) {
131
  log(` ↳ status: ${result.status}`);
132
  }
 
156
  processed += 1;
157
  statusCounts.pipeline_error =
158
  (statusCounts.pipeline_error || 0) + 1;
159
+ onProgress({
160
+ processed,
161
+ accepted,
162
+ status: 'pipeline_error',
163
+ elapsedMs: Date.now() - tStart,
164
+ question,
165
+ mode: seedMode,
166
+ });
167
  errLog(' [pipeline] ERROR:', msg);
168
  }
169
  }
 
368
  accepted += 1;
369
  statusCounts.cached_reward =
370
  (statusCounts.cached_reward || 0) + 1;
371
+ onProgress({
372
+ processed,
373
+ accepted,
374
+ status: 'cached_reward',
375
+ elapsedMs: 0,
376
+ question: q,
377
+ mode: seedMode,
378
+ });
379
  if (verbose)
380
  log(
381
  ` → [q ${processed}] using cached reward, skipping stages`,
 
390
  ` → ${qLabel} Running pipeline for generated question: "${q}"`,
391
  );
392
 
393
+ const tStart = Date.now();
394
  try {
395
  const result = await runPipelineStep({
396
  question: q,
 
404
  statusCounts[result.status] =
405
  (statusCounts[result.status] || 0) + 1;
406
 
407
+ onProgress({
408
+ processed,
409
+ accepted,
410
+ status: result.status,
411
+ elapsedMs: Date.now() - tStart,
412
+ question: q,
413
+ mode: seedMode,
414
+ });
415
+
416
  if (verbose) {
417
  log(` ↳ status: ${result.status}`);
418
  }
 
480
  processed += 1;
481
  statusCounts.pipeline_error =
482
  (statusCounts.pipeline_error || 0) + 1;
483
+ onProgress({
484
+ processed,
485
+ accepted,
486
+ status: 'pipeline_error',
487
+ elapsedMs: Date.now() - tStart,
488
+ question: q,
489
+ mode: seedMode,
490
+ });
491
  errLog(' [pipeline] ERROR:', msg);
492
  }
493
  }