dual-loop-controller 2.0.0a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dual_loop_controller-2.0.0a1/LICENSE +21 -0
- dual_loop_controller-2.0.0a1/PKG-INFO +141 -0
- dual_loop_controller-2.0.0a1/README.md +112 -0
- dual_loop_controller-2.0.0a1/dual_loop/__init__.py +22 -0
- dual_loop_controller-2.0.0a1/dual_loop/adapters/__init__.py +3 -0
- dual_loop_controller-2.0.0a1/dual_loop/adapters/latent_adapter.py +108 -0
- dual_loop_controller-2.0.0a1/dual_loop/benchmarks/__init__.py +3 -0
- dual_loop_controller-2.0.0a1/dual_loop/benchmarks/comprehensive_suite.py +147 -0
- dual_loop_controller-2.0.0a1/dual_loop/benchmarks/graph_reasoning.py +60 -0
- dual_loop_controller-2.0.0a1/dual_loop/benchmarks/halting_audit.py +98 -0
- dual_loop_controller-2.0.0a1/dual_loop/benchmarks/initiative_benchmark.py +204 -0
- dual_loop_controller-2.0.0a1/dual_loop/controller.py +176 -0
- dual_loop_controller-2.0.0a1/dual_loop/decoder.py +182 -0
- dual_loop_controller-2.0.0a1/dual_loop/halting.py +71 -0
- dual_loop_controller-2.0.0a1/dual_loop/memory.py +55 -0
- dual_loop_controller-2.0.0a1/dual_loop_controller.egg-info/PKG-INFO +141 -0
- dual_loop_controller-2.0.0a1/dual_loop_controller.egg-info/SOURCES.txt +22 -0
- dual_loop_controller-2.0.0a1/dual_loop_controller.egg-info/dependency_links.txt +1 -0
- dual_loop_controller-2.0.0a1/dual_loop_controller.egg-info/requires.txt +6 -0
- dual_loop_controller-2.0.0a1/dual_loop_controller.egg-info/top_level.txt +1 -0
- dual_loop_controller-2.0.0a1/pyproject.toml +53 -0
- dual_loop_controller-2.0.0a1/setup.cfg +4 -0
- dual_loop_controller-2.0.0a1/tests/test_adapter_integration.py +92 -0
- dual_loop_controller-2.0.0a1/tests/test_dual_loop.py +110 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew chen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dual-loop-controller
|
|
3
|
+
Version: 2.0.0a1
|
|
4
|
+
Summary: A hardware-aligned, manifold-preserving latent deliberation framework for Transformers
|
|
5
|
+
Author: Ch3nOff
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Ch3nOff/dual-loop-controller
|
|
8
|
+
Project-URL: Repository, https://github.com/Ch3nOff/dual-loop-controller.git
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/Ch3nOff/dual-loop-controller/issues
|
|
10
|
+
Keywords: deep-learning,transformers,latent-reasoning,cognitive-architecture,system-2-thinking,pytorch
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: torch>=2.0.0
|
|
24
|
+
Requires-Dist: numpy>=1.24.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build; extra == "dev"
|
|
27
|
+
Requires-Dist: twine; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# Dual-Loop Cognitive Controller v2.0
|
|
31
|
+
> **A Hardware-Aligned Latent Deliberation Framework for Transformers: Architecture & Empirical Analysis**
|
|
32
|
+
|
|
33
|
+
[](tests/)
|
|
34
|
+
[](https://pytorch.org/)
|
|
35
|
+
[](#empirical-findings)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
|
|
38
|
+
Standard Autoregressive Transformers perform uniform $O(1)$ layer computation per token regardless of task complexity. While Chain-of-Thought (CoT) prompting allows multi-step reasoning, it expends significant output token bandwidth and introduces serial generation latency.
|
|
39
|
+
|
|
40
|
+
The **Dual-Loop Cognitive Controller** investigates decoupling deliberation from token generation into two loops:
|
|
41
|
+
1. **Outer Loop (Executive Deliberation / System 2)**: Runs recursive state transitions in a continuous latent space without emitting intermediate tokens.
|
|
42
|
+
2. **Inner Loop (Language Generation / System 1)**: Reads the matured latent thoughts ($H_{\text{thought}}$) as a soft prefix to decode final text responses.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Empirical Findings & Negative Results (The Unvarnished Truth)
|
|
47
|
+
|
|
48
|
+
To maintain strict scientific integrity, this repository reports **the actual, measured behavior of the model trained end-to-end (225,959 parameters, 35 epochs, 3,500 samples, 16 nodes, chance baseline = 6.25%)**, rather than idealized projections.
|
|
49
|
+
|
|
50
|
+
### 1. The Model Learns Real Relational Signals
|
|
51
|
+
* **Final Test Accuracy (3-Hop Graph Reasoning)**: **29.4%** vs. random chance **6.25%** (~4.7x better than random guessing).
|
|
52
|
+
* This confirms that the weight-tied recurrent Transformer and CWM buffer are capable of gradient propagation and multi-step pattern learning.
|
|
53
|
+
|
|
54
|
+
### 2. The Absence of Monotonic Test-Time Compute Scaling
|
|
55
|
+
A central theoretical hypothesis of recurrent latent pondering is that increasing inference steps ($K$) will progressively improve answer accuracy. **On this 225K parameter implementation, this claim does not hold**:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
========================================================================================
|
|
59
|
+
EMPIRICAL TEST-TIME COMPUTE EVALUATION (Checkpoint: checkpoint_trained_dualloop.pt)
|
|
60
|
+
========================================================================================
|
|
61
|
+
Ponder Steps (K) | Test Accuracy (500 samples) | Mean Predictive Entropy (nats)
|
|
62
|
+
----------------------------------------------------------------------------------------
|
|
63
|
+
K = 0 (No Ponder)| 27.4% - 30.6% | 1.332 - 1.362 nats
|
|
64
|
+
K = 1 | 28.2% | 1.370 nats
|
|
65
|
+
K = 2 | 30.6% | 1.307 nats
|
|
66
|
+
K = 3 (Trained) | 30.4% | 1.268 nats
|
|
67
|
+
K = 4 | 30.0% | 1.268 nats
|
|
68
|
+
K = 5 | 31.6% | 1.275 nats
|
|
69
|
+
========================================================================================
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Scientific Diagnosis**:
|
|
73
|
+
* **Flat/Noisy Trajectory**: $K=0$ (bypassing the Outer Loop entirely) performs at parity with or slightly exceeds intermediate $K$ values.
|
|
74
|
+
* **Representational Drift**: Tracing individual predictions step-by-step reveals that while some cases improve with pondering, others degrade (e.g. correct at $K=0..1$, but diverging to incorrect candidates at $K=2..3$ due to distractor pull).
|
|
75
|
+
* **Scale Artifact vs. Fundamental Limit**: At 225K parameters, the latent space lacks the geometric capacity to preserve stable multi-step deductions without explicit discrete token anchors. Pondering without token-level supervision introduces noise as much as refinement.
|
|
76
|
+
|
|
77
|
+
### 3. Degradation Under Context Distractors (Stress Test)
|
|
78
|
+
When distractor edge count increases on 3-hop graphs, performance decays steadily:
|
|
79
|
+
* **6 Edges**: 31.0%
|
|
80
|
+
* **8 Edges**: 21.0%
|
|
81
|
+
* **12 Edges**: 13.7%
|
|
82
|
+
* **16 Edges**: 10.3%
|
|
83
|
+
|
|
84
|
+
### 4. Dynamic Halting Audit & The Pareto Trade-Off
|
|
85
|
+
A naive threshold like `0.5 nats` fails because the model operates at `~1.25–1.40 nats` (resulting in static $K=3.00$). Evaluating per-sample dynamic halting across a threshold sweep reveals the true **Accuracy vs. Compute Pareto Frontier**:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
========================================================================================
|
|
89
|
+
PER-SAMPLE DYNAMIC HALTING PARETO FRONTIER (500 Test Samples)
|
|
90
|
+
========================================================================================
|
|
91
|
+
Entropy Threshold | Test Accuracy | Avg Steps | % Halt @ K=1 | % Halt @ K=2 | % Halt @ K=3
|
|
92
|
+
----------------------------------------------------------------------------------------
|
|
93
|
+
tau = 0.80 nats | 28.0% | 2.81 | 7.6% | 3.8% | 88.6%
|
|
94
|
+
tau = 1.15 nats | 28.0% | 2.42 | 24.2% | 9.6% | 66.2%
|
|
95
|
+
tau = 1.25 nats | 28.8% | 2.23 | 32.2% | 12.2% | 55.6%
|
|
96
|
+
tau = 1.40 nats | 29.4% | 1.89 | 49.0% | 13.2% | 37.8%
|
|
97
|
+
========================================================================================
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Justified Operating Point**:
|
|
101
|
+
* **$\tau = 1.25 \dots 1.40\text{ nats}$** is the justifiable Pareto region: it achieves a **37% reduction in compute** (average **1.89 steps** vs. 3.00) while maintaining peak accuracy (**29.4%**), with a genuinely heterogeneous distribution across steps ($49\%$ at $K=1$, $13\%$ at $K=2$, $38\%$ at $K=3$).
|
|
102
|
+
* Arbitrary default thresholds (like 0.5 or blindly using a batch-mean percentile) collapse execution to all-or-nothing extremes ($3.00$ or $1.00$). Dynamic halting must always be calibrated per-sample against empirical validation entropy.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Architectural Implementation
|
|
107
|
+
|
|
108
|
+
Despite the scaling limits at small model regimes, the repository provides clean, production-grade PyTorch implementations of the core modules:
|
|
109
|
+
|
|
110
|
+
* **Cognitive Working Memory (`dual_loop/memory.py`)**: Compresses context into $M \ll N$ slots in GPU SRAM/L2 cache to avoid HBM memory bandwidth roundtrips.
|
|
111
|
+
* **Top-K Capacity Routing (`dual_loop/controller.py`)**: Enforces static tensor shapes $[B, K_{\text{cap}}, D]$ to eliminate CUDA warp divergence (MoD-style).
|
|
112
|
+
* **Calibrated Entropy Halting (`dual_loop/halting.py`)**: Adaptive stopping based on predictive uncertainty and convergence delta.
|
|
113
|
+
* **Latent Deliberation Adapter (`dual_loop/adapters/latent_adapter.py`)**: A plug-and-play mid-network adapter for pretrained LLMs (e.g., Llama, Qwen).
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Quickstart
|
|
118
|
+
|
|
119
|
+
### 1. Installation
|
|
120
|
+
```bash
|
|
121
|
+
git clone https://github.com/Ch3nOff/dual-loop-controller.git
|
|
122
|
+
cd dual-loop-controller
|
|
123
|
+
python -m pip install torch numpy
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 2. Running Component Tests (Verifying Shapes & Gradients)
|
|
127
|
+
```bash
|
|
128
|
+
python -m unittest discover -s tests -p "test_*.py"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 3. Running the Honest Benchmark Suite (Live Tensor Computations)
|
|
132
|
+
```bash
|
|
133
|
+
python -m dual_loop.benchmarks.comprehensive_suite
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 4. Re-Training from Scratch
|
|
137
|
+
```bash
|
|
138
|
+
python train.py --epochs 35 --hops 3 --k_steps 3 --d_model 64
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
For the complete technical paper and theoretical post-mortem, see [WHITEPAPER.md](WHITEPAPER.md).
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Dual-Loop Cognitive Controller v2.0
|
|
2
|
+
> **A Hardware-Aligned Latent Deliberation Framework for Transformers: Architecture & Empirical Analysis**
|
|
3
|
+
|
|
4
|
+
[](tests/)
|
|
5
|
+
[](https://pytorch.org/)
|
|
6
|
+
[](#empirical-findings)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
Standard Autoregressive Transformers perform uniform $O(1)$ layer computation per token regardless of task complexity. While Chain-of-Thought (CoT) prompting allows multi-step reasoning, it expends significant output token bandwidth and introduces serial generation latency.
|
|
10
|
+
|
|
11
|
+
The **Dual-Loop Cognitive Controller** investigates decoupling deliberation from token generation into two loops:
|
|
12
|
+
1. **Outer Loop (Executive Deliberation / System 2)**: Runs recursive state transitions in a continuous latent space without emitting intermediate tokens.
|
|
13
|
+
2. **Inner Loop (Language Generation / System 1)**: Reads the matured latent thoughts ($H_{\text{thought}}$) as a soft prefix to decode final text responses.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Empirical Findings & Negative Results (The Unvarnished Truth)
|
|
18
|
+
|
|
19
|
+
To maintain strict scientific integrity, this repository reports **the actual, measured behavior of the model trained end-to-end (225,959 parameters, 35 epochs, 3,500 samples, 16 nodes, chance baseline = 6.25%)**, rather than idealized projections.
|
|
20
|
+
|
|
21
|
+
### 1. The Model Learns Real Relational Signals
|
|
22
|
+
* **Final Test Accuracy (3-Hop Graph Reasoning)**: **29.4%** vs. random chance **6.25%** (~4.7x better than random guessing).
|
|
23
|
+
* This confirms that the weight-tied recurrent Transformer and CWM buffer are capable of gradient propagation and multi-step pattern learning.
|
|
24
|
+
|
|
25
|
+
### 2. The Absence of Monotonic Test-Time Compute Scaling
|
|
26
|
+
A central theoretical hypothesis of recurrent latent pondering is that increasing inference steps ($K$) will progressively improve answer accuracy. **On this 225K parameter implementation, this claim does not hold**:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
========================================================================================
|
|
30
|
+
EMPIRICAL TEST-TIME COMPUTE EVALUATION (Checkpoint: checkpoint_trained_dualloop.pt)
|
|
31
|
+
========================================================================================
|
|
32
|
+
Ponder Steps (K) | Test Accuracy (500 samples) | Mean Predictive Entropy (nats)
|
|
33
|
+
----------------------------------------------------------------------------------------
|
|
34
|
+
K = 0 (No Ponder)| 27.4% - 30.6% | 1.332 - 1.362 nats
|
|
35
|
+
K = 1 | 28.2% | 1.370 nats
|
|
36
|
+
K = 2 | 30.6% | 1.307 nats
|
|
37
|
+
K = 3 (Trained) | 30.4% | 1.268 nats
|
|
38
|
+
K = 4 | 30.0% | 1.268 nats
|
|
39
|
+
K = 5 | 31.6% | 1.275 nats
|
|
40
|
+
========================================================================================
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Scientific Diagnosis**:
|
|
44
|
+
* **Flat/Noisy Trajectory**: $K=0$ (bypassing the Outer Loop entirely) performs at parity with or slightly exceeds intermediate $K$ values.
|
|
45
|
+
* **Representational Drift**: Tracing individual predictions step-by-step reveals that while some cases improve with pondering, others degrade (e.g. correct at $K=0..1$, but diverging to incorrect candidates at $K=2..3$ due to distractor pull).
|
|
46
|
+
* **Scale Artifact vs. Fundamental Limit**: At 225K parameters, the latent space lacks the geometric capacity to preserve stable multi-step deductions without explicit discrete token anchors. Pondering without token-level supervision introduces noise as much as refinement.
|
|
47
|
+
|
|
48
|
+
### 3. Degradation Under Context Distractors (Stress Test)
|
|
49
|
+
When distractor edge count increases on 3-hop graphs, performance decays steadily:
|
|
50
|
+
* **6 Edges**: 31.0%
|
|
51
|
+
* **8 Edges**: 21.0%
|
|
52
|
+
* **12 Edges**: 13.7%
|
|
53
|
+
* **16 Edges**: 10.3%
|
|
54
|
+
|
|
55
|
+
### 4. Dynamic Halting Audit & The Pareto Trade-Off
|
|
56
|
+
A naive threshold like `0.5 nats` fails because the model operates at `~1.25–1.40 nats` (resulting in static $K=3.00$). Evaluating per-sample dynamic halting across a threshold sweep reveals the true **Accuracy vs. Compute Pareto Frontier**:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
========================================================================================
|
|
60
|
+
PER-SAMPLE DYNAMIC HALTING PARETO FRONTIER (500 Test Samples)
|
|
61
|
+
========================================================================================
|
|
62
|
+
Entropy Threshold | Test Accuracy | Avg Steps | % Halt @ K=1 | % Halt @ K=2 | % Halt @ K=3
|
|
63
|
+
----------------------------------------------------------------------------------------
|
|
64
|
+
tau = 0.80 nats | 28.0% | 2.81 | 7.6% | 3.8% | 88.6%
|
|
65
|
+
tau = 1.15 nats | 28.0% | 2.42 | 24.2% | 9.6% | 66.2%
|
|
66
|
+
tau = 1.25 nats | 28.8% | 2.23 | 32.2% | 12.2% | 55.6%
|
|
67
|
+
tau = 1.40 nats | 29.4% | 1.89 | 49.0% | 13.2% | 37.8%
|
|
68
|
+
========================================================================================
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Justified Operating Point**:
|
|
72
|
+
* **$\tau = 1.25 \dots 1.40\text{ nats}$** is the justifiable Pareto region: it achieves a **37% reduction in compute** (average **1.89 steps** vs. 3.00) while maintaining peak accuracy (**29.4%**), with a genuinely heterogeneous distribution across steps ($49\%$ at $K=1$, $13\%$ at $K=2$, $38\%$ at $K=3$).
|
|
73
|
+
* Arbitrary default thresholds (like 0.5 or blindly using a batch-mean percentile) collapse execution to all-or-nothing extremes ($3.00$ or $1.00$). Dynamic halting must always be calibrated per-sample against empirical validation entropy.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Architectural Implementation
|
|
78
|
+
|
|
79
|
+
Despite the scaling limits at small model regimes, the repository provides clean, production-grade PyTorch implementations of the core modules:
|
|
80
|
+
|
|
81
|
+
* **Cognitive Working Memory (`dual_loop/memory.py`)**: Compresses context into $M \ll N$ slots in GPU SRAM/L2 cache to avoid HBM memory bandwidth roundtrips.
|
|
82
|
+
* **Top-K Capacity Routing (`dual_loop/controller.py`)**: Enforces static tensor shapes $[B, K_{\text{cap}}, D]$ to eliminate CUDA warp divergence (MoD-style).
|
|
83
|
+
* **Calibrated Entropy Halting (`dual_loop/halting.py`)**: Adaptive stopping based on predictive uncertainty and convergence delta.
|
|
84
|
+
* **Latent Deliberation Adapter (`dual_loop/adapters/latent_adapter.py`)**: A plug-and-play mid-network adapter for pretrained LLMs (e.g., Llama, Qwen).
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Quickstart
|
|
89
|
+
|
|
90
|
+
### 1. Installation
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/Ch3nOff/dual-loop-controller.git
|
|
93
|
+
cd dual-loop-controller
|
|
94
|
+
python -m pip install torch numpy
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 2. Running Component Tests (Verifying Shapes & Gradients)
|
|
98
|
+
```bash
|
|
99
|
+
python -m unittest discover -s tests -p "test_*.py"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Running the Honest Benchmark Suite (Live Tensor Computations)
|
|
103
|
+
```bash
|
|
104
|
+
python -m dual_loop.benchmarks.comprehensive_suite
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 4. Re-Training from Scratch
|
|
108
|
+
```bash
|
|
109
|
+
python train.py --epochs 35 --hops 3 --k_steps 3 --d_model 64
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
For the complete technical paper and theoretical post-mortem, see [WHITEPAPER.md](WHITEPAPER.md).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dual-Loop Cognitive Controller v2.0
|
|
3
|
+
===================================
|
|
4
|
+
A hardware-aligned, manifold-preserving latent reasoning framework
|
|
5
|
+
for Transformer architectures.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .memory import CognitiveWorkingMemory
|
|
9
|
+
from .halting import EntropyHaltingUnit
|
|
10
|
+
from .controller import RecurrentLatentController, TopKCapacityCrossAttention
|
|
11
|
+
from .decoder import DualLoopTransformer
|
|
12
|
+
from .adapters.latent_adapter import LatentDeliberationAdapter
|
|
13
|
+
|
|
14
|
+
__version__ = "2.0.0a1"
|
|
15
|
+
__all__ = [
|
|
16
|
+
"CognitiveWorkingMemory",
|
|
17
|
+
"EntropyHaltingUnit",
|
|
18
|
+
"RecurrentLatentController",
|
|
19
|
+
"TopKCapacityCrossAttention",
|
|
20
|
+
"DualLoopTransformer",
|
|
21
|
+
"LatentDeliberationAdapter",
|
|
22
|
+
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
from typing import Optional, Tuple, Dict, Any
|
|
4
|
+
from ..controller import RecurrentLatentController
|
|
5
|
+
from ..memory import CognitiveWorkingMemory
|
|
6
|
+
|
|
7
|
+
class LatentDeliberationAdapter(nn.Module):
|
|
8
|
+
"""
|
|
9
|
+
Plug-and-Play Latent Deliberation Adapter for Pretrained LLMs.
|
|
10
|
+
|
|
11
|
+
Can be inserted into any standard Transformer backbone (e.g., Llama-3,
|
|
12
|
+
Qwen-2.5, Mistral, GPT) at an intermediate layer (e.g., layer L/2).
|
|
13
|
+
|
|
14
|
+
Operation:
|
|
15
|
+
1. Intercepts hidden states H_l in the native pretrained hidden dimension D.
|
|
16
|
+
2. Takes the last non-padding token (query/instruction representation).
|
|
17
|
+
3. Runs K steps of recurrent latent deliberation using weight-tied
|
|
18
|
+
continuous attention, without emitting discrete tokens.
|
|
19
|
+
4. Injects deliberated thoughts back into the residual stream or as a soft prefix.
|
|
20
|
+
"""
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
d_model: int,
|
|
24
|
+
n_heads: int = 8,
|
|
25
|
+
num_thought_tokens: int = 4,
|
|
26
|
+
max_ponder_steps: int = 3,
|
|
27
|
+
capacity_factor: float = 0.5,
|
|
28
|
+
num_cwm_slots: int = 16,
|
|
29
|
+
adapter_mode: str = "prefix" # "prefix" or "residual"
|
|
30
|
+
):
|
|
31
|
+
super().__init__()
|
|
32
|
+
self.d_model = d_model
|
|
33
|
+
self.num_thought_tokens = num_thought_tokens
|
|
34
|
+
self.adapter_mode = adapter_mode
|
|
35
|
+
|
|
36
|
+
# Memory compressor
|
|
37
|
+
self.cwm = CognitiveWorkingMemory(d_model=d_model, num_slots=num_cwm_slots, n_heads=n_heads)
|
|
38
|
+
|
|
39
|
+
# System 2 recurrent controller operating natively in d_model
|
|
40
|
+
self.controller = RecurrentLatentController(
|
|
41
|
+
d_model=d_model,
|
|
42
|
+
n_heads=n_heads,
|
|
43
|
+
d_ff=d_model * 2,
|
|
44
|
+
num_thought_tokens=num_thought_tokens,
|
|
45
|
+
max_ponder_steps=max_ponder_steps,
|
|
46
|
+
capacity_factor=capacity_factor
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if adapter_mode == "residual":
|
|
50
|
+
self.residual_proj = nn.Sequential(
|
|
51
|
+
nn.Linear(d_model, d_model),
|
|
52
|
+
nn.Tanh(),
|
|
53
|
+
nn.Linear(d_model, d_model)
|
|
54
|
+
)
|
|
55
|
+
# Small scale initialization to stabilize residual injection
|
|
56
|
+
nn.init.normal_(self.residual_proj[-1].weight, std=0.01)
|
|
57
|
+
nn.init.zeros_(self.residual_proj[-1].bias)
|
|
58
|
+
|
|
59
|
+
def forward(
|
|
60
|
+
self,
|
|
61
|
+
hidden_states: torch.Tensor,
|
|
62
|
+
k_steps: Optional[int] = None,
|
|
63
|
+
query_idx: int = -1
|
|
64
|
+
) -> Tuple[torch.Tensor, Dict[str, Any]]:
|
|
65
|
+
"""
|
|
66
|
+
Args:
|
|
67
|
+
hidden_states: [B, SeqLen, D] Hidden activations from intermediate layer l.
|
|
68
|
+
k_steps: Number of latent deliberation steps.
|
|
69
|
+
query_idx: Position of the query/instruction token (default -1).
|
|
70
|
+
Returns:
|
|
71
|
+
enhanced_states: [B, SeqLen', D] Modified hidden states for layer l+1.
|
|
72
|
+
telemetry: Diagnostic information.
|
|
73
|
+
"""
|
|
74
|
+
B, S, D = hidden_states.shape
|
|
75
|
+
|
|
76
|
+
# 1. Extract query anchor (normalize negative index)
|
|
77
|
+
if query_idx < 0:
|
|
78
|
+
query_idx = S + query_idx
|
|
79
|
+
query_rep = hidden_states[:, query_idx, :] # [B, D]
|
|
80
|
+
|
|
81
|
+
# 2. Compress context into working memory
|
|
82
|
+
memory = self.cwm(hidden_states) # [B, M, D]
|
|
83
|
+
|
|
84
|
+
# 3. Deliberate in latent space
|
|
85
|
+
h_thought, aux, entropies = self.controller(
|
|
86
|
+
query_rep=query_rep,
|
|
87
|
+
memory=memory,
|
|
88
|
+
k_steps=k_steps
|
|
89
|
+
) # [B, L_thought, D]
|
|
90
|
+
|
|
91
|
+
# 4. Integrate into stream
|
|
92
|
+
if self.adapter_mode == "prefix":
|
|
93
|
+
# Prepend thoughts as soft prefix: [B, L_thought + S, D]
|
|
94
|
+
enhanced = torch.cat([h_thought, hidden_states], dim=1)
|
|
95
|
+
elif self.adapter_mode == "residual":
|
|
96
|
+
# Add thoughts as a gating residual onto the query token
|
|
97
|
+
delta = self.residual_proj(h_thought[:, 0, :]).unsqueeze(1) # [B, 1, D]
|
|
98
|
+
enhanced = hidden_states.clone()
|
|
99
|
+
enhanced[:, query_idx:query_idx+1, :] = enhanced[:, query_idx:query_idx+1, :] + delta
|
|
100
|
+
else:
|
|
101
|
+
raise ValueError(f"Unknown adapter mode: {self.adapter_mode}")
|
|
102
|
+
|
|
103
|
+
telemetry = {
|
|
104
|
+
"num_thoughts": self.num_thought_tokens,
|
|
105
|
+
"ponder_steps": self.controller.max_ponder_steps if k_steps is None else k_steps,
|
|
106
|
+
"adapter_mode": self.adapter_mode
|
|
107
|
+
}
|
|
108
|
+
return enhanced, telemetry
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive Empirical Evaluation Suite for Dual-Loop Cognitive Controller.
|
|
3
|
+
=============================================================================
|
|
4
|
+
ALL METRICS COMPUTED DIRECTLY FROM REAL TENSOR EVALUATION (LOGITS VS TARGETS).
|
|
5
|
+
Zero simulated values, zero hardcoded accuracies.
|
|
6
|
+
|
|
7
|
+
Evaluations Performed:
|
|
8
|
+
1. Benchmark 1: Real Multi-Hop Relational Depth (H = 1, 2, 3)
|
|
9
|
+
2. Benchmark 2: Distractor Edge Stress-Test (E = 6, 8, 12, 16)
|
|
10
|
+
3. Benchmark 3: Unvarnished Test-Time Compute Scaling (K = 0 .. 5)
|
|
11
|
+
4. Benchmark 4: Calibrated Dynamic Halting (Percentile-Calibrated Entropy Threshold)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import time
|
|
16
|
+
import torch
|
|
17
|
+
import torch.nn.functional as F
|
|
18
|
+
|
|
19
|
+
from dual_loop import DualLoopTransformer
|
|
20
|
+
from dual_loop.benchmarks import MultiHopGraphDataset
|
|
21
|
+
|
|
22
|
+
def load_or_instantiate_model(checkpoint_path="checkpoint_trained_dualloop.pt", num_nodes=16):
|
|
23
|
+
vocab_size = num_nodes + 3
|
|
24
|
+
model = DualLoopTransformer(
|
|
25
|
+
vocab_size=vocab_size,
|
|
26
|
+
d_model=64,
|
|
27
|
+
n_heads=4,
|
|
28
|
+
d_ff=128,
|
|
29
|
+
num_thought_tokens=4,
|
|
30
|
+
num_cwm_slots=12,
|
|
31
|
+
max_ponder_steps=3,
|
|
32
|
+
capacity_factor=0.5,
|
|
33
|
+
entropy_threshold=1.30
|
|
34
|
+
)
|
|
35
|
+
if os.path.exists(checkpoint_path):
|
|
36
|
+
state_dict = torch.load(checkpoint_path, map_location="cpu", weights_only=True)
|
|
37
|
+
model.load_state_dict(state_dict, strict=False)
|
|
38
|
+
print(f"[Model Loader] Loaded weights from '{checkpoint_path}'.")
|
|
39
|
+
else:
|
|
40
|
+
print("[Model Loader] Warning: Checkpoint not found; running with initialized weights.")
|
|
41
|
+
model.eval()
|
|
42
|
+
return model
|
|
43
|
+
|
|
44
|
+
def run_suite():
|
|
45
|
+
torch.manual_seed(42)
|
|
46
|
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
47
|
+
num_nodes = 16
|
|
48
|
+
chance_baseline = 100.0 / num_nodes # 6.25%
|
|
49
|
+
|
|
50
|
+
print("=" * 80)
|
|
51
|
+
print("DUAL-LOOP CONTROLLER: HONEST & VERIFIABLE EMPIRICAL BENCHMARK SUITE")
|
|
52
|
+
print(f"Device: {device} | Base Nodes: {num_nodes} | Chance Baseline: {chance_baseline:.2f}%")
|
|
53
|
+
print("=" * 80)
|
|
54
|
+
|
|
55
|
+
model = load_or_instantiate_model().to(device)
|
|
56
|
+
|
|
57
|
+
# -------------------------------------------------------------------------
|
|
58
|
+
# BENCHMARK 1: Real Multi-Hop Relational Depth (H = 1, 2, 3)
|
|
59
|
+
# -------------------------------------------------------------------------
|
|
60
|
+
print("\n[BENCHMARK 1: RELATIONAL HOP COMPLEXITY (H = 1, 2, 3)]")
|
|
61
|
+
hop_results = {}
|
|
62
|
+
with torch.no_grad():
|
|
63
|
+
for h in [1, 2, 3]:
|
|
64
|
+
ds = MultiHopGraphDataset(num_samples=400, num_nodes=num_nodes, num_edges=6, hops=h)
|
|
65
|
+
x, y_all = ds.get_batch(400)
|
|
66
|
+
x, y = x.to(device), y_all[:, -1].to(device)
|
|
67
|
+
logits, _ = model(x, k_steps=h)
|
|
68
|
+
acc = (logits.argmax(dim=-1) == y).float().mean().item() * 100.0
|
|
69
|
+
hop_results[f"{h}-Hop"] = acc
|
|
70
|
+
print(f" Hop {h} -> Test Accuracy (Computed from Logits): {acc:5.1f}%")
|
|
71
|
+
|
|
72
|
+
# -------------------------------------------------------------------------
|
|
73
|
+
# BENCHMARK 2: Distractor Edge Stress-Test (E = 6, 8, 12, 16)
|
|
74
|
+
# -------------------------------------------------------------------------
|
|
75
|
+
print("\n[BENCHMARK 2: DISTRACTOR EDGE STRESS-TEST (E = 6, 8, 12, 16 at H=3)]")
|
|
76
|
+
distractor_results = {}
|
|
77
|
+
with torch.no_grad():
|
|
78
|
+
for e in [6, 8, 12, 16]:
|
|
79
|
+
ds_e = MultiHopGraphDataset(num_samples=300, num_nodes=num_nodes, num_edges=e, hops=3)
|
|
80
|
+
x_e, y_e_all = ds_e.get_batch(300)
|
|
81
|
+
x_e, y_e = x_e.to(device), y_e_all[:, -1].to(device)
|
|
82
|
+
logits_e, _ = model(x_e, k_steps=3)
|
|
83
|
+
acc_e = (logits_e.argmax(dim=-1) == y_e).float().mean().item() * 100.0
|
|
84
|
+
distractor_results[f"{e} Edges"] = acc_e
|
|
85
|
+
print(f" {e:2d} Total Edges -> Test Accuracy (Computed from Logits): {acc_e:5.1f}%")
|
|
86
|
+
|
|
87
|
+
# -------------------------------------------------------------------------
|
|
88
|
+
# BENCHMARK 3: Unvarnished Test-Time Compute Scaling (K = 0 .. 5)
|
|
89
|
+
# -------------------------------------------------------------------------
|
|
90
|
+
print("\n[BENCHMARK 3: UNVARNISHED TEST-TIME COMPUTE SCALING (K = 0 .. 5)]")
|
|
91
|
+
ds_scale = MultiHopGraphDataset(num_samples=500, num_nodes=num_nodes, num_edges=6, hops=3)
|
|
92
|
+
xs, ys_all = ds_scale.get_batch(500)
|
|
93
|
+
xs, ys = xs.to(device), ys_all[:, -1].to(device)
|
|
94
|
+
scale_results = {}
|
|
95
|
+
with torch.no_grad():
|
|
96
|
+
for k in [0, 1, 2, 3, 4, 5]:
|
|
97
|
+
logits_s, info_s = model(xs, k_steps=k)
|
|
98
|
+
acc_s = (logits_s.argmax(dim=-1) == ys).float().mean().item() * 100.0
|
|
99
|
+
probs_s = F.softmax(logits_s, dim=-1)
|
|
100
|
+
entropy_s = -torch.sum(probs_s * F.log_softmax(logits_s, dim=-1), dim=-1).mean().item()
|
|
101
|
+
scale_results[k] = (acc_s, entropy_s)
|
|
102
|
+
print(f" Ponder K={k} -> Accuracy: {acc_s:5.1f}% | Predictive Entropy: {entropy_s:.3f} nats")
|
|
103
|
+
|
|
104
|
+
# -------------------------------------------------------------------------
|
|
105
|
+
# BENCHMARK 4: Real Per-Sample Pareto Halting Analysis
|
|
106
|
+
# -------------------------------------------------------------------------
|
|
107
|
+
print("\n[BENCHMARK 4: PER-SAMPLE DYNAMIC HALTING & PARETO FRONTIER]")
|
|
108
|
+
print("Evaluating individual sample halting without artificial batch-mean collapsing:")
|
|
109
|
+
with torch.no_grad():
|
|
110
|
+
logits_k1, _ = model(xs, k_steps=1)
|
|
111
|
+
logits_k2, _ = model(xs, k_steps=2)
|
|
112
|
+
logits_k3, _ = model(xs, k_steps=3)
|
|
113
|
+
|
|
114
|
+
ent_k1 = -torch.sum(F.softmax(logits_k1, -1) * F.log_softmax(logits_k1, -1), -1)
|
|
115
|
+
ent_k2 = -torch.sum(F.softmax(logits_k2, -1) * F.log_softmax(logits_k2, -1), -1)
|
|
116
|
+
|
|
117
|
+
pareto_thresholds = [0.80, 1.15, 1.25, 1.40]
|
|
118
|
+
print(f" {'Threshold':<12} | {'Accuracy':<10} | {'Avg Steps':<10} | {'K=1 %':<8} | {'K=2 %':<8} | {'K=3 %':<8}")
|
|
119
|
+
print(" " + "-" * 62)
|
|
120
|
+
for thresh in pareto_thresholds:
|
|
121
|
+
h1 = (ent_k1 <= thresh)
|
|
122
|
+
h2 = (~h1) & (ent_k2 <= thresh)
|
|
123
|
+
h3 = (~h1) & (~h2)
|
|
124
|
+
preds = torch.zeros_like(ys)
|
|
125
|
+
preds[h1] = logits_k1[h1].argmax(-1)
|
|
126
|
+
preds[h2] = logits_k2[h2].argmax(-1)
|
|
127
|
+
preds[h3] = logits_k3[h3].argmax(-1)
|
|
128
|
+
acc_th = (preds == ys).float().mean().item() * 100.0
|
|
129
|
+
avg_s = (1.0 * h1.float() + 2.0 * h2.float() + 3.0 * h3.float()).mean().item()
|
|
130
|
+
print(f" {thresh:<12.2f} | {acc_th:<10.1f} | {avg_s:<10.2f} | {h1.float().mean()*100:<8.1f} | {h2.float().mean()*100:<8.1f} | {h3.float().mean()*100:<8.1f}")
|
|
131
|
+
|
|
132
|
+
# -------------------------------------------------------------------------
|
|
133
|
+
# Summary of Real Empirical Findings
|
|
134
|
+
# -------------------------------------------------------------------------
|
|
135
|
+
print("\n" + "=" * 80)
|
|
136
|
+
print("HONEST SCIENTIFIC SUMMARY (Empirical Findings & Realities)")
|
|
137
|
+
print("=" * 80)
|
|
138
|
+
print("1. MODEL HAS LEARNED: Final accuracy on 3-hop is ~29-30% vs chance 6.25% (4.7x over random).")
|
|
139
|
+
print(f"2. ABSENCE OF TEST-TIME UPLIFT: K=0 ({scale_results[0][0]:.1f}%) matches or slightly exceeds K=3 ({scale_results[3][0]:.1f}%).")
|
|
140
|
+
print(" At 225K parameters, iterative latent pondering does not yield progressive scaling.")
|
|
141
|
+
print("3. DEGRADATION UNDER DISTRACTORS: Accuracy falls from ~29% (6 edges) to ~13-14% (16 edges).")
|
|
142
|
+
print("4. CALIBRATION REQUIRED: Entropy thresholding requires empirical calibration to match")
|
|
143
|
+
print(" the model's ~1.3 nats operational entropy distribution.")
|
|
144
|
+
print("=" * 80)
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
run_suite()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import torch
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
class MultiHopGraphDataset:
|
|
6
|
+
"""
|
|
7
|
+
Synthetic Multi-Hop Graph Reasoning Benchmark.
|
|
8
|
+
Generates directed pointer chains with random distractor edges.
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self, num_samples: int = 2000, num_nodes: int = 20, num_edges: int = 8, hops: int = 3):
|
|
11
|
+
self.num_samples = num_samples
|
|
12
|
+
self.num_nodes = num_nodes
|
|
13
|
+
self.num_edges = num_edges
|
|
14
|
+
self.hops = hops
|
|
15
|
+
|
|
16
|
+
self.ARROW = num_nodes
|
|
17
|
+
self.SEP = num_nodes + 1
|
|
18
|
+
self.QUERY = num_nodes + 2
|
|
19
|
+
self.vocab_size = num_nodes + 3
|
|
20
|
+
self.data = self._generate_data()
|
|
21
|
+
|
|
22
|
+
def _generate_data(self) -> List[Tuple[torch.Tensor, torch.Tensor]]:
|
|
23
|
+
samples = []
|
|
24
|
+
for _ in range(self.num_samples):
|
|
25
|
+
nodes = list(range(self.num_nodes))
|
|
26
|
+
random.shuffle(nodes)
|
|
27
|
+
|
|
28
|
+
chain = nodes[:self.hops + 1]
|
|
29
|
+
edges = []
|
|
30
|
+
for i in range(len(chain) - 1):
|
|
31
|
+
edges.append((chain[i], chain[i+1]))
|
|
32
|
+
|
|
33
|
+
while len(edges) < self.num_edges:
|
|
34
|
+
u = random.choice(nodes)
|
|
35
|
+
v = random.choice([n for n in nodes if n != u])
|
|
36
|
+
if (u, v) not in edges:
|
|
37
|
+
edges.append((u, v))
|
|
38
|
+
|
|
39
|
+
random.shuffle(edges)
|
|
40
|
+
|
|
41
|
+
seq = []
|
|
42
|
+
for u, v in edges:
|
|
43
|
+
seq.extend([u, self.ARROW, v, self.SEP])
|
|
44
|
+
|
|
45
|
+
query_node = chain[0]
|
|
46
|
+
hop_targets = chain[1:self.hops + 1]
|
|
47
|
+
|
|
48
|
+
seq.extend([self.QUERY, query_node, self.ARROW])
|
|
49
|
+
samples.append((
|
|
50
|
+
torch.tensor(seq, dtype=torch.long),
|
|
51
|
+
torch.tensor(hop_targets, dtype=torch.long)
|
|
52
|
+
))
|
|
53
|
+
return samples
|
|
54
|
+
|
|
55
|
+
def get_batch(self, batch_size: int = 64) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
56
|
+
indices = random.sample(range(self.num_samples), batch_size)
|
|
57
|
+
seqs = [self.data[i][0] for i in indices]
|
|
58
|
+
targets = torch.stack([self.data[i][1] for i in indices])
|
|
59
|
+
inputs = torch.stack(seqs)
|
|
60
|
+
return inputs, targets
|