Code2aum commited on
Commit
623c70a
·
verified ·
1 Parent(s): 020871c

Single-model build with model dropdown

Browse files
Files changed (2) hide show
  1. README.md +4 -5
  2. index.html +28 -70
README.md CHANGED
@@ -8,9 +8,8 @@ pinned: false
8
  license: mit
9
  ---
10
 
11
- # Smart MCQ Solver — Final Deployment (DLGenAI Project)
12
 
13
- A 5-option MCQ science-question solver. The custom transformer (built from
14
- scratch in PyTorch, trained with W&B tracking) is exported to ONNX and runs
15
- fully client-side with onnxruntime-web. Returns top-3 ranked answers (MAP@3
16
- format from the Kaggle competition).
 
8
  license: mit
9
  ---
10
 
11
+ # Smart MCQ Solver (DLGenAI Project)
12
 
13
+ Solves 5-option (A–E) multiple-choice science questions and returns the top-3
14
+ ranked answers (Kaggle MAP@3 format). Inference runs fully client-side via
15
+ onnxruntime-web.
 
index.html CHANGED
@@ -14,7 +14,6 @@
14
  --text-primary: #0b0b0b;
15
  --text-secondary: #52514e;
16
  --accent: #2a78d6;
17
- --accent-soft: #2a78d61a;
18
  --track: #edecea;
19
  }
20
  @media (prefers-color-scheme: dark) {
@@ -26,7 +25,6 @@
26
  --text-primary: #ffffff;
27
  --text-secondary: #c3c2b7;
28
  --accent: #3987e5;
29
- --accent-soft: #3987e52e;
30
  --track: #313130;
31
  }
32
  }
@@ -45,12 +43,12 @@
45
  border-radius: 12px; padding: 1.25rem; margin-bottom: 1.25rem;
46
  }
47
  label { display: block; font-size: .8rem; font-weight: 600; color: var(--text-secondary); margin: .8rem 0 .25rem; }
48
- textarea, input[type=text] {
49
  width: 100%; border: 1px solid var(--border); border-radius: 8px;
50
  background: var(--surface); color: var(--text-primary);
51
  padding: .55rem .7rem; font: inherit; resize: vertical;
52
  }
53
- textarea:focus, input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
54
  .examples { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1rem; }
55
  .examples button {
56
  border: 1px solid var(--border); background: var(--card); color: var(--text-secondary);
@@ -63,10 +61,6 @@
63
  }
64
  #solve:disabled { opacity: .55; cursor: wait; }
65
  #status { font-size: .85rem; color: var(--text-secondary); margin-left: .8rem; }
66
- fieldset { border: 1px solid var(--border); border-radius: 8px; padding: .6rem .9rem; margin-bottom: .4rem; }
67
- legend { font-size: .8rem; font-weight: 600; color: var(--text-secondary); padding: 0 .3rem; }
68
- .models label { display: inline-flex; align-items: center; gap: .35rem; margin: 0 1rem 0 0; font-size: .88rem; font-weight: 400; color: var(--text-primary); }
69
- .models input { accent-color: var(--accent); }
70
  .top3 { font-size: 1.05rem; margin-bottom: 1rem; }
71
  .top3 b { letter-spacing: .15em; color: var(--accent); }
72
  .bar-row { display: grid; grid-template-columns: 1.4rem 1fr 3.6rem; align-items: center; gap: .6rem; margin: .45rem 0; }
@@ -76,7 +70,6 @@
76
  .fill { background: var(--accent); height: 100%; border-radius: 0 4px 4px 0; width: 0; transition: width .35s ease; }
77
  .winner .letter, .winner .val { color: var(--accent); }
78
  footer { color: var(--text-secondary); font-size: .8rem; margin-top: 2rem; }
79
- footer a { color: var(--accent); }
80
  #result { display: none; }
81
  </style>
82
  </head>
@@ -84,21 +77,18 @@
84
  <div class="wrap">
85
  <h1>🧠 Smart MCQ Solver</h1>
86
  <p class="sub">
