fluidlayer 2.0.0__tar.gz → 2.2.0__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.
Files changed (89) hide show
  1. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/PKG-INFO +30 -31
  2. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/README.md +28 -29
  3. fluidlayer-2.2.0/deployment/__init__.py +1 -0
  4. fluidlayer-2.2.0/deployment/session_registry.py +178 -0
  5. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/BLOG.md +3 -4
  6. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/CHANGELOG.md +20 -0
  7. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/COMPARISON.md +4 -4
  8. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/DESIGN_pinned_memory.md +11 -12
  9. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/FRAMEWORK.md +6 -2
  10. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/GETTING_STARTED.md +17 -0
  11. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/GRAFT.md +12 -47
  12. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/MILESTONES.md +9 -16
  13. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/NEGATIVE-RESULTS.md +34 -87
  14. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/POSITIONING.md +9 -23
  15. fluidlayer-2.2.0/docs/PROCESS.md +347 -0
  16. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/README.md +4 -0
  17. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/REPRODUCING.md +40 -6
  18. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/TEN-MILLION.md +8 -2
  19. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/index.html +3 -3
  20. fluidlayer-2.2.0/examples/appkit/attach_layer.py +235 -0
  21. fluidlayer-2.2.0/examples/appkit/load_model.py +63 -0
  22. fluidlayer-2.2.0/experiments/e15_scale_model.py +317 -0
  23. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e9_ten_million.py +96 -78
  24. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/pretrain_prior.py +5 -3
  25. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/__init__.py +1 -1
  26. fluidlayer-2.2.0/fluidlayer/qwen2_model.py +156 -0
  27. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer.egg-info/PKG-INFO +30 -31
  28. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer.egg-info/SOURCES.txt +9 -0
  29. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer.egg-info/top_level.txt +1 -0
  30. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/pyproject.toml +3 -3
  31. fluidlayer-2.2.0/tests/test_qwen2_model.py +26 -0
  32. fluidlayer-2.2.0/tests/test_session_registry.py +126 -0
  33. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/CITATION.cff +0 -0
  34. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/LICENSE +0 -0
  35. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/MANIFEST.in +0 -0
  36. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/Makefile +0 -0
  37. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/API.md +0 -0
  38. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/LIMITATION.md +0 -0
  39. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/examples/gate_demo.py +0 -0
  40. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/examples/graft_demo.py +0 -0
  41. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/examples/quickstart.py +0 -0
  42. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e10_gate.py +0 -0
  43. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e11_exact.py +0 -0
  44. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e12_router.py +0 -0
  45. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e13_knn_baseline.py +0 -0
  46. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e14_real_gpt2.py +0 -0
  47. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e1_prior_perplexity.py +0 -0
  48. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e2_scaling.py +0 -0
  49. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e4_gbalance.py +0 -0
  50. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e5_graft.py +0 -0
  51. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e6_graft.py +0 -0
  52. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e6_real_corpus.py +0 -0
  53. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e7_lossy_fix.py +0 -0
  54. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/gpu_graft.py +0 -0
  55. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/run_all.py +0 -0
  56. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/autodiff.py +0 -0
  57. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/blocks.py +0 -0
  58. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/bpe.py +0 -0
  59. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/data.py +0 -0
  60. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/diffusion.py +0 -0
  61. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/gpt2_model.py +0 -0
  62. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/gpt2_tokenizer.py +0 -0
  63. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/graft.py +0 -0
  64. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/losses.py +0 -0
  65. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/memory.py +0 -0
  66. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/model.py +0 -0
  67. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/optim.py +0 -0
  68. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/py.typed +0 -0
  69. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/sheaf.py +0 -0
  70. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/topology.py +0 -0
  71. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/torch_graft.py +0 -0
  72. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer.egg-info/dependency_links.txt +0 -0
  73. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer.egg-info/requires.txt +0 -0
  74. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer_single.py +0 -0
  75. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/requirements.txt +0 -0
  76. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/run_without_pytest.py +0 -0
  77. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/selftest.py +0 -0
  78. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/setup.cfg +0 -0
  79. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/conftest.py +0 -0
  80. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_bpe.py +0 -0
  81. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_graft.py +0 -0
  82. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_memory.py +0 -0
  83. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_selftest.py +0 -0
  84. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_topology.py +0 -0
  85. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_torch_parity.py +0 -0
  86. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tools/_bundle_template.py +0 -0
  87. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tools/bundle_single_file.py +0 -0
  88. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tools/download_gpt2.py +0 -0
  89. {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tools/fetch_pypi.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluidlayer
3
- Version: 2.0.0
4
- Summary: A research memory layer for language models using cellular-sheaf diffusion
3
+ Version: 2.2.0
4
+ Summary: A research memory layer for language models with optional GPT-2 and Qwen2 adapters
5
5
  Author: Kapil Dhinwa
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/kapildhinwa2010-dev/fluidtech
@@ -216,14 +216,17 @@ Details: [`docs/TEN-MILLION.md`](docs/TEN-MILLION.md)
216
216
  ## The negative results
217
217
 
218
218
  This is the part that makes the rest believable.
219
- Eleven results came out negative or against expectation, and all eleven are measured, reproducible, and published.
219
+ Eleven negative or unexpected findings are documented. Most have checked-in
220
+ result files; the archived transformer and real-GPT-2 outputs are now
221
+ checked in. Their original model/script environments are not fully included,
222
+ so those historical runs cannot be independently repeated as-is.
220
223
 
221
224
  | What we expected | What happened |
222
225
  |---|---|
223
226
  | The **topological** selection criterion picks the right facts | It does **not** — `0.000` precision vs `0.970` for plain geometric affinity. |
224
227
  | `‖L·1‖∞` detects the frozen state | It does **not** — the frustration index does; the old claim is now a test case. |
225
228
  | `λ₂` predicts long-context quality | Barely — Pearson `0.20`. |
226
- | A transformer loses at some scale | It **wins** at every scale we could train. |
229
+ | A transformer loses at some scale | The archived run says it **wins**; see `results/train_results.json`. |
227
230
  | A domain prior beats a frequency table | It helps (`−0.006 nats`) but a **unigram table wins 6× more**. |
228
231
  | The diffusion tier is neutral on a frozen model | It **degrades** the model by **`+0.81 nats`** (fixed → `+0.0000`, but no upside either). |
229
232
  | Training the layer helps | It **hurts** — the training-free identity map is better (`0.800 vs 0.650`). |
@@ -235,16 +238,18 @@ Read it: [`docs/NEGATIVE-RESULTS.md`](docs/NEGATIVE-RESULTS.md)
235
238
  ## The repository
236
239
 
237
240
  ```text
238
- fluidlayer/ the package (pure NumPy; torch only in torch_graft.py)
241
+ fluidlayer/ the package (NumPy core; optional PyTorch adapters)
239
242
  graft.py the attachable memory layer
240
243
  memory.py the frozen register (20 B per fact, bit-exact)
241
244
  torch_graft.py the same layer in PyTorch, for a real LLM
242
245
  gpt2_model.py a GPT-2 forward pass in PyTorch (no transformers needed)
243
246
  gpt2_tokenizer.py the GPT-2 byte-level BPE, in pure Python
247
+ qwen2_model.py an optional PyTorch Qwen2 forward pass
244
248
  sheaf.py topology.py diffusion.py blocks.py autodiff.py ...
245
249
 
246
250
  experiments/ every measurement; each writes a JSON into results/
247
251
  e14_real_gpt2.py the graft on a REAL pretrained GPT-2 (124M)
252
+ e15_scale_model.py the graft on Qwen2.5-0.5B
248
253
  e13_knn_baseline.py the layer vs. the closest prior art (kNN-LM)
249
254
 
250
255
  tools/ the artifact checker, bundler, checkpoint downloader,
@@ -253,7 +258,7 @@ fluidlayer/bpe.py the byte-level BPE tokenizer
253
258
 
254
259
  tests/ the unit tests (pytest, or run them without it)
255
260
 
256
- examples/ quickstart.py, graft_demo.py, gate_demo.py
261
+ examples/ quickstart.py, graft_demo.py, gate_demo.py, appkit/
257
262
 
258
263
  docs/ the design docs, the API, the limits, the theory report
259
264
 
@@ -273,28 +278,20 @@ and [limitations](docs/LIMITATION.md).
273
278
 
274
279
  ---
275
280
 
276
- ## Status — and it now includes a REAL pretrained LLM
281
+ ## Real-model experiment status
277
282
 
278
- GPT-2 (124M parameters, the real 50,257-token BPE tokenizer), run on CPU with the memory layer attached:
283
+ The E14 GPT-2 experiment script, forward implementation, tokenizer, and
284
+ archived `results/e14_real_gpt2.json` are included. The checkpoint is not;
285
+ rerun E14 to independently reproduce the recorded measurements. See
286
+ [the reproduction guide](docs/REPRODUCING.md) for download and run commands.
279
287
 
280
- | Test | Result |
281
- |---|---|
282
- | Attaching the layer | The logits are **bit-identical** (max diff `0.000e+00`). |
283
- | A fact planted **2,947 tokens** before the query | The base GPT-2 predicts `464`; **the layer predicts the fact**. |
284
- | The base model’s perplexity | `4.0289` (`ppl 56.20`) → **`4.0019` (`ppl 54.70`)** with the layer. |
285
-
286
- And one result that narrows the claim honestly:
287
-
288
- On the real model, the classic kNN-LM interpolation beats the gated override by **8×** on perplexity — the opposite of the 131k-parameter model, where the gate won by **7.4×**.
289
- The gate wins where the base model is weak; interpolation wins where it is strong.
290
- The capability works on both.
291
-
292
- The checkpoint, the forward pass and the tokenizer are all in this repository:
293
-
294
- - [`fluidlayer/gpt2_model.py`](fluidlayer/gpt2_model.py)
295
- - [`fluidlayer/gpt2_tokenizer.py`](fluidlayer/gpt2_tokenizer.py)
296
-
297
- No `transformers` dependency, and the generation is verified coherent.
288
+ E15 adds a from-scratch Qwen2.5-0.5B forward implementation and app-kit
289
+ integration. Its archived run records bit-identical attachment, exact recall
290
+ of a planted fact 1,411 tokens back, and a best gated loss improvement of
291
+ `0.041` nats; the kNN-LM baseline improves loss by `0.274` nats, so this run
292
+ does not show the gate outperforming that baseline. The checkpoint is not
293
+ bundled. Reproduction requires the optional GPU dependencies and local model
294
+ weights; see [E15 instructions](docs/REPRODUCING.md#qwen2-scale-experiment-e15).
298
295
 
299
296
  The Torch twin is now tested against the NumPy reference in [`tests/test_torch_parity.py`](tests/test_torch_parity.py): the two implementations must agree on the router, the store and the gated override.
300
297
 
@@ -314,7 +311,9 @@ claim or validate production-scale operation.
314
311
 
315
312
  ## A research prototype, honestly labelled
316
313
 
317
- Everything above was measured on CPU, at small scale, on a character-level or 512-token subword corpus, with a **131,392-parameter model**.
314
+ The core research experiments use small CPU models and character-level or
315
+ 512-token subword corpora. E14 and E15 are separate pretrained-model
316
+ experiments; their checkpoints are not included.
318
317
 
319
318
  The subword repeat is the closest thing to a realistic tokenizer in this repository.
320
319
  The GPU run, [`experiments/gpu_graft.py`](experiments/gpu_graft.py), is written and has not been executed here.
@@ -370,7 +369,7 @@ A research memory layer with an operator-level stability bound, a bounded in-mem
370
369
  ## Origin and acknowledgments
371
370
 
372
371
  The project began with the author's idea of flowing information through a
373
- topological structure. The implementation and repository organization were
374
- developed with AI assistance. The measurements, limitations, and research
375
- decisions are documented alongside the code so the work can be evaluated and
376
- reproduced.
372
+ topological structure. The author describes it as a school project and
373
+ credits AI assistance for implementation and repository organization. The
374
+ research decisions, measurements, and limitations are documented alongside
375
+ the code so the work can be evaluated and reproduced.
@@ -179,14 +179,17 @@ Details: [`docs/TEN-MILLION.md`](docs/TEN-MILLION.md)
179
179
  ## The negative results
180
180
 
181
181
  This is the part that makes the rest believable.
182
- Eleven results came out negative or against expectation, and all eleven are measured, reproducible, and published.
182
+ Eleven negative or unexpected findings are documented. Most have checked-in
183
+ result files; the archived transformer and real-GPT-2 outputs are now
184
+ checked in. Their original model/script environments are not fully included,
185
+ so those historical runs cannot be independently repeated as-is.
183
186
 
184
187
  | What we expected | What happened |
185
188
  |---|---|
186
189
  | The **topological** selection criterion picks the right facts | It does **not** — `0.000` precision vs `0.970` for plain geometric affinity. |
187
190
  | `‖L·1‖∞` detects the frozen state | It does **not** — the frustration index does; the old claim is now a test case. |
188
191
  | `λ₂` predicts long-context quality | Barely — Pearson `0.20`. |
189
- | A transformer loses at some scale | It **wins** at every scale we could train. |
192
+ | A transformer loses at some scale | The archived run says it **wins**; see `results/train_results.json`. |
190
193
  | A domain prior beats a frequency table | It helps (`−0.006 nats`) but a **unigram table wins 6× more**. |
191
194
  | The diffusion tier is neutral on a frozen model | It **degrades** the model by **`+0.81 nats`** (fixed → `+0.0000`, but no upside either). |
192
195
  | Training the layer helps | It **hurts** — the training-free identity map is better (`0.800 vs 0.650`). |
@@ -198,16 +201,18 @@ Read it: [`docs/NEGATIVE-RESULTS.md`](docs/NEGATIVE-RESULTS.md)
198
201
  ## The repository
199
202
 
200
203
  ```text
201
- fluidlayer/ the package (pure NumPy; torch only in torch_graft.py)
204
+ fluidlayer/ the package (NumPy core; optional PyTorch adapters)
202
205
  graft.py the attachable memory layer
203
206
  memory.py the frozen register (20 B per fact, bit-exact)
204
207
  torch_graft.py the same layer in PyTorch, for a real LLM
205
208
  gpt2_model.py a GPT-2 forward pass in PyTorch (no transformers needed)
206
209
  gpt2_tokenizer.py the GPT-2 byte-level BPE, in pure Python
210
+ qwen2_model.py an optional PyTorch Qwen2 forward pass
207
211
  sheaf.py topology.py diffusion.py blocks.py autodiff.py ...
208
212
 
209
213
  experiments/ every measurement; each writes a JSON into results/
210
214
  e14_real_gpt2.py the graft on a REAL pretrained GPT-2 (124M)
215
+ e15_scale_model.py the graft on Qwen2.5-0.5B
211
216
  e13_knn_baseline.py the layer vs. the closest prior art (kNN-LM)
212
217
 
213
218
  tools/ the artifact checker, bundler, checkpoint downloader,
@@ -216,7 +221,7 @@ fluidlayer/bpe.py the byte-level BPE tokenizer
216
221
 
217
222
  tests/ the unit tests (pytest, or run them without it)
218
223
 
219
- examples/ quickstart.py, graft_demo.py, gate_demo.py
224
+ examples/ quickstart.py, graft_demo.py, gate_demo.py, appkit/
220
225
 
221
226
  docs/ the design docs, the API, the limits, the theory report
222
227
 
@@ -236,28 +241,20 @@ and [limitations](docs/LIMITATION.md).
236
241
 
237
242
  ---
238
243
 
239
- ## Status — and it now includes a REAL pretrained LLM
244
+ ## Real-model experiment status
240
245
 
241
- GPT-2 (124M parameters, the real 50,257-token BPE tokenizer), run on CPU with the memory layer attached:
246
+ The E14 GPT-2 experiment script, forward implementation, tokenizer, and
247
+ archived `results/e14_real_gpt2.json` are included. The checkpoint is not;
248
+ rerun E14 to independently reproduce the recorded measurements. See
249
+ [the reproduction guide](docs/REPRODUCING.md) for download and run commands.
242
250
 
243
- | Test | Result |
244
- |---|---|
245
- | Attaching the layer | The logits are **bit-identical** (max diff `0.000e+00`). |
246
- | A fact planted **2,947 tokens** before the query | The base GPT-2 predicts `464`; **the layer predicts the fact**. |
247
- | The base model’s perplexity | `4.0289` (`ppl 56.20`) → **`4.0019` (`ppl 54.70`)** with the layer. |
248
-
249
- And one result that narrows the claim honestly:
250
-
251
- On the real model, the classic kNN-LM interpolation beats the gated override by **8×** on perplexity — the opposite of the 131k-parameter model, where the gate won by **7.4×**.
252
- The gate wins where the base model is weak; interpolation wins where it is strong.
253
- The capability works on both.
254
-
255
- The checkpoint, the forward pass and the tokenizer are all in this repository:
256
-
257
- - [`fluidlayer/gpt2_model.py`](fluidlayer/gpt2_model.py)
258
- - [`fluidlayer/gpt2_tokenizer.py`](fluidlayer/gpt2_tokenizer.py)
259
-
260
- No `transformers` dependency, and the generation is verified coherent.
251
+ E15 adds a from-scratch Qwen2.5-0.5B forward implementation and app-kit
252
+ integration. Its archived run records bit-identical attachment, exact recall
253
+ of a planted fact 1,411 tokens back, and a best gated loss improvement of
254
+ `0.041` nats; the kNN-LM baseline improves loss by `0.274` nats, so this run
255
+ does not show the gate outperforming that baseline. The checkpoint is not
256
+ bundled. Reproduction requires the optional GPU dependencies and local model
257
+ weights; see [E15 instructions](docs/REPRODUCING.md#qwen2-scale-experiment-e15).
261
258
 
262
259
  The Torch twin is now tested against the NumPy reference in [`tests/test_torch_parity.py`](tests/test_torch_parity.py): the two implementations must agree on the router, the store and the gated override.
263
260
 
@@ -277,7 +274,9 @@ claim or validate production-scale operation.
277
274
 
278
275
  ## A research prototype, honestly labelled
279
276
 
280
- Everything above was measured on CPU, at small scale, on a character-level or 512-token subword corpus, with a **131,392-parameter model**.
277
+ The core research experiments use small CPU models and character-level or
278
+ 512-token subword corpora. E14 and E15 are separate pretrained-model
279
+ experiments; their checkpoints are not included.
281
280
 
282
281
  The subword repeat is the closest thing to a realistic tokenizer in this repository.
283
282
  The GPU run, [`experiments/gpu_graft.py`](experiments/gpu_graft.py), is written and has not been executed here.
@@ -333,7 +332,7 @@ A research memory layer with an operator-level stability bound, a bounded in-mem
333
332
  ## Origin and acknowledgments
334
333
 
335
334
  The project began with the author's idea of flowing information through a
336
- topological structure. The implementation and repository organization were
337
- developed with AI assistance. The measurements, limitations, and research
338
- decisions are documented alongside the code so the work can be evaluated and
339
- reproduced.
335
+ topological structure. The author describes it as a school project and
336
+ credits AI assistance for implementation and repository organization. The
337
+ research decisions, measurements, and limitations are documented alongside
338
+ the code so the work can be evaluated and reproduced.
@@ -0,0 +1 @@
1
+ """Optional deployment helpers for applications embedding FluidLayer."""
@@ -0,0 +1,178 @@
1
+ """Bounded, process-local isolation for stateful per-session memory branches.
2
+
3
+ This helper is an application integration aid, not an authentication system
4
+ or a cross-process session store. Applications must derive identifiers from
5
+ their authenticated identity layer and must not expose branch objects beyond
6
+ the lease that owns them.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import math
12
+ import threading
13
+ import time
14
+ from collections.abc import Iterator
15
+ from contextlib import contextmanager
16
+ from dataclasses import dataclass, field
17
+ from typing import Callable, Generic, Protocol, TypeVar
18
+
19
+
20
+ class Resettable(Protocol):
21
+ """The minimum interface required from a stateful branch."""
22
+
23
+ def reset(self) -> None:
24
+ """Clear all state associated with the branch."""
25
+
26
+
27
+ BranchT = TypeVar("BranchT", bound=Resettable)
28
+
29
+
30
+ class SessionRegistryError(RuntimeError):
31
+ """Base exception for session-registry lifecycle errors."""
32
+
33
+
34
+ class SessionCapacityError(SessionRegistryError):
35
+ """Raised when the registry is full of unexpired sessions."""
36
+
37
+
38
+ class SessionBusyError(SessionRegistryError):
39
+ """Raised when a caller tries to reset or close an active session."""
40
+
41
+
42
+ @dataclass
43
+ class _Session(Generic[BranchT]):
44
+ branch: BranchT
45
+ last_used: float
46
+ active_leases: int = 0
47
+ lock: threading.RLock = field(default_factory=threading.RLock)
48
+
49
+
50
+ class SessionRegistry(Generic[BranchT]):
51
+ """Manage separate stateful branches for `(tenant_id, session_id)` keys.
52
+
53
+ Each lease holds a per-session lock for its whole duration. This prevents
54
+ concurrent requests from interleaving writes to one branch while allowing
55
+ independent sessions to proceed concurrently. Expired idle sessions are
56
+ removed when the registry is next accessed. Capacity is a hard bound:
57
+ active or unexpired sessions are never silently evicted.
58
+
59
+ The registry is process-local and volatile. For multiple workers, use
60
+ worker affinity or an application-owned session service; do not serialize
61
+ live branch objects and assume they are safe to restore.
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ branch_factory: Callable[[], BranchT],
67
+ *,
68
+ max_sessions: int = 1024,
69
+ ttl_seconds: float = 1800.0,
70
+ clock: Callable[[], float] = time.monotonic,
71
+ ) -> None:
72
+ if not callable(branch_factory):
73
+ raise TypeError("branch_factory must be callable")
74
+ if isinstance(max_sessions, bool) or not isinstance(max_sessions, int):
75
+ raise TypeError("max_sessions must be an integer")
76
+ if max_sessions <= 0:
77
+ raise ValueError("max_sessions must be greater than zero")
78
+ if isinstance(ttl_seconds, bool) or not isinstance(ttl_seconds, (int, float)):
79
+ raise TypeError("ttl_seconds must be a finite positive number")
80
+ if not math.isfinite(ttl_seconds) or ttl_seconds <= 0:
81
+ raise ValueError("ttl_seconds must be a finite positive number")
82
+ if not callable(clock):
83
+ raise TypeError("clock must be callable")
84
+
85
+ self._branch_factory = branch_factory
86
+ self._max_sessions = max_sessions
87
+ self._ttl_seconds = float(ttl_seconds)
88
+ self._clock = clock
89
+ self._sessions: dict[tuple[str, str], _Session[BranchT]] = {}
90
+ self._lock = threading.RLock()
91
+
92
+ @staticmethod
93
+ def _validate_id(value: str, name: str) -> str:
94
+ if not isinstance(value, str):
95
+ raise TypeError(f"{name} must be a string")
96
+ if not value.strip():
97
+ raise ValueError(f"{name} must not be empty")
98
+ return value
99
+
100
+ def _expire_idle_sessions(self, now: float) -> None:
101
+ expired = [
102
+ key
103
+ for key, session in self._sessions.items()
104
+ if session.active_leases == 0
105
+ and now - session.last_used >= self._ttl_seconds
106
+ ]
107
+ for key in expired:
108
+ del self._sessions[key]
109
+
110
+ @contextmanager
111
+ def lease(self, tenant_id: str, session_id: str) -> Iterator[BranchT]:
112
+ """Yield the branch for one identity pair, serialized per session."""
113
+ tenant = self._validate_id(tenant_id, "tenant_id")
114
+ session_name = self._validate_id(session_id, "session_id")
115
+ key = (tenant, session_name)
116
+ now = self._clock()
117
+
118
+ with self._lock:
119
+ self._expire_idle_sessions(now)
120
+ session = self._sessions.get(key)
121
+ if session is None:
122
+ if len(self._sessions) >= self._max_sessions:
123
+ raise SessionCapacityError(
124
+ f"session registry is at capacity "
125
+ f"({self._max_sessions}); retry after sessions expire "
126
+ "or close an idle session"
127
+ )
128
+ session = _Session(self._branch_factory(), last_used=now)
129
+ self._sessions[key] = session
130
+ session.active_leases += 1
131
+ session.last_used = now
132
+
133
+ try:
134
+ with session.lock:
135
+ yield session.branch
136
+ finally:
137
+ with self._lock:
138
+ session.active_leases -= 1
139
+ session.last_used = self._clock()
140
+
141
+ def reset_session(self, tenant_id: str, session_id: str) -> bool:
142
+ """Reset an idle session; return False if it does not exist."""
143
+ key = (
144
+ self._validate_id(tenant_id, "tenant_id"),
145
+ self._validate_id(session_id, "session_id"),
146
+ )
147
+ with self._lock:
148
+ self._expire_idle_sessions(self._clock())
149
+ session = self._sessions.get(key)
150
+ if session is None:
151
+ return False
152
+ if session.active_leases:
153
+ raise SessionBusyError("cannot reset a session with an active lease")
154
+ session.branch.reset()
155
+ session.last_used = self._clock()
156
+ return True
157
+
158
+ def close_session(self, tenant_id: str, session_id: str) -> bool:
159
+ """Remove an idle session; return False if it does not exist."""
160
+ key = (
161
+ self._validate_id(tenant_id, "tenant_id"),
162
+ self._validate_id(session_id, "session_id"),
163
+ )
164
+ with self._lock:
165
+ session = self._sessions.get(key)
166
+ if session is None:
167
+ return False
168
+ if session.active_leases:
169
+ raise SessionBusyError("cannot close a session with an active lease")
170
+ del self._sessions[key]
171
+ return True
172
+
173
+ @property
174
+ def session_count(self) -> int:
175
+ """Return the number of registered sessions after expiring idle entries."""
176
+ with self._lock:
177
+ self._expire_idle_sessions(self._clock())
178
+ return len(self._sessions)
@@ -85,10 +85,9 @@ suite.
85
85
  λ₂ does not predict quality (pearson 0.20). The certificate is true
86
86
  about the operator; it says nothing about the task. The claim was
87
87
  weakened, not the data massaged.
88
- A transformer beats the fluid at every scale I could train
89
- (2.6331 vs 2.7397 nats). The fluid only wins against fixed-receptive-field
90
- aggregators (97.3 % vs 65.6 % / 71.0 %) — and it loses to full attention,
91
- which is the honest summary of the entire architecture.
88
+ Archived results record a transformer beating the fluid (2.6331 vs 2.7397
89
+ nats) and a 97.3 % mechanism result. The historical source scripts are not
90
+ included, so see N4 in `docs/NEGATIVE-RESULTS.md` for the reproduction caveat.
92
91
  Grafted onto a frozen model, my own diffusion tier made the model
93
92
  worse — +0.81 nats (ppl 13.3 -> 29.9) on real English. The cause is
94
93
  structural: the persistent state sits outside the autodiff graph, so the
@@ -6,6 +6,26 @@ uses [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.2.0] - 2026-11-03
10
+
11
+ ### Added
12
+
13
+ - Include the optional deployment session-isolation companion in the
14
+ published wheel and source distribution.
15
+
16
+ ### Changed
17
+
18
+ - Add clean-install CI verification for the deployment package.
19
+
20
+ ## [2.1.0] - 2026-11-03
21
+
22
+ ### Added
23
+
24
+ - Added the optional PyTorch Qwen2 adapter, E15 scale-model experiment,
25
+ archived measurements, and a runnable app-kit example.
26
+ - Categorized the new project-process, app-kit, experiment, and result files
27
+ into `docs/`, `examples/`, `experiments/`, and `results/`.
28
+
9
29
  ### Changed
10
30
 
11
31
  - Improved the Markdown documentation structure, added a documentation
@@ -205,7 +205,7 @@ what makes per-user persistent memory economically possible at all.
205
205
  | ---|---|---|---|---|---|---|---|---|---|
206
206
  | exact needle at a position|✅|✅ recent|✅|❌|❌|❌|❌|✅|✅ pinned / L3|
207
207
  | exact specific fact|✅|✅ recent|✅|❌|❌|❌|❌|✅|✅ pinned (20 B)|
208
- | global gist / aggregate|✅|❌|✅|✅|✅|✅|❌|❌|✅ 97.3 % meas.|
208
+ | global gist / aggregate|✅|❌|✅|✅|✅|✅|❌|❌|✅ archived 97.3 % result (`results/mechanism.json`)|
209
209
  | positional recall ( "3rd paragraph ")|✅|✅|✅|✅|❌|❌|❌|❌|❌|
210
210
  | infinite stream / never re-encode|❌|✅|❌|❌|✅|✅|n/a|✅|✅|
211
211
  | no "lost in the middle "|❌|❌|❌|❌|~ (no positions)|~|n/a|~|✅ (no positional decay)|
@@ -266,12 +266,12 @@ the measured semantic/local-geometric policy performed better.
266
266
  KV bytes/token, KV @ctx, sessions that fit in VRAM, prefill FLOPs, crossover point
267
267
  fluid read cost 16 KB = 0.009 µs; fluid compute = 384 flops/token/layer
268
268
  our `selftest.py` → 50/50
269
- mechanism test: fluid 97.3 % sign acc vs local R=4 65.6 %, R=8 71.0 %
270
- LM A/B: transformer wins — 2.6331 vs 2.7397 final val (fewer params on our side)
269
+ mechanism test: archived result records 97.3 % sign accuracy; original source unavailable
270
+ LM A/B: archived result records transformer 2.6331 vs FluidLayer 2.7397; original training source unavailable
271
271
  param-matched FLOPs: 1.10× cheaper @8K → 2.54× @128K
272
272
  ⚠️ PROJECTED — needs E1/E5 to become fact
273
273
  that the 16 KB fluid carries long-range information at usable quality
274
- that a real LLM actually routes to the fluid (the 97.3 % was a synthetic task)
274
+ that a real LLM actually routes to the fluid (the archived 97.3 % synthetic-task result does not establish real-model routing)
275
275
  that topology picks the right facts to freeze
276
276
  that pinned facts survive a real model's distribution shift
277
277
  Every table above is a CONDITIONAL: "IF the fluid is usable, THEN this".
@@ -45,10 +45,9 @@ This is the entire design in one move:
45
45
  FluidLayer stops being a memory. It becomes an index.
46
46
  The memory moves to a write-once register; the fluid answers where, and
47
47
  the register answers what.
48
- And the reason this is not a retreat: the 97.3 % mechanism result was always a
49
- routing result. In the mechanism test the fluid predicted the right global
50
- aggregate to 97.3 % sign accuracy. It was already an excellent addresser.
51
- We just gave it an exact value to point at.
48
+ The archived `results/mechanism.json` records a 97.3 % global-aggregation
49
+ result. Its original experiment source is not present, so the historical run
50
+ cannot be independently regenerated (see N4 in `docs/NEGATIVE-RESULTS.md`).
52
51
 
53
52
  ## 1. The impossibility, stated precisely
54
53
 
@@ -89,7 +88,7 @@ keep.
89
88
  └──────────────────────────────────────────────────┘
90
89
  Slot layout (20 B, as implemented):`key uint64 | val int32 | sig uint32 | anchor uint16 | meta uint16`.
91
90
  The read path is three clean steps, each with a guarantee:
92
- query h ──▶ L2 routes h → anchor a (approx: 97.3 % measured)
91
+ query h ──▶ L2 routes h → anchor a (approximate; archived score in results/mechanism.json)
93
92
  anchor a → slot id s (exact: 1 byte)
94
93
  L1[s] = (key, token_id) (exact: 4 bytes)
95
94
  verify key matches h's context hash (fail-loud)
@@ -174,12 +173,12 @@ This is a testable prediction, and it is falsifiable.
174
173
  Which tokens to freeze? The longest bars = the structural landmarks =
175
174
  the tokens that carry the document's skeleton. Freeze those and you get
176
175
  exact extraction of the load-bearing facts, not random trivia.
177
- Why the fluid wins global aggregation (explains the 97.3 %). The
176
+ The theory suggests why diffusion can carry global aggregation: the
178
177
  harmonic branch of `L` (its kernel) is the globally dominant H₀ mode —
179
- and the global mean is the longest bar. The fluid preserves the kernel
180
- component exactly; that is precisely why it beat local aggregators
181
- (65.6 % / 71.0 %) on the global-mean task and lost on position-sensitive
182
- tasks. The v0.1 theory report and the mechanism experiment now have one
178
+ and the global mean is the longest bar. The archived mechanism output records
179
+ a 97.3 % score, beating local aggregators (65.6 % / 71.0 %); its original
180
+ source is unavailable, so the run cannot be independently regenerated. The
181
+ v0.1 theory report and the mechanism experiment now have one
183
182
  unified explanation. That is a real, publishable narrative.
184
183
 
185
184
  ## 5. Budget: the 3-D trade-off
@@ -366,8 +365,8 @@ P4 needs one GPU for a few hours.
366
365
 
367
366
  Attention remembers by position. We remember by geometry. But geometry alone
368
367
  smears: diffusion destroys exactness by construction. So we split the
369
- memory. A 16 KB sheaf routes — 97.3 % accurate at picking the right
370
- landmark — and an append-only frozen register stores the exact token id
368
+ memory. A 16 KB sheaf routes to an approximate landmark (the archived
369
+ mechanism result records 97.3 % sign accuracy) — and an append-only frozen register stores the exact token id
371
370
  at 14 bytes per fact, 196,234× cheaper than a KV cache. Topology decides
372
371
  which landmarks get exact storage, using the document's own persistence
373
372
  diagram, at zero per-token cost. The model can promote a fact or recall one
@@ -3,6 +3,10 @@
3
3
  > **Historical design note:** this is not a current implementation contract.
4
4
  > For current behavior and limitations, see [the API reference](API.md) and
5
5
  > [limitations](LIMITATION.md).
6
+ > Archived LM-comparison and mechanism-test JSON outputs are included under
7
+ > `results/`, but their original experiment source files are unavailable.
8
+ > Treat the numbers as historical snapshots, not independently rerunnable
9
+ > measurements.
6
10
 
7
11
  A topological fluid architecture for sequence models: replace the
8
12
  transformer's quadratic attention with a cellular-sheaf diffusion whose
@@ -345,8 +349,8 @@ fluidlayer/
345
349
  losses.py CE (+ tensor variant), sheaf energy, band decorrelation
346
350
  optim.py Adam (decoupled WD, clipping, cosine schedule)
347
351
  data.py Gutenberg corpus + char tokenizer + synthetic fallback
348
- selftest.py 15 numerical checks of the mathematics -> results/selftest.json
349
- train.py LM A/B + global-aggregation demo -> results/train_results.json
352
+ selftest.py historical numerical checks
353
+ train.py historical LM A/B + global-aggregation demo (source unavailable; see archived results/)
350
354
 
351
355
  ### Run it
352
356
 
@@ -106,6 +106,23 @@ integration must handle the model's positional embeddings, attention masks,
106
106
  device placement, session boundaries, and task-specific gain calibration.
107
107
  Call `branch.reset()` when starting a new independent session.
108
108
 
109
+ ## Run the GPT-2/Qwen2 app kit
110
+
111
+ The repository includes a standalone app kit that loads a local GPT-2 or
112
+ Qwen2 checkpoint and demonstrates attachment and recall:
113
+
114
+ ```bash
115
+ python -m pip install "fluidlayer[gpu]"
116
+ hf download Qwen/Qwen2.5-0.5B --local-dir models/qwen05b
117
+ python examples/appkit/attach_layer.py --model models/qwen05b
118
+ ```
119
+
120
+ The Qwen2 forward implementation is in the published `fluidlayer` package;
121
+ the Qwen tokenizer is provided by the optional Transformers dependency.
122
+ Model weights are not bundled. This app kit is a research example, not a
123
+ production serving wrapper. See [the E15 result](../results/e15_scale_model.json)
124
+ and [reproduction guide](REPRODUCING.md#qwen2-scale-experiment-e15).
125
+
109
126
  ## Memory behavior and safety
110
127
 
111
128
  The attachable store is bounded. Its reported 20-byte slot cost is a logical