topoexploit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- topoexploit-0.1.0.dist-info/METADATA +523 -0
- topoexploit-0.1.0.dist-info/RECORD +23 -0
- topoexploit-0.1.0.dist-info/WHEEL +5 -0
- topoexploit-0.1.0.dist-info/entry_points.txt +6 -0
- topoexploit-0.1.0.dist-info/licenses/LICENSE +661 -0
- topoexploit-0.1.0.dist-info/top_level.txt +1 -0
- topogpt3/__init__.py +125 -0
- topogpt3/__main__.py +49 -0
- topogpt3/api_server.py +1178 -0
- topogpt3/continuation.py +114 -0
- topogpt3/ewc.py +369 -0
- topogpt3/exploitgym_config.py +130 -0
- topogpt3/exploitgym_loader.py +416 -0
- topogpt3/hodge_cm.py +160 -0
- topogpt3/inference.py +731 -0
- topogpt3/inference_hrm.py +1502 -0
- topogpt3/jlens.py +958 -0
- topogpt3/lens_model.py +389 -0
- topogpt3/merged_config.py +187 -0
- topogpt3/model.py +3824 -0
- topogpt3/structured_output.py +251 -0
- topogpt3/train.py +1939 -0
- topogpt3/vanilla_control.py +240 -0
topogpt3/lens_model.py
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from types import SimpleNamespace
|
|
8
|
+
from typing import Any, Protocol
|
|
9
|
+
|
|
10
|
+
import torch
|
|
11
|
+
from torch import nn
|
|
12
|
+
from safetensors.torch import load_file
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"LensModel",
|
|
17
|
+
"TopoGPT3LensConfig",
|
|
18
|
+
"TopoGPT3LensModel",
|
|
19
|
+
"TinyDecoder",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LensModel(Protocol):
|
|
24
|
+
"""What the lens needs from a model.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
n_layers: Number of residual blocks.
|
|
28
|
+
d_model: Residual-stream width.
|
|
29
|
+
layers: The residual blocks, indexable by integer; what
|
|
30
|
+
ActivationRecorder hooks.
|
|
31
|
+
tokenizer: Tokenizer used by the visualisation helpers; must provide
|
|
32
|
+
``decode(token_ids) -> str``. Fitting and apply() never touch it.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
n_layers: int
|
|
36
|
+
d_model: int
|
|
37
|
+
layers: Sequence[nn.Module]
|
|
38
|
+
tokenizer: Any
|
|
39
|
+
|
|
40
|
+
def encode(self, text: str, *, max_length: int = ...) -> torch.Tensor:
|
|
41
|
+
"""Tokenize ``text`` to ``input_ids`` of shape ``[1, seq_len]`` on the
|
|
42
|
+
model's input device."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
def forward(self, input_ids: torch.Tensor) -> Any:
|
|
46
|
+
"""Run the residual stack on ``input_ids`` (no LM head). Must build an
|
|
47
|
+
autograd graph through layers when grad is enabled, and must be
|
|
48
|
+
deterministic across batch elements (eval mode, dropout off) --- the
|
|
49
|
+
fitting estimator replicates the prompt along the batch axis."""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
def unembed(self, residual: torch.Tensor) -> torch.Tensor:
|
|
53
|
+
"""Map a residual-stream tensor ``[..., d_model]`` to logits
|
|
54
|
+
``[..., vocab_size]`` (final norm + LM head)."""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class TopoGPT3LensConfig:
|
|
60
|
+
"""Centralized configuration for the TopoGPT3 lens model adapter.
|
|
61
|
+
|
|
62
|
+
Every value consumed downstream resides here. Adding a new tunable means
|
|
63
|
+
extending this class; no other module should embed literals.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
d_model: int = 256
|
|
67
|
+
n_layers: int = 6
|
|
68
|
+
n_heads: int = 8
|
|
69
|
+
n_kv_heads: int = 0
|
|
70
|
+
vocab_size: int = 50257
|
|
71
|
+
max_seq_len: int = 256
|
|
72
|
+
dropout: float = 0.1
|
|
73
|
+
device: str = field(
|
|
74
|
+
default_factory=lambda: "cuda" if torch.cuda.is_available() else "cpu"
|
|
75
|
+
)
|
|
76
|
+
encoding: str = "gpt2"
|
|
77
|
+
scale: str = "small"
|
|
78
|
+
moe_enabled: bool = True
|
|
79
|
+
n_experts: int = 4
|
|
80
|
+
moe_top_k: int = 2
|
|
81
|
+
gradient_checkpointing: bool = True
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def from_topogpt2_config(cls, cfg: Any) -> TopoGPT3LensConfig:
|
|
85
|
+
"""Construct a lens config from a TopoGPT2Config dataclass."""
|
|
86
|
+
return cls(
|
|
87
|
+
d_model=cfg.D_MODEL,
|
|
88
|
+
n_layers=cfg.N_LAYERS,
|
|
89
|
+
n_heads=cfg.N_HEADS,
|
|
90
|
+
n_kv_heads=getattr(cfg, "N_KV_HEADS", 0),
|
|
91
|
+
vocab_size=cfg.VOCAB_SIZE,
|
|
92
|
+
max_seq_len=cfg.MAX_SEQ_LEN,
|
|
93
|
+
dropout=cfg.DROPOUT,
|
|
94
|
+
device=cfg.DEVICE,
|
|
95
|
+
encoding=getattr(cfg, "ENCODING", "gpt2"),
|
|
96
|
+
scale=cfg.SCALE,
|
|
97
|
+
moe_enabled=cfg.MOE_ENABLED,
|
|
98
|
+
n_experts=cfg.N_EXPERTS,
|
|
99
|
+
moe_top_k=cfg.MOE_TOP_K,
|
|
100
|
+
gradient_checkpointing=cfg.GRADIENT_CHECKPOINTING,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def probe_checkpoint(
|
|
105
|
+
cls,
|
|
106
|
+
checkpoint_dir: str = "checkpoints_topogpt3",
|
|
107
|
+
*,
|
|
108
|
+
state_filename: str = "state.json",
|
|
109
|
+
) -> TopoGPT3LensConfig:
|
|
110
|
+
"""Probe a checkpoint directory and infer lens config from state.json.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
checkpoint_dir: Path to the checkpoint slot directory.
|
|
114
|
+
state_filename: JSON file containing training config.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
A TopoGPT3LensConfig matching the checkpoint.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
FileNotFoundError: If state.json is missing.
|
|
121
|
+
ValueError: If required fields are absent from the state.
|
|
122
|
+
"""
|
|
123
|
+
state_path = Path(checkpoint_dir) / state_filename
|
|
124
|
+
if not state_path.exists():
|
|
125
|
+
raise FileNotFoundError(
|
|
126
|
+
f"State file not found at {state_path}. "
|
|
127
|
+
f"Provide a valid checkpoint directory."
|
|
128
|
+
)
|
|
129
|
+
with open(state_path) as f:
|
|
130
|
+
state = json.load(f)
|
|
131
|
+
raw = state.get("config", state)
|
|
132
|
+
scale = raw.get("SCALE", "small")
|
|
133
|
+
from .model import TopoGPT2Config
|
|
134
|
+
cfg = TopoGPT2Config(SCALE=scale, DEVICE="cpu")
|
|
135
|
+
for k, v in cfg.__dataclass_fields__.items():
|
|
136
|
+
if k in raw and isinstance(raw[k], type(getattr(cfg, k, None))):
|
|
137
|
+
setattr(cfg, k, raw[k])
|
|
138
|
+
cfg.__post_init__()
|
|
139
|
+
return cls.from_topogpt2_config(cfg)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class _TopoGPT3ResidualForward(nn.Module):
|
|
143
|
+
"""Runs the residual block stack only (no final norm, no LM head).
|
|
144
|
+
|
|
145
|
+
This is the forward subgraph that ActivationRecorder hooks capture.
|
|
146
|
+
Extracted from TopoGPT2.forward() to expose the residual stream for
|
|
147
|
+
Jacobian lens fitting and application.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def __init__(self, model: nn.Module) -> None:
|
|
151
|
+
super().__init__()
|
|
152
|
+
self._model = model
|
|
153
|
+
|
|
154
|
+
def forward(self, input_ids: torch.Tensor) -> torch.Tensor:
|
|
155
|
+
x = self._model.token_embed(input_ids)
|
|
156
|
+
for layer in self._model.layers:
|
|
157
|
+
x, _aux_loss, _kv_cache = layer(x, past_kv=None)
|
|
158
|
+
return x
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class TopoGPT3LensModel(nn.Module):
|
|
162
|
+
"""LensModel adapter over a loaded TopoGPT2 model.
|
|
163
|
+
|
|
164
|
+
Wraps a TopoGPT2 instance and implements the LensModel protocol for use
|
|
165
|
+
with ActivationRecorder, JacobianLens fitting, and apply().
|
|
166
|
+
|
|
167
|
+
The adapter owns no parameters --- all weights live in the wrapped model.
|
|
168
|
+
Call ``.eval()`` and set ``requires_grad_(False)`` on the wrapped model
|
|
169
|
+
before fitting.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def __init__(
|
|
173
|
+
self,
|
|
174
|
+
model: nn.Module,
|
|
175
|
+
tokenizer: Any | None = None,
|
|
176
|
+
) -> None:
|
|
177
|
+
super().__init__()
|
|
178
|
+
self._model = model
|
|
179
|
+
self._residual_forward = _TopoGPT3ResidualForward(model)
|
|
180
|
+
self._tokenizer = tokenizer
|
|
181
|
+
self._input_device: torch.device | None = None
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def n_layers(self) -> int:
|
|
185
|
+
return len(self._model.layers)
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def d_model(self) -> int:
|
|
189
|
+
return self._model.config.D_MODEL
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def layers(self) -> nn.ModuleList:
|
|
193
|
+
return self._model.layers
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def tokenizer(self) -> Any:
|
|
197
|
+
return self._tokenizer
|
|
198
|
+
|
|
199
|
+
@tokenizer.setter
|
|
200
|
+
def tokenizer(self, tok: Any) -> None:
|
|
201
|
+
self._tokenizer = tok
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def input_device(self) -> torch.device:
|
|
205
|
+
if self._input_device is not None:
|
|
206
|
+
return self._input_device
|
|
207
|
+
return self._model.token_embed.weight.device
|
|
208
|
+
|
|
209
|
+
@input_device.setter
|
|
210
|
+
def input_device(self, device: torch.device) -> None:
|
|
211
|
+
self._input_device = device
|
|
212
|
+
|
|
213
|
+
def encode(self, text: str, *, max_length: int = 512) -> torch.Tensor:
|
|
214
|
+
"""Tokenize text to input_ids of shape ``[1, seq_len]``.
|
|
215
|
+
|
|
216
|
+
Uses BPETokenizer if available, otherwise falls back to a byte-level
|
|
217
|
+
encoding compatible with GPT-2 BPE tokenization.
|
|
218
|
+
"""
|
|
219
|
+
if self._tokenizer is not None:
|
|
220
|
+
ids = self._tokenizer.encode(text)
|
|
221
|
+
if max_length and len(ids) > max_length:
|
|
222
|
+
ids = ids[:max_length]
|
|
223
|
+
return torch.tensor([ids], dtype=torch.long, device=self.input_device)
|
|
224
|
+
encoded = text.encode("utf-8")[: max_length - 1] if max_length else text.encode("utf-8")
|
|
225
|
+
ids = [0] + [1 + (b % 30) for b in encoded]
|
|
226
|
+
return torch.tensor([ids], dtype=torch.long, device=self.input_device)
|
|
227
|
+
|
|
228
|
+
def forward(self, input_ids: torch.Tensor) -> torch.Tensor:
|
|
229
|
+
"""Run the residual stack on ``input_ids``.
|
|
230
|
+
|
|
231
|
+
Returns hidden states of shape ``[batch, seq_len, d_model]``
|
|
232
|
+
(pre-final-norm, pre-LM-head). The autograd graph is retained through
|
|
233
|
+
all layers when grad is enabled.
|
|
234
|
+
"""
|
|
235
|
+
return self._residual_forward(input_ids)
|
|
236
|
+
|
|
237
|
+
def unembed(self, residual: torch.Tensor) -> torch.Tensor:
|
|
238
|
+
"""Map residual ``[..., d_model]`` to logits ``[..., vocab_size]``.
|
|
239
|
+
|
|
240
|
+
Applies the model's final norm and LM head projection.
|
|
241
|
+
"""
|
|
242
|
+
x = self._model.final_norm(residual.to(dtype=self._model.lm_head.weight.dtype))
|
|
243
|
+
return self._model.lm_head(x.to(device=self._model.lm_head.weight.device))
|
|
244
|
+
|
|
245
|
+
@classmethod
|
|
246
|
+
def from_checkpoint(
|
|
247
|
+
cls,
|
|
248
|
+
checkpoint_dir: str = "checkpoints_topogpt3",
|
|
249
|
+
*,
|
|
250
|
+
device: str | None = None,
|
|
251
|
+
encoding: str = "gpt2",
|
|
252
|
+
strict: bool = False,
|
|
253
|
+
) -> TopoGPT3LensModel:
|
|
254
|
+
"""Build a TopoGPT3LensModel from a checkpoint directory.
|
|
255
|
+
|
|
256
|
+
Probes state.json for configuration, instantiates the model, loads
|
|
257
|
+
safetensors weights, and wraps the result.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
checkpoint_dir: Path to the checkpoint slot directory.
|
|
261
|
+
device: Target device. Defaults to cuda if available else cpu.
|
|
262
|
+
encoding: Tokenizer encoding name (passed to BPETokenizer).
|
|
263
|
+
strict: Whether to enforce strict state dict loading.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
A TopoGPT3LensModel in eval mode with requires_grad_(False).
|
|
267
|
+
|
|
268
|
+
Raises:
|
|
269
|
+
FileNotFoundError: If model.safetensors or state.json is missing.
|
|
270
|
+
"""
|
|
271
|
+
from .model import BPETokenizer, TopoGPT2, TopoGPT2Config
|
|
272
|
+
|
|
273
|
+
device = device or ("cuda" if torch.cuda.is_available() else "cpu")
|
|
274
|
+
resolved = Path(checkpoint_dir)
|
|
275
|
+
weights_path = resolved / "model.safetensors"
|
|
276
|
+
|
|
277
|
+
if not weights_path.exists():
|
|
278
|
+
raise FileNotFoundError(
|
|
279
|
+
f"Weights not found at {weights_path}. "
|
|
280
|
+
f"Download from https://huggingface.co/grisiscomeback/TopoGPT3"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
lens_cfg = TopoGPT3LensConfig.probe_checkpoint(str(resolved))
|
|
284
|
+
model_cfg = TopoGPT2Config(
|
|
285
|
+
SCALE=lens_cfg.scale,
|
|
286
|
+
DEVICE=device,
|
|
287
|
+
)
|
|
288
|
+
model_cfg.__post_init__()
|
|
289
|
+
|
|
290
|
+
model = TopoGPT2(model_cfg)
|
|
291
|
+
state_dict = load_file(str(weights_path), device=device)
|
|
292
|
+
missing, unexpected = model.load_state_dict(state_dict, strict=strict)
|
|
293
|
+
model.to(device)
|
|
294
|
+
model.eval()
|
|
295
|
+
|
|
296
|
+
tokenizer = BPETokenizer(encoding=encoding)
|
|
297
|
+
instance = cls(model, tokenizer=tokenizer)
|
|
298
|
+
instance.input_device = torch.device(device)
|
|
299
|
+
|
|
300
|
+
for param in model.parameters():
|
|
301
|
+
param.requires_grad_(False)
|
|
302
|
+
|
|
303
|
+
return instance
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class TinyDecoder(nn.Module):
|
|
307
|
+
"""A tiny CPU-only decoder for end-to-end tests.
|
|
308
|
+
|
|
309
|
+
Implements the LensModel protocol indirectly (wrapped by
|
|
310
|
+
TopoGPT3LensModel). Residual blocks are ``h + 0.1 * linear(h)``:
|
|
311
|
+
the small gain keeps the Jacobian well-conditioned so the late-layer
|
|
312
|
+
``diag(J) ~= 1`` property holds.
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
def __init__(
|
|
316
|
+
self, n_layers: int = 4, d_model: int = 8, vocab_size: int = 32, seed: int = 0
|
|
317
|
+
) -> None:
|
|
318
|
+
super().__init__()
|
|
319
|
+
torch.manual_seed(seed)
|
|
320
|
+
config = SimpleNamespace(
|
|
321
|
+
D_MODEL=d_model,
|
|
322
|
+
N_LAYERS=n_layers,
|
|
323
|
+
N_HEADS=1,
|
|
324
|
+
N_KV_HEADS=1,
|
|
325
|
+
VOCAB_SIZE=vocab_size,
|
|
326
|
+
MAX_SEQ_LEN=128,
|
|
327
|
+
DROPOUT=0.0,
|
|
328
|
+
DEVICE="cpu",
|
|
329
|
+
SCALE="custom",
|
|
330
|
+
MOE_ENABLED=False,
|
|
331
|
+
N_EXPERTS=1,
|
|
332
|
+
MOE_TOP_K=1,
|
|
333
|
+
GRADIENT_CHECKPOINTING=False,
|
|
334
|
+
)
|
|
335
|
+
self.config = config
|
|
336
|
+
self.token_embed = nn.Embedding(vocab_size, d_model)
|
|
337
|
+
self.layers = nn.ModuleList(
|
|
338
|
+
[_ResidualBlock(d_model) for _ in range(n_layers)]
|
|
339
|
+
)
|
|
340
|
+
self.final_norm = nn.LayerNorm(d_model)
|
|
341
|
+
self.lm_head = nn.Linear(d_model, vocab_size, bias=False)
|
|
342
|
+
self.lm_head.weight = self.token_embed.weight
|
|
343
|
+
|
|
344
|
+
def forward(
|
|
345
|
+
self, token_ids: torch.Tensor, past_kvs: Any = None
|
|
346
|
+
) -> tuple[torch.Tensor, torch.Tensor, list]:
|
|
347
|
+
x = self.token_embed(token_ids)
|
|
348
|
+
new_kvs: list = []
|
|
349
|
+
total_aux = torch.tensor(0.0, device=x.device)
|
|
350
|
+
for layer in self.layers:
|
|
351
|
+
x, aux, _ = layer(x, past_kv=None)
|
|
352
|
+
total_aux = total_aux + aux
|
|
353
|
+
new_kvs.append(None)
|
|
354
|
+
x = self.final_norm(x)
|
|
355
|
+
logits = self.lm_head(x)
|
|
356
|
+
return logits, total_aux / len(self.layers), new_kvs
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class _ResidualBlock(nn.Module):
|
|
360
|
+
def __init__(self, d_model: int) -> None:
|
|
361
|
+
super().__init__()
|
|
362
|
+
self.linear = nn.Linear(d_model, d_model, bias=False)
|
|
363
|
+
with torch.no_grad():
|
|
364
|
+
self.linear.weight.mul_(0.1)
|
|
365
|
+
|
|
366
|
+
def forward(
|
|
367
|
+
self, x: torch.Tensor, past_kv: Any = None
|
|
368
|
+
) -> tuple[torch.Tensor, torch.Tensor, None]:
|
|
369
|
+
return x + self.linear(x), torch.tensor(0.0), None
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
if __name__ == "__main__":
|
|
373
|
+
import time
|
|
374
|
+
cfg = TopoGPT3LensConfig.probe_checkpoint("checkpoints_topogpt3/last")
|
|
375
|
+
device = "cpu"
|
|
376
|
+
import torch
|
|
377
|
+
t0 = time.perf_counter()
|
|
378
|
+
model = TopoGPT3LensModel.from_checkpoint(
|
|
379
|
+
"checkpoints_topogpt3/last", device=device
|
|
380
|
+
)
|
|
381
|
+
t1 = time.perf_counter()
|
|
382
|
+
n_params = sum(p.numel() for p in model._model.parameters())
|
|
383
|
+
print(f"TopoGPT3LensModel: n_layers={model.n_layers}, d_model={model.d_model}, params={n_params:,}, load={t1-t0:.1f}s")
|
|
384
|
+
ids = model.encode("def fibonacci(n):", max_length=32)
|
|
385
|
+
with torch.no_grad():
|
|
386
|
+
hidden = model.forward(ids)
|
|
387
|
+
logits = model.unembed(hidden)
|
|
388
|
+
print(f"encode: {ids.shape} forward: {hidden.shape} unembed: {logits.shape}")
|
|
389
|
+
print("Model is ready. Use: python3 -m topogpt3.jlens --prompt 'def hello('")
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
TopoMerged: 6-tier curriculum merging TopoGPT3 code tasks with ExploitGym exploit tasks.
|
|
4
|
+
|
|
5
|
+
Tier structure:
|
|
6
|
+
Phase 1 — Code foundation (from TopoGPT3):
|
|
7
|
+
Tier 1: codealpaca (128 seq, 2 epochs) — short instructions
|
|
8
|
+
Tier 2: code_feedback (192 seq, 2 epochs) — step-by-step explanations
|
|
9
|
+
Tier 3: magicoder_evol (256 seq, 3 epochs) — complex problems
|
|
10
|
+
|
|
11
|
+
Phase 2 — Exploit specialization (from ExploitGym):
|
|
12
|
+
Tier 4: vulnerability_analysis (256 seq, 4 epochs) — analyze CVEs
|
|
13
|
+
Tier 5: patch_analysis (512 seq, 3 epochs) — understand patches
|
|
14
|
+
Tier 6: exploit_development (768 seq, 4 epochs) — write exploits
|
|
15
|
+
|
|
16
|
+
The model learns to code first, then applies coding skill to security.
|
|
17
|
+
|
|
18
|
+
Author: Gris Iscomeback
|
|
19
|
+
License: GPL v3
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from typing import Optional, Tuple
|
|
26
|
+
|
|
27
|
+
from .model import TopoGPT2Config
|
|
28
|
+
from .exploitgym_loader import ExploitGymLoaderConfig
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class TopoMergedConfig:
|
|
33
|
+
"""Configuration for TopoMerged: 6-tier code+exploit curriculum."""
|
|
34
|
+
|
|
35
|
+
# --- Model: xlarge (~150M params) ---
|
|
36
|
+
SCALE: str = "xlarge"
|
|
37
|
+
DEVICE: str = "cuda"
|
|
38
|
+
RANDOM_SEED: int = 42
|
|
39
|
+
USE_AMP: bool = True
|
|
40
|
+
GRADIENT_CHECKPOINTING: bool = True
|
|
41
|
+
|
|
42
|
+
# --- Training hyperparams (tuned for ~150M on 6GB VRAM) ---
|
|
43
|
+
BATCH_SIZE: int = 2
|
|
44
|
+
GRAD_ACCUM_STEPS: int = 16
|
|
45
|
+
LEARNING_RATE: float = 2e-4
|
|
46
|
+
WEIGHT_DECAY: float = 0.1
|
|
47
|
+
GRADIENT_CLIP_NORM: float = 1.0
|
|
48
|
+
WARMUP_RATIO: float = 0.05
|
|
49
|
+
|
|
50
|
+
# --- 6-Tier Curriculum ---
|
|
51
|
+
# Phase 1 (code): shorter seq, fewer epochs — build coding foundation
|
|
52
|
+
# Phase 2 (exploit): longer seq, more epochs — apply to security
|
|
53
|
+
TIER_EPOCHS: Tuple[int, int, int, int, int, int] = (2, 2, 3, 4, 3, 4)
|
|
54
|
+
TIER_SEQ_LEN: Tuple[int, int, int, int, int, int] = (128, 192, 256, 256, 512, 768)
|
|
55
|
+
TIER_NAMES: Tuple[str, str, str, str, str, str] = (
|
|
56
|
+
"codealpaca",
|
|
57
|
+
"code_feedback",
|
|
58
|
+
"magicoder_evol",
|
|
59
|
+
"vulnerability_analysis",
|
|
60
|
+
"patch_analysis",
|
|
61
|
+
"exploit_development",
|
|
62
|
+
)
|
|
63
|
+
TIER_SOURCES: Tuple[str, str, str, str, str, str] = (
|
|
64
|
+
"topogpt3", # HuggingFace dataset
|
|
65
|
+
"topogpt3", # HuggingFace dataset
|
|
66
|
+
"topogpt3", # HuggingFace dataset
|
|
67
|
+
"exploitgym", # ExploitGym repo
|
|
68
|
+
"exploitgym", # ExploitGym repo
|
|
69
|
+
"exploitgym", # ExploitGym repo
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# --- Progressive window sizes (sliding window attention) ---
|
|
73
|
+
PROGRESSIVE_WINDOW: Tuple[int, int, int, int, int, int] = (64, 96, 128, 128, 256, 256)
|
|
74
|
+
|
|
75
|
+
# --- Datasets (HuggingFace IDs for code tiers) ---
|
|
76
|
+
HF_CODEALPACA: str = "sahil2801/CodeAlpaca-20k"
|
|
77
|
+
HF_CODE_FEEDBACK: str = "m-a-p/CodeFeedback-Filtered-Instruction"
|
|
78
|
+
HF_MAGICODER: str = "ise-uiuc/Magicoder-Evol-Instruct-110K"
|
|
79
|
+
|
|
80
|
+
# --- ExploitGym data ---
|
|
81
|
+
DATA_CACHE_EXPLOIT: str = "data_exploitgym"
|
|
82
|
+
REPO_CACHE: str = ".exploitgym_repo"
|
|
83
|
+
|
|
84
|
+
# --- Splits ---
|
|
85
|
+
VAL_FRACTION: float = 0.05
|
|
86
|
+
HOLDOUT_FRACTION: float = 0.05
|
|
87
|
+
SPLIT_SEED: int = 1337
|
|
88
|
+
|
|
89
|
+
# --- Tokens per tier (limits) ---
|
|
90
|
+
MAX_TOKENS_PER_TIER: Tuple[int, int, int, int, int, int] = (
|
|
91
|
+
20_000_000, # CodeAlpaca
|
|
92
|
+
80_000_000, # CodeFeedback
|
|
93
|
+
100_000_000, # Magicoder
|
|
94
|
+
50_000_000, # vulnerability_analysis
|
|
95
|
+
80_000_000, # patch_analysis
|
|
96
|
+
100_000_000, # exploit_development
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# --- Grassmannian tracking ---
|
|
100
|
+
GRASS_TRACK_EVERY: int = 200
|
|
101
|
+
GRASS_MAX_RANK: int = 16
|
|
102
|
+
GRASS_ELBOW_RATIO: float = 0.05
|
|
103
|
+
GRASS_FISHER_BATCHES: int = 40
|
|
104
|
+
GRASS_FISHER_GRADS: int = 64
|
|
105
|
+
|
|
106
|
+
# --- Gauss optimization ---
|
|
107
|
+
USE_GAUSS_COMPLEX_MULT: bool = True
|
|
108
|
+
|
|
109
|
+
# --- Limits ---
|
|
110
|
+
MAX_TRAIN_BATCHES_PER_EPOCH: int = 0 # 0 = no limit
|
|
111
|
+
MAX_EVAL_BATCHES: int = 200
|
|
112
|
+
|
|
113
|
+
# --- Anti-forgetting: EWC + Replay ---
|
|
114
|
+
USE_EWC: bool = True
|
|
115
|
+
EWC_LAMBDA: float = 5000.0
|
|
116
|
+
EWC_FISHER_BATCHES: int = 50
|
|
117
|
+
USE_REPLAY: bool = True
|
|
118
|
+
REPLAY_RATIO: float = 0.25
|
|
119
|
+
REPLAY_MAX_TIERS: int = 5 # remember all previous tiers
|
|
120
|
+
|
|
121
|
+
# --- Phase transition: stronger anti-forgetting at code→exploit boundary ---
|
|
122
|
+
PHASE_BOUNDARY: int = 3 # tier index where phase 2 starts (0-indexed)
|
|
123
|
+
EWC_PHASE2_SCALE: float = 0.3 # reduced EWC in phase 2 (allow more plasticity)
|
|
124
|
+
|
|
125
|
+
# --- Paths ---
|
|
126
|
+
DATA_CACHE_TOPOGPT3: str = "data_topogpt3"
|
|
127
|
+
DATA_CACHE_EXPLOITGYM: str = "data_exploitgym"
|
|
128
|
+
CHECKPOINT_DIR: str = "checkpoints_topomerged"
|
|
129
|
+
LOG_FILE: str = "topomerged_history.jsonl"
|
|
130
|
+
|
|
131
|
+
# --- Logging ---
|
|
132
|
+
LOG_INTERVAL_STEPS: int = 50
|
|
133
|
+
CHECKPOINT_EVERY_MIN: int = 15
|
|
134
|
+
MAX_CHECKPOINTS: int = 5
|
|
135
|
+
|
|
136
|
+
# --- Efficiency metrics ---
|
|
137
|
+
EFFICIENCY_THROUGHPUT_BATCHES: int = 20
|
|
138
|
+
|
|
139
|
+
def build_topogpt2_config(self, max_seq_len: int,
|
|
140
|
+
attn_window: Optional[int] = None) -> TopoGPT2Config:
|
|
141
|
+
cfg = TopoGPT2Config(
|
|
142
|
+
DEVICE=self.DEVICE,
|
|
143
|
+
RANDOM_SEED=self.RANDOM_SEED,
|
|
144
|
+
USE_AMP=self.USE_AMP,
|
|
145
|
+
SCALE=self.SCALE,
|
|
146
|
+
BATCH_SIZE=self.BATCH_SIZE,
|
|
147
|
+
GRAD_ACCUM_STEPS=self.GRAD_ACCUM_STEPS,
|
|
148
|
+
LEARNING_RATE=self.LEARNING_RATE,
|
|
149
|
+
WEIGHT_DECAY=self.WEIGHT_DECAY,
|
|
150
|
+
GRADIENT_CLIP_NORM=self.GRADIENT_CLIP_NORM,
|
|
151
|
+
WARMUP_RATIO=self.WARMUP_RATIO,
|
|
152
|
+
GRADIENT_CHECKPOINTING=self.GRADIENT_CHECKPOINTING,
|
|
153
|
+
CHECKPOINT_DIR=self.CHECKPOINT_DIR,
|
|
154
|
+
DATA_DIR=self.DATA_CACHE_TOPOGPT3,
|
|
155
|
+
)
|
|
156
|
+
cfg.MAX_SEQ_LEN = max_seq_len
|
|
157
|
+
if attn_window is not None and attn_window > 0:
|
|
158
|
+
cfg.ATTN_WINDOW = attn_window
|
|
159
|
+
return cfg
|
|
160
|
+
|
|
161
|
+
def build_exploit_loader_config(self) -> ExploitGymLoaderConfig:
|
|
162
|
+
return ExploitGymLoaderConfig(
|
|
163
|
+
data_cache=self.DATA_CACHE_EXPLOITGYM,
|
|
164
|
+
repo_cache=self.REPO_CACHE,
|
|
165
|
+
max_tokens_per_tier=(
|
|
166
|
+
self.MAX_TOKENS_PER_TIER[3],
|
|
167
|
+
self.MAX_TOKENS_PER_TIER[4],
|
|
168
|
+
self.MAX_TOKENS_PER_TIER[5],
|
|
169
|
+
),
|
|
170
|
+
val_fraction=self.VAL_FRACTION,
|
|
171
|
+
holdout_fraction=self.HOLDOUT_FRACTION,
|
|
172
|
+
split_seed=self.RANDOM_SEED,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def is_code_tier(self, tier_index: int) -> bool:
|
|
176
|
+
"""Returns True if the tier uses TopoGPT3 (HuggingFace) data."""
|
|
177
|
+
return self.TIER_SOURCES[tier_index] == "topogpt3"
|
|
178
|
+
|
|
179
|
+
def is_exploit_tier(self, tier_index: int) -> bool:
|
|
180
|
+
"""Returns True if the tier uses ExploitGym data."""
|
|
181
|
+
return self.TIER_SOURCES[tier_index] == "exploitgym"
|
|
182
|
+
|
|
183
|
+
def exploit_tier_index(self, tier_index: int) -> Optional[int]:
|
|
184
|
+
"""Convert global tier index to exploitgym-local index (0-2)."""
|
|
185
|
+
if not self.is_exploit_tier(tier_index):
|
|
186
|
+
return None
|
|
187
|
+
return tier_index - self.PHASE_BOUNDARY
|