87
- Final deployment — DLGenAI course project. Two 2-layer transformers built
88
- <strong>from scratch in PyTorch</strong> (~500K params each, trained with synthetic-QA expansion +
89
- option-permutation augmentation, tracked with W&amp;B), exported to ONNX and running
90
- <strong>entirely in your browser</strong> — plus their probability-averaged ensemble
91
- (Milestone-5 strategy). Returns the top-3 ranked answers (Kaggle MAP@3 format).
92
  </p>
93
 
94
  <div class="card">
95
  <div class="examples" id="examples"></div>
96
- <fieldset class="models">
97
- <legend>Model</legend>
98
- <label><input type="radio" name="model" value="m1" checked> Scratch v1 — W&amp;B pipeline (MAP@3 0.41)</label>
99
- <label><input type="radio" name="model" value="m2"> Scratch v2 — Kaggle notebook (MAP@3 0.40)</label>
100
- <label><input type="radio" name="model" value="ens"> Ensemble — average of both</label>
101
- </fieldset>
102
  <label for="prompt">Question prompt</label>
103
  <textarea id="prompt" rows="3" placeholder="Enter the multiple-choice question…"></textarea>
104
  <div id="options"></div>
@@ -110,20 +100,13 @@
110
  <div id="bars"></div>
111
  </div>
112
 
113
- <footer>
114
- Models: <code>best_scratch_model.pt</code> + <code>best_model.pt</code> → ONNX, real trained
115
- weights running client-side · The DeBERTa+RoBERTa pipeline demo lives in the repo's Gradio app.
116
- </footer>
117
  </div>
118
 
119
  <script>
120
  const OPTIONS = ["A", "B", "C", "D", "E"];
121
  const MAX_LEN = 128, PAD = 0n, UNK = 1, CLS = 2n, SEP = 3n;
122
- const MODELS = {
123
- m1: { onnx: "mcq_scratch.onnx", vocabFile: "vocab.json" },
124
- m2: { onnx: "mcq_scratch2.onnx", vocabFile: "vocab2.json" },
125
- };
126
- let examples = [];
127
 
128
  const optsDiv = document.getElementById("options");
