File size: 7,787 Bytes
53d8e12
89bd596
 
53d8e12
89bd596
 
53d8e12
89bd596
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53d8e12
 
 
 
89bd596
53d8e12
 
 
89bd596
53d8e12
89bd596
 
 
53d8e12
 
 
89bd596
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53d8e12
89bd596
53d8e12
 
 
89bd596
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53d8e12
89bd596
 
 
 
53d8e12
89bd596
53d8e12
89bd596
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53d8e12
 
 
89bd596
53d8e12
89bd596
53d8e12
 
 
89bd596
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
---
language:
- en
license: apache-2.0
library_name: pytorch
pipeline_tag: text-generation
tags:
- stacklm
- multi-task
- lora-composition
- query-time-fit
- unlearning
- custom-architecture
- tiny
- research
model-index:
- name: stacklm-tiny
  results:
  - task:
      type: text-generation
      name: Multi-task composition
    dataset:
      name: Synthetic Markov chains
      type: synthetic-markov-chains
    metrics:
    - name: Query-fit vs oracle (ratio)
      type: query_oracle_ratio
      value: 1.005
    - name: Query-fit vs softmax (ratio)
      type: query_softmax_ratio
      value: 0.915
    - name: Anti-stack cancellation
      type: cancellation_ratio
      value: 0.091
    - name: Composition linearity
      type: linearity_log_diff
      value: 0.023
    - name: Per-sample vs joint alpha (ratio)
      type: per_sample_joint_ratio
      value: 0.928
---

# stacklm-tiny

A tiny transformer (~15K parameters) demonstrating **additive stack composition** for multi-task language modeling.

## Architecture

Frozen base transformer + N additive residual stacks on output logits. No router parameters. Alpha (stack mixing weights) is fit at query time on a small labeled example set.

The base is a 1-layer causal transformer with `d_model=32`, 4 heads, and a 16-token vocabulary. Each stack is a rank-8 low-rank projection from the base's hidden state to the output logit space. Stacks are trained sequentially: stack `i` fits the residual left by `base + stacks[0..i-1]` on task `i`.

At inference, no router is used. The mixing weights `α` are fit directly on a query batch by gradient descent on a cross-entropy objective.

## Validated claims

All numbers below are **mean ± std across 3 seeds** (seed 0, 1, 2). The model card template recommends reporting evaluation results in a structured format . Run `python stacklm_tiny.py --seed {0,1,2}` to reproduce.

| Claim | Mean ± std | Baseline | Interpretation |
|---|---|---|---|
| Query-fit α ≈ oracle | **1.005 ± 0.002** | 20 labeled examples | Query-fit matches oracle within 0.5% |
| Query-fit vs softmax | **0.915 ± 0.009** | Trained router | Query-fit beats softmax by 8.5% |
| Anti-stack cancellation | **0.091 ± 0.029** | log-space ratio | ~91% cancellation |
| Composition linearity | **0.023 ± 0.006** | [1,1] vs [2,0] | 2.3% deviation from exact |
| Per-sample vs joint α | **0.928 ± 0.003** | 7.2% improvement | Per-sample α is consistently better |

### What each claim means

**1. Query-fit ≈ oracle.** Fitting α on 20 labeled examples produces perplexity within 0.5% of fitting α on the full test set. The mixing weights do not need a trained router; they can be solved at query time.

**2. Query-fit vs softmax.** A softmax router (a task classifier trained on 6,000 examples) is 8.5% worse than query-fit. The softmax router learns to predict a task ID from input tokens; query-fit learns the optimal mixing weights directly from labeled examples. The latter is more robust because it doesn't require the input to carry a task-identifying signal.

**3. Anti-stack cancellation.** Training a stack to fit `−stack_0` reduces the composed output's divergence from the base by ~91%. This is partial cancellation, not exact erasure. For "unlearning" in the regulatory sense, this is not sufficient. For soft revocation or A/B testing, it is.

**4. Composition linearity.** `[1,1]` weights on (stack, copy-stack) approximates `[2,0]` weights on (stack, zero) to within 2.3% in log-space. The raw stack logits are exactly linear; the deviation comes from the softmax, which is nonlinear. Composition is approximately linear, not exactly.

