reneeice commited on
Commit
145bb99
·
verified ·
1 Parent(s): 7ccd47d

Upload generate_validator_data.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. generate_validator_data.py +335 -0
generate_validator_data.py ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Generate validator-matching AI + human text samples with resume support.
3
+
4
+ Usage:
5
+ python3 generate_validator_data.py --output data.jsonl --n-samples 1000
6
+ python3 generate_validator_data.py --output data.jsonl --restart # force fresh
7
+ python3 generate_validator_data.py --n-samples 1000 --dry-run # show plan only
8
+ """
9
+ import sys, os, json, time, logging, argparse, random
10
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
11
+
12
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
13
+ logger = logging.getLogger(__name__)
14
+
15
+ from validator_data_gen.config import MODELS, AI_IN_MIDDLE_PROB, N_SAMPLES, N_HUMAN_SAMPLES, N_AI_SAMPLES
16
+
17
+ KEY_FILES = {
18
+ 'groq': '/root/groqkey1',
19
+ 'groq2': '/root/groqkey2',
20
+ 'nvidia_nim': '/root/nvidianmikey',
21
+ 'llm7': '/root/llm7key',
22
+ 'sambanova': '/root/sambanovakey',
23
+ 'deepseek': '/root/deepseekkey',
24
+ 'gemini': '/root/geminikey',
25
+ }
26
+ TOKENIZER_NAME = 'pangram/editlens_roberta-large'
27
+
28
+
29
+ def load_keys():
30
+ keys = {}
31
+ for name, path in KEY_FILES.items():
32
+ if os.path.exists(path):
33
+ keys[name] = open(path).read().strip()
34
+ return keys
35
+
36
+
37
+ def compute_targets(n_ai, n_human):
38
+ """Distribute n_ai samples across model slots matching validator ratios."""
39
+ unique = [m for m in MODELS if not m.get('_dup')]
40
+ mid_models = [m for m in unique if m.get('in_the_middle')]
41
+ n_mid = int(n_ai * AI_IN_MIDDLE_PROB)
42
+
43
+ targets = []
44
+ if mid_models:
45
+ per_mid = n_mid // len(mid_models)
46
+ extra_mid = n_mid - per_mid * len(mid_models)
47
+ for i, m in enumerate(mid_models):
48
+ cnt = per_mid + (1 if i < extra_mid else 0)
49
+ if cnt > 0:
50
+ targets.append({'name': m['name'], 'type': 'ai_in_middle', 'target': cnt, 'text_mode': m.get('text_mode', False)})
51
+
52
+ n_full = n_ai - n_mid
53
+ if unique:
54
+ per_full = n_full // len(unique)
55
+ extra_full = n_full - per_full * len(unique)
56
+ for i, m in enumerate(unique):
57
+ cnt = per_full + (1 if i < extra_full else 0)
58
+ if cnt > 0:
59
+ targets.append({'name': m['name'], 'type': 'ai_full', 'target': cnt, 'text_mode': m.get('text_mode', False)})
60
+
61
+ return targets, n_human
62
+
63
+
64
+ def recount_output(path):
65
+ """Count complete JSON lines in output file, return count and fix truncated last line."""
66
+ if not os.path.exists(path):
67
+ return 0
68
+ count = 0
69
+ with open(path, 'r') as f:
70
+ for line in f:
71
+ line = line.strip()
72
+ if not line:
73
+ continue
74
+ try:
75
+ json.loads(line)
76
+ count += 1
77
+ except json.JSONDecodeError:
78
+ fix_path = path + '.fix'
79
+ with open(path, 'r') as r, open(fix_path, 'w') as w:
80
+ for i, l in enumerate(r):
81
+ if i < count:
82
+ w.write(l)
83
+ os.replace(fix_path, path)
84
+ logger.warning(f'Truncated malformed last line, output now has {count} records')
85
+ break
86
+ return count
87
+
88
+
89
+ def load_state(path):
90
+ if not os.path.exists(path):
91
+ return None
92
+ with open(path) as f:
93
+ return json.load(f)
94
+
95
+
96
+ def write_state(state, path):
97
+ tmp = path + '.tmp'
98
+ with open(tmp, 'w') as f:
99
+ json.dump(state, f, indent=2)
100
+ f.flush()
101
+ os.fsync(f.fileno())
102
+ os.replace(tmp, path)
103
+
104
+
105
+ def _load_human_texts(n=600):
106
+ """Download human text samples from HF dataset."""
107
+ from datasets import load_dataset
108
+ ds = load_dataset('cc_news', split='train', streaming=True).take(n + 100)
109
+ texts = []
110
+ for sample in ds:
111
+ text = sample.get('text') or sample.get('title', '') + '\n' + sample.get('description', '')
112
+ text = text.strip()
113
+ if len(text.split()) >= 100:
114
+ texts.append(text)
115
+ if len(texts) >= n:
116
+ break
117
+ if len(texts) < n:
118
+ logger.warning(f'Only got {len(texts)} human texts (wanted {n})')
119
+ logger.info(f'Loaded {len(texts)} human texts from cc_news')
120
+ return texts
121
+
122
+
123
+ def text_source(texts, min_len=500):
124
+ idx = list(range(len(texts)))
125
+ random.shuffle(idx)
126
+ i = 0
127
+ while True:
128
+ text = texts[idx[i % len(texts)]]
129
+ if len(text) < min_len:
130
+ i += 1
131
+ continue
132
+ yield text
133
+ i += 1
134
+
135
+
136
+ def main():
137
+ parser = argparse.ArgumentParser()
138
+ parser.add_argument('--output', default='generated_data.jsonl')
139
+ parser.add_argument('--n-samples', type=int, default=N_SAMPLES)
140
+ parser.add_argument('--restart', action='store_true')
141
+ parser.add_argument('--dry-run', action='store_true')
142
+ args = parser.parse_args()
143
+
144
+ keys = load_keys()
145
+ if not keys:
146
+ logger.error('No API keys found')
147
+ sys.exit(1)
148
+
149
+ from validator_data_gen.api_hub import APIHub
150
+ from validator_data_gen.model_map import build_providers
151
+ from validator_data_gen.replicate import generate_one_sample
152
+
153
+ providers = build_providers(keys)
154
+ hub = APIHub(providers)
155
+ logger.info(f'Providers: {[p.name for p in providers]}')
156
+
157
+ # Load human text source from HF datasets (no local cache needed)
158
+ logger.info('Loading human text source from HF datasets...')
159
+ human_texts = _load_human_texts(n=2000)
160
+ logger.info(f'Loaded {len(human_texts)} human texts')
161
+
162
+ # Tokenizer not needed for generation — prep_training_data.py handles that
163
+
164
+ n_ai = args.n_samples
165
+ n_human_target = max(1, int(n_ai * N_HUMAN_SAMPLES / N_AI_SAMPLES))
166
+ targets, _ = compute_targets(n_ai, n_human_target)
167
+
168
+ if args.dry_run:
169
+ logger.info(f'=== DRY RUN: {n_ai} AI + {n_human_target} human ===')
170
+ by_type = {}
171
+ for t in targets:
172
+ by_type.setdefault(t['type'], []).append(t)
173
+ for ttype, items in by_type.items():
174
+ logger.info(f' {ttype}: {sum(i["target"] for i in items)} samples across {len(items)} models')
175
+ for i in items:
176
+ logger.info(f' {i["name"]}: {i["target"]}')
177
+ logger.info(f' human: {n_human_target}')
178
+ return
179
+
180
+ state_path = args.output.rsplit('.', 1)[0] + '_state.json'
181
+ output_fd = None
182
+
183
+ # Resume or fresh start
184
+ if args.restart or not os.path.exists(state_path):
185
+ existing = recount_output(args.output)
186
+ if existing > 0 and not args.restart:
187
+ logger.info(f'Found {existing} existing records, resuming')
188
+ # will reconcile below
189
+ state = load_state(state_path) or {}
190
+ else:
191
+ state = {
192
+ 'output_path': args.output, 'n_ai': n_ai, 'n_human_target': n_human_target,
193
+ 'targets': targets, 'human_done': 0, 'total_done': 0, 'started_at': time.time(),
194
+ 'version': 2,
195
+ }
196
+ # start fresh
197
+ if args.restart and existing > 0:
198
+ logger.info(f'Restart forced, discarding {existing} existing records')
199
+ os.remove(args.output)
200
+ else:
201
+ state = load_state(state_path)
202
+ if state is None:
203
+ logger.error(f'State file {state_path} corrupted')
204
+ sys.exit(1)
205
+ existing = recount_output(args.output)
206
+ logger.info(f'Resuming: state says {state["total_done"]} done, output has {existing} records')
207
+
208
+ # Reconcile: state may claim more done than output actually has (crash after output write but before state update)
209
+ if state['total_done'] > existing:
210
+ diff = state['total_done'] - existing
211
+ logger.warning(f'State ahead by {diff} — crash occurred after output write. Correcting state.')
212
+ state['total_done'] = existing
213
+ # rebuild per-target done from output file
214
+ if existing > 0:
215
+ done_map = {}
216
+ with open(args.output) as f:
217
+ for line in f:
218
+ line = line.strip()
219
+ if not line:
220
+ continue
221
+ try:
222
+ r = json.loads(line)
223
+ if r['type'] != 'human':
224
+ key = (r['model'], r['type'])
225
+ done_map[key] = done_map.get(key, 0) + 1
226
+ else:
227
+ state['human_done'] = state.get('human_done', 0) + 1
228
+ except json.JSONDecodeError:
229
+ continue
230
+ for t in state['targets']:
231
+ t['done'] = done_map.get((t['name'], t['type']), 0)
232
+
233
+ # Open output for appending
234
+ output_fd = open(args.output, 'a')
235
+ os.fsync(output_fd.fileno())
236
+
237
+ # Human text source
238
+ human_gen = text_source(human_texts, min_len=300)
239
+
240
+ # Primary generation loop
241
+ gen_start = time.time()
242
+ consecutive_model_failures = 0
243
+
244
+ for ti, target in enumerate(state['targets']):
245
+ model_name = target['name']
246
+ gen_type = target['type']
247
+ target_cnt = target['target']
248
+ done = target.get('done', 0)
249
+ remaining = target_cnt - done
250
+
251
+ if remaining <= 0:
252
+ continue
253
+
254
+ logger.info(f'[{ti+1}/{len(state["targets"])}] {model_name} ({gen_type}): {done}/{target_cnt} done, {remaining} remaining')
255
+ model_fails = 0
256
+
257
+ for j in range(remaining):
258
+ sample = None
259
+ try:
260
+ src_text = next(human_gen)
261
+ prompt = src_text[:int(len(src_text) * random.uniform(0.25, 0.75))]
262
+ sample = generate_one_sample(hub, model_name, gen_type, target.get('text_mode', False), src_text, prompt)
263
+ except Exception as e:
264
+ logger.warning(f'{model_name}/{gen_type} attempt {j}: {e}')
265
+ model_fails += 1
266
+ consecutive_model_failures += 1
267
+ if model_fails >= 5:
268
+ logger.warning(f'{model_name} failed {model_fails} times consecutively, skipping')
269
+ break
270
+ if consecutive_model_failures >= 10:
271
+ logger.warning('Too many consecutive failures across models, exiting')
272
+ break
273
+ continue
274
+
275
+ if sample is None:
276
+ model_fails += 1
277
+ consecutive_model_failures += 1
278
+ if model_fails >= 5:
279
+ break
280
+ j -= 1 # retry same index
281
+ continue
282
+
283
+ consecutive_model_failures = 0
284
+ model_fails = 0
285
+
286
+ # Atomic write: output → fsync → state update → state write
287
+ output_fd.write(json.dumps(sample) + '\n')
288
+ output_fd.flush()
289
+ os.fsync(output_fd.fileno())
290
+
291
+ # Confirm it was written (read-back check)
292
+ state['total_done'] += 1
293
+ target['done'] = target.get('done', 0) + 1
294
+ write_state(state, state_path)
295
+
296
+ if (j + 1) % 10 == 0:
297
+ elapsed = time.time() - gen_start
298
+ rate = state['total_done'] / max(elapsed, 1)
299
+ logger.info(f' {model_name}: {target["done"]}/{target_cnt} done, total={state["total_done"]}, rate={rate:.2f}/s')
300
+
301
+ target['finalized'] = True
302
+ write_state(state, state_path)
303
+
304
+ if consecutive_model_failures >= 10:
305
+ logger.warning('Too many consecutive failures, stopping generation')
306
+ break
307
+
308
+ # Human phase (no API calls, just from cache)
309
+ human_remaining = n_human_target - state.get('human_done', 0)
310
+ if human_remaining > 0:
311
+ logger.info(f'Generating {human_remaining} human samples from cache...')
312
+ for j in range(human_remaining):
313
+ src_text = next(human_gen)
314
+ labels = [0] * len(src_text.split())
315
+ sample = {'text': src_text, 'text_raw': src_text, 'labels': labels,
316
+ 'model': 'human', 'params': {}, 'type': 'human', 'augmentations': []}
317
+ output_fd.write(json.dumps(sample) + '\n')
318
+ output_fd.flush()
319
+ os.fsync(output_fd.fileno())
320
+ state['human_done'] = state.get('human_done', 0) + 1
321
+ state['total_done'] += 1
322
+ write_state(state, state_path)
323
+
324
+ output_fd.close()
325
+ state['status'] = 'completed'
326
+ state['elapsed'] = time.time() - gen_start
327
+ write_state(state, state_path)
328
+
329
+ logger.info(f'Done! {state["total_done"]} samples in {state["elapsed"]:.0f}s')
330
+ n_ai_done = sum(t.get('done', 0) for t in state['targets'])
331
+ logger.info(f' AI: {n_ai_done}, Human: {state.get("human_done", 0)}')
332
+
333
+
334
+ if __name__ == '__main__':
335
+ main()