gnai-creator commited on
Commit
b1fcbcb
·
verified ·
1 Parent(s): 3c5b2ff

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. README.md +28 -88
  2. handler.py +98 -9
README.md CHANGED
@@ -15,108 +15,48 @@ language:
15
  pipeline_tag: text-generation
16
  ---
17
 
18
- # 🧠 Noesis Decoder (AletheiaEngine)
19
 
20
- **Repository:** [gnai-creator/noesis-decoder](https://huggingface.co/gnai-creator/noesis-decoder)
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
- ## ⚙️ Model Architecture
 
35
 
36
- * **Framework:** PyTorch → ONNX Runtime
37
- * **Files:**
38
 
39
- * `model_infer.onnx` Inference model (optimized)
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
- ## 🧩 Example Usage
 
48
 
49
- ### 🔹 Python + ONNX Runtime
50
 
51
- ```python
52
- from huggingface_hub import hf_hub_download
53
- import onnxruntime as ort
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
- * **CPU:** Intel Sapphire Rapids (1vCPU / 2GB)
102
- * **GPU:** NVIDIA T4 for larger batch inference
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
- ## 📜 License
115
 
116
- This repository is distributed under the **Apache License 2.0**.
117
- See [LICENSE](./LICENSE) for details.
 
 
118
 
119
- ---
120
 
121
- > *“Truth is not imposed; it emerges from alignment.”*
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
- model_path = self.model_dir / "model.onnx"
57
- if not model_path.exists():
58
- available = ", ".join(sorted(str(p.name) for p in self.model_dir.glob("*.onnx"))) or "<none>"
59
- raise FileNotFoundError(
60
- "Could not locate 'model.onnx' in %s (available: %s)" % (self.model_dir, available)
61
- )
62
- return ort.InferenceSession(str(model_path), providers=["CPUExecutionProvider"])
 
 
 
 
 
 
 
 
 
 
 
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 = payload.get("vector") or payload.get("psi_s") or payload.get("inputs")
 
 
 
 
 
 
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] = {self.primary_input: self._coerce_array(psi)}
 
 
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)