Tensor Logic

Fonte: source/classes/tensorlogic.prg, source/function/fwtensor.c

FiveWin implementa Tensor Logic: The Language of AI de Pedro Domingos (arXiv:2510.12269). A ideia-chave: uma regra lógica e uma soma de Einstein são a mesma operação. Um programa é um conjunto de equações tensoriais; cada equação faz o join dos tensores do lado direito (produto sobre índices compartilhados), projeta os índices que não aparecem no lado esquerdo (soma) e aplica opcionalmente uma não-linearidade. Um mesmo motor executa redes neurais (atenção, MLPs) e raciocínio simbólico (Datalog sobre tabelas DBF) — e pode diferenciar qualquer programa automaticamente.

flowchart LR EQ["Equação tensorial
Y[i] = f( W[i,j] * X[j] )"] --> N[Neural: atenção, MLP, transformer] EQ --> S[Simbólico: regras Datalog sobre DBFs] EQ --> G["Autodiff: gradiente =
mesmo einsum, fatores trocados"]

Início rápido

  1. Compile a biblioteca FWH com sua variante (p.ex. mfwh_new.bat hb32) para que tensorlogic.prg e fwtensor.c estejam na lib.
  2. A partir de samples\ai, compile e execute o companheiro do tutorial:
    cd /d C:\fwteam\samples\ai
    C:\fwteam\samples\build_new.bat tltutor hb32
    tltutor.exe
    A saída fica em tltutor.log. Leia-a junto com o tutorial passo a passo.
  3. Velocidade opcional: coloque libopenblas.dll no PATH ou junto ao exe (ou defina FWT_BLAS_DLL). Verifique com ? FWT_BlasActive().

Equações tensoriais

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] )" )    // um perceptron, uma equacao
oTL:Run()                                          // forward chaining até ponto fixo
? oTL:GetArray( "Y" )                              // {1, 0}

Índices repetidos à direita são joinados; os ausentes à esquerda são somados. Não-linearidades: step, sigmoid, relu, tanh, gelu, softmax (último índice), lnorm (layer norm, último índice, sem parte afim — gamma/beta são expressas como equações adicionais). Fatores numéricos escalam um termo: "S[p,q] = Q[p,k] * K[q,k] * 0.7071".

Sintaxe de equações

FormaSignificado
Out[i,k] = A[i,j] * B[j,k]Join em j, projeta j (produto matricial).
Y[i] = step( W[i,j] * X[j] + B[i] )Vários termos aditivos + não-linearidade.
S[p,q] = Q[p,d] * K[q,d] * 0.7071Factor numérico de escala en um termo.
A[p,q.] = softmax( S[p,q] )O . final marca o eixo do softmax.
N[p,d] = lnorm( R[p,d] )Layer norm sobre o último índice.
N2[p,d] = N[p,d] * G[d] + Ones[p] * B[d]Parte afim gamma/beta após lnorm.

Termos com qualquer número de fatores são avaliados encadeando contrações em pares: só sobrevivem as letras ainda necessárias para a saída, de modo que cada passo pode usar o caminho rápido GEMM do OpenBLAS.

Se o parse falhar, Eq() retorna .F. e preenche oTL:cError.

TTensorLogic

