DibaGuard / runtime /go /seqnet.go
DibaAi's picture
DibaGuard: public licence-clean release (Dibachain)
5d97275 verified
Raw History Blame Contribute Delete
5.64 kB
package dibaguard
import (
"fmt"
"math"
)
// SeqNet is the tier-2 sequence network (dibaguard/flows/seqnet.py): event encoder -> causal GRU (one hidden
// state per source, one step per flow) -> site-conditioned head. Weights come from the flat export of seqnet.npz.
type SeqNet struct {
EmbPort, EmbProto []float32 // [PortVocab*16], [ProtoVocab*4]
Enc0W, Enc0B, Enc1W, Enc1B []float32 // [D*In], [D], [D*D], [D]
WIH, WHH, BIH, BHH [][]float32
CtxW, CtxB []float32 // [2D*D], [2D] (nil without context)
Head0W, Head0B []float32 // [64*D], [64]
Head1W, Head1B []float32 // [C*64], [C]
Gamma, Beta []float32 // [D] from the active site context
D, In, Layers, C, NumIn int
PortVocab, ProtoVocab int
Temp float64
Info map[string]any
}
// LoadSeqNet reads a flat export of SeqNet and applies the shipped default context when present.
func LoadSeqNet(path string) (*SeqNet, error) {
f, err := LoadFlat(path)
if err != nil {
return nil, err
}
m := &SeqNet{Info: f.Info, Temp: 1}
get := func(name string) ([]float32, []int) {
if err != nil {
return nil, nil
}
var a []float32
var s []int
a, s, err = f.Float32s(name)
return a, s
}
var shp []int
m.EmbPort, shp = get("emb_port")
if shp != nil {
m.PortVocab = shp[0]
}
m.EmbProto, shp = get("emb_proto")
if shp != nil {
m.ProtoVocab = shp[0]
}
m.Enc0W, shp = get("enc.0.w")
if shp != nil {
m.D, m.In = shp[0], shp[1]
m.NumIn = m.In - 20
}
m.Enc0B, _ = get("enc.0.b")
m.Enc1W, _ = get("enc.1.w")
m.Enc1B, _ = get("enc.1.b")
for l := 0; ; l++ {
if _, ok := f.arrays[fmt.Sprintf("gru.l%d.w_hh", l)]; !ok {
break
}
wih, _ := get(fmt.Sprintf("gru.l%d.w_ih", l))
whh, _ := get(fmt.Sprintf("gru.l%d.w_hh", l))
bih, _ := get(fmt.Sprintf("gru.l%d.b_ih", l))
bhh, _ := get(fmt.Sprintf("gru.l%d.b_hh", l))
m.WIH, m.WHH, m.BIH, m.BHH = append(m.WIH, wih), append(m.WHH, whh), append(m.BIH, bih), append(m.BHH, bhh)
m.Layers++
}
m.Head0W, _ = get("head.0.w")
m.Head0B, _ = get("head.0.b")
m.Head1W, shp = get("head.1.w")
if shp != nil {
m.C = shp[0]
}
m.Head1B, _ = get("head.1.b")
if _, ok := f.arrays["ctx.w"]; ok {
m.CtxW, _ = get("ctx.w")
m.CtxB, _ = get("ctx.b")
}
if err != nil {
return nil, err
}
if v, ok := f.Info["temperature"].(float64); ok {
m.Temp = v
}
m.Gamma, m.Beta = make([]float32, m.D), make([]float32, m.D)
if _, ok := f.arrays["ctx_pool_default"]; ok {
pool, _ := get("ctx_pool_default")
m.SetContext(pool)
}
return m, nil
}
func gelu(x float32) float32 {
y := float64(x)
return float32(0.5 * y * (1 + math.Tanh(0.7978845608*(y+0.044715*y*y*y))))
}
func sigmoid32(x float32) float32 { return float32(1 / (1 + math.Exp(-float64(x)))) }
// SetContext derives (gamma, beta) from a pooled benign hidden vector [D] (the site profile). nil = none.
func (m *SeqNet) SetContext(pool []float32) {
for j := range m.Gamma {
m.Gamma[j], m.Beta[j] = 0, 0
}
if m.CtxW == nil || pool == nil {
return
}
for i := 0; i < 2*m.D; i++ {
s := m.CtxB[i]
for j := 0; j < m.D; j++ {
s += m.CtxW[i*m.D+j] * pool[j]
}
g := float32(math.Tanh(float64(s)))
if i < m.D {
m.Gamma[i] = g
} else {
m.Beta[i-m.D] = g
}
}
}
// NewState allocates one source's GRU state (Layers*D zeros).
func (m *SeqNet) NewState() []float32 { return make([]float32, m.Layers*m.D) }
// Step consumes one flow of a source: numeric features [NumIn], port id, proto id, the source's state (updated
// in place) -> logits [C] (index 0 = attack). scratch may be nil.
func (m *SeqNet) Step(x []float32, port, proto int, state []float32, out, scratch []float32) []float32 {
D := m.D
need := m.In + 2*D + 3*D + 3*D + 64
if cap(scratch) < need {
scratch = make([]float32, need)
}
scratch = scratch[:need]
z := scratch[:m.In]
copy(z, x[:m.NumIn])
copy(z[m.NumIn:], m.EmbPort[port*16:port*16+16])
copy(z[m.NumIn+16:], m.EmbProto[proto*4:proto*4+4])
h1 := scratch[m.In : m.In+D]
for j := 0; j < D; j++ {
s := m.Enc0B[j]
row := m.Enc0W[j*m.In : j*m.In+m.In]
for i, zi := range z {
s += row[i] * zi
}
h1[j] = gelu(s)
}
inp := scratch[m.In+D : m.In+2*D]
for j := 0; j < D; j++ {
s := m.Enc1B[j]
row := m.Enc1W[j*D : j*D+D]
for i, hi := range h1 {
s += row[i] * hi
}
inp[j] = s
}
gi := scratch[m.In+2*D : m.In+5*D]
gh := scratch[m.In+5*D : m.In+8*D]
for l := 0; l < m.Layers; l++ {
h := state[l*D : l*D+D]
wih, whh := m.WIH[l], m.WHH[l]
for j := 0; j < 3*D; j++ {
s, t := m.BIH[l][j], m.BHH[l][j]
ri, rh := wih[j*D:j*D+D], whh[j*D:j*D+D]
for i := 0; i < D; i++ {
s += ri[i] * inp[i]
t += rh[i] * h[i]
}
gi[j], gh[j] = s, t
}
for j := 0; j < D; j++ {
r := sigmoid32(gi[j] + gh[j])
zg := sigmoid32(gi[D+j] + gh[D+j])
n := float32(math.Tanh(float64(gi[2*D+j] + r*gh[2*D+j])))
h[j] = (1-zg)*n + zg*h[j]
}
copy(inp, h)
}
hh := scratch[m.In+8*D : m.In+8*D+64]
top := state[(m.Layers-1)*D : m.Layers*D]
for j := 0; j < 64; j++ {
s := m.Head0B[j]
row := m.Head0W[j*D : j*D+D]
for i := 0; i < D; i++ {
s += row[i] * (top[i]*(1+m.Gamma[i]) + m.Beta[i])
}
hh[j] = gelu(s)
}
if cap(out) < m.C {
out = make([]float32, m.C)
}
out = out[:m.C]
for c := 0; c < m.C; c++ {
s := m.Head1B[c]
row := m.Head1W[c*64 : c*64+64]
for i, hi := range hh {
s += row[i] * hi
}
out[c] = s
}
return out
}
// ProbaAttack is sigmoid(logit0 / temperature).
func (m *SeqNet) ProbaAttack(logits []float32) float64 {
return 1 / (1 + math.Exp(-float64(logits[0])/m.Temp))
}