925 lines
35 KiB
Plaintext
925 lines
35 KiB
Plaintext
# -*- coding: utf-8 -*-
|
|
# ============================================================
|
|
# torch-lite: 仿 PyTorch 的极简张量/神经网络库(Viper)
|
|
#
|
|
# 设计原则:
|
|
# * "足够用就行": 只覆盖 MNIST 两层 MLP 训练/推理所需的原语。
|
|
# * API 命名贴近 PyTorch: Tensor / linear_fwd(bias 融合) / relu /
|
|
# relu_backward / softmax_crossentropy(前向+梯度一体) / sgd_step。
|
|
# * 张量为 2D 稠密 double (rows x cols, 行主序), 权重形状同 PyTorch:
|
|
# Linear(in, out) -> weight (out, in) / bias (out,), forward = x @ W^T + b。
|
|
# * 计算热点结合 vipersimd:
|
|
# - linear_fwd 内积按 k%4 用 simd_dot4d 累加(纯 AVX2 点积);
|
|
# - relu_fwd 用 simd_max4d(x, 0) 四路取大;
|
|
# - 向量乘标量用 simd_mul_scalar4d(归一化 /255 等)。
|
|
# backward / softmax / sgd 等按标量写(数据量小, 可读性优先)。
|
|
# * 内存: 所有 Tensor 及 data/grad 都从调用方传入的 memhub.MemBuddy
|
|
# 分配(与 includes 既有约定一致, pool.alloc 免手动 free)。
|
|
# ============================================================
|
|
from stdint import *
|
|
import t, c
|
|
import stdlib
|
|
import string
|
|
import stdio
|
|
import math
|
|
import vipersimd
|
|
import memhub
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# Tensor
|
|
# ------------------------------------------------------------
|
|
novirtual class Tensor:
|
|
Data: t.CPtr # rows*cols*8 字节连续 double
|
|
Grad: t.CPtr # 同尺寸梯度(可为 None)
|
|
Rows: sizet
|
|
Cols: sizet
|
|
|
|
# 字段: Data/Grad/Rows/Cols = 32 字节(无 vtable)
|
|
TENSOR_SIZE: t.CDefine = 32
|
|
|
|
|
|
def _zeros_block(pool: memhub.MemBuddy*, bytes_n: sizet) -> t.CPtr:
|
|
"""分配并清零一块内存"""
|
|
p: t.CPtr = pool.alloc(bytes_n)
|
|
if p is not None:
|
|
string.memset(p, 0, bytes_n)
|
|
return p
|
|
|
|
|
|
def zeros(pool: memhub.MemBuddy*, rows: sizet,
|
|
cols: sizet, with_grad: int = 1) -> Tensor | t.CPtr:
|
|
"""torch.zeros: 创建 rows x cols 的全零 Tensor, 可选分配 Grad 缓冲"""
|
|
if pool is None:
|
|
return None
|
|
t0: rawptr[Tensor] = pool.alloc(TENSOR_SIZE)
|
|
if t0 is None:
|
|
return None
|
|
string.memset(t0, 0, TENSOR_SIZE)
|
|
nb: sizet = rows * cols * sizet(8)
|
|
t0.Data = _zeros_block(pool, nb)
|
|
if t0.Data is None:
|
|
return None
|
|
if with_grad != 0:
|
|
t0.Grad = _zeros_block(pool, nb)
|
|
t0.Rows = rows
|
|
t0.Cols = cols
|
|
return t0
|
|
|
|
|
|
def from_raw(pool: memhub.MemBuddy*, data: t.CPtr,
|
|
rows: sizet, cols: sizet,
|
|
with_grad: int = 0) -> Tensor | t.CPtr:
|
|
"""torch.from_raw: 包装一块已有内存为 Tensor(不复制, 不拥有)"""
|
|
if pool is None:
|
|
return None
|
|
t0: rawptr[Tensor] = pool.alloc(TENSOR_SIZE)
|
|
if t0 is None:
|
|
return None
|
|
string.memset(t0, 0, TENSOR_SIZE)
|
|
t0.Data = data
|
|
t0.Grad = None
|
|
t0.Rows = rows
|
|
t0.Cols = cols
|
|
return t0
|
|
|
|
|
|
def tensor_free(t0: Tensor | t.CPtr):
|
|
"""释放 Tensor 外壳(数据由 MemBuddy 池统一回收, 无需手动 free)"""
|
|
pass
|
|
|
|
|
|
def tensor_data(t0: Tensor | t.CPtr) -> t.CPtr:
|
|
if t0 is None:
|
|
return None
|
|
return t0.Data
|
|
|
|
|
|
def tensor_grad(t0: Tensor | t.CPtr) -> t.CPtr:
|
|
if t0 is None:
|
|
return None
|
|
return t0.Grad
|
|
|
|
|
|
def tensor_rows(t0: Tensor | t.CPtr) -> sizet:
|
|
if t0 is None:
|
|
return 0
|
|
return t0.Rows
|
|
|
|
|
|
def tensor_cols(t0: Tensor | t.CPtr) -> sizet:
|
|
if t0 is None:
|
|
return 0
|
|
return t0.Cols
|
|
|
|
|
|
def fill(t0: Tensor | t.CPtr, val: double):
|
|
"""torch.fill: 全部元素置为 val"""
|
|
if t0 is None or t0.Data is None:
|
|
return
|
|
d: double | t.CPtr = t0.Data
|
|
n: sizet = t0.Rows * t0.Cols
|
|
i: sizet = 0
|
|
while i < n:
|
|
d[i] = val
|
|
i += 1
|
|
|
|
|
|
def zero_grad(t0: Tensor | t.CPtr):
|
|
"""torch.zero_grad: Grad 缓冲清零"""
|
|
if t0 is None or t0.Grad is None:
|
|
return
|
|
string.memset(t0.Grad, 0, t0.Rows * t0.Cols * sizet(8))
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# 确定性伪随机(LCG, minstd)与 Kaiming 均匀初始化
|
|
# ------------------------------------------------------------
|
|
_rng_state: double = 12345.0
|
|
LCG_M: double = 2147483647.0
|
|
LCG_A: double = 48271.0
|
|
|
|
|
|
def manual_seed(seed: int64):
|
|
global _rng_state
|
|
_rng_state = double(seed % int64(2147483647))
|
|
if _rng_state <= double(0.0):
|
|
_rng_state = double(12345.0)
|
|
|
|
|
|
def _next_rand() -> double:
|
|
"""(0,1) 均匀随机"""
|
|
global _rng_state
|
|
_rng_state = math.fmod(_rng_state * LCG_A, LCG_M)
|
|
return _rng_state / LCG_M
|
|
|
|
|
|
def uniform_fill(t0: Tensor | t.CPtr, lo: double, hi: double):
|
|
"""U(lo, hi) 填充"""
|
|
if t0 is None or t0.Data is None:
|
|
return
|
|
d: double | t.CPtr = t0.Data
|
|
n: sizet = t0.Rows * t0.Cols
|
|
span: double = hi - lo
|
|
i: sizet = 0
|
|
while i < n:
|
|
d[i] = lo + _next_rand() * span
|
|
i += 1
|
|
|
|
|
|
def kaiming_uniform_fill(w: Tensor | t.CPtr, fan_in: sizet,
|
|
fan_out: sizet):
|
|
"""PyTorch Linear 默认初始化: U(-sqrt(6/(fan_in+fan_out)), +...)"""
|
|
bound: double = math.sqrt(
|
|
double(6.0) / double(fan_in + fan_out))
|
|
uniform_fill(w, -bound, bound)
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# 基础元素级运算(结合 vipersimd 4 宽指令)
|
|
# ------------------------------------------------------------
|
|
def vector_mul_scalar4(src: t.CPtr, scalar_ptr: t.CPtr, dst: t.CPtr,
|
|
n: sizet):
|
|
"""dst = src * scalar (逐 4 元素 simd_mul_scalar4d + 标量尾数)"""
|
|
if src is None or scalar_ptr is None or dst is None:
|
|
return
|
|
i: sizet = 0
|
|
while i + 4 <= n:
|
|
vipersimd.simd_mul_scalar4d(
|
|
t.CPtr(uint64(src) + uint64(i) * 8),
|
|
scalar_ptr,
|
|
t.CPtr(uint64(dst) + uint64(i) * 8))
|
|
i += 4
|
|
sd: double | t.CPtr = src
|
|
dd: double | t.CPtr = dst
|
|
# 尾部标量
|
|
sp: double | t.CPtr = scalar_ptr
|
|
sval: double = sp[0]
|
|
while i < n:
|
|
dd[i] = sd[i] * sval
|
|
i += 1
|
|
|
|
|
|
def vector_add_inplace(a: t.CPtr, b: t.CPtr, n: sizet):
|
|
"""a += b (逐 4 元素 simd_add_array 尾部标量)"""
|
|
if a is None or b is None:
|
|
return
|
|
tmp: t.CPtr = stdlib.malloc(8)
|
|
if tmp is None:
|
|
return
|
|
i: sizet = 0
|
|
while i + 4 <= n:
|
|
vipersimd.simd_add_array(
|
|
t.CPtr(uint64(a) + uint64(i) * 8),
|
|
t.CPtr(uint64(b) + uint64(i) * 8),
|
|
t.CPtr(uint64(a) + uint64(i) * 8))
|
|
i += 4
|
|
da: double | t.CPtr = a
|
|
db: double | t.CPtr = b
|
|
while i < n:
|
|
da[i] = da[i] + db[i]
|
|
i += 1
|
|
stdlib.free(tmp)
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# 线性层: forward = x @ W^T + b (W: out x in, x: m x in, out: m x out)
|
|
# 内积按 k%4 用 vipersimd.simd_dot4d —— MNIST 主计算热点
|
|
# ------------------------------------------------------------
|
|
def linear_fwd(w: Tensor | t.CPtr, b: Tensor | t.CPtr,
|
|
x: Tensor | t.CPtr, out: Tensor | t.CPtr):
|
|
if w is None or b is None or x is None or out is None:
|
|
return
|
|
m: sizet = x.Rows
|
|
k: sizet = x.Cols
|
|
n: sizet = w.Rows
|
|
wd: double | t.CPtr = w.Data
|
|
bd: double | t.CPtr = b.Data
|
|
xd: double | t.CPtr = x.Data
|
|
od: double | t.CPtr = out.Data
|
|
acc_buf: t.CPtr = stdlib.malloc(8)
|
|
if acc_buf is None:
|
|
return
|
|
accp: double | t.CPtr = acc_buf
|
|
i: sizet = 0
|
|
while i < m:
|
|
j: sizet = 0
|
|
while j < n:
|
|
acc: double = bd[j]
|
|
kk: sizet = 0
|
|
while kk + 4 <= k:
|
|
vipersimd.simd_dot4d(
|
|
t.CPtr(uint64(xd) + uint64(i * k + kk) * 8),
|
|
t.CPtr(uint64(wd) + uint64(j * k + kk) * 8),
|
|
acc_buf)
|
|
acc = acc + accp[0]
|
|
kk += 4
|
|
while kk < k:
|
|
acc = acc + xd[i * k + kk] * wd[j * k + kk]
|
|
kk += 1
|
|
od[i * n + j] = acc
|
|
j += 1
|
|
i += 1
|
|
stdlib.free(acc_buf)
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# 线性层反向: 输入 w(out,in) x(m,in) g(m,out)
|
|
# dw[j,i] += sum_b g[b,j]*x[b,i] ; db[j] += sum_b g[b,j]
|
|
# dx[b,i] = sum_j g[b,j]*w[j,i]
|
|
# 注意 dw/db 按"累加"语义(调用前清零 Grad)
|
|
# ------------------------------------------------------------
|
|
def linear_bwd(w: Tensor | t.CPtr, x: Tensor | t.CPtr,
|
|
g: Tensor | t.CPtr, dw: Tensor | t.CPtr,
|
|
db: Tensor | t.CPtr, dx: Tensor | t.CPtr,
|
|
accum: int = 0):
|
|
if w is None or x is None or g is None:
|
|
return
|
|
if dw is None or db is None or dx is None:
|
|
return
|
|
m: sizet = x.Rows
|
|
k: sizet = x.Cols
|
|
n: sizet = w.Rows
|
|
wd: double | t.CPtr = w.Data
|
|
xd: double | t.CPtr = x.Data
|
|
gd: double | t.CPtr = g.Data
|
|
dwd: double | t.CPtr = dw.Data
|
|
dbd: double | t.CPtr = db.Data
|
|
dxd: double | t.CPtr = dx.Data
|
|
|
|
# dw/db/dx 清零(dx 恒清零: 它是本样本的输入梯度, 不跨样本累积)
|
|
# accum=1 时保留 dw/db 现值 —— 逐样本反向累积整批参数梯度的调用方用
|
|
if accum == 0:
|
|
string.memset(dw.Data, 0, n * k * sizet(8))
|
|
string.memset(db.Data, 0, n * sizet(8))
|
|
string.memset(dx.Data, 0, m * k * sizet(8))
|
|
|
|
b: sizet = 0
|
|
while b < m:
|
|
j: sizet = 0
|
|
while j < n:
|
|
gj: double = gd[b * n + j]
|
|
if gj != double(0.0):
|
|
dbd[j] = dbd[j] + gj
|
|
i: sizet = 0
|
|
while i < k:
|
|
dwd[j * k + i] = dwd[j * k + i] + gj * xd[b * k + i]
|
|
dxd[b * k + i] = dxd[b * k + i] + gj * wd[j * k + i]
|
|
i += 1
|
|
j += 1
|
|
b += 1
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# ReLU (simd_max4d 版本) 与反向
|
|
# ------------------------------------------------------------
|
|
def relu_fwd(x: Tensor | t.CPtr, out: Tensor | t.CPtr):
|
|
if x is None or out is None:
|
|
return
|
|
n: sizet = x.Rows * x.Cols
|
|
zero4: t.CPtr = stdlib.malloc(32)
|
|
if zero4 is None:
|
|
return
|
|
string.memset(zero4, 0, 32)
|
|
xd: double | t.CPtr = x.Data
|
|
od: double | t.CPtr = out.Data
|
|
i: sizet = 0
|
|
while i + 4 <= n:
|
|
vipersimd.simd_max4d(
|
|
t.CPtr(uint64(xd) + uint64(i) * 8),
|
|
zero4,
|
|
t.CPtr(uint64(od) + uint64(i) * 8))
|
|
i += 4
|
|
while i < n:
|
|
if xd[i] > double(0.0):
|
|
od[i] = xd[i]
|
|
else:
|
|
od[i] = double(0.0)
|
|
i += 1
|
|
stdlib.free(zero4)
|
|
|
|
|
|
def relu_backward(x: Tensor | t.CPtr, g: Tensor | t.CPtr,
|
|
gx: Tensor | t.CPtr):
|
|
"""gx = g * (x > 0)"""
|
|
if x is None or g is None or gx is None:
|
|
return
|
|
n: sizet = x.Rows * x.Cols
|
|
xd: double | t.CPtr = x.Data
|
|
gd: double | t.CPtr = g.Data
|
|
od: double | t.CPtr = gx.Data
|
|
i: sizet = 0
|
|
while i < n:
|
|
if xd[i] > double(0.0):
|
|
od[i] = gd[i]
|
|
else:
|
|
od[i] = double(0.0)
|
|
i += 1
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# softmax + CrossEntropy(融合, 数值稳定) —— MNIST 输出层
|
|
# logits: (m, C), labels: m 个 int (0..C-1)
|
|
# loss 写入 *loss, 梯度(已按 1/m 归一)写入 grad (m, C)
|
|
# ------------------------------------------------------------
|
|
def softmax_crossentropy(logits: Tensor | t.CPtr,
|
|
labels: int | t.CPtr,
|
|
n_labels: sizet,
|
|
loss: double | t.CPtr,
|
|
grad: Tensor | t.CPtr):
|
|
if logits is None or labels is None or grad is None:
|
|
return
|
|
if loss is None:
|
|
return
|
|
m: sizet = logits.Rows
|
|
c: sizet = logits.Cols
|
|
ld: double | t.CPtr = logits.Data
|
|
gd: double | t.CPtr = grad.Data
|
|
lb: int | t.CPtr = labels
|
|
total: double = double(0.0)
|
|
inv_m: double = double(1.0) / double(m)
|
|
b: sizet = 0
|
|
while b < m:
|
|
# 数值稳定 softmax
|
|
mx: double = ld[b * c]
|
|
j: sizet = 1
|
|
while j < c:
|
|
if ld[b * c + j] > mx:
|
|
mx = ld[b * c + j]
|
|
j += 1
|
|
s: double = double(0.0)
|
|
j = 0
|
|
while j < c:
|
|
s = s + math.exp(ld[b * c + j] - mx)
|
|
j += 1
|
|
y: int = lb[b]
|
|
# loss += -(logits[lab] - max - log(s))
|
|
total = total - (ld[b * c + sizet(y)] - mx - math.log(s))
|
|
# 梯度: (softmax - onehot) / m
|
|
j = 0
|
|
while j < c:
|
|
p: double = math.exp(ld[b * c + j] - mx) / s
|
|
o: double = double(0.0)
|
|
if sizet(y) == j:
|
|
o = double(1.0)
|
|
gd[b * c + j] = (p - o) * inv_m
|
|
j += 1
|
|
b += 1
|
|
loss[0] = total
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# 每行 argmax (分类预测)
|
|
# ------------------------------------------------------------
|
|
def row_argmax(x: Tensor | t.CPtr, pred: int | t.CPtr):
|
|
if x is None or pred is None:
|
|
return
|
|
m: sizet = x.Rows
|
|
c: sizet = x.Cols
|
|
d: double | t.CPtr = x.Data
|
|
i: sizet = 0
|
|
while i < m:
|
|
best: sizet = 0
|
|
bv: double = d[i * c]
|
|
j: sizet = 1
|
|
while j < c:
|
|
if d[i * c + j] > bv:
|
|
bv = d[i * c + j]
|
|
best = j
|
|
j += 1
|
|
pred[i] = int(best)
|
|
i += 1
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# SGD: data -= lr * grad(全量梯度) / 重球法: v = m*v+g, data -= lr*v
|
|
# ------------------------------------------------------------
|
|
def sgd_step(param: Tensor | t.CPtr, lr: double):
|
|
if param is None or param.Data is None or param.Grad is None:
|
|
return
|
|
d: double | t.CPtr = param.Data
|
|
g: double | t.CPtr = param.Grad
|
|
n: sizet = param.Rows * param.Cols
|
|
i: sizet = 0
|
|
while i < n:
|
|
d[i] = d[i] - lr * g[i]
|
|
i += 1
|
|
|
|
|
|
def sgd_step_momentum(param: Tensor | t.CPtr, vel: t.CPtr,
|
|
lr: double, momentum: double):
|
|
"""带动量(重球法)的 SGD: v = m*v + g ; w -= lr*v
|
|
|
|
vel 是与 param 同元素个数的 double 缓冲, 由调用方分配并清零(跨 step 持有);
|
|
vel 传 None 时退化为普通 sgd_step。动量是 CNN/MLP 训练收敛速度的关键 ——
|
|
纯 SGD 在固定 lr 下前几个 epoch 很慢且容易卡在低准确率。"""
|
|
if param is None or param.Data is None or param.Grad is None:
|
|
return
|
|
if vel is None:
|
|
sgd_step(param, lr)
|
|
return
|
|
d: double | t.CPtr = param.Data
|
|
g: double | t.CPtr = param.Grad
|
|
v: double | t.CPtr = vel
|
|
n: sizet = param.Rows * param.Cols
|
|
i: sizet = 0
|
|
while i < n:
|
|
vv: double = momentum * v[i] + g[i]
|
|
v[i] = vv
|
|
d[i] = d[i] - lr * vv
|
|
i += 1
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# 逐元素乘加 (调试/扩展用, vipersimd fma_array)
|
|
# a = a*b + c
|
|
# ------------------------------------------------------------
|
|
def fma_array_inplace(a: Tensor | t.CPtr, b: t.CPtr, c: t.CPtr):
|
|
if a is None or a.Data is None:
|
|
return
|
|
vipersimd.simd_fma_array(a.Data, b, c, a.Data, a.Rows * a.Cols)
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# Conv2d / MaxPool2d —— 轻量 CNN 用的原始缓冲算子
|
|
#
|
|
# 这些算子直接吃裸缓冲(不用 Tensor: torch.Tensor 只有 2D 形状),
|
|
# 布局一律 NCHW 连续 double; 卷积权重 [cout][cin][k][k]。
|
|
# 约定:
|
|
# *_fwd 覆盖写输出
|
|
# conv2d_bwd_dx 先清零再累加(逐样本输入梯度, 不跨样本累积)
|
|
# maxpool2d_bwd 同上
|
|
# conv2d_bwd_dw += 累加参数梯度(跨样本累积, 批次开始由调用方清零)
|
|
#
|
|
# SIMD: 三个卷积算子的内层都按"输出列"做 4 路 AVX2 double。
|
|
# 依据是 stride==1 时相邻输出列恰好对应相邻输入列, 故一列 4 个 double
|
|
# 在内存里连续, 可直接 vmovupd + vfmadd213pd, 权重标量 vbroadcastsd。
|
|
# 代价是需要一个"安全列区间": 4 条 lane 的输出列都在 [0,wout) 内, 且它们
|
|
# 触及的输入列 [ox-pad, ox+3+k-1-pad] 全在 [0,win) 内 —— 即
|
|
# ox ∈ [pad, win+pad-k-3]。区间外的列与 stride!=1 一律走原标量分支。
|
|
# (maxpool2d 不做 SIMD: k==stride==2 时 4 个窗口起点间隔 2 个 double,
|
|
# 不是连续地址, 无 gather 原语; 且它占总计算量不到 1%。)
|
|
# ------------------------------------------------------------
|
|
def _dptr(base: t.CPtr, elem: sizet) -> t.CPtr:
|
|
"""裸 double 缓冲的元素下标 → 字节地址
|
|
|
|
裸 t.CPtr 是 i8*, `p + n` 走的是字节偏移; 而 vipersimd 的 4 路原语按
|
|
"4 个 double = 32 字节"读写, 所以下标必须显式 ×8。
|
|
(与 linear_fwd 里的 t.CPtr(uint64(p) + i * 8) 同一写法。)"""
|
|
return t.CPtr(uint64(base) + uint64(elem) * 8)
|
|
|
|
|
|
def conv2d_fwd(x: t.CPtr, w: t.CPtr, b: t.CPtr, out: t.CPtr,
|
|
n: sizet, cin: sizet, hin: sizet, win: sizet,
|
|
cout: sizet, k: sizet, stride: sizet,
|
|
pad: sizet):
|
|
"""out[n][cout][hout][wout] = conv2d(x, w) + b
|
|
hout = (hin + 2*pad - k)/stride + 1(wout 同理, 整除向下取整)"""
|
|
if x is None or w is None or out is None:
|
|
return
|
|
hout: sizet = (hin + pad * 2 - k) / stride + 1
|
|
wout: sizet = (win + pad * 2 - k) / stride + 1
|
|
xd: double | t.CPtr = x
|
|
wd: double | t.CPtr = w
|
|
od: double | t.CPtr = out
|
|
kk: sizet = cin * k * k
|
|
# SIMD 安全区间(见文件头说明)
|
|
simd_on: int = 0
|
|
smax: sizet = 0
|
|
if stride == 1 and win + pad >= k + 3:
|
|
simd_on = 1
|
|
smax = win + pad - (k + 3)
|
|
wb: t.CPtr = None # 权重标量 × 4 路广播副本(kk 个标量)
|
|
t4: t.CPtr = None # 4 路累加器
|
|
if simd_on != 0:
|
|
wb = stdlib.malloc(kk * 32)
|
|
t4 = stdlib.malloc(32)
|
|
if wb is None or t4 is None:
|
|
if wb is not None:
|
|
stdlib.free(wb)
|
|
if t4 is not None:
|
|
stdlib.free(t4)
|
|
wb = None
|
|
t4 = None
|
|
simd_on = 0
|
|
wbd: double | t.CPtr = wb
|
|
t4d: double | t.CPtr = t4
|
|
|
|
bi: sizet = 0
|
|
while bi < n:
|
|
co: sizet = 0
|
|
while co < cout:
|
|
bias: double = double(0.0)
|
|
if b is not None:
|
|
bd: double | t.CPtr = b
|
|
bias = bd[co]
|
|
# 权重标量预广播: 每个 (ci,ky,kx) 一个 4 路副本, 本通道所有输出
|
|
# 行/列共用。否则 vbroadcastsd(4 次 store) 会跟着 4 路循环重复,
|
|
# 把 SIMD 的收益吃掉一半。
|
|
if simd_on != 0:
|
|
wb4: sizet = 0
|
|
while wb4 < kk:
|
|
wq: double = wd[co * kk + wb4]
|
|
wbd[wb4 * 4] = wq
|
|
wbd[wb4 * 4 + 1] = wq
|
|
wbd[wb4 * 4 + 2] = wq
|
|
wbd[wb4 * 4 + 3] = wq
|
|
wb4 += 1
|
|
oy: sizet = 0
|
|
while oy < hout:
|
|
oy0: sizet = oy * stride
|
|
ox: sizet = 0
|
|
while ox < wout:
|
|
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
|
|
# ---- 4 路: 一次算 4 个相邻输出列 ----
|
|
t4d[0] = bias
|
|
t4d[1] = bias
|
|
t4d[2] = bias
|
|
t4d[3] = bias
|
|
ci: sizet = 0
|
|
while ci < cin:
|
|
ky: sizet = 0
|
|
while ky < k:
|
|
iy: sizet = oy0 + ky
|
|
if iy >= pad:
|
|
yy: sizet = iy - pad
|
|
if yy < hin:
|
|
xrow: sizet = ((bi * cin + ci) * hin + yy) * win
|
|
kx: sizet = 0
|
|
while kx < k:
|
|
vipersimd.simd_fma4d(
|
|
_dptr(x, xrow + ox + kx - pad),
|
|
_dptr(wb, ((ci * k + ky) * k + kx) * 4),
|
|
t4, t4)
|
|
kx += 1
|
|
ky += 1
|
|
ci += 1
|
|
ob: sizet = ((bi * cout + co) * hout + oy) * wout + ox
|
|
od[ob] = t4d[0]
|
|
od[ob + 1] = t4d[1]
|
|
od[ob + 2] = t4d[2]
|
|
od[ob + 3] = t4d[3]
|
|
ox += 4
|
|
else:
|
|
# ---- 标量: 边界列 / stride != 1 ----
|
|
ox0: sizet = ox * stride
|
|
acc: double = bias
|
|
ci2: sizet = 0
|
|
while ci2 < cin:
|
|
ky2: sizet = 0
|
|
while ky2 < k:
|
|
iy2: sizet = oy0 + ky2
|
|
if iy2 >= pad:
|
|
yy2: sizet = iy2 - pad
|
|
if yy2 < hin:
|
|
kx2: sizet = 0
|
|
while kx2 < k:
|
|
ix2: sizet = ox0 + kx2
|
|
if ix2 >= pad:
|
|
xx2: sizet = ix2 - pad
|
|
if xx2 < win:
|
|
acc = acc + \
|
|
wd[co * kk + (ci2 * k + ky2) * k + kx2] * \
|
|
xd[((bi * cin + ci2) * hin + yy2) * win + xx2]
|
|
kx2 += 1
|
|
ky2 += 1
|
|
ci2 += 1
|
|
od[((bi * cout + co) * hout + oy) * wout + ox] = acc
|
|
ox += 1
|
|
oy += 1
|
|
co += 1
|
|
bi += 1
|
|
if wb is not None:
|
|
stdlib.free(wb)
|
|
if t4 is not None:
|
|
stdlib.free(t4)
|
|
|
|
|
|
def conv2d_bwd_dw(x: t.CPtr, dout: t.CPtr, dw: t.CPtr, db: t.CPtr,
|
|
n: sizet, cin: sizet, hin: sizet, win: sizet,
|
|
cout: sizet, k: sizet, stride: sizet,
|
|
pad: sizet):
|
|
"""累加: dw[co][ci][ky][kx] += sum dout*x ; db[co] += sum dout
|
|
(+= 语义, 调用方在批次开始前清零)
|
|
|
|
循环顺序为 (ci,ky,kx) 在外、(oy,ox) 在内: 每个权重元素只做一次
|
|
读-改-写; 内层按输出列 4 路 SIMD 累加, 最后 hsum 成标量。"""
|
|
if x is None or dout is None or dw is None:
|
|
return
|
|
hout: sizet = (hin + pad * 2 - k) / stride + 1
|
|
wout: sizet = (win + pad * 2 - k) / stride + 1
|
|
xd: double | t.CPtr = x
|
|
gd: double | t.CPtr = dout
|
|
dwd: double | t.CPtr = dw
|
|
dbd: double | t.CPtr = None
|
|
if db is not None:
|
|
dbd = db
|
|
kk: sizet = cin * k * k
|
|
simd_on: int = 0
|
|
smax: sizet = 0
|
|
if stride == 1 and win + pad >= k + 3:
|
|
simd_on = 1
|
|
smax = win + pad - (k + 3)
|
|
acc: t.CPtr = None
|
|
if simd_on != 0:
|
|
acc = stdlib.malloc(32)
|
|
if acc is None:
|
|
simd_on = 0
|
|
accd: double | t.CPtr = acc
|
|
|
|
bi: sizet = 0
|
|
while bi < n:
|
|
co: sizet = 0
|
|
while co < cout:
|
|
gp: sizet = (bi * cout + co) * hout * wout
|
|
# ---- db[co] += sum(g): g 的整个通道平面是连续的 ----
|
|
if dbd is not None:
|
|
tot0: double = double(0.0)
|
|
if simd_on != 0:
|
|
accd[0] = double(0.0)
|
|
accd[1] = double(0.0)
|
|
accd[2] = double(0.0)
|
|
accd[3] = double(0.0)
|
|
gcnt: sizet = hout * wout
|
|
gof: sizet = 0
|
|
while gof + 4 <= gcnt:
|
|
if simd_on != 0:
|
|
vipersimd.simd_add4d(_dptr(dout, gp + gof), acc, acc)
|
|
else:
|
|
tot0 = tot0 + gd[gp + gof]
|
|
tot0 = tot0 + gd[gp + gof + 1]
|
|
tot0 = tot0 + gd[gp + gof + 2]
|
|
tot0 = tot0 + gd[gp + gof + 3]
|
|
gof += 4
|
|
while gof < gcnt:
|
|
tot0 = tot0 + gd[gp + gof]
|
|
gof += 1
|
|
if simd_on != 0:
|
|
vipersimd.simd_hsum4d(acc, acc)
|
|
tot0 = tot0 + accd[0]
|
|
dbd[co] = dbd[co] + tot0
|
|
# ---- dw[co][ci][ky][kx] ----
|
|
ci: sizet = 0
|
|
while ci < cin:
|
|
ky: sizet = 0
|
|
while ky < k:
|
|
kx: sizet = 0
|
|
while kx < k:
|
|
tot: double = double(0.0)
|
|
if simd_on != 0:
|
|
accd[0] = double(0.0)
|
|
accd[1] = double(0.0)
|
|
accd[2] = double(0.0)
|
|
accd[3] = double(0.0)
|
|
oy: sizet = 0
|
|
while oy < hout:
|
|
iy: sizet = oy * stride + ky
|
|
if iy >= pad:
|
|
yy: sizet = iy - pad
|
|
if yy < hin:
|
|
xrow: sizet = ((bi * cin + ci) * hin + yy) * win
|
|
goff: sizet = gp + oy * wout
|
|
ox: sizet = 0
|
|
while ox < wout:
|
|
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
|
|
vipersimd.simd_fma4d(
|
|
_dptr(dout, goff + ox),
|
|
_dptr(x, xrow + ox + kx - pad),
|
|
acc, acc)
|
|
ox += 4
|
|
else:
|
|
ix: sizet = ox * stride + kx
|
|
if ix >= pad:
|
|
xx: sizet = ix - pad
|
|
if xx < win:
|
|
tot = tot + gd[goff + ox] * \
|
|
xd[xrow + xx]
|
|
ox += 1
|
|
oy += 1
|
|
if simd_on != 0:
|
|
vipersimd.simd_hsum4d(acc, acc)
|
|
tot = tot + accd[0]
|
|
wj: sizet = co * kk + (ci * k + ky) * k + kx
|
|
dwd[wj] = dwd[wj] + tot
|
|
kx += 1
|
|
ky += 1
|
|
ci += 1
|
|
co += 1
|
|
bi += 1
|
|
if acc is not None:
|
|
stdlib.free(acc)
|
|
|
|
|
|
def conv2d_bwd_dx(w: t.CPtr, dout: t.CPtr, dx: t.CPtr,
|
|
n: sizet, cin: sizet, hin: sizet, win: sizet,
|
|
cout: sizet, k: sizet, stride: sizet,
|
|
pad: sizet):
|
|
"""dx 先清零再累加(逐样本输入梯度): dx[ci][iy][ix] += sum dout*w
|
|
|
|
stride==1 时内层按输出列 4 路 SIMD: 4 条 lane 对应的 dx 单元也是连续的,
|
|
故可直接对 dx 做 in-place fma(g4 * w_broadcast + dx4)。"""
|
|
if w is None or dout is None or dx is None:
|
|
return
|
|
hout: sizet = (hin + pad * 2 - k) / stride + 1
|
|
wout: sizet = (win + pad * 2 - k) / stride + 1
|
|
wd: double | t.CPtr = w
|
|
gd: double | t.CPtr = dout
|
|
dxd: double | t.CPtr = dx
|
|
string.memset(dx, 0, n * cin * hin * win * sizet(8))
|
|
kk: sizet = cin * k * k
|
|
simd_on: int = 0
|
|
smax: sizet = 0
|
|
if stride == 1 and win + pad >= k + 3:
|
|
simd_on = 1
|
|
smax = win + pad - (k + 3)
|
|
wb: t.CPtr = None # 整个权重张量的 4 路广播副本(cout*kk 个标量)
|
|
if simd_on != 0:
|
|
wb = stdlib.malloc(cout * kk * 32)
|
|
if wb is None:
|
|
simd_on = 0
|
|
wbd: double | t.CPtr = wb
|
|
if simd_on != 0:
|
|
# 权重在一次调用内不变 → 整体预广播一次(注意别在 4 路循环里重复
|
|
# vbroadcastsd: 那是 4 次 store, 会把 SIMD 的收益吃掉一半)
|
|
wjn: sizet = cout * kk
|
|
wj4: sizet = 0
|
|
while wj4 < wjn:
|
|
wq2: double = wd[wj4]
|
|
wbd[wj4 * 4] = wq2
|
|
wbd[wj4 * 4 + 1] = wq2
|
|
wbd[wj4 * 4 + 2] = wq2
|
|
wbd[wj4 * 4 + 3] = wq2
|
|
wj4 += 1
|
|
|
|
bi: sizet = 0
|
|
while bi < n:
|
|
ci: sizet = 0
|
|
while ci < cin:
|
|
ky: sizet = 0
|
|
while ky < k:
|
|
oy: sizet = 0
|
|
while oy < hout:
|
|
iy: sizet = oy * stride + ky
|
|
if iy >= pad:
|
|
yy: sizet = iy - pad
|
|
if yy < hin:
|
|
dxrow: sizet = ((bi * cin + ci) * hin + yy) * win
|
|
ox: sizet = 0
|
|
while ox < wout:
|
|
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
|
|
co: sizet = 0
|
|
while co < cout:
|
|
kx: sizet = 0
|
|
while kx < k:
|
|
goff: sizet = ((bi * cout + co) * hout + oy) * wout + ox
|
|
doff: sizet = dxrow + ox + kx - pad
|
|
vipersimd.simd_fma4d(
|
|
_dptr(dout, goff),
|
|
_dptr(wb, (co * kk + (ci * k + ky) * k + kx) * 4),
|
|
_dptr(dx, doff), _dptr(dx, doff))
|
|
kx += 1
|
|
co += 1
|
|
ox += 4
|
|
else:
|
|
co2: sizet = 0
|
|
while co2 < cout:
|
|
kx2: sizet = 0
|
|
while kx2 < k:
|
|
ix: sizet = ox * stride + kx2
|
|
if ix >= pad:
|
|
xx: sizet = ix - pad
|
|
if xx < win:
|
|
xi: sizet = dxrow + xx
|
|
gi: sizet = ((bi * cout + co2) * hout + oy) * wout + ox
|
|
dxd[xi] = dxd[xi] + \
|
|
gd[gi] * wd[co2 * kk + (ci * k + ky) * k + kx2]
|
|
kx2 += 1
|
|
co2 += 1
|
|
ox += 1
|
|
oy += 1
|
|
ky += 1
|
|
ci += 1
|
|
bi += 1
|
|
if wb is not None:
|
|
stdlib.free(wb)
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# MaxPool2d (k x k, stride 通常 = k)
|
|
# ------------------------------------------------------------
|
|
def maxpool2d_fwd(x: t.CPtr, out: t.CPtr, argmax: t.CPtr,
|
|
n: sizet, c: sizet, hin: sizet, win: sizet,
|
|
k: sizet, stride: sizet):
|
|
"""argmax: int 数组 [n*c*hout*wout], 记窗口内最大值在本通道内的下标(yy*win+xx)"""
|
|
if x is None or out is None or argmax is None:
|
|
return
|
|
hout: sizet = (hin - k) / stride + 1
|
|
wout: sizet = (win - k) / stride + 1
|
|
xd: double | t.CPtr = x
|
|
od: double | t.CPtr = out
|
|
am: int | t.CPtr = argmax
|
|
bi: sizet = 0
|
|
while bi < n:
|
|
ch: sizet = 0
|
|
while ch < c:
|
|
plano: sizet = (bi * c + ch) * hin * win
|
|
oy: sizet = 0
|
|
while oy < hout:
|
|
oy0: sizet = oy * stride
|
|
ox: sizet = 0
|
|
while ox < wout:
|
|
ox0: sizet = ox * stride
|
|
best: double = double(0.0)
|
|
bidx: int = 0
|
|
first: int = 1
|
|
ky: sizet = 0
|
|
while ky < k:
|
|
yy: sizet = oy0 + ky
|
|
kx: sizet = 0
|
|
while kx < k:
|
|
xx: sizet = ox0 + kx
|
|
v: double = xd[plano + yy * win + xx]
|
|
if first != 0 or v > best:
|
|
best = v
|
|
bidx = int(yy * win + xx)
|
|
first = 0
|
|
kx += 1
|
|
ky += 1
|
|
oi: sizet = (bi * c + ch) * hout * wout + oy * wout + ox
|
|
od[oi] = best
|
|
am[oi] = bidx
|
|
ox += 1
|
|
oy += 1
|
|
ch += 1
|
|
bi += 1
|
|
|
|
|
|
def maxpool2d_bwd(dout: t.CPtr, argmax: t.CPtr, dx: t.CPtr,
|
|
n: sizet, c: sizet, hin: sizet, win: sizet,
|
|
k: sizet, stride: sizet):
|
|
"""dx 先清零再散射累加: dx[argmax] += dout"""
|
|
if dout is None or argmax is None or dx is None:
|
|
return
|
|
hout: sizet = (hin - k) / stride + 1
|
|
wout: sizet = (win - k) / stride + 1
|
|
gd: double | t.CPtr = dout
|
|
am: int | t.CPtr = argmax
|
|
dxd: double | t.CPtr = dx
|
|
string.memset(dx, 0, n * c * hin * win * sizet(8))
|
|
bi: sizet = 0
|
|
while bi < n:
|
|
ch: sizet = 0
|
|
while ch < c:
|
|
plano: sizet = (bi * c + ch) * hin * win
|
|
oy: sizet = 0
|
|
while oy < hout:
|
|
ox: sizet = 0
|
|
while ox < wout:
|
|
oi: sizet = (bi * c + ch) * hout * wout + oy * wout + ox
|
|
tgt: sizet = plano + sizet(am[oi])
|
|
dxd[tgt] = dxd[tgt] + gd[oi]
|
|
ox += 1
|
|
oy += 1
|
|
ch += 1
|
|
bi += 1
|