MétodoDescrição
New()Cria um programa vazio (tensores, equações, estado Adam).
Let( cName, uData )Liga um tensor (array aninhado de qualquer rank ou FW_Tensor).
Param( cName, uData )Let + marcar como treinável (atualizado por Step() / StepAdam()).
Eq( cEquation )Adiciona uma equação tensorial (string). Retorna .F. se não parsear.
Run( nMaxPasses, nTol )Forward chaining até ponto fixo (padrão 100 passadas, tol 1e-6). Regras Datalog recursivas convergem.
Forward()Uma passagem em ordem de declaração; cacheia pré-ativações para Backward(). Use ao treinar, não Run().
ZeroGrad()Zera todos os gradientes acumulados.
Seed( cName, tGrad )Define dL/dName (gradiente da perda em relação a um tensor de saída).
Backward()Varredura inversa. O gradiente de uma equação = mesmo einsum com fator e saída trocados.
GetGrad( cName )Retorna dL/dName como FW_Tensor.
Step( nLr )SGD: cada Param -= lr * grad.
StepAdam( nLr, nB1, nB2, nEps )Otimizador Adam (kernel C FWT_AdamUpdate com correção de viés). Padrão: β1=0.9, β2=0.999, ε=1e-8.
ClipGrads( nMaxNorm )Clipping global L2 de gradientes. Retorna a norma pré-clip.
ClearActGrads()Remove gradientes de ativações, mantém os de parâmetros (acumulação por mini-batch).
SaveParams( cFile, hUser ) / LoadParams( cFile )Checkpoint: cada tensor Param() como float32 crudo más un hash opcional de usuario. Load retorna el hash o NIL si falla.
Get( cName ) / GetArray( cName )Tensor resultado / array aninhado (2D).
LetDbf( cName, cAlias, aFields, oDomain )Registros DBF → tensor booleano de relação (uma dimensão por campo).
LetSet( cName, aEntities, oDomain )Lista de nomes → tensor booleano unário.
Query( cName, oDomain, nThresh )Tensor resultado → tuplas de nomes (limiar padrão 0.5).

DATA: hTensors, aEqs, cError, hPre, hGrads, aParams, hAdamM, hAdamV, nAdamT.

Padrões de loop de treino

// Amostra ú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 )

// Acumulação por mini-batch (estilo TTransformerTL)
oTL:ZeroGrad()
for i := 1 to nBatch
   oT:AccumStep( aIds[i], aTgt[i], 1 / nBatch )   // grads de params somam
next
oTL:ClipGrads( 1.0 )
oTL:StepAdam( nLr )

TTLDomain

Mapa entidade ↔ índice compartilhado por todas as relações para que as dimensões coincidam.

MétodoDescrição
New()Domínio vazio.
Add( cName )Registra el nombre si es nuevo; retorna id 1-based.
Id( cName )Id ou 0 se não existir.
Name( nId )Nome do id.
Len()Número de entidades.
Harvest( cAlias, aFields )Registra todos os valores desses campos no alias aberto. Chamar antes de LetDbf().

TTransformerTL

Fachada transformer estilo GPT sobre um programa de tensor logic (~15 equações por camada). As cabeças são um índice (sem fatiar colunas): WQ[d,h,k] absorve o repart por cabeça e WO[h,k,d] absorve concat+Wo. Com os mesmos pesos reproduz ForwardSeq() da classe Transformer clássica de perto, e treina e gera com o autodiff genérico — zero código de backprop específico.

MétodoDescrição
New( nLayers, dModel, nHeads, nVocab )Constrói o programa + inicializa parâmetros (estilo Xavier). Padrão: 1, 8, 2, 4.
BuildProgram()Emite residual, atenção multi-cabeça, FFN e projeção para vocabulário como equações.
InitParams()Tensores Param() aleatórios (reproduzíveis) para todos os pesos.
LoadFromTransformer( oT )Copia pesos de um Transformer clássico (testes de paridade).
SetInput( aIds )One-hot X, máscara causal, Pos, vetores de uns.
ForwardSeq( aIds )→ array aninhado de probs [seq][vocab].
TrainStep( aIds, aTargets, nLr )Passo de perda CE (ZeroGrad + Backward + Step). Retorna a perda.
AccumStep( aIds, aTargets, nScale )Só acumula gradientes (para mini-batches). Retorna a perda escalada.
EvalLoss( aIds, aTargets )CE só forward (validação).
Generate( aSeed, nNew )Geração 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 atenção como equações (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 tabelas DBF

USE tlfam ALIAS FAM NEW              // campos PARENT, CHILD
oDom := TTLDomain():New()
oDom:Harvest( "FAM", { "PARENT", "CHILD" } )       // fixar o domínio primeiro
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 )                                      // ponto fixo
AEval( oTL:Query( "Ancestor", oDom ), {| a | QOut( a[1] + " -> " + a[2] ) } )

