Download models/network.py from jsantillana/vx-one: direct link, hf CLI and curl.
- Browser
- Download file 6.44 kB
-
https://huggingface.co/jsantillana/vx-one/resolve/main/models/network.py
- Command line
-
hf download hf://jsantillana/vx-one/models/network.py
-
curl -L -o network.py https://huggingface.co/jsantillana/vx-one/resolve/main/models/network.py
6.44 kB
| """VXOneNet (spec F2 Β§2): proyecciΓ³n + embeddings de texto + tronco + cabezas. | |
| Estructura por bloque: Linear β LayerNorm β GELU β Dropout(0.1); bloque 1 | |
| mapea R^{d_in} β R^w, bloques 2..L son wβw con residual h_b = h_{b-1} + | |
| bloque_b(h_{b-1}). LayerNorm (no BatchNorm): streaming por shards, batch-1 en | |
| inferencia y drift temporal (F2 Β§2.1). | |
| Cabezas por dataset (F2 Β§3.1): | |
| - UNSW: noul(1) + choice_family(10) + corn(3 condicionales) | |
| - CIC: noul(1) + choice_family(8|6) + corn + choice_ddos(4) [solo strat] | |
| - EMBER: noul(1) + choice_family(11, unknown entrenado) | |
| Dims congeladas (Β§2.2) y conteo de parΓ‘metros verificado (Β§2.3): los tests | |
| comparan contra la tabla exacta de la spec. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| import torch | |
| from torch import nn | |
| # ββ Dims congeladas por escala (F2 Β§2.2) ββββββββββββββββββββββββββββββββ | |
| SCALES: dict[str, dict[str, int]] = { | |
| "nano": {"L": 2, "w_unsw": 224, "w_cic": 224, "w_ember": 192, | |
| "d0_unsw": 112, "d0_cic": 112, "d0_ember": 20, "e": 16}, | |
| "small": {"L": 3, "w_unsw": 576, "w_cic": 576, "w_ember": 576, | |
| "d0_unsw": 288, "d0_cic": 288, "d0_ember": 96, "e": 32}, | |
| "base": {"L": 4, "w_unsw": 1280, "w_cic": 1280, "w_ember": 1280, | |
| "d0_unsw": 512, "d0_cic": 512, "d0_ember": 384, "e": 64}, | |
| } | |
| DROPOUT = 0.1 | |
| class HeadSpec: | |
| """Cabezas activas de una corrida (nombres congelados).""" | |
| n_candidates: int # choice_family (K) | |
| ddos_candidates: int = 0 # 0 = head ddos ausente | |
| has_score: bool = True # False en EMBER (S6) | |
| def names(self) -> tuple[str, ...]: | |
| ns = ["noul", "choice"] | |
| if self.has_score: | |
| ns.append("score") | |
| if self.ddos_candidates: | |
| ns.append("ddos") | |
| return tuple(ns) | |
| def head_spec_for(source: str, scale: str, split_scheme: str, | |
| n_candidates: int) -> HeadSpec: | |
| """Candidate set por corrida (D12): familias con β₯1 fila en train. | |
| La verificaciΓ³n de n_candidates ocurre en data.py; aquΓ solo se tipa. | |
| """ | |
| if source == "unsw": | |
| return HeadSpec(n_candidates=n_candidates) | |
| if source == "cic": | |
| return HeadSpec(n_candidates=n_candidates, | |
| ddos_candidates=4 if split_scheme == "strat" else 0) | |
| if source == "ember": | |
| return HeadSpec(n_candidates=n_candidates, has_score=False) | |
| raise ValueError(f"source desconocido: {source!r}") | |
| class _Block(nn.Module): | |
| def __init__(self, d_in: int, w: int) -> None: | |
| super().__init__() | |
| self.lin = nn.Linear(d_in, w) | |
| self.ln = nn.LayerNorm(w) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return nn.functional.dropout( | |
| nn.functional.gelu(self.ln(self.lin(x))), DROPOUT, | |
| training=self.training, | |
| ) | |
| class VXOneNet(nn.Module): | |
| """Motor VX-One v0.1. d_num incluye el flag de fila (d+1 ya aplicado | |
| por el feature stage: la entrada de P0 es [u ; r] β R^{d+1}).""" | |
| def __init__( | |
| self, | |
| source: str, | |
| scale: str, | |
| heads: HeadSpec, | |
| d_num: int, # d canΓ³nico (sin flag); P0 entra con d+1 | |
| vocab_sizes: dict[str, int] | None = None, # {campo: V+2} (solo UNSW) | |
| ) -> None: | |
| super().__init__() | |
| cfg = SCALES[scale] | |
| self.source = source | |
| self.scale = scale | |
| self.heads = heads | |
| if source == "ember": | |
| w, d0, L = cfg["w_ember"], cfg["d0_ember"], cfg["L"] | |
| else: | |
| w = cfg["w_unsw"] if source == "unsw" else cfg["w_cic"] | |
| d0 = cfg["d0_unsw"] if source == "unsw" else cfg["d0_cic"] | |
| L = cfg["L"] | |
| e = cfg["e"] | |
| self.vocab_sizes = vocab_sizes or {} | |
| m = len(self.vocab_sizes) | |
| # P0: Linear((d+1) β d0) β el +1 es el flag de fila (Β§2.3) | |
| self.proj = nn.Linear(d_num + 1, d0) | |
| # Embeddings lookup: una tabla por campo, (V+2) Γ e | |
| self.emb = nn.ModuleDict( | |
| {f: nn.Embedding(vs, e) for f, vs in self.vocab_sizes.items()} | |
| ) | |
| d_in = d0 + m * e | |
| # Tronco: bloque 1 d_inβw, bloques 2..L wβw con residual | |
| self.blocks = nn.ModuleList([_Block(d_in, w)] + [_Block(w, w) for _ in range(L - 1)]) | |
| # Cabezas (Β§2.3): Noul w+1; Choice wK+K; CORN w+3; ddos 4w+4 | |
| self.noul = nn.Linear(w, 1) | |
| self.choice = nn.Linear(w, heads.n_candidates) | |
| self.has_score = heads.has_score | |
| if heads.has_score: | |
| self.corn_u = nn.Linear(w, 1, bias=False) # u^T h | |
| self.corn_b = nn.Parameter(torch.tensor([1.0, 0.0, -1.0])) # init Β§2.1 | |
| self.has_ddos = heads.ddos_candidates > 0 | |
| if self.has_ddos: | |
| self.ddos = nn.Linear(w, heads.ddos_candidates) | |
| # ββ forward ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def trunk(self, u_r: torch.Tensor, txt_slots: dict[str, torch.Tensor] | None = None) -> torch.Tensor: | |
| h = self.proj(u_r) | |
| if self.emb: | |
| parts = [h] | |
| for f, tab in self.emb.items(): | |
| parts.append(tab(txt_slots[f])) | |
| h = torch.cat(parts, dim=1) | |
| for i, block in enumerate(self.blocks): | |
| h = h + block(h) if i > 0 else block(h) | |
| return h | |
| def forward(self, u_r: torch.Tensor, txt_slots: dict[str, torch.Tensor] | None = None) -> dict[str, torch.Tensor]: | |
| """u_r float32 [B, d+1]; txt_slots {campo: int64 [B]} (solo UNSW).""" | |
| h = self.trunk(u_r, txt_slots) | |
| out: dict[str, torch.Tensor] = { | |
| "noul": self.noul(h).squeeze(-1), # logits [B] | |
| "choice": self.choice(h), # logits [B, K] | |
| } | |
| if self.has_score: | |
| out["corn_logits"] = (self.corn_u(h).squeeze(-1).unsqueeze(1) | |
| + self.corn_b.unsqueeze(0)) # [B, 3] | |
| if self.has_ddos: | |
| out["ddos"] = self.ddos(h) # logits [B, 4] | |
| return out | |
| # ββ conteo de parΓ‘metros (contrato Β§2.3; ApΓ©ndice B) βββββββββββββ | |
| def n_parameters(self) -> int: | |
| return sum(p.numel() for p in self.parameters()) | |