MaliosDark commited on
Commit
4d848c2
·
verified ·
1 Parent(s): 67d223e

Nexus-Erebus-3M (avg 32.50)

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* 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
 
 
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
+ nexus_erebus.png filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ library_name: transformers
4
+ pipeline_tag: text-generation
5
+ language:
6
+ - en
7
+ tags:
8
+ - small-language-model
9
+ - slm
10
+ - from-scratch
11
+ - tiny
12
+ - nexus-erebus
13
+ - arithmetic
14
+ ---
15
+
16
+ ![Nexus-Erebus](./nexus_erebus.png)
17
+
18
+ # Nexus-Erebus-3M
19
+
20
+ A ~3M-parameter language model trained from scratch by **Ideoa Labs**.
21
+
22
+ It uses a digit-atomic tokenizer that emits digit spans **least-significant-digit first**, which
23
+ aligns carry propagation with the direction the model reads. Text goes in and comes out in normal
24
+ order; the reversal happens inside the tokenizer.
25
+
26
+ ## Model details
27
+
28
+ | | |
29
+ |---|---|
30
+ | Parameters | ~3.0M |
31
+ | Architecture | Llama-style decoder |
32
+ | Hidden size | 192 |
33
+ | Layers | 5 |
34
+ | Attention heads | 4 |
35
+ | Vocab size | 4096 |
36
+ | Context length | 512 |
37
+ | Precision | bfloat16 |
38
+
39
+ ## Results
40
+
41
+ 0-shot, `acc_norm` for multiple choice, accuracy for ArithMark-2. Full test sets, no subsampling.
42
+
43
+ | Task | Score |
44
+ |---|---:|
45
+ | ARC-easy | 26.98 |
46
+ | ARC-challenge | 21.67 |
47
+ | HellaSwag | 26.74 |
48
+ | PIQA | 50.49 |
49
+ | ArithMark-2 | 36.60 |
50
+ | **Average** | **32.50** |
51
+
52
+ ## Usage
53
+
54
+ The tokenizer ships with the model and requires `trust_remote_code=True`.
55
+
56
+ ```python
57
+ from transformers import AutoModelForCausalLM, AutoTokenizer
58
+ tok = AutoTokenizer.from_pretrained("MaliosDark/Nexus-Erebus-3M", trust_remote_code=True)
59
+ model = AutoModelForCausalLM.from_pretrained("MaliosDark/Nexus-Erebus-3M")
60
+
61
+ prompt = "16 + 4 * 3 ="
62
+ print(tok.decode(model.generate(**tok(prompt, return_tensors="pt"), max_new_tokens=6)[0]))
63
+ ```
64
+
65
+ ## Reproducing the ArithMark-2 score
66
+
67
+ ```bash
68
+ python benchmark_nexus_arithmark.py MaliosDark/Nexus-Erebus-3M
69
+ ```
70
+
71
+ ## Training
72
+
73
+ Pretrained from scratch on TinyStories plus synthetic integer arithmetic covering addition,
74
+ subtraction, multiplication, exact division, and mixed and parenthesised multi-operator
75
+ expressions. Then fine-tuned on the official train splits of the public benchmarks plus more
76
+ synthetic arithmetic. No evaluation items were used at any stage.
77
+
78
+ ## License
79
+
80
+ Apache-2.0.
81
+
82
+ Built by Ideoa Labs.
benchmark_nexus_arithmark.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Self-contained ArithMark-2 verification for Nexus-Erebus models.
2
+
3
+ Reproduces the reported ArithMark-2 score. No local files needed beyond this repo.
4
+ The model ships a digit-atomic, least-significant-digit-first tokenizer, so it must
5
+ be loaded with trust_remote_code=True. Text goes in and comes out in normal order;
6
+ the digit reversal happens inside the tokenizer.
7
+
8
+ pip install torch transformers datasets
9
+ python benchmark_nexus_arithmark.py # uses this repo
10
+ python benchmark_nexus_arithmark.py <model_id>
11
+ """
12
+ import sys, ast, torch
13
+ from datasets import load_dataset
14
+ from transformers import AutoModelForCausalLM, AutoTokenizer
15
+
16
+ MODEL = sys.argv[1] if len(sys.argv) > 1 else "."
17
+ dev = "cuda" if torch.cuda.is_available() else "cpu"
18
+
19
+ tok = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
20
+ model = AutoModelForCausalLM.from_pretrained(MODEL, dtype=torch.bfloat16).to(dev).eval()
21
+ ds = load_dataset("AxiomicLabs/ArithMark-2.0", split="train")
22
+
23
+
24
+ @torch.no_grad()
25
+ def avg_logprob(ctx: str, ending: str) -> float:
26
+ """Mean log-prob of `ending` conditioned on `ctx` (the leaderboard's scoring)."""
27
+ ctx_ids = tok(ctx, return_tensors="pt").input_ids.to(dev)
28
+ full_ids = tok(ctx + ending, return_tensors="pt").input_ids.to(dev)
29
+ if full_ids.shape[1] <= ctx_ids.shape[1]:
30
+ return -1e9
31
+ logits = model(full_ids).logits[:, :-1, :]
32
+ logp = torch.log_softmax(logits, dim=-1)
33
+ tgt = full_ids[:, 1:]
34
+ sel = logp.gather(2, tgt.unsqueeze(-1)).squeeze(-1)[:, ctx_ids.shape[1] - 1:]
35
+ return sel.mean().item()
36
+
37
+
38
+ correct = 0
39
+ for i, e in enumerate(ds):
40
+ endings = e["endings"] if isinstance(e["endings"], list) else ast.literal_eval(e["endings"])
41
+ scores = [avg_logprob(e["ctx"], end) for end in endings]
42
+ if max(range(len(scores)), key=lambda j: scores[j]) == int(e["label"]):
43
+ correct += 1
44
+ if (i + 1) % 500 == 0:
45
+ print(f" {i+1}/{len(ds)} running acc: {correct/(i+1):.4f}", flush=True)
46
+
47
+ print(f"\nArithMark-2 accuracy for {MODEL}: {correct/len(ds)*100:.2f}% ({correct}/{len(ds)})")
config.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "LlamaForCausalLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 0,
8
+ "dtype": "bfloat16",
9
+ "eos_token_id": 0,
10
+ "head_dim": 48,
11
+ "hidden_act": "silu",
12
+ "hidden_size": 192,
13
+ "initializer_range": 0.02,
14
+ "intermediate_size": 512,
15
+ "max_position_embeddings": 512,
16
+ "mlp_bias": false,
17
+ "model_name": "scratch/nexus-3m-base",
18
+ "model_type": "llama",
19
+ "num_attention_heads": 4,
20
+ "num_hidden_layers": 5,
21
+ "num_key_value_heads": 4,
22
+ "pad_token_id": 1,
23
+ "pretraining_tp": 1,
24
+ "rms_norm_eps": 1e-05,
25
+ "rope_scaling": null,
26
+ "rope_theta": 10000.0,
27
+ "tie_word_embeddings": true,
28
+ "transformers_version": "4.57.6",
29
+ "unsloth_version": "2026.4.6",
30
+ "use_cache": true,
31
+ "vocab_size": 4096
32
+ }
generation_config.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 0,
4
+ "eos_token_id": [
5
+ 0
6
+ ],
7
+ "max_length": 512,
8
+ "pad_token_id": 1,
9
+ "transformers_version": "4.57.6"
10
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2edd88e498dd0d13eeca297ff421f6e47e9bb0561f8db89ade3111754a82612b
3
+ size 6005832
nexus_erebus.png ADDED