Raciocínio em espaço de embeddings (a ponte neuro-simbólica do paper): entidades como vetores unitários, a relação embutida, consulta inversa e sigmoide com temperatura — T→0 dedutivo (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 se (x,y) está em Parent
tSoft := FWT_Sigmoid( tShifted, nTemperature )

Helpers (PRG)

FunçãoDescrição
TL_TensorND( aNested )Array Harbour aninhado (qualquer rank) → FW_Tensor.
TL_NotEq( n )[n,n] uns exceto a diagonal (guarda Datalog x != y). Igual a FWT_NotEq.
TL_UnitEmb( n, d, nSeed )Embeddings unitarios [n,d] (determinísticos). Igual a FWT_UnitEmb.
TL_Rnd2 / TL_Rnd3 / TL_Const1Init estilo Xavier e vetores constantes usados por TTransformerTL.

Motor C (fwtensor.c)

FunçãoDescrição
FWT_New( aShape ) / FWT_FromArray / FWT_ToArray / FWT_Shape / FWT_Size / FWT_Get / FWT_SetCriar e inspecionar tensores float32 planos (com GC).
FWT_EinSum( cSpec, t1, ... )Soma de Einstein generalizada, p.ex. "ij,jk->ik", "pd,qd->pq", até 6 operandos. A primitiva do tensor logic.
FWT_MatMul / FWT_Add / FWT_AddBias / FWT_Scale / FWT_TransposeÁlgebra linear básica (MatMul usa BLAS se disponível).
FWT_SoftmaxLast / FWT_LNormLastSoftmax / layer norm sobre a última dimensão, qualquer rank.
FWT_Softmax / FWT_LayerNormVariantes 2D por linhas (caminho GPT-2 clássico).
FWT_Sigmoid( t, nT ) / FWT_Step( t, nThresh ) / FWT_ReLU / FWT_Tanh / FWT_GELUNão-linearidades elemento a elemento; sigmoid aceita a temperatura de raciocínio.
FWT_ActDeriv / FWT_SoftmaxBackwardLast / FWT_LNormBackwardLastKernels backward usados por TTensorLogic:Backward().
FWT_Mul / FWT_Sub / FWT_Fill / FWT_Clone / FWT_MaxDiffOperações elementwise e utilitários.
FWT_AdamUpdate / FWT_SumSq / FWT_ScaleInPlacePasso Adam, normas de gradiente, clipping.
FWT_GetBytes / FWT_SetBytesBuffer float32 bruto como string binário (checkpointing).
FWT_OneHot( aIds, nVocab )[len,vocab] one-hot a partir de ids 1-based.
FWT_CausalMask( nSeq, nVal )Máscara aditiva de atenção (0 em/abaixo da diagonal, nVal acima; padrão −1e9).
FWT_SinPos( nSeq, nDim )Codificação 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 e vistas de colunas (caminho transformer clássico).
FWT_LoadSafe( cFile, nOffset, aShape )Lê um tensor float32 de um blob estilo safetensors.
FWT_BlasActive().T. quando há backend OpenBLAS sgemm carregado.

Aceleração OpenBLAS

fwtensor.c carrega OpenBLAS dinamicamente no primeiro uso — nunca é uma dependência obrigatória: libopenblas.dll no PATH ou junto ao exe, o la variable de entorno FWT_BLAS_DLL (com off força os loops C portáveis; também detecta builds ILP64 como libscipy_openblas64_ do numpy). FWT_MatMul e todo FWT_EinSum de 2 operandos redutível a um GEMM (em lote) roda sobre cblas_sgemm, usando as transposições do BLAS para que os padrões de atenção não precisem de reempacotamento.

Padrão (Xeon W-2140B, 8c)Loops 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

O OpenBLAS do numpy alcança 446 GFLOPS no matmul de 1024 na mesma máquina — o einsum do FWH iguala o desempenho classe BLAS; a distância restante ao PyTorch são os kernels AVX-512 do MKL e menor overhead por chamada, não a linguagem.

Exemplos

Compilar qualquer exemplo da mesma forma:

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

Veja também