Text Generation
Transformers
Safetensors
English
babylm
babylm-2026
strict-small
linear-attention
state-tracking
delta-rule
custom_code
Instructions to use SecludedCorner/bind2_0 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use SecludedCorner/bind2_0 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="SecludedCorner/bind2_0", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("SecludedCorner/bind2_0", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use SecludedCorner/bind2_0 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "SecludedCorner/bind2_0" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "SecludedCorner/bind2_0", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/SecludedCorner/bind2_0
- SGLang
How to use SecludedCorner/bind2_0 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "SecludedCorner/bind2_0" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "SecludedCorner/bind2_0", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "SecludedCorner/bind2_0" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "SecludedCorner/bind2_0", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use SecludedCorner/bind2_0 with Docker Model Runner:
docker model run hf.co/SecludedCorner/bind2_0
bind2_0: release package
Browse filesFrozen snapshot. Uploaded as a single commit per batch.
- release/.gitattributes +7 -0
- release/BUILD.md +35 -0
- release/LICENSE +21 -0
- release/MANIFEST.json +45 -0
- release/README.md +37 -0
- release/data/tokenizer.json +0 -0
- release/requirements.txt +5 -0
- release/run_train.py +73 -0
- release/src/modeling_bind2_0.py +118 -0
- release/src/train_bind2_0_babylm.py +71 -0
release/.gitattributes
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Release packages are byte-frozen snapshots: MANIFEST.json records a SHA-256 per artifact,
|
| 2 |
+
# and for some packages a preregistration pins a subset of them as well. Any end-of-line
|
| 3 |
+
# conversion on checkout silently changes those bytes and breaks every one of those hashes
|
| 4 |
+
# on the cloner's machine while still verifying on ours (this repo has core.autocrlf=true).
|
| 5 |
+
#
|
| 6 |
+
# -text disables all eol conversion. Do not remove.
|
| 7 |
+
* -text
|
release/BUILD.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Building `bind2_0`
|
| 2 |
+
|
| 3 |
+
**Verdict: BLOCKED** — **Does not run as-is.** The blockers are listed below and are real, not cosmetic. A launcher works around what can be worked around without editing frozen files; the rest is stated honestly rather than papered over.
|
| 4 |
+
|
| 5 |
+
## Dependencies
|
| 6 |
+
|
| 7 |
+
```
|
| 8 |
+
torch==2.12.1
|
| 9 |
+
numpy==2.4.6
|
| 10 |
+
tokenizers==0.22.2
|
| 11 |
+
flash-linear-attention==0.5.1 # CUDA-only (Triton kernels)
|
| 12 |
+
# on Windows also: triton-windows==3.7.1.post27
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
Python 3.11 is the version everything here was developed and smoke-tested against.
|
| 16 |
+
|
| 17 |
+
## Known blockers and portability notes
|
| 18 |
+
|
| 19 |
+
- train_bind2_0_babylm.py:20 reads os.environ['BABYLM_WORK'] as a bare subscript at module scope -- an uncaught KeyError with no default. The shipped launcher works around this without editing the file.
|
| 20 |
+
- dev='cuda' is hardcoded at :30 with no CPU path.
|
| 21 |
+
- flash-linear-attention is a hard dependency and its kernels are CUDA-only, so this package cannot run on CPU or on non-NVIDIA hardware at all.
|
| 22 |
+
|
| 23 |
+
## Launcher
|
| 24 |
+
|
| 25 |
+
`run_train.py` exists because this package's trainer hardcodes machine-specific inputs inside files that are frozen and must not be edited, so the launcher supplies them from outside instead.
|
| 26 |
+
|
| 27 |
+
**How it does that differs by package, so read `run_train.py`'s own docstring rather than this paragraph.** Some launchers import the frozen modules and rebind module-level constants before the entry point runs; at least one cannot, because its trainer reads an environment variable at import time and rebinding the attribute afterwards is already too late -- that launcher sets the variable first. Each launcher documents its own mechanism, and each was checked against the source it works around.
|
| 28 |
+
|
| 29 |
+
## Inputs you must supply
|
| 30 |
+
|
| 31 |
+
- `alaya-strict-small/tokens_u16.bin` (~32.6 MB) — status: REFERENCED-TOO-LARGE
|
| 32 |
+
|
| 33 |
+
## Verification
|
| 34 |
+
|
| 35 |
+
Every `.py` in `src/` is byte-identical to the working-tree file recorded in `MANIFEST.json` and compiles under Python 3.11 (`python -m compileall src`).
|
release/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Yulin Yang (SecludedCorner)
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
release/MANIFEST.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"package": "bind2_0",
|
| 3 |
+
"spec_title": "bind2_0 \u2014 delta-rule backbone with a forced bottleneck",
|
| 4 |
+
"generation": "bind2_0",
|
| 5 |
+
"buildable": "BLOCKED",
|
| 6 |
+
"entries": [
|
| 7 |
+
{
|
| 8 |
+
"source": "pilot-4060/train_bind2_0_babylm.py",
|
| 9 |
+
"kind": "code",
|
| 10 |
+
"sha256": "c325148dbecc8da56faf6f94b9494f1910cd437bc16d4c0b340eccdd279fb0c8",
|
| 11 |
+
"bytes": 4138,
|
| 12 |
+
"source_commit": "c3d39af06e8ac30ad3d8c930548dc03db4698ffb",
|
| 13 |
+
"status": "COPIED",
|
| 14 |
+
"dest": "src/train_bind2_0_babylm.py"
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"source": "pilot-4060/modeling_bind2_0.py",
|
| 18 |
+
"kind": "code",
|
| 19 |
+
"sha256": "1a950720257d560e02833b4c2e6c5017ee03a7585be6204c1b726930fd80e6ed",
|
| 20 |
+
"bytes": 5047,
|
| 21 |
+
"source_commit": "c3d39af06e8ac30ad3d8c930548dc03db4698ffb",
|
| 22 |
+
"status": "COPIED",
|
| 23 |
+
"dest": "src/modeling_bind2_0.py",
|
| 24 |
+
"source_sha256": "12ea39f0b958ae7ad304bbf4cc7a71afc984fe37c5805c97151084348a47f899",
|
| 25 |
+
"scrubbed": "1 text rule(s); AST verified: structure identical"
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"source": "alaya-strict-small/tokenizer.json",
|
| 29 |
+
"kind": "data",
|
| 30 |
+
"sha256": "6582b724cb5196ca72baf2a6b91c17be2631901e93feadce1fcd74c8d5e9089a",
|
| 31 |
+
"bytes": 1096025,
|
| 32 |
+
"source_commit": "c3d39af06e8ac30ad3d8c930548dc03db4698ffb",
|
| 33 |
+
"status": "COPIED",
|
| 34 |
+
"dest": "data/tokenizer.json"
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"source": "alaya-strict-small/tokens_u16.bin",
|
| 38 |
+
"kind": "external",
|
| 39 |
+
"sha256": "bce1a7681c6dc323ad0166219d63e8af01ae55a8c07d74d9774432d23b440c4e",
|
| 40 |
+
"bytes": 32592654,
|
| 41 |
+
"source_commit": "(untracked)",
|
| 42 |
+
"status": "REFERENCED-TOO-LARGE"
|
| 43 |
+
}
|
| 44 |
+
]
|
| 45 |
+
}
|
release/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# bind2_0 — delta-rule backbone with a forced bottleneck
|
| 2 |
+
|
| 3 |
+
Gated-delta-rule backbone; the parameter-matching reference for the bind2_1 arms.
|
| 4 |
+
|
| 5 |
+
> Research artifact. Not a competition entry.
|
| 6 |
+
|
| 7 |
+
## What this is
|
| 8 |
+
|
| 9 |
+
A **frozen snapshot** of the code for one model generation. It is not a living repository: it is never updated after release. Files shared with another generation are duplicated here on purpose, so that the code closure is complete and nothing has to be fetched from a sibling package.
|
| 10 |
+
|
| 11 |
+
**Self-contained in code, not in data.** Every module the code imports ships here. Large or non-redistributable inputs -- corpora, official evaluation sets, token streams -- do not, and are listed under "Inputs you must supply" in `BUILD.md` with how to obtain or regenerate each. Do not read the completeness of `src/` as a claim that the package runs with no further downloads.
|
| 12 |
+
|
| 13 |
+
## Contents
|
| 14 |
+
|
| 15 |
+
| | count |
|
| 16 |
+
|---|---|
|
| 17 |
+
| source files (`src/`) | 2 |
|
| 18 |
+
| shipped data (`data/`) | 1 |
|
| 19 |
+
| externally-obtained inputs | 1 |
|
| 20 |
+
|
| 21 |
+
`MANIFEST.json` records, for every artifact, its original path in the working tree, its SHA-256, its size, and the git commit it came from. That is the traceability record: it is what lets you prove which state of the code produced a published result.
|
| 22 |
+
|
| 23 |
+
## External inputs (not redistributed here)
|
| 24 |
+
|
| 25 |
+
- `alaya-strict-small/tokens_u16.bin` — regenerable from the bind1 package's tokenizer chain
|
| 26 |
+
|
| 27 |
+
## Building and running
|
| 28 |
+
|
| 29 |
+
See `BUILD.md`. It states plainly what works, what does not, and why.
|
| 30 |
+
|
| 31 |
+
## Availability of this package
|
| 32 |
+
|
| 33 |
+
If a model card, paper or dataset page points at this package, that pointer must resolve for whoever reads it. A public card that links private code makes a reproducibility promise it cannot keep — and a licence line on unreachable code claims a release that has not happened. Before publishing any pointer to this package, confirm the package is actually reachable by the audience that will read the pointer.
|
| 34 |
+
|
| 35 |
+
## Licence
|
| 36 |
+
|
| 37 |
+
MIT (see `LICENSE`). Code only — model weights and evaluation artifacts are distributed separately and carry their own terms.
|
release/data/tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
release/requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch==2.12.1
|
| 2 |
+
numpy==2.4.6
|
| 3 |
+
tokenizers==0.22.2
|
| 4 |
+
flash-linear-attention==0.5.1 # CUDA-only (Triton kernels)
|
| 5 |
+
# on Windows also: triton-windows==3.7.1.post27
|
release/run_train.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Portable launcher for the bind2_0 trainer.
|
| 3 |
+
|
| 4 |
+
WHY THIS EXISTS
|
| 5 |
+
---------------
|
| 6 |
+
`src/train_bind2_0_babylm.py` line 20 is:
|
| 7 |
+
|
| 8 |
+
WORK = os.environ["BABYLM_WORK"]
|
| 9 |
+
|
| 10 |
+
a bare subscript at module scope with no default, so merely IMPORTING the module raises an
|
| 11 |
+
uncaught KeyError when the variable is unset — the failure a newcomer hits first, with no message
|
| 12 |
+
explaining what to set. It is read at import time, not call time, so it must be set BEFORE the
|
| 13 |
+
import; rebinding the attribute afterwards is too late. This launcher sets it, checks the two
|
| 14 |
+
files it derives (tokenizer.json and tokens_u16.bin), and reports clearly if they are absent.
|
| 15 |
+
|
| 16 |
+
The trainer takes bare positional sys.argv (line 26), not argparse, so arguments are passed
|
| 17 |
+
through verbatim in order.
|
| 18 |
+
|
| 19 |
+
python run_train.py --work <dir> -- <positional args for train_bind2_0_babylm.py>
|
| 20 |
+
python run_train.py --work <dir> --check
|
| 21 |
+
|
| 22 |
+
HONEST LIMITATION, not worked around: line 30 hardcodes dev = "cuda" with no CPU path, and
|
| 23 |
+
modeling_bind2_0.py imports flash-linear-attention, whose kernels are CUDA-only. This package
|
| 24 |
+
cannot run on CPU or on non-NVIDIA hardware. A launcher cannot fix that; only editing the frozen
|
| 25 |
+
source could, and that is deliberately not done here.
|
| 26 |
+
"""
|
| 27 |
+
from __future__ import annotations
|
| 28 |
+
import argparse
|
| 29 |
+
import os
|
| 30 |
+
import sys
|
| 31 |
+
from pathlib import Path
|
| 32 |
+
|
| 33 |
+
HERE = Path(__file__).resolve().parent
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def main() -> int:
|
| 37 |
+
ap = argparse.ArgumentParser(description=__doc__,
|
| 38 |
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 39 |
+
ap.add_argument("--work", default=os.environ.get("BABYLM_WORK", str(HERE / "data")),
|
| 40 |
+
help="directory holding tokenizer.json and tokens_u16.bin")
|
| 41 |
+
ap.add_argument("--check", action="store_true", help="resolve inputs and exit")
|
| 42 |
+
args, passthrough = ap.parse_known_args()
|
| 43 |
+
if passthrough and passthrough[0] == "--":
|
| 44 |
+
passthrough = passthrough[1:]
|
| 45 |
+
|
| 46 |
+
work = Path(args.work)
|
| 47 |
+
os.environ["BABYLM_WORK"] = str(work) # MUST precede the import
|
| 48 |
+
sys.path.insert(0, str(HERE / "src"))
|
| 49 |
+
|
| 50 |
+
need = {"tokenizer.json": work / "tokenizer.json", "tokens_u16.bin": work / "tokens_u16.bin"}
|
| 51 |
+
missing = [k for k, v in need.items() if not v.exists()]
|
| 52 |
+
if args.check or missing:
|
| 53 |
+
print(f" BABYLM_WORK = {work}")
|
| 54 |
+
for k, v in need.items():
|
| 55 |
+
print(f" {k:16} {'OK ' if v.exists() else 'MISSING'} {v}")
|
| 56 |
+
if missing:
|
| 57 |
+
print("\nERROR: tokens_u16.bin is not redistributed inside this package (it is large and "
|
| 58 |
+
"regenerable). Build it with the bind1 package's tokenizer chain "
|
| 59 |
+
"(train_tokenizer.py then make_tokens.py), or point --work at a directory that "
|
| 60 |
+
"already has it. See BUILD.md.", file=sys.stderr)
|
| 61 |
+
return 2
|
| 62 |
+
if args.check:
|
| 63 |
+
print("\nAll inputs resolve. Re-run without --check to train.")
|
| 64 |
+
return 0
|
| 65 |
+
|
| 66 |
+
import train_bind2_0_babylm as T
|
| 67 |
+
sys.argv = ["train_bind2_0_babylm.py"] + passthrough
|
| 68 |
+
T.main()
|
| 69 |
+
return 0
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
raise SystemExit(main())
|
release/src/modeling_bind2_0.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""modeling_bind2_0.py -- bind2_0 architecture: forced-bottleneck delta-overwrite recurrence
|
| 2 |
+
(2026-07-12).
|
| 3 |
+
|
| 4 |
+
d=384, L=12, 3:1 GatedDeltaNet:chunked-attn (9 GDN + 3 chunked-attn), ~24M (matched to bind1 23.9M).
|
| 5 |
+
- GDN layer (fla GatedDeltaNet): delta-rule state S_t = a_t S_{t-1}(I - b_t k k^T) + b_t v k^T,
|
| 6 |
+
o_t = S_t q_t. Per-token state UPDATE carried along the sequence = the "apply an operation to
|
| 7 |
+
update state" mechanism (the verb-dynamics bind1 lacks; bind1 only labels roles).
|
| 8 |
+
- CHUNKED attention (forced bottleneck, P0): strict intra-chunk, NON-overlapping mask
|
| 9 |
+
M[t,s]=1 iff floor(t/C)==floor(s/C) and s<=t. Cross-chunk info can flow ONLY through S.
|
| 10 |
+
- Unified readout: LM head on the residual stream after all blocks (GDN o_t already added to
|
| 11 |
+
residual) -> every position, no hard routing.
|
| 12 |
+
"""
|
| 13 |
+
import math
|
| 14 |
+
import torch, torch.nn as nn, torch.nn.functional as F
|
| 15 |
+
from fla.layers import GatedDeltaNet
|
| 16 |
+
|
| 17 |
+
# --- self-contained generic primitives (bind2_0 is architecturally independent of bind1/mono;
|
| 18 |
+
# these are standard transformer building blocks, copied here so modeling_bind2_0.py stands alone) ---
|
| 19 |
+
|
| 20 |
+
def build_rope(T, D, device, base=10000.0):
|
| 21 |
+
inv = 1.0 / (base ** (torch.arange(0, D, 2, device=device).float() / D))
|
| 22 |
+
t = torch.arange(T, device=device).float()
|
| 23 |
+
f = torch.outer(t, inv)
|
| 24 |
+
emb = torch.cat([f, f], dim=-1)
|
| 25 |
+
return emb.cos(), emb.sin()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def rotate_half(x):
|
| 29 |
+
x1, x2 = x.chunk(2, dim=-1)
|
| 30 |
+
return torch.cat((-x2, x1), dim=-1)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def apply_rope(x, cos, sin):
|
| 34 |
+
cos = cos[None, None]; sin = sin[None, None]
|
| 35 |
+
return x * cos + rotate_half(x) * sin
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class SwiGLU(nn.Module):
|
| 39 |
+
def __init__(self, d, h):
|
| 40 |
+
super().__init__()
|
| 41 |
+
self.w1 = nn.Linear(d, h, bias=False); self.w3 = nn.Linear(d, h, bias=False); self.w2 = nn.Linear(h, d, bias=False)
|
| 42 |
+
|
| 43 |
+
def forward(self, x):
|
| 44 |
+
return self.w2(F.silu(self.w1(x)) * self.w3(x))
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _hdim(d):
|
| 48 |
+
return ((int(8 / 3 * d) + 63) // 64) * 64
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class ChunkedAttn(nn.Module):
|
| 52 |
+
"""Forced bottleneck: causal attention restricted to within non-overlapping chunks of size C."""
|
| 53 |
+
def __init__(self, d, nh, chunk):
|
| 54 |
+
super().__init__()
|
| 55 |
+
self.nh = nh; self.hd = d // nh; self.chunk = chunk
|
| 56 |
+
self.qkv = nn.Linear(d, 3 * d, bias=False); self.o = nn.Linear(d, d, bias=False)
|
| 57 |
+
|
| 58 |
+
def forward(self, x, cos, sin):
|
| 59 |
+
B, T, D = x.shape
|
| 60 |
+
qkv = self.qkv(x).view(B, T, 3, self.nh, self.hd).permute(2, 0, 3, 1, 4)
|
| 61 |
+
q, k, v = qkv[0], qkv[1], qkv[2]
|
| 62 |
+
q = apply_rope(q, cos, sin); k = apply_rope(k, cos, sin)
|
| 63 |
+
idx = torch.arange(T, device=x.device)
|
| 64 |
+
same = (idx[:, None] // self.chunk) == (idx[None, :] // self.chunk)
|
| 65 |
+
causal = idx[:, None] >= idx[None, :]
|
| 66 |
+
keep = same & causal
|
| 67 |
+
mask = torch.zeros(T, T, device=x.device, dtype=q.dtype).masked_fill(~keep, float("-inf"))
|
| 68 |
+
o = F.scaled_dot_product_attention(q, k, v, attn_mask=mask)
|
| 69 |
+
return self.o(o.transpose(1, 2).reshape(B, T, D))
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class GDNBlock(nn.Module):
|
| 73 |
+
def __init__(self, d, idx, mlp_hidden, gdn_heads=4, gdn_hd=72):
|
| 74 |
+
super().__init__()
|
| 75 |
+
self.n1 = nn.RMSNorm(d)
|
| 76 |
+
self.gdn = GatedDeltaNet(hidden_size=d, num_heads=gdn_heads, head_dim=gdn_hd, layer_idx=idx)
|
| 77 |
+
self.n2 = nn.RMSNorm(d); self.mlp = SwiGLU(d, mlp_hidden)
|
| 78 |
+
|
| 79 |
+
def forward(self, x):
|
| 80 |
+
m = self.gdn(self.n1(x))[0] # fla returns (output, attn, cache)
|
| 81 |
+
x = x + m
|
| 82 |
+
return x + self.mlp(self.n2(x))
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
class AttnBlock(nn.Module):
|
| 86 |
+
def __init__(self, d, nh, chunk, mlp_hidden):
|
| 87 |
+
super().__init__()
|
| 88 |
+
self.n1 = nn.RMSNorm(d); self.attn = ChunkedAttn(d, nh, chunk)
|
| 89 |
+
self.n2 = nn.RMSNorm(d); self.mlp = SwiGLU(d, mlp_hidden)
|
| 90 |
+
|
| 91 |
+
def forward(self, x, cos, sin):
|
| 92 |
+
x = x + self.attn(self.n1(x), cos, sin)
|
| 93 |
+
return x + self.mlp(self.n2(x))
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class Bind2_0LM(nn.Module):
|
| 97 |
+
def __init__(self, vocab, d=384, depth=12, nh=6, chunk=32, mlp_hidden=576, gdn_heads=4, gdn_hd=72):
|
| 98 |
+
super().__init__()
|
| 99 |
+
self.emb = nn.Embedding(vocab, d)
|
| 100 |
+
self.kinds = ["attn" if (i + 1) % 4 == 0 else "gdn" for i in range(depth)] # 3:1 GDN:attn
|
| 101 |
+
self.blocks = nn.ModuleList([
|
| 102 |
+
GDNBlock(d, i, mlp_hidden, gdn_heads, gdn_hd) if k == "gdn" else AttnBlock(d, nh, chunk, mlp_hidden)
|
| 103 |
+
for i, k in enumerate(self.kinds)])
|
| 104 |
+
self.nf = nn.RMSNorm(d); self.head = nn.Linear(d, vocab, bias=False)
|
| 105 |
+
self.head.weight = self.emb.weight
|
| 106 |
+
self.d = d; self.nh = nh; self.chunk = chunk
|
| 107 |
+
nl = depth
|
| 108 |
+
for m in self.modules():
|
| 109 |
+
if isinstance(m, nn.Linear):
|
| 110 |
+
nn.init.normal_(m.weight, std=0.02)
|
| 111 |
+
nn.init.normal_(self.emb.weight, std=0.02)
|
| 112 |
+
|
| 113 |
+
def forward(self, ids):
|
| 114 |
+
cos, sin = build_rope(ids.shape[1], self.d // self.nh, ids.device)
|
| 115 |
+
h = self.emb(ids)
|
| 116 |
+
for blk, k in zip(self.blocks, self.kinds):
|
| 117 |
+
h = blk(h) if k == "gdn" else blk(h, cos, sin)
|
| 118 |
+
return self.head(self.nf(h))
|
release/src/train_bind2_0_babylm.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""train_bind2_0_babylm.py -- train bind2_0 on the REAL BabyLM strict-small corpus (2026-07-12),
|
| 2 |
+
matched to bind1/mono's regime (SEQ256, batch, 150M tokens, flat-chunk of tokens_u16.bin).
|
| 3 |
+
|
| 4 |
+
CONTAMINATION-SAFE: each SEQ-block is a separate batch element, so the GDN state S starts from 0
|
| 5 |
+
per block (no cross-block carry) -- same data regime as the stateless bind1/mono flat-chunk runs.
|
| 6 |
+
Eval/inference forwards each probe independently (S from 0), so items never influence each other.
|
| 7 |
+
|
| 8 |
+
Usage: python train_bind2_0_babylm.py DIM DEPTH CHUNK SEQ BATCH TARGET TAG
|
| 9 |
+
Reads BABYLM_WORK/tokens_u16.bin (the real 16.3M-token BabyLM corpus). Writes {TAG}_final.pt +
|
| 10 |
+
train_{TAG}.json (arch bind2_0) + grid ckpts at BABYLM_CKPT_MARKS.
|
| 11 |
+
"""
|
| 12 |
+
import os, sys, time, json, random
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch, torch.nn.functional as F
|
| 15 |
+
from tokenizers import Tokenizer
|
| 16 |
+
|
| 17 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 18 |
+
from modeling_bind2_0 import Bind2_0LM
|
| 19 |
+
|
| 20 |
+
WORK = os.environ["BABYLM_WORK"]
|
| 21 |
+
TOKJSON = os.path.join(WORK, "tokenizer.json")
|
| 22 |
+
TOKBIN = os.path.join(WORK, "tokens_u16.bin")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def main():
|
| 26 |
+
a = sys.argv
|
| 27 |
+
DIM, DEPTH, CHUNK = int(a[1]), int(a[2]), int(a[3])
|
| 28 |
+
SEQ, BATCH, TARGET, TAG = int(a[4]), int(a[5]), int(a[6]), a[7]
|
| 29 |
+
MLPH = int(a[8]) if len(a) > 8 else 576 # 832 => ~27.4M (BabyLM scale, matches mono); 576 => 23.9M (bind1 scale)
|
| 30 |
+
dev = "cuda"
|
| 31 |
+
SEED = int(os.environ.get("BABYLM_SEED", "0")); random.seed(SEED); torch.manual_seed(SEED); np.random.seed(SEED)
|
| 32 |
+
tok = Tokenizer.from_file(TOKJSON); V = tok.get_vocab_size()
|
| 33 |
+
toks = np.fromfile(TOKBIN, dtype=np.uint16).astype(np.int64); data = torch.from_numpy(toks)
|
| 34 |
+
nblk = (len(toks) - 1) // SEQ
|
| 35 |
+
assert nblk > 0
|
| 36 |
+
|
| 37 |
+
model = Bind2_0LM(V, DIM, DEPTH, 6, chunk=CHUNK, mlp_hidden=MLPH).to(dev).train()
|
| 38 |
+
P = sum(p.numel() for p in model.parameters())
|
| 39 |
+
print(f"bind2_0 params={P/1e6:.2f}M | REAL-BabyLM flat-chunk seq{SEQ}xb{BATCH} chunk{CHUNK} (S from 0 per block)", flush=True)
|
| 40 |
+
opt = torch.optim.AdamW(model.parameters(), lr=6e-4, betas=(0.9, 0.95), weight_decay=0.01)
|
| 41 |
+
g = torch.Generator().manual_seed(SEED)
|
| 42 |
+
torch.cuda.reset_peak_memory_stats(); t0 = time.time()
|
| 43 |
+
MARKS = sorted(int(float(m) * 1e6) for m in os.environ.get("BABYLM_CKPT_MARKS", "").split(",") if m.strip())
|
| 44 |
+
mi = 0; seen = 0; step = 0
|
| 45 |
+
while seen < TARGET:
|
| 46 |
+
idx = torch.randint(0, nblk, (BATCH,), generator=g)
|
| 47 |
+
x = torch.stack([data[i * SEQ:(i + 1) * SEQ + 1] for i in idx]).to(dev)
|
| 48 |
+
with torch.autocast("cuda", dtype=torch.bfloat16):
|
| 49 |
+
logits = model(x[:, :-1])
|
| 50 |
+
ce = F.cross_entropy(logits.reshape(-1, V).float(), x[:, 1:].reshape(-1))
|
| 51 |
+
opt.zero_grad(set_to_none=True); ce.backward()
|
| 52 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0); opt.step()
|
| 53 |
+
step += 1; seen += BATCH * SEQ
|
| 54 |
+
while mi < len(MARKS) and seen >= MARKS[mi]:
|
| 55 |
+
os.makedirs(WORK + "/ckpt", exist_ok=True)
|
| 56 |
+
torch.save(model.state_dict(), WORK + f"/ckpt/{TAG}_g{MARKS[mi]/1e6:g}M.pt")
|
| 57 |
+
print(f" [ckpt-grid] {TAG}_g{MARKS[mi]/1e6:g}M.pt (seen={seen})", flush=True); mi += 1
|
| 58 |
+
if step == 1 or step % 200 == 0:
|
| 59 |
+
print(f"[{TAG}] st {step:6d} {seen/1e6:.1f}M ce {ce.item():.3f} ppl {np.exp(min(ce.item(),20)):.1f} | {round(seen/(time.time()-t0))} tok/s", flush=True)
|
| 60 |
+
dt = time.time() - t0
|
| 61 |
+
summ = {"tag": TAG, "arch": "bind2_0", "dim": DIM, "depth": DEPTH, "chunk": CHUNK, "mlp_hidden": MLPH,
|
| 62 |
+
"params_M": round(P / 1e6, 2), "vocab": V, "seq": SEQ, "batch": BATCH,
|
| 63 |
+
"peak_MiB": round(torch.cuda.max_memory_allocated() / 2**20), "tokens": seen,
|
| 64 |
+
"tok_s": round(seen / dt), "final_ce": round(ce.item(), 4), "ckpt_marks": os.environ.get("BABYLM_CKPT_MARKS", "")}
|
| 65 |
+
json.dump(summ, open(WORK + f"/train_{TAG}.json", "w", encoding="utf-8"), indent=2)
|
| 66 |
+
torch.save(model.state_dict(), WORK + f"/{TAG}_final.pt")
|
| 67 |
+
print(f"[{TAG}] DONE bind2_0 {P/1e6:.1f}M {seen/1e6:.1f}M ce {ce.item():.3f} ppl {np.exp(min(ce.item(),20)):.1f} {summ['tok_s']} tok/s peak {summ['peak_MiB']}MiB", flush=True)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
if __name__ == "__main__":
|
| 71 |
+
main()
|