File size: 15,346 Bytes
c7d626e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
{
 "nbformat": 4,
 "nbformat_minor": 0,
 "metadata": {
  "colab": {
   "provenance": [],
   "gpuType": "T4"
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  },
  "accelerator": "GPU"
 },
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cell 1 β€” Install `LayerInteractions` directly from Hugging Face\n",
    "\n",
    "**What the next cell does**\n",
    "\n",
    "This cell installs the public `LayerInteractions` Python package directly from the Hugging Face repository `Angshul/LayerInteractions`. It uses `pip` with the repository's Git URL, so the code being tested is the same code that an external user would obtain from Hugging Face rather than a local copy.\n",
    "\n",
    "**Why this cell is here**\n",
    "\n",
    "The purpose is to validate the distribution path itself: if this installation succeeds, the Hugging Face repository is installable as a normal Python package. This is the only package-installation step for `LayerInteractions` in the notebook.\n",
    "\n",
    "**What it does not do**\n",
    "\n",
    "It does **not** load a language model, allocate the Gumini model on the GPU, prune any layers, or run an experiment. It only installs the package and its declared dependencies. No runtime restart is required by this notebook unless Colab itself reports an unrelated dependency conflict."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "b2BcOsh3EcwF"
   },
   "outputs": [],
   "source": [
    "!pip install \"git+https://huggingface.co/Angshul/LayerInteractions\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cell 2 β€” Verify that the public pruning API imports correctly\n",
    "\n",
    "**What the next cell does**\n",
    "\n",
    "This cell imports `Order2Pruner`, the main user-facing class of `LayerInteractions`, and prints a short confirmation message.\n",
    "\n",
    "**Why this matters**\n",
    "\n",
    "Successful installation alone does not prove that the package exports its public API correctly. This import checks that Python can find the installed package and that `Order2Pruner` is exposed from `layer_interactions` exactly as intended for users.\n",
    "\n",
    "**Computational cost**\n",
    "\n",
    "Negligible. The cell does **not** instantiate a model and therefore does not download Gumini or consume meaningful GPU memory."
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "from layer_interactions import Order2Pruner\n",
    "print(\"LayerInteractions import OK\")"
   ],
   "metadata": {
    "id": "cf3WKk3BFHQz"
   },
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cell 3 β€” Verify that the result object is also part of the public API\n",
    "\n",
    "**What the next cell does**\n",
    "\n",
    "This cell imports `Order2Result`, the object used to store the measurements and selections produced by the second-order pruning procedure. It then prints the class so that the notebook confirms both major public objects are available from the installed package.\n",
    "\n",
    "**Role of `Order2Result`**\n",
    "\n",
    "During the real experiment, this object records the baseline loss, single-layer deletion losses, pairwise deletion losses, derived first- and second-order interaction terms, and the greedy deletion path. Its `select(...)` method is later used to obtain the retained and deleted layer indices for a requested target depth.\n",
    "\n",
    "**Computational cost**\n",
    "\n",
    "Again, negligible. No model is downloaded or loaded in this cell. The actual compression experiment starts in the following cell."
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "from layer_interactions import Order2Result\n",
    "\n",
    "print(\"Order2Result import OK\")\n",
    "print(Order2Result)"
   ],
   "metadata": {
    "id": "GpjE59UOFZo1"
   },
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cell 4 β€” Run the actual Order-2 layer-interaction experiment: Gumini 10 β†’ 5\n",
    "\n",
    "**What the next cell does**\n",
    "\n",
    "This is the main compression cell. It installs the Hugging Face `datasets` package if needed, loads a small fixed calibration subset from WikiText-2, loads `GuminiResearch/Gumini-1B-Base`, and runs the `LayerInteractions` second-order pruning procedure. The original model has **10 transformer layers**, and the experiment asks for enough measurements to support deletion of 5 layers, leaving a **5-layer model**.\n",
    "\n",
    "**Calibration data**\n",
    "\n",
    "The cell uses the WikiText-2 **training split** only for calibration. It keeps the first 64 nontrivial text entries whose stripped length is at least 100 characters. The held-out test split is deliberately reserved for the next cell so that the final evaluation is not performed on the calibration data.\n",
    "\n",
    "**What `fit(...)` measures**\n",
    "\n",
    "For a 10-layer model, the second-order model needs the unpruned baseline, the effect of deleting each individual layer, and every pairwise layer deletion. There are `C(10,2) = 45` layer pairs. From these measurements, the package constructs the second-order interaction surrogate and a greedy deletion path. With `max_delete=5`, it computes the path far enough to obtain a 10 β†’ 5 selection.\n",
    "\n",
    "**Checkpointing**\n",
    "\n",
    "Measurements are written to `/content/gumini_order2.json`. This allows the expensive measurements to be reused if the fitting process is interrupted while the same Colab runtime/storage remains available.\n",
    "\n",
    "**Important distinction**\n",
    "\n",
    "`fit(...)` computes the measurements and the layer selection, but it does **not yet physically remove the selected layers from the in-memory model**. The final line prints the five layers that will be retained. Physical pruning is performed only in the evaluation cell that follows.\n",
    "\n",
    "**Expected scale**\n",
    "\n",
    "Gumini's weights are about 2.16 GB on Hugging Face and the model has only 10 transformer layers, so this is deliberately chosen as a quick end-to-end demonstration of the package on a T4-class GPU without the hundreds of pair evaluations required by 28–32 layer models."
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "# Cell 3 β€” LayerInteractions: Gumini-1B-Base, 10 -> 5\n",
    "\n",
    "!pip install -q datasets\n",
    "\n",
    "from datasets import load_dataset\n",
    "from layer_interactions import Order2Pruner\n",
    "\n",
    "MODEL_ID = \"GuminiResearch/Gumini-1B-Base\"\n",
    "\n",
    "# Fixed, small calibration corpus\n",
    "ds = load_dataset(\n",
    "    \"Salesforce/wikitext\",\n",
    "    \"wikitext-2-raw-v1\",\n",
    "    split=\"train\"\n",
    ")\n",
    "\n",
    "texts = [\n",
    "    x[\"text\"].strip()\n",
    "    for x in ds\n",
    "    if len(x[\"text\"].strip()) >= 100\n",
    "][:64]\n",
    "\n",
    "print(f\"Calibration texts: {len(texts)}\")\n",
    "print(f\"Model: {MODEL_ID}\")\n",
    "print(\"Compression: 10 -> 5 layers\")\n",
    "print(\"Order-2 pairs: 45\")\n",
    "\n",
    "pruner = Order2Pruner(MODEL_ID)\n",
    "\n",
    "result = pruner.fit(\n",
    "    texts=texts,\n",
    "    checkpoint_path=\"/content/gumini_order2.json\",\n",
    "    max_delete=5,\n",
    ")\n",
    "\n",
    "print(\"\\nDONE\")\n",
    "print(\"Retained layers:\", result.select(5))"
   ],
   "metadata": {
    "id": "EIzouN4bGcqq"
   },
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cell 5 β€” Evaluate the original model, apply the computed pruning decision, and evaluate the 5-layer model\n",
    "\n",
    "**What the next cell does**\n",
    "\n",
    "This cell performs the final held-out comparison. It uses WikiText-2's **test split**, not the training/calibration split. The same fixed set of 64 nontrivial test texts is used for both the original and compressed models. The metric is token-level negative log-likelihood (NLL), with perplexity (PPL) reported as `exp(NLL)`.\n",
    "\n",
    "**Step 1: evaluate the untouched 10-layer model**\n",
    "\n",
    "Before modifying the model, the cell measures the baseline NLL and perplexity. This establishes the reference performance on exactly the text that will later be used for the compressed model.\n",
    "\n",
    "**Step 2: recover the already-computed selection**\n",
    "\n",
    "`result.select(5)` reads the result from Cell 4 and reports the retained and deleted layer indices. No interaction measurements are recomputed here.\n",
    "\n",
    "**Step 3: physically prune the model**\n",
    "\n",
    "`pruner.apply(5, result=result)` applies the already-computed 10 β†’ 5 decision to the in-memory Hugging Face model. After this call, `pruner.model` contains only five transformer layers. This is a real structural reduction, not merely masking or reporting indices.\n",
    "\n",
    "**Step 4: evaluate the compressed model**\n",
    "\n",
    "The now-5-layer model is evaluated on the exact same held-out text. The cell reports original and compressed NLL, the NLL increase, original and compressed perplexity, and the perplexity ratio.\n",
    "\n",
    "**Important: this cell mutates the model in place**\n",
    "\n",
    "Run this cell **once after Cell 4**. After `pruner.apply(...)`, the object in `pruner.model` is already the 5-layer model. Re-running only this evaluation cell would therefore no longer start from the original 10-layer baseline. To repeat the complete comparison, rerun the experiment cell first so that a fresh 10-layer model is loaded.\n",
    "\n",
    "**Result from the demonstrated run**\n",
    "\n",
    "The previously observed run retained layers `[0, 1, 2, 7, 9]`. On 9,061 held-out tokens, the original model obtained NLL 4.6452 / PPL 104.08, while the 5-layer model obtained NLL 7.4391 / PPL 1701.28. That result demonstrates that the API works end to end; it also shows that this aggressive 50% pruning point substantially degrades this particular model."
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "# Cell 4 β€” FINAL evaluation\n",
    "# Uses the already-computed Order-2 result from Cell 3.\n",
    "# NO refitting. NO pair calculations.\n",
    "\n",
    "import math\n",
    "import torch\n",
    "import torch.nn.functional as F\n",
    "from datasets import load_dataset\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# Held-out WikiText-2 test set\n",
    "# ------------------------------------------------------------\n",
    "\n",
    "test_ds = load_dataset(\n",
    "    \"Salesforce/wikitext\",\n",
    "    \"wikitext-2-raw-v1\",\n",
    "    split=\"test\"\n",
    ")\n",
    "\n",
    "test_texts = [\n",
    "    x[\"text\"].strip()\n",
    "    for x in test_ds\n",
    "    if len(x[\"text\"].strip()) >= 100\n",
    "][:64]\n",
    "\n",
    "tokenizer = pruner.tokenizer\n",
    "\n",
    "\n",
    "def evaluate_nll(model, texts, max_length=512):\n",
    "    model.eval()\n",
    "\n",
    "    total_loss = 0.0\n",
    "    total_tokens = 0\n",
    "\n",
    "    device = next(model.parameters()).device\n",
    "\n",
    "    for text in texts:\n",
    "        enc = tokenizer(\n",
    "            text,\n",
    "            return_tensors=\"pt\",\n",
    "            truncation=True,\n",
    "            max_length=max_length\n",
    "        )\n",
    "\n",
    "        input_ids = enc[\"input_ids\"].to(device)\n",
    "\n",
    "        if input_ids.shape[1] < 2:\n",
    "            continue\n",
    "\n",
    "        with torch.no_grad():\n",
    "            logits = model(input_ids=input_ids).logits\n",
    "\n",
    "        shift_logits = logits[:, :-1, :].contiguous()\n",
    "        shift_labels = input_ids[:, 1:].contiguous()\n",
    "\n",
    "        loss = F.cross_entropy(\n",
    "            shift_logits.view(-1, shift_logits.size(-1)),\n",
    "            shift_labels.view(-1),\n",
    "            reduction=\"sum\"\n",
    "        )\n",
    "\n",
    "        total_loss += loss.item()\n",
    "        total_tokens += shift_labels.numel()\n",
    "\n",
    "    nll = total_loss / total_tokens\n",
    "    ppl = math.exp(nll)\n",
    "\n",
    "    return nll, ppl, total_tokens\n",
    "\n",
    "\n",
    "# ============================================================\n",
    "# ORIGINAL MODEL\n",
    "# ============================================================\n",
    "\n",
    "print(\"Evaluating original 10-layer model...\")\n",
    "\n",
    "base_nll, base_ppl, n_tokens = evaluate_nll(\n",
    "    pruner.model,\n",
    "    test_texts\n",
    ")\n",
    "\n",
    "selection = result.select(5)\n",
    "\n",
    "print(f\"Baseline NLL : {base_nll:.4f}\")\n",
    "print(f\"Baseline PPL : {base_ppl:.2f}\")\n",
    "print(f\"Tokens       : {n_tokens:,}\")\n",
    "print(\"Retained     :\", selection[\"retained_layers\"])\n",
    "print(\"Deleted      :\", selection[\"deleted_layers\"])\n",
    "\n",
    "\n",
    "# ============================================================\n",
    "# APPLY THE ALREADY-COMPUTED 10 -> 5 SELECTION\n",
    "# Correct LayerInteractions API signature:\n",
    "#     apply(target_layers, *, result=...)\n",
    "# ============================================================\n",
    "\n",
    "pruner.apply(5, result=result)\n",
    "\n",
    "print(\"\\nApplied retained-layer selection.\")\n",
    "print(\"Physical depth:\", len(pruner.model.model.layers))\n",
    "\n",
    "\n",
    "# ============================================================\n",
    "# COMPRESSED MODEL\n",
    "# ============================================================\n",
    "\n",
    "print(\"\\nEvaluating compressed 5-layer model...\")\n",
    "\n",
    "comp_nll, comp_ppl, _ = evaluate_nll(\n",
    "    pruner.model,\n",
    "    test_texts\n",
    ")\n",
    "\n",
    "\n",
    "# ============================================================\n",
    "# RESULTS\n",
    "# ============================================================\n",
    "\n",
    "print(\"\\n\" + \"=\" * 60)\n",
    "print(\"Gumini-1B-Base β€” LayerInteractions\")\n",
    "print(\"=\" * 60)\n",
    "\n",
    "print(\"Compression      : 10 -> 5\")\n",
    "print(\"Retained layers  :\", selection[\"retained_layers\"])\n",
    "print(\"Deleted layers   :\", selection[\"deleted_layers\"])\n",
    "print(f\"Evaluation tokens: {n_tokens:,}\")\n",
    "\n",
    "print(\"-\" * 60)\n",
    "\n",
    "print(f\"Original NLL     : {base_nll:.4f}\")\n",
    "print(f\"Compressed NLL   : {comp_nll:.4f}\")\n",
    "print(f\"NLL increase     : {comp_nll - base_nll:+.4f}\")\n",
    "\n",
    "print()\n",
    "\n",
    "print(f\"Original PPL     : {base_ppl:.2f}\")\n",
    "print(f\"Compressed PPL   : {comp_ppl:.2f}\")\n",
    "print(f\"PPL ratio        : {comp_ppl / base_ppl:.3f}x\")\n",
    "\n",
    "print(\"=\" * 60)"
   ],
   "metadata": {
    "id": "06RcwysfH-Lj"
   },
   "execution_count": null,
   "outputs": []
  }
 ]
}