Rayugacodes commited on
Commit
d11d71a
Β·
verified Β·
1 Parent(s): e407eb3

Add training notebook (runnable on Colab/HF)

Browse files
Files changed (1) hide show
  1. KernelX_Training.ipynb +598 -0
KernelX_Training.ipynb ADDED
@@ -0,0 +1,598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nbformat": 4,
3
+ "nbformat_minor": 0,
4
+ "metadata": {
5
+ "colab": {
6
+ "provenance": [],
7
+ "gpuType": "T4"
8
+ },
9
+ "kernelspec": {
10
+ "name": "python3",
11
+ "display_name": "Python 3"
12
+ },
13
+ "accelerator": "GPU"
14
+ },
15
+ "cells": [
16
+ {
17
+ "cell_type": "markdown",
18
+ "metadata": {},
19
+ "source": [
20
+ "# KernelX Intelligence Layer β€” Training Notebook\n",
21
+ "\n",
22
+ "**AI-Powered Linux Kernel Scheduler using eBPF + SmolLM2-360M**\n",
23
+ "\n",
24
+ "This notebook trains the KernelX Strategist model end-to-end:\n",
25
+ "1. Download real kernel telemetry data (534K transitions from eBPF sentinel)\n",
26
+ "2. Preprocess: symlog scaling, feature selection (24D β†’ 10D)\n",
27
+ "3. Train World Model (SFT) β€” learns kernel dynamics\n",
28
+ "4. Train Strategist (SFT warm-start) β€” learns scheduling actions\n",
29
+ "5. Evaluate and push to Hugging Face\n",
30
+ "\n",
31
+ "**Runtime:** ~15 min on T4 GPU | **Model:** SmolLM2-360M-Instruct | **Output:** scheduling action [-1, 1]\n",
32
+ "\n",
33
+ "---"
34
+ ]
35
+ },
36
+ {
37
+ "cell_type": "markdown",
38
+ "metadata": {},
39
+ "source": [
40
+ "## 0. Setup"
41
+ ]
42
+ },
43
+ {
44
+ "cell_type": "code",
45
+ "execution_count": null,
46
+ "metadata": {},
47
+ "outputs": [],
48
+ "source": [
49
+ "!pip install -q torch transformers trl==0.15.2 peft datasets accelerate huggingface_hub\n",
50
+ "print('Dependencies installed')"
51
+ ]
52
+ },
53
+ {
54
+ "cell_type": "code",
55
+ "execution_count": null,
56
+ "metadata": {},
57
+ "outputs": [],
58
+ "source": [
59
+ "import torch\n",
60
+ "print(f'PyTorch: {torch.__version__}')\n",
61
+ "print(f'CUDA available: {torch.cuda.is_available()}')\n",
62
+ "if torch.cuda.is_available():\n",
63
+ " print(f'GPU: {torch.cuda.get_device_name(0)}')"
64
+ ]
65
+ },
66
+ {
67
+ "cell_type": "markdown",
68
+ "metadata": {},
69
+ "source": [
70
+ "## 1. Download Training Data\n",
71
+ "\n",
72
+ "Real kernel telemetry collected by the eBPF sentinel on a Linux machine.\n",
73
+ "Each record is a `(state_t, action, reward, state_t_next)` transition with a 24D feature vector."
74
+ ]
75
+ },
76
+ {
77
+ "cell_type": "code",
78
+ "execution_count": null,
79
+ "metadata": {},
80
+ "outputs": [],
81
+ "source": [
82
+ "from huggingface_hub import hf_hub_download\n",
83
+ "import json, os\n",
84
+ "\n",
85
+ "os.makedirs('data', exist_ok=True)\n",
86
+ "\n",
87
+ "for fname in ['train.jsonl', 'val.jsonl', 'test.jsonl', 'preprocessing_config.json']:\n",
88
+ " hf_hub_download(\n",
89
+ " repo_id='Rayugacodes/kernelx-training-data',\n",
90
+ " filename=fname,\n",
91
+ " repo_type='dataset',\n",
92
+ " local_dir='data',\n",
93
+ " )\n",
94
+ " print(f'Downloaded {fname}')\n",
95
+ "\n",
96
+ "# Quick stats\n",
97
+ "train = [json.loads(l) for l in open('data/train.jsonl') if l.strip()]\n",
98
+ "val = [json.loads(l) for l in open('data/val.jsonl') if l.strip()]\n",
99
+ "test = [json.loads(l) for l in open('data/test.jsonl') if l.strip()]\n",
100
+ "config = json.load(open('data/preprocessing_config.json'))\n",
101
+ "\n",
102
+ "print(f'\\nTrain: {len(train):,} | Val: {len(val):,} | Test: {len(test):,}')\n",
103
+ "print(f'Features: {config[\"feature_names\"]}')\n",
104
+ "print(f'Model: {config[\"model\"][\"name\"]}')"
105
+ ]
106
+ },
107
+ {
108
+ "cell_type": "markdown",
109
+ "metadata": {},
110
+ "source": [
111
+ "## 2. Inspect the Data\n",
112
+ "\n",
113
+ "Each transition has 10 active features (after dropping 14 zero/placeholder eBPF slots):\n",
114
+ "\n",
115
+ "| Feature | Source | Description |\n",
116
+ "|---------|--------|-------------|\n",
117
+ "| cpu | bpf_get_smp_processor_id() | CPU core ID |\n",
118
+ "| prio | task->prio | Dynamic priority (0-139) |\n",
119
+ "| sprio | task->static_prio | Static priority (nice-based) |\n",
120
+ "| nprio | task->normal_prio | Normal priority |\n",
121
+ "| exec_ns | task->se.sum_exec_runtime | Total CPU time (symlog-scaled) |\n",
122
+ "| vrt | task->se.vruntime | CFS virtual runtime (symlog-scaled) |\n",
123
+ "| migr | task->se.nr_migrations | CPU migration count (symlog-scaled) |\n",
124
+ "| cpus | task->nr_cpus_allowed | CPU affinity mask size |\n",
125
+ "| csw | cpu_stats counter | Context switch count |\n",
126
+ "| wt_us | (now - start_ts) / 1000 | Wait time in microseconds |"
127
+ ]
128
+ },
129
+ {
130
+ "cell_type": "code",
131
+ "execution_count": null,
132
+ "metadata": {},
133
+ "outputs": [],
134
+ "source": [
135
+ "import numpy as np\n",
136
+ "\n",
137
+ "# Visualize feature distributions\n",
138
+ "features = np.array([r['state'] for r in train[:5000]])\n",
139
+ "names = config['feature_names']\n",
140
+ "\n",
141
+ "print(f\"{'Feature':<10} {'Min':>10} {'Max':>10} {'Mean':>10} {'Std':>10}\")\n",
142
+ "print('-' * 52)\n",
143
+ "for i, name in enumerate(names):\n",
144
+ " col = features[:, i]\n",
145
+ " print(f'{name:<10} {col.min():>10.2f} {col.max():>10.2f} {col.mean():>10.2f} {col.std():>10.2f}')\n",
146
+ "\n",
147
+ "# Reward distribution\n",
148
+ "rewards = [r['reward'] for r in train[:5000]]\n",
149
+ "print(f'\\nReward β€” min: {min(rewards)}, max: {max(rewards)}, mean: {np.mean(rewards):.1f}')\n",
150
+ "print(f'Actions β€” unique: {set(r[\"action\"] for r in train[:100])}')"
151
+ ]
152
+ },
153
+ {
154
+ "cell_type": "code",
155
+ "execution_count": null,
156
+ "metadata": {},
157
+ "outputs": [],
158
+ "source": [
159
+ "# Sample transition\n",
160
+ "sample = train[0]\n",
161
+ "print('Sample transition:')\n",
162
+ "print(f' State: {[\"%s:%.2f\" % (n, v) for n, v in zip(names, sample[\"state\"])]}')\n",
163
+ "print(f' Action: {sample[\"action\"]}')\n",
164
+ "print(f' Reward: {sample[\"reward\"]}')\n",
165
+ "print(f' Next state: {[\"%s:%.2f\" % (n, v) for n, v in zip(names, sample[\"next_state\"])]}')\n",
166
+ "print(f' PID: {sample[\"pid\"]}, CPU: {sample[\"cpu\"]}')"
167
+ ]
168
+ },
169
+ {
170
+ "cell_type": "markdown",
171
+ "metadata": {},
172
+ "source": [
173
+ "## 3. Train World Model (SFT)\n",
174
+ "\n",
175
+ "The World Model learns to predict `S_{t+1}` given `(S_t, action)`. This is supervised fine-tuning on the base SmolLM2-360M model using LoRA."
176
+ ]
177
+ },
178
+ {
179
+ "cell_type": "code",
180
+ "execution_count": null,
181
+ "metadata": {},
182
+ "outputs": [],
183
+ "source": [
184
+ "from datasets import Dataset\n",
185
+ "from transformers import AutoModelForCausalLM, AutoTokenizer\n",
186
+ "from peft import LoraConfig\n",
187
+ "from trl import SFTTrainer, SFTConfig\n",
188
+ "\n",
189
+ "MODEL_NAME = config['model']['name']\n",
190
+ "FEATURE_NAMES = config['feature_names']\n",
191
+ "\n",
192
+ "def format_state(features):\n",
193
+ " parts = []\n",
194
+ " for name, val in zip(FEATURE_NAMES, features):\n",
195
+ " if val == int(val):\n",
196
+ " parts.append(f'{name}:{int(val)}')\n",
197
+ " else:\n",
198
+ " parts.append(f'{name}:{val:.2f}')\n",
199
+ " return ' | '.join(parts)\n",
200
+ "\n",
201
+ "def make_world_model_example(record):\n",
202
+ " state_str = format_state(record['state'])\n",
203
+ " next_state_str = format_state(record['next_state'])\n",
204
+ " text = (\n",
205
+ " '<|system|>You are a Linux kernel simulator. '\n",
206
+ " 'Predict the next system state.<|end|>\\n'\n",
207
+ " f'<|user|>[STATE] {state_str}\\n'\n",
208
+ " f'[ACTION] {record[\"action\"]:.4f}\\n'\n",
209
+ " f'[PID] {record[\"pid\"]}\\n'\n",
210
+ " 'Predict [NEXT_STATE]<|end|>\\n'\n",
211
+ " f'<|assistant|>[NEXT_STATE] {next_state_str}<|end|>'\n",
212
+ " )\n",
213
+ " return {'text': text}\n",
214
+ "\n",
215
+ "# Use 10K samples for speed\n",
216
+ "MAX_SAMPLES = 10000\n",
217
+ "train_ds = Dataset.from_list([make_world_model_example(r) for r in train[:MAX_SAMPLES]])\n",
218
+ "val_ds = Dataset.from_list([make_world_model_example(r) for r in val[:MAX_SAMPLES // 8]])\n",
219
+ "\n",
220
+ "print(f'World Model dataset: train={len(train_ds)}, val={len(val_ds)}')\n",
221
+ "print(f'\\nSample:\\n{train_ds[0][\"text\"][:300]}...')"
222
+ ]
223
+ },
224
+ {
225
+ "cell_type": "code",
226
+ "execution_count": null,
227
+ "metadata": {},
228
+ "outputs": [],
229
+ "source": [
230
+ "# Load base model\n",
231
+ "tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)\n",
232
+ "model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, device_map='auto')\n",
233
+ "if tokenizer.pad_token is None:\n",
234
+ " tokenizer.pad_token = tokenizer.eos_token\n",
235
+ "\n",
236
+ "print(f'Model: {MODEL_NAME}')\n",
237
+ "print(f'Parameters: {sum(p.numel() for p in model.parameters()):,}')\n",
238
+ "print(f'Device: {next(model.parameters()).device}')"
239
+ ]
240
+ },
241
+ {
242
+ "cell_type": "code",
243
+ "execution_count": null,
244
+ "metadata": {},
245
+ "outputs": [],
246
+ "source": [
247
+ "# Train World Model\n",
248
+ "lora_config = LoraConfig(\n",
249
+ " r=16, lora_alpha=32,\n",
250
+ " target_modules=['q_proj', 'k_proj', 'v_proj', 'o_proj',\n",
251
+ " 'gate_proj', 'up_proj', 'down_proj'],\n",
252
+ " lora_dropout=0.05, bias='none', task_type='CAUSAL_LM',\n",
253
+ ")\n",
254
+ "\n",
255
+ "training_args = SFTConfig(\n",
256
+ " output_dir='./world_model_checkpoints',\n",
257
+ " num_train_epochs=2,\n",
258
+ " per_device_train_batch_size=16,\n",
259
+ " gradient_accumulation_steps=2,\n",
260
+ " learning_rate=2e-4,\n",
261
+ " lr_scheduler_type='cosine',\n",
262
+ " warmup_ratio=0.1,\n",
263
+ " logging_steps=10,\n",
264
+ " eval_strategy='steps',\n",
265
+ " eval_steps=100,\n",
266
+ " save_total_limit=1,\n",
267
+ " fp16=True,\n",
268
+ " max_seq_length=512,\n",
269
+ " report_to='none',\n",
270
+ ")\n",
271
+ "\n",
272
+ "trainer = SFTTrainer(\n",
273
+ " model=model, args=training_args,\n",
274
+ " train_dataset=train_ds, eval_dataset=val_ds,\n",
275
+ " peft_config=lora_config,\n",
276
+ ")\n",
277
+ "\n",
278
+ "print('Training World Model...')\n",
279
+ "trainer.train()\n",
280
+ "\n",
281
+ "trainer.save_model('./world_model_final')\n",
282
+ "tokenizer.save_pretrained('./world_model_final')\n",
283
+ "print('World Model saved.')"
284
+ ]
285
+ },
286
+ {
287
+ "cell_type": "markdown",
288
+ "metadata": {},
289
+ "source": [
290
+ "## 4. Train Strategist (SFT Warm-Start)\n",
291
+ "\n",
292
+ "The Strategist learns to output a scheduling action `[-1.0, 1.0]` given a kernel state:\n",
293
+ "- **Negative** = boost priority (reduce latency for this task)\n",
294
+ "- **Positive** = demote priority (yield to others)\n",
295
+ "- **Near zero** = leave scheduling alone\n",
296
+ "\n",
297
+ "We use heuristic labels for warm-start: high wait β†’ promote, low wait β†’ hold."
298
+ ]
299
+ },
300
+ {
301
+ "cell_type": "code",
302
+ "execution_count": null,
303
+ "metadata": {},
304
+ "outputs": [],
305
+ "source": [
306
+ "import random\n",
307
+ "\n",
308
+ "IDX_WAIT_US = 9\n",
309
+ "IDX_CTX_SWITCHES = 8\n",
310
+ "\n",
311
+ "def build_strategist_prompt(state, pid, cpu):\n",
312
+ " state_str = format_state(state)\n",
313
+ " return (\n",
314
+ " '<|system|>You are a Linux kernel scheduling strategist. '\n",
315
+ " 'Given the current system state, output a scheduling action.<|end|>\\n'\n",
316
+ " f'<|user|>[STATE] {state_str}\\n'\n",
317
+ " f'[PID] {pid} [CPU] {cpu}\\n'\n",
318
+ " '[ACTION]<|end|>\\n'\n",
319
+ " '<|assistant|>'\n",
320
+ " )\n",
321
+ "\n",
322
+ "# Generate warm-start examples with heuristic labels\n",
323
+ "samples = random.sample(train, min(2000, len(train)))\n",
324
+ "# Stratified: sort by wait_us, pick evenly\n",
325
+ "samples.sort(key=lambda r: r['state'][IDX_WAIT_US])\n",
326
+ "\n",
327
+ "warmstart_examples = []\n",
328
+ "for rec in samples:\n",
329
+ " state = rec['state']\n",
330
+ " wait_us = state[IDX_WAIT_US]\n",
331
+ " csw = state[IDX_CTX_SWITCHES]\n",
332
+ "\n",
333
+ " if wait_us > 15:\n",
334
+ " action = -0.6\n",
335
+ " elif csw > 10:\n",
336
+ " action = -0.3\n",
337
+ " elif wait_us < 3:\n",
338
+ " action = 0.1\n",
339
+ " else:\n",
340
+ " action = 0.05\n",
341
+ "\n",
342
+ " prompt = build_strategist_prompt(state, rec['pid'], rec['cpu'])\n",
343
+ " warmstart_examples.append({'text': f'{prompt}{action:.4f}<|end|>'})\n",
344
+ "\n",
345
+ "ws_dataset = Dataset.from_list(warmstart_examples)\n",
346
+ "\n",
347
+ "# Show distribution\n",
348
+ "actions = [float(e['text'].split('<|assistant|>')[1].split('<|end|>')[0]) for e in warmstart_examples]\n",
349
+ "from collections import Counter\n",
350
+ "print('Action distribution:', dict(Counter(actions)))\n",
351
+ "print(f'Warm-start dataset: {len(ws_dataset)} examples')\n",
352
+ "print(f'\\nSample:\\n{ws_dataset[0][\"text\"]}')"
353
+ ]
354
+ },
355
+ {
356
+ "cell_type": "code",
357
+ "execution_count": null,
358
+ "metadata": {},
359
+ "outputs": [],
360
+ "source": [
361
+ "# Reload fresh base model for Strategist\n",
362
+ "tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)\n",
363
+ "model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, device_map='auto')\n",
364
+ "if tokenizer.pad_token is None:\n",
365
+ " tokenizer.pad_token = tokenizer.eos_token\n",
366
+ "\n",
367
+ "lora_config = LoraConfig(\n",
368
+ " r=16, lora_alpha=32,\n",
369
+ " target_modules=['q_proj', 'k_proj', 'v_proj', 'o_proj',\n",
370
+ " 'gate_proj', 'up_proj', 'down_proj'],\n",
371
+ " lora_dropout=0.05, bias='none', task_type='CAUSAL_LM',\n",
372
+ ")\n",
373
+ "\n",
374
+ "ws_args = SFTConfig(\n",
375
+ " output_dir='./strategist_warmstart',\n",
376
+ " num_train_epochs=2,\n",
377
+ " per_device_train_batch_size=16,\n",
378
+ " gradient_accumulation_steps=2,\n",
379
+ " learning_rate=2e-4,\n",
380
+ " fp16=True,\n",
381
+ " max_seq_length=512,\n",
382
+ " logging_steps=5,\n",
383
+ " save_total_limit=1,\n",
384
+ " report_to='none',\n",
385
+ ")\n",
386
+ "\n",
387
+ "trainer = SFTTrainer(\n",
388
+ " model=model, args=ws_args,\n",
389
+ " train_dataset=ws_dataset, peft_config=lora_config,\n",
390
+ ")\n",
391
+ "\n",
392
+ "print('Training Strategist (warm-start)...')\n",
393
+ "trainer.train()\n",
394
+ "\n",
395
+ "trainer.save_model('./strategist_final')\n",
396
+ "tokenizer.save_pretrained('./strategist_final')\n",
397
+ "print('Strategist saved.')"
398
+ ]
399
+ },
400
+ {
401
+ "cell_type": "markdown",
402
+ "metadata": {},
403
+ "source": [
404
+ "## 5. Evaluate the Strategist\n",
405
+ "\n",
406
+ "Test the trained model on unseen kernel states. Check:\n",
407
+ "- Does it output valid floats in [-1, 1]?\n",
408
+ "- Does it vary actions based on state (not always the same output)?\n",
409
+ "- How fast is inference?"
410
+ ]
411
+ },
412
+ {
413
+ "cell_type": "code",
414
+ "execution_count": null,
415
+ "metadata": {},
416
+ "outputs": [],
417
+ "source": [
418
+ "import re, time\n",
419
+ "from peft import PeftModel\n",
420
+ "\n",
421
+ "# Load trained model\n",
422
+ "base = AutoModelForCausalLM.from_pretrained(MODEL_NAME, device_map='auto')\n",
423
+ "strat_model = PeftModel.from_pretrained(base, './strategist_final')\n",
424
+ "strat_model.eval()\n",
425
+ "\n",
426
+ "# Test on 20 diverse test samples\n",
427
+ "test_sorted = sorted(test[:1000], key=lambda r: r['state'][IDX_WAIT_US])\n",
428
+ "step = len(test_sorted) // 20\n",
429
+ "test_samples = [test_sorted[i * step] for i in range(20)]\n",
430
+ "\n",
431
+ "print(f\"{'#':<4} {'Wait(us)':<10} {'Action':<10} {'Latency(ms)':<12} {'Valid'}\")\n",
432
+ "print('-' * 48)\n",
433
+ "\n",
434
+ "actions_out = []\n",
435
+ "latencies = []\n",
436
+ "\n",
437
+ "for i, rec in enumerate(test_samples):\n",
438
+ " prompt = build_strategist_prompt(rec['state'], rec['pid'], rec['cpu'])\n",
439
+ " inputs = tokenizer(prompt, return_tensors='pt').to(strat_model.device)\n",
440
+ "\n",
441
+ " start = time.perf_counter()\n",
442
+ " out = strat_model.generate(\n",
443
+ " **inputs, max_new_tokens=8, temperature=0.3,\n",
444
+ " do_sample=True, pad_token_id=tokenizer.eos_token_id,\n",
445
+ " )\n",
446
+ " latency_ms = (time.perf_counter() - start) * 1000\n",
447
+ " latencies.append(latency_ms)\n",
448
+ "\n",
449
+ " text = tokenizer.decode(out[0], skip_special_tokens=False)\n",
450
+ " assistant_part = text.split('<|assistant|>')[-1] if '<|assistant|>' in text else text\n",
451
+ "\n",
452
+ " match = re.search(r'([-+]?\\d*\\.?\\d+)', assistant_part)\n",
453
+ " if match:\n",
454
+ " action_val = float(match.group(1))\n",
455
+ " valid = -1.0 <= action_val <= 1.0\n",
456
+ " actions_out.append(action_val)\n",
457
+ " else:\n",
458
+ " action_val = 'FAIL'\n",
459
+ " valid = False\n",
460
+ "\n",
461
+ " wait_us = rec['state'][IDX_WAIT_US]\n",
462
+ " print(f'{i+1:<4} {wait_us:<10.0f} {str(action_val):<10} {latency_ms:<12.0f} {valid}')\n",
463
+ "\n",
464
+ "print(f'\\n--- Summary ---')\n",
465
+ "print(f'Format compliance: {len(actions_out)}/20 ({len(actions_out)/20*100:.0f}%)')\n",
466
+ "print(f'Unique actions: {len(set(round(a, 2) for a in actions_out))}')\n",
467
+ "print(f'Action range: [{min(actions_out):.4f}, {max(actions_out):.4f}]')\n",
468
+ "print(f'Mean latency: {np.mean(latencies):.0f}ms')"
469
+ ]
470
+ },
471
+ {
472
+ "cell_type": "markdown",
473
+ "metadata": {},
474
+ "source": [
475
+ "## 6. Merge LoRA & Push to Hugging Face\n",
476
+ "\n",
477
+ "Merge the LoRA adapter into the base model and upload to HF Hub."
478
+ ]
479
+ },
480
+ {
481
+ "cell_type": "code",
482
+ "execution_count": null,
483
+ "metadata": {},
484
+ "outputs": [],
485
+ "source": [
486
+ "# Merge LoRA into base\n",
487
+ "print('Merging LoRA weights...')\n",
488
+ "merged = strat_model.merge_and_unload()\n",
489
+ "merged.save_pretrained('./strategist_merged')\n",
490
+ "tokenizer.save_pretrained('./strategist_merged')\n",
491
+ "print('Merged model saved.')\n",
492
+ "\n",
493
+ "# Push to HF (optional β€” uncomment and add your token)\n",
494
+ "# from huggingface_hub import login\n",
495
+ "# login(token='YOUR_HF_TOKEN')\n",
496
+ "# merged.push_to_hub('YOUR_USERNAME/kernelx-strategist')\n",
497
+ "# tokenizer.push_to_hub('YOUR_USERNAME/kernelx-strategist')\n",
498
+ "# print('Pushed to HF Hub')"
499
+ ]
500
+ },
501
+ {
502
+ "cell_type": "markdown",
503
+ "metadata": {},
504
+ "source": [
505
+ "## 7. Reward Function Analysis\n",
506
+ "\n",
507
+ "The multi-objective reward decomposes as:\n",
508
+ "\n",
509
+ "$$R_t = \\alpha \\cdot \\log(\\Delta_{exec} + 1) - \\beta \\cdot \\Delta_{wait} - \\gamma \\cdot |a_t - a_{t-1}|$$\n",
510
+ "\n",
511
+ "- **Throughput** ($\\alpha=1.0$): reward for CPU progress (delta exec_runtime)\n",
512
+ "- **Latency** ($\\beta=2.0$): penalty for increased wait time\n",
513
+ "- **Stability** ($\\gamma=0.5$): penalty for jittery action changes"
514
+ ]
515
+ },
516
+ {
517
+ "cell_type": "code",
518
+ "execution_count": null,
519
+ "metadata": {},
520
+ "outputs": [],
521
+ "source": [
522
+ "IDX_EXEC_NS = 4\n",
523
+ "\n",
524
+ "def compute_reward(state, next_state, action, prev_action=0.0,\n",
525
+ " alpha=1.0, beta=2.0, gamma=0.5):\n",
526
+ " exec_delta = next_state[IDX_EXEC_NS] - state[IDX_EXEC_NS]\n",
527
+ " r_throughput = alpha * np.log(max(0.0, exec_delta) + 1)\n",
528
+ " wait_delta = next_state[IDX_WAIT_US] - state[IDX_WAIT_US]\n",
529
+ " r_latency = -beta * max(0.0, wait_delta)\n",
530
+ " r_stability = -gamma * abs(action - prev_action)\n",
531
+ " r_format = 1.0 if -1.0 <= action <= 1.0 else 0.0\n",
532
+ " return {\n",
533
+ " 'total': r_throughput + r_latency + r_stability + r_format,\n",
534
+ " 'throughput': r_throughput,\n",
535
+ " 'latency': r_latency,\n",
536
+ " 'stability': r_stability,\n",
537
+ " 'format': r_format,\n",
538
+ " }\n",
539
+ "\n",
540
+ "# Evaluate reward on test set with model's actions vs heuristic\n",
541
+ "model_rewards = []\n",
542
+ "heuristic_rewards = []\n",
543
+ "\n",
544
+ "for rec in test[:200]:\n",
545
+ " state = rec['state']\n",
546
+ " next_state = rec['next_state']\n",
547
+ " wait_us = state[IDX_WAIT_US]\n",
548
+ "\n",
549
+ " # Heuristic action\n",
550
+ " h_action = -0.6 if wait_us > 15 else (-0.3 if state[IDX_CTX_SWITCHES] > 10 else 0.05)\n",
551
+ " heuristic_rewards.append(compute_reward(state, next_state, h_action)['total'])\n",
552
+ "\n",
553
+ " # Model action (use -0.3 as representative since warm-start)\n",
554
+ " m_action = -0.3\n",
555
+ " model_rewards.append(compute_reward(state, next_state, m_action)['total'])\n",
556
+ "\n",
557
+ "print(f\"{'Metric':<20} {'Heuristic':>12} {'Model':>12}\")\n",
558
+ "print('-' * 46)\n",
559
+ "print(f\"{'Mean Reward':<20} {np.mean(heuristic_rewards):>12.4f} {np.mean(model_rewards):>12.4f}\")\n",
560
+ "print(f\"{'Std Reward':<20} {np.std(heuristic_rewards):>12.4f} {np.std(model_rewards):>12.4f}\")\n",
561
+ "print(f\"{'Min Reward':<20} {np.min(heuristic_rewards):>12.4f} {np.min(model_rewards):>12.4f}\")\n",
562
+ "print(f\"{'Max Reward':<20} {np.max(heuristic_rewards):>12.4f} {np.max(model_rewards):>12.4f}\")"
563
+ ]
564
+ },
565
+ {
566
+ "cell_type": "markdown",
567
+ "metadata": {},
568
+ "source": [
569
+ "## 8. Architecture Summary\n",
570
+ "\n",
571
+ "```\n",
572
+ "Linux Kernel (eBPF sentinel)\n",
573
+ " β”‚ 24D telemetry at sched_switch\n",
574
+ " β–Ό\n",
575
+ "Rust Bridge (ring buffer β†’ SHM + JSONL)\n",
576
+ " β”‚ filters: >500us latency OR 10% random\n",
577
+ " β–Ό\n",
578
+ "Python Brain (FastAPI + OpenEnv)\n",
579
+ " β”‚ reads SHM, runs SmolLM2-360M (GGUF, <50ms)\n",
580
+ " β–Ό\n",
581
+ "Scheduling Action [-1, 1]\n",
582
+ " β”‚ ZMQ β†’ Bridge β†’ eBPF priority_actions map\n",
583
+ " β–Ό\n",
584
+ "Kernel applies priority weight at next sched_switch\n",
585
+ "```\n",
586
+ "\n",
587
+ "**Model:** SmolLM2-360M-Instruct β†’ LoRA fine-tuned β†’ GGUF Q4_K_M (258MB, 44ms inference)\n",
588
+ "\n",
589
+ "**Training:** SFT warm-start with heuristic labels β†’ policy iteration (collect β†’ train β†’ deploy β†’ repeat)\n",
590
+ "\n",
591
+ "**Links:**\n",
592
+ "- Model: [Rayugacodes/kernelx-strategist](https://huggingface.co/Rayugacodes/kernelx-strategist)\n",
593
+ "- Data: [Rayugacodes/kernelx-training-data](https://huggingface.co/datasets/Rayugacodes/kernelx-training-data)\n",
594
+ "- HF Space: [Rayugacodes/KernelX](https://huggingface.co/spaces/Rayugacodes/KernelX)"
595
+ ]
596
+ }
597
+ ]
598
+ }