**5. Per-sample α.** Fitting a separate α vector for each input sample beats fitting a single α vector for the whole batch by 7.2%, reproducibly across all 3 seeds. This is the strongest single result: the optimal mixing weights genuinely vary per input, not just per task.

## Usage

```python
from stacklm_tiny import StackLM, StackLMConfig, TrainConfig
import torch

# Load the model
model = StackLM.from_pretrained("./stacklm-tiny")
tcfg = TrainConfig()

# Fit alpha on 20 labeled examples
X_adapt, Y_adapt = get_adapt_examples()  # shape (20, seq_len-1)
alpha = model.fit_alpha_joint(X_adapt, Y_adapt, model.n_active, tcfg)

# Inference
logits = model(X_test, alpha=alpha)

# Per-sample refinement (better quality, same 20 examples)
alpha_ps = model.fit_alpha_per_sample(X_test, Y_test, model.n_active, tcfg)
logits = model(X_test, alpha=alpha_ps)
```

### Revocation

```python
# Train an anti-stack to cancel stack 0
anti_idx = model.train_anti_stack(task, target_idx=0, tcfg=tcfg)

# Apply both: base + stack0 + anti ≈ base (91% cancellation)
alpha = torch.tensor([1., 1.])
out = model(X, alpha=alpha, n=2)
```

## Training data

Synthetic Markov chains over a 16-token vocabulary. Five chains: one for the base model (task 0) and four for the stacks (tasks 1–4). Chains share 70% of their transition structure and have 30% task-specific structure. Each task has a distinct initial-token bias to give the router a weak input signal.

This is a **demonstration dataset**, not a language modeling benchmark. It is designed to make the composition mechanics observable, not to test language quality.

## Training procedure

- **Base**: 400 steps, AdamW, lr 1e-3, weight decay 0.05, early stopping on validation loss
- **Stacks**: 300 steps each, Adam, lr 3e-3, fit on the residual left by prior stacks
- **α fit**: 80 Adam steps on a length-N parameter, lr 5e-2
- **Per-sample α**: 20 Adam steps on a (B, N) parameter

Hardware: CPU only. Total training time: ~90–106 seconds per seed.

## Evaluation

Evaluated on 300 held-out sequences per task. The primary metric is perplexity (exponentiated cross-entropy on the task's test split). All claims are measured with the same code that produces the numbers. No cherry-picking.

## Limitations

- **Tiny scale.** 15K parameters, 16-token vocabulary, 20-token sequences. Nothing about this model generalizes to real LLMs without re-testing.
- **Synthetic tasks.** Markov chains, not natural language. Composition mechanics may behave differently on real text.
- **No causal masking bug check.** The base uses a standard causal mask; the composition is applied to output logits post-attention.
- **Single architecture.** Only one base shape tested. Different depths or attention patterns may produce different composition behavior.
- **Cancellation is partial.** ~91% is not 100%. Do not rely on this for data erasure.
- **Per-sample α is stochastic.** The 7.2% improvement is consistent across seeds but the mechanism is not understood. It may be an artifact of the specific synthetic setup.

## What this is / is not

**Is:** a proof-of-concept demonstrating that (a) multi-task can be additive rather than routed, (b) mixing weights are optimally fitted at query time, (c) adapters can be partially revoked by adding a cancellation stack, (d) per-sample mixing weights beat batch-level weights.

**Is not:** a useful language model, a benchmark result, or evidence that these claims hold at scale. For real use cases, the same architecture would apply to LoRA stacks on a real base model, and all claims would need re-testing.

## Files

- `pytorch_model.bin` — base + stack weights
- `config.json` — architecture config and `n_active` (number of trained stacks)
- `stacklm_tiny.py` — model code (self-contained)
- `README.md` — this file

## Citation

```bibtex
@misc{stacklm-tiny,
  title={stacklm-tiny: Additive Stack Composition for Multi-Task Language Modeling},
  author={zeechimp},
  year={2026},
  howpublished={\url{https://huggingface.co/zeechimp/stacklm-tiny}}
}
```

## Contact

For questions or to report issues, open a discussion on the model repository.