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.

flowchart LR EQ["Ecuación tensorial
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

  1. Compila la librería FWH con tu variante (p.ej. mfwh_new.bat hb32) para que tensorlogic.prg y fwtensor.c estén en la lib.
  2. Desde samples\ai, compila y ejecuta el compañero del tutorial:
    cd /d C:\fwteam\samples\ai
    C:\fwteam\samples\build_new.bat tltutor hb32
    tltutor.exe
    La salida queda en tltutor.log. Léela junto al tutorial paso a paso.
  3. Velocidad opcional: pon libopenblas.dll en el PATH o junto al exe (o define FWT_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

FormaSignificado
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.7071Factor 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étodoDescripció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étodoDescripció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étodoDescripció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ónDescripció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_Const1Init estilo Xavier y vectores constantes usados por TTransformerTL.

Motor C (fwtensor.c)

FunciónDescripción
FWT_New( aShape ) / FWT_FromArray / FWT_ToArray / FWT_Shape / FWT_Size / FWT_Get / FWT_SetCrear 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_LNormLastSoftmax / layer norm sobre la última dimensión, cualquier rango.
FWT_Softmax / FWT_LayerNormVariantes 2D por filas (ruta GPT-2 clásica).
FWT_Sigmoid( t, nT ) / FWT_Step( t, nThresh ) / FWT_ReLU / FWT_Tanh / FWT_GELUNo-linealidades elemento a elemento; sigmoid acepta la temperatura de razonamiento.
FWT_ActDeriv / FWT_SoftmaxBackwardLast / FWT_LNormBackwardLastKernels backward usados por TTensorLogic:Backward().
FWT_Mul / FWT_Sub / FWT_Fill / FWT_Clone / FWT_MaxDiffOperaciones elementwise y utilidades.
FWT_AdamUpdate / FWT_SumSq / FWT_ScaleInPlacePaso Adam, normas de gradiente, clipping.
FWT_GetBytes / FWT_SetBytesBuffer 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_SetColsLookup 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 COpenBLASPyTorch 2.11 / MKL
matmul 1024x1024x10240.32 GFLOPS497 GFLOPS982 GFLOPS
matmul 512x512x5120.32 GFLOPS290 GFLOPS806 GFLOPS
attention scores p=512 d=640.31 GFLOPS55 GFLOPS466 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

Compilar cualquier ejemplo igual:

cd /d C:\fwteam\samples\ai
C:\fwteam\samples\build_new.bat <nombre> hb32
<nombre>.exe

Ver también