UmeshYapa commited on
Commit
37c04af
·
verified ·
1 Parent(s): fb0397e

Adding 4 files

Browse files

Adding 4 files for final handover

Files changed (4) hide show
  1. README.md +45 -0
  2. inference.py +349 -0
  3. predictions.jsonl +0 -0
  4. requirements.txt +10 -0
README.md ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Team AlphaPack — SinhalaMMLU Gemma 4 E4B V2
2
+
3
+ **Status: audited submission CANDIDATE; full clean-room prediction reproduction NOT yet demonstrated.**
4
+ Team identifier: AlphaPack. **Before submission: add your real team-member details and confirm there are no team adapters/extra training assets omitted.**
5
+
6
+ ## Submission
7
+
8
+ - Base model: [`google/gemma-4-E4B-it`](https://huggingface.co/google/gemma-4-E4B-it)
9
+ - Revision: `ee0ef6023621cff504d758262d4e04895a5af4a2`
10
+ - Unique inference-time parameter count verified in the source recovery notebook: **7,941,100,832** (7.9411B; below 8,000,000,000), including vision/audio modules. **No additional trainable inference adapter is included in this package.**
11
+ - Technique: native Gemma 4 thinking (`enable_thinking=True`) + exact **V2 explanation-first / last-line `ANSWER: X`** prompt, with automatic invalid-answer retries. The text is SHA-256 pinned in `inference.py`.
12
+ - Decoding: greedy (`do_sample=False`); first pass batch size 4, 2,048 new tokens; strict targeted retries batch size 1, 4,096 new tokens. The first-pass settings are reconstructed from research notebook material, **not independently proven identical to the historical all-2,206 run**.
13
+ - Hardware used for historical research: Kaggle **NVIDIA T4 ×2**; FP16; SDPA. Original targeted retry set a four-hour run limit, not a measured full-set runtime. **Actual complete fresh-run runtime: not yet measured.**
14
+ - Seed: 2026 (reconstructed setting; confirm against initial production records).
15
+ - Dataset used for evaluation: SinhalaMMLU Shared Task public questions, downloaded by the script from `DATA_REPO`.
16
+ - Additional training / retrieval data: **none in this system package**. Confirm any other teammates' work before publication.
17
+ - Team member details: **Team AlphaPack — add member names here before publication**.
18
+
19
+ ## Files
20
+
21
+ - `inference.py` — best-effort self-contained fresh inference candidate; loads every `.json` in `DATA_REPO`, downloads pinned model from HF, uses native thinking and V2 prompt, retries invalid answers, and writes `predictions.jsonl` in the required numeric-label format.
22
+ - `predictions.jsonl` — **unchanged** original user-provided 2,238-row final submission candidate; SHA-256 `dcf8cf70066f06b56c319a5eb6604bbeb823cc70096ab8a5c40e8c6d05e06b00`.
23
+ - `requirements.txt` — reconstructed package versions from the research environment.
24
+ - `recovery/evidence/` — independently verified historical first-pass 2,206 + 32 failures package, merge audit, hashes and merge source code.
25
+ - `recovery/MIDDLE_E4B_RETRY32_COLDSTART.ipynb` and `.py` — verbatim original 32-question historical recovery code, requiring the original combined-2,206 package and 41 unchanged JSON question files as inputs. Its source contains the final strict parser, per-question logs, model hash, dataset checks and successful-only final export logic. **This notebook has no embedded execution outputs.**
26
+ - `training/README.md` and `data/README.md` — no fine-tuning, adapters or extra training data claimed; review if other team components were used.
27
+ - `REPRODUCIBILITY_STATUS.md` — limitations and outstanding evidence.
28
+ - `check_submission.py`, `verify_final_predictions.py` — file integrity / output validation helpers.
29
+
30
+ ## Fresh environment reproduction
31
+
32
+ 1. On Kaggle, choose **GPU T4 ×2**, enable Internet, and accept the Gemma license through your Hugging Face account if required. The model is gated. Authenticate externally through `HF_TOKEN` or pre-existing Hugging Face login; never place tokens in code.
33
+ 2. Install `pip install -r requirements.txt`.
34
+ 3. The organizers change **only `DATA_REPO`** in `inference.py`; then run `python inference.py` from a clean working directory. It downloads the model from Hugging Face and produces `predictions.jsonl`.
35
+ 4. Keep an immutable copy of this repository's original predictions outside the run directory. Running the script overwrites the `predictions.jsonl` in its working directory.
36
+ 5. Compare the reproduced predictions to the original 2,238-row file with `python check_submission.py ORIGINAL.jsonl GENERATED.jsonl`. Byte-identical SHA is the strongest evidence; report any differences, never conceal them.
37
+
38
+ ## Critical reproducibility limitations
39
+
40
+ The original `E4B_COMBINED_2206_WITH_FAILED32.zip` is now included under `recovery/evidence/`. It provides the original 2,206 valid predictions, 32 failed attempts, 32 raw failure outputs, merged first-pass audit, dataset SHA-256 manifest and original merge script. An independent offline audit verified **all 2,206 entries** match `predictions.jsonl` exactly and the remaining **32 indices** are disjoint and have matching question metadata. The first-pass merge audit confirms the same V2 prompt hash as RETRY32. **Three original shard ZIPs are still not provided**, although their names and SHA-256 hashes are recorded. The complete original first-pass generator is still not available as one final standalone script; the integrated `inference.py` is reconstructed, and a full clean-room GPU run has not been executed here. Clean-room inference has **not** been run or checked against the exact submitted file here. This README is intentionally not a claim of exact output reproducibility.
41
+
42
+ ## Required repository links
43
+
44
+ - System repository: `https://huggingface.co/SinhalaMMLU-SharedTask/team-AlphaPack`
45
+ - Model weights: `https://huggingface.co/google/gemma-4-E4B-it` — pinned revision above; no fine-tuned adapter claimed.
inference.py ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """SinhalaMMLU E4B V2 standalone inference + automatic invalid-answer retry.
2
+
3
+ CANDIDATE reconstructed from archived research code and original RETRY32.
4
+ NOT certified to regenerate byte-identical historical 2,238 predictions.
5
+ The exact first-pass 2,206-answer production script is not provided.
6
+
7
+ Organizers change ONLY DATA_REPO. All model resources come from Hugging Face.
8
+ Never access reference predictions or test answer keys during generation.
9
+ """
10
+ # ===================== ORGANIZERS CHANGE ONLY THIS LINE =====================
11
+ DATA_REPO = "SinhalaMMLU-SharedTask/Sinhala-MMLU-SharedTask-Test-Dataset"
12
+ # ===========================================================================
13
+
14
+ MODEL_REPO = "google/gemma-4-E4B-it"
15
+ REVISION = "ee0ef6023621cff504d758262d4e04895a5af4a2"
16
+ WEIGHT_SHA256 = "cfbd3d2f1cd71bd471c37fe2bf8546d5028d41e5736f64e1ca6c6b8893125503"
17
+ EXPECTED_PARAMS = 7_941_100_832
18
+ PROMPT_SHA256 = "f53159bafe22a5d0063a065b6d3aef8167e7a6d6b998ce65e224feeeca68287d"
19
+ BATCH_SIZE = 4
20
+ FIRST_PASS_TOKENS = 2048
21
+ RETRY_TOKENS = 4096
22
+ SEED = 2026
23
+ OUTPUT_FILE = "predictions.jsonl"
24
+ OUTPUT_DIR = "inference_records"
25
+
26
+ import hashlib
27
+ import json
28
+ import logging
29
+ import os
30
+ import random
31
+ import re
32
+ import time
33
+ from pathlib import Path
34
+
35
+ import numpy as np
36
+ import torch
37
+ from huggingface_hub import HfFileSystem, list_repo_files, snapshot_download
38
+
39
+ V2_PROMPT = (
40
+ 'Answer this Sinhala multiple-choice question. '
41
+ 'Use your reasoning to select the correct option. '
42
+ 'In your final response, provide a brief explanation '
43
+ 'in English, approximately 90 words. '
44
+ 'Then put ANSWER: X on the last line, '
45
+ 'where X is A, B, C, or D. '
46
+ 'Do not change the selected answer.'
47
+ )
48
+ assert hashlib.sha256(V2_PROMPT.encode('utf-8')).hexdigest() == PROMPT_SHA256
49
+ LETTER_TO_NUMBER = {'A':'1','B':'2','C':'3','D':'4'}
50
+ FIELDS = ('q_no','subject','category','level','answer')
51
+
52
+
53
+ def sha_file(path):
54
+ h = hashlib.sha256()
55
+ with open(path, 'rb') as f:
56
+ for block in iter(lambda: f.read(8*1024*1024), b''):
57
+ h.update(block)
58
+ return h.hexdigest()
59
+
60
+
61
+ def save_atomic(path, data):
62
+ path = Path(path)
63
+ path.parent.mkdir(parents=True, exist_ok=True)
64
+ temp = path.with_suffix(path.suffix+'.tmp')
65
+ temp.write_text(json.dumps(data,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
66
+ os.replace(temp,path)
67
+
68
+
69
+ def init_seed():
70
+ random.seed(SEED)
71
+ np.random.seed(SEED)
72
+ torch.manual_seed(SEED)
73
+ if torch.cuda.is_available():
74
+ torch.cuda.manual_seed_all(SEED)
75
+
76
+
77
+ def load_questions(data_repo):
78
+ """Load every .json file per organizer template; preserve source position."""
79
+ fs = HfFileSystem()
80
+ files = sorted(f for f in list_repo_files(data_repo, repo_type='dataset') if f.endswith('.json'))
81
+ rows = []
82
+ for f in files:
83
+ with fs.open(f'datasets/{data_repo}/{f}','r',encoding='utf-8') as fh:
84
+ raw = json.load(fh)
85
+ if isinstance(raw,dict):
86
+ raw = next(v for v in raw.values() if isinstance(v,list))
87
+ assert isinstance(raw,list),f
88
+ for position,r in enumerate(raw):
89
+ r = dict(r)
90
+ r['_file'] = f
91
+ r['_position'] = position
92
+ rows.append(r)
93
+ if not rows:
94
+ raise ValueError('No questions found in DATA_REPO')
95
+ return rows
96
+
97
+
98
+ def get_level(r):
99
+ return (r.get('metadata') or {}).get('difficulty') or r['_file'].split('/')[0]
100
+
101
+
102
+ def canonical_order(rows):
103
+ """Historical final file: Easy, Medium, Hard; source basename, row order.
104
+
105
+ Do not hard-code question count, index or test answers.
106
+ This is deliberately separate from the organizer's loader.
107
+ """
108
+ priority = {'easy':0,'medium':1,'hard':2}
109
+ for row in rows:
110
+ if get_level(row) not in priority:
111
+ raise ValueError(f'Unknown difficulty {get_level(row)}')
112
+ if len(row['choices']) != 4 or not all(isinstance(c,str) for c in row['choices']):
113
+ raise ValueError('Expected exactly four string choices')
114
+ return sorted(rows,key=lambda r:(priority[get_level(r)],Path(r['_file']).name.casefold(),r['_position']))
115
+
116
+
117
+ def fingerprint(r):
118
+ keys = ('q_no','subject','category','question','choices')
119
+ content = {k:r[k] for k in keys}
120
+ content['level'] = get_level(r)
121
+ content['file'] = r['_file']
122
+ content['source_position'] = r['_position']
123
+ return hashlib.sha256(json.dumps(content,ensure_ascii=False,sort_keys=True,separators=(',',':')).encode('utf-8')).hexdigest()
124
+
125
+
126
+ def make_prompt(r):
127
+ opts = '\n'.join(f'{letter}. {choice}' for letter,choice in zip('ABCD',r['choices']))
128
+ return V2_PROMPT + '\n\n' + r['question'] + '\n\n' + opts
129
+
130
+
131
+ def split_tokens(tokenizer, sequence, width, pad_id, stop_ids):
132
+ actual=[]
133
+ ended=False
134
+ for token in sequence[width:].tolist():
135
+ if token == pad_id:
136
+ break
137
+ actual.append(token)
138
+ if token in stop_ids:
139
+ ended=True
140
+ break
141
+ return actual,ended
142
+
143
+
144
+ def parse_retry_strict(raw, ended, token_count, limit):
145
+ """Verbatim logic from RETRY32 for final answer and validity."""
146
+ parts = re.split(r'<\|channel>final|<channel\|>',raw)
147
+ final = re.sub(r'<[^>\n]+>','',parts[-1]).strip()
148
+ lines = [x.strip() for x in final.splitlines() if x.strip()]
149
+ last = lines[-1] if lines else ''
150
+ m = re.fullmatch(r'\*{0,2}\s*ANSWER\s*:\s*([ABCD])\s*[.!]?\s*\*{0,2}',last,flags=re.I)
151
+ pred = m.group(1).upper() if m else None
152
+ hit_limit = not ended and token_count>=limit
153
+ valid = pred in LETTER_TO_NUMBER and ended and token_count>0
154
+ status = ('completed' if valid else 'token_limit' if hit_limit else
155
+ 'padding_only' if not token_count else 'unparseable_or_unterminated')
156
+ return pred if valid else None,status,last
157
+
158
+
159
+ def parse_first_pass(raw):
160
+ """Candidate first-pass parser; exact historical 2206 parser not supplied.
161
+
162
+ Do not parse thought tokens if final channel missing.
163
+ """
164
+ if ('<|channel>thought' in raw or '<channel|>thought' in raw) and not (
165
+ '<|channel>final' in raw or '<channel|>final' in raw
166
+ ):
167
+ return None
168
+ parts=re.split(r'<\|channel>final|<channel\|>',raw)
169
+ final=re.sub(r'<[^>\n]+>','',parts[-1]).strip()
170
+ lines=[x.strip() for x in final.splitlines() if x.strip()]
171
+ if not lines:
172
+ return None
173
+ m=re.fullmatch(r'\*{0,2}\s*ANSWER\s*:\s*([ABCD])\s*[.!]?\s*\*{0,2}',lines[-1],flags=re.I)
174
+ return m.group(1).upper() if m else None
175
+
176
+
177
+ def model_device_map(model_dir):
178
+ from transformers import AutoConfig, AutoModelForMultimodalLM
179
+ from accelerate import init_empty_weights
180
+ cfg=AutoConfig.from_pretrained(str(model_dir),local_files_only=True)
181
+ with init_empty_weights():
182
+ skeleton=AutoModelForMultimodalLM.from_config(cfg,torch_dtype=torch.float16)
183
+ root='model.language_model.layers'
184
+ assert len(dict(skeleton.named_modules())[root])==42
185
+ decoders={f'{root}.{i}' for i in range(42)}
186
+ dm={}
187
+ def includes_decoder(name):
188
+ prefix=name+'.' if name else ''
189
+ return any(d==name or d.startswith(prefix) for d in decoders)
190
+ def assign(mod,name=''):
191
+ if name in decoders:
192
+ dm[name]='cuda:1'
193
+ return
194
+ if name and not includes_decoder(name):
195
+ dm[name]='cuda:0'
196
+ return
197
+ assert not list(mod.named_parameters(recurse=False))
198
+ assert not list(mod.named_buffers(recurse=False))
199
+ for childname,child in mod.named_children():
200
+ assign(child,f'{name}.{childname}' if name else childname)
201
+ assign(skeleton)
202
+ assert len(dm)==53 and '' not in dm
203
+ for name,_ in list(skeleton.named_parameters())+list(skeleton.named_buffers()):
204
+ assert sum(name==k or name.startswith(k+'.') for k in dm)==1,name
205
+ del skeleton
206
+ return dm
207
+
208
+
209
+ def load_system():
210
+ from transformers import AutoProcessor, AutoModelForMultimodalLM
211
+ assert torch.cuda.device_count()==2, 'Original system requires T4 x2'
212
+ model_dir = Path(OUTPUT_DIR)/'model_download'
213
+ model_dir.mkdir(parents=True,exist_ok=True)
214
+ # Downloads original checkpoint and processor from HF (never from Kaggle Input/Drive).
215
+ snapshot_download(repo_id=MODEL_REPO,revision=REVISION,local_dir=str(model_dir),token=os.getenv('HF_TOKEN') or None)
216
+ weights = model_dir/'model.safetensors'
217
+ assert weights.is_file() and weights.stat().st_size==15992595884
218
+ assert sha_file(weights)==WEIGHT_SHA256,'Pinned model weights SHA256 mismatch'
219
+ processor=AutoProcessor.from_pretrained(str(model_dir),local_files_only=True)
220
+ processor.tokenizer.padding_side='left'
221
+ dm=model_device_map(model_dir)
222
+ model=AutoModelForMultimodalLM.from_pretrained(
223
+ str(model_dir),local_files_only=True,dtype=torch.float16,
224
+ attn_implementation='sdpa',device_map=dm,low_cpu_mem_usage=True).eval()
225
+ assert type(model).__name__=='Gemma4ForConditionalGeneration'
226
+ assert sum(p.numel() for p in model.parameters())==EXPECTED_PARAMS
227
+ assert not model.training
228
+ tok=processor.tokenizer
229
+ pad=tok.pad_token_id
230
+ turn=tok.convert_tokens_to_ids('<turn|>')
231
+ assert pad is not None and tok.decode([turn],skip_special_tokens=False)=='<turn|>'
232
+ eos=model.generation_config.eos_token_id
233
+ stop=({eos} if isinstance(eos,int) else set(eos or []))|{turn}
234
+ return processor,model,pad,stop
235
+
236
+
237
+ def run_batch(system, rows, limit):
238
+ processor,model,pad,stop=system
239
+ tok=processor.tokenizer
240
+ msgs=[[{'role':'user','content':[{'type':'text','text':make_prompt(row)}]}] for row in rows]
241
+ encoded=processor.apply_chat_template(msgs,tokenize=True,add_generation_prompt=True,
242
+ enable_thinking=True,padding=True,return_dict=True,return_tensors='pt').to('cuda:0')
243
+ width=encoded['input_ids'].shape[1]
244
+ for i in range(2):torch.cuda.synchronize(i)
245
+ started=time.perf_counter()
246
+ with torch.inference_mode():
247
+ seqs=model.generate(**encoded,max_new_tokens=limit,do_sample=False,
248
+ use_cache=True,pad_token_id=pad)
249
+ for i in range(2):torch.cuda.synchronize(i)
250
+ elapsed=time.perf_counter()-started
251
+ results=[]
252
+ for j,row in enumerate(rows):
253
+ ids,ended=split_tokens(tok,seqs[j],width,pad,stop)
254
+ raw=tok.decode(ids,skip_special_tokens=False)
255
+ if limit==RETRY_TOKENS:
256
+ pred,status,last=parse_retry_strict(raw,ended,len(ids),limit)
257
+ else:
258
+ pred=parse_first_pass(raw)
259
+ status='completed' if pred else 'invalid_first_pass'
260
+ last=''
261
+ results.append({'fingerprint':fingerprint(row),'prediction':pred,'status':status,
262
+ 'raw_output':raw,'generated_tokens':len(ids),'terminated':bool(ended),
263
+ 'max_new_tokens':limit,'batch_size':len(rows),'elapsed_batch_seconds':round(elapsed,3),
264
+ 'last_line':last})
265
+ return results
266
+
267
+
268
+ def predict_all(system,rows):
269
+ root=Path(OUTPUT_DIR)
270
+ root.mkdir(parents=True,exist_ok=True)
271
+ config={'model':MODEL_REPO,'revision':REVISION,'prompt_sha256':PROMPT_SHA256,
272
+ 'parameters':EXPECTED_PARAMS,'native_thinking':True,'batch_size':BATCH_SIZE,
273
+ 'first_pass_tokens':FIRST_PASS_TOKENS,'retry_tokens':RETRY_TOKENS,
274
+ 'decoding':'greedy','seed':SEED,'rows':len(rows),
275
+ 'input_hashes':[fingerprint(r) for r in rows]}
276
+ config_path=root/'configuration.json'
277
+ if config_path.exists():
278
+ assert json.loads(config_path.read_text('utf-8'))==config,'Resume configuration differs'
279
+ else:save_atomic(config_path,config)
280
+ answers={}
281
+ for p in (root/'first_pass').glob('q_*.json'):
282
+ obj=json.loads(p.read_text('utf-8'))
283
+ i=obj['index']
284
+ assert 0<=i<len(rows) and obj['fingerprint']==fingerprint(rows[i])
285
+ assert i not in answers
286
+ answers[i]=obj['prediction']
287
+ for start in range(0,len(rows),BATCH_SIZE):
288
+ # Recreate the ORIGINAL full batch when resuming a partially saved batch.
289
+ # Dropping completed members would change padding and potentially outputs.
290
+ idx=list(range(start,min(start+BATCH_SIZE,len(rows))))
291
+ if all(i in answers for i in idx):continue
292
+ results=run_batch(system,[rows[i] for i in idx],FIRST_PASS_TOKENS)
293
+ for i,obj in zip(idx,results):
294
+ obj['index']=i
295
+ if i in answers:
296
+ assert answers[i]==obj['prediction'],(
297
+ f'Replayed batch disagreed at index {i}; preserve old checkpoint')
298
+ else:
299
+ save_atomic(root/'first_pass'/f'q_{i:05}.json',obj)
300
+ answers[i]=obj['prediction']
301
+ logging.info('First pass %d/%d saved; valid=%d',len(answers),len(rows),sum(v in LETTER_TO_NUMBER for v in answers.values()))
302
+ assert len(answers)==len(rows)
303
+ failed=[i for i in range(len(rows)) if answers[i] not in LETTER_TO_NUMBER]
304
+ logging.info('First pass valid=%d failed=%d',len(rows)-len(failed),len(failed))
305
+ recovered={}
306
+ for i in failed:
307
+ p=root/'retry'/f'q_{i:05}.json'
308
+ if p.exists():
309
+ obj=json.loads(p.read_text('utf-8'))
310
+ assert obj['fingerprint']==fingerprint(rows[i])
311
+ else:
312
+ obj=run_batch(system,[rows[i]],RETRY_TOKENS)[0]
313
+ obj['index']=i
314
+ save_atomic(p,obj)
315
+ recovered[i]=obj['prediction']
316
+ logging.info('Retry idx=%d status=%s valid=%s',i,obj['status'],obj['prediction'] in LETTER_TO_NUMBER)
317
+ combined=[recovered[i] if i in recovered else answers[i] for i in range(len(rows))]
318
+ unresolved=[i for i,pred in enumerate(combined) if pred not in LETTER_TO_NUMBER]
319
+ save_atomic(root/'completion_status.json',{'questions':len(rows),'first_pass_valid':len(rows)-len(failed),
320
+ 'retry_targets':len(failed),'recovered_valid':len(failed)-len(unresolved),'still_invalid':unresolved})
321
+ if unresolved:
322
+ raise RuntimeError(f'{len(unresolved)} unresolved final answers; refusing to guess: {unresolved[:30]}')
323
+ return [LETTER_TO_NUMBER[pred] for pred in combined]
324
+
325
+
326
+ def write_submission(rows,answers):
327
+ assert len(rows)==len(answers)
328
+ path=Path(OUTPUT_FILE)
329
+ tmp=path.with_suffix('.jsonl.tmp')
330
+ with tmp.open('w',encoding='utf-8') as f:
331
+ for r,answer in zip(rows,answers):
332
+ assert answer in ('1','2','3','4')
333
+ item={'q_no':r['q_no'],'subject':r['subject'],'category':r['category'],
334
+ 'level':get_level(r),'answer':answer}
335
+ f.write(json.dumps(item,ensure_ascii=False,separators=(',',':'))+'\n')
336
+ os.replace(tmp,path)
337
+ logging.info('Saved %s sha256=%s',path,sha_file(path))
338
+
339
+
340
+ if __name__=='__main__':
341
+ logging.basicConfig(filename='inference.log',level=logging.INFO,
342
+ format='%(asctime)s UTC %(levelname)s %(message)s')
343
+ init_seed()
344
+ rows=canonical_order(load_questions(DATA_REPO))
345
+ logging.info('Questions loaded: %d',len(rows))
346
+ system=load_system()
347
+ answers=predict_all(system,rows)
348
+ write_submission(rows,answers)
349
+ print('Complete:',len(answers),'predictions; sha256=',sha_file(OUTPUT_FILE),flush=True)
predictions.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Versions recorded in executed Gemma 4 E4B notebook (Kaggle GPU run)
2
+ transformers==5.16.1
3
+ accelerate==1.14.0
4
+ # Kaggle notebook recorded torch 2.11.0+cu128; installing PyTorch with
5
+ # the correct CUDA index may be environment-dependent.
6
+ torch==2.11.0
7
+ huggingface-hub>=0.36,<2
8
+ numpy>=2,<3
9
+ sentencepiece
10
+ safetensors