129
  for (const o of OPTIONS) {
@@ -141,8 +124,7 @@ for (const o of OPTIONS) {
141
  </div>`);
142
  }
143
 
144
- // Mirrors LectureScratchTokenizer.encode_mcq_choice: <CLS> prompt <SEP> option <SEP>
145
- function encodeChoice(vocab, prompt, option) {
146
  const words = t => String(t).toLowerCase().split(/\s+/).filter(Boolean);
147
  const ids = [CLS];
148
  for (const w of words(prompt)) ids.push(BigInt(vocab[w] ?? UNK));
@@ -156,55 +138,28 @@ function encodeChoice(vocab, prompt, option) {
156
  return { ids, mask };
157
  }
158
 
159
- async function loadModel(key) {
160
- const m = MODELS[key];
161
- if (!m.session) {
162
- const status = document.getElementById("status");
163
- status.textContent = "loading model…";
164
- const [vocabRes, sess] = await Promise.all([
165
- fetch(m.vocabFile).then(r => r.json()),
166
- ort.InferenceSession.create(m.onnx),
167
- ]);
168
- m.vocab = vocabRes;
169
- m.session = sess;
170
- status.textContent = "model ready ✓";
171
- }
172
- return m;
173
- }
174
 
175
- async function runModel(key, prompt, opts) {
176
- const m = await loadModel(key);
177
  const allIds = new BigInt64Array(5 * MAX_LEN);
178
  const allMask = new Float32Array(5 * MAX_LEN);
179
  opts.forEach((opt, i) => {
180
- const { ids, mask } = encodeChoice(m.vocab, prompt, opt);
181
  allIds.set(ids, i * MAX_LEN);
182
  allMask.set(mask, i * MAX_LEN);
183
  });
184
- const out = await m.session.run({
 
185
  input_ids: new ort.Tensor("int64", allIds, [1, 5, MAX_LEN]),
186
  attention_mask: new ort.Tensor("float32", allMask, [1, 5, MAX_LEN]),
187
  });
188
  const logits = Array.from(out.logits.data);
189
- const mx = Math.max(...logits);
190
- const exps = logits.map(v => Math.exp(v - mx));
191
  const s = exps.reduce((a, b) => a + b, 0);
192
- return exps.map(v => v / s);
193
- }
194
-
195
- async function solve() {
196
- const prompt = document.getElementById("prompt").value.trim();
197
- const opts = OPTIONS.map(o => document.getElementById("opt" + o).value.trim());
198
- if (!prompt || opts.some(o => !o)) { alert("Please fill in the prompt and all five options."); return; }
199
-
200
- const mode = document.querySelector('input[name="model"]:checked').value;
201
- let probs;
202
- if (mode === "ens") {
203
- const [p1, p2] = await Promise.all([runModel("m1", prompt, opts), runModel("m2", prompt, opts)]);
204
- probs = p1.map((p, i) => (p + p2[i]) / 2);
205
- } else {
206
- probs = await runModel(mode, prompt, opts);
207
- }
208
 
209
  const ranked = probs.map((p, i) => [p, i]).sort((a, b) => b[0] - a[0]);
210
  document.getElementById("top3").textContent = ranked.slice(0, 3).map(r => OPTIONS[r[1]]).join(" ");
@@ -229,7 +184,9 @@ async function init() {
229
  const btn = document.getElementById("solve");
230
  btn.disabled = true;
231
  try {
232
- examples = await (await fetch("examples.json")).json();
 
 
233
  const exDiv = document.getElementById("examples");
234
  examples.forEach((ex, i) => {
235
  const b = document.createElement("button");
@@ -238,7 +195,8 @@ async function init() {
238
  b.onclick = () => loadExample(ex);
239
  exDiv.appendChild(b);
240
  });
241
- await loadModel("m1");
 
242
  btn.disabled = false;
243
  loadExample(examples[0]);
244
  } catch (e) {
 
14
  --text-primary: #0b0b0b;
15
  --text-secondary: #52514e;
16
  --accent: #2a78d6;
 
17
  --track: #edecea;
18
  }
19
  @media (prefers-color-scheme: dark) {
 
25
  --text-primary: #ffffff;
26
  --text-secondary: #c3c2b7;
27
  --accent: #3987e5;
 
28
  --track: #313130;
29
  }
30
  }
 
43
  border-radius: 12px; padding: 1.25rem; margin-bottom: 1.25rem;
44
  }
45
  label { display: block; font-size: .8rem; font-weight: 600; color: var(--text-secondary); margin: .8rem 0 .25rem; }
46
+ textarea, input[type=text], select {
47
  width: 100%; border: 1px solid var(--border); border-radius: 8px;
48
  background: var(--surface); color: var(--text-primary);
49
  padding: .55rem .7rem; font: inherit; resize: vertical;
50
  }
51
+ textarea:focus, input:focus, select:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
52
  .examples { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1rem; }
53
  .examples button {
54
  border: 1px solid var(--border); background: var(--card); color: var(--text-secondary);
 
61
  }
62
  #solve:disabled { opacity: .55; cursor: wait; }
63
  #status { font-size: .85rem; color: var(--text-secondary); margin-left: .8rem; }
 
 
 
 
64
  .top3 { font-size: 1.05rem; margin-bottom: 1rem; }
65
  .top3 b { letter-spacing: .15em; color: var(--accent); }
66
  .bar-row { display: grid; grid-template-columns: 1.4rem 1fr 3.6rem; align-items: center; gap: .6rem; margin: .45rem 0; }
 
70
  .fill { background: var(--accent); height: 100%; border-radius: 0 4px 4px 0; width: 0; transition: width .35s ease; }
71
  .winner .letter, .winner .val { color: var(--accent); }
72
  footer { color: var(--text-secondary); font-size: .8rem; margin-top: 2rem; }
 
73
  #result { display: none; }
74
  </style>
75
  </head>
 
77
  <div class="wrap">
78
  <h1>🧠 Smart MCQ Solver</h1>
79
  <p class="sub">
80
+ Final deployment — DLGenAI course project. Solves 5-option (A–E) multiple-choice
81
+ science questions and returns the top-3 ranked answers (Kaggle MAP@3 format).
 
 
 
82
  </p>
83
 
84
  <div class="card">
85
  <div class="examples" id="examples"></div>
86
+ <label for="model">Model</label>
87
+ <select id="model">
88
+ <option value="from_scratch_v2">from_scratch_v2</option>
89
+ <option value="qwen3.5">qwen3.5</option>
90
+ <option value="deberta_v3">deberta_v3</option>
91
+ </select>
92
  <label for="prompt">Question prompt</label>
93
  <textarea id="prompt" rows="3" placeholder="Enter the multiple-choice question…"></textarea>
94
  <div id="options"></div>
 
100
  <div id="bars"></div>
101
  </div>
102
 
103
+ <footer>Transformer trained with W&amp;B tracking on the competition data, running client-side via ONNX.</footer>
 
 
 
104
  </div>
105
 
106
  <script>
107
  const OPTIONS = ["A", "B", "C", "D", "E"];
108
  const MAX_LEN = 128, PAD = 0n, UNK = 1, CLS = 2n, SEP = 3n;
109
+ let vocab = null, session = null, examples = [];
 
 
 
 
110
 
111
  const optsDiv = document.getElementById("options");
112
  for (const o of OPTIONS) {
 
124
  </div>`);
125
  }
