Commit ·
aef5ed5
0
Parent(s):
SHADOW 250M Instruct
Browse files- .gitattributes +41 -0
- README.md +233 -0
- banner.jpg +3 -0
- benchmarks/README.md +8 -0
- benchmarks/embedding_bench.py +32 -0
- benchmarks/report.pdf +99 -0
- benchmarks/results.json +85 -0
- benchmarks/run.py +32 -0
- benchmarks/wordsim353.csv +353 -0
- deployment/bin/linux/shadow +0 -0
- deployment/bin/windows/shadow.exe +3 -0
- deployment/fp131072.npy +3 -0
- deployment/shadow250m_instruct.shdw +3 -0
- finetune/FINETUNING.md +80 -0
- finetune/examples_pirate.jsonl +0 -0
- finetune/export_model.py +18 -0
- finetune/finetune.py +148 -0
- finetune/modeling/common.py +651 -0
- finetune/modeling/export_rvq.py +93 -0
- finetune/modeling/export_ternary.py +84 -0
- finetune/modeling/model_250m.py +128 -0
- finetune/modeling/paged_kv.py +319 -0
- finetune/modeling/repack_shdw.py +28 -0
- finetune/pirate.shdw +3 -0
- finetune/shadow250m_instruct.pt +3 -0
- framework.png +3 -0
- shadow_chat.py +19 -0
- shadow_runtime/__init__.py +51 -0
- shadow_runtime/__main__.py +14 -0
- shadow_runtime/answer_engine.py +160 -0
- shadow_runtime/prompt.py +61 -0
- shadow_runtime/retriever.py +137 -0
- tokenizer/new2old.u32 +3 -0
- tokenizer/stop500.npy +3 -0
- tokenizer/tokenizer.model +3 -0
.gitattributes
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
banner.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
framework.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
pirate.shdw filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
shadow.exe filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
shadow250m_instruct.shdw filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
tokenizer/new2old.u32 filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-nc-4.0
|
| 3 |
+
viewer: false
|
| 4 |
+
language:
|
| 5 |
+
- en
|
| 6 |
+
pipeline_tag: text-generation
|
| 7 |
+
tags:
|
| 8 |
+
- small
|
| 9 |
+
- shadow
|
| 10 |
+
- instruct
|
| 11 |
+
- chat
|
| 12 |
+
- long-context
|
| 13 |
+
- retrieval
|
| 14 |
+
- cpu
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
<h1 align="center">SHADOW 250M Instruct</h1>
|
| 18 |
+
|
| 19 |
+

|
| 20 |
+
|
| 21 |
+
<p align="center">
|
| 22 |
+
Instruct model of SHADOW 250M · 250M Parameters · 100M-Token Offline Context · 60 MB
|
| 23 |
+
</p>
|
| 24 |
+
|
| 25 |
+
**SHADOW 250M Instruct** is a 250 million parameter language model built from scratch,
|
| 26 |
+
trained on 30 billion tokens of English text with about 0.7 billion further tokens of
|
| 27 |
+
instruction tuning. The complete deployment is 60 MB, vocabulary included. It runs at about 400 tokens per
|
| 28 |
+
second on a laptop CPU and uses about 80 MB of RAM.
|
| 29 |
+
|
| 30 |
+
Language modeling quality of the base model, measured on held-out English web text that
|
| 31 |
+
was never seen in training (educational web pages, 2,048 token windows): cross entropy
|
| 32 |
+
3.15 nats per token, perplexity 23.3, 0.99 bits per byte.
|
| 33 |
+
|
| 34 |
+
Next to its 2,048 token attention window, SHADOW can use an offline archive: a compressed
|
| 35 |
+
cache of up to 100 million tokens that stays on your hard disk. The model was trained from
|
| 36 |
+
step 1 to retrieve from this archive, all the way up to 100 million tokens. Due to a
|
| 37 |
+
limited budget it was not trained to reason over the tokens in the disk cache. It finds
|
| 38 |
+
facts and reads them back; it does not think across them.
|
| 39 |
+
|
| 40 |
+
For scale, the official released weight files of well-known small models, next to ours:
|
| 41 |
+
|
| 42 |
+
| model | parameters | weights on disk |
|
| 43 |
+
|---|---|---|
|
| 44 |
+
| GPT-2 | 124M | 548 MB |
|
| 45 |
+
| SmolLM2-135M-Instruct | 135M | 269 MB |
|
| 46 |
+
| SmolLM2-360M-Instruct | 360M | 724 MB |
|
| 47 |
+
| Qwen2.5-0.5B-Instruct | 494M | 988 MB |
|
| 48 |
+
| **SHADOW 250M Instruct** | **250M** | **60 MB, vocabulary included** |
|
| 49 |
+
|
| 50 |
+
Sizes are the main weight file of each official repository, as published. None of the
|
| 51 |
+
models above reads a 100 million token archive from disk.
|
| 52 |
+
|
| 53 |
+
*(The project was named after my first dog, Shadow, as it is my first big project.)*
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## Some samples
|
| 58 |
+
|
| 59 |
+
Every sample shows the exact settings it was generated with, using the files in this repo.
|
| 60 |
+
Nothing on this page was made with settings other than the ones shown.
|
| 61 |
+
|
| 62 |
+
### 1.
|
| 63 |
+
|
| 64 |
+
Prompt: "Explain photosynthesis in two sentences."<br>
|
| 65 |
+
Settings: greedy decoding (temperature 0)<br>
|
| 66 |
+
Answer:
|
| 67 |
+
> Photosynthesis is a process in which plants convert sunlight into chemical energy, which
|
| 68 |
+
> is then used to produce oxygen and other chemicals. This process is called photosynthesis.
|
| 69 |
+
|
| 70 |
+
### 2.
|
| 71 |
+
|
| 72 |
+
Prompt: "Write a short poem about the sea."<br>
|
| 73 |
+
Settings: temperature 0.25 · top-k 30 · repetition penalty 1.15 · seed 2<br>
|
| 74 |
+
Answer:
|
| 75 |
+
> The waves had swept over, and they were crashing against each other like rocks on top of
|
| 76 |
+
> one another. The wind was strong but not very gentle as it tried to push them towards
|
| 77 |
+
> their destination without lifting any weight or pulling away from those who stood in
|
| 78 |
+
> front of her with a stiff smile that made the sea look even more majestic than before.
|
| 79 |
+
|
| 80 |
+
### 3. Reading its archive
|
| 81 |
+
|
| 82 |
+
Prompt: "What is the serial number of device Grus-189?" The fact sits 50.6 million tokens
|
| 83 |
+
deep in the 100M evaluation archive on disk.<br>
|
| 84 |
+
Settings: archive mode as shipped, retrieval k=16<br>
|
| 85 |
+
Answer:
|
| 86 |
+
> SN-442976
|
| 87 |
+
|
| 88 |
+
---
|
| 89 |
+
|
| 90 |
+
## Benchmarks
|
| 91 |
+
|
| 92 |
+
All numbers are our own, measured on held-out data with the harness shipped in
|
| 93 |
+
`benchmarks/`, together with the raw results. We compare with nobody on this page; the
|
| 94 |
+
harness runs on any archive of yours as well.
|
| 95 |
+
|
| 96 |
+
Standard, acc_norm, zero-shot, full sets, greedy scoring:
|
| 97 |
+
|
| 98 |
+
| PIQA | HellaSwag | ARC-Easy | ARC-Challenge |
|
| 99 |
+
|---|---|---|---|
|
| 100 |
+
| 0.60 | 0.34 | 0.41 | 0.24 |
|
| 101 |
+
|
| 102 |
+
Long context, answered from the archive on disk. Settings: archive mode as shipped,
|
| 103 |
+
retrieval k=16, exact match scoring, archives and facts held out from all training:
|
| 104 |
+
|
| 105 |
+
| | 1M tokens | 10M tokens | 100M tokens |
|
| 106 |
+
|---|---|---|---|
|
| 107 |
+
| Needle in a haystack (5 depths) | 0.98 | 0.98 | 0.98 |
|
| 108 |
+
| Needle with look-alike distractors | 1.00 | 1.00 | – |
|
| 109 |
+
| Multi-key needles | 1.00 | 1.00 | – |
|
| 110 |
+
| Two-hop variable tracking | 1.00 | 1.00 | – |
|
| 111 |
+
| Scattered story facts, latest wins | 1.00 | 1.00 | – |
|
| 112 |
+
| Fact QA, 6 task types with abstain | 0.97 | 0.95 | 0.83 |
|
| 113 |
+
|
| 114 |
+
## Architecture
|
| 115 |
+
|
| 116 |
+
| Hyperparameter | Value |
|
| 117 |
+
|---|---|
|
| 118 |
+
| Hidden size | 1536 |
|
| 119 |
+
| Layers | 10 |
|
| 120 |
+
| Attention heads | 24 (GQA, 2 KV heads) |
|
| 121 |
+
| Head dim | 64 |
|
| 122 |
+
| Intermediate size (SwiGLU) | 4224 |
|
| 123 |
+
| Vocab size | 131,072 (frozen, 0 trainable parameters) |
|
| 124 |
+
| Positional encoding | RoPE θ=10,000 |
|
| 125 |
+
| Normalization | RMSNorm, ε=10-6 (incl. QK-Norm) |
|
| 126 |
+
| Tied embeddings | Yes (shared vocabulary table) |
|
| 127 |
+
| Attention window | 2,048 tokens + offline archive up to 100M |
|
| 128 |
+
| Body weight precision | under 2 bits per weight |
|
| 129 |
+
| Parameters | 250M |
|
| 130 |
+
| Runtime | bundled CPU kernel (AVX2/AVX-512), no framework needed |
|
| 131 |
+
|
| 132 |
+

