Tensor Logic
Fuente: source/classes/tensorlogic.prg, source/function/fwtensor.c
FiveWin implementa Tensor Logic: The Language of AI de Pedro Domingos (arXiv:2510.12269). Su idea clave: una regla lógica y una suma de Einstein son la misma operación. Un programa es un conjunto de ecuaciones tensoriales; cada ecuación hace el join de los tensores de su lado derecho (producto sobre índices compartidos), proyecta los índices que no aparecen en el lado izquierdo (suma) y aplica opcionalmente una no-linealidad. Un mismo motor ejecuta redes neuronales (atención, MLPs) y razonamiento simbólico (Datalog sobre tablas DBF) — y puede diferenciar cualquier programa automáticamente.
Y[i] = f( W[i,j] * X[j] )"] --> N[Neuronal: atención, MLP, transformer] EQ --> S[Simbólico: reglas Datalog sobre DBFs] EQ --> G["Autodiff: gradiente =
mismo einsum, factores intercambiados"]
Inicio rápido
- Compila la librería FWH con tu variante (p.ej.
mfwh_new.bat hb32) para quetensorlogic.prgyfwtensor.cestén en la lib. - Desde
samples\ai, compila y ejecuta el compañero del tutorial:
La salida queda encd /d C:\fwteam\samples\ai C:\fwteam\samples\build_new.bat tltutor hb32 tltutor.exetltutor.log. Léela junto al tutorial paso a paso. - Velocidad opcional: pon
libopenblas.dllen el PATH o junto al exe (o defineFWT_BLAS_DLL). Comprueba con? FWT_BlasActive().
Ecuaciones tensoriales
oTL := TTensorLogic():New()
oTL:Let( "W", { { 1, -1, 0 }, { 0, 1, -1 } } ) // liga tensores (arrays o FW_Tensor)
oTL:Let( "X", { 1, 2, 0.5 } )
oTL:Let( "B", { 2, -2 } )
oTL:Eq( "Y[i] = step( W[i,j] * X[j] + B[i] )" ) // un perceptron, una ecuacion
oTL:Run() // forward chaining hasta punto fijo
? oTL:GetArray( "Y" ) // {1, 0}
Los índices repetidos a la derecha se joinean; los que faltan a la izquierda se suman.
No-linealidades: step, sigmoid, relu, tanh,
gelu, softmax (último índice), lnorm (layer norm, último índice,
sin parte afín — gamma/beta se expresan como ecuaciones adicionales). Los factores numéricos escalan
un término: "S[p,q] = Q[p,k] * K[q,k] * 0.7071".
Sintaxis de ecuaciones
| Forma | Significado |
|---|---|
Out[i,k] = A[i,j] * B[j,k] | Join en j, proyecta j (producto matricial). |
Y[i] = step( W[i,j] * X[j] + B[i] ) | Varios términos aditivos + no-linealidad. |
S[p,q] = Q[p,d] * K[q,d] * 0.7071 | Factor numérico de escala en un término. |
A[p,q.] = softmax( S[p,q] ) | El . final marca el eje del softmax. |
N[p,d] = lnorm( R[p,d] ) | Layer norm sobre el último índice. |
N2[p,d] = N[p,d] * G[d] + Ones[p] * B[d] | Parte afín gamma/beta tras lnorm. |
Los términos con cualquier número de factores se evalúan encadenando contracciones por pares: solo sobreviven las letras aún necesarias para la salida, de modo que cada paso puede usar el camino rápido GEMM de OpenBLAS.
Si el parseo falla, Eq() devuelve .F. y rellena oTL:cError.
TTensorLogic
| Método | Descripción |
|---|---|
New() | Crea un programa vacío (tensores, ecuaciones, estado Adam). |
Let( cName, uData ) | Liga un tensor (array anidado de cualquier rango o FW_Tensor). |
Param( cName, uData ) | Let + marcar como entrenable (lo actualiza Step() / StepAdam()). |
Eq( cEquation ) | Añade una ecuación tensorial (string). Devuelve .F. si no parsea. |
Run( nMaxPasses, nTol ) | Forward chaining hasta punto fijo (por defecto 100 pasadas, tol 1e-6). Las reglas Datalog recursivas convergen. |
Forward() | Una pasada en orden de declaración; cachea pre-activaciones para Backward(). Úsalo al entrenar, no Run(). |
ZeroGrad() | Pone a cero todos los gradientes acumulados. |
Seed( cName, tGrad ) | Fija dL/dName (gradiente de la pérdida respecto a un tensor de salida). |
Backward() | Barrido inverso. El gradiente de una ecuación = mismo einsum con factor y salida intercambiados. |
GetGrad( cName ) | Devuelve dL/dName como FW_Tensor. |
Step( nLr ) | SGD: cada Param -= lr * grad. |
StepAdam( nLr, nB1, nB2, nEps ) | Optimizador Adam (kernel C FWT_AdamUpdate con corrección de sesgo). Por defecto: β1=0.9, β2=0.999, ε=1e-8. |
ClipGrads( nMaxNorm ) | Clipping global L2 de gradientes. Devuelve la norma previa al clip. |
ClearActGrads() | Borra gradientes de activaciones, conserva los de parámetros (acumulación por mini-batch). |
SaveParams( cFile, hUser ) / LoadParams( cFile ) | Checkpoint: cada tensor Param() como float32 crudo más un hash opcional de usuario. Load devuelve el hash o NIL si falla. |
Get( cName ) / GetArray( cName ) | Tensor resultado / array anidado (2D). |
LetDbf( cName, cAlias, aFields, oDomain ) | Registros DBF → tensor booleano de relación (una dimensión por campo). |
LetSet( cName, aEntities, oDomain ) | Lista de nombres → tensor booleano unario. |
Query( cName, oDomain, nThresh ) | Tensor resultado → tuplas de nombres (umbral por defecto 0.5). |
DATA: hTensors, aEqs, cError, hPre, hGrads,
aParams, hAdamM, hAdamV, nAdamT.
Patrones de bucle de entrenamiento
// Muestra única / batch completo
oTL:Forward()
oTL:ZeroGrad()
oTL:Seed( "Y", FWT_Scale( FWT_Sub( oTL:Get( "Y" ), tTarget ), 2 ) ) // d MSE
oTL:Backward()
oTL:ClipGrads( 1.0 )
oTL:StepAdam( 0.05 )
// Acumulación por mini-batch (estilo TTransformerTL)
oTL:ZeroGrad()
for i := 1 to nBatch
oT:AccumStep( aIds[i], aTgt[i], 1 / nBatch ) // grads de params se suman
next
oTL:ClipGrads( 1.0 )
oTL:StepAdam( nLr )
TTLDomain
Mapa entidad ↔ índice compartido por todas las relaciones para que las dimensiones coincidan.
| Método | Descripción |
|---|---|
New() | Dominio vacío. |
Add( cName ) | Registra el nombre si es nuevo; devuelve id 1-based. |
Id( cName ) | Id o 0 si no existe. |
Name( nId ) | Nombre del id. |
Len() | Número de entidades. |
Harvest( cAlias, aFields ) | Registra todos los valores de esos campos en el alias abierto. Llamar antes de LetDbf(). |
TTransformerTL
Fachada transformer estilo GPT sobre un programa de tensor logic (~15 ecuaciones por capa).
Las cabezas son un índice (sin trocear columnas): WQ[d,h,k] absorbe el reparto
por cabeza y WO[h,k,d] absorbe concat+Wo. Con los mismos pesos reproduce
ForwardSeq() de la clase Transformer clásica de cerca, y
entrena y genera con el autodiff genérico — cero código de backprop específico.
| Método | Descripción |
|---|---|
New( nLayers, dModel, nHeads, nVocab ) | Construye el programa + inicializa parámetros (estilo Xavier). Por defecto: 1, 8, 2, 4. |
BuildProgram() | Emite residual, atención multi-cabeza, FFN y proyección a vocabulario como ecuaciones. |
InitParams() | Tensores Param() aleatorios (reproducibles) para todos los pesos. |
LoadFromTransformer( oT ) | Copia pesos desde un Transformer clásico (tests de paridad). |
SetInput( aIds ) | One-hot X, máscara causal, Pos, vectores de unos. |
ForwardSeq( aIds ) | → array anidado de probs [seq][vocab]. |
TrainStep( aIds, aTargets, nLr ) | Paso de pérdida CE (ZeroGrad + Backward + Step). Devuelve la pérdida. |
AccumStep( aIds, aTargets, nScale ) | Solo acumula gradientes (para mini-batches). Devuelve la pérdida escalada. |
EvalLoss( aIds, aTargets ) | CE solo forward (validación). |
Generate( aSeed, nNew ) | Generación greedy argmax de nNew tokens. |
oTLT := TTransformerTL():New( 2, 8, 2, nVocab )
aProbs := oTLT:ForwardSeq( { 2, 3, 4, 5, 6 } )
nLoss := oTLT:TrainStep( aIds, aTargets, 0.01 )
aOut := oTLT:Generate( { 1 }, 8 )
// multi-batch:
oTLT:oTL:ZeroGrad()
for i := 1 to nBatch
oTLT:AccumStep( aWin[i], aTgt[i], 1 / nBatch )
next
oTLT:oTL:ClipGrads( 1.0 )
oTLT:oTL:StepAdam( nLr )
Esqueleto de atención como ecuaciones (de BuildProgram()):
oTL:Eq( "Q[h,p,k] = H0[p,d] * WQ[d,h,k]" )
oTL:Eq( "S[h,p,q] = Q[h,p,k] * K[h,q,k] * 0.5 + OnesH[h] * Mask[p,q]" )
oTL:Eq( "A[h,p,q.] = softmax( S[h,p,q] )" )
oTL:Eq( "O[h,p,k] = A[h,p,q] * V[h,q,k]" )
oTL:Eq( "Att[p,d] = O[h,p,k] * WO[h,k,d]" )
oTL:Eq( "N1[p,d] = lnorm( Att[p,d] + H0[p,d] )" ) // residual + layer norm
// ... FFN, segunda norma, proyeccion a vocabulario, softmax
Datalog sobre tablas DBF
USE tlfam ALIAS FAM NEW // campos PARENT, CHILD
oDom := TTLDomain():New()
oDom:Harvest( "FAM", { "PARENT", "CHILD" } ) // fijar el dominio primero
oTL:LetDbf( "Parent", "FAM", { "PARENT", "CHILD" }, oDom )
oTL:LetSet( "Male", { "abe", "homer" }, oDom )
oTL:Let( "NotEq", TL_NotEq( oDom:Len() ) )
oTL:Let( "Ancestor", FWT_New( { oDom:Len(), oDom:Len() } ) )
oTL:Eq( "Father[x,y] = step( Parent[x,y] * Male[x] )" )
oTL:Eq( "Sibling[x,y] = step( Parent[p,x] * Parent[p,y] * NotEq[x,y] )" )
oTL:Eq( "Ancestor[x,y] = step( Parent[x,y] + Ancestor[x,z] * Parent[z,y] )" ) // recursiva
oTL:Run( 20 ) // punto fijo
AEval( oTL:Query( "Ancestor", oDom ), {| a | QOut( a[1] + " -> " + a[2] ) } )
Razonamiento en espacio de embeddings (el puente neuro-simbólico del paper): entidades como vectores unitarios, la relación embebida, consulta inversa y sigmoide con temperatura — T→0 deductivo (0/1 duro), T alto analógico:
oE:Let( "Emb", TL_UnitEmb( n, 256, 1.234 ) )
oE:Eq( "EmbR[i,j] = Parent[x,y] * Emb[x,i] * Emb[y,j]" )
oE:Eq( "Score[x,y] = EmbR[i,j] * Emb[x,i] * Emb[y,j]" ) // ~1 si (x,y) esta en Parent
tSoft := FWT_Sigmoid( tShifted, nTemperature )
Helpers (PRG)
| Función | Descripción |
|---|---|
TL_TensorND( aNested ) | Array Harbour anidado (cualquier rango) → FW_Tensor. |
TL_NotEq( n ) | [n,n] unos salvo la diagonal (guarda Datalog x != y). Igual que FWT_NotEq. |
TL_UnitEmb( n, d, nSeed ) | Embeddings unitarios [n,d] (deterministas). Igual que FWT_UnitEmb. |
TL_Rnd2 / TL_Rnd3 / TL_Const1 | Init estilo Xavier y vectores constantes usados por TTransformerTL. |
Motor C (fwtensor.c)
| Función | Descripción |
|---|---|
FWT_New( aShape ) / FWT_FromArray / FWT_ToArray / FWT_Shape / FWT_Size / FWT_Get / FWT_Set | Crear e inspeccionar tensores float32 planos (con GC). |
FWT_EinSum( cSpec, t1, ... ) | Suma de Einstein generalizada, p.ej. "ij,jk->ik", "pd,qd->pq", hasta 6 operandos. La primitiva de tensor logic. |
FWT_MatMul / FWT_Add / FWT_AddBias / FWT_Scale / FWT_Transpose | Álgebra lineal básica (MatMul usa BLAS si está disponible). |
FWT_SoftmaxLast / FWT_LNormLast | Softmax / layer norm sobre la última dimensión, cualquier rango. |
FWT_Softmax / FWT_LayerNorm | Variantes 2D por filas (ruta GPT-2 clásica). |
FWT_Sigmoid( t, nT ) / FWT_Step( t, nThresh ) / FWT_ReLU / FWT_Tanh / FWT_GELU | No-linealidades elemento a elemento; sigmoid acepta la temperatura de razonamiento. |
FWT_ActDeriv / FWT_SoftmaxBackwardLast / FWT_LNormBackwardLast | Kernels backward usados por TTensorLogic:Backward(). |
FWT_Mul / FWT_Sub / FWT_Fill / FWT_Clone / FWT_MaxDiff | Operaciones elementwise y utilidades. |
FWT_AdamUpdate / FWT_SumSq / FWT_ScaleInPlace | Paso Adam, normas de gradiente, clipping. |
FWT_GetBytes / FWT_SetBytes | Buffer float32 crudo como string binario (checkpointing). |
FWT_OneHot( aIds, nVocab ) | [len,vocab] one-hot desde ids 1-based. |
FWT_CausalMask( nSeq, nVal ) | Máscara aditiva de atención (0 en/bajo diagonal, nVal arriba; por defecto −1e9). |
FWT_SinPos( nSeq, nDim ) | Codificación posicional sinusoidal [seq,dim]. |
FWT_NotEq( n ) / FWT_UnitEmb( n, d, nSeed ) | Builders C para helpers Datalog / embeddings. |
FWT_GatherRows / FWT_SliceCols / FWT_SetCols | Lookup de embeddings y vistas de columnas (ruta transformer clásica). |
FWT_LoadSafe( cFile, nOffset, aShape ) | Lee un tensor float32 de un blob estilo safetensors. |
FWT_BlasActive() | .T. cuando hay backend OpenBLAS sgemm cargado. |
Aceleración OpenBLAS
fwtensor.c carga OpenBLAS dinámicamente en el primer uso — nunca es una
dependencia dura: libopenblas.dll en el PATH o junto al exe, o la variable de entorno
FWT_BLAS_DLL (con off fuerza los bucles C portables; también detecta builds
ILP64 como libscipy_openblas64_ de numpy). FWT_MatMul y todo
FWT_EinSum de 2 operandos reducible a un GEMM (por lotes) corre sobre
cblas_sgemm, usando las transposiciones de BLAS para que los patrones de atención no
necesiten reempaquetado.
| Patrón (Xeon W-2140B, 8c) | Bucles C | OpenBLAS | PyTorch 2.11 / MKL |
|---|---|---|---|
| matmul 1024x1024x1024 | 0.32 GFLOPS | 497 GFLOPS | 982 GFLOPS |
| matmul 512x512x512 | 0.32 GFLOPS | 290 GFLOPS | 806 GFLOPS |
| attention scores p=512 d=64 | 0.31 GFLOPS | 55 GFLOPS | 466 GFLOPS |
El OpenBLAS de numpy alcanza 446 GFLOPS en el matmul de 1024 en la misma máquina — el einsum de FWH iguala el rendimiento clase BLAS; la distancia restante a PyTorch son los kernels AVX-512 de MKL y menor overhead por llamada, no el lenguaje.
Ejemplos
samples/ai/tltutor.prg— compañero ejecutable del tutorial (lecciones 1–9 →tltutor.log).samples/ai/tensorlogic.prg— suite del motor: einsum vs matmul, cabeza de atención en 6 ecuaciones, punto fijo Datalog, comprobaciones de gradiente vs diferencias finitas, entrenamiento XOR (9 PASS).samples/ai/tltransformer.prg— transformer como ecuaciones, paridad con la clase clásica, entrenamiento + generación (3 PASS).samples/ai/tldatalog.prg— Datalog sobre un DBF familiar + razonamiento en embeddings con temperatura (6 PASS).samples/ai/tlbench.prg— benchmark de einsum, BLAS vs bucles C.samples/ai/tlminillm.prg— mini-LLM estilo DeepSeek/MiMo como ecuaciones puras: atención latente MLA, RoPE, mixture-of-experts, predicción multi-token (3 PASS).samples/ai/tlfwcode.prg— modelo de lenguaje char-level sobre fuentes FWH reales: Adam + warmup/cosine, acumulación por lotes, clipping, split train/val, checkpoint del mejor-val con resume, sampling top-k. Configurable conTLFW_*; resume conTLFW_RESUME=1.samples/ai/tlc.prg— receta de entrenamiento sintético de código en miniatura: generador + oráculo exacto, aprendizaje de ejecución (36–40% vs 10% azar) y decoding con máscara gramatical (100% válido incluso sin entrenar).
Compilar cualquier ejemplo igual:
cd /d C:\fwteam\samples\ai
C:\fwteam\samples\build_new.bat <nombre> hb32
<nombre>.exe