126
 
127
+ function encodeChoice(prompt, option) {
 
128
  const words = t => String(t).toLowerCase().split(/\s+/).filter(Boolean);
129
  const ids = [CLS];
130
  for (const w of words(prompt)) ids.push(BigInt(vocab[w] ?? UNK));
 
138
  return { ids, mask };
139
  }
140
 
141
+ async function solve() {
142
+ const prompt = document.getElementById("prompt").value.trim();
143
+ const opts = OPTIONS.map(o => document.getElementById("opt" + o).value.trim());
144
+ if (!prompt || opts.some(o => !o)) { alert("Please fill in the prompt and all five options."); return; }
 
 
 
 
 
 
 
 
 
 
 
145
 
 
 
146
  const allIds = new BigInt64Array(5 * MAX_LEN);
147
  const allMask = new Float32Array(5 * MAX_LEN);
148
  opts.forEach((opt, i) => {
149
+ const { ids, mask } = encodeChoice(prompt, opt);
150
  allIds.set(ids, i * MAX_LEN);
151
  allMask.set(mask, i * MAX_LEN);
152
  });
153
+
154
+ const out = await session.run({
155
  input_ids: new ort.Tensor("int64", allIds, [1, 5, MAX_LEN]),
156
  attention_mask: new ort.Tensor("float32", allMask, [1, 5, MAX_LEN]),
157
  });
158
  const logits = Array.from(out.logits.data);
159
+ const m = Math.max(...logits);
160
+ const exps = logits.map(v => Math.exp(v - m));
161
  const s = exps.reduce((a, b) => a + b, 0);
162
+ const probs = exps.map(v => v / s);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
  const ranked = probs.map((p, i) => [p, i]).sort((a, b) => b[0] - a[0]);
165
  document.getElementById("top3").textContent = ranked.slice(0, 3).map(r => OPTIONS[r[1]]).join(" ");
 
184
  const btn = document.getElementById("solve");
185
  btn.disabled = true;
186
  try {
187
+ const [vocabRes, exRes] = await Promise.all([fetch("vocab2.json"), fetch("examples.json")]);
188
+ vocab = await vocabRes.json();
189
+ examples = await exRes.json();
190
  const exDiv = document.getElementById("examples");
191
  examples.forEach((ex, i) => {
192
  const b = document.createElement("button");
 
195
  b.onclick = () => loadExample(ex);
196
  exDiv.appendChild(b);
197
  });
198
+ session = await ort.InferenceSession.create("mcq_scratch2.onnx");
199
+ status.textContent = "model ready ✓";
200
  btn.disabled = false;
201
  loadExample(examples[0]);
202
  } catch (e) {