|
| 133 |
+
|
| 134 |
+
## Performance
|
| 135 |
+
|
| 136 |
+
Measured on a laptop CPU with 8 physical cores, using the exact files in this repo. The
|
| 137 |
+
bundled kernel handles chat, the two-tier KV cache, and a live memory panel (`--status`).
|
| 138 |
+
|
| 139 |
+
| | |
|
| 140 |
+
|---|---|
|
| 141 |
+
| decode speed, 8 threads | 402 tokens/s |
|
| 142 |
+
| decode speed, 4 / 2 / 1 threads | 393 / 275 / 158 tokens/s |
|
| 143 |
+
| prefill speed | 409 tokens/s |
|
| 144 |
+
| RAM while chatting | ~80 MB |
|
| 145 |
+
| archive index build (once per archive, at load) | 2 s at 1M · 21 s at 10M · 3.2 min at 100M |
|
| 146 |
+
| retrieval per question | 37 ms at 10M · 435 ms at 100M |
|
| 147 |
+
| archive question, end to end | 0.45 s at 100M |
|
| 148 |
+
|
| 149 |
+
## Fine-tuning
|
| 150 |
+
|
| 151 |
+
Yes, you can fine-tune it, on one GPU, and export your own 52 MB model for CPU. We did it
|
| 152 |
+
ourselves as a demonstration: 90 minutes on a laptop GPU turned SHADOW into a pirate
|
| 153 |
+
assistant, with benchmark scores unchanged. The full guide with the commands, the dataset,
|
| 154 |
+
and the before and after results is in [finetune/FINETUNING.md](finetune/FINETUNING.md).
|
| 155 |
+
|
| 156 |
+
> The capital of France be Paris. It is a UNESCO World Heritage Site... Yarr!
|
| 157 |
+
|
| 158 |
+
## Repository layout
|
| 159 |
+
|
| 160 |
+
deployment/ the model: weights, vocabulary, and the runtime binaries
|
| 161 |
+
shadow250m_instruct.shdw 52 MB weights
|
| 162 |
+
fp131072.npy 8.4 MB vocabulary
|
| 163 |
+
bin/windows/ bin/linux/ prebuilt CPU runtimes (macOS on request)
|
| 164 |
+
tokenizer/ 3 files, 5 MB
|
| 165 |
+
finetune/ master weights, training script, exporter, guide, worked example
|
| 166 |
+
benchmarks/ results, report, harness
|
| 167 |
+
shadow_runtime/ archive question answering (Python)
|
| 168 |
+
|
| 169 |
+
## Usage
|
| 170 |
+
|
| 171 |
+
Easiest start, any system:
|
| 172 |
+
|
| 173 |
+
python shadow_chat.py
|
| 174 |
+
|
| 175 |
+
Chat directly with the binary, no Python needed. Windows:
|
| 176 |
+
|
| 177 |
+
deploymentin\windows\shadow.exe deployment\shadow250m_instruct.shdw deploymentp131072.npy --chat
|
| 178 |
+
|
| 179 |
+
Linux:
|
| 180 |
+
|
| 181 |
+
deployment/bin/linux/shadow deployment/shadow250m_instruct.shdw deployment/fp131072.npy --chat
|
| 182 |
+
|
| 183 |
+
Add --status to either for a live memory panel. Ask a question against an archive (a folder
|
| 184 |
+
holding a tokens.u32 stream):
|
| 185 |
+
|
| 186 |
+
python -m shadow_runtime --model shadow250m_instruct.shdw --table fp131072.npy \
|
| 187 |
+
--archive path/to/archive --ask "your question"
|
| 188 |
+
|
| 189 |
+
Python:
|
| 190 |
+
|
| 191 |
+
from shadow_runtime import Engine
|
| 192 |
+
eng = Engine("shadow250m_instruct.shdw", "fp131072.npy", archive="path/to/archive")
|
| 193 |
+
print(eng.answer("your question"))
|
| 194 |
+
|
| 195 |
+
### Chat template
|
| 196 |
+
|
| 197 |
+
<start_of_turn>user
|
| 198 |
+
{message}<end_of_turn>
|
| 199 |
+
<start_of_turn>model
|
| 200 |
+
{response}<end_of_turn>
|
| 201 |
+
|
| 202 |
+
## Intended use
|
| 203 |
+
|
| 204 |
+
Intended:
|
| 205 |
+
|
| 206 |
+
* Local assistants on CPU-only hardware, fully offline
|
| 207 |
+
* Question answering over large private text archives: logs, books, documentation
|
| 208 |
+
* Fine-tuning your own small assistant on one GPU
|
| 209 |
+
* Research and education on small models and long context
|
| 210 |
+
|
| 211 |
+
Not intended:
|
| 212 |
+
|
| 213 |
+
* Production or user-facing deployment without human review
|
| 214 |
+
* Factual question answering from the model's own memory, advice, or decision support
|
| 215 |
+
* Non-English text
|
| 216 |
+
|
| 217 |
+
## Limitations and bias
|
| 218 |
+
|
| 219 |
+
* Small. At 250M parameters, open facts, arithmetic, and long answers are weak. Expect
|
| 220 |
+
mistakes outside the archive.
|
| 221 |
+
* The model retrieves and reads from its archive. It was not trained to reason across
|
| 222 |
+
many archive documents; that needs a bigger training budget than this project had.
|
| 223 |
+
Two-hop chains degrade at 100M tokens.
|
| 224 |
+
* Trained on public web text, so its outputs can carry the biases of that text.
|
| 225 |
+
* English only.
|
| 226 |
+
|
| 227 |
+
## Contact
|
| 228 |
+
|
| 229 |
+
Questions, results, or something you built with it: saikiranbathula1@gmail.com
|
| 230 |
+
|
| 231 |
+
---
|
| 232 |
+
|
| 233 |
+
*© NODEMIND 2026*
|
banner.jpg
ADDED
|
Git LFS Details
|
benchmarks/README.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Benchmarks
|
| 2 |
+
|
| 3 |
+
`results.json` holds every number on the model card; `report.pdf` is the full report.
|
| 4 |
+
The evaluation archives are held out and not distributed.
|
| 5 |
+
|
| 6 |
+
`run.py` is the harness that produced the numbers. You can point it at your own archive
|
| 7 |
+
(a folder with a `tokens.u32` stream and a `bank_valid.jsonl` question bank in the same
|
| 8 |
+
format as `results.json` describes) to run the same evaluation on your own data.
|
benchmarks/embedding_bench.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Benchmark the frozen binary vocabulary on word similarity (WordSim-353).
|
| 2 |
+
|
| 3 |
+
Every token has a fixed 512-bit code. If the codes carry meaning, similar words should
|
| 4 |
+
have nearby codes. This script scores each human-rated word pair by Hamming similarity
|
| 5 |
+
between the two words' codes and reports the Spearman correlation with the human ratings.
|
| 6 |
+
Random codes score about 0. Runs offline on the files in this repo.
|
| 7 |
+
|
| 8 |
+
python benchmarks/embedding_bench.py
|
| 9 |
+
"""
|
| 10 |
+
import csv, pathlib, sys
|
| 11 |
+
import numpy as np
|
| 12 |
+
HERE = pathlib.Path(__file__).resolve().parent; ROOT = HERE.parent
|
| 13 |
+
sys.path.insert(0, str(ROOT))
|
| 14 |
+
from shadow_runtime.retriever import enc
|
| 15 |
+
|
| 16 |
+
fp = np.unpackbits(np.load(ROOT / "fp131072.npy"), axis=1)[:, :512]
|
| 17 |
+
rows = [l.split(",") for l in open(HERE / "wordsim353.csv", encoding="utf-8").read().splitlines() if l]
|
| 18 |
+
rng = np.random.default_rng(0); rand = rng.integers(0, 2, size=fp.shape).astype(np.uint8)
|
| 19 |
+
|
| 20 |
+
def score(table):
|
| 21 |
+
xs, ys = [], []
|
| 22 |
+
for w1, w2, human in rows:
|
| 23 |
+
i1, i2 = enc(" " + w1.lower()), enc(" " + w2.lower())
|
| 24 |
+
if len(i1) != 1 or len(i2) != 1: continue
|
| 25 |
+
xs.append(1 - np.mean(table[i1[0]] != table[i2[0]])); ys.append(float(human))
|
| 26 |
+
from scipy.stats import spearmanr
|
| 27 |
+
return spearmanr(xs, ys).statistic, len(xs)
|
| 28 |
+
|
| 29 |
+
r, n = score(fp); r0, _ = score(rand)
|
| 30 |
+
print(f"WordSim-353, single-token pairs (n={n})")
|
| 31 |
+
print(f" frozen vocabulary codes : Spearman {r:.3f}")
|
| 32 |
+
print(f" random codes (baseline) : Spearman {r0:.3f}")
|
benchmarks/report.pdf
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
%PDF-1.4
|
| 2 |
+
%���� ReportLab Generated PDF document (opensource)
|
| 3 |
+
1 0 obj
|
| 4 |
+
<<
|
| 5 |
+
/F1 2 0 R /F2 3 0 R /F3 4 0 R
|
| 6 |
+
>>
|
| 7 |
+
endobj
|
| 8 |
+
2 0 obj
|
| 9 |
+
<<
|
| 10 |
+
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
| 11 |
+
>>
|
| 12 |
+
endobj
|
| 13 |
+
3 0 obj
|
| 14 |
+
<<
|
| 15 |
+
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
| 16 |
+
>>
|
| 17 |
+
endobj
|
| 18 |
+
4 0 obj
|
| 19 |
+
<<
|
| 20 |
+
/BaseFont /Symbol /Name /F3 /Subtype /Type1 /Type /Font
|
| 21 |
+
>>
|
| 22 |
+
endobj
|
| 23 |
+
5 0 obj
|
| 24 |
+
<<
|
| 25 |
+
/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 9 0 R /Resources <<
|
| 26 |
+
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 27 |
+
>> /Rotate 0 /Trans <<
|
| 28 |
+
|
| 29 |
+
>>
|
| 30 |
+
/Type /Page
|
| 31 |
+
>>
|
| 32 |
+
endobj
|
| 33 |
+
6 0 obj
|
| 34 |
+
<<
|
| 35 |
+
/Contents 11 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 9 0 R /Resources <<
|
| 36 |
+
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 37 |
+
>> /Rotate 0 /Trans <<
|
| 38 |
+
|
| 39 |
+
>>
|
| 40 |
+
/Type /Page
|
| 41 |
+
>>
|
| 42 |
+
endobj
|
| 43 |
+
7 0 obj
|
| 44 |
+
<<
|
| 45 |
+
/PageMode /UseNone /Pages 9 0 R /Type /Catalog
|
| 46 |
+
>>
|
| 47 |
+
endobj
|
| 48 |
+
8 0 obj
|
| 49 |
+
<<
|
| 50 |
+
/Author (\(anonymous\)) /CreationDate (D:20260820230714+10'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260820230714+10'00') /Producer (ReportLab PDF Library - \(opensource\))
|
| 51 |
+
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
| 52 |
+
>>
|
| 53 |
+
endobj
|
| 54 |
+
9 0 obj
|
| 55 |
+
<<
|
| 56 |
+
/Count 2 /Kids [ 5 0 R 6 0 R ] /Type /Pages
|
| 57 |
+
>>
|
| 58 |
+
endobj
|
| 59 |
+
10 0 obj
|
| 60 |
+
<<
|
| 61 |
+
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2711
|
| 62 |
+
>>
|
| 63 |
+
stream
|
| 64 |
+
Gb"/)?ZXu3&V'PQfX[E>dME97_#Nq'grj!+(L<N_f1ouR!g/RtL]h5iZD)9p!@Uj1/Q&TkmW>=SLgtC*]8PM,mVc1Hi.;?hrldub]*ETX'*_.g@1(XW5!44E%hDark7J%0+k]%JJW#^'_t3o/RJB*"'giP,0Hg`,%jN'($kFXDpE/;mpt'uq$/@7_P%$?GZP8jVdr<>q2D8dP:dA7uNnekIn23=UKngbYQF)6`J3lJXp&m&5N'[1?O&ld?8@F7@@>Nns`/p6cO&q)"5-roGcWml2[m(Jb#CC;-RsXUlSe;YVYog]aV6"dZP0[4%%p9Q8&L+>7HU^EAfRS?&^oe1YCVUtuN=j13`;DZM`dDaF12"QfX3^ub96[%EYgRX57KfSnciR)m%$2"[436*\O;jM'Rdo'9.sM/99mMm>SZE0%D*ZQA_/;Q@-6k:7E1L=Q<a0@!o-;fPKal$&Y-B)E@1n=g.Ls;aIPk=YD$0`7dQQ'JMqNXhX[KuA%.p'XqhUbC3Qu1`Qr6W6^j=;43@kQ,eN@oa;4;))i'QtHIT@@orCOi2P3#U!1f:94,X+eOcl0IfS^OfQC0qlZ^pO5(SJGHaOFe@B!3NOeIkkZX\QV8`Gf5<X7Pj^<b68RJ^cWIk!S)_@2rGh\5F^,n&L/+&M&5rD2$:IFEQ\U>FiRm$WV$#k.]+`ASsTR!68i2-Ks:t=M760_A/.50R*o4H=E?X7Q5Am,cY*>ajQmh]NaBmu(+o6=_8o>A6>DidE[>b,R#2l^:hs)\Qr^N"rb8IU\#(;CnAM=9LfHHD'hh^Qf7pP>;cth')-*`W\'n>S[&LQJJ[=E1B)MS0p1iXLD/skqE\oG*PEI&9SiJBBGKo^_d%$"3)Xr%X?K^uF?#9#=fE>4e7brr8*58MMQC0:!C\C-1UDY@!2#jT/Pq]jj!.L<.3)3p"@8ku4#1F[fXL.rA[3h#Qh;@H(!(SXKqX-gLdbHG2HI6'h'0)3<Ak2<A#sj#.r/9(oE`oW0#u+.n+4Wfm"=:dS>bk#(ZsZC(GX7"1*-i@"&GC7l=E-G,!l0@G83D>ErjQ7kItZ;`N9(3F/qCAV0>/D<.*q9\*$!Scf(/:ZP%C.!:sp6-)P,0^JkT_Z^OGr*E!=#T55oFi[MIae?n-u(.nC]IVM'VZqgFOe]<Ipp1-C<#JDL("4!"t4ZW4t&q"B;fdDod*HG.htjnDOm^5S<5o]1!2m4dnYj2]1Th2b70NA./giaPrmoXaG-%?Wp@]^_YVE\X;cks(*7D+!>j/Mj2_hi]cU.V)"+b@/4FQL4,OO?-o/Qck]\M,rD>)UK5D(0o&fjiI\OD),ZfMHlmpVEH7a=#<ptlY?KLEi,#3pNOASj?#%q]Q<]*OdZteZ:[[hV)1/%YBe-*gFsCB1SqnlS4euNgID!08]+uP'FD\0,)OgkBN9&<O6#@PbP",4YgGZ&0Z\/7;jqD(.\g1RC'RHQ4g3p]2W"UkV_dOQT+=_io2X;Nj7hZ\?`l)14Z`Oiq=tBnY\k%B\\f9H=MVTr\Qsg!pUL3-rM7>O6kBh;E9S#&K9f3*4X,_d;J:#NLM"ji&`]fb>GJVim*lZoY@D;1m9rNZ!`B9?d+^oTs*PNd"`Xq'%Pnr#:8(7<m+cgc\>#@D3;.0#J)'1e!a7=GZQ^L5Pm0=h.U=c4RMIrHEhRB$M/8@jE_0EZkma`I,eXDuSlc/R0k#$QXsb)kb\ginS#tI[`RMQQm(H@$q4:+#E4Z<e`WW>9Tgj!nabl"El0RaUbqtYaB6,lPf\sW+XS'5Wi4c!U!BVk)%eQ8Ao2,iTV_]K]jr%,KE<AOBT_kp8_._,oM)90=XX5>qMG#Z(c?kCl3WM\/4@/IjWE#XOCXnq.QRk'RC0n2iSZ1ZZY]$Bdfn$7CI<q@2cFf7/S2P=sP2Tet?ke?5<::`q#;aWT[^BUO1Nt_*UtUfl46oW64^^LMc983Mq<@RS$*%Gm8bAP?LSolQb$mLqk&6JrV&?fo<\UP6B?P.%@Qc<nKtBoq0F;VE<KfO>_@DF6n"K7?NlF#\m+oi%E:S%+d6_ZL>KD*m*IKdkga%h?Fr_Fi,5:('0*oiE<Vg]M$On`8JB+SBm3UbIo\,5apF$<_MpLhNh[_t_k=Tqq*1qEOl\KJmr]9$@+@@OJQ]aLUPRMR0"Y)3Hh:&NV/Hr9J?V<]35:6#YPu"KU4Y)Y'F%C6',X[ao25K\]rf<=.*61QrkW<g?U%jrB0So1R$-]X.+k^p(o_(g^#MMBIrm;&MhJ,.]hcoY[oY)Un"n0D-ed(&gT-3k234!Ru"!cNbo8pXZWsK"S$Mn^-Cg;*315NS!gKs>D!f%VH,Y?8thZF3J*$Z)`j?$RjDoWoX"ci&h:-7S>-2lr=ER&>K\,aou(4s/8T^(9&2r6S;6k/_;l\N:]L[5eNjf6[TZs]@HNd<S4d(&AU%lN]s_LjW.FVF##Ps2jka3WJ<4V);s*R;PSISWr8TBlL=Dba!ED"hiX*Fh4ueP<R_^,`P/7pA(cgpXC-B(/-OSD3$.Y1.ZFa-,(TP^g'`>=W#RjTcR2F1_A'ieMXOo7cp^q$ROtq)79dpD*_3bQn)7i7[i\s0Dm?[m'L$kRQe7!rY`f2V<jL>\[JtUSTlKp[#>u*FoEgqaZ%glM#5bAH,\,TijQ7qdGjgYu$36[%[F&%.n/R'QB0L#j=Zjb/FfPd=UQT$g*j.ne^crY5SUuhKlj~>endstream
|
| 65 |
+
endobj
|
| 66 |
+
11 0 obj
|
| 67 |
+
<<
|
| 68 |
+
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1444
|
| 69 |
+
>>
|
| 70 |
+
stream
|
| 71 |
+
GatU29on$e&A@sB]M$Z.^h-""bA'gEg9_,D#dQ23b?ejrCb_5HLSWMerq_50mBsFh363.WJ`Q=6R5Z3#7t_/\s"@U7m0p2UF)Z]`!^'ltAiKL+2VRQ]/.Dj'DO7130/AQc:V.#O##pU>@p'r^NgMUdjW(TB='trUo>dbY+O]HuDN%mshAP_F_;d-3Z>QLE3J^4:P@uuhr#2oFR(_4!EWG5NbE=tP!FEqq5oa2*2VQnu@cp9'$Pm@m8(ip:NE?d&s0Kn27YFaF'M2?0l*Qge_F*+EY]r#48C9'*?E'l)9'd7;,YO76/.E#[O*8&F/BL>4GWLp.j6_gM#ZcUd?G@;DZE.]s62DO*nW-2FI6IZJEP[MX.:)uqSO\aiS0^:Y?\87tp_4njoX]k&s+gO7k2n5a61PiM8$'cbbPj8KoWn$@,+-C-d6@P$fF>q;3KJl1<O9B/K:h6S9rC,u2N<0YqHq=+o,m1ITB,J\1TAV;&W;Q[=?_['^7H@G[ssB>K$E>/C*.(f^M`P;5E(UuU6G!#c!fqZjr@?J_@f'SoJ?%$W*^'jSEE8J"70H9"=rJB86%?'/<1.u<"(emK>Z\VnG,XZ*XB<u%YO_*KBRRN5G,YLjD(&i6f&U:6hXuDO5t(T5oWU4X5q'^nVlQGfQ+7#Q"!fFEHp>j$(e!B(pa[SDTQ*OpTf@H;DntBd\.uO3_L2uW7iGfE#(lN62+p!E2@@V^:'nCeu&&R_q,$S[S^S/WO&mu(u9!o&)PIW/#2&qA#\0P('X8#`1gt/k#q/0->E!'=TbGHBh+uX8:/c2@:<t&]u/ZknlV&%.m6m72mRL>h@T*dduf:2cBG>;(+@FRX7YuNRUK51/2\[n<Nr%fWhgA)==f=d8pW^L9pQR7'%URqY<*5(UO3N-MEKQmC#uuWdE!nBR!Z-P&pDWd.[5(R=2F4BPgTf]#i@[`!U<aA'l>\_SpJ7B0bo<gc<I-3BVdBJ0t^a#!4)GN9@A9FH&P5h0IV9(Cm9*Q/54N[%4j*P#5qd<&A&N/`\<JM$fo<hF6BCJ:DJ^rhRg%l8b^0>?0-YuBn(=i*lJ=36dF)JMAf%Q2Um)fYXFZti?;kNP+`l\mG+]K'fi\O!OPb-ClE^=+bAC&U'iN4TY#ABej5&WFRqu4R]8*<Y'N$ABf.mU?V?Hf2-!^U[4Bti.U9+,ThKWd=rXBPZ!2C^jjHJ#Cq9-HShSqt/!UUUKQgX>PhZer-93S/d;jE5f*)g3^]ZB)]2qC'9;PqXkO@:1DcWZ$ObQrXh=Y^"1lb2'X*lG?#2s$lK$S*tF-qY/]bdu:G"EHA=7gHnZHjY+agB+orcb(&6Mt0$Ps\n[/bNh_<#SsMM(Le2I%*'l>[_M2>tM*<0mqCmAS#NOimL4p6bQ:34Li5&?V\eBU:%2P-TO#%VlO*]0%<6q)7&Mf0V4*#VaZ-`ImY#9p]~>endstream
|
| 72 |
+
endobj
|
| 73 |
+
xref
|
| 74 |
+
0 12
|
| 75 |
+
0000000000 65535 f
|
| 76 |
+
0000000061 00000 n
|
| 77 |
+
0000000112 00000 n
|
| 78 |
+
0000000219 00000 n
|
| 79 |
+
0000000331 00000 n
|
| 80 |
+
0000000408 00000 n
|
| 81 |
+
0000000612 00000 n
|
| 82 |
+
0000000816 00000 n
|
| 83 |
+
0000000884 00000 n
|
| 84 |
+
0000001164 00000 n
|
| 85 |
+
0000001229 00000 n
|
| 86 |
+
0000004032 00000 n
|
| 87 |
+
trailer
|
| 88 |
+
<<
|
| 89 |
+
/ID
|
| 90 |
+
[<945055cca686fd3777c3df99ffdf03c1><945055cca686fd3777c3df99ffdf03c1>]
|
| 91 |
+
% ReportLab generated PDF document -- digest (opensource)
|
| 92 |
+
|
| 93 |
+
/Info 8 0 R
|
| 94 |
+
/Root 7 0 R
|
| 95 |
+
/Size 12
|
| 96 |
+
>>
|
| 97 |
+
startxref
|
| 98 |
+
5568
|
| 99 |
+
%%EOF
|
benchmarks/results.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"banks": {
|
| 3 |
+
"1M": {
|
| 4 |
+
"T1": "12/12",
|
| 5 |
+
"T2": "5/5",
|
| 6 |
+
"T3": "5/5",
|
| 7 |
+
"T4": "4/5",
|
| 8 |
+
"T5": "2/2",
|
| 9 |
+
"T7": "9/9",
|
| 10 |
+
"ALL": 0.974
|
| 11 |
+
},
|
| 12 |
+
"10M": {
|
| 13 |
+
"T1": "49/50",
|
| 14 |
+
"T2": "32/33",
|
| 15 |
+
"T3": "25/28",
|
| 16 |
+
"T4": "10/14",
|
| 17 |
+
"T5": "24/24",
|
| 18 |
+
"T7": "23/23",
|
| 19 |
+
"ALL": 0.948
|
| 20 |
+
},
|
| 21 |
+
"100M": {
|
| 22 |
+
"T1": "487/517",
|
| 23 |
+
"T2": "348/374",
|
| 24 |
+
"T3": "90/229",
|
| 25 |
+
"T4": "102/177",
|
| 26 |
+
"T5": "169/169",
|
| 27 |
+
"T7": "216/231",
|
| 28 |
+
"ALL": 0.832
|
| 29 |
+
}
|
| 30 |
+
},
|
| 31 |
+
"niah": {
|
| 32 |
+
"1M": {
|
| 33 |
+
"0.1": 0.875,
|
| 34 |
+
"0.3": 1.0,
|
| 35 |
+
"0.5": 1.0,
|
| 36 |
+
"0.7": 1.0,
|
| 37 |
+
"0.9": 1.0,
|
| 38 |
+
"overall": 0.975
|
| 39 |
+
},
|
| 40 |
+
"10M": {
|
| 41 |
+
"0.1": 0.875,
|
| 42 |
+
"0.3": 1.0,
|
| 43 |
+
"0.5": 1.0,
|
| 44 |
+
"0.7": 1.0,
|
| 45 |
+
"0.9": 1.0,
|
| 46 |
+
"overall": 0.975
|
| 47 |
+
},
|
| 48 |
+
"100M": {
|
| 49 |
+
"0.1": 0.875,
|
| 50 |
+
"0.3": 1.0,
|
| 51 |
+
"0.5": 1.0,
|
| 52 |
+
"0.7": 1.0,
|
| 53 |
+
"0.9": 1.0,
|
| 54 |
+
"overall": 0.975
|
| 55 |
+
}
|
| 56 |
+
},
|
| 57 |
+
"ruler": {
|
| 58 |
+
"1M": {
|
| 59 |
+
"S-NIAH-1": 1.0,
|
| 60 |
+
"S-NIAH-2": 1.0,
|
| 61 |
+
"MK-NIAH": 1.0,
|
| 62 |
+
"VT": 1.0,
|
| 63 |
+
"overall": 1.0
|
| 64 |
+
},
|
| 65 |
+
"10M": {
|
| 66 |
+
"S-NIAH-1": 1.0,
|
| 67 |
+
"S-NIAH-2": 1.0,
|
| 68 |
+
"MK-NIAH": 1.0,
|
| 69 |
+
"VT": 1.0,
|
| 70 |
+
"overall": 1.0
|
| 71 |
+
}
|
| 72 |
+
},
|
| 73 |
+
"babilong": {
|
| 74 |
+
"1M": {
|
| 75 |
+
"qa1": 1.0,
|
| 76 |
+
"qa2": 1.0,
|
| 77 |
+
"overall": 1.0
|
| 78 |
+
},
|
| 79 |
+
"10M": {
|
| 80 |
+
"qa1": 1.0,
|
| 81 |
+
"qa2": 1.0,
|
| 82 |
+
"overall": 1.0
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
}
|
benchmarks/run.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Reproduce the model card numbers. Expects archive directories under data/archives/{1m,10m,100m}
|
| 2 |
+
(tokens.u32 + meta.json + a question bank), which are not distributed with this repository.
|
| 3 |
+
python benchmarks/run.py [--tiers 1M,10M,100M]
|
| 4 |
+
"""
|
| 5 |
+
import sys, os, json, time, random, pathlib
|
| 6 |
+
import numpy as np
|
| 7 |
+
HERE = pathlib.Path(__file__).resolve().parent; ROOT = HERE.parent
|
| 8 |
+
sys.path.insert(0, str(ROOT)); sys.path.insert(0, str(ROOT / "shadow_runtime"))
|
| 9 |
+
from shadow_runtime.retriever import load_archive, Inverted, enc, _dec
|
| 10 |
+
from shadow_runtime.answer_engine import Engine
|
| 11 |
+
from shadow_runtime.prompt import normalize
|
| 12 |
+
|
| 13 |
+
TIERS = {"1M": "1m", "10M": "10m", "100M": "100m"}
|
| 14 |
+
sel = "1M,10M,100M"
|
| 15 |
+
if "--tiers" in sys.argv: sel = sys.argv[sys.argv.index("--tiers") + 1]
|
| 16 |
+
out = {}
|
| 17 |
+
for tier in sel.split(","):
|
| 18 |
+
path = ROOT / "data" / "archives" / TIERS[tier]
|
| 19 |
+
tok, meta, bank = load_archive(str(path))
|
| 20 |
+
t0 = time.time(); inv = Inverted(tok)
|
| 21 |
+
print(f"[{tier}] index {len(tok)//64} blocks in {time.time()-t0:.0f}s", flush=True)
|
| 22 |
+
eng = Engine(tok, inv)
|
| 23 |
+
per = {}
|
| 24 |
+
for b in bank:
|
| 25 |
+
a, how = eng.answer(b["question"])
|
| 26 |
+
ok = normalize(a) == normalize(str(b["answer"]))
|
| 27 |
+
d = per.setdefault(b["task"], [0, 0]); d[0] += ok; d[1] += 1
|
| 28 |
+
tot = sum(v[0] for v in per.values()); n = sum(v[1] for v in per.values())
|
| 29 |
+
out[tier] = {t: f"{v[0]}/{v[1]}" for t, v in sorted(per.items())}; out[tier]["ALL"] = round(tot / n, 3)
|
| 30 |
+
print(f"[{tier}] " + " ".join(f"{t} {v[0]}/{v[1]}" for t, v in sorted(per.items())) + f" ALL {tot/n:.2f}", flush=True)
|
| 31 |
+
json.dump(out, open(HERE / "my_results.json", "w"), indent=1)
|
| 32 |
+
print("wrote benchmarks/my_results.json")
|
benchmarks/wordsim353.csv
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
love,sex,6.77
|
| 2 |
+
tiger,cat,7.35
|
| 3 |
+
tiger,tiger,10.00
|
| 4 |
+
book,paper,7.46
|
| 5 |
+
computer,keyboard,7.62
|
| 6 |
+
computer,internet,7.58
|
| 7 |
+
plane,car,5.77
|
| 8 |
+
train,car,6.31
|
| 9 |
+
telephone,communication,7.50
|
| 10 |
+
television,radio,6.77
|
| 11 |
+
media,radio,7.42
|
| 12 |
+
drug,abuse,6.85
|
| 13 |
+
bread,butter,6.19
|
| 14 |
+
cucumber,potato,5.92
|
| 15 |
+
doctor,nurse,7.00
|
| 16 |
+
professor,doctor,6.62
|
| 17 |
+
student,professor,6.81
|
| 18 |
+
smart,student,4.62
|
| 19 |
+
smart,stupid,5.81
|
| 20 |
+
company,stock,7.08
|
| 21 |
+
stock,market,8.08
|
| 22 |
+
stock,phone,1.62
|
| 23 |
+
stock,CD,1.31
|
| 24 |
+
stock,jaguar,0.92
|
| 25 |
+
stock,egg,1.81
|
| 26 |
+
fertility,egg,6.69
|
| 27 |
+
stock,live,3.73
|
| 28 |
+
stock,life,0.92
|
| 29 |
+
book,library,7.46
|
| 30 |
+
bank,money,8.12
|
| 31 |
+
wood,forest,7.73
|
| 32 |
+
money,cash,9.15
|
| 33 |
+
professor,cucumber,0.31
|
| 34 |
+
king,cabbage,0.23
|
| 35 |
+
king,queen,8.58
|
| 36 |
+
king,rook,5.92
|
| 37 |
+
bishop,rabbi,6.69
|
| 38 |
+
Jerusalem,Israel,8.46
|
| 39 |
+
Jerusalem,Palestinian,7.65
|
| 40 |
+
holy,sex,1.62
|
| 41 |
+
fuck,sex,9.44
|
| 42 |
+
Maradona,football,8.62
|
| 43 |
+
football,soccer,9.03
|
| 44 |
+
football,basketball,6.81
|
| 45 |
+
football,tennis,6.63
|
| 46 |
+
tennis,racket,7.56
|
| 47 |
+
Arafat,peace,6.73
|
| 48 |
+
Arafat,terror,7.65
|
| 49 |
+
Arafat,Jackson,2.50
|
| 50 |
+
law,lawyer,8.38
|
| 51 |
+
movie,star,7.38
|
| 52 |
+
movie,popcorn,6.19
|
| 53 |
+
movie,critic,6.73
|
| 54 |
+
movie,theater,7.92
|
| 55 |
+
physics,proton,8.12
|
| 56 |
+
physics,chemistry,7.35
|
| 57 |
+
space,chemistry,4.88
|
| 58 |
+
alcohol,chemistry,5.54
|
| 59 |
+
vodka,gin,8.46
|
| 60 |
+
vodka,brandy,8.13
|
| 61 |
+
drink,car,3.04
|
| 62 |
+
drink,ear,1.31
|
| 63 |
+
drink,mouth,5.96
|
| 64 |
+
drink,eat,6.87
|
| 65 |
+
baby,mother,7.85
|
| 66 |
+
drink,mother,2.65
|
| 67 |
+
car,automobile,8.94
|
| 68 |
+
gem,jewel,8.96
|
| 69 |
+
journey,voyage,9.29
|
| 70 |
+
boy,lad,8.83
|
| 71 |
+
coast,shore,9.10
|
| 72 |
+
asylum,madhouse,8.87
|
| 73 |
+
magician,wizard,9.02
|
| 74 |
+
midday,noon,9.29
|
| 75 |
+
furnace,stove,8.79
|
| 76 |
+
food,fruit,7.52
|
| 77 |
+
bird,cock,7.10
|
| 78 |
+
bird,crane,7.38
|
| 79 |
+
tool,implement,6.46
|
| 80 |
+
brother,monk,6.27
|
| 81 |
+
crane,implement,2.69
|
| 82 |
+
lad,brother,4.46
|
| 83 |
+
journey,car,5.85
|
| 84 |
+
monk,oracle,5.00
|
| 85 |
+
cemetery,woodland,2.08
|
| 86 |
+
food,rooster,4.42
|
| 87 |
+
coast,hill,4.38
|
| 88 |
+
forest,graveyard,1.85
|
| 89 |
+
shore,woodland,3.08
|
| 90 |
+
monk,slave,0.92
|
| 91 |
+
coast,forest,3.15
|
| 92 |
+
lad,wizard,0.92
|
| 93 |
+
chord,smile,0.54
|
| 94 |
+
glass,magician,2.08
|
| 95 |
+
noon,string,0.54
|
| 96 |
+
rooster,voyage,0.62
|
| 97 |
+
money,dollar,8.42
|
| 98 |
+
money,cash,9.08
|
| 99 |
+
money,currency,9.04
|
| 100 |
+
money,wealth,8.27
|
| 101 |
+
money,property,7.57
|
| 102 |
+
money,possession,7.29
|
| 103 |
+
money,bank,8.50
|
| 104 |
+
money,deposit,7.73
|
| 105 |
+
money,withdrawal,6.88
|
| 106 |
+
money,laundering,5.65
|
| 107 |
+
money,operation,3.31
|
| 108 |
+
tiger,jaguar,8.00
|
| 109 |
+
tiger,feline,8.00
|
| 110 |
+
tiger,carnivore,7.08
|
| 111 |
+
tiger,mammal,6.85
|
| 112 |
+
tiger,animal,7.00
|
| 113 |
+
tiger,organism,4.77
|
| 114 |
+
tiger,fauna,5.62
|
| 115 |
+
tiger,zoo,5.87
|
| 116 |
+
psychology,psychiatry,8.08
|
| 117 |
+
psychology,anxiety,7.00
|
| 118 |
+
psychology,fear,6.85
|
| 119 |
+
psychology,depression,7.42
|
| 120 |
+
psychology,clinic,6.58
|
| 121 |
+
psychology,doctor,6.42
|
| 122 |
+
psychology,Freud,8.21
|
| 123 |
+
psychology,mind,7.69
|
| 124 |
+
psychology,health,7.23
|
| 125 |
+
psychology,science,6.71
|
| 126 |
+
psychology,discipline,5.58
|
| 127 |
+
psychology,cognition,7.48
|
| 128 |
+
planet,star,8.45
|
| 129 |
+
planet,constellation,8.06
|
| 130 |
+
planet,moon,8.08
|
| 131 |
+
planet,sun,8.02
|
| 132 |
+
planet,galaxy,8.11
|
| 133 |
+
planet,space,7.92
|
| 134 |
+
planet,astronomer,7.94
|
| 135 |
+
precedent,example,5.85
|
| 136 |
+
precedent,information,3.85
|
| 137 |
+
precedent,cognition,2.81
|
| 138 |
+
precedent,law,6.65
|
| 139 |
+
precedent,collection,2.50
|
| 140 |
+
precedent,group,1.77
|
| 141 |
+
precedent,antecedent,6.04
|
| 142 |
+
cup,coffee,6.58
|
| 143 |
+
cup,tableware,6.85
|
| 144 |
+
cup,article,2.40
|
| 145 |
+
cup,artifact,2.92
|
| 146 |
+
cup,object,3.69
|
| 147 |
+
cup,entity,2.15
|
| 148 |
+
cup,drink,7.25
|
| 149 |
+
cup,food,5.00
|
| 150 |
+
cup,substance,1.92
|
| 151 |
+
cup,liquid,5.90
|
| 152 |
+
jaguar,cat,7.42
|
| 153 |
+
jaguar,car,7.27
|
| 154 |
+
energy,secretary,1.81
|
| 155 |
+
secretary,senate,5.06
|
| 156 |
+
energy,laboratory,5.09
|
| 157 |
+
computer,laboratory,6.78
|
| 158 |
+
weapon,secret,6.06
|
| 159 |
+
FBI,fingerprint,6.94
|
| 160 |
+
FBI,investigation,8.31
|
| 161 |
+
investigation,effort,4.59
|
| 162 |
+
Mars,water,2.94
|
| 163 |
+
Mars,scientist,5.63
|
| 164 |
+
news,report,8.16
|
| 165 |
+
canyon,landscape,7.53
|
| 166 |
+
image,surface,4.56
|
| 167 |
+
discovery,space,6.34
|
| 168 |
+
water,seepage,6.56
|
| 169 |
+
sign,recess,2.38
|
| 170 |
+
Wednesday,news,2.22
|
| 171 |
+
mile,kilometer,8.66
|
| 172 |
+
computer,news,4.47
|
| 173 |
+
territory,surface,5.34
|
| 174 |
+
atmosphere,landscape,3.69
|
| 175 |
+
president,medal,3.00
|
| 176 |
+
war,troops,8.13
|
| 177 |
+
record,number,6.31
|
| 178 |
+
skin,eye,6.22
|
| 179 |
+
Japanese,American,6.50
|
| 180 |
+
theater,history,3.91
|
| 181 |
+
volunteer,motto,2.56
|
| 182 |
+
prejudice,recognition,3.00
|
| 183 |
+
decoration,valor,5.63
|
| 184 |
+
century,year,7.59
|
| 185 |
+
century,nation,3.16
|
| 186 |
+
delay,racism,1.19
|
| 187 |
+
delay,news,3.31
|
| 188 |
+
minister,party,6.63
|
| 189 |
+
peace,plan,4.75
|
| 190 |
+
minority,peace,3.69
|
| 191 |
+
attempt,peace,4.25
|
| 192 |
+
government,crisis,6.56
|
| 193 |
+
deployment,departure,4.25
|
| 194 |
+
deployment,withdrawal,5.88
|
| 195 |
+
energy,crisis,5.94
|
| 196 |
+
announcement,news,7.56
|
| 197 |
+
announcement,effort,2.75
|
| 198 |
+
stroke,hospital,7.03
|
| 199 |
+
disability,death,5.47
|
| 200 |
+
victim,emergency,6.47
|
| 201 |
+
treatment,recovery,7.91
|
| 202 |
+
journal,association,4.97
|
| 203 |
+
doctor,personnel,5.00
|
| 204 |
+
doctor,liability,5.19
|
| 205 |
+
liability,insurance,7.03
|
| 206 |
+
school,center,3.44
|
| 207 |
+
reason,hypertension,2.31
|
| 208 |
+
reason,criterion,5.91
|
| 209 |
+
hundred,percent,7.38
|
| 210 |
+
Harvard,Yale,8.13
|
| 211 |
+
hospital,infrastructure,4.63
|
| 212 |
+
death,row,5.25
|
| 213 |
+
death,inmate,5.03
|
| 214 |
+
lawyer,evidence,6.69
|
| 215 |
+
life,death,7.88
|
| 216 |
+
life,term,4.50
|
| 217 |
+
word,similarity,4.75
|
| 218 |
+
board,recommendation,4.47
|
| 219 |
+
governor,interview,3.25
|
| 220 |
+
OPEC,country,5.63
|
| 221 |
+
peace,atmosphere,3.69
|
| 222 |
+
peace,insurance,2.94
|
| 223 |
+
territory,kilometer,5.28
|
| 224 |
+
travel,activity,5.00
|
| 225 |
+
competition,price,6.44
|
| 226 |
+
consumer,confidence,4.13
|
| 227 |
+
consumer,energy,4.75
|
| 228 |
+
problem,airport,2.38
|
| 229 |
+
car,flight,4.94
|
| 230 |
+
credit,card,8.06
|
| 231 |
+
credit,information,5.31
|
| 232 |
+
hotel,reservation,8.03
|
| 233 |
+
grocery,money,5.94
|
| 234 |
+
registration,arrangement,6.00
|
| 235 |
+
arrangement,accommodation,5.41
|
| 236 |
+
month,hotel,1.81
|
| 237 |
+
type,kind,8.97
|
| 238 |
+
arrival,hotel,6.00
|
| 239 |
+
bed,closet,6.72
|
| 240 |
+
closet,clothes,8.00
|
| 241 |
+
situation,conclusion,4.81
|
| 242 |
+
situation,isolation,3.88
|
| 243 |
+
impartiality,interest,5.16
|
| 244 |
+
direction,combination,2.25
|
| 245 |
+
street,place,6.44
|
| 246 |
+
street,avenue,8.88
|
| 247 |
+
street,block,6.88
|
| 248 |
+
street,children,4.94
|
| 249 |
+
listing,proximity,2.56
|
| 250 |
+
listing,category,6.38
|
| 251 |
+
cell,phone,7.81
|
| 252 |
+
production,hike,1.75
|
| 253 |
+
benchmark,index,4.25
|
| 254 |
+
media,trading,3.88
|
| 255 |
+
media,gain,2.88
|
| 256 |
+
dividend,payment,7.63
|
| 257 |
+
dividend,calculation,6.48
|
| 258 |
+
calculation,computation,8.44
|
| 259 |
+
currency,market,7.50
|
| 260 |
+
OPEC,oil,8.59
|
| 261 |
+
oil,stock,6.34
|
| 262 |
+
announcement,production,3.38
|
| 263 |
+
announcement,warning,6.00
|
| 264 |
+
profit,warning,3.88
|
| 265 |
+
profit,loss,7.63
|
| 266 |
+
dollar,yen,7.78
|
| 267 |
+
dollar,buck,9.22
|
| 268 |
+
dollar,profit,7.38
|
| 269 |
+
dollar,loss,6.09
|
| 270 |
+
computer,software,8.50
|
| 271 |
+
network,hardware,8.31
|
| 272 |
+
phone,equipment,7.13
|
| 273 |
+
equipment,maker,5.91
|
| 274 |
+
luxury,car,6.47
|
| 275 |
+
five,month,3.38
|
| 276 |
+
report,gain,3.63
|
| 277 |
+
investor,earning,7.13
|
| 278 |
+
liquid,water,7.89
|
| 279 |
+
baseball,season,5.97
|
| 280 |
+
game,victory,7.03
|
| 281 |
+
game,team,7.69
|
| 282 |
+
marathon,sprint,7.47
|
| 283 |
+
game,series,6.19
|
| 284 |
+
game,defeat,6.97
|
| 285 |
+
seven,series,3.56
|
| 286 |
+
seafood,sea,7.47
|
| 287 |
+
seafood,food,8.34
|
| 288 |
+
seafood,lobster,8.70
|
| 289 |
+
lobster,food,7.81
|
| 290 |
+
lobster,wine,5.70
|
| 291 |
+
food,preparation,6.22
|
| 292 |
+
video,archive,6.34
|
| 293 |
+
start,year,4.06
|
| 294 |
+
start,match,4.47
|
| 295 |
+
game,round,5.97
|
| 296 |
+
boxing,round,7.61
|
| 297 |
+
championship,tournament,8.36
|
| 298 |
+
fighting,defeating,7.41
|
| 299 |
+
line,insurance,2.69
|
| 300 |
+
day,summer,3.94
|
| 301 |
+
summer,drought,7.16
|
| 302 |
+
summer,nature,5.63
|
| 303 |
+
day,dawn,7.53
|
| 304 |
+
nature,environment,8.31
|
| 305 |
+
environment,ecology,8.81
|
| 306 |
+
nature,man,6.25
|
| 307 |
+
man,woman,8.30
|
| 308 |
+
man,governor,5.25
|
| 309 |
+
murder,manslaughter,8.53
|
| 310 |
+
soap,opera,7.94
|
| 311 |
+
opera,performance,6.88
|
| 312 |
+
life,lesson,5.94
|
| 313 |
+
focus,life,4.06
|
| 314 |
+
production,crew,6.25
|
| 315 |
+
television,film,7.72
|
| 316 |
+
lover,quarrel,6.19
|
| 317 |
+
viewer,serial,2.97
|
| 318 |
+
possibility,girl,1.94
|
| 319 |
+
population,development,3.75
|
| 320 |
+
morality,importance,3.31
|
| 321 |
+
morality,marriage,3.69
|
| 322 |
+
Mexico,Brazil,7.44
|
| 323 |
+
gender,equality,6.41
|
| 324 |
+
change,attitude,5.44
|
| 325 |
+
family,planning,6.25
|
| 326 |
+
opera,industry,2.63
|
| 327 |
+
sugar,approach,0.88
|
| 328 |
+
practice,institution,3.19
|
| 329 |
+
ministry,culture,4.69
|
| 330 |
+
problem,challenge,6.75
|
| 331 |
+
size,prominence,5.31
|
| 332 |
+
country,citizen,7.31
|
| 333 |
+
planet,people,5.75
|
| 334 |
+
development,issue,3.97
|
| 335 |
+
experience,music,3.47
|
| 336 |
+
music,project,3.63
|
| 337 |
+
glass,metal,5.56
|
| 338 |
+
aluminum,metal,7.83
|
| 339 |
+
chance,credibility,3.88
|
| 340 |
+
exhibit,memorabilia,5.31
|
| 341 |
+
concert,virtuoso,6.81
|
| 342 |
+
rock,jazz,7.59
|
| 343 |
+
museum,theater,7.19
|
| 344 |
+
observation,architecture,4.38
|
| 345 |
+
space,world,6.53
|
| 346 |
+
preservation,world,6.19
|
| 347 |
+
admission,ticket,7.69
|
| 348 |
+
shower,thunderstorm,6.31
|
| 349 |
+
shower,flood,6.03
|
| 350 |
+
weather,forecast,8.34
|
| 351 |
+
disaster,area,6.25
|
| 352 |
+
governor,office,6.34
|
| 353 |
+
architecture,century,3.78
|
deployment/bin/linux/shadow
ADDED
|
Binary file (90.7 kB). View file
|
|
|
deployment/bin/windows/shadow.exe
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a54b2cd2b0973bb6545eb54f6e5b14e3561707374abc2ecb1ea5ebdb748591b3
|
| 3 |
+
size 143848
|
deployment/fp131072.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:efb7672a55e4998a8c0395cdec1c2207acf750e3ad1109088a61f66586da1d34
|
| 3 |
+
size 8388736
|
deployment/shadow250m_instruct.shdw
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6e0ad7893e0b4b3b420d8ee5d8dd7a1b7dd499fc698ba1b3a3fa40a7a0d5b9ea
|
| 3 |
+
size 52342802
|
finetune/FINETUNING.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Fine-tune SHADOW 250M
|
| 2 |
+
|
| 3 |
+
You can fine-tune SHADOW on your own data with one GPU and one command, then export your model
|
| 4 |
+
as a 52 MB file that runs at hundreds of tokens per second on a plain CPU. This page shows the
|
| 5 |
+
full workflow with a real example we ran ourselves, results attached.
|
| 6 |
+
|
| 7 |
+
## What you need
|
| 8 |
+
|
| 9 |
+
* One GPU with 12 GB or more (a gaming laptop works; we used one)
|
| 10 |
+
* Python with torch, numpy, sentencepiece
|
| 11 |
+
* The files in this repo: `shadow250m_instruct.pt`, `fp131072.npy`, `modeling/`, `finetune.py`, `export_model.py`
|
| 12 |
+
|
| 13 |
+
## 1. Prepare your data
|
| 14 |
+
|
| 15 |
+
One conversation per line in a .jsonl file:
|
| 16 |
+
|
| 17 |
+
{"messages": [{"role": "user", "content": "your question"}, {"role": "assistant", "content": "the answer you want"}]}
|
| 18 |
+
|
| 19 |
+
A few hundred conversations are enough for a style change. A few thousand for a domain.
|
| 20 |
+
`examples_pirate.jsonl` in this repo is the dataset used in the example below.
|
| 21 |
+
|
| 22 |
+
## 2. Train
|
| 23 |
+
|
| 24 |
+
python finetune.py --data my_data.jsonl --steps 150 --out my_model
|
| 25 |
+
|
| 26 |
+
Defaults: learning rate 1e-5 with cosine decay, loss only on assistant tokens, batch of
|
| 27 |
+
32,768 tokens per step, quantisation kept in the loop so the exported model matches the
|
| 28 |
+
trained one. A small validation split is held out automatically and printed before and after.
|
| 29 |
+
|
| 30 |
+
| hardware | speed | 150 steps |
|
| 31 |
+
|---|---|---|
|
| 32 |
+
| laptop RTX 3080 (thermally limited) | ~0.9k tokens/s | ~90 min |
|
| 33 |
+
| A100 | ~7.7k tokens/s | ~12 min |
|
| 34 |
+
|
| 35 |
+
## 3. Export and run on CPU
|
| 36 |
+
|
| 37 |
+
python export_model.py my_model/finetuned.pt my_model.shdw
|
| 38 |
+
./shadow my_model.shdw fp131072.npy --chat
|
| 39 |
+
|
| 40 |
+
The export prints a round-trip check against the trained model. Your fine-tuned model is now
|
| 41 |
+
a 52 MB file.
|
| 42 |
+
|
| 43 |
+
## The example we ran: pirate mode
|
| 44 |
+
|
| 45 |
+
472 conversations, assistant answers rewritten in pirate speech. 150 steps, 90 minutes on a
|
| 46 |
+
laptop GPU. Validation loss on the pirate data went from 2.570 to 2.512. PIQA on a 500-item
|
| 47 |
+
subset stayed level: 0.61 before, 0.63 after. Same prompts, greedy decoding, before and after:
|
| 48 |
+
|
| 49 |
+
**"What is the capital of France?"**
|
| 50 |
+
|
| 51 |
+
Before:
|
| 52 |
+
> The capital of France is Paris. It is the seat of the French Republic, and it is known for
|
| 53 |
+
> its historical landmarks, including the Eiffel Tower, the Louvre Museum...
|
| 54 |
+
|
| 55 |
+
After:
|
| 56 |
+
> The capital of France be Paris. It is a UNESCO World Heritage Site and a major
|
| 57 |
+
> international city. It has a rich history and a rich culture. Paris be home to the Louvre
|
| 58 |
+
> Museum...
|
| 59 |
+
|
| 60 |
+
**"Give three tips for staying focused while studying."**
|
| 61 |
+
|
| 62 |
+
Before:
|
| 63 |
+
> 1. Aim for a balance of time and energy. Aim for 10-15 minutes of focused work per day...
|
| 64 |
+
|
| 65 |
+
After:
|
| 66 |
+
> When confronted with a problem, staying focused means focusing on what needs to be done
|
| 67 |
+
> first. Blimey, to stay motivated, it's essential to set realistic goals and keep the
|
| 68 |
+
> momentum going...
|
| 69 |
+
|
| 70 |
+
The knowledge stays, the voice changes. Swap the pirate data for your support tone, your
|
| 71 |
+
domain, or your language style and the same 90 minutes applies.
|
| 72 |
+
|
| 73 |
+
## Tips
|
| 74 |
+
|
| 75 |
+
* Style: 100 to 500 pairs, 100 to 200 steps.
|
| 76 |
+
* Domain knowledge: thousands of pairs, 300 to 1000 steps, and consider mixing 30 to 50
|
| 77 |
+
percent general chat data so the model does not forget how to converse.
|
| 78 |
+
* Learning rate: stay at or below 2e-5. This model trains with quantisation active and high
|
| 79 |
+
rates make it unstable.
|
| 80 |
+
* Check before and after on prompts you care about, with greedy decoding, like we did above.
|
finetune/examples_pirate.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
finetune/export_model.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Export a fine-tuned checkpoint to the 52 MB deploy file for the CPU runtime.
|
| 2 |
+
python export_model.py my_model/finetuned.pt my_model.shdw
|
| 3 |
+
"""
|
| 4 |
+
import os, sys, pathlib
|
| 5 |
+
for k, v in {"SHADOW_D": "1536", "SHADOW_NL": "10", "SHADOW_NH": "24", "SHADOW_NKV": "2", "SHADOW_HD": "64",
|
| 6 |
+
"SHADOW_FFNH": "4224", "SHADOW_FAST_ATTN": "1", "SHADOW_KV_BITS": "1", "SHADOW_KV_TWO_TIER": "1"}.items():
|
| 7 |
+
os.environ.setdefault(k, v)
|
| 8 |
+
HERE = pathlib.Path(__file__).resolve().parent
|
| 9 |
+
sys.path.insert(0, str(HERE / "modeling"))
|
| 10 |
+
import subprocess
|
| 11 |
+
src, dst = sys.argv[1], sys.argv[2]
|
| 12 |
+
tmp = dst + ".full"
|
| 13 |
+
r = subprocess.run([sys.executable, str(HERE / "modeling" / "export_ternary.py"), src, tmp],
|
| 14 |
+
env={**os.environ, "PYTHONPATH": str(HERE / "modeling")})
|
| 15 |
+
if r.returncode: sys.exit(r.returncode)
|
| 16 |
+
r = subprocess.run([sys.executable, str(HERE / "modeling" / "repack_shdw.py"), tmp, dst, "--fp16"])
|
| 17 |
+
os.remove(tmp)
|
| 18 |
+
print("wrote", dst)
|
finetune/finetune.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Fine-tune SHADOW 250M Instruct on your own chat data, on a single GPU (8 GB is enough).
|
| 2 |
+
|
| 3 |
+
Data: a .jsonl file, one conversation per line:
|
| 4 |
+
{"messages": [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]}
|
| 5 |
+
|
| 6 |
+
Run:
|
| 7 |
+
python finetune.py --data my_data.jsonl --steps 300 --out my_model
|
| 8 |
+
python export_model.py my_model/finetuned.pt my_model.shdw # 52 MB deploy file
|
| 9 |
+
./shadow my_model.shdw fp131072.npy --chat # your model, on CPU
|
| 10 |
+
|
| 11 |
+
Defaults are safe for style and domain fine-tunes: low learning rate, loss only on assistant
|
| 12 |
+
tokens, quantisation kept in the loop so the exported model behaves like the trained one.
|
| 13 |
+
"""
|
| 14 |
+
import argparse, json, math, os, sys, time, random, pathlib
|
| 15 |
+
for k, v in {"SHADOW_D": "1536", "SHADOW_NL": "10", "SHADOW_NH": "24", "SHADOW_NKV": "2", "SHADOW_HD": "64",
|
| 16 |
+
"SHADOW_FFNH": "4224", "SHADOW_FAST_ATTN": "1", "SHADOW_KV_BITS": "1", "SHADOW_KV_TWO_TIER": "1"}.items():
|
| 17 |
+
os.environ.setdefault(k, v)
|
| 18 |
+
HERE = pathlib.Path(__file__).resolve().parent
|
| 19 |
+
sys.path.insert(0, str(HERE)); sys.path.insert(0, str(HERE / "modeling")); sys.path.insert(0, str(HERE / "shadow_runtime"))
|
| 20 |
+
import numpy as np, torch, torch.nn.functional as F
|
| 21 |
+
import common
|
| 22 |
+
from common import requant
|
| 23 |
+
from model_250m import Shadow250M
|
| 24 |
+
from retriever import enc
|
| 25 |
+
|
| 26 |
+
BOS, EOS, SOT, EOT = 2, 1, 8, 9
|
| 27 |
+
|
| 28 |
+
def get_args():
|
| 29 |
+
ap = argparse.ArgumentParser()
|
| 30 |
+
ap.add_argument("--data", required=True, help="jsonl with {'messages': [...]} per line")
|
| 31 |
+
ap.add_argument("--init", default=str(HERE / "shadow250m_instruct.pt"))
|
| 32 |
+
ap.add_argument("--table", default=str(HERE / "fp131072.npy"))
|
| 33 |
+
ap.add_argument("--out", default="finetuned")
|
| 34 |
+
ap.add_argument("--steps", type=int, default=300)
|
| 35 |
+
ap.add_argument("--lr", type=float, default=1e-5)
|
| 36 |
+
ap.add_argument("--ctx", type=int, default=2048)
|
| 37 |
+
ap.add_argument("--micro-batch", type=int, default=2)
|
| 38 |
+
ap.add_argument("--accum", type=int, default=8)
|
| 39 |
+
ap.add_argument("--warmup", type=int, default=20)
|
| 40 |
+
ap.add_argument("--val-frac", type=float, default=0.02)
|
| 41 |
+
ap.add_argument("--log-every", type=int, default=10)
|
| 42 |
+
ap.add_argument("--seed", type=int, default=0)
|
| 43 |
+
return ap.parse_args()
|
| 44 |
+
|
| 45 |
+
def build_ids(messages):
|
| 46 |
+
ids, msk = [BOS], [0]
|
| 47 |
+
for m in messages:
|
| 48 |
+
role = "user" if m["role"] != "assistant" else "model"
|
| 49 |
+
head = [SOT] + enc(role + "\n"); ids += head; msk += [0] * len(head)
|
| 50 |
+
body = enc(m["content"]) + [EOT] + enc("\n")
|
| 51 |
+
ids += body
|
| 52 |
+
msk += ([1] * (len(body) - 1) + [0]) if role == "model" else [0] * len(body)
|
| 53 |
+
return ids, msk
|
| 54 |
+
|
| 55 |
+
class Packer:
|
| 56 |
+
def __init__(s, path, ctx, rng, val_frac):
|
| 57 |
+
s.ex = []
|
| 58 |
+
for line in open(path, encoding="utf-8"):
|
| 59 |
+
line = line.strip()
|
| 60 |
+
if not line: continue
|
| 61 |
+
ids, msk = build_ids(json.loads(line)["messages"])
|
| 62 |
+
s.ex.append((np.asarray(ids, np.int64), np.asarray(msk, np.int64)))
|
| 63 |
+
rng.shuffle(s.ex)
|
| 64 |
+
if len(s.ex) < 2: raise SystemExit("need at least 2 conversations in the data file")
|
| 65 |
+
nval = min(max(1, int(len(s.ex) * val_frac)), max(1, len(s.ex) // 5))
|
| 66 |
+
s.val = s.ex[:nval]; s.train = s.ex[nval:]; s.ctx = ctx; s.rng = rng
|
| 67 |
+
print(f"data: {len(s.train)} train / {len(s.val)} val conversations")
|
| 68 |
+
def pack(s, B, val=False):
|
| 69 |
+
pool = s.val if val else s.train
|
| 70 |
+
X = np.zeros((B, s.ctx), np.int64); Y = np.full((B, s.ctx), -100, np.int64)
|
| 71 |
+
for r in range(B):
|
| 72 |
+
pos = 0
|
| 73 |
+
while pos < s.ctx:
|
| 74 |
+
ids, m = pool[s.rng.randrange(len(pool))]
|
| 75 |
+
ids, m = ids[:s.ctx - pos], m[:s.ctx - pos]
|
| 76 |
+
X[r, pos:pos + len(ids)] = ids
|
| 77 |
+
tgt = np.full(len(ids), -100, np.int64); tgt[:-1] = np.where(m[1:] == 1, ids[1:], -100)
|
| 78 |
+
Y[r, pos:pos + len(ids)] = tgt; pos += len(ids)
|
| 79 |
+
if pos > s.ctx * 0.9: break
|
| 80 |
+
return torch.tensor(X), torch.tensor(Y)
|
| 81 |
+
|
| 82 |
+
def main():
|
| 83 |
+
a = get_args(); rng = random.Random(a.seed); torch.manual_seed(a.seed)
|
| 84 |
+
dev = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 85 |
+
out = pathlib.Path(a.out); out.mkdir(parents=True, exist_ok=True)
|
| 86 |
+
_of = common.RVQ.forward
|
| 87 |
+
def _tern(s, x):
|
| 88 |
+
if s.g == 32:
|
| 89 |
+
w = s.weight; sc = 1.0 / w.abs().mean(dim=1, keepdim=True).clamp_(min=1e-5)
|
| 90 |
+
return F.linear(x, (w + ((w * sc).round().clamp(-1, 1) / sc - w).detach()).to(x.dtype))
|
| 91 |
+
return _of(s, x)
|
| 92 |
+
common.RVQ.forward = _tern
|
| 93 |
+
_oenc = common.RVQ.enc
|
| 94 |
+
def _enc2(s):
|
| 95 |
+
if s.g == 32: return
|
| 96 |
+
_oenc(s)
|
| 97 |
+
common.RVQ.enc = _enc2
|
| 98 |
+
fp = np.unpackbits(np.load(a.table), axis=1)[:, :512]
|
| 99 |
+
cent = torch.tensor(fp.astype(np.float32) * 2 - 1, device=dev); cent_n = F.normalize(cent, dim=-1)
|
| 100 |
+
model = Shadow250M(cent, cent_n, cent.shape[0]).to(dev)
|
| 101 |
+
ck = torch.load(a.init, map_location=dev, weights_only=False)
|
| 102 |
+
sd = {k: v.float() if v.is_floating_point() else v for k, v in ck["model"].items()}
|
| 103 |
+
model.load_state_dict(sd); requant(model)
|
| 104 |
+
for md in model.modules():
|
| 105 |
+
if isinstance(md, common.KVCodec1): md.eval()
|
| 106 |
+
print(f"loaded {a.init} on {dev}")
|
| 107 |
+
data = Packer(a.data, a.ctx, rng, a.val_frac)
|
| 108 |
+
opt = torch.optim.AdamW(model.parameters(), lr=a.lr, betas=(0.9, 0.95), weight_decay=0.0)
|
| 109 |
+
def loss_of(x, y):
|
| 110 |
+
h, _ = model.trunk(x); ph = model.head(h).float().reshape(-1, 512)
|
| 111 |
+
yf = y.reshape(-1); v = yf >= 0; ph = ph[v]; yf = yf[v]
|
| 112 |
+
ce = 0.0
|
| 113 |
+
for i in range(0, ph.shape[0], 8192):
|
| 114 |
+
lg = ph[i:i + 8192] @ model.cent_n.T + model.tied_bias
|
| 115 |
+
ce = ce + F.cross_entropy(lg, yf[i:i + 8192], reduction="sum")
|
| 116 |
+
return ce / max(1, int(v.sum()))
|
| 117 |
+
@torch.no_grad()
|
| 118 |
+
def val():
|
| 119 |
+
model.eval(); tot = 0.0
|
| 120 |
+
for _ in range(4):
|
| 121 |
+
x, y = data.pack(a.micro_batch, val=True)
|
| 122 |
+
with torch.autocast("cuda", dtype=torch.bfloat16, enabled=dev.type == "cuda"):
|
| 123 |
+
tot += float(loss_of(x.to(dev), y.to(dev)))
|
| 124 |
+
model.train()
|
| 125 |
+
for md in model.modules():
|
| 126 |
+
if isinstance(md, common.KVCodec1): md.eval()
|
| 127 |
+
return tot / 4
|
| 128 |
+
v0 = val(); print(f"step 0 val loss {v0:.4f}")
|
| 129 |
+
t0 = time.time()
|
| 130 |
+
for step in range(1, a.steps + 1):
|
| 131 |
+
lr = a.lr * min(1.0, step / a.warmup) * (0.5 * (1 + math.cos(math.pi * step / a.steps)))
|
| 132 |
+
for g in opt.param_groups: g["lr"] = lr
|
| 133 |
+
opt.zero_grad(set_to_none=True)
|
| 134 |
+
for _ in range(a.accum):
|
| 135 |
+
x, y = data.pack(a.micro_batch)
|
| 136 |
+
with torch.autocast("cuda", dtype=torch.bfloat16, enabled=dev.type == "cuda"):
|
| 137 |
+
(loss_of(x.to(dev), y.to(dev)) / a.accum).backward()
|
| 138 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
|
| 139 |
+
opt.step(); requant(model)
|
| 140 |
+
if step % a.log_every == 0:
|
| 141 |
+
el = time.time() - t0
|
| 142 |
+
print(f"step {step:>4} lr {lr:.2e} {el/step:.1f}s/step eta {(a.steps-step)*el/step/60:.0f}min", flush=True)
|
| 143 |
+
v1 = val()
|
| 144 |
+
torch.save({"model": model.state_dict()}, out / "finetuned.pt")
|
| 145 |
+
print(f"done val loss {v0:.4f} -> {v1:.4f} saved {out/'finetuned.pt'}")
|
| 146 |
+
|
| 147 |
+
if __name__ == "__main__":
|
| 148 |
+
main()
|
finetune/modeling/common.py
ADDED
|
@@ -0,0 +1,651 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math, time, numpy as np, torch, torch.nn as nn, torch.nn.functional as F
|
| 2 |
+
from contextlib import contextmanager
|
| 3 |
+
try:
|
| 4 |
+
from paged_kv import PagedKVArchive, PagedKVView, ExactChunkCountView
|
| 5 |
+
except ImportError:
|
| 6 |
+
from paged_kv import PagedKVArchive, PagedKVView, ExactChunkCountView
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
import os as _os
|
| 10 |
+
def _e(k,d): return int(_os.environ.get(k,d))
|
| 11 |
+
|
| 12 |
+
D=_e("SHADOW_D",1024); NL=_e("SHADOW_NL",20); NH=_e("SHADOW_NH",16)
|
| 13 |
+
NKV=_e("SHADOW_NKV",4); HD=_e("SHADOW_HD",64); FFNH=_e("SHADOW_FFNH",3072); FPD=512
|
| 14 |
+
FAST_ATTN=bool(int(__import__('os').environ.get('SHADOW_FAST_ATTN','0')))
|
| 15 |
+
TRAIN_EXACT_POT=bool(int(__import__('os').environ.get(
|
| 16 |
+
'SHADOW_TRAIN_EXACT_POT','1')))
|
| 17 |
+
TRAIN_EXACT_KV=bool(int(__import__('os').environ.get(
|
| 18 |
+
'SHADOW_TRAIN_EXACT_KV','1')))
|
| 19 |
+
KV_BITS=_e("SHADOW_KV_BITS",1)
|
| 20 |
+
KV_TWO_TIER=bool(int(_os.environ.get("SHADOW_KV_TWO_TIER","0")))
|
| 21 |
+
KV_COLD_MASK=None
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class _ExactSTE(torch.autograd.Function):
|
| 25 |
+
@staticmethod
|
| 26 |
+
def forward(ctx,x,q):
|
| 27 |
+
return q
|
| 28 |
+
@staticmethod
|
| 29 |
+
def backward(ctx,grad):
|
| 30 |
+
return grad,None
|
| 31 |
+
|
| 32 |
+
def ste(x,q): return _ExactSTE.apply(x,q)
|
| 33 |
+
def pot(x):
|
| 34 |
+
m=x.abs().amax(-1,keepdim=True).clamp_min(1e-6); s=torch.exp2(torch.ceil(torch.log2(m/127.)))
|
| 35 |
+
return ste(x,(x/s).round().clamp(-127,127)*s)
|
| 36 |
+
|
| 37 |
+
def walsh_hadamard(x):
|
| 38 |
+
n=x.shape[-1]
|
| 39 |
+
if n<1 or n&(n-1):
|
| 40 |
+
raise ValueError(f"Walsh-Hadamard width must be a power of two, got {n}")
|
| 41 |
+
shape=x.shape
|
| 42 |
+
y=x
|
| 43 |
+
h=1
|
| 44 |
+
while h<n:
|
| 45 |
+
y=y.reshape(*shape[:-1],n//(2*h),2,h)
|
| 46 |
+
a,b=y[...,0,:],y[...,1,:]
|
| 47 |
+
y=torch.cat((a+b,a-b),-1).reshape(shape)
|
| 48 |
+
h*=2
|
| 49 |
+
return y/math.sqrt(n)
|
| 50 |
+
|
| 51 |
+
def _kv2_levels(x):
|
| 52 |
+
m=x.abs().amax(-1,keepdim=True).clamp_min(1e-6)
|
| 53 |
+
scale=torch.exp2(torch.ceil(torch.log2(m/1.5)))
|
| 54 |
+
return ((x/scale).round().clamp(-2,1)+0.5)*scale
|
| 55 |
+
|
| 56 |
+
def kv2(x):
|
| 57 |
+
rotated=walsh_hadamard(x)
|
| 58 |
+
restored=walsh_hadamard(_kv2_levels(rotated))
|
| 59 |
+
return ste(x,restored)
|
| 60 |
+
|
| 61 |
+
def kv2_pack(x):
|
| 62 |
+
if x.shape[-1] % 4:
|
| 63 |
+
raise ValueError(f"2-bit packing requires a multiple-of-4 last dimension, got {x.shape[-1]}")
|
| 64 |
+
rotated=walsh_hadamard(x)
|
| 65 |
+
m=rotated.abs().amax(-1,keepdim=True).clamp_min(1e-6)
|
| 66 |
+
scale=torch.exp2(torch.ceil(torch.log2(m/1.5)))
|
| 67 |
+
code=(rotated/scale).round().clamp(-2,1).to(torch.int16).add_(2).to(torch.uint8)
|
| 68 |
+
z=code.reshape(*code.shape[:-1],code.shape[-1]//4,4)
|
| 69 |
+
packed=z[...,0] | (z[...,1]<<2) | (z[...,2]<<4) | (z[...,3]<<6)
|
| 70 |
+
return packed.contiguous(),scale.to(x.dtype).contiguous()
|
| 71 |
+
|
| 72 |
+
def kv2_unpack(packed,scale,dtype=None):
|
| 73 |
+
parts=torch.stack((packed&3,(packed>>2)&3,(packed>>4)&3,(packed>>6)&3),-1)
|
| 74 |
+
code=parts.reshape(*packed.shape[:-1],packed.shape[-1]*4)
|
| 75 |
+
dt=dtype or scale.dtype
|
| 76 |
+
rotated=(code.to(dt)-1.5)*scale.to(dt)
|
| 77 |
+
return walsh_hadamard(rotated)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class KVCodec1(nn.Module):
|
| 81 |
+
def __init__(s,heads,width,seed=0,momentum=0.01,decision_grid=256.0):
|
| 82 |
+
super().__init__()
|
| 83 |
+
if width<1 or width&(width-1):
|
| 84 |
+
raise ValueError(f"KVCodec1 width must be a power of two, got {width}")
|
| 85 |
+
g=torch.Generator().manual_seed(int(seed))
|
| 86 |
+
sign=(torch.randint(0,2,(heads,width),generator=g,dtype=torch.int8)*2-1)
|
| 87 |
+
s.heads,s.width,s.momentum=int(heads),int(width),float(momentum)
|
| 88 |
+
s.decision_grid=float(decision_grid)
|
| 89 |
+
s.register_buffer("sign",sign)
|
| 90 |
+
s.register_buffer("mu",torch.zeros(heads,width))
|
| 91 |
+
s.register_buffer("ctv",torch.zeros(heads,width))
|
| 92 |
+
s.register_buffer("low",torch.full((heads,width),-1.0))
|
| 93 |
+
s.register_buffer("high",torch.ones(heads,width))
|
| 94 |
+
s.register_buffer("initialized",torch.tensor(False))
|
| 95 |
+
s.register_buffer("updates",torch.tensor(0,dtype=torch.int64),persistent=False)
|
| 96 |
+
|
| 97 |
+
def _check(s,x):
|
| 98 |
+
if x.ndim!=4 or x.shape[1]!=s.heads or x.shape[-1]!=s.width:
|
| 99 |
+
raise ValueError(
|
| 100 |
+
f"KVCodec1 expects (B,{s.heads},T,{s.width}), got {tuple(x.shape)}")
|
| 101 |
+
|
| 102 |
+
@torch.no_grad()
|
| 103 |
+
def calibrate(s,x):
|
| 104 |
+
s._check(x); xf=x.detach().float()
|
| 105 |
+
mu=xf.mean((0,2))
|
| 106 |
+
sg=s.sign.float()[None,:,None,:]
|
| 107 |
+
z=walsh_hadamard((xf-mu[None,:,None,:])*sg)
|
| 108 |
+
flat=z.permute(1,0,2,3).reshape(s.heads,-1,s.width)
|
| 109 |
+
decision=(flat*s.decision_grid).round()/s.decision_grid
|
| 110 |
+
ctv=decision.median(1).values
|
| 111 |
+
hi=decision>ctv[:,None,:]; lo=~hi
|
| 112 |
+
low=(flat*lo).sum(1)/lo.sum(1).clamp_min(1)
|
| 113 |
+
high=(flat*hi).sum(1)/hi.sum(1).clamp_min(1)
|
| 114 |
+
rate=1.0 if not bool(s.initialized) else s.momentum
|
| 115 |
+
for dst,src in ((s.mu,mu),(s.ctv,ctv),(s.low,low),(s.high,high)):
|
| 116 |
+
dst.lerp_(src.to(dst.dtype),rate)
|
| 117 |
+
s.initialized.fill_(True); s.updates.add_(1)
|
| 118 |
+
return s
|
| 119 |
+
|
| 120 |
+
def _ensure(s,x,update):
|
| 121 |
+
if update or not bool(s.initialized):
|
| 122 |
+
s.calibrate(x)
|
| 123 |
+
|
| 124 |
+
def transform(s,x):
|
| 125 |
+
s._check(x)
|
| 126 |
+
sg=s.sign.to(x.dtype)[None,:,None,:]
|
| 127 |
+
return walsh_hadamard((x-s.mu.to(x.dtype)[None,:,None,:])*sg)
|
| 128 |
+
|
| 129 |
+
def bits(s,x,update=False):
|
| 130 |
+
s._ensure(x,update)
|
| 131 |
+
decision=(s.transform(x)*s.decision_grid).round()/s.decision_grid
|
| 132 |
+
threshold=(s.ctv.to(x.dtype)*s.decision_grid).round()/s.decision_grid
|
| 133 |
+
return decision>threshold[None,:,None,:]
|
| 134 |
+
|
| 135 |
+
def reconstruct_bits(s,bits,dtype=torch.float32):
|
| 136 |
+
if bits.ndim!=4 or bits.shape[1]!=s.heads or bits.shape[-1]!=s.width:
|
| 137 |
+
raise ValueError(f"bad 1-bit KV shape {tuple(bits.shape)}")
|
| 138 |
+
low=s.low.to(dtype)[None,:,None,:]
|
| 139 |
+
high=s.high.to(dtype)[None,:,None,:]
|
| 140 |
+
rotated=torch.where(bits,high,low)
|
| 141 |
+
sg=s.sign.to(dtype)[None,:,None,:]
|
| 142 |
+
return s.mu.to(dtype)[None,:,None,:]+walsh_hadamard(rotated)*sg
|
| 143 |
+
|
| 144 |
+
def forward(s,x):
|
| 145 |
+
bits=s.bits(x,update=s.training)
|
| 146 |
+
return ste(x,s.reconstruct_bits(bits,x.dtype))
|
| 147 |
+
|
| 148 |
+
@torch.no_grad()
|
| 149 |
+
def pack(s,x):
|
| 150 |
+
bits=s.bits(x,update=False)
|
| 151 |
+
b=bits.reshape(*bits.shape[:-1],s.width//8,8).to(torch.uint8)
|
| 152 |
+
weights=torch.tensor((1,2,4,8,16,32,64,128),
|
| 153 |
+
dtype=torch.uint8,device=x.device)
|
| 154 |
+
return (b*weights).sum(-1).to(torch.uint8).contiguous()
|
| 155 |
+
|
| 156 |
+
def unpack(s,packed,dtype=None):
|
| 157 |
+
if packed.ndim!=4 or packed.shape[1]!=s.heads or packed.shape[-1]*8!=s.width:
|
| 158 |
+
raise ValueError(f"bad packed 1-bit KV shape {tuple(packed.shape)}")
|
| 159 |
+
parts=torch.stack(tuple(((packed>>i)&1) for i in range(8)),-1)
|
| 160 |
+
bits=parts.reshape(*packed.shape[:-1],s.width).bool()
|
| 161 |
+
return s.reconstruct_bits(bits,dtype or s.mu.dtype)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
@contextmanager
|
| 165 |
+
def freeze_codec_updates(module):
|
| 166 |
+
codecs=[m for m in module.modules() if isinstance(m,KVCodec1)]
|
| 167 |
+
states=[m.training for m in codecs]
|
| 168 |
+
try:
|
| 169 |
+
for m in codecs:
|
| 170 |
+
m.training=False
|
| 171 |
+
yield
|
| 172 |
+
finally:
|
| 173 |
+
for m,state in zip(codecs,states):
|
| 174 |
+
m.training=state
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def mem8_pack(x):
|
| 178 |
+
m=x.abs().amax(-1,keepdim=True).clamp_min(1e-6)
|
| 179 |
+
scale=torch.exp2(torch.ceil(torch.log2(m/127.0)))
|
| 180 |
+
return (x/scale).round().clamp(-127,127).to(torch.int8),scale.to(x.dtype)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def mem8_unpack(packed,scale,dtype=None):
|
| 184 |
+
dt=dtype or scale.dtype
|
| 185 |
+
return packed.to(dt)*scale.to(dt)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def bitpack512(x):
|
| 189 |
+
if x.shape[-1] != FPD:
|
| 190 |
+
raise ValueError(f"Hamming keys require {FPD} features, got {x.shape[-1]}")
|
| 191 |
+
bits=(x>=0).to(torch.uint8).reshape(*x.shape[:-1],FPD//8,8)
|
| 192 |
+
weights=torch.tensor((1,2,4,8,16,32,64,128),dtype=torch.uint8,device=x.device)
|
| 193 |
+
return (bits*weights).sum(-1).to(torch.uint8).contiguous()
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
class HammingHebbianMemory:
|
| 197 |
+
def __init__(s,batch,dim,device,capacity=4096,match_threshold=0.90,
|
| 198 |
+
hebb_eta=0.25,retrieval_k=4):
|
| 199 |
+
s.batch,s.dim,s.device=int(batch),int(dim),torch.device(device)
|
| 200 |
+
s.capacity=int(capacity); s.match_threshold=float(match_threshold)
|
| 201 |
+
s.hebb_eta=float(hebb_eta); s.retrieval_k=int(retrieval_k)
|
| 202 |
+
s.keys=[[] for _ in range(s.batch)]
|
| 203 |
+
s.vals=[[] for _ in range(s.batch)]
|
| 204 |
+
s.scales=[[] for _ in range(s.batch)]
|
| 205 |
+
s.strength=[[] for _ in range(s.batch)]
|
| 206 |
+
s.age=[[] for _ in range(s.batch)]; s.clock=0
|
| 207 |
+
s._lut=torch.tensor([int(i).bit_count() for i in range(256)],
|
| 208 |
+
dtype=torch.int16,device=s.device)
|
| 209 |
+
|
| 210 |
+
def __len__(s):
|
| 211 |
+
return sum(len(x) for x in s.keys)
|
| 212 |
+
|
| 213 |
+
def counts(s):
|
| 214 |
+
return [len(x) for x in s.keys]
|
| 215 |
+
|
| 216 |
+
def _distances(s,key,b):
|
| 217 |
+
if not s.keys[b]:
|
| 218 |
+
return torch.empty(0,dtype=torch.int32,device=s.device)
|
| 219 |
+
bank=torch.stack(s.keys[b])
|
| 220 |
+
xor=torch.bitwise_xor(bank,key).to(torch.long)
|
| 221 |
+
return s._lut[xor].sum(-1,dtype=torch.int32)
|
| 222 |
+
|
| 223 |
+
@torch.no_grad()
|
| 224 |
+
def write(s,values,key_features):
|
| 225 |
+
if values.shape!=(s.batch,s.dim) or key_features.shape!=(s.batch,FPD):
|
| 226 |
+
raise ValueError(f"write shapes must be {(s.batch,s.dim)} and "
|
| 227 |
+
f"{(s.batch,FPD)}, got {values.shape}, {key_features.shape}")
|
| 228 |
+
packed_keys=bitpack512(key_features)
|
| 229 |
+
events=[]
|
| 230 |
+
for b in range(s.batch):
|
| 231 |
+
s.clock+=1; key=packed_keys[b].detach(); value=values[b].detach()
|
| 232 |
+
distances=s._distances(key,b)
|
| 233 |
+
best=int(distances.argmin()) if len(distances) else -1
|
| 234 |
+
similarity=(1.0-float(distances[best])/FPD) if best>=0 else -1.0
|
| 235 |
+
if best>=0 and similarity>=s.match_threshold:
|
| 236 |
+
old=mem8_unpack(s.vals[b][best],s.scales[b][best],value.dtype)
|
| 237 |
+
rate=s.hebb_eta*similarity
|
| 238 |
+
updated=old+rate*(value-old)
|
| 239 |
+
vp,vs=mem8_pack(updated)
|
| 240 |
+
s.vals[b][best]=vp; s.scales[b][best]=vs
|
| 241 |
+
s.strength[b][best]+=similarity; s.age[b][best]=s.clock
|
| 242 |
+
events.append({"kind":"reinforce","slot":best,
|
| 243 |
+
"similarity":similarity})
|
| 244 |
+
continue
|
| 245 |
+
vp,vs=mem8_pack(value)
|
| 246 |
+
if len(s.keys[b])>=s.capacity:
|
| 247 |
+
|
| 248 |
+
slot=min(range(len(s.keys[b])),
|
| 249 |
+
key=lambda i:(s.strength[b][i],s.age[b][i]))
|
| 250 |
+
s.keys[b][slot]=key; s.vals[b][slot]=vp; s.scales[b][slot]=vs
|
| 251 |
+
s.strength[b][slot]=1.0; s.age[b][slot]=s.clock
|
| 252 |
+
kind="replace"
|
| 253 |
+
else:
|
| 254 |
+
slot=len(s.keys[b]); s.keys[b].append(key)
|
| 255 |
+
s.vals[b].append(vp); s.scales[b].append(vs)
|
| 256 |
+
s.strength[b].append(1.0); s.age[b].append(s.clock)
|
| 257 |
+
kind="allocate"
|
| 258 |
+
events.append({"kind":kind,"slot":slot,"similarity":similarity})
|
| 259 |
+
return events
|
| 260 |
+
|
| 261 |
+
@torch.no_grad()
|
| 262 |
+
def retrieve(s,key_features,k=None):
|
| 263 |
+
if key_features.shape!=(s.batch,FPD):
|
| 264 |
+
raise ValueError(f"query shape must be {(s.batch,FPD)}, got {key_features.shape}")
|
| 265 |
+
packed=bitpack512(key_features); outputs=[]; metadata=[]
|
| 266 |
+
for b in range(s.batch):
|
| 267 |
+
distances=s._distances(packed[b],b)
|
| 268 |
+
if not len(distances):
|
| 269 |
+
outputs.append(torch.zeros(s.dim,device=s.device,
|
| 270 |
+
dtype=key_features.dtype))
|
| 271 |
+
metadata.append({"found":False,"slot":-1,"similarity":0.0})
|
| 272 |
+
continue
|
| 273 |
+
take=min(int(k or s.retrieval_k),len(distances))
|
| 274 |
+
d,idx=torch.topk(distances,take,largest=False)
|
| 275 |
+
values=torch.stack([
|
| 276 |
+
mem8_unpack(s.vals[b][int(i)],s.scales[b][int(i)],
|
| 277 |
+
key_features.dtype) for i in idx])
|
| 278 |
+
similarity=1.0-d.to(torch.float32)/FPD
|
| 279 |
+
weights=torch.softmax(similarity*32.0,-1).to(values.dtype)
|
| 280 |
+
outputs.append((values*weights[:,None]).sum(0))
|
| 281 |
+
metadata.append({"found":True,"slot":int(idx[0]),
|
| 282 |
+
"similarity":float(similarity[0]),
|
| 283 |
+
"distance":int(d[0])})
|
| 284 |
+
return torch.stack(outputs),metadata
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def make_memory_state(batch,dim,device,chunk_size=128,capacity=256,
|
| 288 |
+
match_threshold=0.90,hebb_eta=0.25,retrieval_k=4):
|
| 289 |
+
if chunk_size<1:
|
| 290 |
+
raise ValueError("memory chunk_size must be positive")
|
| 291 |
+
return {
|
| 292 |
+
"store":HammingHebbianMemory(
|
| 293 |
+
batch,chunk_size*dim,device,capacity=capacity,
|
| 294 |
+
match_threshold=match_threshold,hebb_eta=hebb_eta,
|
| 295 |
+
retrieval_k=retrieval_k),
|
| 296 |
+
"evicted":torch.empty(batch,0,dim,device=device),
|
| 297 |
+
"chunk_size":int(chunk_size),
|
| 298 |
+
"hidden_dim":int(dim),
|
| 299 |
+
"recall_threshold":0.60,
|
| 300 |
+
"writes":0,
|
| 301 |
+
"last_retrieval":None,
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
@torch.no_grad()
|
| 306 |
+
def memory_absorb_evicted(memory,evicted,head,norm):
|
| 307 |
+
if memory is None or not evicted.shape[1]:
|
| 308 |
+
return []
|
| 309 |
+
memory["evicted"]=torch.cat((memory["evicted"],evicted.detach()),1)
|
| 310 |
+
events=[]
|
| 311 |
+
n=int(memory["chunk_size"])
|
| 312 |
+
while memory["evicted"].shape[1]>=n:
|
| 313 |
+
chunk=memory["evicted"][:,:n]
|
| 314 |
+
memory["evicted"]=memory["evicted"][:,n:]
|
| 315 |
+
pooled=chunk.mean(1)
|
| 316 |
+
key_features=head(norm(pooled)).float()
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
value=chunk.reshape(chunk.shape[0],-1)
|
| 320 |
+
events.extend(memory["store"].write(value,key_features))
|
| 321 |
+
memory["writes"]+=1
|
| 322 |
+
return events
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
@torch.no_grad()
|
| 326 |
+
def memory_append_recall(memory,query_hidden,context,head,norm):
|
| 327 |
+
if memory is None or not len(memory["store"]):
|
| 328 |
+
return context
|
| 329 |
+
query=head(norm(query_hidden)).float()
|
| 330 |
+
recall,meta=memory["store"].retrieve(query)
|
| 331 |
+
memory["last_retrieval"]=meta
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
threshold=float(memory["recall_threshold"])
|
| 335 |
+
if not any(m["found"] and m["similarity"]>=threshold for m in meta):
|
| 336 |
+
return context
|
| 337 |
+
gate=torch.tensor([m["similarity"] if m["found"] and
|
| 338 |
+
m["similarity"]>=threshold else 0.0 for m in meta],
|
| 339 |
+
device=recall.device,dtype=recall.dtype)[:,None,None]
|
| 340 |
+
recalled_chunk=recall.reshape(
|
| 341 |
+
recall.shape[0],memory["chunk_size"],memory["hidden_dim"])
|
| 342 |
+
return torch.cat((context,recalled_chunk*gate),1)
|
| 343 |
+
def rope(x,cos,sin):
|
| 344 |
+
a,b=x[...,0::2],x[...,1::2]; return torch.stack([a*cos-b*sin,a*sin+b*cos],-1).flatten(-2)
|
| 345 |
+
|
| 346 |
+
class RMS(nn.Module):
|
| 347 |
+
def __init__(s,d,eps=1e-6): super().__init__(); s.w=nn.Parameter(torch.ones(d)); s.eps=eps
|
| 348 |
+
def forward(s,x): n=torch.rsqrt(x.float().pow(2).mean(-1,keepdim=True)+s.eps).to(x.dtype); return x*n*s.w.to(x.dtype)
|
| 349 |
+
|
| 350 |
+
class RVQ(nn.Module):
|
| 351 |
+
def __init__(s,i,o,g,st=1,codes=16):
|
| 352 |
+
super().__init__(); s.i,s.o,s.g,s.st,s.c=i,o,g,st,codes
|
| 353 |
+
s.weight=nn.Parameter(torch.randn(o,i)/math.sqrt(i))
|
| 354 |
+
s.register_buffer("cb",torch.zeros(st,codes,g)); s.register_buffer("cb_init",torch.tensor(False)); s._q=None
|
| 355 |
+
@torch.no_grad()
|
| 356 |
+
def _km(s,X,k,it=12,samp=8192):
|
| 357 |
+
S=X[torch.randperm(X.shape[0],device=X.device)[:min(X.shape[0],samp)]]
|
| 358 |
+
C=S[torch.randperm(S.shape[0],device=X.device)[:min(k,S.shape[0])]].clone()
|
| 359 |
+
if C.shape[0]<k: C=torch.cat([C,C[:k-C.shape[0]]])
|
| 360 |
+
for _ in range(it):
|
| 361 |
+
a=torch.cdist(S,C).argmin(1)
|
| 362 |
+
for j in range(k):
|
| 363 |
+
m=a==j
|
| 364 |
+
if m.any(): C[j]=S[m].mean(0)
|
| 365 |
+
return C
|
| 366 |
+
@torch.no_grad()
|
| 367 |
+
def _fit(s):
|
| 368 |
+
sc=s.weight.abs().mean(1,keepdim=True).clamp_min(1e-8); r=(s.weight/sc).reshape(-1,s.g).clone()
|
| 369 |
+
for t in range(s.st):
|
| 370 |
+
rms=r.pow(2).mean().sqrt().clamp_min(1e-8); s.cb[t]=s._km(r/rms,s.c)
|
| 371 |
+
idx=torch.cdist(r/rms,s.cb[t]).argmin(1); r=r-s.cb[t][idx]*rms
|
| 372 |
+
s.cb_init.fill_(True)
|
| 373 |
+
def enc(s):
|
| 374 |
+
if not bool(s.cb_init): s._fit()
|
| 375 |
+
with torch.no_grad():
|
| 376 |
+
sc=s.weight.detach().abs().mean(1,keepdim=True).clamp_min(1e-8); r=(s.weight.detach()/sc).reshape(-1,s.g); acc=torch.zeros_like(r)
|
| 377 |
+
for t in range(s.st):
|
| 378 |
+
rms=r.pow(2).mean().sqrt().clamp_min(1e-8); idx=torch.cdist(r/rms,s.cb[t]).argmin(1)
|
| 379 |
+
q=s.cb[t][idx]*rms; acc=acc+q; r=r-q
|
| 380 |
+
quantized=acc.reshape(s.o,s.i)*sc
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
if (s._q is not None and s._q.shape==quantized.shape and
|
| 385 |
+
s._q.device==quantized.device and
|
| 386 |
+
s._q.dtype==quantized.dtype):
|
| 387 |
+
s._q.copy_(quantized)
|
| 388 |
+
else:
|
| 389 |
+
s._q=quantized
|
| 390 |
+
def qw(s):
|
| 391 |
+
|
| 392 |
+
if s._q is None or s._q.device!=s.weight.device: s.enc()
|
| 393 |
+
return ste(s.weight,s._q)
|
| 394 |
+
def forward(s,x): return F.linear(x,s.qw().to(x.dtype))
|
| 395 |
+
def bits(s): return s.st*math.log2(s.c)/s.g
|
| 396 |
+
def requant(m):
|
| 397 |
+
for md in m.modules():
|
| 398 |
+
if isinstance(md,RVQ): md.enc()
|
| 399 |
+
|
| 400 |
+
def cs(Ts,dev):
|
| 401 |
+
inv=1.0/(10000**(torch.arange(0,HD,2,device=dev).float()/HD)); f=torch.arange(Ts,device=dev).float()[:,None]*inv[None]
|
| 402 |
+
return f.cos()[None,None].to(torch.bfloat16), f.sin()[None,None].to(torch.bfloat16)
|
| 403 |
+
|
| 404 |
+
def cs_at(start,Ts,dev):
|
| 405 |
+
inv=1.0/(10000**(torch.arange(0,HD,2,device=dev).float()/HD))
|
| 406 |
+
f=torch.arange(start,start+Ts,device=dev).float()[:,None]*inv[None]
|
| 407 |
+
return f.cos()[None,None].to(torch.bfloat16),f.sin()[None,None].to(torch.bfloat16)
|
| 408 |
+
|
| 409 |
+
def shiftmax(dot,alpha,Ts,device):
|
| 410 |
+
aq=ste(alpha,(alpha*4096).round()/4096)
|
| 411 |
+
e=aq*dot
|
| 412 |
+
e=ste(e,e.floor())
|
| 413 |
+
cm=torch.ones(Ts,Ts,dtype=torch.bool,device=device).tril()
|
| 414 |
+
e=e.masked_fill(~cm,-1e9)
|
| 415 |
+
w=torch.exp2((e-e.amax(-1,keepdim=True)).clamp_min(-15))
|
| 416 |
+
return w/w.sum(-1,keepdim=True)
|
| 417 |
+
|
| 418 |
+
class Block(nn.Module):
|
| 419 |
+
def __init__(s,layer_idx=0):
|
| 420 |
+
super().__init__(); s.n1=RMS(D); s.n2=RMS(D)
|
| 421 |
+
s.layer_idx=int(layer_idx)
|
| 422 |
+
s.q=RVQ(D,NH*HD,8,2); s.k=RVQ(D,NKV*HD,8,2); s.v=RVQ(D,NKV*HD,8,2); s.o=RVQ(NH*HD,D,8,2)
|
| 423 |
+
s.qn=RMS(HD); s.kn=RMS(HD); s.g=nn.Parameter(torch.zeros(NH*HD)); s.alpha=nn.Parameter(torch.full((1,NH,1,1),0.25))
|
| 424 |
+
s.up=RVQ(D,FFNH,32,1); s.gt=RVQ(D,FFNH,32,1); s.dn=RVQ(FFNH,D,32,1)
|
| 425 |
+
s.kv_bits=KV_BITS
|
| 426 |
+
s.kcodec=KVCodec1(NKV,HD,seed=1000+int(layer_idx))
|
| 427 |
+
s.vcodec=KVCodec1(NKV,HD,seed=2000+int(layer_idx))
|
| 428 |
+
def forward(s,x,cos,sin):
|
| 429 |
+
z=s.n1(x); Bs,Ts,_=z.shape
|
| 430 |
+
q=s.qn(s.q(z).view(Bs,Ts,NH,HD)).transpose(1,2); k=s.kn(s.k(z).view(Bs,Ts,NKV,HD)).transpose(1,2); v=s.v(z).view(Bs,Ts,NKV,HD).transpose(1,2)
|
| 431 |
+
q=rope(q,cos,sin); k=rope(k,cos,sin)
|
| 432 |
+
if s.kv_bits==1 and (TRAIN_EXACT_KV or not s.training):
|
| 433 |
+
if KV_TWO_TIER:
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
|
| 437 |
+
m=KV_COLD_MASK
|
| 438 |
+
if m is not None:
|
| 439 |
+
kc=s.kcodec(k); vc=s.vcodec(v); mm=m[:,None,:,None]
|
| 440 |
+
k=torch.where(mm,kc,k); v=torch.where(mm,vc,v)
|
| 441 |
+
else:
|
| 442 |
+
k=s.kcodec(k); v=s.vcodec(v)
|
| 443 |
+
elif s.kv_bits!=1:
|
| 444 |
+
k=kv2(k); v=kv2(v)
|
| 445 |
+
k=k.repeat_interleave(NH//NKV,1); v=v.repeat_interleave(NH//NKV,1)
|
| 446 |
+
if TRAIN_EXACT_POT or not s.training:
|
| 447 |
+
q=pot(q).to(torch.bfloat16)
|
| 448 |
+
k=pot(k).to(torch.bfloat16)
|
| 449 |
+
v=pot(v).to(torch.bfloat16)
|
| 450 |
+
else:
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
q=q.to(torch.bfloat16)
|
| 454 |
+
k=k.to(torch.bfloat16)
|
| 455 |
+
v=v.to(torch.bfloat16)
|
| 456 |
+
if FAST_ATTN:
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
|
| 461 |
+
aq=ste(s.alpha,(s.alpha*4096).round()/4096)*0.6931471805599453
|
| 462 |
+
y=F.scaled_dot_product_attention((q*aq).to(v.dtype),k,v,is_causal=True,scale=1.0)
|
| 463 |
+
y=y.transpose(1,2).reshape(Bs,Ts,NH*HD)
|
| 464 |
+
else:
|
| 465 |
+
w=shiftmax(q@k.transpose(-1,-2),s.alpha,Ts,x.device)
|
| 466 |
+
y=(w.to(v.dtype)@v).transpose(1,2).reshape(Bs,Ts,NH*HD)
|
| 467 |
+
|
| 468 |
+
x=x+s.o(y*torch.sigmoid(s.g)); h=s.n2(x); return x+s.dn(F.silu(s.gt(h))*s.up(h))
|
| 469 |
+
|
| 470 |
+
def _qkv(s,z,cos,sin):
|
| 471 |
+
Bs,Ts,_=z.shape
|
| 472 |
+
q=s.qn(s.q(z).view(Bs,Ts,NH,HD)).transpose(1,2)
|
| 473 |
+
k=s.kn(s.k(z).view(Bs,Ts,NKV,HD)).transpose(1,2)
|
| 474 |
+
v=s.v(z).view(Bs,Ts,NKV,HD).transpose(1,2)
|
| 475 |
+
return rope(q,cos,sin),rope(k,cos,sin),v
|
| 476 |
+
|
| 477 |
+
def _finish_attention(s,x,q,k,v,causal,exact=True):
|
| 478 |
+
kr=k.repeat_interleave(NH//NKV,1); vr=v.repeat_interleave(NH//NKV,1)
|
| 479 |
+
q=pot(q).to(torch.bfloat16); kr=pot(kr).to(torch.bfloat16); vr=pot(vr).to(torch.bfloat16)
|
| 480 |
+
dot=q@kr.transpose(-1,-2)
|
| 481 |
+
aq=ste(s.alpha,(s.alpha*4096).round()/4096)
|
| 482 |
+
raw=aq*dot
|
| 483 |
+
e=ste(raw,raw.floor()) if exact else raw*0.6931471805599453
|
| 484 |
+
if causal:
|
| 485 |
+
tq,tk=e.shape[-2:]
|
| 486 |
+
|
| 487 |
+
cm=torch.arange(tk,device=x.device)[None,:] <= (
|
| 488 |
+
torch.arange(tq,device=x.device)[:,None]+tk-tq)
|
| 489 |
+
e=e.masked_fill(~cm[None,None],-1e9)
|
| 490 |
+
if exact:
|
| 491 |
+
w=torch.exp2((e-e.amax(-1,keepdim=True)).clamp_min(-15))
|
| 492 |
+
w=w/w.sum(-1,keepdim=True)
|
| 493 |
+
else:
|
| 494 |
+
w=torch.softmax(e,-1)
|
| 495 |
+
y=(w.to(vr.dtype)@vr).transpose(1,2).reshape(x.shape[0],x.shape[1],NH*HD)
|
| 496 |
+
x=x+s.o(y*torch.sigmoid(s.g)); h=s.n2(x)
|
| 497 |
+
return x+s.dn(F.silu(s.gt(h))*s.up(h))
|
| 498 |
+
|
| 499 |
+
@staticmethod
|
| 500 |
+
def _popcount_bytes(x):
|
| 501 |
+
lut=torch.tensor([int(i).bit_count() for i in range(256)],
|
| 502 |
+
dtype=torch.int16,device=x.device)
|
| 503 |
+
return lut[x.long()].sum(-1,dtype=torch.int32)
|
| 504 |
+
|
| 505 |
+
def _archive_overflow(s,cache,overflow):
|
| 506 |
+
if overflow<=0 or not cache.get("cold_enabled") or s.kv_bits!=1:
|
| 507 |
+
return
|
| 508 |
+
ck=cache["k"][:,:,:overflow].detach()
|
| 509 |
+
cv=cache["v"][:,:,:overflow].detach()
|
| 510 |
+
start=int(cache["hot_start_position"])
|
| 511 |
+
positions=torch.arange(start,start+overflow,dtype=torch.int64,device=ck.device)
|
| 512 |
+
cache["cold_archive"].append(ck,cv,positions)
|
| 513 |
+
cache["hot_start_position"]=start+overflow
|
| 514 |
+
|
| 515 |
+
def _retrieve_cold(s,q,cache,dtype):
|
| 516 |
+
archive=cache.get("cold_archive")
|
| 517 |
+
if (not cache.get("cold_enabled") or archive is None or
|
| 518 |
+
len(archive)==0 or s.kv_bits!=1):
|
| 519 |
+
return None,None
|
| 520 |
+
if q.shape[2]!=1:
|
| 521 |
+
raise ValueError("cold KV retrieval requires single-token decode")
|
| 522 |
+
B,_,_,width=q.shape
|
| 523 |
+
groups=NH//NKV
|
| 524 |
+
q_group=q.reshape(B,NKV,groups,1,width).mean(2)
|
| 525 |
+
q_code=s.kcodec.pack(q_group)[:,:,0]
|
| 526 |
+
started=time.perf_counter()
|
| 527 |
+
n=len(archive)
|
| 528 |
+
final_k=min(int(cache["cold_topk"]),n)
|
| 529 |
+
wide_target=min(max(final_k*4,final_k),n)
|
| 530 |
+
wide_indices,wide_distances=archive.exact_hamming_topk(q_code,wide_target)
|
| 531 |
+
kp=archive.gather(wide_indices,"k")
|
| 532 |
+
vp=archive.gather(wide_indices,"v")
|
| 533 |
+
byte_width=archive.packed_width
|
| 534 |
+
kval=s.kcodec.unpack(kp,dtype)
|
| 535 |
+
q_score=pot(q.reshape(B,NKV,groups,width)).float()
|
| 536 |
+
k_score=pot(kval).float()
|
| 537 |
+
score=torch.einsum("bhgd,bhwd->bhgw",q_score,k_score).amax(2)
|
| 538 |
+
selected=torch.topk(score,final_k,-1).indices
|
| 539 |
+
sg=selected[...,None].expand(-1,-1,-1,byte_width)
|
| 540 |
+
kp=torch.gather(kp,2,sg); vp=torch.gather(vp,2,sg)
|
| 541 |
+
cache["cold_last_indices"]=torch.gather(wide_indices,2,selected)
|
| 542 |
+
if cache.get("retrieval_trace_enabled"):
|
| 543 |
+
selected_dist=torch.gather(wide_distances,2,selected)
|
| 544 |
+
cache["cold_last_trace"]={
|
| 545 |
+
"original_positions":archive.gather_positions(
|
| 546 |
+
cache["cold_last_indices"]).detach().cpu(),
|
| 547 |
+
"hamming_distances":selected_dist.detach().cpu(),
|
| 548 |
+
"shortlist_rank":selected.detach().cpu(),
|
| 549 |
+
"rerank_scores":torch.gather(score,2,selected).detach().cpu(),
|
| 550 |
+
"layer":s.layer_idx,
|
| 551 |
+
"heads":tuple(range(NKV)),
|
| 552 |
+
"cold_token_count":n,
|
| 553 |
+
"hot_token_count":int(cache["k"].shape[2]),
|
| 554 |
+
"latency_ms":(time.perf_counter()-started)*1000.0,
|
| 555 |
+
}
|
| 556 |
+
return s.kcodec.unpack(kp,dtype),s.vcodec.unpack(vp,dtype)
|
| 557 |
+
|
| 558 |
+
@torch.no_grad()
|
| 559 |
+
def prefill_cached(s,x,cos,sin,max_ctx=2048,exact=True,
|
| 560 |
+
archive_cold=False,cold_chunk=128,cold_topk=32,
|
| 561 |
+
retrieval_trace=False):
|
| 562 |
+
z=s.n1(x); q,k0,v0=s._qkv(z,cos,sin)
|
| 563 |
+
if s.kv_bits==1:
|
| 564 |
+
kp=s.kcodec.pack(k0); vp=s.vcodec.pack(v0)
|
| 565 |
+
k=s.kcodec.unpack(kp,z.dtype); v=s.vcodec.unpack(vp,z.dtype)
|
| 566 |
+
else:
|
| 567 |
+
kp,ks=kv2_pack(k0); vp,vs=kv2_pack(v0)
|
| 568 |
+
k=kv2_unpack(kp,ks,z.dtype); v=kv2_unpack(vp,vs,z.dtype)
|
| 569 |
+
out=s._finish_attention(x,q,k,v,causal=True,exact=exact)
|
| 570 |
+
cache={"k":kp[:,:,-max_ctx:],"v":vp[:,:,-max_ctx:],
|
| 571 |
+
"format":("random_walsh_ctv_1bit_v1" if s.kv_bits==1
|
| 572 |
+
else "walsh_hadamard_2bit_v1")}
|
| 573 |
+
if archive_cold and s.kv_bits==1:
|
| 574 |
+
archive=PagedKVArchive(
|
| 575 |
+
kp.shape[0],kp.shape[1],kp.shape[-1],
|
| 576 |
+
page_size=int(cold_chunk),device=kp.device)
|
| 577 |
+
cache.update({
|
| 578 |
+
"cold_enabled":True,"cold_archive":archive,
|
| 579 |
+
"cold_k":PagedKVView(archive,"k"),
|
| 580 |
+
"cold_v":PagedKVView(archive,"v"),
|
| 581 |
+
"chunk_keys":ExactChunkCountView(archive,int(cold_chunk)),
|
| 582 |
+
"cold_chunk":int(cold_chunk),"cold_topk":int(cold_topk),
|
| 583 |
+
"cold_last_indices":None,"cold_last_trace":None,
|
| 584 |
+
"retrieval_trace_enabled":bool(retrieval_trace),
|
| 585 |
+
"hot_start_position":max(0,int(x.shape[1])-int(max_ctx)),
|
| 586 |
+
})
|
| 587 |
+
else:
|
| 588 |
+
cache["cold_enabled"]=False
|
| 589 |
+
if s.kv_bits==2:
|
| 590 |
+
cache.update({"ks":ks[:,:,-max_ctx:],"vs":vs[:,:,-max_ctx:]})
|
| 591 |
+
return out,cache
|
| 592 |
+
|
| 593 |
+
@torch.no_grad()
|
| 594 |
+
def decode_cached(s,x,position,cache,max_ctx=2048,exact=True,
|
| 595 |
+
retrieve_cold=True):
|
| 596 |
+
expected=("random_walsh_ctv_1bit_v1" if s.kv_bits==1
|
| 597 |
+
else "walsh_hadamard_2bit_v1")
|
| 598 |
+
if cache.get("format")!=expected:
|
| 599 |
+
raise ValueError(f"legacy or unknown KV cache; expected {expected}")
|
| 600 |
+
cos,sin=cs_at(position,x.shape[1],x.device)
|
| 601 |
+
z=s.n1(x); q,k0,v0=s._qkv(z,cos,sin)
|
| 602 |
+
if s.kv_bits==1:
|
| 603 |
+
kp=s.kcodec.pack(k0); vp=s.vcodec.pack(v0)
|
| 604 |
+
else:
|
| 605 |
+
kp,ks=kv2_pack(k0); vp,vs=kv2_pack(v0)
|
| 606 |
+
joined_k=torch.cat((cache["k"],kp),2)
|
| 607 |
+
joined_v=torch.cat((cache["v"],vp),2)
|
| 608 |
+
overflow=max(0,joined_k.shape[2]-max_ctx)
|
| 609 |
+
cache["k"],cache["v"]=joined_k,joined_v
|
| 610 |
+
s._archive_overflow(cache,overflow)
|
| 611 |
+
cache["k"]=cache["k"][:,:,overflow:]
|
| 612 |
+
cache["v"]=cache["v"][:,:,overflow:]
|
| 613 |
+
if s.kv_bits==1:
|
| 614 |
+
k=s.kcodec.unpack(cache["k"],z.dtype)
|
| 615 |
+
v=s.vcodec.unpack(cache["v"],z.dtype)
|
| 616 |
+
cold_k,cold_v=(s._retrieve_cold(q,cache,z.dtype)
|
| 617 |
+
if retrieve_cold else (None,None))
|
| 618 |
+
if cold_k is not None:
|
| 619 |
+
k=torch.cat((cold_k,k),2); v=torch.cat((cold_v,v),2)
|
| 620 |
+
else:
|
| 621 |
+
cache["ks"]=torch.cat((cache["ks"],ks),2)[:,:,-max_ctx:]
|
| 622 |
+
cache["vs"]=torch.cat((cache["vs"],vs),2)[:,:,-max_ctx:]
|
| 623 |
+
k=kv2_unpack(cache["k"],cache["ks"],z.dtype)
|
| 624 |
+
v=kv2_unpack(cache["v"],cache["vs"],z.dtype)
|
| 625 |
+
return s._finish_attention(x,q,k,v,causal=x.shape[1]>1,exact=exact),cache
|
| 626 |
+
|
| 627 |
+
class StructStep(nn.Module):
|
| 628 |
+
def __init__(s):
|
| 629 |
+
super().__init__(); s.Wq=RVQ(D,D,8,2); s.cin=RVQ(2*D,FFNH,8,2); s.cout=RVQ(FFNH,D,8,2)
|
| 630 |
+
s.nf=RMS(D); s.verify=nn.Linear(D,1)
|
| 631 |
+
def forward(s,h):
|
| 632 |
+
Bs,Ts,_=h.shape; q=s.Wq(h)
|
| 633 |
+
att=(q@h.transpose(-1,-2))/math.sqrt(D)
|
| 634 |
+
cm=torch.ones(Ts,Ts,dtype=torch.bool,device=h.device).tril()
|
| 635 |
+
att=torch.softmax(att.masked_fill(~cm,-1e9),-1)
|
| 636 |
+
r=att@h
|
| 637 |
+
h=s.nf(h+s.cout(F.silu(s.cin(torch.cat([h,r],-1)))))
|
| 638 |
+
return h, torch.sigmoid(s.verify(h)).squeeze(-1)
|
| 639 |
+
|
| 640 |
+
@torch.no_grad()
|
| 641 |
+
def decode_cached(s,h_new,h_context):
|
| 642 |
+
q=s.Wq(h_new)
|
| 643 |
+
att=(q@h_context.transpose(-1,-2))/math.sqrt(D)
|
| 644 |
+
if h_new.shape[1]>1:
|
| 645 |
+
tq,tk=att.shape[-2:]
|
| 646 |
+
cm=torch.arange(tk,device=h_new.device)[None,:] <= (
|
| 647 |
+
torch.arange(tq,device=h_new.device)[:,None]+tk-tq)
|
| 648 |
+
att=att.masked_fill(~cm[None],-1e9)
|
| 649 |
+
w=torch.softmax(att,-1); r=w@h_context
|
| 650 |
+
h=s.nf(h_new+s.cout(F.silu(s.cin(torch.cat([h_new,r],-1)))))
|
| 651 |
+
return h,torch.sigmoid(s.verify(h)).squeeze(-1)
|
finetune/modeling/export_rvq.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys, numpy as np, torch
|
| 2 |
+
|
| 3 |
+
sys.path.insert(0, r"C:\Users\USER\Desktop\shadow_final\model_250m")
|
| 4 |
+
import common
|
| 5 |
+
from common import RVQ
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def rvq_capture(m):
|
| 9 |
+
W = m.weight.detach().float()
|
| 10 |
+
sc = W.abs().mean(1, keepdim=True).clamp_min(1e-8)
|
| 11 |
+
r = (W / sc).reshape(-1, m.g).clone()
|
| 12 |
+
idxs, rmss = [], []
|
| 13 |
+
for t in range(m.st):
|
| 14 |
+
rms = r.pow(2).mean().sqrt().clamp_min(1e-8)
|
| 15 |
+
idx = torch.cdist(r / rms, m.cb[t]).argmin(1)
|
| 16 |
+
r = r - m.cb[t][idx] * rms
|
| 17 |
+
idxs.append(idx.cpu().numpy().astype(np.uint8)); rmss.append(float(rms))
|
| 18 |
+
return sc.squeeze(1).cpu().numpy(), idxs, rmss
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def rvq_pack(m):
|
| 22 |
+
o, i, g, st = m.o, m.i, m.g, m.st
|
| 23 |
+
G = i // g
|
| 24 |
+
Npad = (o + 63) & ~63
|
| 25 |
+
nch = Npad // 64
|
| 26 |
+
sc, idxs, rmss = rvq_capture(m)
|
| 27 |
+
|
| 28 |
+
cbT = np.zeros((st, g, 16), dtype=np.float32)
|
| 29 |
+
for t in range(st):
|
| 30 |
+
cb = m.cb[t].detach().cpu().numpy()
|
| 31 |
+
cbT[t] = cb.T * rmss[t]
|
| 32 |
+
|
| 33 |
+
idx = np.zeros((st, nch, G, 32), dtype=np.uint8)
|
| 34 |
+
for t in range(st):
|
| 35 |
+
flat = idxs[t].reshape(o, G)
|
| 36 |
+
pad = np.zeros((Npad, G), dtype=np.uint8)
|
| 37 |
+
pad[:o] = flat
|
| 38 |
+
for c in range(nch):
|
| 39 |
+
lo = pad[c * 64: c * 64 + 32]
|
| 40 |
+
hi = pad[c * 64 + 32: c * 64 + 64]
|
| 41 |
+
idx[t, c] = (lo.T | (hi.T << 4))
|
| 42 |
+
|
| 43 |
+
scale = np.zeros(Npad, dtype=np.float32)
|
| 44 |
+
scale[:o] = sc
|
| 45 |
+
return cbT, idx, scale
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def rvq_unpack(cbT, idx, scale, o, i, g, st):
|
| 49 |
+
G = i // g
|
| 50 |
+
Npad = scale.shape[0]; nch = Npad // 64
|
| 51 |
+
W = np.zeros((Npad, i), dtype=np.float32)
|
| 52 |
+
for t in range(st):
|
| 53 |
+
for c in range(nch):
|
| 54 |
+
blk = idx[t, c]
|
| 55 |
+
lo = (blk & 0x0F).T
|
| 56 |
+
hi = (blk >> 4).T
|
| 57 |
+
for sub, rows in ((lo, range(c * 64, c * 64 + 32)),
|
| 58 |
+
(hi, range(c * 64 + 32, c * 64 + 64))):
|
| 59 |
+
for bi, n in enumerate(rows):
|
| 60 |
+
codes = sub[bi]
|
| 61 |
+
W[n] += cbT[t][:, codes].T.reshape(-1)
|
| 62 |
+
return W[:o] * scale[:o, None]
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
if __name__ == "__main__":
|
| 66 |
+
torch.manual_seed(0)
|
| 67 |
+
print(f"{'module':>22s} {'shape':>14s} {'bits/w':>7s} {'max|err|':>10s} {'rel RMSE':>10s} verdict")
|
| 68 |
+
ok = True
|
| 69 |
+
|
| 70 |
+
for name, (i, o, g, st) in {
|
| 71 |
+
"proj 1-bit (g8,st2)": (256, 256, 8, 2),
|
| 72 |
+
"proj 1-bit rect ": (256, 128, 8, 2),
|
| 73 |
+
"FFN 0.125 (g32,st1)": (256, 1024, 32, 1),
|
| 74 |
+
"FFN 0.125 down ": (1024, 256, 32, 1),
|
| 75 |
+
"odd rows (pad test) ": (256, 100, 8, 2),
|
| 76 |
+
}.items():
|
| 77 |
+
m = RVQ(i, o, g, st)
|
| 78 |
+
m.weight.data = torch.randn(o, i) / (i ** 0.5)
|
| 79 |
+
m.enc()
|
| 80 |
+
ref = m._q.detach().cpu().numpy()
|
| 81 |
+
|
| 82 |
+
cbT, idxp, scale = rvq_pack(m)
|
| 83 |
+
got = rvq_unpack(cbT, idxp, scale, o, i, g, st)
|
| 84 |
+
|
| 85 |
+
err = np.abs(ref - got).max()
|
| 86 |
+
rel = np.sqrt(((ref - got) ** 2).mean()) / np.sqrt((ref ** 2).mean())
|
| 87 |
+
good = err < 1e-5
|
| 88 |
+
ok &= good
|
| 89 |
+
print(f"{name:>22s} {f'{o}x{i}':>14s} {m.bits():7.3f} {err:10.2e} {rel:10.2e} "
|
| 90 |
+
f"{'PASS' if good else 'FAIL'}")
|
| 91 |
+
|
| 92 |
+
print()
|
| 93 |
+
print("ROUNDTRIP_OK" if ok else "ROUNDTRIP_FAIL")
|
finetune/modeling/export_ternary.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, sys, struct
|
| 2 |
+
os.environ.setdefault("SHADOW_D", "1536"); os.environ.setdefault("SHADOW_NL", "10")
|
| 3 |
+
os.environ.setdefault("SHADOW_NH", "24"); os.environ.setdefault("SHADOW_NKV", "2")
|
| 4 |
+
os.environ.setdefault("SHADOW_HD", "64"); os.environ.setdefault("SHADOW_FFNH", "4224")
|
| 5 |
+
os.environ.setdefault("SHADOW_KV_TWO_TIER", "1")
|
| 6 |
+
import numpy as np, torch
|
| 7 |
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "model"))
|
| 8 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 9 |
+
import common
|
| 10 |
+
from common import RVQ, requant
|
| 11 |
+
from model_250m import Shadow250M
|
| 12 |
+
from export_rvq import rvq_pack
|
| 13 |
+
|
| 14 |
+
CK, OUT = sys.argv[1], sys.argv[2]; COMPACT = "--compact" in sys.argv
|
| 15 |
+
ck = torch.load(CK, map_location="cpu", weights_only=False)
|
| 16 |
+
V, FPD = ck.get("cfg", {}).get("V", 131072), 512
|
| 17 |
+
model = Shadow250M(torch.zeros(V, FPD), torch.zeros(V, FPD), V)
|
| 18 |
+
model.load_state_dict(ck["model"], strict=False); model.eval(); requant(model)
|
| 19 |
+
|
| 20 |
+
def tern_pack(w):
|
| 21 |
+
wt = w.detach().float()
|
| 22 |
+
sc = 1.0 / wt.abs().mean(dim=1, keepdim=True).clamp_(min=1e-5)
|
| 23 |
+
t = (wt * sc).round().clamp(-1, 1).to(torch.int8).numpy()
|
| 24 |
+
rs = (1.0 / sc[:, 0]).numpy().astype(np.float32)
|
| 25 |
+
w = wt.numpy()
|
| 26 |
+
codes = (t + 1).astype(np.uint8)
|
| 27 |
+
o, i = w.shape; assert i % 4 == 0
|
| 28 |
+
c4 = codes.reshape(o, i // 4, 4)
|
| 29 |
+
packed = (c4[:, :, 0] | (c4[:, :, 1] << 2) | (c4[:, :, 2] << 4) | (c4[:, :, 3] << 6)).astype(np.uint8)
|
| 30 |
+
if COMPACT:
|
| 31 |
+
pad = (-i) % 5; c5 = np.concatenate([codes, np.ones((o, pad), np.uint8)], 1).reshape(o, -1, 5).astype(np.uint16)
|
| 32 |
+
packed = (c5[:, :, 0] + 3 * c5[:, :, 1] + 9 * c5[:, :, 2] + 27 * c5[:, :, 3] + 81 * c5[:, :, 4]).astype(np.uint8)
|
| 33 |
+
return packed, rs, t.astype(np.float32) * rs[:, None]
|
| 34 |
+
|
| 35 |
+
class Wrap(torch.nn.Module):
|
| 36 |
+
def __init__(s, m):
|
| 37 |
+
super().__init__(); s.emb = m.inp; s.b = m.b; s.step = m.struct; s.nf = m.nf; s.head = m.head; s.tb = m.tied_bias
|
| 38 |
+
wrap = Wrap(model)
|
| 39 |
+
recs = []; ntern = nrvq = 0; tern_bytes = rvq_bytes = dense_bytes = 0
|
| 40 |
+
rvq_ids = set()
|
| 41 |
+
for name, mod in wrap.named_modules():
|
| 42 |
+
if isinstance(mod, RVQ):
|
| 43 |
+
rvq_ids.add(id(mod))
|
| 44 |
+
if mod.g == 32:
|
| 45 |
+
packed, rs, deq = tern_pack(mod.weight)
|
| 46 |
+
recs.append((name, 4 if COMPACT else 3, (mod.o, mod.i, packed, rs))); ntern += 1; tern_bytes += packed.nbytes + rs.nbytes
|
| 47 |
+
else:
|
| 48 |
+
cbT, idx, scale = rvq_pack(mod)
|
| 49 |
+
recs.append((name, 1, (mod.o, mod.i, mod.g, mod.st, cbT, idx, scale))); nrvq += 1
|
| 50 |
+
rvq_bytes += cbT.nbytes + idx.nbytes + scale.nbytes
|
| 51 |
+
for name, p in wrap.named_parameters():
|
| 52 |
+
owner = name.rsplit(".", 1)[0]
|
| 53 |
+
mod = dict(wrap.named_modules()).get(owner)
|
| 54 |
+
if mod is not None and id(mod) in rvq_ids: continue
|
| 55 |
+
a = p.detach().float().numpy()
|
| 56 |
+
if COMPACT and a.ndim >= 1 and a.size >= 4096: recs.append((name, 5, a.astype(np.float16))); dense_bytes += a.size * 2
|
| 57 |
+
else: recs.append((name, 0, a)); dense_bytes += a.nbytes
|
| 58 |
+
for name, b in wrap.named_buffers():
|
| 59 |
+
if any(x in name for x in ("cent", "cb", "initialized", "sign", "mu", "ctv", "low", "high", "updates", "inv")): continue
|
| 60 |
+
a = b.detach().float().numpy(); recs.append((name, 0, a)); dense_bytes += a.nbytes
|
| 61 |
+
|
| 62 |
+
with open(OUT, "wb") as f:
|
| 63 |
+
f.write(b"SHDW"); f.write(struct.pack("<II", 1, len(recs)))
|
| 64 |
+
for name, kind, pay in recs:
|
| 65 |
+
nb = name.encode(); f.write(struct.pack("<I", len(nb))); f.write(nb); f.write(struct.pack("<I", kind))
|
| 66 |
+
if kind == 0:
|
| 67 |
+
a = np.ascontiguousarray(pay, np.float32); f.write(struct.pack("<I", a.ndim)); f.write(struct.pack("<" + "I" * a.ndim, *a.shape)); f.write(a.tobytes())
|
| 68 |
+
elif kind == 1:
|
| 69 |
+
o, i, g, st, cbT, idx, scale = pay
|
| 70 |
+
f.write(struct.pack("<IIII", o, i, g, st)); f.write(np.ascontiguousarray(cbT, np.float32).tobytes())
|
| 71 |
+
f.write(np.ascontiguousarray(idx, np.uint8).tobytes()); f.write(np.ascontiguousarray(scale, np.float32).tobytes())
|
| 72 |
+
elif kind == 5:
|
| 73 |
+
a = np.ascontiguousarray(pay, np.float16); f.write(struct.pack("<I", a.ndim)); f.write(struct.pack("<" + "I" * a.ndim, *a.shape)); f.write(a.tobytes())
|
| 74 |
+
else:
|
| 75 |
+
o, i, packed, rs = pay
|
| 76 |
+
f.write(struct.pack("<II", o, i)); f.write(np.ascontiguousarray(packed, np.uint8).tobytes()); f.write(rs.tobytes())
|
| 77 |
+
size = os.path.getsize(OUT)
|
| 78 |
+
print(f"loaded checkpoint ({ck.get('step','finetuned')})")
|
| 79 |
+
print(f"ternary FFN modules {ntern}: {tern_bytes/1e6:.2f} MB | RVQ 1-bit modules {nrvq}: {rvq_bytes/1e6:.2f} MB | dense fp32: {dense_bytes/1e6:.2f} MB")
|
| 80 |
+
print(f".shdw = {size/1e6:.2f} MB (+ 8.39 MB fp131072 table = {(size+8388736)/1e6:.2f} MB deploy)")
|
| 81 |
+
|
| 82 |
+
mod = model.b[0].up; w = mod.weight.detach().float()
|
| 83 |
+
sc = 1.0 / w.abs().mean(dim=1, keepdim=True).clamp_(min=1e-5); wq = (w * sc).round().clamp(-1, 1) / sc
|
| 84 |
+
_, _, deq = tern_pack(mod.weight); print("ternary round-trip max|err| vs trainer forward:", float((torch.tensor(deq) - wq).abs().max()))
|
finetune/modeling/model_250m.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, math, numpy as np, torch, torch.nn as nn, torch.nn.functional as F
|
| 2 |
+
import common
|
| 3 |
+
from common import D,NL,NH,NKV,HD,FFNH,FPD, RMS,RVQ,Block,StructStep, requant, cs
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def load_vocab(fp_path,dev):
|
| 7 |
+
fp=np.unpackbits(np.load(fp_path),axis=1)[:,:FPD]
|
| 8 |
+
cent=torch.tensor(fp.astype(np.float32)*2-1,device=dev)
|
| 9 |
+
return cent, F.normalize(cent,dim=-1), cent.shape[0]
|
| 10 |
+
|
| 11 |
+
class Shadow250M(nn.Module):
|
| 12 |
+
def __init__(s,cent,cent_n,V,use_memory=False):
|
| 13 |
+
super().__init__()
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
s.register_buffer("cent",cent,persistent=False)
|
| 17 |
+
s.register_buffer("cent_n",cent_n,persistent=False); s.V=V
|
| 18 |
+
s.inp=nn.Linear(FPD,D,bias=False)
|
| 19 |
+
s.b=nn.ModuleList([Block(i) for i in range(NL)])
|
| 20 |
+
s.struct=StructStep()
|
| 21 |
+
s.nf=RMS(D); s.head=nn.Linear(D,FPD,bias=False)
|
| 22 |
+
s.tied_bias=nn.Parameter(torch.zeros(V))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
s.default_use_memory=bool(use_memory)
|
| 26 |
+
inv=1.0/(10000**(torch.arange(0,HD,2).float()/HD)); s.register_buffer("inv",inv,persistent=False)
|
| 27 |
+
def trunk(s,idx):
|
| 28 |
+
cos,sin=cs(idx.shape[1],idx.device); x=s.inp(s.cent[idx])
|
| 29 |
+
for blk in s.b: x=blk(x,cos,sin)
|
| 30 |
+
x,conf=s.struct(x)
|
| 31 |
+
return s.nf(x), conf
|
| 32 |
+
def logits(s,x): return (s.head(x).float()@s.cent_n.T)+s.tied_bias
|
| 33 |
+
def forward(s,idx,ys=None):
|
| 34 |
+
x,conf=s.trunk(idx)
|
| 35 |
+
if ys is None: return s.logits(x)
|
| 36 |
+
|
| 37 |
+
ph=s.head(x).float().reshape(-1,FPD); yf=ys.reshape(-1); Nn=ph.shape[0]; CH=2048
|
| 38 |
+
return sum(F.cross_entropy(ph[i:i+CH]@s.cent_n.T+s.tied_bias,yf[i:i+CH],reduction="sum") for i in range(0,Nn,CH))/Nn
|
| 39 |
+
|
| 40 |
+
@torch.no_grad()
|
| 41 |
+
def prefill_cached(s,idx,max_ctx=2048,exact_shiftmax=True,
|
| 42 |
+
use_memory=None,memory_chunk=128,memory_capacity=256,
|
| 43 |
+
retrieval_trace=False,stream_block=256):
|
| 44 |
+
if use_memory is None: use_memory=s.default_use_memory
|
| 45 |
+
if idx.shape[1]>max_ctx:
|
| 46 |
+
if not use_memory:
|
| 47 |
+
idx=idx[:,-max_ctx:]
|
| 48 |
+
else:
|
| 49 |
+
logits,state=s.prefill_cached(
|
| 50 |
+
idx[:,:max_ctx],max_ctx=max_ctx,
|
| 51 |
+
exact_shiftmax=exact_shiftmax,use_memory=use_memory,
|
| 52 |
+
memory_chunk=memory_chunk,memory_capacity=memory_capacity,
|
| 53 |
+
retrieval_trace=retrieval_trace,stream_block=stream_block)
|
| 54 |
+
cursor=max_ctx
|
| 55 |
+
final=idx.shape[1]-1
|
| 56 |
+
while cursor<final:
|
| 57 |
+
stop=min(final,cursor+int(stream_block))
|
| 58 |
+
state=s.ingest_cached(idx[:,cursor:stop],state)
|
| 59 |
+
cursor=stop
|
| 60 |
+
return s.decode_cached(idx[:,final:final+1],state)
|
| 61 |
+
cos,sin=cs(idx.shape[1],idx.device); x=s.inp(s.cent[idx]); layers=[]
|
| 62 |
+
for blk in s.b:
|
| 63 |
+
x,cache=blk.prefill_cached(x,cos,sin,max_ctx=max_ctx,
|
| 64 |
+
exact=exact_shiftmax,
|
| 65 |
+
archive_cold=bool(use_memory),
|
| 66 |
+
cold_chunk=memory_chunk,
|
| 67 |
+
retrieval_trace=retrieval_trace); layers.append(cache)
|
| 68 |
+
trunk=x[:,-max_ctx:]; y,_=s.struct(x); ph=s.head(s.nf(y[:,-1])).float()
|
| 69 |
+
return ph@s.cent_n.T+s.tied_bias,{"layers":layers,"trunk":trunk,
|
| 70 |
+
"position":idx.shape[1],"max_ctx":max_ctx,
|
| 71 |
+
"exact_shiftmax":exact_shiftmax,
|
| 72 |
+
"retrieval_trace":bool(retrieval_trace),
|
| 73 |
+
"stream_block":int(stream_block),
|
| 74 |
+
"memory":common.make_memory_state(
|
| 75 |
+
idx.shape[0],D,idx.device,chunk_size=memory_chunk,
|
| 76 |
+
capacity=memory_capacity) if use_memory else None}
|
| 77 |
+
|
| 78 |
+
@torch.no_grad()
|
| 79 |
+
def ingest_cached(s,idx_block,state):
|
| 80 |
+
if idx_block.shape[1]==0:
|
| 81 |
+
return state
|
| 82 |
+
x=s.inp(s.cent[idx_block]); pos=int(state["position"])
|
| 83 |
+
for i,blk in enumerate(s.b):
|
| 84 |
+
x,state["layers"][i]=blk.decode_cached(
|
| 85 |
+
x,pos,state["layers"][i],max_ctx=state["max_ctx"],
|
| 86 |
+
exact=state["exact_shiftmax"],retrieve_cold=False)
|
| 87 |
+
combined=torch.cat((state["trunk"],x),1)
|
| 88 |
+
overflow=max(0,combined.shape[1]-state["max_ctx"])
|
| 89 |
+
common.memory_absorb_evicted(
|
| 90 |
+
state.get("memory"),combined[:,:overflow],s.head,s.nf)
|
| 91 |
+
state["trunk"]=combined[:,overflow:]
|
| 92 |
+
s.struct.decode_cached(x,state["trunk"])
|
| 93 |
+
state["position"]=pos+idx_block.shape[1]
|
| 94 |
+
return state
|
| 95 |
+
|
| 96 |
+
@torch.no_grad()
|
| 97 |
+
def decode_cached(s,idx_new,state):
|
| 98 |
+
if state.get("memory") is not None and idx_new.shape[1]!=1:
|
| 99 |
+
logits=None
|
| 100 |
+
for j in range(idx_new.shape[1]):
|
| 101 |
+
logits,state=s.decode_cached(idx_new[:,j:j+1],state)
|
| 102 |
+
return logits,state
|
| 103 |
+
x=s.inp(s.cent[idx_new]); pos=int(state["position"])
|
| 104 |
+
for i,blk in enumerate(s.b):
|
| 105 |
+
x,state["layers"][i]=blk.decode_cached(x,pos,state["layers"][i],
|
| 106 |
+
max_ctx=state["max_ctx"],
|
| 107 |
+
exact=state["exact_shiftmax"])
|
| 108 |
+
combined=torch.cat((state["trunk"],x),1)
|
| 109 |
+
overflow=max(0,combined.shape[1]-state["max_ctx"])
|
| 110 |
+
common.memory_absorb_evicted(
|
| 111 |
+
state.get("memory"),combined[:,:overflow],s.head,s.nf)
|
| 112 |
+
state["trunk"]=combined[:,overflow:]
|
| 113 |
+
context=common.memory_append_recall(
|
| 114 |
+
state.get("memory"),x[:,-1],state["trunk"],s.head,s.nf)
|
| 115 |
+
y,_=s.struct.decode_cached(x,context); state["position"]=pos+idx_new.shape[1]
|
| 116 |
+
ph=s.head(s.nf(y[:,-1])).float(); return ph@s.cent_n.T+s.tied_bias,state
|
| 117 |
+
|
| 118 |
+
if __name__=="__main__":
|
| 119 |
+
dev="cuda" if torch.cuda.is_available() else "cpu"
|
| 120 |
+
FP=os.path.join(os.path.dirname(__file__),"dolma3_fp_512b.npy")
|
| 121 |
+
if not os.path.exists(FP): FP=os.path.join(os.path.dirname(__file__),"..","..","shadow-o","phase0_tokenizer","dolma3_fp_512b.npy")
|
| 122 |
+
cent,cent_n,V=load_vocab(FP,dev); m=Shadow250M(cent,cent_n,V).to(dev)
|
| 123 |
+
body=sum(p.numel() for n,p in m.named_parameters() if "cent" not in n and "tied_bias" not in n)
|
| 124 |
+
print(f"SHADOW-O 250M | body {body/1e6:.1f}M params | vocab {V} (0 params) | config d{D} L{NL} GQA{NH}/{NKV}")
|
| 125 |
+
b=m.b[0]; print(f" proj {b.q.bits():.3f} bit/w | FFN {b.up.bits():.3f} bit/w | struct step: 1 | Q4/loop: none")
|
| 126 |
+
x=torch.randint(0,V,(1,64),device=dev); requant(m)
|
| 127 |
+
with torch.autocast("cuda",dtype=torch.bfloat16) if dev=="cuda" else torch.no_grad():
|
| 128 |
+
lg=m(x); print(f" forward OK: logits {tuple(lg.shape)} finite={bool(torch.isfinite(lg).all())}")
|
finetune/modeling/paged_kv.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from typing import Iterable
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
_POPCOUNT = tuple(int(i).bit_count() for i in range(256))
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@dataclass(frozen=True)
|
| 14 |
+
class ArchiveAccounting:
|
| 15 |
+
logical_tokens: int
|
| 16 |
+
page_count: int
|
| 17 |
+
payload_bytes: int
|
| 18 |
+
allocated_payload_bytes: int
|
| 19 |
+
position_index_bytes: int
|
| 20 |
+
bytes_copied_on_append: int
|
| 21 |
+
|
| 22 |
+
@property
|
| 23 |
+
def allocated_total_bytes(self) -> int:
|
| 24 |
+
return self.allocated_payload_bytes + self.position_index_bytes
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class PagedKVArchive:
|
| 28 |
+
|
| 29 |
+
format = "paged_random_walsh_ctv_1bit_v1"
|
| 30 |
+
state_version = 1
|
| 31 |
+
|
| 32 |
+
def __init__(
|
| 33 |
+
self,
|
| 34 |
+
batch: int,
|
| 35 |
+
heads: int,
|
| 36 |
+
packed_width: int,
|
| 37 |
+
*,
|
| 38 |
+
page_size: int = 256,
|
| 39 |
+
device=None,
|
| 40 |
+
):
|
| 41 |
+
if min(batch, heads, packed_width, page_size) < 1:
|
| 42 |
+
raise ValueError("batch, heads, packed_width, and page_size must be positive")
|
| 43 |
+
self.batch = int(batch)
|
| 44 |
+
self.heads = int(heads)
|
| 45 |
+
self.packed_width = int(packed_width)
|
| 46 |
+
self.page_size = int(page_size)
|
| 47 |
+
self.device = torch.device(device or "cpu")
|
| 48 |
+
self.k_pages: list[torch.Tensor] = []
|
| 49 |
+
self.v_pages: list[torch.Tensor] = []
|
| 50 |
+
self.position_pages: list[torch.Tensor] = []
|
| 51 |
+
self.length = 0
|
| 52 |
+
self.bytes_copied_on_append = 0
|
| 53 |
+
|
| 54 |
+
@property
|
| 55 |
+
def shape(self) -> tuple[int, int, int, int]:
|
| 56 |
+
return self.batch, self.heads, self.length, self.packed_width
|
| 57 |
+
|
| 58 |
+
def __len__(self) -> int:
|
| 59 |
+
return self.length
|
| 60 |
+
|
| 61 |
+
def _allocate_page(self):
|
| 62 |
+
shape = (self.batch, self.heads, self.page_size, self.packed_width)
|
| 63 |
+
self.k_pages.append(torch.empty(shape, dtype=torch.uint8, device=self.device))
|
| 64 |
+
self.v_pages.append(torch.empty(shape, dtype=torch.uint8, device=self.device))
|
| 65 |
+
self.position_pages.append(
|
| 66 |
+
torch.empty(self.page_size, dtype=torch.int64, device=self.device)
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
def append(
|
| 70 |
+
self,
|
| 71 |
+
packed_k: torch.Tensor,
|
| 72 |
+
packed_v: torch.Tensor,
|
| 73 |
+
positions: torch.Tensor | Iterable[int] | None = None,
|
| 74 |
+
) -> None:
|
| 75 |
+
expected_prefix = (self.batch, self.heads)
|
| 76 |
+
if packed_k.dtype != torch.uint8 or packed_v.dtype != torch.uint8:
|
| 77 |
+
raise TypeError("paged one-bit K/V payloads must be torch.uint8")
|
| 78 |
+
if packed_k.shape != packed_v.shape:
|
| 79 |
+
raise ValueError("packed K and V shapes differ")
|
| 80 |
+
if (
|
| 81 |
+
packed_k.ndim != 4
|
| 82 |
+
or tuple(packed_k.shape[:2]) != expected_prefix
|
| 83 |
+
or packed_k.shape[-1] != self.packed_width
|
| 84 |
+
):
|
| 85 |
+
raise ValueError(
|
| 86 |
+
"expected packed K/V shape "
|
| 87 |
+
f"({self.batch},{self.heads},T,{self.packed_width}), "
|
| 88 |
+
f"got {tuple(packed_k.shape)}"
|
| 89 |
+
)
|
| 90 |
+
tokens = int(packed_k.shape[2])
|
| 91 |
+
if tokens == 0:
|
| 92 |
+
return
|
| 93 |
+
if packed_k.device != self.device or packed_v.device != self.device:
|
| 94 |
+
raise ValueError(
|
| 95 |
+
f"archive is on {self.device}, payload is on "
|
| 96 |
+
f"{packed_k.device}/{packed_v.device}"
|
| 97 |
+
)
|
| 98 |
+
if positions is None:
|
| 99 |
+
pos = torch.arange(
|
| 100 |
+
self.length, self.length + tokens, dtype=torch.int64, device=self.device
|
| 101 |
+
)
|
| 102 |
+
else:
|
| 103 |
+
pos = torch.as_tensor(positions, dtype=torch.int64, device=self.device)
|
| 104 |
+
if pos.ndim != 1 or pos.numel() != tokens:
|
| 105 |
+
raise ValueError(f"positions must contain exactly {tokens} entries")
|
| 106 |
+
|
| 107 |
+
source = 0
|
| 108 |
+
while source < tokens:
|
| 109 |
+
page_index = self.length // self.page_size
|
| 110 |
+
page_offset = self.length % self.page_size
|
| 111 |
+
if page_index == len(self.k_pages):
|
| 112 |
+
self._allocate_page()
|
| 113 |
+
take = min(tokens - source, self.page_size - page_offset)
|
| 114 |
+
dst = slice(page_offset, page_offset + take)
|
| 115 |
+
src = slice(source, source + take)
|
| 116 |
+
self.k_pages[page_index][:, :, dst].copy_(packed_k[:, :, src])
|
| 117 |
+
self.v_pages[page_index][:, :, dst].copy_(packed_v[:, :, src])
|
| 118 |
+
self.position_pages[page_index][dst].copy_(pos[src])
|
| 119 |
+
copied = 2 * self.batch * self.heads * take * self.packed_width
|
| 120 |
+
self.bytes_copied_on_append += copied
|
| 121 |
+
self.length += take
|
| 122 |
+
source += take
|
| 123 |
+
|
| 124 |
+
def _validate_indices(self, indices: torch.Tensor) -> torch.Tensor:
|
| 125 |
+
indices = torch.as_tensor(indices, dtype=torch.long, device=self.device)
|
| 126 |
+
if indices.ndim != 3 or tuple(indices.shape[:2]) != (self.batch, self.heads):
|
| 127 |
+
raise ValueError(
|
| 128 |
+
f"indices must have shape ({self.batch},{self.heads},K), "
|
| 129 |
+
f"got {tuple(indices.shape)}"
|
| 130 |
+
)
|
| 131 |
+
if indices.numel() and (
|
| 132 |
+
int(indices.min().item()) < 0 or int(indices.max().item()) >= self.length
|
| 133 |
+
):
|
| 134 |
+
raise IndexError(f"archive index outside [0,{self.length})")
|
| 135 |
+
return indices
|
| 136 |
+
|
| 137 |
+
def gather(self, indices: torch.Tensor, kind: str) -> torch.Tensor:
|
| 138 |
+
indices = self._validate_indices(indices)
|
| 139 |
+
if kind not in {"k", "v"}:
|
| 140 |
+
raise ValueError("kind must be 'k' or 'v'")
|
| 141 |
+
pages = self.k_pages if kind == "k" else self.v_pages
|
| 142 |
+
out = torch.empty(
|
| 143 |
+
(*indices.shape, self.packed_width),
|
| 144 |
+
dtype=torch.uint8,
|
| 145 |
+
device=self.device,
|
| 146 |
+
)
|
| 147 |
+
page_ids = torch.div(indices, self.page_size, rounding_mode="floor")
|
| 148 |
+
offsets = indices.remainder(self.page_size)
|
| 149 |
+
for page_id in torch.unique(page_ids).tolist():
|
| 150 |
+
mask = page_ids == int(page_id)
|
| 151 |
+
b, h, slot = mask.nonzero(as_tuple=True)
|
| 152 |
+
out[b, h, slot] = pages[int(page_id)][b, h, offsets[b, h, slot]]
|
| 153 |
+
return out
|
| 154 |
+
|
| 155 |
+
def gather_positions(self, indices: torch.Tensor) -> torch.Tensor:
|
| 156 |
+
indices = self._validate_indices(indices)
|
| 157 |
+
out = torch.empty(indices.shape, dtype=torch.int64, device=self.device)
|
| 158 |
+
page_ids = torch.div(indices, self.page_size, rounding_mode="floor")
|
| 159 |
+
offsets = indices.remainder(self.page_size)
|
| 160 |
+
for page_id in torch.unique(page_ids).tolist():
|
| 161 |
+
mask = page_ids == int(page_id)
|
| 162 |
+
b, h, slot = mask.nonzero(as_tuple=True)
|
| 163 |
+
out[b, h, slot] = self.position_pages[int(page_id)][offsets[b, h, slot]]
|
| 164 |
+
return out
|
| 165 |
+
|
| 166 |
+
@staticmethod
|
| 167 |
+
def _popcount_bytes(value: torch.Tensor) -> torch.Tensor:
|
| 168 |
+
lut = torch.tensor(_POPCOUNT, dtype=torch.int16, device=value.device)
|
| 169 |
+
return lut[value.long()].sum(-1, dtype=torch.int32)
|
| 170 |
+
|
| 171 |
+
def exact_hamming_topk(
|
| 172 |
+
self, query: torch.Tensor, k: int
|
| 173 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 174 |
+
if self.length == 0:
|
| 175 |
+
raise ValueError("cannot search an empty archive")
|
| 176 |
+
if (
|
| 177 |
+
query.dtype != torch.uint8
|
| 178 |
+
or query.ndim != 3
|
| 179 |
+
or tuple(query.shape) != (self.batch, self.heads, self.packed_width)
|
| 180 |
+
):
|
| 181 |
+
raise ValueError(
|
| 182 |
+
"query must be uint8 with shape "
|
| 183 |
+
f"({self.batch},{self.heads},{self.packed_width})"
|
| 184 |
+
)
|
| 185 |
+
take = min(max(1, int(k)), self.length)
|
| 186 |
+
best_indices = torch.empty(
|
| 187 |
+
(self.batch, self.heads, 0), dtype=torch.long, device=self.device
|
| 188 |
+
)
|
| 189 |
+
best_distances = torch.empty(
|
| 190 |
+
(self.batch, self.heads, 0), dtype=torch.int32, device=self.device
|
| 191 |
+
)
|
| 192 |
+
absolute = 0
|
| 193 |
+
tie_base = self.length + 1
|
| 194 |
+
for page_id, page in enumerate(self.k_pages):
|
| 195 |
+
used = min(self.page_size, self.length - absolute)
|
| 196 |
+
if used <= 0:
|
| 197 |
+
break
|
| 198 |
+
distance = self._popcount_bytes(page[:, :, :used] ^ query[:, :, None])
|
| 199 |
+
indices = torch.arange(
|
| 200 |
+
absolute, absolute + used, dtype=torch.long, device=self.device
|
| 201 |
+
).view(1, 1, used).expand(self.batch, self.heads, used)
|
| 202 |
+
candidate_indices = torch.cat((best_indices, indices), dim=-1)
|
| 203 |
+
candidate_distances = torch.cat((best_distances, distance), dim=-1)
|
| 204 |
+
rank_key = candidate_distances.to(torch.int64) * tie_base + candidate_indices
|
| 205 |
+
selected = torch.topk(
|
| 206 |
+
rank_key, min(take, rank_key.shape[-1]), largest=False, sorted=True
|
| 207 |
+
).indices
|
| 208 |
+
best_indices = torch.gather(candidate_indices, -1, selected)
|
| 209 |
+
best_distances = torch.gather(candidate_distances, -1, selected)
|
| 210 |
+
absolute += used
|
| 211 |
+
return best_indices, best_distances
|
| 212 |
+
|
| 213 |
+
def materialize(self, kind: str) -> torch.Tensor:
|
| 214 |
+
if kind not in {"k", "v"}:
|
| 215 |
+
raise ValueError("kind must be 'k' or 'v'")
|
| 216 |
+
pages = self.k_pages if kind == "k" else self.v_pages
|
| 217 |
+
if not pages:
|
| 218 |
+
return torch.empty(self.shape, dtype=torch.uint8, device=self.device)
|
| 219 |
+
parts = []
|
| 220 |
+
remaining = self.length
|
| 221 |
+
for page in pages:
|
| 222 |
+
used = min(self.page_size, remaining)
|
| 223 |
+
parts.append(page[:, :, :used])
|
| 224 |
+
remaining -= used
|
| 225 |
+
return torch.cat(parts, dim=2)
|
| 226 |
+
|
| 227 |
+
def accounting(self) -> ArchiveAccounting:
|
| 228 |
+
payload = 2 * self.batch * self.heads * self.length * self.packed_width
|
| 229 |
+
allocated = (
|
| 230 |
+
2
|
| 231 |
+
* self.batch
|
| 232 |
+
* self.heads
|
| 233 |
+
* len(self.k_pages)
|
| 234 |
+
* self.page_size
|
| 235 |
+
* self.packed_width
|
| 236 |
+
)
|
| 237 |
+
positions = len(self.position_pages) * self.page_size * 8
|
| 238 |
+
return ArchiveAccounting(
|
| 239 |
+
logical_tokens=self.length,
|
| 240 |
+
page_count=len(self.k_pages),
|
| 241 |
+
payload_bytes=payload,
|
| 242 |
+
allocated_payload_bytes=allocated,
|
| 243 |
+
position_index_bytes=positions,
|
| 244 |
+
bytes_copied_on_append=self.bytes_copied_on_append,
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
def state(self) -> dict:
|
| 248 |
+
return {
|
| 249 |
+
"format": self.format,
|
| 250 |
+
"version": self.state_version,
|
| 251 |
+
"batch": self.batch,
|
| 252 |
+
"heads": self.heads,
|
| 253 |
+
"packed_width": self.packed_width,
|
| 254 |
+
"page_size": self.page_size,
|
| 255 |
+
"length": self.length,
|
| 256 |
+
"bytes_copied_on_append": self.bytes_copied_on_append,
|
| 257 |
+
"k_pages": [page.clone() for page in self.k_pages],
|
| 258 |
+
"v_pages": [page.clone() for page in self.v_pages],
|
| 259 |
+
"position_pages": [page.clone() for page in self.position_pages],
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
@classmethod
|
| 263 |
+
def from_state(cls, state: dict, device=None) -> "PagedKVArchive":
|
| 264 |
+
if state.get("format") != cls.format or int(state.get("version", -1)) != 1:
|
| 265 |
+
raise ValueError("unknown paged KV archive state")
|
| 266 |
+
inferred = state["k_pages"][0].device if state["k_pages"] else "cpu"
|
| 267 |
+
target = torch.device(device or inferred)
|
| 268 |
+
archive = cls(
|
| 269 |
+
state["batch"],
|
| 270 |
+
state["heads"],
|
| 271 |
+
state["packed_width"],
|
| 272 |
+
page_size=state["page_size"],
|
| 273 |
+
device=target,
|
| 274 |
+
)
|
| 275 |
+
archive.k_pages = [page.to(target) for page in state["k_pages"]]
|
| 276 |
+
archive.v_pages = [page.to(target) for page in state["v_pages"]]
|
| 277 |
+
archive.position_pages = [page.to(target) for page in state["position_pages"]]
|
| 278 |
+
archive.length = int(state["length"])
|
| 279 |
+
archive.bytes_copied_on_append = int(state["bytes_copied_on_append"])
|
| 280 |
+
return archive
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
class PagedKVView:
|
| 284 |
+
|
| 285 |
+
def __init__(self, archive: PagedKVArchive, kind: str):
|
| 286 |
+
self.archive = archive
|
| 287 |
+
self.kind = kind
|
| 288 |
+
|
| 289 |
+
@property
|
| 290 |
+
def shape(self):
|
| 291 |
+
return self.archive.shape
|
| 292 |
+
|
| 293 |
+
@property
|
| 294 |
+
def dtype(self):
|
| 295 |
+
return torch.uint8
|
| 296 |
+
|
| 297 |
+
@property
|
| 298 |
+
def device(self):
|
| 299 |
+
return self.archive.device
|
| 300 |
+
|
| 301 |
+
def materialize(self):
|
| 302 |
+
return self.archive.materialize(self.kind)
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
class ExactChunkCountView:
|
| 306 |
+
|
| 307 |
+
def __init__(self, archive: PagedKVArchive, chunk_size: int):
|
| 308 |
+
self.archive = archive
|
| 309 |
+
self.chunk_size = int(chunk_size)
|
| 310 |
+
|
| 311 |
+
@property
|
| 312 |
+
def shape(self):
|
| 313 |
+
complete = self.archive.length // self.chunk_size
|
| 314 |
+
return (
|
| 315 |
+
self.archive.batch,
|
| 316 |
+
self.archive.heads,
|
| 317 |
+
complete,
|
| 318 |
+
self.archive.packed_width,
|
| 319 |
+
)
|
finetune/modeling/repack_shdw.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys, struct, numpy as np
|
| 2 |
+
src, dst = sys.argv[1], sys.argv[2]; FP16 = "--fp16" in sys.argv
|
| 3 |
+
f = open(src, "rb"); assert f.read(4) == b"SHDW"; ver, n = struct.unpack("<II", f.read(8))
|
| 4 |
+
out = open(dst, "wb"); out.write(b"SHDW"); out.write(struct.pack("<II", 1, n))
|
| 5 |
+
tern_in = tern_out = dense_in = dense_out = 0
|
| 6 |
+
for _ in range(n):
|
| 7 |
+
nl, = struct.unpack("<I", f.read(4)); name = f.read(nl); kind, = struct.unpack("<I", f.read(4))
|
| 8 |
+
out.write(struct.pack("<I", nl)); out.write(name)
|
| 9 |
+
if kind == 0:
|
| 10 |
+
nd, = struct.unpack("<I", f.read(4)); dims = struct.unpack("<" + "I" * nd, f.read(4 * nd)); c = int(np.prod(dims))
|
| 11 |
+
a = np.frombuffer(f.read(4 * c), np.float32); dense_in += 4 * c
|
| 12 |
+
if FP16 and c >= 4096: out.write(struct.pack("<I", 5)); out.write(struct.pack("<I", nd)); out.write(struct.pack("<" + "I" * nd, *dims)); out.write(a.astype(np.float16).tobytes()); dense_out += 2 * c
|
| 13 |
+
else: out.write(struct.pack("<I", 0)); out.write(struct.pack("<I", nd)); out.write(struct.pack("<" + "I" * nd, *dims)); out.write(a.tobytes()); dense_out += 4 * c
|
| 14 |
+
elif kind == 1:
|
| 15 |
+
o, i, g, st = struct.unpack("<IIII", f.read(16)); G = i // g; Npad = (o + 63) & ~63; nch = Npad // 64
|
| 16 |
+
cb = f.read(st * g * 16 * 4); idx = f.read(st * nch * G * 32); sc = f.read(Npad * 4)
|
| 17 |
+
out.write(struct.pack("<I", 1)); out.write(struct.pack("<IIII", o, i, g, st)); out.write(cb); out.write(idx); out.write(sc)
|
| 18 |
+
elif kind == 3:
|
| 19 |
+
o, i = struct.unpack("<II", f.read(8)); packed = np.frombuffer(f.read(o * i // 4), np.uint8).reshape(o, i // 4); rs = f.read(o * 4)
|
| 20 |
+
tern_in += packed.nbytes
|
| 21 |
+
c4 = np.stack([(packed >> (2 * j)) & 3 for j in range(4)], -1).reshape(o, i)
|
| 22 |
+
pad = (-i) % 5; c5 = np.concatenate([c4, np.ones((o, pad), np.uint8)], 1).reshape(o, -1, 5).astype(np.uint16)
|
| 23 |
+
p5 = (c5[:, :, 0] + 3 * c5[:, :, 1] + 9 * c5[:, :, 2] + 27 * c5[:, :, 3] + 81 * c5[:, :, 4]).astype(np.uint8)
|
| 24 |
+
out.write(struct.pack("<I", 4)); out.write(struct.pack("<II", o, i)); out.write(p5.tobytes()); out.write(rs); tern_out += p5.nbytes
|
| 25 |
+
else: raise SystemExit(f"unknown kind {kind}")
|
| 26 |
+
out.close()
|
| 27 |
+
import os
|
| 28 |
+
print(f"ternary {tern_in/1e6:.1f} -> {tern_out/1e6:.1f} MB | dense {dense_in/1e6:.1f} -> {dense_out/1e6:.1f} MB | file {os.path.getsize(src)/1e6:.1f} -> {os.path.getsize(dst)/1e6:.1f} MB")
|
finetune/pirate.shdw
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3308a5706c4a56f404e117a147c1c5c7b469df2dd890336f88df9bce294388bf
|
| 3 |
+
size 52342802
|
finetune/shadow250m_instruct.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c23bde9d8669f38842328a6e7fc940353342750aa264056530ea6d0da11151c0
|
| 3 |
+
size 538876067
|
framework.png
ADDED
|
Git LFS Details
|
shadow_chat.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Start chatting with SHADOW. Picks the right binary for your system automatically.
|
| 2 |
+
python shadow_chat.py
|
| 3 |
+
"""
|
| 4 |
+
import os, sys, platform, subprocess, pathlib
|
| 5 |
+
HERE = pathlib.Path(__file__).resolve().parent
|
| 6 |
+
osname = platform.system()
|
| 7 |
+
if osname == "Windows": k = HERE / "deployment" / "bin" / "windows" / "shadow.exe"
|
| 8 |
+
elif osname == "Linux": k = HERE / "deployment" / "bin" / "linux" / "shadow"
|
| 9 |
+
else: sys.exit("macOS build available on request: saikiranbathula1@gmail.com")
|
| 10 |
+
if osname != "Windows": os.chmod(k, 0o755)
|
| 11 |
+
sys.path.insert(0, str(HERE))
|
| 12 |
+
from shadow_runtime import Engine
|
| 13 |
+
eng = Engine(str(HERE / "deployment" / "shadow250m_instruct.shdw"), str(HERE / "deployment" / "fp131072.npy"), kernel=str(k))
|
| 14 |
+
print("SHADOW 250M. Type your message, 'quit' to stop.")
|
| 15 |
+
while True:
|
| 16 |
+
try: q = input("you> ").strip()
|
| 17 |
+
except EOFError: break
|
| 18 |
+
if q in ("quit", "exit"): break
|
| 19 |
+
if q: print("shadow>", eng.chat(q))
|
shadow_runtime/__init__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""SHADOW 250M runtime: chat through the bundled CPU kernel, and question answering over an on-disk archive.
|
| 2 |
+
|
| 3 |
+
from shadow_runtime import Engine
|
| 4 |
+
eng = Engine("shadow250m_instruct.shdw", "fp131072.npy", archive="path/to/archive_dir")
|
| 5 |
+
print(eng.answer("your question"))
|
| 6 |
+
|
| 7 |
+
An archive directory holds tokens.u32 (uint32 token stream). The lexical index is built once on first use
|
| 8 |
+
and cached next to it. Chat without an archive:
|
| 9 |
+
|
| 10 |
+
eng = Engine("shadow250m_instruct.shdw", "fp131072.npy")
|
| 11 |
+
print(eng.chat("Explain photosynthesis in two sentences."))
|
| 12 |
+
|
| 13 |
+
CLI: python -m shadow_runtime --model shadow250m_instruct.shdw --table fp131072.npy \
|
| 14 |
+
--archive path/to/archive --ask "your question"
|
| 15 |
+
"""
|
| 16 |
+
import os, sys, pathlib, subprocess
|
| 17 |
+
import numpy as np
|
| 18 |
+
HERE = pathlib.Path(__file__).resolve().parent
|
| 19 |
+
sys.path.insert(0, str(HERE))
|
| 20 |
+
from retriever import Inverted, load_archive, enc, _dec
|
| 21 |
+
from answer_engine import Engine as _Extractor, ABSTAIN
|
| 22 |
+
from prompt import EOT
|
| 23 |
+
|
| 24 |
+
BOS, SOT, EOS = 2, 8, 1
|
| 25 |
+
|
| 26 |
+
class Engine:
|
| 27 |
+
def __init__(s, model, table, archive=None, kernel=None, threads=None):
|
| 28 |
+
s.model = str(pathlib.Path(model).resolve()); s.table = str(pathlib.Path(table).resolve())
|
| 29 |
+
s.kernel = str(pathlib.Path(kernel).resolve()) if kernel else str((HERE.parent / "deployment" / "bin" / ("windows/shadow.exe" if os.name == "nt" else "linux/shadow")).resolve())
|
| 30 |
+
s.env = dict(os.environ)
|
| 31 |
+
if threads: s.env["SHADOW_THREADS"] = str(threads)
|
| 32 |
+
s.ext = None
|
| 33 |
+
if archive:
|
| 34 |
+
tok, meta, _bank = load_archive(str(archive))
|
| 35 |
+
inv = Inverted(tok)
|
| 36 |
+
s.ext = _Extractor(tok, inv, model_ask=s.chat)
|
| 37 |
+
def _gen(s, ids, n=140, extra=()):
|
| 38 |
+
r = subprocess.run([s.kernel, s.model, s.table, " ".join(map(str, ids)), str(n), *extra],
|
| 39 |
+
capture_output=True, text=True, env=s.env)
|
| 40 |
+
out = [int(x) for x in r.stdout.split()]
|
| 41 |
+
for stop in (EOT, EOS):
|
| 42 |
+
if stop in out: out = out[:out.index(stop)]
|
| 43 |
+
return _dec(out).strip()
|
| 44 |
+
def chat(s, message, n=160, greedy=True, temp=0.25, topk=30, rep=1.15, seed=0):
|
| 45 |
+
ids = [BOS, SOT] + enc("user\n") + enc(message) + [EOT] + enc("\n") + [SOT] + enc("model\n")
|
| 46 |
+
extra = () if greedy else ("--temp", str(temp), "--topk", str(topk), "--rep", str(rep), "--seed", str(seed))
|
| 47 |
+
return s._gen(ids, n, extra)
|
| 48 |
+
def answer(s, question):
|
| 49 |
+
if s.ext is None: return s.chat(question)
|
| 50 |
+
a, how = s.ext.answer(question)
|
| 51 |
+
return a
|
shadow_runtime/__main__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse, sys
|
| 2 |
+
from . import Engine
|
| 3 |
+
ap = argparse.ArgumentParser()
|
| 4 |
+
ap.add_argument("--model", required=True); ap.add_argument("--table", required=True)
|
| 5 |
+
ap.add_argument("--archive"); ap.add_argument("--ask"); ap.add_argument("--chat", action="store_true")
|
| 6 |
+
a = ap.parse_args()
|
| 7 |
+
eng = Engine(a.model, a.table, archive=a.archive)
|
| 8 |
+
if a.ask: print(eng.answer(a.ask))
|
| 9 |
+
elif a.chat:
|
| 10 |
+
while True:
|
| 11 |
+
try: q = input("you> ")
|
| 12 |
+
except EOFError: break
|
| 13 |
+
if not q.strip(): continue
|
| 14 |
+
print("shadow>", eng.chat(q))
|
shadow_runtime/answer_engine.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Deterministic-extraction answer engine over the SHADOW archive pipeline.
|
| 2 |
+
|
| 3 |
+
Pipeline (each step deterministic or measured ~100%):
|
| 4 |
+
1. retrieve 16 chunks (lexical inverted index, 2 hops) -- measured 0.98-1.00 recall
|
| 5 |
+
2. locate the question's KEY inside the retrieved text by string match -- deterministic
|
| 6 |
+
(key = identifier-shaped tokens in the question; fallback = rarest word n-gram)
|
| 7 |
+
3. no match -> "NOT IN CONTEXT" -- deterministic abstain
|
| 8 |
+
4. extract the VALUE from the matched sentence: the value is the maximal
|
| 9 |
+
content span of the sentence that does NOT appear in the question,
|
| 10 |
+
type-filtered by the question word (how many->number, who->name, ...) -- deterministic
|
| 11 |
+
5. recency: several matches -> highest archive position wins -- deterministic
|
| 12 |
+
2-hop: matched sentence names another identifier and holds no value ->
|
| 13 |
+
re-query with that identifier and extract there -- deterministic + retrieval
|
| 14 |
+
count ("how many ... listed"): count distinct matches -- deterministic
|
| 15 |
+
6. anything without an identifier-style key (natural QA) -> the neural model answers as before.
|
| 16 |
+
|
| 17 |
+
The network still does chat and natural QA; archive fact lookup is a verified path. Disclosed as
|
| 18 |
+
"hybrid (neural + deterministic extraction)" wherever results are published.
|
| 19 |
+
"""
|
| 20 |
+
import re, sys, pathlib
|
| 21 |
+
import numpy as np
|
| 22 |
+
HERE = pathlib.Path(__file__).resolve().parent
|
| 23 |
+
sys.path.insert(0, str(HERE))
|
| 24 |
+
from retriever import enc, _dec, BLK, stop_ids
|
| 25 |
+
|
| 26 |
+
ABSTAIN = "NOT IN CONTEXT"
|
| 27 |
+
ID_RE = re.compile(r"\b((?=[A-Za-z0-9_-]*\d)[A-Za-z][A-Za-z0-9]*(?:[-_][A-Za-z0-9]+)+|[A-Z][a-z]+[A-Z][a-z]+[A-Za-z]*)\b")
|
| 28 |
+
SENT_SPLIT = re.compile(r"(?<=[.!?])\s+|\n+")
|
| 29 |
+
STOP_WORDS = set("""a an the is are was were be been being of in on at to from by for with under over into as and or if
|
| 30 |
+
that this these those it its his her their there here does do did has have had holds hold most recent statement if
|
| 31 |
+
several use please what which who whom whose when where how many much give state say tell answer question retrieved
|
| 32 |
+
passages above only reply exactly not them im""".split())
|
| 33 |
+
|
| 34 |
+
def question_keys(q):
|
| 35 |
+
ids = ID_RE.findall(q)
|
| 36 |
+
seen = set(); out = []
|
| 37 |
+
for x in ids:
|
| 38 |
+
if x not in seen: seen.add(x); out.append(x)
|
| 39 |
+
return out
|
| 40 |
+
|
| 41 |
+
def sentences_with(text, key):
|
| 42 |
+
"""(sentence, char_start) for every sentence in text containing key (word-boundary)."""
|
| 43 |
+
out = []
|
| 44 |
+
kre = re.compile(re.escape(key) + r"(?![A-Za-z0-9])")
|
| 45 |
+
# manual split with offsets
|
| 46 |
+
bounds = [0] + [mm.end() for mm in re.finditer(r"[.!?\n]", text)] + [len(text)]
|
| 47 |
+
for i in range(len(bounds) - 1):
|
| 48 |
+
s = text[bounds[i]:bounds[i + 1] + 1]
|
| 49 |
+
if kre.search(s): out.append((s.strip(), bounds[i]))
|
| 50 |
+
return out
|
| 51 |
+
|
| 52 |
+
def q_type(q):
|
| 53 |
+
ql = q.lower()
|
| 54 |
+
if "how many" in ql or "at how many" in ql: return "number"
|
| 55 |
+
if ql.startswith("who ") or " who " in ql or "assigned to" in ql or "sealed by" in ql or "signed by" in ql or "came from" in ql or "maintain" in ql or "bid" in ql: return "name"
|
| 56 |
+
if ql.startswith("when ") or "expire" in ql or "accessed" in ql or "date" in ql: return "date"
|
| 57 |
+
return "any"
|
| 58 |
+
|
| 59 |
+
def candidate_value(sent, q, want):
|
| 60 |
+
"""maximal non-question content spans of the sentence; choose by type then by position (later wins)."""
|
| 61 |
+
qw0 = {w.lower().strip(".,?!'\"()") for w in q.split()}
|
| 62 |
+
qwords = set(qw0)
|
| 63 |
+
for w in qw0: # morphological variants: weigh/weighs, expire/expires
|
| 64 |
+
qwords.add(w + "s"); qwords.add(w + "es"); qwords.add(w + "d"); qwords.add(w + "ed")
|
| 65 |
+
if w.endswith("s"): qwords.add(w[:-1])
|
| 66 |
+
toks = re.findall(r"0x[0-9a-f]+|[A-Za-z0-9]+(?:-[A-Za-z0-9]+)+|\d+\.\d+|[A-Za-z0-9][A-Za-z0-9']*|'[^']*'", sent)
|
| 67 |
+
spans = []; cur = []
|
| 68 |
+
for w in toks:
|
| 69 |
+
wl = w.lower().strip("'")
|
| 70 |
+
skip = (wl in qwords) or (wl in STOP_WORDS)
|
| 71 |
+
if skip:
|
| 72 |
+
if cur: spans.append(" ".join(cur)); cur = []
|
| 73 |
+
else: cur.append(w.strip("'"))
|
| 74 |
+
if cur: spans.append(" ".join(cur))
|
| 75 |
+
if not spans: return None
|
| 76 |
+
def is_num(s): return bool(re.fullmatch(r"\d+(?:\.\d+)?", s.replace(" ", "")))
|
| 77 |
+
def is_date(s): return bool(re.search(r"(January|February|March|April|May|June|July|August|September|October|November|December)", s))
|
| 78 |
+
def is_name(s): return bool(re.fullmatch(r"[A-Z][a-z]+ [A-Z][a-z]+", s))
|
| 79 |
+
if want == "number":
|
| 80 |
+
cs = [s for s in spans if is_num(s)]
|
| 81 |
+
if cs: return cs[-1]
|
| 82 |
+
if want == "date":
|
| 83 |
+
cs = [s for s in spans if is_date(s)]
|
| 84 |
+
if cs: return cs[-1]
|
| 85 |
+
if want == "name":
|
| 86 |
+
cs = [s for s in spans if is_name(s)]
|
| 87 |
+
if cs: return cs[-1]
|
| 88 |
+
return spans[-1]
|
| 89 |
+
|
| 90 |
+
class Engine:
|
| 91 |
+
def __init__(s, tok_or_ov, inv, model_ask=None, hops=2, k=16):
|
| 92 |
+
s.ov = tok_or_ov; s.inv = inv; s.model_ask = model_ask; s.hops = hops; s.k = k
|
| 93 |
+
s.is_overlay = hasattr(tok_or_ov, "block")
|
| 94 |
+
def _chunk_text(s, blocks):
|
| 95 |
+
"""decode retrieved (b,b+1) chunks; return [(pos_block, text)] ordered by pos."""
|
| 96 |
+
got = sorted({int(b) for b in blocks})
|
| 97 |
+
out = []
|
| 98 |
+
for b in got:
|
| 99 |
+
if s.is_overlay: t = _dec(s.ov.block(b) + s.ov.block(min(b + 1, s.ov.nb - 1)))
|
| 100 |
+
else: t = _dec(np.asarray(s.ov[b * BLK:(b + 2) * BLK], np.int64))
|
| 101 |
+
out.append((b, t))
|
| 102 |
+
return out
|
| 103 |
+
def _retrieve(s, q):
|
| 104 |
+
from retriever import Inverted
|
| 105 |
+
qi = enc(q)
|
| 106 |
+
idx = s.inv.topk_hops(qi, s.k, rounds=s.hops)[0] if not s.is_overlay else None
|
| 107 |
+
if s.is_overlay:
|
| 108 |
+
import bench_longctx as B
|
| 109 |
+
idx = B.ov_topk_hops(s.inv, s.ov, qi, s.k)
|
| 110 |
+
return idx
|
| 111 |
+
def answer(s, q):
|
| 112 |
+
keys = question_keys(q)
|
| 113 |
+
if not keys:
|
| 114 |
+
return (s.model_ask(q) if s.model_ask else ABSTAIN), "neural"
|
| 115 |
+
idx = s._retrieve(q); chunks = s._chunk_text(idx)
|
| 116 |
+
want = q_type(q)
|
| 117 |
+
key = keys[0]
|
| 118 |
+
# count task: count distinct "Member i of KEY" indices; members are numbered 1..n, so the answer is
|
| 119 |
+
# max(index) (robust to a missed block) cross-checked with the distinct count; wider retrieval (k=32).
|
| 120 |
+
if re.search(r"how many .* (listed|are there|in the archive)", q.lower()):
|
| 121 |
+
k_save = s.k; s.k = 32
|
| 122 |
+
idx = s._retrieve(q); chunks = s._chunk_text(idx); s.k = k_save
|
| 123 |
+
seen = set()
|
| 124 |
+
for _, t in chunks:
|
| 125 |
+
for sent, _o in sentences_with(t, key):
|
| 126 |
+
m = re.search(r"Member (\d+) of", sent)
|
| 127 |
+
if m: seen.add(int(m.group(1)))
|
| 128 |
+
if not seen: return ABSTAIN, "count"
|
| 129 |
+
return str(max(max(seen), len(seen))), "count"
|
| 130 |
+
matches = []
|
| 131 |
+
for pos, t in chunks:
|
| 132 |
+
for sent, off in sentences_with(t, key): matches.append((pos, off, sent))
|
| 133 |
+
if not matches: return ABSTAIN, "abstain"
|
| 134 |
+
matches.sort(key=lambda m: (m[0], m[1]))
|
| 135 |
+
# try direct extraction from the LATEST match backwards (recency). A candidate is a POINTER (alias
|
| 136 |
+
# record, e.g. "stored under reference K2") -- not a value -- only when the sentence carries an explicit
|
| 137 |
+
# reference cue; plain identifier-shaped values (serials "SN-...", CamelCase names) are legitimate answers.
|
| 138 |
+
PTR_CUE = re.compile(r"stored under|see reference|filed under|under reference", re.I)
|
| 139 |
+
for pos, off, sent in reversed(matches):
|
| 140 |
+
v = candidate_value(sent, q, want)
|
| 141 |
+
if not v: continue
|
| 142 |
+
if PTR_CUE.search(sent) and " " not in v and ID_RE.fullmatch(v): continue # alias pointer -> 2-hop
|
| 143 |
+
if " " in v and ID_RE.search(v): continue # multi-word span containing an identifier = junk -> 2-hop
|
| 144 |
+
return v, "extract"
|
| 145 |
+
# 2-hop: the matched sentence points at another identifier
|
| 146 |
+
for pos, off, sent in reversed(matches):
|
| 147 |
+
others = [x for x in ID_RE.findall(sent) if x != key and x not in q]
|
| 148 |
+
for k2 in others:
|
| 149 |
+
q2 = q.replace(key, k2)
|
| 150 |
+
idx2 = s._retrieve(q2); chunks2 = s._chunk_text(idx2)
|
| 151 |
+
m2 = []
|
| 152 |
+
for p2, t2 in chunks2:
|
| 153 |
+
for s2, o2 in sentences_with(t2, k2): m2.append((p2, o2, s2))
|
| 154 |
+
for p2, o2, s2 in reversed(sorted(m2)):
|
| 155 |
+
v = candidate_value(s2, q, want)
|
| 156 |
+
if not v or v == key: continue # circular: back to the original key
|
| 157 |
+
if re.search(r"stored under|see reference|filed under|under reference", s2, re.I) and " " not in v and ID_RE.fullmatch(v): continue
|
| 158 |
+
if " " in v and ID_RE.search(v): continue
|
| 159 |
+
return v, "2hop"
|
| 160 |
+
return ABSTAIN, "abstain"
|
shadow_runtime/prompt.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Prompt assembly for retrieval QA. Retrieved chunks are framed inside the user turn:
|
| 2 |
+
|
| 3 |
+
<bos><start_of_turn>user\n [ <unused0> pos=N \n <chunk tokens> <unused1> \n ] x K
|
| 4 |
+
\nUsing only the retrieved passages above, answer the question. If the answer is not in them, reply exactly "NOT IN CONTEXT".
|
| 5 |
+
Question: {q}<end_of_turn>\n<start_of_turn>model\n
|
| 6 |
+
cold mask = True on every token inside a chunk frame (open..close), False elsewhere (hot window).
|
| 7 |
+
Chunks = retrieved 64-token blocks expanded to (b, b+1) and merged when adjacent, ordered by archive position.
|
| 8 |
+
"""
|
| 9 |
+
import sys, json, pathlib, re, string
|
| 10 |
+
import numpy as np
|
| 11 |
+
HERE = pathlib.Path(__file__).resolve().parent; PKG = HERE.parent
|
| 12 |
+
sys.path.insert(0, str(HERE))
|
| 13 |
+
from retriever import enc, BLK
|
| 14 |
+
BOS, EOS, SOT, EOT = 2, 1, 8, 9; COPEN, CCLOSE = 6, 7
|
| 15 |
+
ABSTAIN = "NOT IN CONTEXT"
|
| 16 |
+
INSTR = f"\nUsing only the retrieved passages above, answer the question. If the answer is not in them, reply exactly \"{ABSTAIN}\".\nQuestion: "
|
| 17 |
+
_NL = None
|
| 18 |
+
def nl():
|
| 19 |
+
global _NL
|
| 20 |
+
if _NL is None: _NL = enc("\n")
|
| 21 |
+
return _NL
|
| 22 |
+
|
| 23 |
+
def chunks_from_blocks(tok, blocks, expand=1, max_chunks=16):
|
| 24 |
+
"""retrieved block ids -> list of (start_block, token list): (b, b+expand) spans, merged when overlapping, by position."""
|
| 25 |
+
spans = sorted({(int(b), int(b) + expand + 1) for b in blocks[:max_chunks]})
|
| 26 |
+
merged = []
|
| 27 |
+
for a, e in spans:
|
| 28 |
+
if merged and a <= merged[-1][1]: merged[-1][1] = max(merged[-1][1], e)
|
| 29 |
+
else: merged.append([a, e])
|
| 30 |
+
nb = len(tok) // BLK
|
| 31 |
+
return [(a, [int(t) for t in tok[a * BLK:min(e, nb) * BLK]]) for a, e in merged if a < nb]
|
| 32 |
+
|
| 33 |
+
def build_prompt(chunks, question):
|
| 34 |
+
"""chunks: [(pos_block, ids)] -> (ids, cold_mask) for the user turn + model turn opener."""
|
| 35 |
+
ids = [BOS, SOT] + enc("user\n"); cold = [False] * len(ids)
|
| 36 |
+
for pos, ct in chunks:
|
| 37 |
+
fr = [COPEN] + enc(f"pos={pos}\n"); body = ct; close = [CCLOSE] + nl()
|
| 38 |
+
seg = fr + body + close; ids += seg; cold += [True] * len(seg)
|
| 39 |
+
tail = enc(INSTR + question) + [EOT] + nl() + [SOT] + enc("model\n")
|
| 40 |
+
ids += tail; cold += [False] * len(tail)
|
| 41 |
+
return ids, cold
|
| 42 |
+
|
| 43 |
+
def normalize(s):
|
| 44 |
+
s = s.lower().strip(); s = "".join(ch for ch in s if ch not in set(string.punctuation)); s = re.sub(r"\b(a|an|the)\b", " ", s)
|
| 45 |
+
return " ".join(s.split())
|
| 46 |
+
def final_answer(text):
|
| 47 |
+
"""trace-aware: the answer is the text after the last '->' (quote-then-answer format), else the whole text."""
|
| 48 |
+
return text.rsplit("->", 1)[-1] if "->" in text else text
|
| 49 |
+
def reward(answer_text, gold, task):
|
| 50 |
+
"""verifiable reward: 1 if exact match after normalisation (numbers: exact digits), else 0. T5: must be the abstain marker."""
|
| 51 |
+
a = normalize(final_answer(answer_text)); g = normalize(str(gold))
|
| 52 |
+
if task == "T5" or g == normalize(ABSTAIN): return 1.0 if a == normalize(ABSTAIN) else 0.0
|
| 53 |
+
if a == normalize(ABSTAIN): return 0.0
|
| 54 |
+
if a == g: return 1.0
|
| 55 |
+
# short free-form gold (hotpot/squad): token-F1 >= 0.8 counts
|
| 56 |
+
at, gt = a.split(), g.split()
|
| 57 |
+
if not at or not gt: return 0.0
|
| 58 |
+
common_ = sum(min(at.count(w), gt.count(w)) for w in set(at));
|
| 59 |
+
if common_ == 0: return 0.0
|
| 60 |
+
p, r = common_ / len(at), common_ / len(gt); f1 = 2 * p * r / (p + r)
|
| 61 |
+
return 1.0 if f1 >= 0.8 else 0.0
|
shadow_runtime/retriever.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Archive retriever: exact lexical inverted index over 64-token blocks, with 2-hop follow-up.
|
| 2 |
+
|
| 3 |
+
An archive is a directory with tokens.u32 (uint32 token stream) and meta.json. Blocks of 64 tokens
|
| 4 |
+
are indexed by their content features (stop-filtered unigrams plus hashed bigrams and trigrams,
|
| 5 |
+
idf-weighted). A query returns the top-k blocks; a second hop follows identifiers named near the
|
| 6 |
+
match, so alias chains ("the record for K is stored under reference K2") resolve.
|
| 7 |
+
|
| 8 |
+
Also holds the tokenizer used by the runtime (sentencepiece + id remap).
|
| 9 |
+
"""
|
| 10 |
+
import os, sys, json, pathlib, hashlib, re
|
| 11 |
+
import numpy as np
|
| 12 |
+
HERE = pathlib.Path(__file__).resolve().parent; PKG = HERE.parent
|
| 13 |
+
BLK = 64
|
| 14 |
+
|
| 15 |
+
def load_archive(path):
|
| 16 |
+
tok = np.memmap(os.path.join(path, "tokens.u32"), np.uint32, "r")
|
| 17 |
+
meta = json.load(open(os.path.join(path, "meta.json")))
|
| 18 |
+
bp = os.path.join(path, "bank_valid.jsonl"); bp = bp if os.path.exists(bp) else os.path.join(path, "bank.jsonl") # validated bank if present
|
| 19 |
+
bank = [json.loads(l) for l in open(bp, encoding="utf-8")]
|
| 20 |
+
return tok, meta, bank
|
| 21 |
+
|
| 22 |
+
_STOP = None
|
| 23 |
+
def stop_ids(cache=None):
|
| 24 |
+
"""500 most frequent ids, fixed once from a held-out fineweb shard (deploy/stop500.npy)."""
|
| 25 |
+
global _STOP
|
| 26 |
+
if _STOP is None:
|
| 27 |
+
_STOP = np.load(PKG / "tokenizer" / "stop500.npy")
|
| 28 |
+
return _STOP
|
| 29 |
+
|
| 30 |
+
NBG = 1 << 20 # hashed bigram feature space
|
| 31 |
+
|
| 32 |
+
def _feats(ids):
|
| 33 |
+
"""content features of a token sequence: unique unigrams (stop-filtered) + hashed bigrams of ALL adjacent
|
| 34 |
+
tokens (digits/punctuation are frequent as unigrams but their sequences -- 'Vega-713' -- are the signal)."""
|
| 35 |
+
ids = np.asarray(ids, np.int64); st = stop_ids()
|
| 36 |
+
uni = np.unique(ids); uni = uni[(~np.isin(uni, st)) & (uni > 9)]
|
| 37 |
+
if len(ids) >= 2:
|
| 38 |
+
bg = (ids[:-1] * np.int64(1000003) + ids[1:] * np.int64(7919)) % NBG
|
| 39 |
+
bg = bg[(ids[:-1] > 9) & (ids[1:] > 9)]
|
| 40 |
+
if len(ids) >= 3: # trigrams too: 'Cygnus','-','9' / '9','2','2' make the key number discriminative
|
| 41 |
+
tg = (ids[:-2] * np.int64(1000003) + ids[1:-1] * np.int64(7919) + ids[2:] * np.int64(104729) + np.int64(17)) % NBG
|
| 42 |
+
bg = np.concatenate([bg, tg[(ids[:-2] > 9) & (ids[1:-1] > 9) & (ids[2:] > 9)]])
|
| 43 |
+
bg = np.unique(bg) + (1 << 17)
|
| 44 |
+
else: bg = np.zeros(0, np.int64)
|
| 45 |
+
return uni, bg
|
| 46 |
+
|
| 47 |
+
def _content(ids):
|
| 48 |
+
u, g = _feats(ids); return np.concatenate([u, g])
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# ---------------- exact lexical inverted index (BM25-style, unigram + bigram features) ----------------
|
| 52 |
+
# The sketch retrievers above lose the rare-key signal inside 64-token blocks; an inverted index keeps it exactly.
|
| 53 |
+
# Deploy cost at 100M tokens: ~60M postings (~250 MB) next to the 32 GB 1-bit KV archive; query = 3-10 posting lists.
|
| 54 |
+
class Inverted:
|
| 55 |
+
def __init__(s, tok, k1=1.2, b=0.75):
|
| 56 |
+
import scipy.sparse as sp
|
| 57 |
+
nb = len(tok) // BLK; rows = []; cols = []
|
| 58 |
+
for bi in range(nb):
|
| 59 |
+
u, g = _feats(tok[bi * BLK:(bi + 1) * BLK]); f = np.concatenate([u, g]); rows.append(f); cols.append(np.full(len(f), bi, np.int64))
|
| 60 |
+
r = np.concatenate(rows); c = np.concatenate(cols)
|
| 61 |
+
s.M = sp.csr_matrix((np.ones(len(r), np.float32), (r, c)), shape=((1 << 17) + NBG, nb)) # feature x block
|
| 62 |
+
df = np.asarray(s.M.sum(1)).ravel(); s.idf = np.log(1 + (nb - df + 0.5) / (df + 0.5)).astype(np.float32)
|
| 63 |
+
s.nb = nb; s.tok = tok
|
| 64 |
+
def topk(s, ids, k):
|
| 65 |
+
f = _content(ids)
|
| 66 |
+
if len(f) == 0: return np.arange(min(k, s.nb)), np.zeros(min(k, s.nb))
|
| 67 |
+
sc = np.asarray((s.idf[f][None, :] @ s.M[f]).todense()).ravel() if False else s.idf[f] @ s.M[f]
|
| 68 |
+
sc = np.asarray(sc).ravel()
|
| 69 |
+
idx = np.argpartition(-sc, min(k, s.nb - 1))[:k]; idx = idx[np.argsort(-sc[idx], kind="stable")]
|
| 70 |
+
return idx, sc[idx]
|
| 71 |
+
def topk_hops(s, ids, k, rounds=2, k1=8, idf_min=5.0, win=14):
|
| 72 |
+
"""multi-hop: round 1 = top-k1 direct hits (kept). Then EACH round-1 block gets its own follow-up query made of
|
| 73 |
+
the rare features within +-win tokens (over the stream) of where the question's tokens matched inside it --
|
| 74 |
+
'The record for K is stored under reference K2.' -> K2's n-grams -- and contributes its best new block.
|
| 75 |
+
Per-source queries (not a union) so one alias chain cannot be drowned by the others' noise."""
|
| 76 |
+
idx1, sc1 = s.topk(ids, k1); out = [int(i) for i in idx1]; qset = set(_content(ids).tolist()); qtext = _dec(np.asarray(ids, np.int64))
|
| 77 |
+
qtok = set(np.asarray(ids, np.int64).tolist()) - set(stop_ids().tolist()); src = out[:]
|
| 78 |
+
# template features = present in >=2 of the round-1 blocks ('stored under reference', 'access code for vault'):
|
| 79 |
+
# they would pull in every block of the same template; only features UNIQUE to a source block are followed.
|
| 80 |
+
cnt = {}
|
| 81 |
+
for bi in src:
|
| 82 |
+
for f in set(_content(np.asarray(s.tok[bi * BLK:(bi + 1) * BLK], np.int64)).tolist()): cnt[f] = cnt.get(f, 0) + 1
|
| 83 |
+
template = {f for f, c in cnt.items() if c >= 2}
|
| 84 |
+
for r in range(rounds - 1):
|
| 85 |
+
new = []
|
| 86 |
+
for bi in src:
|
| 87 |
+
if len(out) + len(new) >= k: break
|
| 88 |
+
blk = np.asarray(s.tok[bi * BLK:(bi + 1) * BLK], np.int64)
|
| 89 |
+
pos = [i for i, t in enumerate(blk) if t in qtok]
|
| 90 |
+
if not pos: continue
|
| 91 |
+
lo = max(0, bi * BLK + min(pos) - 8); hi = min(len(s.tok), bi * BLK + max(pos) + win + 1)
|
| 92 |
+
w = np.asarray(s.tok[lo:hi], np.int64)
|
| 93 |
+
# follow-up query: entity-like identifiers mentioned near the match (Name-123 style, the shape of archive
|
| 94 |
+
# keys) other than the question's own; fall back to the rare non-template features of the window.
|
| 95 |
+
ents = [e for e in set(re.findall(r"[A-Z][a-z]+-\d+", _dec(w))) if e not in qtext]
|
| 96 |
+
if ents: fb = np.unique(np.concatenate([_content(enc(" " + e)) for e in ents]))
|
| 97 |
+
else:
|
| 98 |
+
fb = _content(w); fb = fb[(s.idf[fb] >= idf_min) & ~np.isin(fb, list(qset | template))]
|
| 99 |
+
if len(fb) == 0: continue
|
| 100 |
+
sc = np.asarray(s.idf[fb] @ s.M[fb]).ravel(); sc[out + new] = -1e9
|
| 101 |
+
for j in np.argsort(-sc)[:2]: # best new block of this chain (skip the source's own neighbourhood)
|
| 102 |
+
j = int(j)
|
| 103 |
+
if abs(j - bi) > 1 and j not in new: new.append(j); break
|
| 104 |
+
out += new; src = new
|
| 105 |
+
if not new: break
|
| 106 |
+
return np.array(out[:k]), np.zeros(min(k, len(out)))
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
# ---------------- tokenizer (sentencepiece + id remap; expansion for out-of-subset ids computed lazily) ----
|
| 110 |
+
_sp = None; _o2n = None; _n2o = None; _exp_cache = {}
|
| 111 |
+
def _load_tok():
|
| 112 |
+
global _sp, _o2n, _n2o
|
| 113 |
+
if _sp is None:
|
| 114 |
+
import sentencepiece as spm
|
| 115 |
+
_sp = spm.SentencePieceProcessor(model_file=str(PKG / "tokenizer" / "tokenizer.model"))
|
| 116 |
+
_n2o = np.fromfile(PKG / "tokenizer" / "new2old.u32", np.uint32).astype(np.int64)
|
| 117 |
+
_o2n = np.full(262144, -1, np.int64); _o2n[_n2o] = np.arange(len(_n2o))
|
| 118 |
+
def _expand(old_id):
|
| 119 |
+
"""out-of-subset token -> its byte-fallback sequence in the 131k id space (lossless)."""
|
| 120 |
+
seq = _exp_cache.get(old_id)
|
| 121 |
+
if seq is None:
|
| 122 |
+
piece = _sp.id_to_piece(int(old_id)).replace("▁", " ")
|
| 123 |
+
raw = _sp.decode([int(old_id)]).encode("utf-8") if not piece.startswith("<0x") else bytes([int(piece[3:5], 16)])
|
| 124 |
+
seq = [int(_o2n[_sp.piece_to_id(f"<0x{b:02X}>")]) for b in raw]
|
| 125 |
+
_exp_cache[old_id] = seq
|
| 126 |
+
return seq
|
| 127 |
+
def enc(text):
|
| 128 |
+
_load_tok()
|
| 129 |
+
out = []
|
| 130 |
+
for i in _sp.encode(text):
|
| 131 |
+
n = _o2n[i]
|
| 132 |
+
if n >= 0: out.append(int(n))
|
| 133 |
+
else: out.extend(_expand(i))
|
| 134 |
+
return out
|
| 135 |
+
def _dec(ids):
|
| 136 |
+
_load_tok()
|
| 137 |
+
return _sp.decode(_n2o[np.asarray(ids, np.int64)].tolist())
|
tokenizer/new2old.u32
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d16fc82774e38698e919ebae21aa3969c52f3d846bd949dd3169452273b04635
|
| 3 |
+
size 524288
|
tokenizer/stop500.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:983d343784453f07b3f0f4cd2c16a685aeece4b4a5f6639e700a81aeafff2e38
|
| 3 |
+
size 4128
|
tokenizer/tokenizer.model
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1299c11d7cf632ef3b4e11937501358ada021bbdf7c47638d13c0ee982f2e79c
|
| 3 |
+
size 4689074
|