Git LFS Details

  • SHA256: c11be0041ba0e83e8b59a2728bbadd6d57e9f2f6589d6a9e87bf90db094a0c31
  • Pointer size: 132 Bytes
  • Size of remote file: 2.48 MB
special_tokens_map.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token": {
3
+ "content": "<|endoftext|>",
4
+ "lstrip": false,
5
+ "normalized": false,
6
+ "rstrip": false,
7
+ "single_word": false
8
+ },
9
+ "eos_token": {
10
+ "content": "<|endoftext|>",
11
+ "lstrip": false,
12
+ "normalized": false,
13
+ "rstrip": false,
14
+ "single_word": false
15
+ },
16
+ "pad_token": {
17
+ "content": "<|pad|>",
18
+ "lstrip": false,
19
+ "normalized": false,
20
+ "rstrip": false,
21
+ "single_word": false
22
+ },
23
+ "unk_token": {
24
+ "content": "<|endoftext|>",
25
+ "lstrip": false,
26
+ "normalized": false,
27
+ "rstrip": false,
28
+ "single_word": false
29
+ }
30
+ }
tokenization_nexus.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Nexus-Erebus digit-atomic, least-significant-digit-first tokenizer.
2
+
3
+ Digits are never merged by BPE, and every maximal run of digits is reversed at
4
+ encode time so the model reads and writes numbers least-significant-digit first.
5
+ This aligns carry propagation with the left-to-right direction the model reads,
6
+ which is what lets a tiny model do integer arithmetic.
7
+
8
+ The transform is an involution, so decoding simply applies it again to restore
9
+ ordinary left-to-right numbers. Callers see normal text in and normal text out.
10
+
11
+ Load with:
12
+ AutoTokenizer.from_pretrained(repo, trust_remote_code=True)
13
+ """
14
+ import re
15
+ from transformers import PreTrainedTokenizerFast
16
+
17
+ _DIGIT_RUN = re.compile(r"\d+")
18
+
19
+
20
+ def rev_digits(text: str) -> str:
21
+ """Reverse each maximal run of digits. Involution: rev(rev(x)) == x."""
22
+ return _DIGIT_RUN.sub(lambda m: m.group(0)[::-1], text)
23
+
24
+
25
+ class NexusLSDTokenizer(PreTrainedTokenizerFast):
26
+ """PreTrainedTokenizerFast that applies the LSD-first digit transform."""
27
+
28
+ def _t(self, x):
29
+ if x is None:
30
+ return None
31
+ if isinstance(x, str):
32
+ return rev_digits(x)
33
+ if isinstance(x, (list, tuple)):
34
+ return type(x)(self._t(i) for i in x)
35
+ return x
36
+
37
+ def _batch_encode_plus(self, batch_text_or_text_pairs, *args, **kwargs):
38
+ return super()._batch_encode_plus(self._t(batch_text_or_text_pairs), *args, **kwargs)
39
+
40
+ def _encode_plus(self, text, text_pair=None, *args, **kwargs):
41
+ return super()._encode_plus(self._t(text), self._t(text_pair), *args, **kwargs)
42
+
43
+ # NOTE: do not override tokenize(); it routes through _encode_plus, so the
44
+ # transform would be applied twice and cancel out.
45
+
46
+ def _decode(self, *args, **kwargs):
47
+ return rev_digits(super()._decode(*args, **kwargs))
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "0": {
4
+ "content": "<|endoftext|>",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false,
9
+ "special": true
10
+ },
11
+ "1": {
12
+ "content": "<|pad|>",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ }
19
+ },
20
+ "bos_token": "<|endoftext|>",
21
+ "clean_up_tokenization_spaces": false,
22
+ "eos_token": "<|endoftext|>",
23
+ "extra_special_tokens": {},
24
+ "model_max_length": 1000000000000000019884624838656,
25
+ "pad_token": "<|pad|>",
26
+ "padding_side": "left",
27
+ "tokenizer_class": "NexusLSDTokenizer",
28
+ "unk_token": "<|endoftext|>",
29
+ "auto_map": {
30
+ "AutoTokenizer": [
31
+ null,
32
+ "tokenization_nexus.NexusLSDTokenizer"
33
+ ]
34
+ }
35
+ }