yava-code commited on
Commit
7709bf3
·
verified ·
1 Parent(s): 1f61ab4

Publish Tessera-1B-Nano checkpoint

Browse files
README.md ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
3
+ pipeline_tag: text-generation
4
+ base_model: HuggingFaceTB/SmolLM2-135M
5
+ tags:
6
+ - next-concept-prediction
7
+ - conceptlm
8
+ - causal-lm
9
+ - smollm2
10
+ - tessera
11
+ license: apache-2.0
12
+ ---
13
+
14
+ # Tessera-1B-Nano
15
+
16
+ A Next Concept Prediction checkpoint from Paragon Intelligence Labs: an independent,
17
+ small-scale replication of ConceptLM on `HuggingFaceTB/SmolLM2-135M`. The model keeps
18
+ ordinary next-token generation and adds a causal, product-quantized concept path over
19
+ 4-token chunks: token states are pooled into concepts,
20
+ product-quantized, processed by causal concept blocks, and the predicted next concept
21
+ is fed back into the token decoder.
22
+
23
+ ## TL;DR
24
+
25
+ This checkpoint comes from a token-matched comparison against the unchanged
26
+ HuggingFaceTB/SmolLM2-135M backbone: both arms consumed the same tokens of the
27
+ same packed corpus, in the same order, from the same initialization, with no
28
+ restarts and no NaNs.
29
+
30
+ - **Token loss is neutral**: the matched baseline is within run noise (see the
31
+ whitepaper and `results/fineweb-edu/README.md` in the ncp-smol repository for
32
+ the exact numbers).
33
+ - **The concept channel is causally used**: zeroing predicted concept feedback
34
+ at inference costs +0.0205 nats of held-out NTP loss.
35
+ - **The codebook is rich**: no low-entropy shortcut, usage grows monotonically
36
+ over the run (growth curves are in the repository whitepaper).
37
+ - Full reading of the interventions: `docs/whitepaper.md` in the ncp-smol repo.
38
+
39
+ ## Evaluation
40
+
41
+ | Metric | Value |
42
+ | --- | ---: |
43
+ | Held-out NTP loss | 1.8751 |
44
+ | Held-out perplexity | 6.5213 |
45
+ | Zero feedback delta | 0.0205 |
46
+ | Shuffled feedback delta | 0.0000 |
47
+ | Codebook perplexity / usage | 2.4087 / 33.0% |
48
+ | Training tokens | 16,777,216 |
49
+ | Tracked compute estimate | $1.38 |
50
+
51
+ Intervention deltas are increases in held-out NTP loss relative to normal predicted concept
52
+ feedback, evaluated on identical batches.
53
+
54
+ ## Architecture
55
+
56
+ - chunk size: 4
57
+ - product code: 9 segments x 64 entries
58
+ - causal concept blocks: 2
59
+ - injection point: before token decoder block 2
60
+ - NCP target: next continuous concept
61
+ - loss: `L_ntp + 1 L_ncp + 1 L_vq`
62
+
63
+ This is a compact ConceptLM-style implementation, not an 8.9B NCP-ArchPreview replica. It
64
+ omits iterative residual coding, cross-scale residual connections, and the large-scale
65
+ training recipe.
66
+
67
+ ## Training data and provenance
68
+
69
+ The matched corpus and the training record are pinned in the ncp-smol repository:
70
+ packed-cache SHA256 hashes, the complete metric log, trainer state, and the raw
71
+ intervention evaluation JSON (also shipped in this repository as `eval.json` and
72
+ `metrics.jsonl`).
73
+
74
+ ## Loading
75
+
76
+ ```python
77
+ from transformers import AutoModelForCausalLM, AutoTokenizer
78
+
79
+ tokenizer = AutoTokenizer.from_pretrained("yava-code/Tessera-135M-Gate")
80
+ model = AutoModelForCausalLM.from_pretrained("yava-code/Tessera-135M-Gate")
81
+ ```
82
+
83
+ ## References
84
+
85
+ - ConceptLM: https://arxiv.org/abs/2602.08984
86
+ - NCP-ArchPreview: https://arxiv.org/abs/2609.10715
config.json ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "NcpSmolForCausalLM"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration.NcpSmolConfig",
7
+ "AutoModelForCausalLM": "modeling.NcpSmolForCausalLM"
8
+ },
9
+ "backbone_config": {
10
+ "_name_or_path": "HuggingFaceTB/SmolLM2-135M",
11
+ "add_cross_attention": false,
12
+ "architectures": [
13
+ "LlamaForCausalLM"
14
+ ],
15
+ "attention_bias": false,
16
+ "attention_dropout": 0.0,
17
+ "attn_implementation": null,
18
+ "bad_words_ids": null,
19
+ "begin_suppress_tokens": null,
20
+ "bos_token_id": 0,
21
+ "chunk_size_feed_forward": 0,
22
+ "cross_attention_hidden_size": null,
23
+ "decoder_start_token_id": null,
24
+ "diversity_penalty": 0.0,
25
+ "do_sample": false,
26
+ "dtype": "float32",
27
+ "early_stopping": false,
28
+ "encoder_no_repeat_ngram_size": 0,
29
+ "eos_token_id": 0,
30
+ "exponential_decay_length_penalty": null,
31
+ "finetuning_task": null,
32
+ "forced_bos_token_id": null,
33
+ "forced_eos_token_id": null,
34
+ "head_dim": 64,
35
+ "hidden_act": "silu",
36
+ "hidden_size": 576,
37
+ "id2label": {
38
+ "0": "LABEL_0",
39
+ "1": "LABEL_1"
40
+ },
41
+ "initializer_range": 0.041666666666666664,
42
+ "intermediate_size": 1536,
43
+ "is_decoder": false,
44
+ "is_encoder_decoder": false,
45
+ "is_llama_config": true,
46
+ "label2id": {
47
+ "LABEL_0": 0,
48
+ "LABEL_1": 1
49
+ },
50
+ "length_penalty": 1.0,
51
+ "max_length": 20,
52
+ "max_position_embeddings": 8192,
53
+ "min_length": 0,
54
+ "mlp_bias": false,
55
+ "model_type": "llama",
56
+ "no_repeat_ngram_size": 0,
57
+ "num_attention_heads": 9,
58
+ "num_beam_groups": 1,
59
+ "num_beams": 1,
60
+ "num_hidden_layers": 30,
61
+ "num_key_value_heads": 3,
62
+ "num_return_sequences": 1,
63
+ "output_attentions": false,
64
+ "output_hidden_states": false,
65
+ "output_scores": false,
66
+ "pad_token_id": null,
67
+ "prefix": null,
68
+ "pretraining_tp": 1,
69
+ "problem_type": null,
70
+ "pruned_heads": {},
71
+ "remove_invalid_values": false,
72
+ "repetition_penalty": 1.0,
73
+ "return_dict": true,
74
+ "return_dict_in_generate": false,
75
+ "rms_norm_eps": 1e-05,
76
+ "rope_interleaved": false,
77
+ "rope_scaling": null,
78
+ "rope_theta": 100000,
79
+ "sep_token_id": null,
80
+ "suppress_tokens": null,
81
+ "task_specific_params": null,
82
+ "temperature": 1.0,
83
+ "tf_legacy_loss": false,
84
+ "tie_encoder_decoder": false,
85
+ "tie_word_embeddings": true,
86
+ "tokenizer_class": null,
87
+ "top_k": 50,
88
+ "top_p": 1.0,
89
+ "torchscript": false,
90
+ "transformers_version": "4.57.6",
91
+ "typical_p": 1.0,
92
+ "use_bfloat16": false,
93
+ "use_cache": true,
94
+ "vocab_size": 49152
95
+ },
96
+ "base_model_name_or_path": "HuggingFaceTB/SmolLM2-135M",
97
+ "base_model_revision": "93efa2f097d58c2a74874c7e644dbc9b0cee75a2",
98
+ "chunk_size": 4,
99
+ "codebook_size": 64,
100
+ "concept_layers": 2,
101
+ "dtype": "float32",
102
+ "hidden_size": 576,
103
+ "insert_layer": 1,
104
+ "model_type": "ncp_smol",
105
+ "ncp_target": "continuous",
106
+ "ncp_weight": 1.0,
107
+ "num_attention_heads": 9,
108
+ "num_hidden_layers": 30,
109
+ "num_key_value_heads": 3,
110
+ "segments": 9,
111
+ "transformers_version": "4.57.6",
112
+ "use_cache": false,
113
+ "vocab_size": 49152,
114
+ "vq_weight": 1.0
115
+ }
configuration.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from transformers import LlamaConfig, PretrainedConfig
6
+
7
+
8
+ class NcpSmolConfig(PretrainedConfig):
9
+ model_type = "ncp_smol"
10
+
11
+ def __init__(
12
+ self,
13
+ backbone_config: dict[str, Any] | None = None,
14
+ base_model_name_or_path: str = "HuggingFaceTB/SmolLM2-360M",
15
+ base_model_revision: str | None = None,
16
+ chunk_size: int = 4,
17
+ segments: int | None = None,
18
+ codebook_size: int = 64,
19
+ concept_layers: int = 2,
20
+ insert_layer: int = 1,
21
+ ncp_target: str = "continuous",
22
+ ncp_weight: float = 1.0,
23
+ vq_weight: float = 1.0,
24
+ **kwargs: Any,
25
+ ) -> None:
26
+ super().__init__(**kwargs)
27
+ backbone_config = backbone_config or LlamaConfig().to_dict()
28
+ hidden_size = int(backbone_config["hidden_size"])
29
+ heads = int(backbone_config["num_attention_heads"])
30
+ segments = heads if segments is None else segments
31
+
32
+ if chunk_size < 2:
33
+ raise ValueError("chunk_size must be at least 2")
34
+ if hidden_size % segments:
35
+ raise ValueError("hidden_size must be divisible by segments")
36
+ if not 0 < insert_layer < int(backbone_config["num_hidden_layers"]):
37
+ raise ValueError("insert_layer must split the token backbone")
38
+ if concept_layers < 1:
39
+ raise ValueError("concept_layers must be positive")
40
+ if codebook_size < 2:
41
+ raise ValueError("codebook_size must be at least 2")
42
+ if ncp_target not in {"quantized", "continuous"}:
43
+ raise ValueError("ncp_target must be 'quantized' or 'continuous'")
44
+
45
+ self.backbone_config = backbone_config
46
+ self.hidden_size = hidden_size
47
+ self.vocab_size = int(backbone_config["vocab_size"])
48
+ self.num_hidden_layers = int(backbone_config["num_hidden_layers"])
49
+ self.num_attention_heads = heads
50
+ self.num_key_value_heads = int(backbone_config["num_key_value_heads"])
51
+ self.use_cache = False
52
+ self.base_model_name_or_path = base_model_name_or_path
53
+ self.base_model_revision = base_model_revision
54
+ self.chunk_size = chunk_size
55
+ self.segments = segments
56
+ self.codebook_size = codebook_size
57
+ self.concept_layers = concept_layers
58
+ self.insert_layer = insert_layer
59
+ self.ncp_target = ncp_target
60
+ self.ncp_weight = ncp_weight
61
+ self.vq_weight = vq_weight
62
+ self.architectures = ["NcpSmolForCausalLM"]
63
+ self.auto_map = {
64
+ "AutoConfig": "configuration.NcpSmolConfig",
65
+ "AutoModelForCausalLM": "modeling.NcpSmolForCausalLM",
66
+ }
67
+
68
+
69
+ NcpSmolConfig.register_for_auto_class()
data_metadata.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "dataset": "roneneldan/TinyStories",
3
+ "dtype": "uint32",
4
+ "eos_token_id": 0,
5
+ "revision": "f54c09fd23315a6f9c86f9dc80f725de7d8f9c64",
6
+ "sequence_length": 256,
7
+ "subset": null,
8
+ "tokenizer": "HuggingFaceTB/SmolLM2-135M",
9
+ "tokenizer_revision": "93efa2f097d58c2a74874c7e644dbc9b0cee75a2",
10
+ "train_sha256": "1b72ac9f0a05d841df30feb6feb8fbd7c38170d61528e78cfea3b04c491cc025",
11
+ "train_tokens": 1048576,
12
+ "validation_sha256": "6f0c6b7c842665a23d593f34c00ac7d7a9a9b3050a3d1d1592fac27b12be2462",
13
+ "validation_tokens": 1048576
14
+ }
eval.json ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "intervention": {
3
+ "shuffle_minus_predicted": 2.2351741790771484e-08,
4
+ "similar_shuffle_minus_predicted": -2.9802322387695312e-08,
5
+ "zero_minus_predicted": 0.020453326404094696
6
+ },
7
+ "predicted": {
8
+ "codebook_perplexity": 2.4087092876434326,
9
+ "codebook_usage": 0.3298611044883728,
10
+ "loss_by_chunk_offset": {
11
+ "0": 1.9171534776687622,
12
+ "1": 1.861592411994934,
13
+ "2": 1.8690117597579956,
14
+ "3": 1.8531707525253296
15
+ },
16
+ "ntp_loss": 1.8750678300857544,
17
+ "perplexity": 6.521261443051458
18
+ },
19
+ "shuffle": {
20
+ "codebook_perplexity": 2.4087092876434326,
21
+ "codebook_usage": 0.3298611044883728,
22
+ "loss_by_chunk_offset": {
23
+ "0": 1.9171533584594727,
24
+ "1": 1.8615922927856445,
25
+ "2": 1.869011640548706,
26
+ "3": 1.8531708717346191
27
+ },
28
+ "ntp_loss": 1.8750678524374962,
29
+ "perplexity": 6.521261588813012
30
+ },
31
+ "similar_shuffle": {
32
+ "codebook_perplexity": 2.4087092876434326,
33
+ "codebook_usage": 0.3298611044883728,
34
+ "loss_by_chunk_offset": {
35
+ "0": 1.9171534776687622,
36
+ "1": 1.861592411994934,
37
+ "2": 1.8690117597579956,
38
+ "3": 1.8531707525253296
39
+ },
40
+ "ntp_loss": 1.875067800283432,
41
+ "perplexity": 6.521261248702725
42
+ },
43
+ "zero": {
44
+ "codebook_perplexity": 2.4087092876434326,
45
+ "codebook_usage": 0.3298611044883728,
46
+ "loss_by_chunk_offset": {
47
+ "0": 1.9391287565231323,
48
+ "1": 1.896687388420105,
49
+ "2": 1.8889083862304688,
50
+ "3": 1.8580405712127686
51
+ },
52
+ "ntp_loss": 1.895521156489849,
53
+ "perplexity": 6.656016326994612
54
+ }
55
+ }
experiment.json ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "data": {
3
+ "cache_dir": "/vol/.cache/data/tinystories-overfit",
4
+ "cache_train_tokens": 1048576,
5
+ "dataset": "roneneldan/TinyStories",
6
+ "overfit": true,
7
+ "revision": "f54c09fd23315a6f9c86f9dc80f725de7d8f9c64",
8
+ "sequence_length": 256,
9
+ "shuffle_buffer": 2000,
10
+ "subset": null,
11
+ "text_column": "text",
12
+ "train_split": "train",
13
+ "train_tokens": 16777216,
14
+ "validation_split": "validation",
15
+ "validation_tokens": 1048576
16
+ },
17
+ "model": {
18
+ "base_model": "HuggingFaceTB/SmolLM2-135M",
19
+ "chunk_size": 4,
20
+ "codebook_size": 64,
21
+ "concept_layers": 2,
22
+ "dtype": "bfloat16",
23
+ "insert_layer": 1,
24
+ "ncp_target": "continuous",
25
+ "ncp_weight": 1.0,
26
+ "revision": "93efa2f097d58c2a74874c7e644dbc9b0cee75a2",
27
+ "segments": 9,
28
+ "vq_weight": 1.0
29
+ },
30
+ "optim": {
31
+ "beta1": 0.9,
32
+ "beta2": 0.95,
33
+ "eps": 1e-08,
34
+ "grad_accum_steps": 4,
35
+ "learning_rate": 3e-05,
36
+ "max_grad_norm": 1.0,
37
+ "micro_batch_size": 4,
38
+ "min_lr_ratio": 0.1,
39
+ "warmup_ratio": 0.01,
40
+ "weight_decay": 0.1
41
+ },
42
+ "run": {
43
+ "mode": "ncp",
44
+ "name": "tinystories-overfit",
45
+ "output_dir": "/vol/runs/tinystories-overfit",
46
+ "resume": true,
47
+ "seed": 17
48
+ },
49
+ "train": {
50
+ "compile": false,
51
+ "cost_overhead": 1.15,
52
+ "eval_batches": 16,
53
+ "eval_every_tokens": 4194304,
54
+ "gpu_hourly_usd": 0.8,
55
+ "log_every_steps": 10,
56
+ "max_wall_time_minutes": 180,
57
+ "run_budget_usd": 6.0,
58
+ "save_every_tokens": 8388608
59
+ }
60
+ }
generation_config.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "transformers_version": "4.57.6",
4
+ "use_cache": false
5
+ }
merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
metrics.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d9654b7148811d13babc7012aa9a9ae717164aca02125565145ea342044699b9
3
+ size 568493392
modeling.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Literal
5
+
6
+ import torch
7
+ import torch.nn.functional as F
8
+ from torch import Tensor, nn
9
+ from transformers import AutoModelForCausalLM, GenerationMixin, LlamaConfig, PreTrainedModel
10
+ from transformers.modeling_outputs import ModelOutput
11
+ from transformers.models.llama.modeling_llama import (
12
+ LlamaDecoderLayer,
13
+ LlamaForCausalLM,
14
+ LlamaRotaryEmbedding,
15
+ )
16
+
17
+ from .configuration import NcpSmolConfig
18
+ from .quantizer import ProductVectorQuantizer
19
+
20
+ ConceptMode = Literal["predicted", "zero", "shuffle"]
21
+
22
+
23
+ @dataclass
24
+ class NcpCausalLMOutput(ModelOutput):
25
+ loss: Tensor | None = None
26
+ logits: Tensor | None = None
27
+ ntp_loss: Tensor | None = None
28
+ ncp_loss: Tensor | None = None
29
+ vq_loss: Tensor | None = None
30
+ code_indices: Tensor | None = None
31
+
32
+
33
+ class NcpSmolForCausalLM(PreTrainedModel, GenerationMixin):
34
+ config_class = NcpSmolConfig
35
+ base_model_prefix = "backbone"
36
+ main_input_name = "input_ids"
37
+ _supports_cache_class = False
38
+ _no_split_modules = ["LlamaDecoderLayer"]
39
+ _tied_weights_keys = ["backbone.lm_head.weight"]
40
+
41
+ def __init__(self, config: NcpSmolConfig) -> None:
42
+ super().__init__(config)
43
+ backbone_config = LlamaConfig.from_dict(config.backbone_config)
44
+ self.backbone = LlamaForCausalLM(backbone_config)
45
+
46
+ concept_config = LlamaConfig.from_dict(config.backbone_config)
47
+ concept_config._attn_implementation = "eager"
48
+ self.concept_layers = nn.ModuleList(
49
+ [LlamaDecoderLayer(concept_config, i) for i in range(config.concept_layers)]
50
+ )
51
+ self.concept_rotary = LlamaRotaryEmbedding(concept_config)
52
+ self.quantizer = ProductVectorQuantizer(
53
+ hidden_size=config.hidden_size,
54
+ segments=config.segments,
55
+ codebook_size=config.codebook_size,
56
+ )
57
+ self.concept_head = nn.Linear(
58
+ config.hidden_size,
59
+ config.segments * config.codebook_size,
60
+ )
61
+ self._hook_state: dict[str, Any] = {}
62
+ self._hook = self.backbone.model.layers[config.insert_layer].register_forward_pre_hook(
63
+ self._inject_concepts,
64
+ with_kwargs=True,
65
+ )
66
+ self.post_init()
67
+ self.init_concept_from_backbone()
68
+ self.tie_weights()
69
+
70
+ @classmethod
71
+ def from_backbone(
72
+ cls,
73
+ model_name_or_path: str,
74
+ *,
75
+ revision: str | None = None,
76
+ chunk_size: int = 4,
77
+ segments: int | None = None,
78
+ codebook_size: int = 64,
79
+ concept_layers: int = 2,
80
+ insert_layer: int = 1,
81
+ ncp_target: str = "continuous",
82
+ ncp_weight: float = 1.0,
83
+ vq_weight: float = 1.0,
84
+ **load_kwargs: Any,
85
+ ) -> NcpSmolForCausalLM:
86
+ base = AutoModelForCausalLM.from_pretrained(
87
+ model_name_or_path,
88
+ revision=revision,
89
+ **load_kwargs,
90
+ )
91
+ if not isinstance(base, LlamaForCausalLM):
92
+ raise TypeError("ncp-smol currently requires a Llama-compatible backbone")
93
+ config = NcpSmolConfig(
94
+ backbone_config=base.config.to_dict(),
95
+ base_model_name_or_path=model_name_or_path,
96
+ base_model_revision=revision,
97
+ chunk_size=chunk_size,
98
+ segments=segments,
99
+ codebook_size=codebook_size,
100
+ concept_layers=concept_layers,
101
+ insert_layer=insert_layer,
102
+ ncp_target=ncp_target,
103
+ ncp_weight=ncp_weight,
104
+ vq_weight=vq_weight,
105
+ )
106
+ model = cls(config)
107
+ model.backbone.load_state_dict(base.state_dict())
108
+ model.init_concept_from_backbone()
109
+ model.tie_weights()
110
+ return model
111
+
112
+ def init_concept_from_backbone(self) -> None:
113
+ source = self.backbone.model.layers
114
+ for index, layer in enumerate(self.concept_layers):
115
+ layer.load_state_dict(source[index % len(source)].state_dict())
116
+
117
+ def get_input_embeddings(self) -> nn.Module:
118
+ return self.backbone.get_input_embeddings()
119
+
120
+ def set_input_embeddings(self, value: nn.Module) -> None:
121
+ self.backbone.set_input_embeddings(value)
122
+
123
+ def get_output_embeddings(self) -> nn.Module:
124
+ return self.backbone.get_output_embeddings()
125
+
126
+ def set_output_embeddings(self, value: nn.Module) -> None:
127
+ self.backbone.set_output_embeddings(value)
128
+
129
+ def tie_weights(self) -> None:
130
+ self.backbone.tie_weights()
131
+
132
+ def _concept_mask(self, valid: Tensor, dtype: torch.dtype) -> Tensor:
133
+ batch, length = valid.shape
134
+ mask = torch.full(
135
+ (length, length),
136
+ torch.finfo(dtype).min,
137
+ dtype=dtype,
138
+ device=valid.device,
139
+ ).triu(diagonal=1)
140
+ mask = mask.view(1, 1, length, length).expand(batch, 1, length, length).clone()
141
+ return mask.masked_fill(~valid[:, None, None, :], torch.finfo(dtype).min)
142
+
143
+ def _concept_path(
144
+ self,
145
+ hidden: Tensor,
146
+ attention_mask: Tensor | None,
147
+ mode: ConceptMode,
148
+ ) -> tuple[Tensor, dict[str, Tensor]]:
149
+ batch, length, width = hidden.shape
150
+ chunks = length // self.config.chunk_size
151
+ zero = hidden.sum() * 0
152
+ if chunks == 0:
153
+ return torch.zeros_like(hidden), {
154
+ "ncp_loss": zero,
155
+ "vq_loss": zero,
156
+ "code_indices": torch.empty(
157
+ batch, 0, self.config.segments, dtype=torch.long, device=hidden.device
158
+ ),
159
+ }
160
+
161
+ usable = chunks * self.config.chunk_size
162
+ concepts = (
163
+ hidden[:, :usable]
164
+ .reshape(
165
+ batch,
166
+ chunks,
167
+ self.config.chunk_size,
168
+ width,
169
+ )
170
+ .mean(dim=2)
171
+ )
172
+ if attention_mask is None:
173
+ valid = torch.ones(batch, chunks, dtype=torch.bool, device=hidden.device)
174
+ else:
175
+ valid = (
176
+ attention_mask[:, :usable]
177
+ .bool()
178
+ .reshape(batch, chunks, self.config.chunk_size)
179
+ .all(dim=2)
180
+ )
181
+
182
+ quantized = self.quantizer(concepts)
183
+ positions = torch.arange(chunks, device=hidden.device)
184
+ position_ids = (positions * self.config.chunk_size).unsqueeze(0).expand(batch, chunks)
185
+ concept_hidden = concepts
186
+ rotary = self.concept_rotary(concept_hidden, position_ids)
187
+ causal_mask = self._concept_mask(valid, concept_hidden.dtype)
188
+ for layer in self.concept_layers:
189
+ concept_hidden = layer(
190
+ concept_hidden,
191
+ attention_mask=causal_mask,
192
+ position_ids=position_ids,
193
+ cache_position=positions,
194
+ position_embeddings=rotary,
195
+ use_cache=False,
196
+ )
197
+
198
+ logits = self.concept_head(concept_hidden).view(
199
+ batch,
200
+ chunks,
201
+ self.config.segments,
202
+ self.config.codebook_size,
203
+ )
204
+ predicted = self.quantizer.expected(logits)
205
+
206
+ if chunks > 1 and valid[:, 1:].any():
207
+ target_mask = valid[:, 1:].unsqueeze(-1)
208
+ target = quantized.codes if self.config.ncp_target == "quantized" else concepts
209
+ squared = (predicted[:, :-1] - target[:, 1:].detach()).square()
210
+ ncp_loss = (squared * target_mask).sum() / (target_mask.sum().clamp_min(1) * width)
211
+ else:
212
+ ncp_loss = zero
213
+
214
+ if mode == "zero":
215
+ predicted = torch.zeros_like(predicted)
216
+ elif mode == "shuffle":
217
+ predicted = predicted.roll(shifts=1, dims=0)
218
+
219
+ feedback = self._align_feedback(predicted.detach(), length)
220
+ return feedback, {
221
+ "ncp_loss": ncp_loss,
222
+ "vq_loss": quantized.loss,
223
+ "code_indices": quantized.indices,
224
+ }
225
+
226
+ def _align_feedback(self, predicted: Tensor, length: int) -> Tensor:
227
+ batch, _, width = predicted.shape
228
+ prefix = torch.zeros(batch, 1, width, dtype=predicted.dtype, device=predicted.device)
229
+ repeated = torch.cat((prefix, predicted), dim=1).repeat_interleave(
230
+ self.config.chunk_size,
231
+ dim=1,
232
+ )
233
+ return repeated[:, 1 : length + 1]
234
+
235
+ def _inject_concepts(
236
+ self,
237
+ _module: nn.Module,
238
+ args: tuple[Any, ...],
239
+ kwargs: dict[str, Any],
240
+ ) -> tuple[tuple[Any, ...], dict[str, Any]]:
241
+ if not self._hook_state.get("active", False):
242
+ return args, kwargs
243
+
244
+ hidden = args[0] if args else kwargs["hidden_states"]
245
+ feedback, aux = self._concept_path(
246
+ hidden,
247
+ self._hook_state.get("attention_mask"),
248
+ self._hook_state["mode"],
249
+ )
250
+ self._hook_state["aux"] = aux
251
+ if args:
252
+ args = (hidden + feedback, *args[1:])
253
+ else:
254
+ kwargs["hidden_states"] = hidden + feedback
255
+ return args, kwargs
256
+
257
+ def forward(
258
+ self,
259
+ input_ids: Tensor | None = None,
260
+ attention_mask: Tensor | None = None,
261
+ labels: Tensor | None = None,
262
+ inputs_embeds: Tensor | None = None,
263
+ concept_mode: ConceptMode = "predicted",
264
+ use_cache: bool | None = None,
265
+ return_dict: bool = True,
266
+ **kwargs: Any,
267
+ ) -> NcpCausalLMOutput | tuple[Tensor, ...]:
268
+ if concept_mode not in {"predicted", "zero", "shuffle"}:
269
+ raise ValueError(f"unknown concept_mode: {concept_mode}")
270
+
271
+ self._hook_state = {
272
+ "active": True,
273
+ "attention_mask": attention_mask,
274
+ "mode": concept_mode,
275
+ "aux": None,
276
+ }
277
+ try:
278
+ outputs = self.backbone(
279
+ input_ids=input_ids,
280
+ attention_mask=attention_mask,
281
+ inputs_embeds=inputs_embeds,
282
+ labels=None,
283
+ use_cache=False,
284
+ return_dict=True,
285
+ **kwargs,
286
+ )
287
+ aux = self._hook_state["aux"]
288
+ finally:
289
+ self._hook_state["active"] = False
290
+ if aux is None:
291
+ raise RuntimeError("concept injection hook did not run")
292
+
293
+ ntp_loss = None
294
+ total_loss = None
295
+ if labels is not None:
296
+ shift_logits = outputs.logits[:, :-1].float().contiguous()
297
+ shift_labels = labels[:, 1:].contiguous()
298
+ ntp_loss = F.cross_entropy(
299
+ shift_logits.view(-1, shift_logits.size(-1)),
300
+ shift_labels.view(-1),
301
+ ignore_index=-100,
302
+ )
303
+ total_loss = (
304
+ ntp_loss
305
+ + self.config.ncp_weight * aux["ncp_loss"]
306
+ + self.config.vq_weight * aux["vq_loss"]
307
+ )
308
+
309
+ result = NcpCausalLMOutput(
310
+ loss=total_loss,
311
+ logits=outputs.logits,
312
+ ntp_loss=ntp_loss,
313
+ ncp_loss=aux["ncp_loss"],
314
+ vq_loss=aux["vq_loss"],
315
+ code_indices=aux["code_indices"],
316
+ )
317
+ if return_dict:
318
+ return result
319
+ return tuple(value for value in result.values() if value is not None)
320
+
321
+ def prepare_inputs_for_generation(
322
+ self,
323
+ input_ids: Tensor,
324
+ attention_mask: Tensor | None = None,
325
+ **_: Any,
326
+ ) -> dict[str, Any]:
327
+ return {
328
+ "input_ids": input_ids,
329
+ "attention_mask": attention_mask,
330
+ "use_cache": False,
331
+ }
332
+
333
+
334
+ NcpSmolForCausalLM.register_for_auto_class("AutoModelForCausalLM")
quantizer.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from torch import Tensor, nn
8
+
9
+
10
+ @dataclass
11
+ class QuantizerOutput:
12
+ codes: Tensor
13
+ indices: Tensor
14
+ loss: Tensor
15
+
16
+
17
+ class CodebookTransform(nn.Module):
18
+ def __init__(self, segments: int, dim: int) -> None:
19
+ super().__init__()
20
+ self.layers = nn.ModuleList(
21
+ [
22
+ nn.Sequential(
23
+ nn.Linear(dim, 2 * dim),
24
+ nn.ReLU(),
25
+ nn.Linear(2 * dim, dim),
26
+ )
27
+ for _ in range(segments)
28
+ ]
29
+ )
30
+
31
+ def forward(self, codebook: Tensor) -> Tensor:
32
+ return torch.stack(
33
+ [layer(codes) for layer, codes in zip(self.layers, codebook, strict=True)]
34
+ )
35
+
36
+
37
+ class ProductVectorQuantizer(nn.Module):
38
+ def __init__(self, hidden_size: int, segments: int, codebook_size: int) -> None:
39
+ super().__init__()
40
+ if hidden_size % segments:
41
+ raise ValueError("hidden_size must be divisible by segments")
42
+
43
+ self.hidden_size = hidden_size
44
+ self.segments = segments
45
+ self.codebook_size = codebook_size
46
+ self.segment_dim = hidden_size // segments
47
+ self.register_buffer(
48
+ "codebook",
49
+ torch.empty(segments, codebook_size, self.segment_dim),
50
+ )
51
+ self.transform = CodebookTransform(segments, self.segment_dim)
52
+ nn.init.normal_(self.codebook, std=0.02)
53
+
54
+ def transformed_codes(self) -> Tensor:
55
+ return self.transform(self.codebook)
56
+
57
+ def forward(self, concepts: Tensor) -> QuantizerOutput:
58
+ if concepts.shape[-1] != self.hidden_size:
59
+ raise ValueError(f"expected hidden size {self.hidden_size}, got {concepts.shape[-1]}")
60
+
61
+ shape = concepts.shape[:-1]
62
+ x = concepts.reshape(*shape, self.segments, self.segment_dim)
63
+ target = x.detach()
64
+ codes = self.transformed_codes()
65
+ distances = (
66
+ target.square().sum(dim=-1, keepdim=True)
67
+ + codes.square()
68
+ .sum(dim=-1)
69
+ .view(*([1] * len(shape)), self.segments, self.codebook_size)
70
+ - 2 * torch.einsum("...sd,snd->...sn", target, codes)
71
+ )
72
+ indices = distances.argmin(dim=-1)
73
+ gather_index = indices.unsqueeze(-1).expand(*indices.shape, self.segment_dim)
74
+ expanded = codes.view(*([1] * len(shape)), *codes.shape).expand(*shape, *codes.shape)
75
+ quantized = expanded.gather(-2, gather_index.unsqueeze(-2)).squeeze(-2)
76
+ loss = F.mse_loss(quantized, target)
77
+ return QuantizerOutput(quantized.reshape(*shape, self.hidden_size), indices, loss)
78
+
79
+ def expected(self, logits: Tensor) -> Tensor:
80
+ if logits.shape[-2:] != (self.segments, self.codebook_size):
81
+ raise ValueError("logits do not match the product codebook")
82
+ probs = logits.float().softmax(dim=-1).to(logits.dtype)
83
+ predicted = torch.einsum("...sn,snd->...sd", probs, self.transformed_codes())
84
+ return predicted.flatten(-2)
85
+
86
+ @torch.no_grad()
87
+ def usage(self, indices: Tensor) -> dict[str, float]:
88
+ flat = indices.reshape(-1, self.segments)
89
+ perplexities = []
90
+ active = []
91
+ for segment in range(self.segments):
92
+ counts = torch.bincount(flat[:, segment], minlength=self.codebook_size).float()
93
+ probs = counts / counts.sum().clamp_min(1)
94
+ entropy = -(probs * probs.clamp_min(1e-12).log()).sum()
95
+ perplexities.append(entropy.exp())
96
+ active.append((counts > 0).float().mean())
97
+ return {
98
+ "codebook_perplexity": torch.stack(perplexities).mean().item(),
99
+ "codebook_usage": torch.stack(active).mean().item(),
100
+ }
special_tokens_map.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "additional_special_tokens": [
3
+ "<|endoftext|>",
4
+ "<|im_start|>",
5
+ "<|im_end|>",
6
+ "<repo_name>",
7
+ "<reponame>",
8
+ "<file_sep>",
9
+ "<filename>",
10
+ "<gh_stars>",
11
+ "<issue_start>",
12
+ "<issue_comment>",
13
+ "<issue_closed>",
14
+ "<jupyter_start>",
15
+ "<jupyter_text>",
16
+ "<jupyter_code>",
17
+ "<jupyter_output>",
18
+ "<jupyter_script>",
19
+ "<empty_output>"
20
+ ],
21
+ "bos_token": {
22
+ "content": "<|endoftext|>",
23
+ "lstrip": false,
24
+ "normalized": false,
25
+ "rstrip": false,
26
+ "single_word": false
27
+ },
28
+ "eos_token": {
29
+ "content": "<|endoftext|>",
30
+ "lstrip": false,
31
+ "normalized": false,
32
+ "rstrip": false,
33
+ "single_word": false
34
+ },
35
+ "unk_token": {
36
+ "content": "<|endoftext|>",
37
+ "lstrip": false,
38
+ "normalized": false,
39
+ "rstrip": false,
40
+ "single_word": false
41
+ }
42
+ }
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "added_tokens_decoder": {
4
+ "0": {
5
+ "content": "<|endoftext|>",
6
+ "lstrip": false,
7
+ "normalized": false,
8
+ "rstrip": false,
9
+ "single_word": false,
10
+ "special": true
11
+ },
12
+ "1": {
13
+ "content": "<|im_start|>",
14
+ "lstrip": false,
15
+ "normalized": false,
16
+ "rstrip": false,
17
+ "single_word": false,
18
+ "special": true
19
+ },
20
+ "2": {
21
+ "content": "<|im_end|>",
22
+ "lstrip": false,
23
+ "normalized": false,
24
+ "rstrip": false,
25
+ "single_word": false,
26
+ "special": true
27
+ },
28
+ "3": {
29
+ "content": "<repo_name>",
30
+ "lstrip": false,
31
+ "normalized": false,
32
+ "rstrip": false,
33
+ "single_word": false,
34
+ "special": true
35
+ },
36
+ "4": {
37
+ "content": "<reponame>",
38
+ "lstrip": false,
39
+ "normalized": false,
40
+ "rstrip": false,
41
+ "single_word": false,
42
+ "special": true
43
+ },
44
+ "5": {
45
+ "content": "<file_sep>",
46
+ "lstrip": false,
47
+ "normalized": false,
48
+ "rstrip": false,
49
+ "single_word": false,
50
+ "special": true
51
+ },
52
+ "6": {
53
+ "content": "<filename>",
54
+ "lstrip": false,
55
+ "normalized": false,
56
+ "rstrip": false,
57
+ "single_word": false,
58
+ "special": true
59
+ },
60
+ "7": {
61
+ "content": "<gh_stars>",
62
+ "lstrip": false,
63
+ "normalized": false,
64
+ "rstrip": false,
65
+ "single_word": false,
66
+ "special": true
67
+ },
68
+ "8": {
69
+ "content": "<issue_start>",
70
+ "lstrip": false,
71
+ "normalized": false,
72
+ "rstrip": false,
73
+ "single_word": false,
74
+ "special": true
75
+ },
76
+ "9": {
77
+ "content": "<issue_comment>",
78
+ "lstrip": false,
79
+ "normalized": false,
80
+ "rstrip": false,
81
+ "single_word": false,
82
+ "special": true
83
+ },
84
+ "10": {
85
+ "content": "<issue_closed>",
86
+ "lstrip": false,
87
+ "normalized": false,
88
+ "rstrip": false,
89
+ "single_word": false,
90
+ "special": true
91
+ },
92
+ "11": {
93
+ "content": "<jupyter_start>",
94
+ "lstrip": false,
95
+ "normalized": false,
96
+ "rstrip": false,
97
+ "single_word": false,
98
+ "special": true
99
+ },
100
+ "12": {
101
+ "content": "<jupyter_text>",
102
+ "lstrip": false,
103
+ "normalized": false,
104
+ "rstrip": false,
105
+ "single_word": false,
106
+ "special": true
107
+ },
108
+ "13": {
109
+ "content": "<jupyter_code>",
110
+ "lstrip": false,
111
+ "normalized": false,
112
+ "rstrip": false,
113
+ "single_word": false,
114
+ "special": true
115
+ },
116
+ "14": {
117
+ "content": "<jupyter_output>",
118
+ "lstrip": false,
119
+ "normalized": false,
120
+ "rstrip": false,
121
+ "single_word": false,
122
+ "special": true
123
+ },
124
+ "15": {
125
+ "content": "<jupyter_script>",
126
+ "lstrip": false,
127
+ "normalized": false,
128
+ "rstrip": false,
129
+ "single_word": false,
130
+ "special": true
131
+ },
132
+ "16": {
133
+ "content": "<empty_output>",
134
+ "lstrip": false,
135
+ "normalized": false,
136
+ "rstrip": false,
137
+ "single_word": false,
138
+ "special": true
139
+ }
140
+ },
141
+ "additional_special_tokens": [
142
+ "<|endoftext|>",
143
+ "<|im_start|>",
144
+ "<|im_end|>",
145
+ "<repo_name>",
146
+ "<reponame>",
147
+ "<file_sep>",
148
+ "<filename>",
149
+ "<gh_stars>",
150
+ "<issue_start>",
151
+ "<issue_comment>",
152
+ "<issue_closed>",
153
+ "<jupyter_start>",
154
+ "<jupyter_text>",
155
+ "<jupyter_code>",
156
+ "<jupyter_output>",
157
+ "<jupyter_script>",
158
+ "<empty_output>"
159
+ ],
160
+ "bos_token": "<|endoftext|>",
161
+ "clean_up_tokenization_spaces": false,
162
+ "eos_token": "<|endoftext|>",
163
+ "extra_special_tokens": {},
164
+ "model_max_length": 8192,
165
+ "tokenizer_class": "GPT2Tokenizer",
166
+ "unk_token": "<|endoftext|>",
167
+ "vocab_size": 49152
168
+ }
trainer_state.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "batcher": {
3
+ "cursor": 4096,
4
+ "epoch": 15
5
+ },
6
+ "billable_seconds": 3455.0923419839996,
7
+ "step": 4096,
8
+ "tokens_seen": 16777216
9
+ }
vocab.json ADDED
The diff for this file is too large to render. See raw diff