Loosebag commited on
Commit
da99c2a
·
1 Parent(s): 2e11436

feat(colab): add RL and LLM training notebook for traffic control

Browse files
Files changed (1) hide show
  1. colab/train_rl_and_llm_traffic.ipynb +462 -0
colab/train_rl_and_llm_traffic.ipynb ADDED
@@ -0,0 +1,462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# RL + LLM Training (Colab) for Adaptive Traffic Intelligence\n",
8
+ "\n",
9
+ "This notebook trains:\n",
10
+ "1. **RL traffic controller** (PPO) on the `TrafficEnv`\n",
11
+ "2. **LLM policy model** (LoRA fine-tuning) to imitate the RL controller\n",
12
+ "\n",
13
+ "It also compares **Fixed baseline vs RL vs LLM** on waiting time, queue length, and throughput."
14
+ ]
15
+ },
16
+ {
17
+ "cell_type": "markdown",
18
+ "metadata": {},
19
+ "source": [
20
+ "## Best Model Choices (Practical)\n",
21
+ "\n",
22
+ "### RL (this task)\n",
23
+ "- **Recommended default:** `PPO` (stable, strong for this discrete-control setup)\n",
24
+ "- Alternatives: `DQN` (already in repo), `A2C`\n",
25
+ "\n",
26
+ "### LLM policy model\n",
27
+ "- **Best on Colab T4 (recommended):** `Qwen/Qwen2.5-1.5B-Instruct` with LoRA + 4-bit\n",
28
+ "- Higher quality (needs stronger GPU): `meta-llama/Meta-Llama-3.1-8B-Instruct`\n",
29
+ "- Faster/smaller fallback: `Qwen/Qwen2.5-0.5B-Instruct`"
30
+ ]
31
+ },
32
+ {
33
+ "cell_type": "code",
34
+ "execution_count": null,
35
+ "metadata": {},
36
+ "outputs": [],
37
+ "source": [
38
+ "# ==== 0) Runtime setup ====\n",
39
+ "!nvidia-smi\n",
40
+ "\n",
41
+ "!pip -q install -U gymnasium stable-baselines3 sb3-contrib\n",
42
+ "!pip -q install -U transformers datasets peft trl accelerate bitsandbytes sentencepiece\n"
43
+ ]
44
+ },
45
+ {
46
+ "cell_type": "code",
47
+ "execution_count": null,
48
+ "metadata": {},
49
+ "outputs": [],
50
+ "source": [
51
+ "# ==== 1) Get project code ====\n",
52
+ "import os\n",
53
+ "from pathlib import Path\n",
54
+ "\n",
55
+ "REPO_URL = \"https://github.com/DivyankLosse/TRLE-Hackethon.git\"\n",
56
+ "PROJECT_DIR = Path(\"TRLE-Hackethon\")\n",
57
+ "\n",
58
+ "if not PROJECT_DIR.exists():\n",
59
+ " !git clone {REPO_URL}\n",
60
+ "\n",
61
+ "%cd TRLE-Hackethon\n",
62
+ "\n",
63
+ "# Optional: install local package deps\n",
64
+ "!pip -q install -r requirements.txt\n"
65
+ ]
66
+ },
67
+ {
68
+ "cell_type": "code",
69
+ "execution_count": null,
70
+ "metadata": {},
71
+ "outputs": [],
72
+ "source": [
73
+ "# ==== 2) Imports and Gym wrapper for TrafficEnv ====\n",
74
+ "import math\n",
75
+ "import json\n",
76
+ "import random\n",
77
+ "from dataclasses import dataclass\n",
78
+ "\n",
79
+ "import numpy as np\n",
80
+ "import gymnasium as gym\n",
81
+ "from gymnasium import spaces\n",
82
+ "\n",
83
+ "from traffic_rl.env.traffic_env import TrafficEnv\n",
84
+ "from traffic_rl.baseline.fixed_time_controller import FixedTimeController\n",
85
+ "\n",
86
+ "class TrafficGymEnv(gym.Env):\n",
87
+ " metadata = {\"render_modes\": []}\n",
88
+ "\n",
89
+ " def __init__(self, env_config: dict):\n",
90
+ " super().__init__()\n",
91
+ " self.base_config = dict(env_config)\n",
92
+ " self.base_env = TrafficEnv(config=self.base_config)\n",
93
+ " self.action_space = spaces.Discrete(3)\n",
94
+ " self.observation_space = spaces.Box(\n",
95
+ " low=np.zeros((10,), dtype=np.float32),\n",
96
+ " high=np.full((10,), np.finfo(np.float32).max, dtype=np.float32),\n",
97
+ " dtype=np.float32,\n",
98
+ " )\n",
99
+ "\n",
100
+ " def reset(self, *, seed=None, options=None):\n",
101
+ " if seed is not None:\n",
102
+ " cfg = dict(self.base_config)\n",
103
+ " cfg[\"seed\"] = int(seed)\n",
104
+ " self.base_env = TrafficEnv(config=cfg)\n",
105
+ " obs = self.base_env.reset().astype(np.float32)\n",
106
+ " return obs, {}\n",
107
+ "\n",
108
+ " def step(self, action):\n",
109
+ " obs, reward, done, info = self.base_env.step(int(action))\n",
110
+ " obs = obs.astype(np.float32)\n",
111
+ " terminated = bool(done)\n",
112
+ " truncated = False\n",
113
+ " return obs, float(reward), terminated, truncated, info\n"
114
+ ]
115
+ },
116
+ {
117
+ "cell_type": "code",
118
+ "execution_count": null,
119
+ "metadata": {},
120
+ "outputs": [],
121
+ "source": [
122
+ "# ==== 3) Train RL model (PPO) ====\n",
123
+ "from stable_baselines3 import PPO\n",
124
+ "from stable_baselines3.common.vec_env import DummyVecEnv\n",
125
+ "\n",
126
+ "ENV_CONFIG = {\n",
127
+ " \"max_steps\": 120,\n",
128
+ " \"arrival_mode\": \"stochastic\",\n",
129
+ " \"lane_bias\": (1.6, 0.8, 1.4, 0.6),\n",
130
+ " \"peak_rates\": (3.8, 2.2, 3.4, 1.5),\n",
131
+ " \"offpeak_rates\": (1.4, 0.9, 1.2, 0.7),\n",
132
+ " \"peak_duration\": 35,\n",
133
+ " \"cycle_duration\": 60,\n",
134
+ " \"service_rate\": 2,\n",
135
+ " \"ambulance_spawn_prob\": 0.08,\n",
136
+ " \"seed\": 42,\n",
137
+ "}\n",
138
+ "\n",
139
+ "N_ENVS = 4\n",
140
+ "TOTAL_TIMESTEPS = 120_000 # increase to 300k+ for stronger policy\n",
141
+ "\n",
142
+ "def make_env(rank):\n",
143
+ " def _thunk():\n",
144
+ " cfg = dict(ENV_CONFIG)\n",
145
+ " cfg[\"seed\"] = ENV_CONFIG[\"seed\"] + rank\n",
146
+ " return TrafficGymEnv(cfg)\n",
147
+ " return _thunk\n",
148
+ "\n",
149
+ "vec_env = DummyVecEnv([make_env(i) for i in range(N_ENVS)])\n",
150
+ "\n",
151
+ "ppo = PPO(\n",
152
+ " policy=\"MlpPolicy\",\n",
153
+ " env=vec_env,\n",
154
+ " n_steps=1024,\n",
155
+ " batch_size=256,\n",
156
+ " learning_rate=3e-4,\n",
157
+ " gamma=0.99,\n",
158
+ " gae_lambda=0.95,\n",
159
+ " clip_range=0.2,\n",
160
+ " ent_coef=0.01,\n",
161
+ " vf_coef=0.5,\n",
162
+ " verbose=1,\n",
163
+ " seed=42,\n",
164
+ ")\n",
165
+ "\n",
166
+ "ppo.learn(total_timesteps=TOTAL_TIMESTEPS, progress_bar=True)\n",
167
+ "\n",
168
+ "Path(\"artifacts\").mkdir(exist_ok=True)\n",
169
+ "ppo.save(\"artifacts/ppo_traffic\")\n",
170
+ "print(\"Saved RL model -> artifacts/ppo_traffic.zip\")"
171
+ ]
172
+ },
173
+ {
174
+ "cell_type": "code",
175
+ "execution_count": null,
176
+ "metadata": {},
177
+ "outputs": [],
178
+ "source": [
179
+ "# ==== 4) Evaluate baseline vs RL ====\n",
180
+ "def eval_policy(policy_fn, env_config, episodes=20):\n",
181
+ " metrics = {\n",
182
+ " \"reward\": [],\n",
183
+ " \"avg_queue_length\": [],\n",
184
+ " \"avg_waiting_time\": [],\n",
185
+ " \"throughput\": [],\n",
186
+ " \"ambulance_clearances\": [],\n",
187
+ " }\n",
188
+ "\n",
189
+ " for ep in range(episodes):\n",
190
+ " cfg = dict(env_config)\n",
191
+ " cfg[\"seed\"] = env_config.get(\"seed\", 42) + ep\n",
192
+ " env = TrafficEnv(config=cfg)\n",
193
+ " state = env.reset()\n",
194
+ " done = False\n",
195
+ "\n",
196
+ " rewards, queues, waits, throughputs = [], [], [], []\n",
197
+ " amb_clears = 0\n",
198
+ " step = 0\n",
199
+ "\n",
200
+ " while not done:\n",
201
+ " action = int(policy_fn(state, step))\n",
202
+ " state, reward, done, info = env.step(action)\n",
203
+ " rewards.append(float(reward))\n",
204
+ " queues.append(float(info[\"queue_sum\"]))\n",
205
+ " waits.append(float(info[\"waiting_sum\"]))\n",
206
+ " throughputs.append(float(info[\"throughput\"]))\n",
207
+ " amb_clears += int(bool(info.get(\"ambulance_cleared\", False)))\n",
208
+ " step += 1\n",
209
+ "\n",
210
+ " metrics[\"reward\"].append(sum(rewards))\n",
211
+ " metrics[\"avg_queue_length\"].append(float(np.mean(queues) if queues else 0.0))\n",
212
+ " metrics[\"avg_waiting_time\"].append(float(np.mean(waits) if waits else 0.0))\n",
213
+ " metrics[\"throughput\"].append(sum(throughputs))\n",
214
+ " metrics[\"ambulance_clearances\"].append(float(amb_clears))\n",
215
+ "\n",
216
+ " return {k: float(np.mean(v)) for k, v in metrics.items()}\n",
217
+ "\n",
218
+ "fixed = FixedTimeController(switch_interval=5)\n",
219
+ "baseline_metrics = eval_policy(lambda _s, t: fixed.action_for_step(t), ENV_CONFIG, episodes=20)\n",
220
+ "rl_metrics = eval_policy(lambda s, _t: ppo.predict(s, deterministic=True)[0], ENV_CONFIG, episodes=20)\n",
221
+ "\n",
222
+ "def pct_improve(lower_better_key):\n",
223
+ " b, r = baseline_metrics[lower_better_key], rl_metrics[lower_better_key]\n",
224
+ " return 0.0 if b == 0 else ((b - r) / b) * 100.0\n",
225
+ "\n",
226
+ "def pct_gain(higher_better_key):\n",
227
+ " b, r = baseline_metrics[higher_better_key], rl_metrics[higher_better_key]\n",
228
+ " return 0.0 if b == 0 else ((r - b) / b) * 100.0\n",
229
+ "\n",
230
+ "improvement = {\n",
231
+ " \"waiting_time_improvement_pct\": pct_improve(\"avg_waiting_time\"),\n",
232
+ " \"queue_length_improvement_pct\": pct_improve(\"avg_queue_length\"),\n",
233
+ " \"throughput_gain_pct\": pct_gain(\"throughput\"),\n",
234
+ " \"ambulance_clearance_gain_pct\": pct_gain(\"ambulance_clearances\"),\n",
235
+ "}\n",
236
+ "\n",
237
+ "print(\"Baseline:\", json.dumps(baseline_metrics, indent=2))\n",
238
+ "print(\"RL:\", json.dumps(rl_metrics, indent=2))\n",
239
+ "print(\"Improvement:\", json.dumps(improvement, indent=2))"
240
+ ]
241
+ },
242
+ {
243
+ "cell_type": "code",
244
+ "execution_count": null,
245
+ "metadata": {},
246
+ "outputs": [],
247
+ "source": [
248
+ "# ==== 5) Build policy dataset from RL trajectories ====\n",
249
+ "from datasets import Dataset\n",
250
+ "\n",
251
+ "def state_to_prompt(state):\n",
252
+ " q = [int(x) for x in state[:4]]\n",
253
+ " w = [int(x) for x in state[4:8]]\n",
254
+ " phase = int(state[8])\n",
255
+ " ambulance = int(state[9])\n",
256
+ " return (\n",
257
+ " \"You control a traffic signal. Output only one action token: 0, 1, or 2.\\n\"\n",
258
+ " f\"Queues(Q1..Q4)={q}\\n\"\n",
259
+ " f\"Waiting(W1..W4)={w}\\n\"\n",
260
+ " f\"CurrentPhase={phase} AmbulanceFlag={ambulance}\\n\"\n",
261
+ " \"Action:\"\n",
262
+ " )\n",
263
+ "\n",
264
+ "def collect_policy_examples(model, env_config, episodes=30):\n",
265
+ " rows = []\n",
266
+ " for ep in range(episodes):\n",
267
+ " cfg = dict(env_config)\n",
268
+ " cfg[\"seed\"] = env_config.get(\"seed\", 42) + 1000 + ep\n",
269
+ " env = TrafficEnv(config=cfg)\n",
270
+ " s = env.reset()\n",
271
+ " done = False\n",
272
+ "\n",
273
+ " while not done:\n",
274
+ " a, _ = model.predict(s, deterministic=True)\n",
275
+ " a = int(a)\n",
276
+ " prompt = state_to_prompt(s)\n",
277
+ " text = f\"### Instruction:\\n{prompt}\\n### Response:\\n{a}\"\n",
278
+ " rows.append({\"prompt\": prompt, \"action\": str(a), \"text\": text})\n",
279
+ " s, _r, done, _info = env.step(a)\n",
280
+ "\n",
281
+ " return rows\n",
282
+ "\n",
283
+ "policy_rows = collect_policy_examples(ppo, ENV_CONFIG, episodes=35)\n",
284
+ "print(f\"Collected {len(policy_rows)} examples\")\n",
285
+ "\n",
286
+ "dataset = Dataset.from_list(policy_rows).train_test_split(test_size=0.05, seed=42)\n",
287
+ "dataset"
288
+ ]
289
+ },
290
+ {
291
+ "cell_type": "markdown",
292
+ "metadata": {},
293
+ "source": [
294
+ "## LLM Fine-Tuning (LoRA)\n",
295
+ "Default model is selected for Colab T4 stability. If OOM happens, switch to `Qwen/Qwen2.5-0.5B-Instruct`."
296
+ ]
297
+ },
298
+ {
299
+ "cell_type": "code",
300
+ "execution_count": null,
301
+ "metadata": {},
302
+ "outputs": [],
303
+ "source": [
304
+ "# ==== 6) Load base LLM + LoRA setup ====\n",
305
+ "import torch\n",
306
+ "from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig\n",
307
+ "from peft import LoraConfig, get_peft_model\n",
308
+ "\n",
309
+ "BASE_LLM = \"Qwen/Qwen2.5-1.5B-Instruct\" # recommended on Colab T4\n",
310
+ "# BASE_LLM = \"Qwen/Qwen2.5-0.5B-Instruct\" # fallback if VRAM is tight\n",
311
+ "\n",
312
+ "bnb_config = BitsAndBytesConfig(\n",
313
+ " load_in_4bit=True,\n",
314
+ " bnb_4bit_quant_type=\"nf4\",\n",
315
+ " bnb_4bit_use_double_quant=True,\n",
316
+ " bnb_4bit_compute_dtype=torch.bfloat16,\n",
317
+ ")\n",
318
+ "\n",
319
+ "tokenizer = AutoTokenizer.from_pretrained(BASE_LLM, use_fast=True)\n",
320
+ "if tokenizer.pad_token is None:\n",
321
+ " tokenizer.pad_token = tokenizer.eos_token\n",
322
+ "\n",
323
+ "llm = AutoModelForCausalLM.from_pretrained(\n",
324
+ " BASE_LLM,\n",
325
+ " device_map=\"auto\",\n",
326
+ " quantization_config=bnb_config,\n",
327
+ ")\n",
328
+ "\n",
329
+ "lora_cfg = LoraConfig(\n",
330
+ " r=16,\n",
331
+ " lora_alpha=32,\n",
332
+ " lora_dropout=0.05,\n",
333
+ " bias=\"none\",\n",
334
+ " task_type=\"CAUSAL_LM\",\n",
335
+ " target_modules=[\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\", \"gate_proj\", \"up_proj\", \"down_proj\"],\n",
336
+ ")\n",
337
+ "\n",
338
+ "llm = get_peft_model(llm, lora_cfg)\n",
339
+ "llm.print_trainable_parameters()"
340
+ ]
341
+ },
342
+ {
343
+ "cell_type": "code",
344
+ "execution_count": null,
345
+ "metadata": {},
346
+ "outputs": [],
347
+ "source": [
348
+ "# ==== 7) Fine-tune LLM on RL policy traces ====\n",
349
+ "from trl import SFTTrainer, SFTConfig\n",
350
+ "\n",
351
+ "sft_cfg = SFTConfig(\n",
352
+ " output_dir=\"artifacts/llm_lora_policy\",\n",
353
+ " max_seq_length=512,\n",
354
+ " per_device_train_batch_size=4,\n",
355
+ " gradient_accumulation_steps=4,\n",
356
+ " learning_rate=2e-4,\n",
357
+ " num_train_epochs=1,\n",
358
+ " logging_steps=20,\n",
359
+ " save_strategy=\"epoch\",\n",
360
+ " bf16=torch.cuda.is_available(),\n",
361
+ " fp16=not torch.cuda.is_available(),\n",
362
+ " report_to=\"none\",\n",
363
+ ")\n",
364
+ "\n",
365
+ "trainer = SFTTrainer(\n",
366
+ " model=llm,\n",
367
+ " args=sft_cfg,\n",
368
+ " train_dataset=dataset[\"train\"],\n",
369
+ " eval_dataset=dataset[\"test\"],\n",
370
+ " processing_class=tokenizer,\n",
371
+ " formatting_func=lambda ex: ex[\"text\"],\n",
372
+ ")\n",
373
+ "\n",
374
+ "trainer.train()\n",
375
+ "trainer.model.save_pretrained(\"artifacts/llm_lora_policy\")\n",
376
+ "tokenizer.save_pretrained(\"artifacts/llm_lora_policy\")\n",
377
+ "print(\"Saved LLM adapter -> artifacts/llm_lora_policy\")"
378
+ ]
379
+ },
380
+ {
381
+ "cell_type": "code",
382
+ "execution_count": null,
383
+ "metadata": {},
384
+ "outputs": [],
385
+ "source": [
386
+ "# ==== 8) Evaluate LLM policy ====\n",
387
+ "import re\n",
388
+ "\n",
389
+ "@torch.inference_mode()\n",
390
+ "def llm_action_from_state(state):\n",
391
+ " prompt = state_to_prompt(state)\n",
392
+ " wrapped = f\"### Instruction:\\n{prompt}\\n### Response:\\n\"\n",
393
+ " inputs = tokenizer(wrapped, return_tensors=\"pt\").to(llm.device)\n",
394
+ " out = llm.generate(\n",
395
+ " **inputs,\n",
396
+ " max_new_tokens=3,\n",
397
+ " do_sample=False,\n",
398
+ " temperature=0.0,\n",
399
+ " eos_token_id=tokenizer.eos_token_id,\n",
400
+ " )\n",
401
+ " gen = tokenizer.decode(out[0][inputs[\"input_ids\"].shape[1]:], skip_special_tokens=True).strip()\n",
402
+ " m = re.search(r\"[012]\", gen)\n",
403
+ " return int(m.group(0)) if m else 0\n",
404
+ "\n",
405
+ "llm_metrics = eval_policy(lambda s, _t: llm_action_from_state(s), ENV_CONFIG, episodes=10)\n",
406
+ "print(\"LLM metrics:\")\n",
407
+ "print(json.dumps(llm_metrics, indent=2))"
408
+ ]
409
+ },
410
+ {
411
+ "cell_type": "code",
412
+ "execution_count": null,
413
+ "metadata": {},
414
+ "outputs": [],
415
+ "source": [
416
+ "# ==== 9) Save summary ====\n",
417
+ "summary = {\n",
418
+ " \"baseline\": baseline_metrics,\n",
419
+ " \"rl\": rl_metrics,\n",
420
+ " \"llm\": llm_metrics,\n",
421
+ " \"rl_vs_baseline\": improvement,\n",
422
+ " \"base_llm\": BASE_LLM,\n",
423
+ "}\n",
424
+ "\n",
425
+ "Path(\"artifacts\").mkdir(exist_ok=True)\n",
426
+ "with open(\"artifacts/colab_training_summary.json\", \"w\") as f:\n",
427
+ " json.dump(summary, f, indent=2)\n",
428
+ "\n",
429
+ "print(json.dumps(summary, indent=2))\n",
430
+ "print(\"Saved -> artifacts/colab_training_summary.json\")"
431
+ ]
432
+ },
433
+ {
434
+ "cell_type": "markdown",
435
+ "metadata": {},
436
+ "source": [
437
+ "## Optional: push checkpoints to Hugging Face Hub\n",
438
+ "\n",
439
+ "After training, you can push:\n",
440
+ "- RL checkpoint: `artifacts/ppo_traffic.zip`\n",
441
+ "- LLM adapter: `artifacts/llm_lora_policy/`\n",
442
+ "\n",
443
+ "Use `huggingface_hub` or git-lfs depending on your target repo layout."
444
+ ]
445
+ }
446
+ ],
447
+ "metadata": {
448
+ "colab": {
449
+ "name": "train_rl_and_llm_traffic.ipynb",
450
+ "provenance": []
451
+ },
452
+ "kernelspec": {
453
+ "display_name": "Python 3",
454
+ "name": "python3"
455
+ },
456
+ "language_info": {
457
+ "name": "python"
458
+ }
459
+ },
460
+ "nbformat": 4,
461
+ "nbformat_minor": 5
462
+ }