QAT_to_Llama.cpp_GGUF_TQ2_0_JirackUltra_1b.md
11.9 KB · 218 lines · markdown Raw
1 # Instructions for Ternarization and Export to GGUF/TQ2_0 — JiRack Ultra 1B
2
3 Model: `/mnt/nfs_share/JiRackUltra_1b/`, class `JiRackTransformer` in `JiRackTernaryUltra_1b.py`
4 (standard dense transformer, `model_type: qwen2`; base per [DS-4]:
5 [`CMSManhattan/JiRackUltra_1b`](https://huggingface.co/CMSManhattan/JiRackUltra_1b)).
6
7 > **Naming note:** the repo says "1b" but the config is a **~1.5B-class**
8 > architecture (~1.78B params, [DS1.5-1] values). Treat "1b" as the product
9 > label; the checkpoint to load is `CMSManhattan/JiRackUltra_1b`.
10 >
11 > The code is structurally identical to the 7B/14B/32B files — same `BitLinear`,
12 > lambda warmup, RoPE half-split, `load_hf_state_dict` / `export_ternary_state_dict`,
13 > same 6 smoke tests. Two things are unique to this size: a **different vocab**
14 > (151936, not the siblings' 152064) and a **corrected tie-embeddings fact**
15 > (see below).
16
17 ## Config constants [DS1.5-1] — the source of truth
18
19 These match the published `config.json` in `CMSManhattan/JiRackUltra_1b` exactly
20 (`Qwen2ForCausalLM`, `tie_word_embeddings: false`, `bos 151646`, `eos 151643`).
21
22 | Constant | 1B value | 7B | 14B | 32B |
23 |---|---|---|---|---|
24 | `VOCAB_SIZE` | **151936** ⚠ | 152064 | 152064 | 152064 |
25 | `HIDDEN_SIZE` | 1536 | 3584 | 5120 | 5120 |
26 | `INTERMEDIATE_SIZE` | 8960 | 18944 | 13824 | 27648 |
27 | `NUM_LAYERS` | 28 (same as 7B!) | 28 | 48 | 64 |
28 | `NUM_HEADS` / `NUM_KV_HEADS` | 12 / 2 (6:1) | 28 / 4 | 40 / 8 | 40 / 8 |
29 | `HEAD_DIM` | 128 (q dim 1536, kv dim 256) | 128 | 128 | 128 |
30 | `ROPE_THETA` | 10,000 | 10,000 | 1,000,000 | 1,000,000 |
31 | `RMS_EPS` | 1e-6 | 1e-6 | 1e-5 | 1e-5 |
32 | `MAX_SEQ_LEN` | 4096 ([DS-6]: ckpt supports 131072) | 4096 | 4096 | 4096 |
33 | `ATTN_QKV_BIAS` | True ([DS-2]) | True | True | True |
34
35 **The vocab trap:** 151936 ≠ 152064. `token_emb` and `lm_head` are 151936 × 1536
36 here. If a sibling's conversion `config.json` (vocab 152064) is reused, you get a
37 shape mismatch at best, or a GGUF whose metadata disagrees with the tokenizer at
38 worst.
39
40 **Tokenizer to use:** [`CMSManhattan/JiRackPrecisionTokenizer`](https://huggingface.co/CMSManhattan/JiRackPrecisionTokenizer)
41 — the JiRack tokenizer for the JiRack Ultra family (qwen2-compatible; robotics,
42 routing, FIM and tool-call special tokens included). Its vocab (151,779) fits
43 inside the model's padded embedding matrix (151,936), so:
44 - **Do NOT call `model.resize_token_embeddings()`** — 151,779 < 151,936 would
45 *shrink* the matrix and corrupt the checkpoint. The extra special tokens land
46 in already-existing padded slots; no resize is needed for this model.
47 - Keep `VOCAB_SIZE = 151936` in the config and GGUF metadata (embedding size),
48 regardless of the tokenizer's 151,779 used entries.
49 - The JiRack special tokens have untrained embeddings until QAT/fine-tuning
50 includes them — they're reserved slots, not yet functional semantics.
51
52 **Tie-embeddings — corrected fact ([DS1.5-2]):** the older 7B/32B file comments
53 claimed this size ties embeddings. This file corrects that: the published
54 `CMSManhattan/JiRackUltra_1b` checkpoint **ships a real, untied `lm_head.weight`**
55 (`tie_word_embeddings: false` in its config.json); the fallback branch in
56 `load_hf_state_dict` exists only for third-party re-uploads that strip
57 `lm_head`. If the fallback triggers on the official checkpoint, something is
58 wrong with your shard merge — investigate, don't shrug.
59
60 ## Status template (fill in as you go)
61
62 ⏳ **To do:**
63 - HF → JiRack conversion via `load_hf_state_dict()` (per [DS-4]: load `CMSManhattan/JiRackUltra_1b` or merge its safetensors shards)
64 - Built-in smoke tests: `python JiRackTernaryUltra_1b.py` — all 6 pass
65 - GGUF conversion (pt → safetensors with HF names → GGUF, bf16)
66 - llama-cli smoke test ("What is the capital of France?" → "Paris")
67 - Baseline perplexity on wikitext-100k, c=2048, bf16, lambda=0.0
68 - QAT warmup lambda 0.0→1.0, materialization via `export_ternary_state_dict()`, TQ2_0 export, final perplexity
69
70 Checkpoint size expectation: ~3.5 GB at bf16 for ~1.78B params (~7 GB at fp32) —
71 matches the published `model.safetensors` (3.55 GB, bf16). At this size, fp32
72 loading is fine on any machine — no shard-by-shard gymnastics needed.
73
74 ## Quantization mechanism (identical to siblings, [FIX-1..5])
75
76 `BitLinear` with per-module persistent `lambda_` buffer:
77 - **Weights:** per-tensor absmean gamma (fp32 stats, eps=1e-5 clamp, [FIX-5]);
78 `clamp(round(w/gamma), -1, 1) * gamma` → exactly `(-gamma, 0, +gamma)` →
79 lossless TQ2_0 round-trip.
80 - **Activations:** per-token absmax int8 fake-quant ([FIX-2],[FIX-3]) — QAT only.
81 - **STE via lambda:** `w + lam * (w_quant - w).detach()`; `lambda_` persistent
82 ([FIX-4]) — survives resume, pollutes exports (Section 2).
83 - **Fast path:** eval with `lambda_ < 1e-6` → plain `F.linear`; the λ=0 baseline
84 PPL genuinely measures the unquantized model.
85
86 ---
87
88 ## 0. MTP Block — Not applicable
89
90 Standard `qwen2` attention; no MTP/Medusa draft layer exists or is needed.
91
92 ---
93
94 ## 1. What gets ternarized and what doesn't
95
96 **Ternarized — every `BitLinear`, by construction (7 per block × 28 blocks = 196).**
97 No `apply_bitlinear_patch` step; scope is fixed by the class:
98
99 | JiRack name | HF name (for GGUF export mapping) | Bias |
100 |---|---|---|
101 | `blocks.N.q_proj` | `model.layers.N.self_attn.q_proj` | yes ([DS-2]) |
102 | `blocks.N.k_proj` | `model.layers.N.self_attn.k_proj` | yes ([DS-2]) |
103 | `blocks.N.v_proj` | `model.layers.N.self_attn.v_proj` | yes ([DS-2]) |
104 | `blocks.N.out_proj` | `model.layers.N.self_attn.o_proj` | no |
105 | `blocks.N.ffn_w1` (gate) | `model.layers.N.mlp.gate_proj` | no |
106 | `blocks.N.ffn_w3` (up) | `model.layers.N.mlp.up_proj` | no |
107 | `blocks.N.ffn_w2` (down) | `model.layers.N.mlp.down_proj` | no |
108
109 Same renames as all siblings: `out_proj` ↔ `o_proj`, `ffn_w1/w3/w2` ↔
110 `gate/up/down`. `load_hf_state_dict()` is HF→JiRack; **GGUF export needs the
111 reverse**. [DS-4]: no RoPE permutation needed (half-split matches HF/llama.cpp).
112
113 **NOT ternarized — stays in full precision:**
114 - **QKV biases** ([DS-2]/[DS-5]): weight-only ternarization; preserved by
115 `export_ternary_state_dict()` ([FIX-9]). Verify `blk.*.attn_q.bias` /
116 `attn_k.bias` / `attn_v.bias` land in the GGUF as f32.
117 - All `RMSNorm` weights: `blocks.N.norm1`, `blocks.N.norm2`, `ln_f` — eps = **1e-6**.
118 - `token_emb` (151936 × 1536) and `lm_head` — **untied** ([DS1.5-2], verified).
119 - RoPE: `freqs_cos/sin` are non-persistent buffers — never exported.
120
121 **A size-specific caution on quality:** at this scale, `embed_tokens` + `lm_head`
122 (2 × 151936 × 1536 ≈ 0.47B params) are ~26% of the model — vs ~7% at 7B. Keeping
123 them full precision is therefore both more important for quality *and* a bigger
124 share of the final file size; don't expect the same compression ratio as the
125 larger siblings, and expect ternarization PPL degradation to be relatively larger
126 at this scale (small models have less redundancy to absorb quantization).
127
128 ## 2. Conversion pitfalls — CHECK ALL FIVE
129
130 1. **`lambda_` buffers pollute the state_dict:** 196 persistent `*.lambda_`
131 scalars. Strip before safetensors/GGUF. Clean tensor count: 28 × 12
132 (7 weights + 3 biases + 2 norms) + `token_emb` + `ln_f` + `lm_head`
133 = **339 tensors** — identical to the 7B count! Layer/tensor counts cannot
134 distinguish this model from the 7B; only shapes can (hidden 1536 vs 3584,
135 vocab 151936 vs 152064).
136 2. **`vocab_size` = 151936** — unique among the family. The conversion
137 `config.json` and the GGUF metadata must say 151936 (embedding size), never a
138 sibling's 152064. Tokenizer files: `CMSManhattan/JiRackPrecisionTokenizer`
139 (151,779 used entries — fits the padded matrix, no resize; see vocab-trap
140 section).
141 3. **θ = 10,000 and eps = 1e-6** — same values as the 7B, so 1B↔7B config reuse
142 is safe *for these two values only*; a 14B/32B config (θ=1M, eps=1e-5)
143 silently degrades generation and shifts every norm.
144 4. **RoPE table is 4096 positions** (`MAX_SEQ_LEN`; [DS-6]: ckpt nominally
145 supports 131072). Advertise conservative context in GGUF metadata unless you
146 deliberately raise it and re-verify.
147 5. **Materialization path:** `export_ternary_state_dict()` emits `{name}.codes`
148 (int8 ∈ {-1,0,1}) + `{name}.gamma`, not dense weights. Reconstruct
149 `w = codes.to(dtype) * gamma`, rename to HF names, write safetensors, convert
150 to GGUF, `llama-quantize` to TQ2_0 — lossless round-trip.
151
152 Baseline verification (before any ternarization):
153 - `python JiRackTernaryUltra_1b.py` — all 6 smoke tests pass.
154 - GGUF bf16 smoke test in llama-cli with chat template.
155 - Record your own baseline PPL on wikitext-100k (11 × 2048-token chunks, bf16,
156 λ=0.0). Expect a noticeably higher absolute PPL than the big siblings — that's
157 the model size, not a bug.
158
159 **Note for the future:** an abnormal PPL spike under TQ2_0 means a pipeline bug —
160 suspect QAT warmup, un-stripped `lambda_`, a mis-pinned exception tensor, a
161 sibling config value leaking in (θ/eps/vocab), or the wrong tokenizer.
162
163 ## 3. QAT training
164
165 1. Load the JiRack base weights and the JiRack tokenizer (per [DS-4]):
166 ```python
167 from transformers import AutoModelForCausalLM, AutoTokenizer
168 from JiRackTernaryUltra_1b import JiRackTransformer, JiRackConfig
169
170 tokenizer = AutoTokenizer.from_pretrained("CMSManhattan/JiRackPrecisionTokenizer")
171 assert len(tokenizer) <= 151936, "tokenizer must fit the padded embedding matrix"
172 # NOTE: no model.resize_token_embeddings() -- see the vocab-trap section.
173
174 model = JiRackTransformer(JiRackConfig()) # grad ckpt optional at this size
175 hf = AutoModelForCausalLM.from_pretrained(
176 "CMSManhattan/JiRackUltra_1b", torch_dtype=torch.bfloat16)
177 model.load_hf_state_dict(hf.state_dict())
178 model = model.to(torch.bfloat16)
179 ```
180 With `strict=True`, the official checkpoint must load with **zero** real
181 missing keys — if the tied-embedding fallback fires, your input is a stripped
182 re-upload, not the official `CMSManhattan/JiRackUltra_1b`.
183
184 **λ-start caveat:** if the published checkpoint is already post-QAT (weights
185 materialized in ternary form), a fresh 0→1 warmup partially "un-freezes" them
186 before re-clamping. That won't break training, but consider starting λ near
187 1.0 (or a short ramp) when continuing from an already-ternarized checkpoint;
188 a full 0→1 warmup is for a full-precision base.
189 2. Ramp lambda 0.0 → 1.0 with `model.set_lambda(x)` — one call covers all 196
190 BitLinears, no skip list. Continuity / STE gradients (weights *and* QKV
191 biases) / lambda serialization are covered by smoke tests #1/#3/#4.
192 This is also the size to prototype your λ schedule cheaply before spending
193 compute on 7B/14B/32B — the QAT dynamics transfer, the cost doesn't.
194 3. Save checkpoint (`lambda_` serializes with it).
195 4. Materialize: `export_ternary_state_dict()` → dense ternary weights →
196 safetensors (HF names) → GGUF → TQ2_0 (Section 2, item 5).
197
198 ---
199
200 ## Cross-model trap table — 1B / 7B / 14B / 32B (same code, different constants)
201
202 | Item | Ultra 1B | Ultra 7B | Ultra 14B | Ultra 32B |
203 |---|---|---|---|---|
204 | Base checkpoint | `CMSManhattan/JiRackUltra_1b` | JiRack Ultra 7B base | JiRack Ultra 14B base | JiRack Ultra 32B base |
205 | Arch family | qwen2 | qwen2 | qwen2 | qwen2 |
206 | `vocab_size` | **151936** | 152064 | 152064 | 152064 |
207 | `rope_theta` | 10,000 | 10,000 | 1,000,000 | 1,000,000 |
208 | `rms_norm_eps` | 1e-6 | 1e-6 | 1e-5 | 1e-5 |
209 | Layers | 28 | 28 | 48 | 64 |
210 | Hidden / FFN | 1536 / 8960 | 3584 / 18944 | 5120 / 13824 | 5120 / 27648 |
211 | GQA (q / kv) | 12 / 2 | 28 / 4 | 40 / 8 | 40 / 8 |
212 | BitLinear count | 196 | 196 | 336 | 448 |
213 | Clean tensor count | 339 | 339 | 579 | 771 |
214 | `lambda_` buffers to strip | 196 | 196 | 336 | 448 |
215 | bf16 checkpoint size | ~3.5 GB | ~15–16 GB | ~29–30 GB | ~65 GB |
216 | embed+lm_head share of params | ~26% | ~7% | ~5% | ~3% |
217 | File-specific quirks | "1b" naming vs ~1.5B-class config | — | — | — |
218 | Identical across all four | `BitLinear` math, λ warmup API, RoPE half-split, HF name mapping, QKV bias handling, `export_ternary_state_dict()`, smoke-test suite | ← | ← | ← |