Upload folder using huggingface_hub
Browse files- README.md +28 -88
- handler.py +98 -9
README.md
CHANGED
|
@@ -15,108 +15,48 @@ language:
|
|
| 15 |
pipeline_tag: text-generation
|
| 16 |
---
|
| 17 |
|
| 18 |
-
#
|
| 19 |
|
| 20 |
-
|
| 21 |
-
**Author:** Felipe M. Muniz (`gnai-creator`)
|
| 22 |
-
**License:** Apache-2.0
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
## 🔍 Overview
|
| 27 |
-
|
| 28 |
-
**Noesis Decoder** is the proprietary symbolic decoder of **AletheiaEngine** — a hybrid symbolic–neural system designed for *philosophical artificial general intelligence*.
|
| 29 |
-
|
| 30 |
-
Unlike conventional text generators, Noesis translates **symbolic embeddings (ψₛ)** into meaningful language based on *epistemic coherence*, rather than statistical prediction.
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
| 35 |
|
| 36 |
-
|
| 37 |
-
* **Files:**
|
| 38 |
|
| 39 |
-
|
| 40 |
-
* `noesis.pt` – PyTorch checkpoint (training artifact)
|
| 41 |
-
* `inference.py` – Custom ONNX handler
|
| 42 |
-
* **Input:** float32 symbolic vector, shape `[1, D]`
|
| 43 |
-
* **Output:** decoded float or token embeddings (depending on context)
|
| 44 |
-
|
| 45 |
-
---
|
| 46 |
|
| 47 |
-
|
|
|
|
| 48 |
|
| 49 |
-
###
|
| 50 |
|
| 51 |
-
```
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
import numpy as np
|
| 55 |
-
|
| 56 |
-
# Download ONNX model
|
| 57 |
-
onnx_path = hf_hub_download(
|
| 58 |
-
repo_id="gnai-creator/noesis-decoder",
|
| 59 |
-
filename="model_infer.onnx",
|
| 60 |
-
repo_type="model"
|
| 61 |
-
)
|
| 62 |
-
|
| 63 |
-
# Load runtime
|
| 64 |
-
sess = ort.InferenceSession(onnx_path, providers=["CPUExecutionProvider"])
|
| 65 |
-
input_name = sess.get_inputs()[0].name
|
| 66 |
-
output_name = sess.get_outputs()[0].name
|
| 67 |
-
|
| 68 |
-
# Example symbolic vector ψₛ
|
| 69 |
-
x = np.random.randn(1, 300).astype("float32")
|
| 70 |
-
|
| 71 |
-
# Run inference
|
| 72 |
-
y = sess.run([output_name], {input_name: x})[0]
|
| 73 |
-
print("Output shape:", y.shape)
|
| 74 |
```
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
## 💡 Training Data
|
| 79 |
-
|
| 80 |
-
Trained on **symbolic text pairs** generated from philosophical, logical, and reflective corpora within the AletheiaEngine ecosystem.
|
| 81 |
-
Goal: alignment between **symbolic intention (ψₛ)** and **natural language output**.
|
| 82 |
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
## 📊 Metrics (Indicative)
|
| 86 |
-
|
| 87 |
-
| Metric | Value | Description |
|
| 88 |
-
| ------------- | ------------ | ------------------------------------------ |
|
| 89 |
-
| Cosine(Q) | 0.83 | Symbolic alignment measure |
|
| 90 |
-
| Perplexity | 2.41 | Statistical readability proxy |
|
| 91 |
-
| Latency (CPU) | ~28 ms/token | Inference on Intel Sapphire Rapids (1vCPU) |
|
| 92 |
-
|
| 93 |
-
---
|
| 94 |
-
|
| 95 |
-
## 🚀 Deployment
|
| 96 |
-
|
| 97 |
-
This model is compatible with **Hugging Face Inference Endpoints** using the `Default` engine and the included `handler.py` handler.
|
| 98 |
-
|
| 99 |
-
Recommended hardware:
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
---
|
| 105 |
-
|
| 106 |
-
## ⚠️ Limitations
|
| 107 |
-
|
| 108 |
-
* Not a conventional LLM — requires symbolic vectors as input.
|
| 109 |
-
* Outputs are contextualized to Aletheia’s symbolic reasoning pipeline.
|
| 110 |
-
* Not suited for free-form text generation.
|
| 111 |
|
| 112 |
-
|
| 113 |
|
| 114 |
-
##
|
| 115 |
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
| 118 |
|
| 119 |
-
|
| 120 |
|
| 121 |
-
>
|
| 122 |
-
> — *Felipe M. Muniz (2025)*
|
|
|
|
| 15 |
pipeline_tag: text-generation
|
| 16 |
---
|
| 17 |
|
| 18 |
+
# Aletheia Noesis Decoder
|
| 19 |
|
| 20 |
+
Noesis é o decoder proprietário da AletheiaEngine. Ele traduz estados simbólicos \(\psi_s\) em linguagem natural garantindo a coerência epistemológica medida pela **Qualidade da Verdade (Q)**. Diferente de abordagens puramente estatísticas, o Noesis prioriza a fidelidade semântica entre a intenção simbólica e o texto gerado.
|
|
|
|
|
|
|
| 21 |
|
| 22 |
+
## Arquitetura
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
- **Transformer Decoder** com 8 camadas e atenção cruzada condicionada por \(\psi_s\) e estados contínuos.
|
| 25 |
+
- Entradas: vetor de intenção `psi_s`, memória lenta `state` opcional e sequência de tokens.
|
| 26 |
+
- Saídas: `logits`, embedding semântico `z_text` e métrica \(\hat{Q}\).
|
| 27 |
+
- Função de perda combina cross-entropy, coerência Q e penalizações de restrições.
|
| 28 |
|
| 29 |
+
## API FastAPI
|
|
|
|
| 30 |
|
| 31 |
+
O pacote inclui a aplicação `aletheia_decoder.api.app` com duas rotas:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
+
- `POST /generate`: recebe `psi_s`, `state`, `constraints`, prompts opcionais e parâmetros de decodificação. Retorna texto gerado, tokens, \(\hat{Q}\) e metadados.
|
| 34 |
+
- `POST /train`: permite fine-tuning local a partir de um dataset JSONL.
|
| 35 |
|
| 36 |
+
### Execução local
|
| 37 |
|
| 38 |
+
```bash
|
| 39 |
+
pip install -r requirements.txt
|
| 40 |
+
uvicorn aletheia_decoder.api.app:app --host 0.0.0.0 --port 8000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
```
|
| 42 |
|
| 43 |
+
## Treinamento
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
+
Use o script `aletheia_decoder/model/train.py` para treinar o modelo com dados anotados:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
+
```bash
|
| 48 |
+
python -m aletheia_decoder.model.train data/dataset.jsonl --epochs 200 --batch-size 4 --lr 3e-5
|
| 49 |
+
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
+
Os checkpoints podem ser salvos com `torch.save(model.state_dict(), "noesis.pt")` e exportados para ONNX se necessário.
|
| 52 |
|
| 53 |
+
## Deploy no Hugging Face Spaces
|
| 54 |
|
| 55 |
+
1. Faça fork deste diretório para um repositório separado.
|
| 56 |
+
2. Configure o Space no modo **FastAPI** apontando para `app:app`.
|
| 57 |
+
3. Defina as variáveis de ambiente de acordo com sua infraestrutura (ex.: `DECODER_ENDPOINT`).
|
| 58 |
+
4. Opcionalmente publique checkpoints `.pt` e `.onnx` nos assets do Space.
|
| 59 |
|
| 60 |
+
## Slogan
|
| 61 |
|
| 62 |
+
> **Noesis — onde a intenção se torna linguagem.**
|
|
|
handler.py
CHANGED
|
@@ -18,12 +18,63 @@ from __future__ import annotations
|
|
| 18 |
|
| 19 |
from dataclasses import dataclass
|
| 20 |
from pathlib import Path
|
|
|
|
| 21 |
from typing import Any, Mapping, MutableMapping, Optional
|
| 22 |
|
| 23 |
import numpy as np
|
| 24 |
import onnxruntime as ort
|
| 25 |
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
@dataclass(frozen=True)
|
| 28 |
class _ModelIO:
|
| 29 |
"""Snapshot of ONNX input and output metadata."""
|
|
@@ -42,6 +93,8 @@ class EndpointHandler:
|
|
| 42 |
|
| 43 |
self.primary_input = self.io.inputs[0].name
|
| 44 |
self.slow_input = self._find_input("slow_state")
|
|
|
|
|
|
|
| 45 |
self._defaults = {
|
| 46 |
node.name: self._zeros_like(node)
|
| 47 |
for node in self.io.inputs
|
|
@@ -53,13 +106,24 @@ class EndpointHandler:
|
|
| 53 |
self._slow_fallback = None
|
| 54 |
|
| 55 |
def _load_session(self) -> ort.InferenceSession:
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
)
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
@property
|
| 65 |
def _input_map(self) -> Mapping[str, ort.NodeArg]:
|
|
@@ -75,6 +139,14 @@ class EndpointHandler:
|
|
| 75 |
return node.name
|
| 76 |
return None
|
| 77 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
@staticmethod
|
| 79 |
def _zeros_like(node: ort.NodeArg) -> np.ndarray:
|
| 80 |
shape: list[int] = []
|
|
@@ -99,11 +171,19 @@ class EndpointHandler:
|
|
| 99 |
def _prepare_inputs(self, payload: Mapping[str, Any]) -> MutableMapping[str, np.ndarray]:
|
| 100 |
psi = payload.get("psi")
|
| 101 |
if psi is None:
|
| 102 |
-
psi =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
if psi is None:
|
| 104 |
raise KeyError("Payload must include a 'psi' field containing the symbolic vector.")
|
| 105 |
|
| 106 |
-
inputs: MutableMapping[str, np.ndarray] = {
|
|
|
|
|
|
|
| 107 |
|
| 108 |
if self.slow_input is not None:
|
| 109 |
slow_value = payload.get("slow_state") or payload.get("slow") or payload.get("state")
|
|
@@ -117,6 +197,15 @@ class EndpointHandler:
|
|
| 117 |
|
| 118 |
return inputs
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
@staticmethod
|
| 121 |
def _format_output(name: str, value: np.ndarray) -> Any:
|
| 122 |
value = np.asarray(value, dtype=np.float32)
|
|
|
|
| 18 |
|
| 19 |
from dataclasses import dataclass
|
| 20 |
from pathlib import Path
|
| 21 |
+
import re
|
| 22 |
from typing import Any, Mapping, MutableMapping, Optional
|
| 23 |
|
| 24 |
import numpy as np
|
| 25 |
import onnxruntime as ort
|
| 26 |
|
| 27 |
|
| 28 |
+
_WORD_RE = re.compile(r"\w+", re.UNICODE)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class _TextEncoder:
|
| 32 |
+
"""Deterministic text → vector encoder.
|
| 33 |
+
|
| 34 |
+
The Hugging Face Inference Endpoints frequently pass user prompts as
|
| 35 |
+
strings via the ``inputs`` field. The Noesis decoder, however, expects a
|
| 36 |
+
symbolic vector (``psi``) as input. To provide a graceful fallback the
|
| 37 |
+
handler lazily converts short text prompts into a stable float32 vector by
|
| 38 |
+
hashing tokens onto a hypersphere. This mirrors the lightweight
|
| 39 |
+
``TextEncoder256`` implementation bundled with the full AletheiaEngine
|
| 40 |
+
package while avoiding a heavy import dependency inside the endpoint
|
| 41 |
+
container.
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
def __init__(self, dim: int) -> None:
|
| 45 |
+
self.dim = dim
|
| 46 |
+
|
| 47 |
+
@staticmethod
|
| 48 |
+
def _tokens(text: str) -> list[str]:
|
| 49 |
+
return [tok.lower() for tok in _WORD_RE.findall(text)]
|
| 50 |
+
|
| 51 |
+
@staticmethod
|
| 52 |
+
def _seed(tok: str) -> int:
|
| 53 |
+
# FNV-1a hash for determinism across processes/platforms.
|
| 54 |
+
value = 2166136261
|
| 55 |
+
for byte in tok.encode("utf-8"):
|
| 56 |
+
value ^= byte
|
| 57 |
+
value = (value * 16777619) & 0xFFFFFFFF
|
| 58 |
+
return int(value)
|
| 59 |
+
|
| 60 |
+
def encode(self, text: str) -> np.ndarray:
|
| 61 |
+
tokens = self._tokens(text)
|
| 62 |
+
if not tokens:
|
| 63 |
+
return np.zeros((1, self.dim), dtype=np.float32)
|
| 64 |
+
|
| 65 |
+
vecs = []
|
| 66 |
+
for tok in tokens:
|
| 67 |
+
rs = np.random.RandomState(self._seed(tok))
|
| 68 |
+
embedding = rs.normal(0.0, 1.0, size=(self.dim,)).astype(np.float32)
|
| 69 |
+
norm = float(np.linalg.norm(embedding)) or 1.0
|
| 70 |
+
vecs.append(embedding / norm)
|
| 71 |
+
|
| 72 |
+
stacked = np.stack(vecs, axis=0)
|
| 73 |
+
pooled = stacked.mean(axis=0, dtype=np.float32, keepdims=True)
|
| 74 |
+
pooled_norm = float(np.linalg.norm(pooled)) or 1.0
|
| 75 |
+
return pooled / pooled_norm
|
| 76 |
+
|
| 77 |
+
|
| 78 |
@dataclass(frozen=True)
|
| 79 |
class _ModelIO:
|
| 80 |
"""Snapshot of ONNX input and output metadata."""
|
|
|
|
| 93 |
|
| 94 |
self.primary_input = self.io.inputs[0].name
|
| 95 |
self.slow_input = self._find_input("slow_state")
|
| 96 |
+
self._primary_dim = self._infer_primary_dim()
|
| 97 |
+
self._text_encoder = _TextEncoder(self._primary_dim)
|
| 98 |
self._defaults = {
|
| 99 |
node.name: self._zeros_like(node)
|
| 100 |
for node in self.io.inputs
|
|
|
|
| 106 |
self._slow_fallback = None
|
| 107 |
|
| 108 |
def _load_session(self) -> ort.InferenceSession:
|
| 109 |
+
"""Load the ONNX session, tolerating alternate filenames."""
|
| 110 |
+
|
| 111 |
+
preferred_names = ("model.onnx", "model_infer.onnx")
|
| 112 |
+
for name in preferred_names:
|
| 113 |
+
candidate = self.model_dir / name
|
| 114 |
+
if candidate.exists():
|
| 115 |
+
return ort.InferenceSession(str(candidate), providers=["CPUExecutionProvider"])
|
| 116 |
+
|
| 117 |
+
available = sorted(str(p.name) for p in self.model_dir.glob("*.onnx"))
|
| 118 |
+
if len(available) == 1:
|
| 119 |
+
# Fall back to the lone ONNX artefact if it has a non-standard name.
|
| 120 |
+
return ort.InferenceSession(str(self.model_dir / available[0]), providers=["CPUExecutionProvider"])
|
| 121 |
+
|
| 122 |
+
choices = ", ".join(available) or "<none>"
|
| 123 |
+
raise FileNotFoundError(
|
| 124 |
+
"Could not locate any of %s in %s (available: %s)"
|
| 125 |
+
% (", ".join(preferred_names), self.model_dir, choices)
|
| 126 |
+
)
|
| 127 |
|
| 128 |
@property
|
| 129 |
def _input_map(self) -> Mapping[str, ort.NodeArg]:
|
|
|
|
| 139 |
return node.name
|
| 140 |
return None
|
| 141 |
|
| 142 |
+
def _infer_primary_dim(self) -> int:
|
| 143 |
+
node = self._input_map[self.primary_input]
|
| 144 |
+
for dim in reversed(node.shape):
|
| 145 |
+
if isinstance(dim, int) and dim > 0:
|
| 146 |
+
return dim
|
| 147 |
+
# Conservative default matching TextEncoder256.
|
| 148 |
+
return 256
|
| 149 |
+
|
| 150 |
@staticmethod
|
| 151 |
def _zeros_like(node: ort.NodeArg) -> np.ndarray:
|
| 152 |
shape: list[int] = []
|
|
|
|
| 171 |
def _prepare_inputs(self, payload: Mapping[str, Any]) -> MutableMapping[str, np.ndarray]:
|
| 172 |
psi = payload.get("psi")
|
| 173 |
if psi is None:
|
| 174 |
+
psi = (
|
| 175 |
+
payload.get("vector")
|
| 176 |
+
or payload.get("psi_s")
|
| 177 |
+
or payload.get("inputs")
|
| 178 |
+
or payload.get("prompt")
|
| 179 |
+
or payload.get("text")
|
| 180 |
+
)
|
| 181 |
if psi is None:
|
| 182 |
raise KeyError("Payload must include a 'psi' field containing the symbolic vector.")
|
| 183 |
|
| 184 |
+
inputs: MutableMapping[str, np.ndarray] = {
|
| 185 |
+
self.primary_input: self._vector_from_payload(psi)
|
| 186 |
+
}
|
| 187 |
|
| 188 |
if self.slow_input is not None:
|
| 189 |
slow_value = payload.get("slow_state") or payload.get("slow") or payload.get("state")
|
|
|
|
| 197 |
|
| 198 |
return inputs
|
| 199 |
|
| 200 |
+
def _vector_from_payload(self, value: Any) -> np.ndarray:
|
| 201 |
+
if isinstance(value, str):
|
| 202 |
+
return self._text_encoder.encode(value)
|
| 203 |
+
|
| 204 |
+
if isinstance(value, (list, tuple)) and value and all(isinstance(v, str) for v in value):
|
| 205 |
+
return self._text_encoder.encode(" ".join(value))
|
| 206 |
+
|
| 207 |
+
return self._coerce_array(value)
|
| 208 |
+
|
| 209 |
@staticmethod
|
| 210 |
def _format_output(name: str, value: np.ndarray) -> Any:
|
| 211 |
value = np.asarray(value, dtype=np.float32)
|