jarrelscy commited on
Commit
1a8651a
·
verified ·
1 Parent(s): 9518fe6

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. README.md +152 -0
  2. chat_template.jinja +129 -0
  3. code/analyze_mm_routing.py +59 -0
  4. code/build_calib.py +59 -0
  5. code/build_calib_v3.py +232 -0
  6. code/build_gate_ckpt.py +185 -0
  7. code/build_heldout.py +96 -0
  8. code/build_hybrid.py +448 -0
  9. code/build_mm_manifest.py +56 -0
  10. code/converge.py +153 -0
  11. code/converge_all.py +49 -0
  12. code/converge_w2.py +238 -0
  13. code/converge_w2_all.py +56 -0
  14. code/encode_all.py +54 -0
  15. code/gpqa40.py +35 -0
  16. code/ink_aqlm.py +108 -0
  17. code/ink_common.py +121 -0
  18. code/merge_acts.py +43 -0
  19. code/ppl_eval.py +51 -0
  20. code/pv.py +256 -0
  21. code/pv_all.py +66 -0
  22. code/pv_all_v3.py +64 -0
  23. code/pv_gen_check.py +33 -0
  24. code/pv_generalize.py +143 -0
  25. code/pv_v3.py +206 -0
  26. code/reap_merge.py +47 -0
  27. code/reap_worker.py +128 -0
  28. code/run_capture_acts.py +22 -0
  29. code/run_capture_acts_heldout.py +23 -0
  30. code/run_capture_acts_mm.py +89 -0
  31. code/run_capture_acts_v3.py +34 -0
  32. code/run_route_stats.py +30 -0
  33. code/solve_tier.py +102 -0
  34. code/v1_vs_v2_heldout.py +33 -0
  35. code/validate_gate_ckpt.py +94 -0
  36. code/validate_hybrid.py +138 -0
  37. config.json +140 -0
  38. hf_quant_config.json +1049 -0
  39. model-00022-of-00045.safetensors +3 -0
  40. model-00023-of-00045.safetensors +3 -0
  41. model-00027-of-00045.safetensors +3 -0
  42. model-00031-of-00045.safetensors +3 -0
  43. model-00032-of-00045.safetensors +3 -0
  44. model-00041-of-00045.safetensors +3 -0
  45. model-00043-of-00045.safetensors +3 -0
  46. model.safetensors.index.json +0 -0
  47. processor_config.json +46 -0
  48. special_tokens_map.json +22 -0
  49. tiktoken/tokenizer.model +3 -0
  50. tokenizer_config.json +508 -0
README.md ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ base_model:
4
+ - thinkingmachines/Inkling
5
+ tags:
6
+ - moe
7
+ - nvfp4
8
+ - aqlm
9
+ - quantized
10
+ - inkling
11
+ ---
12
+
13
+ # Inkling-512k-NVFP4-AQLM-hybrid
14
+
15
+ A memory-optimized **hybrid-quantized** build of
16
+ [thinkingmachines/Inkling](https://huggingface.co/thinkingmachines/Inkling)
17
+ (MoE, 256 routed experts/layer, 6 active). Each MoE layer's experts are split
18
+ into a small **hot** set kept at NVFP4 (~4 bpw) and a large **cold** set stored
19
+ as **AQLM 2-bit**. This is the **512k tier**: **14.8% of expert slots are
20
+ NVFP4 (hot)** and **85.2% are AQLM-2bit (cold)**, averaging **~2.66 bits
21
+ per weight** over the routed experts. It targets serving at **512k context on
22
+ 4x96GB GPUs**.
23
+
24
+ > **This checkpoint needs a CUSTOM FUSED MoE KERNEL** that dispatches NVFP4 hot
25
+ > experts and AQLM cold experts within the same fused grouped-GEMM. **It does
26
+ > NOT load on stock vLLM / Transformers.** The on-disk format and dequant math
27
+ > are fully specified below so a kernel can consume it directly.
28
+
29
+ ## Serving (512k on 4x96GB)
30
+ This is the **higher-quality 14.8%-hot** tier: **~358 GB weights** + KV @512k
31
+ (fp8, 11 global layers) ~11.5 GB leaves only **~14.6 GB** runtime headroom on a
32
+ 384 GB box. That is **too tight for CUDA graphs + MTP at 512k** — serve with
33
+ **`--enforce-eager`** at 512k (with MTP), or use **256k** context (KV ~5.8 GB,
34
+ ~20 GB headroom) for non-eager. For non-eager + MTP + CUDA-graphs at full 512k,
35
+ use the **10%-hot** tier instead (~346 GB weights, ~27 GB headroom). TP4,
36
+ `--kv-cache-dtype fp8`, modest `--max-num-seqs`.
37
+
38
+ ## Quality
39
+ Held-out perplexity (teacher-forced, 5-domain mix disjoint from calibration;
40
+ gate-proxy = cold experts requantized to NVFP4 so it loads on stock vLLM, whose
41
+ per-expert error tracks the true hybrid). Lower is better.
42
+
43
+ | domain | Inkling-NVFP4 | prior hybrid | **this build (v3, 14.8% hot)** |
44
+ |---|---|---|---|
45
+ | code | 2.222 | 2.250 | 2.244 |
46
+ | agentic | 1.930 | 1.938 | 1.934 |
47
+ | instruction | 4.674 | 5.169 | **4.892** |
48
+ | medical | 5.464 | 5.964 | **5.817** |
49
+ | prose | 5.399 | 6.691 | **6.563** |
50
+ | **aggregate** | **3.406** | **3.678** | **3.608** |
51
+
52
+ This **v3** build re-tiers and re-calibrates on a broad **multimodal** corpus
53
+ (code / agentic / instruction / medical / prose, plus image + audio routing).
54
+ It beats the prior text-only-calibrated hybrid by **−1.9% aggregate**, with the
55
+ gains concentrated where the prior build was starved (**instruction −5.4%,
56
+ medical −2.5%, prose −1.9%**); code/agentic are unchanged within noise. This
57
+ 14.8% tier edges the 10%-hot tier (−1.1%) at the cost of the tighter 512k fit
58
+ noted above.
59
+
60
+ ## Provenance
61
+ - **Hot experts (NVFP4):** sliced verbatim from the ModelOpt NVFP4 base
62
+ (`thinkingmachines/Inkling-NVFP4`), no requantization.
63
+ - **Cold experts (AQLM):** quantized from the **Inkling BF16 teacher**.
64
+ Both `w13` and `w2` cold books are **activation-aware converged** then
65
+ **output-preserving PV-tuned** against the BF16 teacher on **multimodal**
66
+ calibration activations (text + image + audio routing), with a disjoint
67
+ held-out early-stop.
68
+ - **Assignment** of hot vs cold per layer is in
69
+ `hf_quant_config.json['aqlm_hybrid']['aqlm_layer_books']` and reproduced from
70
+ the multimodal REAP importance study (`assign_512k_v3.json`, 14.8% hot). Reproduction scripts are in
71
+ `code/`.
72
+
73
+ ## Layout summary
74
+ - **63 NVFP4-packed MoE layers** carry the hybrid expert format below
75
+ (NVFP4 hot slabs + AQLM cold).
76
+ - **1 bf16-special MoE layer(s)** (layer **2**) have **no NVFP4
77
+ base**, so they use the SAME hybrid format EXCEPT the hot slabs are stored as
78
+ **bf16** (`experts.{proj}_hot_bf16`, no `.scale`/`.scale2`); their cold experts
79
+ are AQLM like every other layer. The fused uncompressed bf16 `experts.w13_weight`
80
+ / `experts.w2_weight` are **dropped**. Layers 0-1 are dense MLP (not experts).
81
+ - All **non-expert** tensors (attention, embeddings, norms, `shared_experts`,
82
+ `gate`, vision, audio, MTP) are copied verbatim from the NVFP4 base.
83
+ - Total hot slots 2417 / 16384 expert slots.
84
+
85
+ ## On-disk tensor format (per NVFP4-packed MoE layer L)
86
+ `prefix = model.llm.layers.{L}.mlp.experts`. Routed experts (256 total) are
87
+ partitioned into `n_hot` hot + `n_cold` cold. For each projection
88
+ `proj in {w13, w2}` (w13 = fused gate|up, out=6144, in=6144; w2 = down,
89
+ out=6144, in=3072):
90
+
91
+ **Hot — NVFP4-packed layers (ModelOpt, `hot_format=nvfp4`):**
92
+ | tensor | dtype | shape |
93
+ |---|---|---|
94
+ | `{prefix}.{proj}_hot_weight` | uint8 | `[n_hot, out, in/2]` (2 fp4 codes/byte) |
95
+ | `{prefix}.{proj}_hot_weight.scale` | float8_e4m3 | `[n_hot, out, in/16]` (per-block-16 scale) |
96
+ | `{prefix}.{proj}_hot_weight.scale2` | float32 | `[n_hot]` (per-expert global scale) |
97
+
98
+ **Hot — bf16-special layer(s) (`hot_format=bf16`, e.g. layer 2):**
99
+ | tensor | dtype | shape |
100
+ |---|---|---|
101
+ | `{prefix}.{proj}_hot_bf16` | bfloat16 | `[n_hot, out, in]` (dense bf16, no scales) |
102
+
103
+ **Cold (AQLM) — same for every layer. One code tensor PER BOOK, smallest lossless dtype:**
104
+ | tensor | dtype | shape |
105
+ |---|---|---|
106
+ | `{prefix}.{proj}_cold_codes.{b}` | uint8 if book has <=256 entries else int16 | `[n_cold, out, in/8]` (group_size=8) |
107
+ | `{prefix}.{proj}_cold_codebook.{b}` | float16 | `[entries_b, 8]` |
108
+ | `{prefix}.{proj}_cold_scales` | float16 | `[n_cold, out]` (per-output-row scale) |
109
+
110
+ Per-book code dtypes: `w13` book0 = **int16** (65536 entries); `w2` book0 =
111
+ **int16** (65536), `w2` book1 = **uint8** (256 entries, codes 0..255 — lossless).
112
+ `{proj}_cold_codes.{b}` replaces the old single `{proj}_cold_codes` tensor
113
+ (the book axis is now the trailing `.{b}` suffix), saving ~38 GB losslessly.
114
+
115
+ **Index maps:**
116
+ | tensor | dtype | shape | meaning |
117
+ |---|---|---|---|
118
+ | `{prefix}.hot_ids` | int32 | `[n_hot]` | routed-expert index (0..255) of each hot slab |
119
+ | `{prefix}.cold_ids` | int32 | `[n_cold]` | routed-expert index (0..255) of each cold slab |
120
+
121
+ `hot_ids[i]` gives the original routed-expert id for hot slab `i` (and likewise
122
+ `cold_ids`); together they partition `0..255`. **group_size = 8.**
123
+ - `w13`: **1 book**, `entries = [65536]`.
124
+ - `w2`: **2 books**, `entries = [65536, 256]`.
125
+
126
+ ## Dequant math
127
+ **Cold (AQLM), expert e, output row r, group g (8 weights):**
128
+ ```
129
+ acc[8] = 0
130
+ for b in range(n_books):
131
+ acc += codebook_b[ codes_b[e, r, g] ] # codes_b = cold_codes.{b}; gather an 8-vector
132
+ W[e, r, g*8:(g+1)*8] = acc * cold_scales[e, r] # per-row scale
133
+ ```
134
+ Codes are read from the per-book tensor `{proj}_cold_codes.{b}`. uint8 books
135
+ (<=256 entries) index the codebook directly. For the int16 65536-entry book,
136
+ negative int16 values wrap to the intended unsigned row (`idx & 0xFFFF`).
137
+
138
+ **Hot (NVFP4, standard ModelOpt e2m1):**
139
+ ```
140
+ codes = unpack_nibbles(hot_weight) # 0..15 -> e2m1 LUT value
141
+ W = LUT[codes] * scale.repeat_interleave(16, dim=-1) * scale2
142
+ ```
143
+ e2m1 LUT = `[0, .5, 1, 1.5, 2, 3, 4, 6, -0, -.5, -1, -1.5, -2, -3, -4, -6]`;
144
+ low nibble is the first weight of each pair.
145
+
146
+ ## Files
147
+ - `model-*.safetensors` + `model.safetensors.index.json` — the sharded weights.
148
+ - `hf_quant_config.json` — original NVFP4 config **plus** the `aqlm_hybrid` block
149
+ (`quant_method`, per-layer `{n_nvfp4, n_cold, packed}`, book entry counts).
150
+ - `config.json` — base config **plus** a `quantization_config` block advertising
151
+ `quant_method = inkling_nvfp4_aqlm_hybrid`.
152
+ - `code/` — the exact repackaging scripts (`build_hybrid.py`, `ink_common.py`, ...).
chat_template.jinja ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- set effort_map = {"none": 0.0, "minimal": 0.1, "low": 0.2, "medium": 0.7, "high": 0.9, "max": 0.99} -%}
2
+ {%- set role_token = {"user": "<|message_user|>", "assistant": "<|message_model|>", "system": "<|message_system|>", "tool": "<|message_tool|>"} -%}
3
+
4
+ {%- macro emit_thinking_effort() -%}
5
+ {%- set eff = reasoning_effort if reasoning_effort is defined and reasoning_effort is not none else 0.9 -%}
6
+ {%- if eff is string -%}
7
+ {%- set key = eff | trim -%}
8
+ {%- if key not in effort_map -%}
9
+ {{- raise_exception("Unknown reasoning_effort: " ~ eff) -}}
10
+ {%- endif -%}
11
+ {%- set num = effort_map[key] -%}
12
+ {%- else -%}
13
+ {%- set num = eff | float -%}
14
+ {%- endif -%}
15
+ {%- if num < 0.0 or num > 0.99 -%}
16
+ {{- raise_exception("reasoning_effort must be in [0.0, 0.99]") -}}
17
+ {%- endif -%}
18
+ {{- "<|message_system|><|content_text|>Thinking effort level: " -}}
19
+ {%- if num == 0.0 -%}0{%- else -%}{{ num }}{%- endif -%}
20
+ {{- "<|end_message|>" -}}
21
+ {%- endmacro -%}
22
+
23
+ {%- if tools -%}
24
+ {%- set tool_state = namespace(specs=[]) -%}
25
+ {%- for tool in tools -%}
26
+ {%- set fn = tool.function if tool.function is defined else tool -%}
27
+ {%- set spec = {
28
+ "description": (fn.description if fn.description is defined and fn.description else ""),
29
+ "name": fn.name,
30
+ "parameters": (fn.parameters if fn.parameters is defined and fn.parameters else {}),
31
+ "type": (tool.type if tool.type is defined and tool.type else "function"),
32
+ } -%}
33
+ {%- set tool_state.specs = tool_state.specs + [spec] -%}
34
+ {%- endfor -%}
35
+ {{- "<|message_system|>tool_declare<|content_xml|>" -}}
36
+ {{- tool_state.specs | tojson(sort_keys=true, separators=(",", ":")) -}}
37
+ {{- "<|end_message|>" -}}
38
+ {%- endif -%}
39
+
40
+ {%- set state = namespace(effort_emitted=false) -%}
41
+ {%- for message in messages -%}
42
+ {%- if message.role not in role_token -%}
43
+ {{- raise_exception("Unknown message role: " ~ message.role) -}}
44
+ {%- endif -%}
45
+ {%- if not state.effort_emitted and message.role != "system" -%}
46
+ {{- emit_thinking_effort() -}}
47
+ {%- set state.effort_emitted = true -%}
48
+ {%- endif -%}
49
+
50
+ {%- set rtok = role_token[message.role] -%}
51
+
52
+ {%- if message.role == "tool" -%}
53
+ {%- set tool_name_state = namespace(name="") -%}
54
+ {%- if message.name is defined and message.name -%}
55
+ {%- set tool_name_state.name = message.name -%}
56
+ {%- elif message.tool_call_id is defined and message.tool_call_id -%}
57
+ {%- for prev in messages -%}
58
+ {%- if prev.role == "assistant" and prev.tool_calls -%}
59
+ {%- for tc in prev.tool_calls -%}
60
+ {%- if tc.id is defined and tc.id == message.tool_call_id and tc.function.name is defined -%}
61
+ {%- set tool_name_state.name = tc.function.name -%}
62
+ {%- endif -%}
63
+ {%- endfor -%}
64
+ {%- endif -%}
65
+ {%- endfor -%}
66
+ {%- endif -%}
67
+ {{- rtok -}}
68
+ {%- if tool_name_state.name -%}{{- tool_name_state.name -}}{%- endif -%}
69
+ {{- "<|content_text|>" -}}
70
+ {%- if message.content is string -%}{{- message.content -}}{%- endif -%}
71
+ {{- "<|end_message|>" -}}
72
+
73
+ {%- else -%}
74
+ {%- if message.role == "assistant" and message.reasoning_content is defined and message.reasoning_content -%}
75
+ {{- "<|message_model|><|content_thinking|>" ~ message.reasoning_content ~ "<|end_message|>" -}}
76
+ {%- endif -%}
77
+
78
+ {%- if message.content is string -%}
79
+ {{- rtok ~ "<|content_text|>" ~ message.content ~ "<|end_message|>" -}}
80
+ {%- elif message.content -%}
81
+ {%- for part in message.content -%}
82
+ {%- if part is string -%}
83
+ {{- rtok ~ "<|content_text|>" ~ part ~ "<|end_message|>" -}}
84
+ {%- elif part.type is not defined or part.type in ("text", "input_text") -%}
85
+ {%- set text_part = (part.text if part.text is defined and part.text is string else "") -%}
86
+ {{- rtok ~ "<|content_text|>" ~ text_part ~ "<|end_message|>" -}}
87
+ {%- elif part.type in ("image", "input_image", "image_url") -%}
88
+ {{- rtok ~ "<|content_image|><|unused_200054|><|end_message|>" -}}
89
+ {%- elif part.type in ("audio", "input_audio", "audio_url") -%}
90
+ {{- rtok ~ "<|content_audio_input|><|unused_200053|><|audio_end|><|end_message|>" -}}
91
+ {%- else -%}
92
+ {{- raise_exception("Unsupported content part type: " ~ part.type) -}}
93
+ {%- endif -%}
94
+ {%- endfor -%}
95
+ {%- endif -%}
96
+
97
+ {%- if message.role == "assistant" and message.tool_calls -%}
98
+ {%- for tc in message.tool_calls -%}
99
+ {%- set fn = tc.function -%}
100
+ {%- if fn.name is not defined or fn.name is not string -%}
101
+ {{- raise_exception("tool call function name must be a string") -}}
102
+ {%- endif -%}
103
+ {%- set args = fn.arguments if fn.arguments is defined and fn.arguments else {} -%}
104
+ {%- if args is string -%}
105
+ {{- raise_exception("tool call arguments must be a parsed object, not a JSON string; canonicalize upstream") -}}
106
+ {%- endif -%}
107
+ {%- if args is not mapping -%}
108
+ {{- raise_exception("tool call arguments must be an object") -}}
109
+ {%- endif -%}
110
+ {{- "<|message_model|>" ~ fn.name ~ "<|content_invoke_tool_json|>" -}}
111
+ {{- '{"name":' ~ (fn.name | tojson(sort_keys=true, separators=(",", ":"))) ~ ',"args":' -}}
112
+ {{- (args | tojson(sort_keys=true, separators=(",", ":"))) -}}
113
+ {{- "}<|end_message|>" -}}
114
+ {%- endfor -%}
115
+ {%- endif -%}
116
+
117
+ {%- if message.role == "assistant" -%}
118
+ {{- "<|content_model_end_sampling|>" -}}
119
+ {%- endif -%}
120
+ {%- endif -%}
121
+ {%- endfor -%}
122
+
123
+ {%- if not state.effort_emitted -%}
124
+ {{- emit_thinking_effort() -}}
125
+ {%- endif -%}
126
+
127
+ {%- if add_generation_prompt -%}
128
+ {{- "<|message_model|>" -}}
129
+ {%- endif -%}
code/analyze_mm_routing.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Diagnostic: do image/audio tokens route to experts that v2's (text-only) tiering
3
+ left COLD? If yes, PV alone can't fully protect multimodal -> a v4 re-tier with
4
+ multimodal routing is warranted. Reads the captured mm acts (ids) — free, no GPU.
5
+
6
+ Reports, per modality: fraction of routed token-slots landing on v2-COLD experts,
7
+ aggregate + worst layers. Writes $RUN/mm_routing.txt.
8
+ """
9
+ import json, os, sys, glob
10
+ import torch
11
+
12
+ Q = "/data/inkling-quant"
13
+ ASSIGN = f"{Q}/assign_512k_v2.json"
14
+ OUT = sys.argv[1] if len(sys.argv) > 1 else f"{Q}/v3_run/mm_routing.txt"
15
+
16
+ def cold_frac(acts_dir, hot):
17
+ """Over all layers with acts in acts_dir, fraction of (token,slot) routings
18
+ that hit a COLD expert. Returns (agg_frac, [(layer,frac,ntok)])."""
19
+ per = []
20
+ tot_cold = tot = 0
21
+ for p in sorted(glob.glob(f"{acts_dir}/acts_layer_*.pt")):
22
+ L = int(p.split("_")[-1].split(".")[0])
23
+ ids = torch.load(p, map_location="cpu").get("ids")
24
+ if ids is None or ids.numel() == 0:
25
+ continue
26
+ hotL = torch.tensor(sorted(hot.get(str(L), [])))
27
+ is_hot = torch.isin(ids, hotL) # [N,6] bool
28
+ c = int((~is_hot).sum()); n = int(is_hot.numel())
29
+ tot_cold += c; tot += n
30
+ per.append((L, c / max(1, n), ids.shape[0]))
31
+ per.sort(key=lambda x: -x[1])
32
+ return (tot_cold / max(1, tot)), per
33
+
34
+ def main():
35
+ hot = json.load(open(ASSIGN))["hot_experts"]
36
+ lines = ["=== multimodal routing vs v2 (text-only) tiering ===",
37
+ "fraction of image/audio routings landing on v2-COLD experts:"]
38
+ rec = False
39
+ for mm, d in [("image", f"{Q}/acts_v3_img"), ("audio", f"{Q}/acts_v3_audio")]:
40
+ if not os.path.isdir(d):
41
+ lines.append(f" {mm}: (no acts at {d})"); continue
42
+ frac, per = cold_frac(d, hot)
43
+ lines.append(f" {mm}: {100*frac:.1f}% of routings on cold experts "
44
+ f"(worst layers: " + ", ".join(f"L{L}={100*f:.0f}%" for L,f,_ in per[:5]) + ")")
45
+ if frac > 0.55: # cold experts get ~85% of text mass by design; >55% mm-cold => mm under-served
46
+ rec = True
47
+ # reference: text cold fraction (baseline expectation ~85% since 85% experts are cold)
48
+ if os.path.isdir(f"{Q}/acts_v3"):
49
+ tf, _ = cold_frac(f"{Q}/acts_v3", hot)
50
+ lines.append(f" [ref] text: {100*tf:.1f}% on cold (expected high; ~85% of experts are cold)")
51
+ lines.append("")
52
+ lines.append("RE-TIER RECOMMENDED (v4): multimodal routing significantly under-served by v2 tiering."
53
+ if rec else
54
+ "NO re-tier needed: multimodal routing overlaps v2 hot set adequately; PV suffices.")
55
+ open(OUT, "w").write("\n".join(lines) + "\n")
56
+ print("\n".join(lines))
57
+
58
+ if __name__ == "__main__":
59
+ main()
code/build_calib.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Assemble a diverse local calibration corpus for routing-stats / tiering.
3
+ Mixes code (vLLM src), prose/docs (markdown), and synthetic chat/instruction
4
+ prompts. Not distribution-perfect, but exercises diverse expert routing.
5
+ Output: /data/inkling-quant/calib.jsonl (one {"text": ...} per line, ~6k chars).
6
+ """
7
+ import json, os, glob, random
8
+ random.seed(42)
9
+ OUT = "/data/inkling-quant/calib.jsonl"
10
+ CHUNK = 6000 # chars (~1.5-2k tokens)
11
+ MAX_CHUNKS = 700
12
+ chunks = []
13
+
14
+ def add_files(patterns, cap):
15
+ got = 0
16
+ files = []
17
+ for p in patterns:
18
+ files += glob.glob(p, recursive=True)
19
+ random.shuffle(files)
20
+ for f in files:
21
+ if got >= cap: break
22
+ try:
23
+ t = open(f, errors="ignore").read()
24
+ except Exception:
25
+ continue
26
+ if len(t) < 500: continue
27
+ for i in range(0, min(len(t), CHUNK*3), CHUNK):
28
+ c = t[i:i+CHUNK]
29
+ if len(c) >= 1000:
30
+ chunks.append(c); got += 1
31
+ if got >= cap: break
32
+
33
+ # code (python + rust)
34
+ add_files(["/data/vllm-inkling/vllm/**/*.py", "/data/vllm-inkling/rust/**/*.rs"], 300)
35
+ # prose / docs
36
+ add_files(["/data/vllm-inkling/docs/**/*.md", "/data/glm52/code/**/*.md"], 150)
37
+ # synthetic instruction/chat/agentic (varied domains)
38
+ prompts = [
39
+ "Explain how photosynthesis converts sunlight into chemical energy, step by step.",
40
+ "Write a haiku about the ocean at dawn, then explain the imagery.",
41
+ "You are a helpful assistant. A user asks: how do I safely defrost chicken? Answer thoroughly.",
42
+ "Summarize the causes of the French Revolution in three paragraphs.",
43
+ "Translate 'the quick brown fox jumps over the lazy dog' into French and German.",
44
+ "Given a list of integers, describe an algorithm to find the two numbers summing to a target.",
45
+ "A patient reports fatigue and joint pain. List a differential diagnosis reasoning process (educational).",
46
+ "Write a short story about a lighthouse keeper who discovers a message in a bottle.",
47
+ "Explain the difference between TCP and UDP and when to use each.",
48
+ "Describe how a mixture-of-experts transformer routes tokens to experts.",
49
+ ]
50
+ for _ in range(12):
51
+ for p in prompts:
52
+ chunks.append(p + "\n\n" + random.choice(prompts) + "\n\n" + random.choice(prompts))
53
+
54
+ random.shuffle(chunks)
55
+ chunks = chunks[:MAX_CHUNKS]
56
+ with open(OUT, "w") as fh:
57
+ for c in chunks:
58
+ fh.write(json.dumps({"text": c}) + "\n")
59
+ print(f"wrote {len(chunks)} chunks to {OUT} (~{sum(len(c) for c in chunks)//4} tokens est)")
code/build_calib_v3.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """v3 corpus — BROAD GLM-5.2-style mix, with per-category disjoint held-out.
3
+
4
+ Mirrors GLM-5.2's calib composition (code 40 / agentic 25 / instruction 15 /
5
+ medical 10 / prose 10) instead of narrow wikitext, so routing coverage matches
6
+ what Inkling actually serves. Each category is split 3 ways by content hash:
7
+ train -> calib_v3.jsonl (PV trains; big, for cold-expert coverage)
8
+ pv -> heldout_v3.jsonl (PV early-stop signal; disjoint)
9
+ ppl -> ppl_v3.jsonl (pre/post ppl; disjoint; {"text","cat"} for per-domain)
10
+
11
+ Medical = PUBLIC LITERATURE / EXAM text only (PubMedQA abstracts, MedMCQA
12
+ questions+explanations, general medical-education prompts). NO patient data / PHI.
13
+
14
+ Run with the datasets-enabled venv:
15
+ /home/coder/git/glm52/.venv/bin/python build_calib_v3.py
16
+ """
17
+ import json, os, glob, random, hashlib, re
18
+ random.seed(1337)
19
+
20
+ RAW = "/data/inkling-quant/corpus_v3/raw"
21
+ OUT = "/data/inkling-quant"
22
+ CHUNK = 6000; MIN_CHUNK = 1000
23
+ # token budgets (approx via chars/4). GLM used ~15M; we aim ~9M train.
24
+ TRAIN_TOK = 9_000_000
25
+ MIX = {"code":0.40, "agentic":0.25, "instruction":0.15, "medical":0.10, "prose":0.10}
26
+ # per-category held-out caps (chunks). ppl small+fast (~GLM's 46.7k tok); pv larger.
27
+ PPL_CHUNKS_PER_CAT = 20 # ~20*1.5k = ~30k tok/cat -> ~50k+ ppl tok (~GLM's 46.7k)
28
+ PV_CHUNKS_PER_CAT = 60
29
+
30
+ def chunks_of(text, cap=10**9):
31
+ out=[]; text=text.strip()
32
+ for i in range(0, len(text), CHUNK):
33
+ c=text[i:i+CHUNK]
34
+ if len(c)>=MIN_CHUNK: out.append(c)
35
+ if len(out)>=cap: break
36
+ return out
37
+
38
+ def pack(docs, cap_tok=10**12):
39
+ """Concatenate a stream of (possibly short) docs into a buffer and emit
40
+ CHUNK-sized pieces, so short examples aren't dropped (GLM's approach)."""
41
+ out=[]; buf=""; tok=0
42
+ for d in docs:
43
+ if not d: continue
44
+ buf += d.rstrip()+"\n\n"
45
+ while len(buf)>=CHUNK:
46
+ out.append(buf[:CHUNK]); buf=buf[CHUNK:]; tok+=CHUNK//4
47
+ if tok>=cap_tok: return out
48
+ if len(buf)>=MIN_CHUNK: out.append(buf)
49
+ return out
50
+
51
+ # ---------------- sources ----------------
52
+ def src_code_local(cap_tok):
53
+ pats=["/data/vllm-inkling/vllm/**/*.py","/data/vllm-inkling/csrc/**/*.*",
54
+ "/data/vllm-inkling/rust/**/*.rs","/data/vllm-glm52-sm120/vllm/**/*.py",
55
+ "/data/vllm-glm52-sm120/csrc/**/*.cu","/home/coder/Antidoom/**/*.py"]
56
+ files=[]; [files.extend(glob.glob(p,recursive=True)) for p in pats]
57
+ files=sorted(set(files)); random.shuffle(files)
58
+ docs=[]; tok=0
59
+ for f in files:
60
+ if tok>=cap_tok: break
61
+ try: t=open(f,errors="ignore").read()
62
+ except: continue
63
+ if len(t)<400: continue
64
+ docs.append(f"# ==== {os.path.basename(f)} ====\n{t[:60000]}"); tok+=len(t[:60000])//4
65
+ return docs
66
+
67
+ def _hf(name, cfg=None):
68
+ from datasets import load_dataset
69
+ return load_dataset(name, cfg, split="train", streaming=True) if cfg else \
70
+ load_dataset(name, split="train", streaming=True)
71
+
72
+ def src_code_hf(cap_tok):
73
+ docs=[]; tok=0
74
+ try:
75
+ for r in _hf("m-a-p/CodeFeedback-Filtered-Instruction"):
76
+ docs.append(f"### Task\n{r['query']}\n\n### Solution\n{r['answer']}"); tok+=len(docs[-1])//4
77
+ if tok>=cap_tok*0.6: break
78
+ except Exception as e: print(" code_hf CodeFeedback:",str(e)[:80])
79
+ try:
80
+ for r in _hf("jtatman/python-code-dataset-500k"):
81
+ docs.append(f"# {r.get('instruction','')}\n{r.get('output','')}"); tok+=len(docs[-1])//4
82
+ if tok>=cap_tok: break
83
+ except Exception as e: print(" code_hf py500k:",str(e)[:80])
84
+ return docs
85
+
86
+ TOOLS=["web_search","python","bash","read_file","sql_query","http_get","calculator","grep","git_log"]
87
+ AG_TASKS=["Find the latest release version of numpy and summarize the changelog.",
88
+ "Compute the 20th Fibonacci number and verify it two ways.",
89
+ "Read config.yaml and report the database connection settings.",
90
+ "Query the orders table for total revenue in Q3 grouped by region.",
91
+ "Check whether the service on port 8080 is healthy and report latency.",
92
+ "Refactor this function to be O(n) and run the tests.",
93
+ "Download the CSV, parse it, and report the top 5 rows by value.",
94
+ "Diagnose why the CI build failed and propose a minimal fix.",
95
+ "Search the codebase for all callers of parse_config and summarize them.",
96
+ "Given a stack trace, locate the offending line and explain the bug."]
97
+ def src_agentic(cap_tok):
98
+ """Varied synthetic tool-call sessions (content randomized so chunks are unique)."""
99
+ docs=[]; tok=0; rng=random.Random(7)
100
+ while tok<cap_tok:
101
+ task=rng.choice(AG_TASKS); n=rng.randint(2,5)
102
+ s=[f"<|user|>\n{task}\n<|assistant|>\nLet me work through this step by step.\n"]
103
+ for i in range(n):
104
+ tool=rng.choice(TOOLS); rid=rng.randint(1000,9999)
105
+ args=json.dumps({"q":f"{task[:30]}","step":i,"rid":rid,"limit":rng.randint(5,50)})
106
+ res=json.dumps({"rid":rid,"rows":rng.randint(0,200),"ok":rng.random()>0.1,
107
+ "note":rng.choice(["cache miss","200 OK","partial","retry","done"])})
108
+ s.append(f'<tool_call>\n{{"name":"{tool}","arguments":{args}}}\n</tool_call>\n')
109
+ s.append(f'<tool_response>\n{res}\n</tool_response>\n')
110
+ s.append(rng.choice(["Observed the result; continuing.","That narrows it down.",
111
+ "Now I'll verify.","Proceeding to the next step."])+f" (step {i})\n")
112
+ s.append(f"Final answer: {task[:40]}... resolved after {n} tool calls (session {rng.randint(10**4,10**6)}).\n")
113
+ c="".join(s); docs.append(c); tok+=len(c)//4
114
+ return docs
115
+
116
+ def src_instruction(cap_tok):
117
+ docs=[]; tok=0
118
+ try:
119
+ for r in _hf("tatsu-lab/alpaca"):
120
+ instr=r["instruction"]+(("\n\n"+r["input"]) if r.get("input") else "")
121
+ docs.append(f"<|user|>\n{instr}\n<|assistant|>\n{r['output']}"); tok+=len(docs[-1])//4
122
+ if tok>=cap_tok: break
123
+ except Exception as e: print(" instruction alpaca:",str(e)[:80])
124
+ return docs
125
+
126
+ MEDICAL_QA=[
127
+ "Explain the differential diagnosis approach for acute chest pain across cardiac, pulmonary, GI, and musculoskeletal causes.",
128
+ "Describe how to systematically read a chest X-ray and common findings and pitfalls.",
129
+ "Explain the pharmacology of beta-blockers: mechanism, selectivity, indications, contraindications.",
130
+ "Walk through the pathophysiology of type 2 diabetes and the mechanism of each major drug class.",
131
+ "Explain how CT and MRI work physically and when each is preferred clinically.",
132
+ "Describe interpretation of a full blood count and common patterns and differentials.",
133
+ "Explain TNM staging of solid tumours and how imaging and histopathology contribute.",
134
+ "Describe the cardiac cycle and how it maps to ECG waveforms and heart sounds.",
135
+ "Explain sepsis: definitions, pathophysiology, recognition criteria, initial management.",
136
+ "Explain acid-base disturbances and step-by-step arterial blood gas interpretation."]
137
+ RADIOLOGY_CSV="/data/inkling-quant/corpus_v3/raw/radiology/ReportsDATASET.csv"
138
+ def src_medical(cap_tok):
139
+ """PUBLIC LITERATURE / EXAM text only (no patient data)."""
140
+ docs=[]; tok=0
141
+ # local de-identified radiology reports (harrison.ai domain), if present
142
+ if os.path.exists(RADIOLOGY_CSV):
143
+ import csv as _csv
144
+ for r in _csv.DictReader(open(RADIOLOGY_CSV,errors="ignore")):
145
+ t=next(iter(r.values()),"") or ""
146
+ if len(t)>40: docs.append(t); tok+=len(t)//4
147
+ if tok>=cap_tok*0.3: break
148
+ try:
149
+ for r in _hf("qiaojin/PubMedQA","pqa_labeled"):
150
+ ctx=" ".join(r["context"]["contexts"]) if isinstance(r.get("context"),dict) else str(r.get("context",""))
151
+ docs.append(f"{r['question']}\n{ctx}\n{r.get('long_answer','')}"); tok+=len(docs[-1])//4
152
+ if tok>=cap_tok*0.5: break
153
+ except Exception as e: print(" medical pubmedqa:",str(e)[:80])
154
+ try:
155
+ for r in _hf("openlifescienceai/medmcqa"):
156
+ opts=f"A){r['opa']} B){r['opb']} C){r['opc']} D){r['opd']}"
157
+ docs.append(f"{r['question']}\n{opts}\nExplanation: {r.get('exp') or ''}"); tok+=len(docs[-1])//4
158
+ if tok>=cap_tok: break
159
+ except Exception as e: print(" medical medmcqa:",str(e)[:80])
160
+ for q in MEDICAL_QA: docs.append(f"<|user|>\n{q} Be thorough.\n<|assistant|>")
161
+ return docs
162
+
163
+ def src_prose(cap_tok):
164
+ docs=[]; tok=0
165
+ p=f"{RAW}/wikitext2-train.txt" # encyclopedic
166
+ if os.path.exists(p):
167
+ raw=open(p,errors="ignore").read()
168
+ for para in re.split(r"\n = [^=]", raw):
169
+ docs.append(para); tok+=len(para)//4
170
+ if tok>=cap_tok*0.5: break
171
+ for f in sorted(glob.glob("/data/vllm-inkling/docs/**/*.md",recursive=True)): # docs
172
+ if tok>=cap_tok*0.7: break
173
+ try: t=open(f,errors="ignore").read()
174
+ except: continue
175
+ docs.append(t); tok+=len(t)//4
176
+ try: # dolly general knowledge
177
+ for r in _hf("databricks/databricks-dolly-15k"):
178
+ docs.append("\n\n".join(x for x in [r.get("instruction",""),r.get("context",""),r.get("response","")] if x))
179
+ tok+=len(docs[-1])//4
180
+ if tok>=cap_tok: break
181
+ except Exception as e: print(" prose dolly:",str(e)[:80])
182
+ return docs
183
+
184
+ SOURCES={"code":lambda b:src_code_local(int(b*0.35))+src_code_hf(int(b*0.65)),
185
+ "agentic":src_agentic,"instruction":src_instruction,
186
+ "medical":src_medical,"prose":src_prose}
187
+
188
+ # ---------------- split + write ----------------
189
+ def _key(c): return hashlib.md5(c[:512].encode()).hexdigest()
190
+
191
+ def build():
192
+ os.makedirs(RAW,exist_ok=True)
193
+ train,pv,ppl = [],[],[] # (text,cat)
194
+ seen=set()
195
+ for cat,frac in MIX.items():
196
+ budget=int(TRAIN_TOK*frac / 0.8) # +25% headroom for the held-out slices
197
+ docs=SOURCES[cat](budget)
198
+ chunks=pack(docs, budget) # pack short docs; split long ones
199
+ # dedup within category + globally
200
+ uniq=[]
201
+ for c in chunks:
202
+ k=_key(c)
203
+ if k in seen: continue
204
+ seen.add(k); uniq.append(c)
205
+ random.Random(hash(cat)&0xffff).shuffle(uniq)
206
+ # allocate: ppl first, then pv, rest train (disjoint by slice)
207
+ cppl=uniq[:PPL_CHUNKS_PER_CAT]
208
+ cpv =uniq[PPL_CHUNKS_PER_CAT:PPL_CHUNKS_PER_CAT+PV_CHUNKS_PER_CAT]
209
+ ctr =uniq[PPL_CHUNKS_PER_CAT+PV_CHUNKS_PER_CAT:]
210
+ ppl += [(c,cat) for c in cppl]; pv += [(c,cat) for c in cpv]; train += [(c,cat) for c in ctr]
211
+ tt=sum(len(c)//4 for c in ctr)
212
+ print(f" {cat:12s}: train {len(ctr)} (~{tt:,} tok) | pv {len(cpv)} | ppl {len(cppl)}")
213
+ random.shuffle(train); random.shuffle(pv)
214
+ def w(path, items, with_cat=False):
215
+ with open(path,"w") as f:
216
+ for c,cat in items:
217
+ f.write(json.dumps({"text":c,**({"cat":cat} if with_cat else {})})+"\n")
218
+ print(f" wrote {path}: {len(items)} chunks (~{sum(len(c)//4 for c,_ in items):,} tok)")
219
+ w(f"{OUT}/calib_v3.jsonl", train)
220
+ w(f"{OUT}/heldout_v3.jsonl", pv)
221
+ w(f"{OUT}/ppl_v3.jsonl", ppl, with_cat=True)
222
+ # disjointness assertion
223
+ def keys(p): return {_key(json.loads(l)["text"]) for l in open(p)}
224
+ kC,kH,kP=keys(f"{OUT}/calib_v3.jsonl"),keys(f"{OUT}/heldout_v3.jsonl"),keys(f"{OUT}/ppl_v3.jsonl")
225
+ print(f"\n overlap calib∩pv={len(kC&kH)} calib∩ppl={len(kC&kP)} pv∩ppl={len(kH&kP)} (all must be 0)")
226
+ from collections import Counter
227
+ print(" ppl per-category:", dict(Counter(cat for _,cat in ppl)))
228
+
229
+ if __name__=="__main__":
230
+ print(">>> building BROAD v3 corpus (GLM-style mix) ...")
231
+ build()
232
+ print(">>> done.")
code/build_gate_ckpt.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Build the 512k QUALITY-GATE checkpoint: a standard ModelOpt-NVFP4 checkpoint
3
+ identical to /data/inkling-nvfp4 EXCEPT COLD experts (per assign_512k.json) have
4
+ their fused w13/w2 weights REPLACED by re-quantizing the AQLM parts to NVFP4
5
+ (dequant AQLM->bf16->NVFP4). HOT experts keep their original Inkling-NVFP4 values.
6
+ Loads on the existing vLLM NVFP4 path (no custom kernel).
7
+ w13 <- converged parts (parts_conv), w2 <- encode parts (parts).
8
+
9
+ The three tensors of each fused expert group -- packed `...experts.{w13,w2}_weight`,
10
+ `.scale`, `.scale2` -- live in DIFFERENT safetensors shards (per weight_map).
11
+ Two phases:
12
+ phase1 (GPU 4-7, parallel): requant every cold expert ONCE per (layer,proj);
13
+ write compact per-group cache files (cold rows only). Resumable.
14
+ phase2 (CPU): stream each source shard, overwrite the cold-expert slices of any
15
+ expert packed/.scale/.scale2 tensor present, copy everything else
16
+ verbatim, keep the exact same shard layout. Resumable per shard.
17
+ Out: /data/inkling-512k-gate/ (~592GB).
18
+ """
19
+ import argparse, json, os, re, shutil
20
+ import torch
21
+ from safetensors import safe_open
22
+ from safetensors.torch import save_file
23
+ import ink_common as ic
24
+
25
+ SRC = ic.NVFP4_DIR
26
+ # env-overridable so the SAME builder makes v1 / v2 / v3 gate proxies:
27
+ # v1: W13_PARTS=parts_conv W2_PARTS=parts ASSIGN=assign_512k.json
28
+ # v2: W13_PARTS=parts_conv W2_PARTS=parts_conv ASSIGN=assign_512k_v2.json GATE_OUT=/data/inkling-512k-gate-v2
29
+ # v3: W13_PARTS=parts_pv_v3 W2_PARTS=parts_pv_v3 ASSIGN=assign_512k_v2.json GATE_OUT=/data/inkling-512k-gate-v3
30
+ OUT = os.environ.get("GATE_OUT", "/data/inkling-512k-gate")
31
+ W13_PARTS = os.environ.get("W13_PARTS", "/data/inkling-quant/parts_conv") # w13 source
32
+ W2_PARTS = os.environ.get("W2_PARTS", "/data/inkling-quant/parts") # w2 source
33
+ CONV = W13_PARTS; PARTS = W2_PARTS # back-compat aliases
34
+ ASSIGN = os.environ.get("ASSIGN", "/data/inkling-quant/assign_512k.json")
35
+ CACHE = os.environ.get("REQUANT_CACHE", "/data/inkling-quant/tools/requant_cache")
36
+
37
+ # packed `..._weight`, or `.scale`, or `.scale2` of a fused MoE expert group
38
+ EXP_RE = re.compile(r"model\.llm\.layers\.(\d+)\.mlp\.experts\.(w13|w2)_weight(\.scale2|\.scale)?$")
39
+ SUF2KEY = {"": "packed", ".scale": "scale", ".scale2": "scale2"}
40
+
41
+
42
+ def cold_map():
43
+ a = json.load(open(ASSIGN))["hot_experts"]
44
+ return {int(L): [e for e in range(ic.N_EXPERTS) if e not in set(hot)]
45
+ for L, hot in a.items()}
46
+
47
+
48
+ def full_layers():
49
+ """MoE layers stored full-precision (bf16, NOT NVFP4-packed) in the base
50
+ checkpoint -> identified by the absence of a `...w13_weight.scale` tensor.
51
+ In Inkling-NVFP4 this is layer 2 (the first MoE layer kept high precision)."""
52
+ wm = json.load(open(f"{SRC}/model.safetensors.index.json"))["weight_map"]
53
+ fl = set()
54
+ for L in ic.moe_layers():
55
+ if f"model.llm.layers.{L}.mlp.experts.w13_weight.scale" not in wm:
56
+ fl.add(L)
57
+ return fl
58
+
59
+
60
+ def cache_path(L, proj):
61
+ return f"{CACHE}/L{L}_{proj}.pt"
62
+
63
+
64
+ # ---------------------------------------------------------------- phase 1 (GPU)
65
+ def phase1(layers, dev="cuda"):
66
+ cold = cold_map()
67
+ full = full_layers()
68
+ os.makedirs(CACHE, exist_ok=True)
69
+ for L in layers:
70
+ for proj in ("w13", "w2"):
71
+ cp = cache_path(L, proj)
72
+ if os.path.exists(cp):
73
+ print(f"[gpu] skip cache L{L} {proj} (exists)", flush=True)
74
+ continue
75
+ d = CONV if proj == "w13" else PARTS
76
+ part = torch.load(f"{d}/layer_{L}_{proj}.pt", map_location="cpu")
77
+ ce = cold[L]
78
+ if L in full:
79
+ # full-precision (bf16) layer: cache AQLM dequant as bf16, no NVFP4
80
+ wl = [ic.aqlm_dequant_expert(part, proj, e, device=dev).to(torch.bfloat16).cpu()
81
+ for e in ce]
82
+ out = {"cold": torch.tensor(ce, dtype=torch.long),
83
+ "weight": torch.stack(wl)}
84
+ del wl
85
+ tag = "bf16"
86
+ else:
87
+ pk_l, sf_l, s2_l = [], [], []
88
+ for e in ce:
89
+ W = ic.aqlm_dequant_expert(part, proj, e, device=dev).float()
90
+ pk, sf8, s2 = ic.quantize_nvfp4(W)
91
+ pk_l.append(pk.cpu()); sf_l.append(sf8.cpu()); s2_l.append(s2.cpu().reshape(()))
92
+ out = {"cold": torch.tensor(ce, dtype=torch.long),
93
+ "packed": torch.stack(pk_l),
94
+ "scale": torch.stack(sf_l),
95
+ "scale2": torch.stack(s2_l)}
96
+ del pk_l, sf_l, s2_l
97
+ tag = "nvfp4"
98
+ tmp = cp + ".tmp"
99
+ torch.save(out, tmp); os.replace(tmp, cp)
100
+ del part, out
101
+ print(f"[gpu] cached L{L} {proj} ({len(ce)} cold experts, {tag})", flush=True)
102
+
103
+
104
+ # ---------------------------------------------------------------- phase 2 (CPU)
105
+ def phase2():
106
+ os.makedirs(OUT, exist_ok=True)
107
+ cold = cold_map()
108
+ idx = json.load(open(f"{SRC}/model.safetensors.index.json"))
109
+ wm = idx["weight_map"]
110
+ shards = sorted(set(wm.values()))
111
+ for si, shard in enumerate(shards):
112
+ outp = f"{OUT}/{shard}"
113
+ if os.path.exists(outp):
114
+ print(f"[{si+1}/{len(shards)}] skip {shard} (exists)", flush=True); continue
115
+ tensors, meta = {}, {}
116
+ with safe_open(f"{SRC}/{shard}", framework="pt") as f:
117
+ meta = f.metadata() or {}
118
+ for k in f.keys():
119
+ tensors[k] = f.get_tensor(k)
120
+ gcache = {} # (L,proj) -> loaded cache dict; scoped to this shard
121
+
122
+ def gload(L, proj):
123
+ key = (L, proj)
124
+ if key not in gcache:
125
+ gcache[key] = torch.load(cache_path(L, proj), map_location="cpu")
126
+ return gcache[key]
127
+
128
+ changed = 0
129
+ for k in list(tensors.keys()):
130
+ m = EXP_RE.match(k)
131
+ if not m:
132
+ continue
133
+ L = int(m.group(1)); proj = m.group(2); suf = m.group(3) or ""
134
+ if L not in cold:
135
+ continue
136
+ g = gload(L, proj)
137
+ ce = g["cold"]
138
+ if "weight" in g: # full-precision (bf16) layer
139
+ assert suf == "", f"unexpected aux tensor {k} on a bf16 layer"
140
+ src = g["weight"]
141
+ else: # NVFP4-packed layer
142
+ src = g[SUF2KEY[suf]]
143
+ t = tensors[k]
144
+ t[ce] = src.to(t.dtype) # overwrite cold rows (cold-ordered) in place
145
+ tensors[k] = t
146
+ changed += 1
147
+ tmp = outp + ".tmp"
148
+ save_file(tensors, tmp, metadata=meta or {"format": "pt"})
149
+ os.replace(tmp, outp)
150
+ del tensors, gcache
151
+ print(f"[{si+1}/{len(shards)}] {shard}: {changed} expert tensors patched -> saved", flush=True)
152
+
153
+ # copy every non-safetensors file (config, tokenizer, index, hf_quant_config, ...)
154
+ # incl. dirs like tiktoken; skip the HF download cache dir.
155
+ SKIP = {".cache"}
156
+ for fn in os.listdir(SRC):
157
+ if fn.endswith(".safetensors") or fn in SKIP:
158
+ continue
159
+ src, dst = f"{SRC}/{fn}", f"{OUT}/{fn}"
160
+ if os.path.exists(dst):
161
+ continue
162
+ if os.path.isdir(src):
163
+ shutil.copytree(src, dst)
164
+ else:
165
+ shutil.copy2(src, dst)
166
+ print("PHASE2 DONE", flush=True)
167
+
168
+
169
+ def main():
170
+ ap = argparse.ArgumentParser()
171
+ ap.add_argument("--mode", required=True, choices=["phase1", "phase2"])
172
+ ap.add_argument("--worker", type=int, default=0)
173
+ ap.add_argument("--nworkers", type=int, default=1)
174
+ ap.add_argument("--device", default="cuda")
175
+ a = ap.parse_args()
176
+ if a.mode == "phase1":
177
+ layers = ic.moe_layers()[a.worker::a.nworkers]
178
+ print(f"[gpu] worker {a.worker}/{a.nworkers} layers: {layers}", flush=True)
179
+ phase1(layers, a.device)
180
+ else:
181
+ phase2()
182
+
183
+
184
+ if __name__ == "__main__":
185
+ main()
code/build_heldout.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Build a HELD-OUT text set DISJOINT from calib.jsonl for PV-generalization eval.
3
+
4
+ Sources are deliberately DIFFERENT from calib's (calib = /data/vllm-inkling vllm
5
+ python/rust src, /data/vllm-inkling docs md, /data/glm52/code md, + 10 synthetic
6
+ prompts). Held-out uses:
7
+ - code: /home/coder/Antidoom/antidoom/src/**/*.py (different repo, NOT vllm)
8
+ - prose: /data/inkling-recon/RECON.md, /data/inkling-nvfp4/README.md,
9
+ /data/inkling-512k-hybrid/README.md, /data/glm52-*/README.md
10
+ - fresh synthetic chat/instruction prompts (distinct topics from calib's 10)
11
+ Then VERIFIES every held-out chunk is disjoint from calib (no held-out chunk is a
12
+ substring of any calib chunk, and no long shared window) and drops any that overlap.
13
+ Output: /data/inkling-quant/heldout.jsonl (~48 {"text": ...} chunks).
14
+ """
15
+ import json, glob, random
16
+ random.seed(7)
17
+ OUT = "/data/inkling-quant/heldout.jsonl"
18
+ CALIB = "/data/inkling-quant/calib.jsonl"
19
+ CHUNK = 6000
20
+ chunks = []
21
+
22
+ def add_files(patterns, cap, per_file_cap=3):
23
+ got = 0; files = []
24
+ for p in patterns:
25
+ files += glob.glob(p, recursive=True)
26
+ random.shuffle(files)
27
+ for f in files:
28
+ if got >= cap: break
29
+ try:
30
+ t = open(f, errors="ignore").read()
31
+ except Exception:
32
+ continue
33
+ if len(t) < 500: continue
34
+ n_here = 0
35
+ for i in range(0, len(t), CHUNK):
36
+ c = t[i:i+CHUNK]
37
+ if len(c) >= 1000:
38
+ chunks.append(c); got += 1; n_here += 1
39
+ if got >= cap or n_here >= per_file_cap: break
40
+
41
+ # code from a DIFFERENT repo than calib (antidoom, not vllm)
42
+ add_files(["/home/coder/Antidoom/antidoom/src/**/*.py"], 22, per_file_cap=3)
43
+ # prose/docs NOT in calib globs
44
+ add_files(["/data/inkling-recon/RECON.md", "/data/inkling-nvfp4/README.md",
45
+ "/data/inkling-512k-hybrid/README.md", "/data/glm52-*/README.md",
46
+ "/data/glm52-fp8o/README.md"], 14, per_file_cap=2)
47
+ # fresh synthetic prompts (distinct domains from calib's 10)
48
+ syn = [
49
+ "Describe the lifecycle of a star from nebula to supernova, and what determines its final state.",
50
+ "Write a Python function that merges two sorted linked lists into one sorted list, with comments.",
51
+ "A customer emails that their package arrived damaged. Draft a professional, empathetic reply offering a resolution.",
52
+ "Explain the Byzantine Generals Problem and how blockchain consensus addresses it.",
53
+ "Compose a limerick about a cat who refuses to use its expensive bed.",
54
+ "What are the trade-offs between microservices and a monolithic architecture for a small startup?",
55
+ "Outline a 30-minute beginner yoga routine, naming each pose and its benefit.",
56
+ "Explain how vaccines train the adaptive immune system, appropriate for a curious teenager.",
57
+ "Given a noisy time series, describe how a Kalman filter estimates the underlying state.",
58
+ "Write a SQL query to find the second-highest salary in an employees table, and explain it.",
59
+ "Discuss the ecological role of mycorrhizal fungi in forest ecosystems.",
60
+ "Draft a short cover letter for a data-analyst role emphasizing curiosity and rigor.",
61
+ ]
62
+ for _ in range(3):
63
+ for p in syn:
64
+ chunks.append(p + "\n\n" + random.choice(syn) + "\n\n" + random.choice(syn))
65
+
66
+ # --- disjointness verification vs calib ---
67
+ calib_chunks = [json.loads(l)["text"] for l in open(CALIB)]
68
+ calib_blob = "\n".join(calib_chunks)
69
+
70
+ def overlaps_calib(c, win=200, step=100):
71
+ # substring containment either way
72
+ if c in calib_blob:
73
+ return True
74
+ for cc in calib_chunks:
75
+ if cc in c:
76
+ return True
77
+ # long shared window
78
+ for i in range(0, max(1, len(c) - win), step):
79
+ if c[i:i+win] in calib_blob:
80
+ return True
81
+ return False
82
+
83
+ random.shuffle(chunks)
84
+ kept, dropped = [], 0
85
+ for c in chunks:
86
+ if overlaps_calib(c):
87
+ dropped += 1; continue
88
+ kept.append(c)
89
+ if len(kept) >= 48:
90
+ break
91
+
92
+ with open(OUT, "w") as fh:
93
+ for c in kept:
94
+ fh.write(json.dumps({"text": c}) + "\n")
95
+ print(f"wrote {len(kept)} held-out chunks (dropped {dropped} for calib overlap) -> {OUT}")
96
+ print(f"~{sum(len(c) for c in kept)//4} tokens est; calib has {len(calib_chunks)} chunks")
code/build_hybrid.py ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Assemble the COMPRESSED 512k hybrid checkpoint (the real memory-efficient
3
+ artifact the custom kernel will run). NO requant: cold experts are already AQLM
4
+ (parts_conv w13 / parts w2), hot are already NVFP4 (inkling-nvfp4). Pure repackage.
5
+
6
+ FORMAT (per NVFP4-packed MoE layer L; layer 2 is bf16-special -> copied as-is):
7
+ experts.{w13,w2}_hot_weight u8 [n_hot, out, in/2] (NVFP4 packed)
8
+ experts.{w13,w2}_hot_weight.scale f8e4m3 [n_hot, out, in/16]
9
+ experts.{w13,w2}_hot_weight.scale2 f32 [n_hot]
10
+ experts.{w13,w2}_cold_codes int16 [n_cold, books, out, in/8] (AQLM)
11
+ experts.{w13,w2}_cold_codebook.{b} f16 [entries_b, 8] (w13: 1 book@65536; w2: 65536 + 256)
12
+ experts.{w13,w2}_cold_scales f16 [n_cold, out]
13
+ experts.hot_ids / experts.cold_ids int32
14
+ All NON-expert tensors (attn, embed, norms, shared_experts, gate, vision, audio,
15
+ mtp, layer-2 experts) copied verbatim from inkling-nvfp4.
16
+ Config: hf_quant_config.json extended with an 'aqlm_hybrid' block giving per-layer
17
+ {n_nvfp4, n_cold, w13_books, w2_books, entries} + the assignment. See FORMAT in README.
18
+ """
19
+ import argparse, json, os, re
20
+ import torch
21
+ from safetensors import safe_open
22
+ from safetensors.torch import save_file
23
+ import ink_common as ic
24
+
25
+ SRC = ic.NVFP4_DIR
26
+ # env-overridable for v3: HYBRID_OUT + COLD_PARTS (cold w13+w2 source) + ASSIGN
27
+ # v2: COLD_PARTS=parts_conv HYBRID_OUT=/data/inkling-512k-hybrid-v2
28
+ # v3: COLD_PARTS=parts_pv_v3 HYBRID_OUT=/data/inkling-512k-hybrid-v3
29
+ OUT = os.environ.get("HYBRID_OUT", "/data/inkling-512k-hybrid-v2")
30
+ CONV = os.environ.get("COLD_PARTS", "/data/inkling-quant/parts_conv")
31
+ PARTS = CONV
32
+ ASSIGN = os.environ.get("ASSIGN", "/data/inkling-quant/assign_512k_v2.json")
33
+ SHARD_BYTES = 8 * 2**30
34
+
35
+ def hot_map():
36
+ a = json.load(open(ASSIGN))["hot_experts"]
37
+ return {int(L): sorted(v) for L, v in a.items()}
38
+
39
+ def packed_layers():
40
+ idx = json.load(open(f"{SRC}/model.safetensors.index.json"))["weight_map"]
41
+ ls = set()
42
+ for k in idx:
43
+ m = re.match(r"model\.llm\.layers\.(\d+)\.mlp\.experts\.w13_weight\.scale$", k)
44
+ if m: ls.add(int(m.group(1)))
45
+ return ls
46
+
47
+ EXP_ANY = re.compile(r"model\.llm\.layers\.(\d+)\.mlp\.experts\.(w13|w2)_weight(\.scale2|\.scale)?$")
48
+
49
+ def book_code_dtype(entries):
50
+ """Smallest int dtype that losslessly holds this book's codes.
51
+ <=256 entries -> uint8 (codes 0..255); otherwise int16 (65536-entry books
52
+ use int16 with the negative-wrap convention for rows >= 32768)."""
53
+ return torch.uint8 if entries <= 256 else torch.int16
54
+
55
+ def build_layer_tensors(L, hot, nv):
56
+ """Hybrid expert tensors for one MoE layer.
57
+ Branches on the source format: NVFP4-packed layers keep packed uint8 hot
58
+ slabs (+scale/scale2); the bf16-special layer (layer 2, no NVFP4 base) keeps
59
+ bf16 hot slabs (no scales). Cold experts are AQLM in both cases, stored as
60
+ ONE code tensor per book with the smallest lossless dtype."""
61
+ out = {}
62
+ hids = hot[L]; cids = [e for e in range(ic.N_EXPERTS) if e not in set(hids)]
63
+ hidx = torch.tensor(hids); cidx = torch.tensor(cids)
64
+ for proj in ("w13", "w2"):
65
+ base = f"model.llm.layers.{L}.mlp.experts.{proj}"
66
+ is_nvfp4 = nv.has(f"{base}_weight.scale")
67
+ if is_nvfp4:
68
+ # HOT: NVFP4 packed slabs sliced verbatim.
69
+ packed = nv.get(f"{base}_weight"); scale = nv.get(f"{base}_weight.scale"); scale2 = nv.get(f"{base}_weight.scale2")
70
+ out[f"{base}_hot_weight"] = packed[hidx].contiguous()
71
+ out[f"{base}_hot_weight.scale"] = scale[hidx].contiguous()
72
+ out[f"{base}_hot_weight.scale2"] = scale2[hidx].contiguous()
73
+ else:
74
+ # HOT (layer 2): bf16 slabs gathered from the fused bf16 tensor, NO scales.
75
+ fused = nv.get(f"{base}_weight") # bf16 [E, out, in]
76
+ out[f"{base}_hot_bf16"] = fused[hidx].contiguous().to(torch.bfloat16)
77
+ # COLD: AQLM, one code tensor per book with minimal lossless dtype.
78
+ part = torch.load(f"{CONV}/layer_{L}_{proj}.pt", map_location="cpu") # v2: both w13+w2 converged
79
+ codes = part[f"{proj}_codes"][cidx] # [n_cold, books, out, ng] int16
80
+ for b, cb in enumerate(part[f"{proj}_codebooks"]):
81
+ dt = book_code_dtype(cb.shape[0])
82
+ out[f"{base}_cold_codes.{b}"] = codes[:, b].to(dt).contiguous()
83
+ out[f"{base}_cold_codebook.{b}"] = cb.contiguous()
84
+ out[f"{base}_cold_scales"] = part[f"{proj}_scales"][cidx].contiguous()
85
+ out[f"model.llm.layers.{L}.mlp.experts.hot_ids"] = torch.tensor(hids, dtype=torch.int32)
86
+ out[f"model.llm.layers.{L}.mlp.experts.cold_ids"] = torch.tensor(cids, dtype=torch.int32)
87
+ return out
88
+
89
+ def _dtype_str(dt):
90
+ return str(dt).split(".")[-1]
91
+
92
+ def make_hybrid_config(hot, packed):
93
+ part_w13 = torch.load(f"{CONV}/layer_{min(packed)}_w13.pt", map_location="cpu")
94
+ part_w2 = torch.load(f"{CONV}/layer_{min(packed)}_w2.pt", map_location="cpu")
95
+ books = {"w13": [c.shape[0] for c in part_w13["w13_codebooks"]],
96
+ "w2": [c.shape[0] for c in part_w2["w2_codebooks"]]}
97
+ code_dtypes = {p: [_dtype_str(book_code_dtype(e)) for e in books[p]] for p in ("w13", "w2")}
98
+ layer_books = {}
99
+ for L in sorted(hot):
100
+ nhot = len(hot[L]); ncold = ic.N_EXPERTS - nhot
101
+ packed_flag = L in packed
102
+ layer_books[str(L)] = {"n_hot": nhot, "n_nvfp4": nhot, "n_cold": ncold,
103
+ "packed": packed_flag,
104
+ "hot_format": "nvfp4" if packed_flag else "bf16"}
105
+ return {
106
+ "quant_method": "inkling_nvfp4_aqlm_hybrid",
107
+ "base_nvfp4": "thinkingmachines/Inkling-NVFP4",
108
+ "group_size": 8,
109
+ "w13_book_entries": books["w13"], "w2_book_entries": books["w2"],
110
+ "w13_code_dtypes": code_dtypes["w13"], "w2_code_dtypes": code_dtypes["w2"],
111
+ "cold_code_layout": "per-book tensors {proj}_cold_codes.{b}, dtype uint8 if book<=256 entries else int16",
112
+ "aqlm_layer_books": layer_books,
113
+ "note": "cold experts: AQLM, per-book code tensors {proj}_cold_codes.{b} (w2 book0=int16@65536, "
114
+ "book1=uint8@256; w13 book0=int16@65536) + per-book codebooks + per-row scales. "
115
+ "hot experts: NVFP4 (hot_format=nvfp4) except layer 2 which has NO NVFP4 base and stores "
116
+ "bf16 hot slabs {proj}_hot_bf16 (no scale/scale2, hot_format=bf16). Its cold experts are "
117
+ "AQLM like every other layer. hot_ids/cold_ids give the routed-expert index for each slab.",
118
+ }
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # Full-checkpoint assembly (streaming, resumable, CPU/IO only).
122
+ # ---------------------------------------------------------------------------
123
+ import struct, shutil
124
+
125
+ def is_dropped(name, built):
126
+ """True iff `name` is a fused MoE-expert weight/scale/scale2 of a layer that is
127
+ rebuilt as a hybrid layer (every MoE layer, incl. layer 2's bf16 experts).
128
+ input_amax/original_shape are NOT matched by EXP_ANY, so they are kept."""
129
+ m = EXP_ANY.match(name)
130
+ return bool(m) and int(m.group(1)) in built
131
+
132
+ def _read_st_header(path):
133
+ with open(path, "rb") as f:
134
+ n = struct.unpack("<Q", f.read(8))[0]
135
+ h = json.loads(f.read(n))
136
+ h.pop("__metadata__", None)
137
+ return h
138
+
139
+ class LayerCache:
140
+ """Builds one packed layer's hybrid tensors on demand, caching the most recent."""
141
+ def __init__(self, hot, nv):
142
+ self.hot = hot; self.nv = nv; self._L = None; self._t = None
143
+ def get(self, L):
144
+ if self._L != L:
145
+ self._t = build_layer_tensors(L, self.hot, self.nv)
146
+ self._L = L
147
+ return self._t
148
+
149
+ def plan_tensors(hot, built):
150
+ """Return ordered list of plan entries + weight_map skeleton.
151
+ entry = {name, nbytes, kind:'src'|'hyb', src_shard?, layer?}.
152
+ `built` = every MoE layer rebuilt as a hybrid layer (packed NVFP4 + layer 2)."""
153
+ src_idx = json.load(open(f"{SRC}/model.safetensors.index.json"))["weight_map"]
154
+ # per-shard header cache for exact source byte sizes
155
+ hdr_cache = {}
156
+ def src_nbytes(name, shard):
157
+ if shard not in hdr_cache:
158
+ hdr_cache[shard] = _read_st_header(f"{SRC}/{shard}")
159
+ o = hdr_cache[shard][name]["data_offsets"]
160
+ return o[1] - o[0]
161
+
162
+ plan = []
163
+ # 1) all kept source tensors, in source weight_map order
164
+ for name, shard in src_idx.items():
165
+ if is_dropped(name, built):
166
+ continue
167
+ plan.append({"name": name, "nbytes": src_nbytes(name, shard),
168
+ "kind": "src", "src_shard": shard})
169
+ # 2) hybrid expert tensors per rebuilt layer (build each once for exact sizes)
170
+ nv = ic.ShardReader(SRC)
171
+ for L in sorted(built):
172
+ t = build_layer_tensors(L, hot, nv)
173
+ for name, v in t.items():
174
+ plan.append({"name": name, "nbytes": v.numel() * v.element_size(),
175
+ "kind": "hyb", "layer": L})
176
+ del t
177
+ return plan
178
+
179
+ def bin_pack(plan, shard_bytes):
180
+ """Greedy pack preserving order; a tensor larger than shard_bytes gets its own shard."""
181
+ shards = []; cur = []; cur_b = 0
182
+ for e in plan:
183
+ if cur and cur_b + e["nbytes"] > shard_bytes:
184
+ shards.append(cur); cur = []; cur_b = 0
185
+ cur.append(e); cur_b += e["nbytes"]
186
+ if cur:
187
+ shards.append(cur)
188
+ return shards
189
+
190
+ def assemble():
191
+ os.makedirs(OUT, exist_ok=True)
192
+ hot = hot_map(); packed = packed_layers()
193
+ built = set(hot) # every MoE layer is now rebuilt as a hybrid layer
194
+ bf16 = sorted(built - packed) # layer(s) with no NVFP4 base -> bf16 hot slabs
195
+ print(f"[plan] hybrid MoE layers: {len(built)} (NVFP4-packed: {len(packed)}, bf16-hot: {bf16})")
196
+ plan = plan_tensors(hot, built)
197
+ total = sum(e["nbytes"] for e in plan)
198
+ print(f"[plan] {len(plan)} output tensors, total {total/1e9:.1f} GB")
199
+ shards = bin_pack(plan, SHARD_BYTES)
200
+ NN = len(shards)
201
+ print(f"[plan] {NN} output shards (~{SHARD_BYTES/2**30:.0f}GB target)")
202
+
203
+ def shard_name(i):
204
+ return f"model-{i:05d}-of-{NN:05d}.safetensors"
205
+
206
+ # index.json (deterministic from plan)
207
+ weight_map = {}
208
+ for i, sh in enumerate(shards, 1):
209
+ for e in sh:
210
+ weight_map[e["name"]] = shard_name(i)
211
+ idx = {"metadata": {"total_size": total}, "weight_map": weight_map}
212
+ with open(f"{OUT}/model.safetensors.index.json", "w") as f:
213
+ json.dump(idx, f, indent=2)
214
+ print(f"[index] wrote model.safetensors.index.json ({len(weight_map)} tensors)")
215
+
216
+ # write shards (resumable: fully-written shards get renamed from .tmp)
217
+ src_readers = {}
218
+ def src_get(shard, name):
219
+ if shard not in src_readers:
220
+ src_readers[shard] = safe_open(f"{SRC}/{shard}", framework="pt")
221
+ return src_readers[shard].get_tensor(name)
222
+
223
+ lc = LayerCache(hot, ic.ShardReader(SRC))
224
+ for i, sh in enumerate(shards, 1):
225
+ fname = shard_name(i); fpath = f"{OUT}/{fname}"
226
+ exp_b = sum(e["nbytes"] for e in sh)
227
+ if os.path.exists(fpath):
228
+ print(f"[shard {i}/{NN}] {fname} exists, skip")
229
+ continue
230
+ tmp = fpath + ".tmp"
231
+ if os.path.exists(tmp):
232
+ os.remove(tmp)
233
+ tensors = {}
234
+ for e in sh:
235
+ if e["kind"] == "src":
236
+ tensors[e["name"]] = src_get(e["src_shard"], e["name"])
237
+ else:
238
+ t = lc.get(e["layer"])[e["name"]]
239
+ assert t.numel() * t.element_size() == e["nbytes"], \
240
+ f"size mismatch {e['name']}: built {t.numel()*t.element_size()} vs planned {e['nbytes']}"
241
+ tensors[e["name"]] = t
242
+ save_file(tensors, tmp, metadata={"format": "pt"})
243
+ os.replace(tmp, fpath)
244
+ del tensors
245
+ print(f"[shard {i}/{NN}] wrote {fname} {exp_b/1e9:.2f} GB ({len(sh)} tensors)")
246
+
247
+ write_side_files(hot, packed)
248
+ print(f"[done] assembled {NN} shards, {total/1e9:.1f} GB -> {OUT}")
249
+ return NN, total
250
+
251
+ def write_side_files(hot, packed):
252
+ # verbatim copies
253
+ for fn in ("tokenizer.json", "tokenizer_config.json", "chat_template.jinja",
254
+ "processor_config.json", "special_tokens_map.json"):
255
+ src = f"{SRC}/{fn}"
256
+ if os.path.exists(src):
257
+ shutil.copy2(src, f"{OUT}/{fn}")
258
+ if os.path.isdir(f"{SRC}/tiktoken"):
259
+ shutil.copytree(f"{SRC}/tiktoken", f"{OUT}/tiktoken", dirs_exist_ok=True)
260
+
261
+ # config.json + quantization_config block
262
+ cfg = json.load(open(f"{SRC}/config.json"))
263
+ cfg["quantization_config"] = {
264
+ "quant_method": "inkling_nvfp4_aqlm_hybrid",
265
+ "base_nvfp4": "thinkingmachines/Inkling-NVFP4",
266
+ "group_size": 8,
267
+ "note": "Mixed per-expert quant: hot experts ModelOpt-NVFP4, cold experts AQLM-2bit. "
268
+ "Full spec in hf_quant_config.json['aqlm_hybrid'] and README.md. "
269
+ "Requires a custom fused-MoE kernel; does NOT load on stock vLLM.",
270
+ }
271
+ with open(f"{OUT}/config.json", "w") as f:
272
+ json.dump(cfg, f, indent=2)
273
+
274
+ # hf_quant_config.json = original NVFP4 + aqlm_hybrid block
275
+ hq = json.load(open(f"{SRC}/hf_quant_config.json"))
276
+ hq["aqlm_hybrid"] = make_hybrid_config(hot, packed)
277
+ with open(f"{OUT}/hf_quant_config.json", "w") as f:
278
+ json.dump(hq, f, indent=2)
279
+
280
+ # reproducibility: tools as code/
281
+ os.makedirs(f"{OUT}/code", exist_ok=True)
282
+ for py in os.listdir(os.path.dirname(os.path.abspath(__file__))):
283
+ if py.endswith(".py"):
284
+ shutil.copy2(os.path.join(os.path.dirname(os.path.abspath(__file__)), py),
285
+ f"{OUT}/code/{py}")
286
+
287
+ write_readme(hot, packed)
288
+ print("[side] wrote config.json, hf_quant_config.json, tokenizer/processor files, code/, README.md")
289
+
290
+ def write_readme(hot, packed):
291
+ built = set(hot); bf16 = sorted(built - packed)
292
+ tot_hot = sum(len(hot[L]) for L in built); tot_slots = len(built) * ic.N_EXPERTS
293
+ pct_hot = 100.0 * tot_hot / tot_slots
294
+ readme = README_TEMPLATE.format(
295
+ n_packed=len(packed), n_bf16=len(bf16), bf16_layers=", ".join(map(str, bf16)),
296
+ tot_hot=tot_hot, tot_slots=tot_slots,
297
+ pct_hot=f"{pct_hot:.1f}", pct_cold=f"{100-pct_hot:.1f}")
298
+ with open(f"{OUT}/README.md", "w") as f:
299
+ f.write(readme)
300
+
301
+ README_TEMPLATE = r"""---
302
+ license: other
303
+ base_model:
304
+ - thinkingmachines/Inkling
305
+ tags:
306
+ - moe
307
+ - nvfp4
308
+ - aqlm
309
+ - quantized
310
+ - inkling
311
+ ---
312
+
313
+ # Inkling-512k-NVFP4-AQLM-hybrid
314
+
315
+ A memory-optimized **hybrid-quantized** build of
316
+ [thinkingmachines/Inkling](https://huggingface.co/thinkingmachines/Inkling)
317
+ (MoE, 256 routed experts/layer, 6 active). Each MoE layer's experts are split
318
+ into a small **hot** set kept at NVFP4 (~4 bpw) and a large **cold** set stored
319
+ as **AQLM 2-bit**. This is the **512k tier**: **{pct_hot}% of expert slots are
320
+ NVFP4 (hot)** and **{pct_cold}% are AQLM-2bit (cold)**, averaging **~2.66 bits
321
+ per weight** over the routed experts. It targets serving at **512k context on
322
+ 4x96GB GPUs**.
323
+
324
+ > **This checkpoint needs a CUSTOM FUSED MoE KERNEL** that dispatches NVFP4 hot
325
+ > experts and AQLM cold experts within the same fused grouped-GEMM. **It does
326
+ > NOT load on stock vLLM / Transformers.** The on-disk format and dequant math
327
+ > are fully specified below so a kernel can consume it directly.
328
+
329
+ ## Quality
330
+ Measured **+1.0% perplexity vs Inkling-NVFP4** on the gate-proxy eval — i.e. a
331
+ 1% relative degradation for a large drop in expert memory.
332
+
333
+ ## Provenance
334
+ - **Hot experts (NVFP4):** sliced verbatim from the ModelOpt NVFP4 base
335
+ (`thinkingmachines/Inkling-NVFP4`), no requantization.
336
+ - **Cold experts (AQLM):** quantized from the **Inkling BF16 teacher**. The
337
+ `w13` cold books are **activation-aware converged** (calibrated on captured
338
+ activations); `w2` cold books are AQLM-encoded.
339
+ - **Assignment** of hot vs cold per layer is in
340
+ `hf_quant_config.json['aqlm_hybrid']['aqlm_layer_books']` and reproduced from
341
+ the routing/importance study (`assign_512k.json`). Reproduction scripts are in
342
+ `code/`.
343
+
344
+ ## Layout summary
345
+ - **{n_packed} NVFP4-packed MoE layers** carry the hybrid expert format below
346
+ (NVFP4 hot slabs + AQLM cold).
347
+ - **{n_bf16} bf16-special MoE layer(s)** (layer **{bf16_layers}**) have **no NVFP4
348
+ base**, so they use the SAME hybrid format EXCEPT the hot slabs are stored as
349
+ **bf16** (`experts.{{proj}}_hot_bf16`, no `.scale`/`.scale2`); their cold experts
350
+ are AQLM like every other layer. The fused uncompressed bf16 `experts.w13_weight`
351
+ / `experts.w2_weight` are **dropped**. Layers 0-1 are dense MLP (not experts).
352
+ - All **non-expert** tensors (attention, embeddings, norms, `shared_experts`,
353
+ `gate`, vision, audio, MTP) are copied verbatim from the NVFP4 base.
354
+ - Total hot slots {tot_hot} / {tot_slots} expert slots.
355
+
356
+ ## On-disk tensor format (per NVFP4-packed MoE layer L)
357
+ `prefix = model.llm.layers.{{L}}.mlp.experts`. Routed experts (256 total) are
358
+ partitioned into `n_hot` hot + `n_cold` cold. For each projection
359
+ `proj in {{w13, w2}}` (w13 = fused gate|up, out=6144, in=6144; w2 = down,
360
+ out=6144, in=3072):
361
+
362
+ **Hot — NVFP4-packed layers (ModelOpt, `hot_format=nvfp4`):**
363
+ | tensor | dtype | shape |
364
+ |---|---|---|
365
+ | `{{prefix}}.{{proj}}_hot_weight` | uint8 | `[n_hot, out, in/2]` (2 fp4 codes/byte) |
366
+ | `{{prefix}}.{{proj}}_hot_weight.scale` | float8_e4m3 | `[n_hot, out, in/16]` (per-block-16 scale) |
367
+ | `{{prefix}}.{{proj}}_hot_weight.scale2` | float32 | `[n_hot]` (per-expert global scale) |
368
+
369
+ **Hot — bf16-special layer(s) (`hot_format=bf16`, e.g. layer 2):**
370
+ | tensor | dtype | shape |
371
+ |---|---|---|
372
+ | `{{prefix}}.{{proj}}_hot_bf16` | bfloat16 | `[n_hot, out, in]` (dense bf16, no scales) |
373
+
374
+ **Cold (AQLM) — same for every layer. One code tensor PER BOOK, smallest lossless dtype:**
375
+ | tensor | dtype | shape |
376
+ |---|---|---|
377
+ | `{{prefix}}.{{proj}}_cold_codes.{{b}}` | uint8 if book has <=256 entries else int16 | `[n_cold, out, in/8]` (group_size=8) |
378
+ | `{{prefix}}.{{proj}}_cold_codebook.{{b}}` | float16 | `[entries_b, 8]` |
379
+ | `{{prefix}}.{{proj}}_cold_scales` | float16 | `[n_cold, out]` (per-output-row scale) |
380
+
381
+ Per-book code dtypes: `w13` book0 = **int16** (65536 entries); `w2` book0 =
382
+ **int16** (65536), `w2` book1 = **uint8** (256 entries, codes 0..255 — lossless).
383
+ `{{proj}}_cold_codes.{{b}}` replaces the old single `{{proj}}_cold_codes` tensor
384
+ (the book axis is now the trailing `.{{b}}` suffix), saving ~38 GB losslessly.
385
+
386
+ **Index maps:**
387
+ | tensor | dtype | shape | meaning |
388
+ |---|---|---|---|
389
+ | `{{prefix}}.hot_ids` | int32 | `[n_hot]` | routed-expert index (0..255) of each hot slab |
390
+ | `{{prefix}}.cold_ids` | int32 | `[n_cold]` | routed-expert index (0..255) of each cold slab |
391
+
392
+ `hot_ids[i]` gives the original routed-expert id for hot slab `i` (and likewise
393
+ `cold_ids`); together they partition `0..255`. **group_size = 8.**
394
+ - `w13`: **1 book**, `entries = [65536]`.
395
+ - `w2`: **2 books**, `entries = [65536, 256]`.
396
+
397
+ ## Dequant math
398
+ **Cold (AQLM), expert e, output row r, group g (8 weights):**
399
+ ```
400
+ acc[8] = 0
401
+ for b in range(n_books):
402
+ acc += codebook_b[ codes_b[e, r, g] ] # codes_b = cold_codes.{{b}}; gather an 8-vector
403
+ W[e, r, g*8:(g+1)*8] = acc * cold_scales[e, r] # per-row scale
404
+ ```
405
+ Codes are read from the per-book tensor `{{proj}}_cold_codes.{{b}}`. uint8 books
406
+ (<=256 entries) index the codebook directly. For the int16 65536-entry book,
407
+ negative int16 values wrap to the intended unsigned row (`idx & 0xFFFF`).
408
+
409
+ **Hot (NVFP4, standard ModelOpt e2m1):**
410
+ ```
411
+ codes = unpack_nibbles(hot_weight) # 0..15 -> e2m1 LUT value
412
+ W = LUT[codes] * scale.repeat_interleave(16, dim=-1) * scale2
413
+ ```
414
+ e2m1 LUT = `[0, .5, 1, 1.5, 2, 3, 4, 6, -0, -.5, -1, -1.5, -2, -3, -4, -6]`;
415
+ low nibble is the first weight of each pair.
416
+
417
+ ## Files
418
+ - `model-*.safetensors` + `model.safetensors.index.json` — the sharded weights.
419
+ - `hf_quant_config.json` — original NVFP4 config **plus** the `aqlm_hybrid` block
420
+ (`quant_method`, per-layer `{{n_nvfp4, n_cold, packed}}`, book entry counts).
421
+ - `config.json` — base config **plus** a `quantization_config` block advertising
422
+ `quant_method = inkling_nvfp4_aqlm_hybrid`.
423
+ - `code/` — the exact repackaging scripts (`build_hybrid.py`, `ink_common.py`, ...).
424
+ """
425
+
426
+ if __name__ == "__main__":
427
+ ap = argparse.ArgumentParser()
428
+ ap.add_argument("--layer", type=int, default=-1, help="build+inspect one layer (test)")
429
+ ap.add_argument("--assemble", action="store_true", help="assemble full checkpoint -> OUT")
430
+ ap.add_argument("--validate", action="store_true", help="validate assembled checkpoint")
431
+ a = ap.parse_args()
432
+ hot = hot_map(); packed = packed_layers()
433
+ if a.assemble:
434
+ assemble()
435
+ elif a.validate:
436
+ from validate_hybrid import validate
437
+ validate()
438
+ elif a.layer >= 0:
439
+ nv = ic.ShardReader(SRC)
440
+ t = build_layer_tensors(a.layer, hot, nv)
441
+ print(f"layer {a.layer} hybrid tensors ({len(t)}):")
442
+ for k, v in t.items():
443
+ print(f" {k.split('.mlp.')[-1]}: {v.dtype} {tuple(v.shape)}")
444
+ tot = sum(v.numel()*v.element_size() for v in t.values())
445
+ print(f"layer bytes: {tot/1e6:.1f} MB")
446
+ else:
447
+ print("packed MoE layers:", len(packed), "| bf16-special layers:", sorted(set(hot)-packed))
448
+ print(json.dumps(make_hybrid_config(hot, packed)["aqlm_layer_books"]["3"]))
code/build_mm_manifest.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Assemble the v3 multimodal IMAGE manifest from all staged image pools, with a
3
+ disjoint train/held split. Emits jsonl of {"path","domain","prompt"} that
4
+ run_capture_acts_mm.py consumes (image+text -> vLLM -> MoE acts).
5
+
6
+ Domains (each exercises different vision routing):
7
+ chest_xray medical radiograph (NIH sample)
8
+ fracatlas fracture radiograph
9
+ natural general/natural imagery
10
+ ocr receipts / scanned text
11
+ screenshot desktop/web/mobile GUI (computer-use)
12
+ """
13
+ import json, glob, os, random
14
+ random.seed(1337)
15
+ MM = "/data/inkling-mm"
16
+ OUT = "/data/inkling-quant"
17
+ PROMPTS = {
18
+ "chest_xray": "<|content_image|>\nDescribe the findings in this chest radiograph.",
19
+ "fracatlas": "<|content_image|>\nIs there a fracture in this radiograph? Describe it.",
20
+ "natural": "<|content_image|>\nDescribe this image in detail.",
21
+ "ocr": "<|content_image|>\nTranscribe all text visible in this image.",
22
+ "screenshot": "<|content_image|>\nDescribe this UI screenshot and the actions available.",
23
+ }
24
+ POOLS = {
25
+ "chest_xray": [f"{MM}/chest_xray/**/*.png", f"{MM}/chest_xray/**/*.jpeg", f"{MM}/chest_xray/**/*.jpg"],
26
+ "fracatlas": [f"{MM}/fracatlas/**/*.jpg", f"{MM}/fracatlas/**/*.png"],
27
+ "natural": [f"{MM}/natural/**/*.jpg", f"{MM}/natural/**/*.png"],
28
+ "ocr": [f"{MM}/ocr/**/*.jpg", f"{MM}/ocr/**/*.png", f"{MM}/ocr_hf/**/*.jpg"],
29
+ "screenshot": [f"{MM}/screenshots/**/*.jpg", f"{MM}/screenshots/**/*.png"],
30
+ }
31
+ PER_DOMAIN = 600 # cap per domain for train (balanced; not ham)
32
+ HELD = 40 # per domain held-out for mm ppl
33
+
34
+ def files(pats):
35
+ fs=[]
36
+ for p in pats: fs+=glob.glob(p, recursive=True)
37
+ return sorted(set(fs))
38
+
39
+ def main():
40
+ train, held = [], []
41
+ for dom, pats in POOLS.items():
42
+ fs = files(pats); random.shuffle(fs)
43
+ if not fs:
44
+ print(f" {dom:11s}: 0 (missing)"); continue
45
+ h = fs[:HELD]; t = fs[HELD:HELD+PER_DOMAIN]
46
+ for f in t: train.append({"path":f,"domain":dom,"prompt":PROMPTS[dom]})
47
+ for f in h: held.append({"path":f,"domain":dom,"prompt":PROMPTS[dom]})
48
+ print(f" {dom:11s}: {len(fs)} avail -> train {len(t)} held {len(h)}")
49
+ random.shuffle(train)
50
+ for name, rows in (("mm_img_train.jsonl",train),("mm_img_held.jsonl",held)):
51
+ with open(f"{OUT}/{name}","w") as fh:
52
+ for r in rows: fh.write(json.dumps(r)+"\n")
53
+ print(f" wrote {OUT}/{name}: {len(rows)}")
54
+
55
+ if __name__=="__main__":
56
+ print(">>> building multimodal image manifest ..."); main()
code/converge.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Activation-aware AQLM convergence (Phase E1) for Inkling cold experts, w13.
3
+
4
+ Objective per layer over cold experts (shared codebook):
5
+ min sum_{e,o,g} s[e,o]^2 * || (v[e,o,g] - cb[code]) * sqrt(h_e[g]) ||^2
6
+ where v = W/s (row-normalized teacher), h_e = diag input Hessian = E[x^2] over
7
+ tokens routed to expert e (from /data/inkling-quant/acts). Warm-starts from the
8
+ encode part; iterates weighted re-encode -> weighted codebook update -> scale
9
+ refit. w13 input is the MoE hidden x directly (no gate/up interleaving needed).
10
+
11
+ Writes /data/inkling-quant/parts_conv/layer_{L}_w13.pt (same schema as encode
12
+ part) + records rel_err before/after (unweighted, weight-space).
13
+ """
14
+ from __future__ import annotations
15
+ import argparse, os, time, json
16
+ import torch
17
+ import ink_common as ic
18
+
19
+ import os as _os
20
+ PARTS = _os.environ.get("INK_PARTS", "/data/inkling-quant/parts")
21
+ OUT = _os.environ.get("INK_OUT", "/data/inkling-quant/parts_conv")
22
+ ACTS = _os.environ.get("INK_ACTS", "/data/inkling-quant/acts")
23
+ ASSIGN = _os.environ.get("INK_ASSIGN", "/data/inkling-quant/assign_512k.json")
24
+ G = 8; CHUNK = 16384
25
+
26
+ def cold_experts(layer, assign):
27
+ hot = set(assign["hot_experts"].get(str(layer), []))
28
+ return [e for e in range(ic.N_EXPERTS) if e not in hot]
29
+
30
+ def w13_hessians(layer, cold, device):
31
+ a = torch.load(f"{ACTS}/acts_layer_{layer}.pt")
32
+ x = a["x"].float().to(device); ids = a["ids"].to(device) # x[N,6144], ids[N,6]
33
+ H = torch.ones(ic.N_EXPERTS, 6144, device=device)
34
+ for e in cold:
35
+ m = (ids == e).any(1)
36
+ if int(m.sum()) >= 8:
37
+ xe = x[m]
38
+ H[e] = (xe * xe).mean(0)
39
+ H.clamp_min_(1e-8)
40
+ return H
41
+
42
+ def chunk_weight(st, n, out_dim, ng, hsqrt):
43
+ """Per-chunk weight[n,8] = sqrt(h_e[group cols]); depends only on (e,g)."""
44
+ idx = torch.arange(st, st + n, device=hsqrt.device)
45
+ e = idx // (out_dim * ng); g = idx % ng
46
+ return hsqrt[e, g] # [n,8]
47
+
48
+ def wencode(vecs, cb, out_dim, ng, hsqrt):
49
+ """vecs[N,8], cb[K,8] -> codes[N] int32 (weighted nearest), weights on the fly."""
50
+ cbh = cb.half(); cb2h = (cb * cb).half()
51
+ out = torch.empty(vecs.shape[0], dtype=torch.int32, device=vecs.device)
52
+ for s in range(0, vecs.shape[0], CHUNK):
53
+ v = vecs[s:s+CHUNK]; w = chunk_weight(s, v.shape[0], out_dim, ng, hsqrt)
54
+ score = 2.0 * ((v * w) @ cbh.t()) - (w @ cb2h.t()) # [chunk,K]
55
+ out[s:s+CHUNK] = score.argmax(-1).to(torch.int32)
56
+ return out
57
+
58
+ def rel_err(vecs, recon, roww):
59
+ # weight-space unweighted rel err uses row scale only (compare vs encode part metric)
60
+ num = torch.zeros((), device=vecs.device); den = torch.zeros((), device=vecs.device)
61
+ for s in range(0, vecs.shape[0], CHUNK*8):
62
+ v = vecs[s:s+CHUNK*8].float(); r = recon[s:s+CHUNK*8].float()
63
+ num += (v - r).pow(2).sum(); den += v.pow(2).sum()
64
+ return float((num.sqrt() / den.sqrt()))
65
+
66
+ def werr(vecs, codes, cb, out_dim, ng, hsqrt, s2):
67
+ """Weighted (activation-aware) rel err = sqrt(sum w(v-r)^2)/sqrt(sum w v^2),
68
+ w = h(e,g)*s^2(e,o) — the actual converge objective."""
69
+ num = torch.zeros((), device=vecs.device); den = torch.zeros((), device=vecs.device)
70
+ for st in range(0, vecs.shape[0], CHUNK*8):
71
+ n = min(CHUNK*8, vecs.shape[0]-st)
72
+ idx = torch.arange(st, st+n, device=vecs.device)
73
+ e = idx // (out_dim*ng); o = (idx // ng) % out_dim; g = idx % ng
74
+ w = (hsqrt[e, g].float()**2) * s2[e, o].unsqueeze(1) # [n,8]
75
+ v = vecs[st:st+n].float(); r = cb[codes[st:st+n].long()].float()
76
+ num += (w*(v-r).pow(2)).sum(); den += (w*v.pow(2)).sum()
77
+ return float((num.sqrt()/den.sqrt()))
78
+
79
+ def converge_layer(layer, iters, device="cuda"):
80
+ assign = json.load(open(ASSIGN))
81
+ cold = cold_experts(layer, assign)
82
+ part = torch.load(f"{PARTS}/layer_{layer}_w13.pt")
83
+ cb = part["w13_codebooks"][0].float().to(device) # [K,8]
84
+ K = cb.shape[0]
85
+ tc = ic.ShardReader(ic.BF16_DIR)
86
+ # teacher cold experts -> normalized vecs + per-row scale, built PER-EXPERT to
87
+ # avoid a full [C,6144,6144] float copy (OOM). vecs half, s fp32.
88
+ C = len(cold); out = 6144; ind = 6144; ng = ind // G
89
+ s = torch.empty(C, out, device=device)
90
+ vecs = torch.empty(C * out * ng, G, dtype=torch.float16, device=device)
91
+ for ci, e in enumerate(cold):
92
+ we = ic.teacher_expert(tc, layer, "w13", e, device=device).float() # [6144,6144]
93
+ se = we.abs().mean(-1).clamp_min(1e-8) # [6144]
94
+ s[ci] = se
95
+ base = ci * out * ng
96
+ vecs[base:base + out*ng] = (we / se.unsqueeze(-1)).reshape(out*ng, G).half()
97
+ del we
98
+ torch.cuda.empty_cache()
99
+ # compact hessian tables (no full [Nv,8] materialization)
100
+ H = w13_hessians(layer, cold, device) # [256,6144]
101
+ hcold = H[cold].reshape(C, ng, G) # [C,ng,8] (= h per e,group)
102
+ hsqrt = torch.sqrt(hcold).half() # [C,ng,8]
103
+ s2 = (s * s) # [C,out] row weight
104
+ # init recon rel err (warm start = encode part codes)
105
+ codes0 = part["w13_codes"][:, 0][torch.tensor(cold)].reshape(-1).to(device).long()
106
+ err_before = rel_err(vecs, cb[codes0].half(), None)
107
+ werr_before = werr(vecs, codes0, cb, out, ng, hsqrt, s2)
108
+ print(f" L{layer} w13 warm-start: unweighted {err_before:.4f} | WEIGHTED {werr_before:.4f}", flush=True)
109
+ codes = codes0; err = err_before
110
+ for it in range(iters):
111
+ codes = wencode(vecs, cb, out, ng, hsqrt) # weighted re-encode
112
+ # weighted codebook update: c_k = sum(rw*v)/sum(rw), rw = h(e,g) * s^2(e,o), per chunk
113
+ newcb = torch.zeros(K, G, device=device); wsum = torch.zeros(K, G, device=device)
114
+ for st in range(0, vecs.shape[0], CHUNK*8):
115
+ n = min(CHUNK*8, vecs.shape[0]-st)
116
+ idx = torch.arange(st, st+n, device=device)
117
+ e = idx // (out*ng); o = (idx // ng) % out; g = idx % ng
118
+ r = (hsqrt[e, g].float() ** 2) * s2[e, o].unsqueeze(1) # [n,8] = h * s^2
119
+ c = codes[st:st+n].long(); v = vecs[st:st+n].float()
120
+ newcb.index_add_(0, c, v * r); wsum.index_add_(0, c, r)
121
+ nz = wsum[:, 0] > 0
122
+ cb[nz] = (newcb[nz] / wsum[nz].clamp_min(1e-12))
123
+ err = rel_err(vecs, cb[codes].half(), None)
124
+ we = werr(vecs, codes, cb, out, ng, hsqrt, s2)
125
+ print(f" L{layer} w13 iter{it}: unweighted {err:.4f} | WEIGHTED {we:.4f}", flush=True)
126
+ # refit per-row scale s (closed form, h-weighted): s = <W, s0*recon_h>/<recon,recon>_h ... approx: keep s (rows already normalized). recompute scales via weighted lsq on original W
127
+ # (scale refit folded into part's existing per-row scale; recon already in normalized space)
128
+ codes_r = codes.reshape(C, 1, out, ng).to(torch.int16).cpu()
129
+ # write full-256 arrays: cold updated, hot slots keep encode-part values (unused for hot)
130
+ full_codes = part["w13_codes"].clone()
131
+ full_codes[torch.tensor(cold), 0] = codes_r[:, 0]
132
+ return {
133
+ "expert_ids": part["expert_ids"],
134
+ "w13_codes": full_codes,
135
+ "w13_codebooks": [cb.half().cpu()],
136
+ "w13_book_entries": part["w13_book_entries"],
137
+ "w13_scales": part["w13_scales"],
138
+ "cold_experts": cold,
139
+ "rel_err_before": err_before, "rel_err_after": err,
140
+ }
141
+
142
+ if __name__ == "__main__":
143
+ ap = argparse.ArgumentParser()
144
+ ap.add_argument("--layer", type=int, required=True)
145
+ ap.add_argument("--iters", type=int, default=2)
146
+ ap.add_argument("--save", action="store_true")
147
+ a = ap.parse_args()
148
+ os.makedirs(OUT, exist_ok=True)
149
+ t0 = time.time()
150
+ r = converge_layer(a.layer, a.iters)
151
+ print(f"L{a.layer} w13: {r['rel_err_before']:.4f} -> {r['rel_err_after']:.4f} ({time.time()-t0:.0f}s)")
152
+ if a.save:
153
+ torch.save(r, f"{OUT}/layer_{a.layer}_w13.pt"); print("saved.")
code/converge_all.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Full w13 activation-aware converge (Phase E1) across all MoE layers.
3
+ Shards layers across CUDA_VISIBLE_DEVICES GPUs, 1 spawn-worker/GPU, resumable
4
+ (skips layers already in parts_conv/). 2 iters (gains plateau after iter0).
5
+ Run: CUDA_VISIBLE_DEVICES=4,5,6,7 python converge_all.py
6
+ """
7
+ import os, time
8
+ import torch.multiprocessing as mp
9
+ import ink_common as ic
10
+
11
+ import os as _os
12
+ OUT = _os.environ.get("INK_OUT", "/data/inkling-quant/parts_conv")
13
+ ITERS = 2
14
+
15
+ def worker(gpu, layers):
16
+ os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu)
17
+ os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
18
+ import torch, converge as cv
19
+ for L in layers:
20
+ out = f"{OUT}/layer_{L}_w13.pt"
21
+ if os.path.exists(out):
22
+ print(f"[gpu{gpu}] skip L{L} (exists)", flush=True); continue
23
+ t0 = time.time()
24
+ try:
25
+ r = cv.converge_layer(L, ITERS, device="cuda")
26
+ torch.save(r, out)
27
+ print(f"[gpu{gpu}] L{L} w13 {r['rel_err_before']:.4f}->{r['rel_err_after']:.4f} "
28
+ f"({time.time()-t0:.0f}s) saved", flush=True)
29
+ except Exception as ex:
30
+ print(f"[gpu{gpu}] L{L} FAILED: {ex}", flush=True)
31
+
32
+ def main():
33
+ os.makedirs(OUT, exist_ok=True)
34
+ gpus = [int(x) for x in os.environ.get("CUDA_VISIBLE_DEVICES", "0").split(",")]
35
+ layers = ic.moe_layers()
36
+ todo = [L for L in layers if not os.path.exists(f"{OUT}/layer_{L}_w13.pt")]
37
+ print(f"{len(layers)} layers, {len(todo)} remaining, {len(gpus)} GPUs {gpus}", flush=True)
38
+ shards = {g: [] for g in gpus}
39
+ for i, L in enumerate(todo):
40
+ shards[gpus[i % len(gpus)]].append(L)
41
+ mp.set_start_method("spawn", force=True)
42
+ ps = [mp.Process(target=worker, args=(g, shards[g])) for g in gpus]
43
+ for p in ps: p.start()
44
+ for p in ps: p.join()
45
+ done = sum(1 for L in layers if os.path.exists(f"{OUT}/layer_{L}_w13.pt"))
46
+ print(f"CONVERGE DONE: {done}/{len(layers)} layers", flush=True)
47
+
48
+ if __name__ == "__main__":
49
+ main()
code/converge_w2.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Activation-aware AQLM convergence (Phase E1) for Inkling cold experts, w2.
3
+
4
+ Mirrors the validated w13 converger (converge.py) with the TWO w2 differences:
5
+
6
+ 1. HESSIAN INPUT is the per-expert intermediate (down-proj input), not the MoE
7
+ hidden x. For each cold expert e with routed calib tokens x_e [n_e,6144]:
8
+ gate_e = teacher_w13_e[0::2] ([3072,6144]) # row-interleaved gate|up
9
+ up_e = teacher_w13_e[1::2] ([3072,6144])
10
+ inter = silu(gate_e @ x_e^T) * (up_e @ x_e^T) -> [3072, n_e]
11
+ h_e = mean_tokens(inter^2) -> [3072] (clamp 1e-8)
12
+ w2 teacher weight is [6144,3072] (out=6144, in=3072) so groups-of-8 run along
13
+ the 3072 input dim -> ng=384.
14
+
15
+ 2. TWO BOOKS. v = W/s (row-normalized teacher) is reconstructed additively as
16
+ cb0[c0] + cb1[c1] (book0 @65536 encodes v, book1 @256 encodes the residual
17
+ v - cb0[c0]). Coordinate descent per iter:
18
+ (a) c0 = weighted re-encode of v against cb0
19
+ (b) resid = v - cb0[c0]
20
+ (c) c1 = weighted re-encode of resid against cb1
21
+ (d) update cb0 (weighted mean of v over c0), then cb1 (weighted mean of
22
+ v - cb0_new[c0] over c1); weight = h_e[group cols] * s^2 per row.
23
+
24
+ Objective (same as w13): min sum_{e,o,g} s[e,o]^2 * || (v - recon) * sqrt(h_e[g]) ||^2.
25
+ Warm-starts codes+codebooks from parts/layer_{L}_w2.pt. Only cold experts are
26
+ converged (per assign_512k.json complement); hot slots keep encode values.
27
+
28
+ Writes /data/inkling-quant/parts_conv/layer_{L}_w2.pt (same schema as the encode
29
+ part) + rel_err_before/after (unweighted) and werr_before/after (weighted).
30
+
31
+ NOTE vs w13: wencode / codebook-update / err reductions run in fp32 here (w13
32
+ used fp16). w2's Hessian is E[intermediate^2] which is far larger in magnitude
33
+ than w13's E[x^2]; fp32 in the h-touching ops avoids fp16 overflow->nan. This
34
+ does not change the objective, only the arithmetic precision.
35
+ """
36
+ from __future__ import annotations
37
+ import argparse, os, time, json
38
+ import torch
39
+ import torch.nn.functional as F
40
+ import ink_common as ic
41
+
42
+ import os as _os
43
+ PARTS = _os.environ.get("INK_PARTS", "/data/inkling-quant/parts")
44
+ OUT = _os.environ.get("INK_OUT", "/data/inkling-quant/parts_conv")
45
+ ACTS = _os.environ.get("INK_ACTS", "/data/inkling-quant/acts")
46
+ ASSIGN = _os.environ.get("INK_ASSIGN", "/data/inkling-quant/assign_512k.json")
47
+ G = 8; CHUNK = 16384
48
+ OUTD = 6144; IND = 3072; NG = IND // G # w2: out=6144, in=3072, ng=384
49
+
50
+
51
+ def cold_experts(layer, assign):
52
+ hot = set(assign["hot_experts"].get(str(layer), []))
53
+ return [e for e in range(ic.N_EXPERTS) if e not in hot]
54
+
55
+
56
+ def chunk_weight(st, n, out_dim, ng, hsqrt):
57
+ """Per-chunk sqrt-weight[n,8] = sqrt(h_e[group cols]); depends only on (e,g)."""
58
+ idx = torch.arange(st, st + n, device=hsqrt.device)
59
+ e = idx // (out_dim * ng); g = idx % ng
60
+ return hsqrt[e, g] # [n,8]
61
+
62
+
63
+ def wencode(vecs, cb, out_dim, ng, hsqrt, resid_cb=None, resid_codes=None):
64
+ """codes[N] int32 = weighted nearest of (vecs - resid) against cb.
65
+ If resid_cb/resid_codes given, target = vecs - resid_cb[resid_codes] (book1).
66
+ Weights (sqrt(h)) applied on the fly; fp32 to stay overflow-safe for w2."""
67
+ cbf = cb.float(); cb2 = (cbf * cbf)
68
+ out = torch.empty(vecs.shape[0], dtype=torch.int32, device=vecs.device)
69
+ for s in range(0, vecs.shape[0], CHUNK):
70
+ v = vecs[s:s + CHUNK].float()
71
+ if resid_cb is not None:
72
+ v = v - resid_cb[resid_codes[s:s + CHUNK].long()].float()
73
+ w = chunk_weight(s, v.shape[0], out_dim, ng, hsqrt).float() # [n,8]
74
+ score = 2.0 * ((v * w) @ cbf.t()) - (w @ cb2.t()) # [n,K]
75
+ out[s:s + CHUNK] = score.argmax(-1).to(torch.int32)
76
+ return out
77
+
78
+
79
+ def _wupdate(vecs, codes, K, out_dim, ng, hsqrt, s2, sub_cb=None, sub_codes=None):
80
+ """Weighted codebook update: cb_k = sum(r*t)/sum(r), r = h(e,g)*s^2(e,o),
81
+ t = vecs - sub_cb[sub_codes] (residual target for book1; vecs for book0).
82
+ Returns new cb [K,8]; entries with no assigned weight left at 0 (caller keeps
83
+ the old row)."""
84
+ newcb = torch.zeros(K, G, device=vecs.device)
85
+ wsum = torch.zeros(K, G, device=vecs.device)
86
+ for st in range(0, vecs.shape[0], CHUNK * 8):
87
+ n = min(CHUNK * 8, vecs.shape[0] - st)
88
+ idx = torch.arange(st, st + n, device=vecs.device)
89
+ e = idx // (out_dim * ng); o = (idx // ng) % out_dim; g = idx % ng
90
+ r = (hsqrt[e, g].float() ** 2) * s2[e, o].unsqueeze(1) # [n,8] = h*s^2
91
+ t = vecs[st:st + n].float()
92
+ if sub_cb is not None:
93
+ t = t - sub_cb[sub_codes[st:st + n].long()].float()
94
+ c = codes[st:st + n].long()
95
+ newcb.index_add_(0, c, t * r); wsum.index_add_(0, c, r)
96
+ return newcb, wsum
97
+
98
+
99
+ def _recon_chunk(c0, c1, cb0, cb1):
100
+ return cb0[c0.long()] + cb1[c1.long()]
101
+
102
+
103
+ def rel_err2(vecs, c0, c1, cb0, cb1):
104
+ """Unweighted weight-space rel err of the 2-book recon (chunked, fp32)."""
105
+ num = torch.zeros((), device=vecs.device); den = torch.zeros((), device=vecs.device)
106
+ for s in range(0, vecs.shape[0], CHUNK * 8):
107
+ n = min(CHUNK * 8, vecs.shape[0] - s)
108
+ v = vecs[s:s + n].float()
109
+ r = _recon_chunk(c0[s:s + n], c1[s:s + n], cb0, cb1).float()
110
+ num += (v - r).pow(2).sum(); den += v.pow(2).sum()
111
+ return float((num.sqrt() / den.sqrt()))
112
+
113
+
114
+ def werr2(vecs, c0, c1, cb0, cb1, out_dim, ng, hsqrt, s2):
115
+ """Weighted (activation-aware) rel err of the 2-book recon = the objective:
116
+ w = h(e,g)*s^2(e,o). Scale-invariant to any global rescale of h."""
117
+ num = torch.zeros((), device=vecs.device); den = torch.zeros((), device=vecs.device)
118
+ for st in range(0, vecs.shape[0], CHUNK * 8):
119
+ n = min(CHUNK * 8, vecs.shape[0] - st)
120
+ idx = torch.arange(st, st + n, device=vecs.device)
121
+ e = idx // (out_dim * ng); o = (idx // ng) % out_dim; g = idx % ng
122
+ w = (hsqrt[e, g].float() ** 2) * s2[e, o].unsqueeze(1) # [n,8]
123
+ v = vecs[st:st + n].float()
124
+ r = _recon_chunk(c0[st:st + n], c1[st:st + n], cb0, cb1).float()
125
+ num += (w * (v - r).pow(2)).sum(); den += (w * v.pow(2)).sum()
126
+ return float((num.sqrt() / den.sqrt()))
127
+
128
+
129
+ def build_w2(layer, cold, device):
130
+ """Per-cold-expert: normalized vecs [C*out*ng,8] half, per-row scale s [C,out],
131
+ and diagonal input Hessian hsqrt [C,ng,8] (sqrt of E[intermediate^2]).
132
+ Built per expert to avoid a full float teacher copy (OOM)."""
133
+ a = torch.load(f"{ACTS}/acts_layer_{layer}.pt")
134
+ x = a["x"].to(device).float() # [N,6144]
135
+ ids = a["ids"].to(device) # [N,6]
136
+ tc = ic.ShardReader(ic.BF16_DIR)
137
+ C = len(cold)
138
+ s = torch.empty(C, OUTD, device=device)
139
+ vecs = torch.empty(C * OUTD * NG, G, dtype=torch.float16, device=device)
140
+ hsqrt = torch.empty(C, NG, G, device=device)
141
+ for ci, e in enumerate(cold):
142
+ # ---- weight vectors (teacher w2) ----
143
+ w2e = ic.teacher_expert(tc, layer, "w2", e, device=device).float() # [6144,3072]
144
+ se = w2e.abs().mean(-1).clamp_min(1e-8) # [6144]
145
+ s[ci] = se
146
+ base = ci * OUTD * NG
147
+ vecs[base:base + OUTD * NG] = (w2e / se.unsqueeze(-1)).reshape(OUTD * NG, G).half()
148
+ del w2e
149
+ # ---- Hessian: intermediate = silu(gate@x^T)*(up@x^T), teacher w13 ----
150
+ m = (ids == e).any(1)
151
+ if int(m.sum()) >= 8:
152
+ xe = x[m] # [n_e,6144]
153
+ w13e = ic.teacher_expert(tc, layer, "w13", e, device=device).float() # [6144,6144]
154
+ gate = w13e[0::2]; up = w13e[1::2] # [3072,6144] each
155
+ del w13e
156
+ g_out = gate @ xe.t(); u_out = up @ xe.t() # [3072,n_e]
157
+ inter = F.silu(g_out) * u_out # [3072,n_e]
158
+ h_e = inter.pow(2).mean(1).clamp_min(1e-8) # [3072]
159
+ del xe, g_out, u_out, inter
160
+ else:
161
+ h_e = torch.ones(IND, device=device)
162
+ hsqrt[ci] = h_e.reshape(NG, G).sqrt()
163
+ del x, ids
164
+ torch.cuda.empty_cache()
165
+ return vecs, s, hsqrt
166
+
167
+
168
+ def converge_layer(layer, iters, device="cuda"):
169
+ assign = json.load(open(ASSIGN))
170
+ cold = cold_experts(layer, assign)
171
+ part = torch.load(f"{PARTS}/layer_{layer}_w2.pt")
172
+ cb0 = part["w2_codebooks"][0].float().to(device) # [65536,8]
173
+ cb1 = part["w2_codebooks"][1].float().to(device) # [256,8]
174
+ K0 = cb0.shape[0]; K1 = cb1.shape[0]
175
+ C = len(cold)
176
+ coldt = torch.tensor(cold)
177
+
178
+ vecs, s, hsqrt = build_w2(layer, cold, device)
179
+ s2 = (s * s) # [C,out]
180
+
181
+ # warm-start codes for cold experts (int16 -> positive index; book0 wraps mod 65536)
182
+ c0 = (part["w2_codes"][:, 0][coldt].reshape(-1).to(device).long() % K0).to(torch.int32)
183
+ c1 = (part["w2_codes"][:, 1][coldt].reshape(-1).to(device).long() % K1).to(torch.int32)
184
+
185
+ err_before = rel_err2(vecs, c0, c1, cb0, cb1)
186
+ werr_before = werr2(vecs, c0, c1, cb0, cb1, OUTD, NG, hsqrt, s2)
187
+ print(f" L{layer} w2 warm-start: unweighted {err_before:.4f} | WEIGHTED {werr_before:.4f}", flush=True)
188
+
189
+ err = err_before; we = werr_before
190
+ for it in range(iters):
191
+ # (a) re-encode book0 against cb0 (target v)
192
+ c0 = wencode(vecs, cb0, OUTD, NG, hsqrt)
193
+ # (b)+(c) re-encode book1 against cb1 (target v - cb0[c0])
194
+ c1 = wencode(vecs, cb1, OUTD, NG, hsqrt, resid_cb=cb0, resid_codes=c0)
195
+ # (d) update cb0 (weighted mean of v over c0)
196
+ newcb0, wsum0 = _wupdate(vecs, c0, K0, OUTD, NG, hsqrt, s2)
197
+ nz0 = wsum0[:, 0] > 0
198
+ cb0[nz0] = newcb0[nz0] / wsum0[nz0].clamp_min(1e-12)
199
+ # then update cb1 (weighted mean of v - cb0_new[c0] over c1)
200
+ newcb1, wsum1 = _wupdate(vecs, c1, K1, OUTD, NG, hsqrt, s2, sub_cb=cb0, sub_codes=c0)
201
+ nz1 = wsum1[:, 0] > 0
202
+ cb1[nz1] = newcb1[nz1] / wsum1[nz1].clamp_min(1e-12)
203
+
204
+ err = rel_err2(vecs, c0, c1, cb0, cb1)
205
+ we = werr2(vecs, c0, c1, cb0, cb1, OUTD, NG, hsqrt, s2)
206
+ print(f" L{layer} w2 iter{it}: unweighted {err:.4f} | WEIGHTED {we:.4f}", flush=True)
207
+
208
+ # reshape codes back to [C,out,ng] and store as int16 (book0 wraps to signed)
209
+ c0r = c0.reshape(C, OUTD, NG).to(torch.int16).cpu()
210
+ c1r = c1.reshape(C, OUTD, NG).to(torch.int16).cpu()
211
+ full_codes = part["w2_codes"].clone() # [256,2,out,ng]
212
+ full_codes[coldt, 0] = c0r
213
+ full_codes[coldt, 1] = c1r
214
+ return {
215
+ "expert_ids": part["expert_ids"],
216
+ "w2_codes": full_codes,
217
+ "w2_codebooks": [cb0.half().cpu(), cb1.half().cpu()],
218
+ "w2_book_entries": part["w2_book_entries"],
219
+ "w2_scales": part["w2_scales"],
220
+ "cold_experts": cold,
221
+ "rel_err_before": err_before, "rel_err_after": err,
222
+ "werr_before": werr_before, "werr_after": we,
223
+ }
224
+
225
+
226
+ if __name__ == "__main__":
227
+ ap = argparse.ArgumentParser()
228
+ ap.add_argument("--layer", type=int, required=True)
229
+ ap.add_argument("--iters", type=int, default=2)
230
+ ap.add_argument("--save", action="store_true")
231
+ a = ap.parse_args()
232
+ os.makedirs(OUT, exist_ok=True)
233
+ t0 = time.time()
234
+ r = converge_layer(a.layer, a.iters)
235
+ print(f"L{a.layer} w2: unw {r['rel_err_before']:.4f}->{r['rel_err_after']:.4f} | "
236
+ f"WEIGHTED {r['werr_before']:.4f}->{r['werr_after']:.4f} ({time.time()-t0:.0f}s)")
237
+ if a.save:
238
+ torch.save(r, f"{OUT}/layer_{a.layer}_w2.pt"); print("saved.")
code/converge_w2_all.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Full w2 activation-aware converge (Phase E1) across all MoE layers.
3
+ Shards layers across CUDA_VISIBLE_DEVICES GPUs, 1 spawn-worker/GPU, resumable
4
+ (skips layers already in parts_conv/). 2 iters.
5
+ Run: CUDA_VISIBLE_DEVICES=4,5,6,7 python converge_w2_all.py
6
+ """
7
+ import os, time
8
+ import torch.multiprocessing as mp
9
+ import ink_common as ic
10
+
11
+ import os as _os
12
+ OUT = _os.environ.get("INK_OUT", "/data/inkling-quant/parts_conv")
13
+ ITERS = 2
14
+
15
+
16
+ def worker(gpu, layers):
17
+ os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu)
18
+ os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
19
+ import torch, converge_w2 as cv
20
+ for L in layers:
21
+ out = f"{OUT}/layer_{L}_w2.pt"
22
+ if os.path.exists(out):
23
+ print(f"[gpu{gpu}] skip L{L} (exists)", flush=True); continue
24
+ t0 = time.time()
25
+ try:
26
+ r = cv.converge_layer(L, ITERS, device="cuda")
27
+ tmp = out + ".tmp"
28
+ torch.save(r, tmp); os.replace(tmp, out)
29
+ print(f"[gpu{gpu}] L{L} w2 unw {r['rel_err_before']:.4f}->{r['rel_err_after']:.4f} "
30
+ f"| WEIGHTED {r['werr_before']:.4f}->{r['werr_after']:.4f} "
31
+ f"({time.time()-t0:.0f}s) saved", flush=True)
32
+ except Exception as ex:
33
+ import traceback
34
+ print(f"[gpu{gpu}] L{L} FAILED: {ex}", flush=True)
35
+ traceback.print_exc()
36
+
37
+
38
+ def main():
39
+ os.makedirs(OUT, exist_ok=True)
40
+ gpus = [int(x) for x in os.environ.get("CUDA_VISIBLE_DEVICES", "0").split(",")]
41
+ layers = ic.moe_layers()
42
+ todo = [L for L in layers if not os.path.exists(f"{OUT}/layer_{L}_w2.pt")]
43
+ print(f"{len(layers)} layers, {len(todo)} remaining, {len(gpus)} GPUs {gpus}", flush=True)
44
+ shards = {g: [] for g in gpus}
45
+ for i, L in enumerate(todo):
46
+ shards[gpus[i % len(gpus)]].append(L)
47
+ mp.set_start_method("spawn", force=True)
48
+ ps = [mp.Process(target=worker, args=(g, shards[g])) for g in gpus]
49
+ for p in ps: p.start()
50
+ for p in ps: p.join()
51
+ done = sum(1 for L in layers if os.path.exists(f"{OUT}/layer_{L}_w2.pt"))
52
+ print(f"CONVERGE_W2 DONE: {done}/{len(layers)} layers", flush=True)
53
+
54
+
55
+ if __name__ == "__main__":
56
+ main()
code/encode_all.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Encode ALL routed experts (AQLM) for every MoE layer, ship scheme
3
+ (w13=[65536], w2=[65536,256]). Shards (layer,proj) jobs across the GPUs given
4
+ in CUDA_VISIBLE_DEVICES, one worker process per GPU. Resumable: skips parts
5
+ already on disk. Codebooks are shared per layer, so we encode all 256 experts;
6
+ the checkpoint build later slices hot vs cold per assign_512k.json.
7
+ Run: CUDA_VISIBLE_DEVICES=4,5,6,7 python encode_all.py
8
+ """
9
+ import os, sys, time, json
10
+ import torch
11
+ import torch.multiprocessing as mp
12
+ import ink_common as ic
13
+
14
+ PARTS = "/data/inkling-quant/parts"
15
+ BOOK = {"w13": [65536], "w2": [65536, 256]}
16
+
17
+ def worker(gpu, jobs, ret):
18
+ os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu)
19
+ os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
20
+ import torch
21
+ import ink_aqlm as aq
22
+ for (layer, proj) in jobs:
23
+ out = f"{PARTS}/layer_{layer}_{proj}.pt"
24
+ if os.path.exists(out):
25
+ print(f"[gpu{gpu}] skip L{layer} {proj} (exists)", flush=True); continue
26
+ t0 = time.time()
27
+ try:
28
+ r = aq.quantize_layer(layer, proj, BOOK[proj], device="cuda")
29
+ torch.save(r, out)
30
+ print(f"[gpu{gpu}] L{layer} {proj} rel_err={r['rel_err']:.4f} ({time.time()-t0:.0f}s) -> saved", flush=True)
31
+ except Exception as e:
32
+ print(f"[gpu{gpu}] L{layer} {proj} FAILED: {e}", flush=True)
33
+
34
+ def main():
35
+ os.makedirs(PARTS, exist_ok=True)
36
+ gpus = [int(x) for x in os.environ.get("CUDA_VISIBLE_DEVICES", "0").split(",")]
37
+ layers = ic.moe_layers()
38
+ jobs = [(L, p) for L in layers for p in ("w13", "w2")]
39
+ todo = [j for j in jobs if not os.path.exists(f"{PARTS}/layer_{j[0]}_{j[1]}.pt")]
40
+ print(f"{len(jobs)} total jobs, {len(todo)} remaining, {len(gpus)} GPUs {gpus}", flush=True)
41
+ # round-robin assign
42
+ shards = {g: [] for g in gpus}
43
+ for i, j in enumerate(todo):
44
+ shards[gpus[i % len(gpus)]].append(j)
45
+ mp.set_start_method("spawn", force=True)
46
+ procs = []
47
+ for g in gpus:
48
+ p = mp.Process(target=worker, args=(g, shards[g], None)); p.start(); procs.append(p)
49
+ for p in procs: p.join()
50
+ done = sum(1 for j in jobs if os.path.exists(f"{PARTS}/layer_{j[0]}_{j[1]}.pt"))
51
+ print(f"DONE: {done}/{len(jobs)} parts present", flush=True)
52
+
53
+ if __name__ == "__main__":
54
+ main()
code/gpqa40.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import glob, re, json, time, concurrent.futures as cf, urllib.request
2
+ import pyarrow.parquet as pq
3
+ K="sk-98f2447ba021acf1c9ac0aea25f65bb58cf3066dcda734e5"
4
+ URL="http://localhost:8001/v1/chat/completions"; MODEL="GLM-5.2-NVFP4-AQLM-hybrid"
5
+ N=40; CONC=6; MAXTOK=20000
6
+ p=glob.glob("/home/coder/.cache/huggingface/hub/datasets--fingertap--GPQA-Diamond/snapshots/*/test/*.parquet")[0]
7
+ d=pq.read_table(p).to_pydict(); Q=d["question"]; A=d["answer"]
8
+ rows=list(range(N))
9
+ def ask(i):
10
+ q=Q[i]
11
+ prompt=(q.strip()+"\n\nThink step by step, then end with a single line exactly:\nAnswer: <letter>")
12
+ body=json.dumps({"model":MODEL,"messages":[{"role":"user","content":prompt}],
13
+ "max_tokens":MAXTOK,"temperature":0}).encode()
14
+ req=urllib.request.Request(URL,data=body,headers={"Authorization":f"Bearer {K}","Content-Type":"application/json"})
15
+ for _ in range(2):
16
+ try:
17
+ r=json.load(urllib.request.urlopen(req,timeout=900))
18
+ txt=r["choices"][0]["message"]["content"]; fin=r["choices"][0]["finish_reason"]
19
+ m=re.findall(r"Answer:\s*\**([A-Da-d])\b", txt)
20
+ if not m: m=re.findall(r"\b([A-Da-d])\b\s*$", txt.strip())
21
+ pred=(m[-1].upper() if m else "?")
22
+ return i,pred,(pred==A[i].upper()),r["usage"]["completion_tokens"],fin
23
+ except Exception as e:
24
+ last=str(e)[:60]; time.sleep(2)
25
+ return i,"ERR",False,0,last
26
+ t0=time.time(); res={}
27
+ with cf.ThreadPoolExecutor(max_workers=CONC) as ex:
28
+ for i,pred,ok,tok,fin in ex.map(ask,rows):
29
+ res[i]=(pred,ok,tok,fin)
30
+ print(f"q{i:02d} gold={A[i]} pred={pred} {'OK ' if ok else 'x '} tok={tok} {fin}",flush=True)
31
+ c=sum(1 for i in rows if res[i][1]); trunc=sum(1 for i in rows if res[i][3]=="length")
32
+ print(f"\nGPQA-40 (fingertap MC, current TP4-no-DCP serve): {c}/{N} = {c/N:.3f}")
33
+ print(f"reference fixA full-198 = 0.889 ; e4m3 = 0.884 ; (this is a 40-subset, expect +-0.05 noise)")
34
+ print(f"truncated(length-capped) answers: {trunc} (these may undercount) | wall {time.time()-t0:.0f}s")
35
+ json.dump({str(i):res[i] for i in rows}, open("/data/inkling-quant/gpqa40_current.json","w"))
code/ink_aqlm.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """AQLM (additive k-means) 2-bit quantizer for Inkling routed experts.
3
+
4
+ Per (layer, proj) over a set of experts, minimize ||W - What||^2 (init-grade;
5
+ activation-aware convergence is a later pass). Overfit to Inkling's FUSED
6
+ layout: teacher W is [E, out, in] bf16 read from /data/inkling.
7
+
8
+ Scheme (mirrors the GLM hybrid, adapted to fused tensors):
9
+ - per-(expert,out-channel) scale s[e,o] = mean|W[e,o,:]| (rows normalized by s)
10
+ - groups of GDIM=8 along the INPUT dim -> vectors in R^8
11
+ - ENTRIES=65536 shared codebook(s) fit by GPU k-means on a row sample
12
+ (shared across all experts of the layer); books=2 encodes a 2nd-codebook
13
+ residual (used for w2), books=1 for w13
14
+ - least-squares refit of s after encoding
15
+ Output part: /data/inkling-quant/parts/layer_{L}_{proj}.pt with
16
+ expert_ids, {proj}_codes int16 [E, books, out, in/8], {proj}_codebooks fp16
17
+ [books, ENTRIES, 8], {proj}_scales fp16 [E, out], err_before/after.
18
+ """
19
+ from __future__ import annotations
20
+ import argparse, os, time
21
+ import torch
22
+ import ink_common as ic
23
+
24
+ ENTRIES = 65536
25
+ GDIM = 8
26
+ SAMPLE_VECS = 4_000_000
27
+ KMEANS_ITERS = 12
28
+ CHUNK = 32_768 # scores [CHUNK, ENTRIES] fp16 ~4.3GB
29
+
30
+ def _argmax_codes(v16, cent16, cn16):
31
+ d = v16 @ cent16.t() # [n, ENTRIES]
32
+ d.mul_(2).sub_(cn16) # 2 v.c - |c|^2 (argmax == nearest)
33
+ return d.argmax(-1).to(torch.int32)
34
+
35
+ def _encode(vecs16, cent16):
36
+ cn16 = (cent16.float() * cent16.float()).sum(-1).half()
37
+ out = torch.empty(vecs16.shape[0], dtype=torch.int32, device=vecs16.device)
38
+ for s in range(0, vecs16.shape[0], CHUNK):
39
+ out[s:s+CHUNK] = _argmax_codes(vecs16[s:s+CHUNK], cent16, cn16)
40
+ return out
41
+
42
+ def _kmeans(vecs16, iters, gen, entries=ENTRIES):
43
+ n = vecs16.shape[0]; dev = vecs16.device
44
+ cent = vecs16[torch.randperm(n, generator=gen, device=dev)[:entries]].float()
45
+ for _ in range(iters):
46
+ cent16 = cent.half(); cn16 = (cent*cent).sum(-1).half()
47
+ sums = torch.zeros(entries, GDIM, device=dev); cnts = torch.zeros(entries, device=dev)
48
+ for s in range(0, n, CHUNK):
49
+ v = vecs16[s:s+CHUNK]; a = _argmax_codes(v, cent16, cn16).long()
50
+ sums.index_add_(0, a, v.float()); cnts.index_add_(0, a, torch.ones_like(a, dtype=torch.float32))
51
+ nz = cnts > 0; cent[nz] = sums[nz] / cnts[nz].unsqueeze(1)
52
+ return cent.half()
53
+
54
+ def quantize_layer(layer, proj, book_entries, device="cuda", seed=0):
55
+ """book_entries: list of codebook sizes per book, e.g. w13=[65536], w2=[65536,256]."""
56
+ books = len(book_entries)
57
+ tc = ic.ShardReader(ic.BF16_DIR)
58
+ gen = torch.Generator(device=device); gen.manual_seed(seed + 1000*layer + (0 if proj=="w13" else 1))
59
+ # stack teacher experts in bf16 (avoid full float copies -> OOM on w13): [E, out, in]
60
+ W = torch.stack([ic.teacher_expert(tc, layer, proj, e, device=device) for e in range(ic.N_EXPERTS)])
61
+ E, out, ind = W.shape
62
+ assert ind % GDIM == 0
63
+ s = W.abs().float().mean(-1).clamp_min(1e-8) # [E, out] per-row scale (fp32)
64
+ vecs = (W.float() / s.unsqueeze(-1)).reshape(E*out*(ind//GDIM), GDIM).half() # normalized groups-of-8
65
+ del W; torch.cuda.empty_cache()
66
+ codes = torch.empty(books, vecs.shape[0], dtype=torch.int32, device=device)
67
+ codebooks = [] # variable sizes per book
68
+ recon = None
69
+ for b in range(books):
70
+ target = vecs if b == 0 else (vecs - recon).half()
71
+ idx = torch.randperm(target.shape[0], generator=gen, device=device)[:SAMPLE_VECS]
72
+ cbb = _kmeans(target[idx], KMEANS_ITERS, gen, entries=book_entries[b])
73
+ codes[b] = _encode(target, cbb); codebooks.append(cbb.cpu())
74
+ contrib = cbb[codes[b].long()]
75
+ recon = contrib if recon is None else (recon + contrib).half()
76
+ del target
77
+ # rel err (weight-space, unweighted) — chunked to avoid full float copies
78
+ num = torch.zeros((), device=device); den = torch.zeros((), device=device)
79
+ for st in range(0, vecs.shape[0], CHUNK*8):
80
+ v = vecs[st:st+CHUNK*8].float(); r = recon[st:st+CHUNK*8].float()
81
+ num += (v-r).pow(2).sum(); den += v.pow(2).sum()
82
+ rel = (num.sqrt() / den.sqrt())
83
+ codes = codes.reshape(books, E, out, ind//GDIM).permute(1,0,2,3).contiguous() # [E,books,out,in/8]
84
+ return {
85
+ "expert_ids": torch.arange(E, dtype=torch.int32),
86
+ f"{proj}_codes": codes.to(torch.int16).cpu(),
87
+ f"{proj}_codebooks": codebooks, # list of [entries_b, 8] fp16
88
+ f"{proj}_book_entries": list(book_entries),
89
+ f"{proj}_scales": s.to(torch.float16).cpu(),
90
+ "rel_err": float(rel),
91
+ }
92
+
93
+ if __name__ == "__main__":
94
+ ap = argparse.ArgumentParser()
95
+ ap.add_argument("--layer", type=int, default=3)
96
+ ap.add_argument("--proj", default="w2", choices=["w13","w2"])
97
+ ap.add_argument("--device", default="cuda")
98
+ ap.add_argument("--save", action="store_true")
99
+ a = ap.parse_args()
100
+ # ship scheme: w13=[65536], w2=[65536,256]
101
+ be = [65536] if a.proj=="w13" else [65536, 256]
102
+ t0=time.time()
103
+ r = quantize_layer(a.layer, a.proj, be, a.device)
104
+ print(f"L{a.layer} {a.proj} book_entries={be}: weight-space rel_err={r['rel_err']:.4f} ({time.time()-t0:.1f}s)")
105
+ if a.save:
106
+ os.makedirs("/data/inkling-quant/parts", exist_ok=True)
107
+ torch.save(r, f"/data/inkling-quant/parts/layer_{a.layer}_{a.proj}.pt")
108
+ print("saved.")
code/ink_common.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Foundational tooling for the Inkling NVFP4+AQLM hybrid.
3
+
4
+ Handles Inkling's FUSED per-layer expert layout:
5
+ NVFP4 (/data/inkling-nvfp4): mlp.experts.w13_weight u8 [E, out, in/2]
6
+ + .scale f8_e4m3 [E, out, in/16] (block-16) + .scale2 f32 [E] (per-expert global)
7
+ BF16 teacher (/data/inkling): mlp.experts.w13_weight bf16 [E, out, in]
8
+ w13: out=6144 (gate|up=2*3072), in=6144. w2: out=6144, in=3072.
9
+ """
10
+ from __future__ import annotations
11
+ import json, os
12
+ import torch
13
+
14
+ NVFP4_DIR = os.environ.get("INK_NVFP4_DIR", "/data/inkling-nvfp4")
15
+ BF16_DIR = os.environ.get("INK_BF16_DIR", "/data/inkling")
16
+ N_EXPERTS = 256
17
+ G = 8 # AQLM group dim
18
+ # NVFP4 e2m1 value table (low nibble = first of each pair), matches GLM/modelopt.
19
+ FP4_LUT = torch.tensor([0.0,0.5,1.0,1.5,2.0,3.0,4.0,6.0,
20
+ -0.0,-0.5,-1.0,-1.5,-2.0,-3.0,-4.0,-6.0], dtype=torch.float32)
21
+
22
+ class ShardReader:
23
+ """Random access to tensors across a repo's safetensors shards."""
24
+ def __init__(self, repo_dir: str):
25
+ idx = json.load(open(os.path.join(repo_dir, "model.safetensors.index.json")))
26
+ self.weight_map = idx["weight_map"]
27
+ self.repo_dir = repo_dir
28
+ self._open = {}
29
+ def has(self, name: str) -> bool:
30
+ return name in self.weight_map
31
+ def get(self, name: str) -> torch.Tensor:
32
+ from safetensors import safe_open
33
+ shard = self.weight_map[name]
34
+ if shard not in self._open:
35
+ self._open[shard] = safe_open(os.path.join(self.repo_dir, shard), framework="pt")
36
+ return self._open[shard].get_tensor(name)
37
+
38
+ def _pfx(layer: int, proj: str) -> str:
39
+ return f"model.llm.layers.{layer}.mlp.experts.{proj}_weight"
40
+
41
+ def dequant_nvfp4_expert(reader: ShardReader, layer: int, proj: str, e: int, device="cuda") -> torch.Tensor:
42
+ """Dequantize one NVFP4 expert (proj in {'w13','w2'}) -> bf16 [out, in]."""
43
+ p = _pfx(layer, proj)
44
+ lut = FP4_LUT.to(device)
45
+ packed = reader.get(p)[e].to(device) # u8 [out, in/2]
46
+ scale = reader.get(f"{p}.scale")[e].to(device) # f8_e4m3 [out, in/16]
47
+ scale2 = reader.get(f"{p}.scale2")[e].to(torch.float32).to(device) # f32 scalar
48
+ lo = (packed & 0x0F).long(); hi = (packed >> 4).long()
49
+ out, half = packed.shape
50
+ vals = torch.empty(out, half * 2, dtype=torch.float32, device=device)
51
+ vals[:, 0::2] = lut[lo]; vals[:, 1::2] = lut[hi]
52
+ del lo, hi
53
+ blk = scale.to(torch.float32).repeat_interleave(16, dim=1)
54
+ return (vals * blk * scale2).to(torch.bfloat16)
55
+
56
+ def teacher_expert(reader: ShardReader, layer: int, proj: str, e: int, device="cuda") -> torch.Tensor:
57
+ """BF16 teacher expert -> bf16 [out, in]."""
58
+ return reader.get(_pfx(layer, proj))[e].to(device).to(torch.bfloat16)
59
+
60
+ def quantize_nvfp4(W: torch.Tensor):
61
+ """bf16/fp32 [out,in] -> ModelOpt NVFP4 (inverse of dequant_nvfp4_expert):
62
+ packed u8 [out,in/2], scale f8_e4m3 [out,in/16] (block-16), scale2 f32 scalar.
63
+ W ~= lut[code] * scale.repeat_interleave(16) * scale2."""
64
+ dev = W.device; Wf = W.float()
65
+ out, ind = Wf.shape; assert ind % 16 == 0
66
+ lut = FP4_LUT.to(dev)
67
+ amax = Wf.abs().amax().clamp_min(1e-8)
68
+ scale2 = amax / (6.0 * 448.0) # global fp32
69
+ w = Wf / scale2
70
+ wb = w.reshape(out, ind // 16, 16)
71
+ bmax = wb.abs().amax(-1, keepdim=True).clamp_min(1e-8)
72
+ bscale = (bmax / 6.0) # per-block, -> f8
73
+ bscale_f8 = bscale.to(torch.float8_e4m3fn).float().clamp_min(1e-8)
74
+ q = wb / bscale_f8 # in fp4 dynamic range
75
+ # nearest fp4 level
76
+ diff = (q.unsqueeze(-1) - lut.view(1, 1, 1, -1)).abs()
77
+ codes = diff.argmin(-1).to(torch.uint8) # [out, nb, 16] indices 0..15
78
+ codes = codes.reshape(out, ind)
79
+ lo = codes[:, 0::2]; hi = codes[:, 1::2]
80
+ packed = (lo | (hi << 4)).to(torch.uint8) # [out, in/2]
81
+ scale_f8 = bscale.reshape(out, ind // 16).to(torch.float8_e4m3fn)
82
+ return packed, scale_f8, scale2.to(torch.float32).reshape(1)
83
+
84
+ def aqlm_dequant_expert(part, proj, e, device="cuda"):
85
+ """Reconstruct one AQLM expert -> bf16 [out,in] from a converge/encode part.
86
+ part has {proj}_codes [E,books,out,ng] int16, {proj}_codebooks list of
87
+ [entries,8] fp16, {proj}_scales [E,out] fp16."""
88
+ codes = part[f"{proj}_codes"][e].to(device).long() # [books,out,ng]
89
+ cbs = part[f"{proj}_codebooks"] # list of [entries,8]
90
+ scales = part[f"{proj}_scales"][e].to(device).float() # [out]
91
+ books, out, ng = codes.shape
92
+ recon = torch.zeros(out, ng, G, device=device)
93
+ for b in range(books):
94
+ cb = cbs[b].to(device).float() # [entries,8]; neg idx wraps ok for 65536
95
+ recon += cb[codes[b]] # [out,ng,8]
96
+ recon = recon.reshape(out, ng * G) * scales.unsqueeze(-1)
97
+ return recon.to(torch.bfloat16)
98
+
99
+ def dequant_nvfp4_raw(packed, scale_f8, scale2, device="cuda"):
100
+ """Dequant raw NVFP4 tensors (for round-trip checks)."""
101
+ lut = FP4_LUT.to(device)
102
+ packed = packed.to(device); scale_f8 = scale_f8.to(device); scale2 = scale2.to(device).float()
103
+ lo = (packed & 0x0F).long(); hi = (packed >> 4).long()
104
+ out, half = packed.shape
105
+ vals = torch.empty(out, half * 2, dtype=torch.float32, device=device)
106
+ vals[:, 0::2] = lut[lo]; vals[:, 1::2] = lut[hi]
107
+ blk = scale_f8.to(torch.float32).repeat_interleave(16, dim=1)
108
+ return (vals * blk * scale2).to(torch.bfloat16)
109
+
110
+ def moe_layers() -> list[int]:
111
+ """Layers that carry routed experts (skip the 2 dense layers)."""
112
+ idx = json.load(open(os.path.join(NVFP4_DIR, "model.safetensors.index.json")))["weight_map"]
113
+ ls = set()
114
+ for k in idx:
115
+ if ".mlp.experts.w13_weight" == k[k.find(".mlp"):] if ".mlp.experts.w13_weight" in k else False:
116
+ pass
117
+ import re
118
+ for k in idx:
119
+ m = re.match(r"model\.llm\.layers\.(\d+)\.mlp\.experts\.w13_weight$", k)
120
+ if m: ls.add(int(m.group(1)))
121
+ return sorted(ls)
code/merge_acts.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Merge text + image + audio acts into ONE dir per split, so REAP / converge / PV
3
+ see the full multimodal routing. Concatenates x/ids and sums n per layer, capped.
4
+
5
+ train: acts_v3 + acts_v3_img + acts_v3_audio -> acts_v3_all
6
+ held : acts_v3_heldout + acts_v3_img_held + acts_v3_audio_held -> acts_v3_all_held
7
+ """
8
+ import os, glob, torch
9
+ Q = "/data/inkling-quant"
10
+ CAP = 160000 # max token-rows/layer in the merged file (text-heavy but mm represented)
11
+ SETS = {
12
+ "acts_v3_all": ["acts_v3", "acts_v3_img", "acts_v3_audio"],
13
+ "acts_v3_all_held": ["acts_v3_heldout", "acts_v3_img_held", "acts_v3_audio_held"],
14
+ }
15
+
16
+ def layers(dirs):
17
+ s = set()
18
+ for d in dirs:
19
+ for f in glob.glob(f"{Q}/{d}/acts_layer_*.pt"):
20
+ s.add(int(f.split("_")[-1].split(".")[0]))
21
+ return sorted(s)
22
+
23
+ def main():
24
+ for out, srcs in SETS.items():
25
+ od = f"{Q}/{out}"; os.makedirs(od, exist_ok=True)
26
+ present = [s for s in srcs if os.path.isdir(f"{Q}/{s}")]
27
+ print(f">>> {out} <- {present}")
28
+ for L in layers(present):
29
+ xs, ids, n = [], [], 0
30
+ for s in present:
31
+ p = f"{Q}/{s}/acts_layer_{L}.pt"
32
+ if not os.path.exists(p): continue
33
+ a = torch.load(p, map_location="cpu")
34
+ xs.append(a["x"]); ids.append(a["ids"]); n += int(a.get("n", a["x"].shape[0]))
35
+ if not xs: continue
36
+ x = torch.cat(xs, 0); i = torch.cat(ids, 0)
37
+ if x.shape[0] > CAP:
38
+ sel = torch.randperm(x.shape[0])[:CAP]; x = x[sel]; i = i[sel]
39
+ torch.save({"x": x, "ids": i, "n": x.shape[0]}, f"{od}/acts_layer_{L}.pt")
40
+ print(f" wrote {len(layers(present))} layers -> {od}")
41
+
42
+ if __name__ == "__main__":
43
+ main()
code/ppl_eval.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Perplexity + coherence gate for a checkpoint (baseline NVFP4 vs 512k gate).
3
+ Offline vLLM: teacher-forced prompt_logprobs over a fixed held-out text set ->
4
+ mean NLL -> perplexity. Same text for both models => the RATIO is the quality
5
+ signal (absolute may be optimistic since eval text overlaps calib distribution).
6
+ Usage: CUDA_VISIBLE_DEVICES=4,5,6,7 python ppl_eval.py --model DIR --tag NAME
7
+ """
8
+ import argparse, json, math, os
9
+
10
+ def main():
11
+ ap = argparse.ArgumentParser()
12
+ ap.add_argument("--model", required=True)
13
+ ap.add_argument("--tag", required=True)
14
+ ap.add_argument("--n", type=int, default=48)
15
+ # v3: default to the TRULY HELD-OUT set (wikitext-2 test, disjoint from all
16
+ # calib/tuning) so pre/post ppl is honest. Pass --eval-file for others.
17
+ ap.add_argument("--eval-file", default="/data/inkling-quant/ppl_v3.jsonl")
18
+ a = ap.parse_args()
19
+ os.environ.setdefault("VLLM_USE_DEEP_GEMM", "0")
20
+ from vllm import LLM, SamplingParams
21
+ # fixed eval set: first N chunks (identical text for every model -> fair ratio).
22
+ # v3 ppl_v3.jsonl carries a per-chunk "cat" -> report per-domain ppl too.
23
+ rows = [json.loads(l) for l in open(a.eval_file)][:a.n]
24
+ prompts = [r["text"] for r in rows]
25
+ cats = [r.get("cat", "all") for r in rows]
26
+ print(f"PPL_EVAL model={a.model} tag={a.tag} eval_file={a.eval_file} n={len(prompts)}")
27
+ llm = LLM(model=a.model, tensor_parallel_size=4, max_model_len=8192,
28
+ gpu_memory_utilization=0.9, enforce_eager=True, trust_remote_code=True)
29
+ outs = llm.generate(prompts, SamplingParams(max_tokens=1, temperature=0, prompt_logprobs=0))
30
+ tot_nll = 0.0; tot_tok = 0
31
+ by = {} # cat -> [nll_sum, tok]
32
+ for cat, o in zip(cats, outs):
33
+ c = by.setdefault(cat, [0.0, 0])
34
+ for d in o.prompt_logprobs: # list per prompt token; [0] is None
35
+ if not d:
36
+ continue
37
+ lp = next(iter(d.values())).logprob # teacher-forced -> actual token
38
+ tot_nll += -lp; tot_tok += 1
39
+ c[0] += -lp; c[1] += 1
40
+ ppl = math.exp(tot_nll / max(1, tot_tok))
41
+ print(f"PPL_RESULT tag={a.tag} tokens={tot_tok} mean_nll={tot_nll/max(1,tot_tok):.4f} ppl={ppl:.4f}")
42
+ for cat in sorted(by):
43
+ n, t = by[cat]
44
+ print(f" PPL_CAT tag={a.tag} cat={cat:12s} tokens={t} ppl={math.exp(n/max(1,t)):.4f}")
45
+ # coherence probes
46
+ for q in ["The capital of France is", "def fibonacci(n):", "Water is made of hydrogen and"]:
47
+ r = llm.generate([q], SamplingParams(max_tokens=24, temperature=0))[0].outputs[0].text
48
+ print(f"COHERENCE [{q[:22]!r}] -> {r[:60]!r}")
49
+
50
+ if __name__ == "__main__":
51
+ main()
code/pv.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Output-preserving (PV) tuning (Phase E2) for Inkling 512k v2 cold experts.
3
+
4
+ For each layer, tune the SHARED AQLM codebook VALUES + per-(cold-expert,out-row)
5
+ SCALES so the dequantized expert's OUTPUT matches the BF16 teacher's output on
6
+ calib activations. CODES stay FIXED (loaded from the converged parts).
7
+
8
+ Trainable nn.Parameters (init from parts_conv):
9
+ cb13 [65536,8] w13 codebook (1 book)
10
+ cb2_0 [65536,8] w2 codebook book0
11
+ cb2_1 [256,8] w2 codebook book1
12
+ s13 [ncold,6144] w13 per-(cold-expert,out-row) scales
13
+ s2 [ncold,6144] w2 per-(cold-expert,out-row) scales
14
+
15
+ Differentiable dequant (codes fixed):
16
+ Wq = scales.unsqueeze(-1) * sum_b codebook_b[idx_b] reshaped to [out,in]
17
+ idx = codes.long() % cb.shape[0] (int16 negative-wrap for the 65536 book)
18
+
19
+ Expert forward (w13 ROW-INTERLEAVED: gate=w13[0::2], up=w13[1::2]):
20
+ h = silu(gate @ x^T) * (up @ x^T) [3072, n]
21
+ f = w2 @ h [6144, n]
22
+ Teacher forward uses the BF16 teacher weights (fixed, precomputed).
23
+
24
+ Loss = sum over cold experts of MSE(f_quant(x_e), f_teacher(x_e)) (unweighted).
25
+ Optimizer: Adam, lr 1e-3 (scales) / 1e-4 (codebooks), param groups.
26
+
27
+ Writes /data/inkling-quant/parts_pv/layer_{L}_{w13,w2}.pt with the SAME schema as
28
+ parts_conv ({proj}_codes [unchanged], {proj}_codebooks [tuned], {proj}_book_entries,
29
+ {proj}_scales [tuned], cold_experts, expert_ids) + output_mse_before/after and the
30
+ weight-space rel_err sanity numbers.
31
+ """
32
+ from __future__ import annotations
33
+ import argparse, os, time, json
34
+ import torch
35
+ import torch.nn.functional as F
36
+ import ink_common as ic
37
+
38
+ CONV = "/data/inkling-quant/parts_conv"
39
+ OUT = "/data/inkling-quant/parts_pv"
40
+ ACTS = "/data/inkling-quant/acts"
41
+ ASSIGN = "/data/inkling-quant/assign_512k.json"
42
+ G = 8
43
+ OUTD = 6144
44
+ W13_IN = 6144; W13_NG = W13_IN // G # 768
45
+ W2_IN = 3072; W2_NG = W2_IN // G # 384
46
+ MIN_TOK = 8
47
+ MAX_TOK = 4096
48
+
49
+
50
+ def cold_experts(layer, assign):
51
+ hot = set(assign["hot_experts"].get(str(layer), []))
52
+ return [e for e in range(ic.N_EXPERTS) if e not in hot]
53
+
54
+
55
+ def dequant_w13(ci, codes13, cb13, s13):
56
+ """codes13[ci,0] -> [6144,768]; returns Wq13 [6144,6144] (differentiable)."""
57
+ idx = codes13[ci, 0].long() % cb13.shape[0] # [6144,768]
58
+ recon = cb13[idx].reshape(OUTD, W13_IN) # [6144,6144]
59
+ return s13[ci].unsqueeze(-1) * recon
60
+
61
+
62
+ def dequant_w2(ci, codes2, cb2_0, cb2_1, s2):
63
+ """codes2[ci] -> books; returns Wq2 [6144,3072] (differentiable)."""
64
+ idx0 = codes2[ci, 0].long() % cb2_0.shape[0] # [6144,384]
65
+ idx1 = codes2[ci, 1].long() % cb2_1.shape[0] # [6144,384]
66
+ recon = (cb2_0[idx0] + cb2_1[idx1]).reshape(OUTD, W2_IN) # [6144,3072]
67
+ return s2[ci].unsqueeze(-1) * recon
68
+
69
+
70
+ def expert_forward(Wq13, Wq2, xe):
71
+ """xe [n,6144] -> expert output [6144,n]."""
72
+ gate = Wq13[0::2] # [3072,6144]
73
+ up = Wq13[1::2] # [3072,6144]
74
+ xt = xe.t() # [6144,n]
75
+ h = F.silu(gate @ xt) * (up @ xt) # [3072,n]
76
+ return Wq2 @ h # [6144,n]
77
+
78
+
79
+ def build_data(layer, cold, device):
80
+ """Per cold expert with >=MIN_TOK routed tokens: xe [n,6144] fp32, teacher
81
+ output ftea [6144,n] fp32 (precomputed from BF16 teacher weights, fixed).
82
+ Returns list of (ci, e, xe, ftea) and the count of data experts."""
83
+ a = torch.load(f"{ACTS}/acts_layer_{layer}.pt")
84
+ x = a["x"].to(device).float() # [N,6144]
85
+ ids = a["ids"].to(device) # [N,6]
86
+ tc = ic.ShardReader(ic.BF16_DIR)
87
+ data = []
88
+ gen = torch.Generator(device=device); gen.manual_seed(1234 + layer)
89
+ for ci, e in enumerate(cold):
90
+ m = (ids == e).any(1)
91
+ n = int(m.sum())
92
+ if n < MIN_TOK:
93
+ continue
94
+ xe = x[m] # [n,6144]
95
+ if n > MAX_TOK:
96
+ sel = torch.randperm(n, generator=gen, device=device)[:MAX_TOK]
97
+ xe = xe[sel]
98
+ w13 = ic.teacher_expert(tc, layer, "w13", e, device=device).float() # [6144,6144]
99
+ w2 = ic.teacher_expert(tc, layer, "w2", e, device=device).float() # [6144,3072]
100
+ with torch.no_grad():
101
+ ftea = expert_forward(w13, w2, xe) # [6144,n]
102
+ data.append((ci, e, xe.contiguous(), ftea.contiguous()))
103
+ del w13, w2
104
+ del x, ids
105
+ torch.cuda.empty_cache()
106
+ return data
107
+
108
+
109
+ @torch.no_grad()
110
+ def eval_output_mse(data, codes13, codes2, cb13, cb2_0, cb2_1, s13, s2):
111
+ """Mean over data experts of per-expert MSE(f_quant, f_teacher), plus the
112
+ mean normalized rel-err (||fq-ft||^2 / ||ft||^2)."""
113
+ mse_sum = 0.0; rel_sum = 0.0
114
+ for (ci, e, xe, ftea) in data:
115
+ Wq13 = dequant_w13(ci, codes13, cb13, s13)
116
+ Wq2 = dequant_w2(ci, codes2, cb2_0, cb2_1, s2)
117
+ fq = expert_forward(Wq13, Wq2, xe)
118
+ diff = (fq - ftea)
119
+ mse_sum += float(diff.pow(2).mean())
120
+ rel_sum += float(diff.pow(2).sum() / ftea.pow(2).sum().clamp_min(1e-12))
121
+ ne = len(data)
122
+ return mse_sum / ne, rel_sum / ne
123
+
124
+
125
+ @torch.no_grad()
126
+ def weight_relerr(layer, data, codes13, codes2, cb13, cb2_0, cb2_1, s13, s2, device):
127
+ """Aggregate plain weight-space rel-err ||Wq-Wt||/||Wt|| over the data experts,
128
+ for w13 and w2 separately (reloads BF16 teacher weights)."""
129
+ tc = ic.ShardReader(ic.BF16_DIR)
130
+ n13 = d13 = n2 = d2 = 0.0
131
+ for (ci, e, _, _) in data:
132
+ Wt13 = ic.teacher_expert(tc, layer, "w13", e, device=device).float()
133
+ Wq13 = dequant_w13(ci, codes13, cb13, s13)
134
+ n13 += float((Wq13 - Wt13).pow(2).sum()); d13 += float(Wt13.pow(2).sum())
135
+ del Wt13, Wq13
136
+ Wt2 = ic.teacher_expert(tc, layer, "w2", e, device=device).float()
137
+ Wq2 = dequant_w2(ci, codes2, cb2_0, cb2_1, s2)
138
+ n2 += float((Wq2 - Wt2).pow(2).sum()); d2 += float(Wt2.pow(2).sum())
139
+ del Wt2, Wq2
140
+ return (n13 / d13) ** 0.5, (n2 / d2) ** 0.5
141
+
142
+
143
+ def pv_layer(layer, steps=300, lr_cb=1e-4, lr_s=1e-3, device="cuda", verbose=True):
144
+ assign = json.load(open(ASSIGN))
145
+ cold = cold_experts(layer, assign)
146
+ coldt = torch.tensor(cold)
147
+ p13 = torch.load(f"{CONV}/layer_{layer}_w13.pt", map_location="cpu")
148
+ p2 = torch.load(f"{CONV}/layer_{layer}_w2.pt", map_location="cpu")
149
+
150
+ # fixed codes (cold-indexed) on device
151
+ codes13 = p13["w13_codes"][coldt].to(device) # [ncold,1,6144,768] int16
152
+ codes2 = p2["w2_codes"][coldt].to(device) # [ncold,2,6144,384] int16
153
+
154
+ # trainable params (init from converged parts)
155
+ cb13 = p13["w13_codebooks"][0].float().to(device).clone().requires_grad_()
156
+ cb2_0 = p2["w2_codebooks"][0].float().to(device).clone().requires_grad_()
157
+ cb2_1 = p2["w2_codebooks"][1].float().to(device).clone().requires_grad_()
158
+ s13 = p13["w13_scales"][coldt].float().to(device).clone().requires_grad_()
159
+ s2 = p2["w2_scales"][coldt].float().to(device).clone().requires_grad_()
160
+
161
+ data = build_data(layer, cold, device)
162
+ if verbose:
163
+ print(f" L{layer}: {len(cold)} cold, {len(data)} with >={MIN_TOK} tokens", flush=True)
164
+ if len(data) == 0:
165
+ raise RuntimeError(f"L{layer}: no cold experts with calib tokens")
166
+
167
+ # baseline (converged-parts init)
168
+ mse0, rel0 = eval_output_mse(data, codes13, codes2, cb13, cb2_0, cb2_1, s13, s2)
169
+ w13rel0, w2rel0 = weight_relerr(layer, data, codes13, codes2, cb13, cb2_0, cb2_1, s13, s2, device)
170
+ if verbose:
171
+ print(f" L{layer} BEFORE: out_mse={mse0:.6e} rel={rel0:.4f} | "
172
+ f"w13_relerr={w13rel0:.4f} w2_relerr={w2rel0:.4f}", flush=True)
173
+
174
+ opt = torch.optim.Adam([
175
+ {"params": [cb13, cb2_0, cb2_1], "lr": lr_cb},
176
+ {"params": [s13, s2], "lr": lr_s},
177
+ ])
178
+
179
+ for step in range(steps):
180
+ opt.zero_grad(set_to_none=True)
181
+ tot = 0.0
182
+ for (ci, e, xe, ftea) in data:
183
+ Wq13 = dequant_w13(ci, codes13, cb13, s13)
184
+ Wq2 = dequant_w2(ci, codes2, cb2_0, cb2_1, s2)
185
+ fq = expert_forward(Wq13, Wq2, xe)
186
+ loss = F.mse_loss(fq, ftea)
187
+ loss.backward()
188
+ tot += float(loss.detach())
189
+ opt.step()
190
+ if verbose and (step % 50 == 0 or step == steps - 1):
191
+ print(f" L{layer} step{step}: sum_mse={tot:.6e} (mean={tot/len(data):.6e})", flush=True)
192
+
193
+ mse1, rel1 = eval_output_mse(data, codes13, codes2, cb13, cb2_0, cb2_1, s13, s2)
194
+ w13rel1, w2rel1 = weight_relerr(layer, data, codes13, codes2, cb13, cb2_0, cb2_1, s13, s2, device)
195
+ if verbose:
196
+ print(f" L{layer} AFTER: out_mse={mse1:.6e} rel={rel1:.4f} | "
197
+ f"w13_relerr={w13rel1:.4f} w2_relerr={w2rel1:.4f}", flush=True)
198
+
199
+ # assemble full-256 tuned arrays (cold rows tuned; hot rows carried from conv part)
200
+ with torch.no_grad():
201
+ full_s13 = p13["w13_scales"].clone()
202
+ full_s13[coldt] = s13.detach().half().cpu()
203
+ full_s2 = p2["w2_scales"].clone()
204
+ full_s2[coldt] = s2.detach().half().cpu()
205
+
206
+ common = dict(
207
+ cold_experts=cold,
208
+ output_mse_before=mse0, output_mse_after=mse1,
209
+ output_relerr_before=rel0, output_relerr_after=rel1,
210
+ conv_rel_err_after_w13=float(p13.get("rel_err_after", float("nan"))),
211
+ conv_rel_err_after_w2=float(p2.get("rel_err_after", float("nan"))),
212
+ )
213
+ out13 = {
214
+ "expert_ids": p13["expert_ids"],
215
+ "w13_codes": p13["w13_codes"], # UNCHANGED
216
+ "w13_codebooks": [cb13.detach().half().cpu()], # tuned
217
+ "w13_book_entries": p13["w13_book_entries"],
218
+ "w13_scales": full_s13, # tuned
219
+ "weight_relerr_before": w13rel0, "weight_relerr_after": w13rel1,
220
+ **common,
221
+ }
222
+ out2 = {
223
+ "expert_ids": p2["expert_ids"],
224
+ "w2_codes": p2["w2_codes"], # UNCHANGED
225
+ "w2_codebooks": [cb2_0.detach().half().cpu(), cb2_1.detach().half().cpu()], # tuned
226
+ "w2_book_entries": p2["w2_book_entries"],
227
+ "w2_scales": full_s2, # tuned
228
+ "weight_relerr_before": w2rel0, "weight_relerr_after": w2rel1,
229
+ **common,
230
+ }
231
+ return out13, out2, dict(
232
+ mse0=mse0, mse1=mse1, rel0=rel0, rel1=rel1,
233
+ w13rel0=w13rel0, w13rel1=w13rel1, w2rel0=w2rel0, w2rel1=w2rel1,
234
+ )
235
+
236
+
237
+ if __name__ == "__main__":
238
+ ap = argparse.ArgumentParser()
239
+ ap.add_argument("--layer", type=int, required=True)
240
+ ap.add_argument("--steps", type=int, default=300)
241
+ ap.add_argument("--lr_cb", type=float, default=1e-4)
242
+ ap.add_argument("--lr_s", type=float, default=1e-3)
243
+ ap.add_argument("--save", action="store_true")
244
+ a = ap.parse_args()
245
+ os.makedirs(OUT, exist_ok=True)
246
+ t0 = time.time()
247
+ out13, out2, m = pv_layer(a.layer, a.steps, a.lr_cb, a.lr_s)
248
+ dt = time.time() - t0
249
+ print(f"L{a.layer} PV: out_mse {m['mse0']:.6e} -> {m['mse1']:.6e} "
250
+ f"(rel {m['rel0']:.4f}->{m['rel1']:.4f}) | "
251
+ f"w13_relerr {m['w13rel0']:.4f}->{m['w13rel1']:.4f} "
252
+ f"w2_relerr {m['w2rel0']:.4f}->{m['w2rel1']:.4f} ({dt:.0f}s)")
253
+ if a.save:
254
+ torch.save(out13, f"{OUT}/layer_{a.layer}_w13.pt")
255
+ torch.save(out2, f"{OUT}/layer_{a.layer}_w2.pt")
256
+ print("saved.")
code/pv_all.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Full output-preserving (PV) tuning (Phase E2) across all MoE layers.
3
+ Shards layers across CUDA_VISIBLE_DEVICES GPUs, 1 spawn-worker/GPU, resumable
4
+ (skips layers whose BOTH parts_pv files already exist).
5
+ Run: CUDA_VISIBLE_DEVICES=4,5,6,7 python pv_all.py [--steps 300]
6
+ """
7
+ import os, sys, time, argparse
8
+ import torch.multiprocessing as mp
9
+ import ink_common as ic
10
+
11
+ OUT = "/data/inkling-quant/parts_pv"
12
+
13
+
14
+ def done(L):
15
+ return (os.path.exists(f"{OUT}/layer_{L}_w13.pt")
16
+ and os.path.exists(f"{OUT}/layer_{L}_w2.pt"))
17
+
18
+
19
+ def worker(gpu, layers, steps):
20
+ os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu)
21
+ os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
22
+ import torch, pv
23
+ for L in layers:
24
+ if done(L):
25
+ print(f"[gpu{gpu}] skip L{L} (exists)", flush=True); continue
26
+ t0 = time.time()
27
+ try:
28
+ out13, out2, m = pv.pv_layer(L, steps=steps, device="cuda", verbose=False)
29
+ for proj, obj in (("w13", out13), ("w2", out2)):
30
+ dst = f"{OUT}/layer_{L}_{proj}.pt"
31
+ tmp = dst + ".tmp"
32
+ torch.save(obj, tmp); os.replace(tmp, dst)
33
+ improved = "OK" if m["mse1"] < m["mse0"] else "WORSE"
34
+ print(f"[gpu{gpu}] L{L} PV out_mse {m['mse0']:.4e}->{m['mse1']:.4e} "
35
+ f"(rel {m['rel0']:.4f}->{m['rel1']:.4f}) {improved} | "
36
+ f"w13rel {m['w13rel0']:.4f}->{m['w13rel1']:.4f} "
37
+ f"w2rel {m['w2rel0']:.4f}->{m['w2rel1']:.4f} "
38
+ f"({time.time()-t0:.0f}s) saved", flush=True)
39
+ except Exception as ex:
40
+ import traceback
41
+ print(f"[gpu{gpu}] L{L} FAILED: {ex}", flush=True)
42
+ traceback.print_exc()
43
+
44
+
45
+ def main():
46
+ ap = argparse.ArgumentParser()
47
+ ap.add_argument("--steps", type=int, default=300)
48
+ a = ap.parse_args()
49
+ os.makedirs(OUT, exist_ok=True)
50
+ gpus = [int(x) for x in os.environ.get("CUDA_VISIBLE_DEVICES", "0").split(",")]
51
+ layers = ic.moe_layers()
52
+ todo = [L for L in layers if not done(L)]
53
+ print(f"{len(layers)} layers, {len(todo)} remaining, {len(gpus)} GPUs {gpus}", flush=True)
54
+ shards = {g: [] for g in gpus}
55
+ for i, L in enumerate(todo):
56
+ shards[gpus[i % len(gpus)]].append(L)
57
+ mp.set_start_method("spawn", force=True)
58
+ ps = [mp.Process(target=worker, args=(g, shards[g], a.steps)) for g in gpus]
59
+ for p in ps: p.start()
60
+ for p in ps: p.join()
61
+ d = sum(1 for L in layers if done(L))
62
+ print(f"PV DONE: {d}/{len(layers)} layers", flush=True)
63
+
64
+
65
+ if __name__ == "__main__":
66
+ main()
code/pv_all_v3.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Full PV-v3 across all MoE layers, with held-out early-stop (pv_v3).
3
+ Shards layers across CUDA_VISIBLE_DEVICES, 1 spawn-worker/GPU, resumable.
4
+ Needs acts_v3/ (train) + acts_v3_heldout/ (val) captured first.
5
+ Run: CUDA_VISIBLE_DEVICES=4,5,6,7 python pv_all_v3.py [--max-steps 2000] [--patience 6]
6
+ """
7
+ import os, time, argparse
8
+ import torch.multiprocessing as mp
9
+ import ink_common as ic
10
+
11
+ import os as _os
12
+ OUT = _os.environ.get("INK_PV_OUT", "/data/inkling-quant/parts_pv_v3")
13
+
14
+ def done(L):
15
+ return (os.path.exists(f"{OUT}/layer_{L}_w13.pt")
16
+ and os.path.exists(f"{OUT}/layer_{L}_w2.pt"))
17
+
18
+ def worker(gpu, layers, max_steps, eval_every, patience, tune_cb):
19
+ os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu)
20
+ os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
21
+ import torch, pv_v3
22
+ for L in layers:
23
+ if done(L):
24
+ print(f"[gpu{gpu}] skip L{L} (exists)", flush=True); continue
25
+ t0 = time.time()
26
+ try:
27
+ out13, out2, m = pv_v3.pv_layer_v3(L, max_steps=max_steps, eval_every=eval_every,
28
+ patience=patience, tune_cb=tune_cb,
29
+ device="cuda", verbose=False)
30
+ for proj, obj in (("w13", out13), ("w2", out2)):
31
+ dst = f"{OUT}/layer_{L}_{proj}.pt"; tmp = dst + ".tmp"
32
+ torch.save(obj, tmp); os.replace(tmp, dst)
33
+ tag = "OK" if (m["val1"] <= m["val0"] or m["val0"] != m["val0"]) else "WORSE"
34
+ print(f"[gpu{gpu}] L{L} PVv3 val_rel {m['val0']:.4f}->{m['val1']:.4f} "
35
+ f"best@{m['best_step']} {tag} ({time.time()-t0:.0f}s) saved", flush=True)
36
+ except Exception as ex:
37
+ import traceback
38
+ print(f"[gpu{gpu}] L{L} FAILED: {ex}", flush=True); traceback.print_exc()
39
+
40
+ def main():
41
+ ap = argparse.ArgumentParser()
42
+ ap.add_argument("--max-steps", type=int, default=2000)
43
+ ap.add_argument("--eval-every", type=int, default=25)
44
+ ap.add_argument("--patience", type=int, default=6)
45
+ ap.add_argument("--no-tune-cb", action="store_true")
46
+ a = ap.parse_args()
47
+ os.makedirs(OUT, exist_ok=True)
48
+ gpus = [int(x) for x in os.environ.get("CUDA_VISIBLE_DEVICES", "0").split(",")]
49
+ layers = ic.moe_layers()
50
+ todo = [L for L in layers if not done(L)]
51
+ print(f"{len(layers)} layers, {len(todo)} remaining, {len(gpus)} GPUs {gpus}", flush=True)
52
+ shards = {g: [] for g in gpus}
53
+ for i, L in enumerate(todo):
54
+ shards[gpus[i % len(gpus)]].append(L)
55
+ mp.set_start_method("spawn", force=True)
56
+ ps = [mp.Process(target=worker, args=(g, shards[g], a.max_steps, a.eval_every,
57
+ a.patience, not a.no_tune_cb)) for g in gpus]
58
+ for p in ps: p.start()
59
+ for p in ps: p.join()
60
+ d = sum(1 for L in layers if done(L))
61
+ print(f"PVv3 DONE: {d}/{len(layers)} layers", flush=True)
62
+
63
+ if __name__ == "__main__":
64
+ main()
code/pv_gen_check.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch, json, random, statistics as st
2
+ import ink_common as ic
3
+ random.seed(0); dev="cuda"
4
+ assign=json.load(open("/data/inkling-quant/assign_512k_v2.json"))["hot_experts"]
5
+ tc=ic.ShardReader(ic.BF16_DIR)
6
+ def expert_out(W13,W2,x): # x[n,6144]; W13[6144,6144] interleaved; W2[6144,3072]
7
+ gate=W13[0::2]; up=W13[1::2]
8
+ h=torch.nn.functional.silu(x@gate.t())*(x@up.t())
9
+ return h@W2.t()
10
+ layers=[3,8,15,22,30,38,45,52,58,63]
11
+ conv=[]; pv=[]
12
+ for L in layers:
13
+ a=torch.load(f"/data/inkling-quant/acts_heldout/acts_layer_{L}.pt",map_location=dev)
14
+ x=a["x"].float(); ids=a["ids"]
15
+ hot=set(assign[str(L)]); cold=[e for e in range(256) if e not in hot]
16
+ pc13=torch.load(f"/data/inkling-quant/parts_conv/layer_{L}_w13.pt",map_location="cpu")
17
+ pc2 =torch.load(f"/data/inkling-quant/parts_conv/layer_{L}_w2.pt",map_location="cpu")
18
+ pp13=torch.load(f"/data/inkling-quant/parts_pv/layer_{L}_w13.pt",map_location="cpu")
19
+ pp2 =torch.load(f"/data/inkling-quant/parts_pv/layer_{L}_w2.pt",map_location="cpu")
20
+ for e in random.sample(cold,min(12,len(cold))):
21
+ m=(ids==e).any(1)
22
+ if int(m.sum())<8: continue
23
+ xe=x[m][:2048]
24
+ ft=expert_out(ic.teacher_expert(tc,L,"w13",e,device=dev).float(),ic.teacher_expert(tc,L,"w2",e,device=dev).float(),xe)
25
+ nt=ft.norm()
26
+ conv.append(((expert_out(ic.aqlm_dequant_expert(pc13,"w13",e,device=dev).float(),ic.aqlm_dequant_expert(pc2,"w2",e,device=dev).float(),xe)-ft).norm()/nt).item())
27
+ pv.append(((expert_out(ic.aqlm_dequant_expert(pp13,"w13",e,device=dev).float(),ic.aqlm_dequant_expert(pp2,"w2",e,device=dev).float(),xe)-ft).norm()/nt).item())
28
+ c=st.mean(conv); p=st.mean(pv)
29
+ print(f"HELD-OUT output rel-err over {len(conv)} cold experts:")
30
+ print(f" converged (parts_conv): {c:.4f}")
31
+ print(f" PV-tuned (parts_pv): {p:.4f}")
32
+ print(f" PV vs converged (held-out): {100*(1-p/c):+.1f}%")
33
+ print(f" VERDICT: {'PV GENERALIZES -> use parts_pv' if p < c*0.98 else 'PV OVERFIT / no held-out gain -> use parts_conv'}")
code/pv_generalize.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """PV generalization check (the v2 ship decision).
3
+
4
+ On HELD-OUT tokens (acts_heldout/, disjoint from calib), for a SAMPLE of cold
5
+ experts across shallow/mid/deep layers, compute the expert OUTPUT rel-err vs the
6
+ BF16 teacher for TWO weight sources:
7
+ (a) converged parts_conv (REAP+converge only)
8
+ (b) PV-tuned parts_pv (tuned on calib tokens)
9
+
10
+ Cold = experts in the v2 complement (assign_512k_v2.json) AND actually PV-tuned
11
+ (in the part's stored cold_experts). Identical held-out tokens used for (a)/(b).
12
+
13
+ Expert output rel-err = ||f_e(x)-f_teacher(x)||^2 / ||f_teacher(x)||^2, where
14
+ f_e(x)=w2 @ (silu(gate@x^T)*(up@x^T)), w13 ROW-INTERLEAVED gate=w13[0::2]/up=w13[1::2].
15
+
16
+ VERDICT: if PV held-out rel-err is LOWER than converged by >2% relative -> PV
17
+ GENERALIZES (ship parts_pv). Else PV OVERFIT (ship parts_conv).
18
+ """
19
+ from __future__ import annotations
20
+ import json, argparse, time
21
+ import torch
22
+ import ink_common as ic
23
+ import pv # expert_forward
24
+
25
+ ACTS_HELDOUT = "/data/inkling-quant/acts_heldout"
26
+ CONV = "/data/inkling-quant/parts_conv"
27
+ PV = "/data/inkling-quant/parts_pv"
28
+ ASSIGN_V2 = "/data/inkling-quant/assign_512k_v2.json"
29
+ MIN_TOK = 16
30
+ MAX_TOK = 4096
31
+ DEFAULT_LAYERS = [3, 8, 12, 20, 28, 36, 44, 52, 58, 65]
32
+ N_EXP_PER_LAYER = 16
33
+
34
+
35
+ @torch.no_grad()
36
+ def expert_relerr(part13, part2, e, xe, ftea, device):
37
+ Wq13 = ic.aqlm_dequant_expert(part13, "w13", e, device=device).float()
38
+ Wq2 = ic.aqlm_dequant_expert(part2, "w2", e, device=device).float()
39
+ fq = pv.expert_forward(Wq13, Wq2, xe)
40
+ diff = fq - ftea
41
+ return float(diff.pow(2).sum() / ftea.pow(2).sum().clamp_min(1e-12))
42
+
43
+
44
+ @torch.no_grad()
45
+ def eval_layer(layer, n_exp, device):
46
+ v2 = json.load(open(ASSIGN_V2))["hot_experts"]
47
+ v2_cold = set(range(ic.N_EXPERTS)) - set(v2.get(str(layer), []))
48
+ c13 = torch.load(f"{CONV}/layer_{layer}_w13.pt", map_location="cpu")
49
+ c2 = torch.load(f"{CONV}/layer_{layer}_w2.pt", map_location="cpu")
50
+ p13 = torch.load(f"{PV}/layer_{layer}_w13.pt", map_location="cpu")
51
+ p2 = torch.load(f"{PV}/layer_{layer}_w2.pt", map_location="cpu")
52
+ pv_tuned = set(c13["cold_experts"]) # experts PV actually tuned (v1 cold)
53
+ cand = sorted(v2_cold & pv_tuned) # v2-cold AND PV-tuned
54
+
55
+ a = torch.load(f"{ACTS_HELDOUT}/acts_layer_{layer}.pt")
56
+ x = a["x"].to(device).float() # [N,6144]
57
+ ids = a["ids"].to(device) # [N,6]
58
+
59
+ # eligible = enough held-out tokens; sample a seeded subset
60
+ counts = {e: int((ids == e).any(1).sum()) for e in cand}
61
+ elig = [e for e in cand if counts[e] >= MIN_TOK]
62
+ gen = torch.Generator().manual_seed(2024 + layer)
63
+ if len(elig) > n_exp:
64
+ perm = torch.randperm(len(elig), generator=gen).tolist()
65
+ sample = sorted(elig[i] for i in perm[:n_exp])
66
+ else:
67
+ sample = elig
68
+
69
+ tc = ic.ShardReader(ic.BF16_DIR)
70
+ gsel = torch.Generator(device=device).manual_seed(99 + layer)
71
+ conv_errs, pv_errs, toks = [], [], []
72
+ for e in sample:
73
+ m = (ids == e).any(1)
74
+ xe = x[m]
75
+ if xe.shape[0] > MAX_TOK:
76
+ sel = torch.randperm(xe.shape[0], generator=gsel, device=device)[:MAX_TOK]
77
+ xe = xe[sel]
78
+ xe = xe.contiguous()
79
+ wt13 = ic.teacher_expert(tc, layer, "w13", e, device=device).float()
80
+ wt2 = ic.teacher_expert(tc, layer, "w2", e, device=device).float()
81
+ ftea = pv.expert_forward(wt13, wt2, xe)
82
+ del wt13, wt2
83
+ conv_errs.append(expert_relerr(c13, c2, e, xe, ftea, device))
84
+ pv_errs.append(expert_relerr(p13, p2, e, xe, ftea, device))
85
+ toks.append(int(xe.shape[0]))
86
+ del ftea, xe
87
+ del x, ids
88
+ torch.cuda.empty_cache()
89
+
90
+ conv_m = sum(conv_errs) / len(conv_errs)
91
+ pv_m = sum(pv_errs) / len(pv_errs)
92
+ # calib numbers stored in the PV part (combined-expert output relerr on calib)
93
+ calib_conv = float(p13["output_relerr_before"])
94
+ calib_pv = float(p13["output_relerr_after"])
95
+ return dict(layer=layer, n=len(sample), tot_tok=sum(toks),
96
+ conv=conv_m, pv=pv_m, calib_conv=calib_conv, calib_pv=calib_pv,
97
+ experts=sample)
98
+
99
+
100
+ def main():
101
+ ap = argparse.ArgumentParser()
102
+ ap.add_argument("--layers", type=int, nargs="+", default=DEFAULT_LAYERS)
103
+ ap.add_argument("--n_exp", type=int, default=N_EXP_PER_LAYER)
104
+ a = ap.parse_args()
105
+ device = "cuda"
106
+ print(f"HELD-OUT PV-generalization check | layers {a.layers} | <= {a.n_exp} cold experts/layer")
107
+ print(f"{'layer':>5} {'nexp':>4} {'tok':>6} | {'HELDOUT conv':>13} {'HELDOUT pv':>11} {'d%':>7} || "
108
+ f"{'CALIB conv':>10} {'CALIB pv':>9} {'d%':>7}")
109
+ rows = []
110
+ for L in a.layers:
111
+ t0 = time.time()
112
+ r = eval_layer(L, a.n_exp, device)
113
+ rows.append(r)
114
+ hd = 100 * (r["pv"] - r["conv"]) / r["conv"]
115
+ cd = 100 * (r["calib_pv"] - r["calib_conv"]) / r["calib_conv"]
116
+ print(f"{r['layer']:>5} {r['n']:>4} {r['tot_tok']:>6} | "
117
+ f"{r['conv']:>13.4f} {r['pv']:>11.4f} {hd:>+6.1f}% || "
118
+ f"{r['calib_conv']:>10.4f} {r['calib_pv']:>9.4f} {cd:>+6.1f}% ({time.time()-t0:.0f}s)", flush=True)
119
+
120
+ conv_mean = sum(r["conv"] for r in rows) / len(rows)
121
+ pv_mean = sum(r["pv"] for r in rows) / len(rows)
122
+ cconv = sum(r["calib_conv"] for r in rows) / len(rows)
123
+ cpv = sum(r["calib_pv"] for r in rows) / len(rows)
124
+ hd = 100 * (pv_mean - conv_mean) / conv_mean
125
+ cd = 100 * (cpv - cconv) / cconv
126
+ print("-" * 96)
127
+ print(f"{'MEAN':>5} {sum(r['n'] for r in rows):>4} {sum(r['tot_tok'] for r in rows):>6} | "
128
+ f"{conv_mean:>13.4f} {pv_mean:>11.4f} {hd:>+6.1f}% || "
129
+ f"{cconv:>10.4f} {cpv:>9.4f} {cd:>+6.1f}%")
130
+ print()
131
+ print(f"HELD-OUT: converged rel-err {conv_mean:.4f} vs PV rel-err {pv_mean:.4f} ({hd:+.1f}%)")
132
+ print(f"CALIB : converged rel-err {cconv:.4f} vs PV rel-err {cpv:.4f} ({cd:+.1f}%)")
133
+ if hd < -2.0:
134
+ print(f"\nVERDICT: PV GENERALIZES (held-out {hd:+.1f}% <= -2%). SHIP parts_pv for v2 cold experts.")
135
+ elif hd > 2.0:
136
+ print(f"\nVERDICT: PV OVERFIT (held-out {hd:+.1f}% WORSE). SHIP parts_conv for v2 cold experts.")
137
+ else:
138
+ print(f"\nVERDICT: PV ~= converged on held-out ({hd:+.1f}%, within +-2%) despite winning calib "
139
+ f"({cd:+.1f}%) => PV did NOT generalize. SHIP parts_conv (simpler, no overfit risk).")
140
+
141
+
142
+ if __name__ == "__main__":
143
+ main()
code/pv_v3.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """PV-tune v3 — output-preserving tune WITH a held-out early-stop.
3
+
4
+ Why v3 differs from pv.py: v2's PV overfit (calib output-err -32% but held-out
5
+ +10.5% WORSE; weight-err rose 0.30->0.43) because it ran a FIXED step count on a
6
+ tiny per-expert calib sample. v3 fixes that two ways:
7
+ 1. TRAIN on the larger calib_v3 acts (acts_v3/), so cold experts see more tokens.
8
+ 2. EARLY-STOP on a DISJOINT held-out acts capture (acts_v3_heldout/): every
9
+ EVAL_EVERY steps, measure aggregate held-out output rel-err; keep the BEST-val
10
+ params; stop after PATIENCE evals without improvement. We SAVE best-val, not last.
11
+
12
+ Same schema/mechanics as pv.py (tunes shared codebooks + per-cold-expert scales,
13
+ codes fixed, w13 row-interleaved gate/up). Assignment + converged init = v2's
14
+ (assign_512k_v2.json + parts_conv) since v3 is strictly PV-focused (tiering/MTP unchanged).
15
+
16
+ GPU phase — needs acts captured FIRST (see run_capture_acts for calib_v3 -> acts_v3/
17
+ and heldout_v3 -> acts_v3_heldout/). Writes parts_pv_v3/layer_{L}_{w13,w2}.pt.
18
+ """
19
+ from __future__ import annotations
20
+ import argparse, os, time, json
21
+ import torch
22
+ import torch.nn.functional as F
23
+ import ink_common as ic
24
+ from pv import dequant_w13, dequant_w2, expert_forward # reuse pure dequant/forward
25
+
26
+ # PV trains on the UNION of text + image + audio acts (multimodal coverage so
27
+ # image/audio-routed experts aren't degraded); each modality captured to its own
28
+ # dir. Only dirs that exist are used.
29
+ import os as _os
30
+ # default = merged multimodal acts + v3 tiering/converge (env-overridable)
31
+ TRAIN_ACT_DIRS = _os.environ.get("INK_ACTS_TRAIN", "/data/inkling-quant/acts_v3_all").split(",")
32
+ VAL_ACT_DIRS = _os.environ.get("INK_ACTS_VAL", "/data/inkling-quant/acts_v3_all_held").split(",")
33
+ CONV = _os.environ.get("INK_CONV", "/data/inkling-quant/parts_conv_v3")
34
+ OUT = _os.environ.get("INK_PV_OUT", "/data/inkling-quant/parts_pv_v3")
35
+ ASSIGN = _os.environ.get("INK_ASSIGN", "/data/inkling-quant/assign_512k_v3.json")
36
+ OUTD = 6144
37
+ MIN_TOK = 8 # min TRAIN tokens to tune an expert
38
+ MIN_VAL = 4 # min held-out tokens for an expert to count toward the val signal
39
+ MAX_TOK = 2048 # train-token cap/expert (was 8192 -> ~4x faster steps; still ample for a codebook tune)
40
+ MAX_VAL = 1024
41
+
42
+
43
+ def cold_experts(layer, assign):
44
+ hot = set(assign["hot_experts"].get(str(layer), []))
45
+ return [e for e in range(ic.N_EXPERTS) if e not in hot]
46
+
47
+
48
+ def _tokens_multi(dirs, layer, cold, device, cap, seed):
49
+ """Union tokens across acts dirs (text+image+audio) per cold expert, then cap."""
50
+ acc = {} # ci -> list of xe tensors
51
+ for d in dirs:
52
+ p = f"{d}/acts_layer_{layer}.pt"
53
+ if not os.path.exists(p):
54
+ continue
55
+ a = torch.load(p, map_location="cpu")
56
+ x = a["x"].to(device).float(); ids = a["ids"].to(device)
57
+ for ci, e in enumerate(cold):
58
+ m = (ids == e).any(1)
59
+ if int(m.sum()) == 0:
60
+ continue
61
+ acc.setdefault(ci, []).append(x[m].contiguous())
62
+ del x, ids; torch.cuda.empty_cache()
63
+ gen = torch.Generator(device=device); gen.manual_seed(seed)
64
+ out = {}
65
+ for ci, parts in acc.items():
66
+ xe = torch.cat(parts, 0); n = xe.shape[0]
67
+ if n > cap:
68
+ sel = torch.randperm(n, generator=gen, device=device)[:cap]; xe = xe[sel]
69
+ out[ci] = xe.contiguous()
70
+ torch.cuda.empty_cache()
71
+ return out
72
+
73
+
74
+ def build_split(layer, cold, device):
75
+ """Train + val data (multimodal union). For each expert build teacher outputs once.
76
+ train: [(ci,e,xe,ftea)] with >=MIN_TOK ; val: [(ci,e,xe,ftea)] with >=MIN_VAL."""
77
+ xt_by = _tokens_multi(TRAIN_ACT_DIRS, layer, cold, device, MAX_TOK, 1234+layer)
78
+ xv_by = _tokens_multi(VAL_ACT_DIRS, layer, cold, device, MAX_VAL, 9876+layer)
79
+ tc = ic.ShardReader(ic.BF16_DIR)
80
+ train, val = [], []
81
+ for ci, e in enumerate(cold):
82
+ need = (ci in xt_by and xt_by[ci].shape[0] >= MIN_TOK) or \
83
+ (ci in xv_by and xv_by[ci].shape[0] >= MIN_VAL)
84
+ if not need:
85
+ continue
86
+ w13 = ic.teacher_expert(tc, layer, "w13", e, device=device).float()
87
+ w2 = ic.teacher_expert(tc, layer, "w2", e, device=device).float()
88
+ if ci in xt_by and xt_by[ci].shape[0] >= MIN_TOK:
89
+ xe = xt_by[ci]
90
+ with torch.no_grad(): ft = expert_forward(w13, w2, xe)
91
+ train.append((ci, e, xe, ft.contiguous()))
92
+ if ci in xv_by and xv_by[ci].shape[0] >= MIN_VAL:
93
+ xe = xv_by[ci]
94
+ with torch.no_grad(): ft = expert_forward(w13, w2, xe)
95
+ val.append((ci, e, xe, ft.contiguous()))
96
+ del w13, w2
97
+ torch.cuda.empty_cache()
98
+ return train, val
99
+
100
+
101
+ @torch.no_grad()
102
+ def agg_relerr(data, codes13, codes2, cb13, cb2_0, cb2_1, s13, s2):
103
+ if not data: return float("nan")
104
+ rel = 0.0
105
+ for (ci, e, xe, ft) in data:
106
+ Wq13 = dequant_w13(ci, codes13, cb13, s13)
107
+ Wq2 = dequant_w2(ci, codes2, cb2_0, cb2_1, s2)
108
+ fq = expert_forward(Wq13, Wq2, xe)
109
+ rel += float((fq-ft).pow(2).sum() / ft.pow(2).sum().clamp_min(1e-12))
110
+ return rel / len(data)
111
+
112
+
113
+ def pv_layer_v3(layer, max_steps=2000, eval_every=25, patience=6,
114
+ lr_cb=1e-4, lr_s=1e-3, tune_cb=True, device="cuda", verbose=True):
115
+ assign = json.load(open(ASSIGN))
116
+ cold = cold_experts(layer, assign); coldt = torch.tensor(cold)
117
+ p13 = torch.load(f"{CONV}/layer_{layer}_w13.pt", map_location="cpu")
118
+ p2 = torch.load(f"{CONV}/layer_{layer}_w2.pt", map_location="cpu")
119
+ codes13 = p13["w13_codes"][coldt].to(device)
120
+ codes2 = p2["w2_codes"][coldt].to(device)
121
+ cb13 = p13["w13_codebooks"][0].float().to(device).clone().requires_grad_(tune_cb)
122
+ cb2_0 = p2["w2_codebooks"][0].float().to(device).clone().requires_grad_(tune_cb)
123
+ cb2_1 = p2["w2_codebooks"][1].float().to(device).clone().requires_grad_(tune_cb)
124
+ s13 = p13["w13_scales"][coldt].float().to(device).clone().requires_grad_()
125
+ s2 = p2["w2_scales"][coldt].float().to(device).clone().requires_grad_()
126
+
127
+ train, val = build_split(layer, cold, device)
128
+ if not train:
129
+ raise RuntimeError(f"L{layer}: no cold experts with >= {MIN_TOK} train tokens")
130
+ if verbose:
131
+ print(f" L{layer}: {len(cold)} cold | train {len(train)} | val {len(val)}"
132
+ + ("" if val else " (WARN no held-out acts -> falls back to fixed-step, NO early-stop)"), flush=True)
133
+
134
+ val0 = agg_relerr(val, codes13, codes2, cb13, cb2_0, cb2_1, s13, s2) if val else float("nan")
135
+ params = [{"params": [s13, s2], "lr": lr_s}]
136
+ if tune_cb: params.append({"params": [cb13, cb2_0, cb2_1], "lr": lr_cb})
137
+ opt = torch.optim.Adam(params)
138
+
139
+ def snapshot():
140
+ return (cb13.detach().clone(), cb2_0.detach().clone(), cb2_1.detach().clone(),
141
+ s13.detach().clone(), s2.detach().clone())
142
+ best_val = val0; best = snapshot(); best_step = 0; bad = 0
143
+
144
+ for step in range(1, max_steps+1):
145
+ opt.zero_grad(set_to_none=True); tot = 0.0
146
+ for (ci, e, xe, ft) in train:
147
+ Wq13 = dequant_w13(ci, codes13, cb13, s13)
148
+ Wq2 = dequant_w2(ci, codes2, cb2_0, cb2_1, s2)
149
+ loss = F.mse_loss(expert_forward(Wq13, Wq2, xe), ft)
150
+ loss.backward(); tot += float(loss.detach())
151
+ opt.step()
152
+ if val and (step % eval_every == 0 or step == max_steps):
153
+ vr = agg_relerr(val, codes13, codes2, cb13, cb2_0, cb2_1, s13, s2)
154
+ improved = vr < best_val - 1e-5
155
+ if improved:
156
+ best_val = vr; best = snapshot(); best_step = step; bad = 0
157
+ else:
158
+ bad += 1
159
+ if verbose:
160
+ print(f" L{layer} step{step}: train_mse={tot/len(train):.3e} "
161
+ f"val_rel={vr:.4f} best={best_val:.4f}@{best_step} bad={bad}", flush=True)
162
+ if bad >= patience:
163
+ if verbose: print(f" L{layer} EARLY-STOP at {step} (best val {best_val:.4f} @ {best_step})", flush=True)
164
+ break
165
+
166
+ # restore best-val params
167
+ cb13d, cb2_0d, cb2_1d, s13d, s2d = best
168
+ val1 = agg_relerr(val, codes13, codes2, cb13d, cb2_0d, cb2_1d, s13d, s2d) if val else float("nan")
169
+ if verbose:
170
+ print(f" L{layer} DONE: val_rel {val0:.4f} -> {val1:.4f} (best @ step {best_step})", flush=True)
171
+
172
+ with torch.no_grad():
173
+ full_s13 = p13["w13_scales"].clone(); full_s13[coldt] = s13d.half().cpu()
174
+ full_s2 = p2["w2_scales"].clone(); full_s2[coldt] = s2d.half().cpu()
175
+ common = dict(cold_experts=cold, val_relerr_before=val0, val_relerr_after=val1,
176
+ best_step=best_step, max_steps=max_steps, tuned_cb=tune_cb)
177
+ out13 = {"expert_ids": p13["expert_ids"], "w13_codes": p13["w13_codes"],
178
+ "w13_codebooks": [cb13d.half().cpu()], "w13_book_entries": p13["w13_book_entries"],
179
+ "w13_scales": full_s13, **common}
180
+ out2 = {"expert_ids": p2["expert_ids"], "w2_codes": p2["w2_codes"],
181
+ "w2_codebooks": [cb2_0d.half().cpu(), cb2_1d.half().cpu()],
182
+ "w2_book_entries": p2["w2_book_entries"], "w2_scales": full_s2, **common}
183
+ return out13, out2, dict(val0=val0, val1=val1, best_step=best_step)
184
+
185
+
186
+ if __name__ == "__main__":
187
+ ap = argparse.ArgumentParser()
188
+ ap.add_argument("--layer", type=int, required=True)
189
+ ap.add_argument("--max-steps", type=int, default=2000)
190
+ ap.add_argument("--eval-every", type=int, default=25)
191
+ ap.add_argument("--patience", type=int, default=6)
192
+ ap.add_argument("--lr_cb", type=float, default=1e-4)
193
+ ap.add_argument("--lr_s", type=float, default=1e-3)
194
+ ap.add_argument("--no-tune-cb", action="store_true", help="scales-only (most overfit-safe)")
195
+ ap.add_argument("--save", action="store_true")
196
+ a = ap.parse_args()
197
+ os.makedirs(OUT, exist_ok=True)
198
+ t0 = time.time()
199
+ out13, out2, m = pv_layer_v3(a.layer, a.max_steps, a.eval_every, a.patience,
200
+ a.lr_cb, a.lr_s, tune_cb=not a.no_tune_cb)
201
+ print(f"L{a.layer} PVv3: val_rel {m['val0']:.4f} -> {m['val1']:.4f} "
202
+ f"(best@{m['best_step']}) ({time.time()-t0:.0f}s)")
203
+ if a.save:
204
+ torch.save(out13, f"{OUT}/layer_{a.layer}_w13.pt")
205
+ torch.save(out2, f"{OUT}/layer_{a.layer}_w2.pt")
206
+ print("saved ->", OUT)
code/reap_merge.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Merge per-layer REAP tmp files -> /data/inkling-quant/reap_scores.pt and validate."""
3
+ import torch, glob, os, re
4
+ import ink_common as ic
5
+
6
+ TMP = os.environ.get("INK_REAP_TMP", "/data/inkling-quant/reap_tmp")
7
+ OUT = os.environ.get("INK_REAP_OUT", "/data/inkling-quant/reap_scores.pt")
8
+
9
+ def spearman(a, b):
10
+ a = a.double(); b = b.double()
11
+ ra = a.argsort().argsort().double()
12
+ rb = b.argsort().argsort().double()
13
+ ra = ra - ra.mean(); rb = rb - rb.mean()
14
+ return float((ra @ rb) / (ra.norm() * rb.norm()).clamp_min(1e-12))
15
+
16
+ layers = ic.moe_layers()
17
+ scores = {}
18
+ details = {}
19
+ for L in layers:
20
+ f = f"{TMP}/layer_{L}.pt"
21
+ if not os.path.exists(f):
22
+ print(f"MISSING layer {L}"); continue
23
+ d = torch.load(f, map_location="cpu")
24
+ scores[L] = d["score"].float()
25
+ details[L] = d
26
+
27
+ torch.save(scores, OUT)
28
+ print(f"WROTE {OUT} with {len(scores)} layers (expected 64)")
29
+
30
+ rs = torch.load("/data/inkling-quant/route_stats.pt", map_location="cpu")
31
+ print("\n== validation: spearman(reap_score, route wmass) for 3 layers ==")
32
+ for L in [layers[0], layers[len(layers)//2], layers[-1]]:
33
+ wm = rs[L]["wmass"].float()
34
+ sc = scores[L]
35
+ print(f"L{L}: spearman={spearman(sc, wm):+.3f} "
36
+ f"score mean={sc.mean():.4g} max={sc.max():.4g} min={sc.min():.4g} "
37
+ f"relerr mean={details[L]['relerr'].mean():.4f} "
38
+ f"experts_with_tokens={int((details[L]['ntok']>0).sum())}/256")
39
+
40
+ print("\n== per-layer score sanity (all layers) ==")
41
+ allmean = torch.tensor([scores[L].mean() for L in scores])
42
+ allmax = torch.tensor([scores[L].max() for L in scores])
43
+ zerocnt = sum(int((scores[L]==0).sum()) for L in scores)
44
+ print(f"layers={len(scores)} score.mean range [{allmean.min():.4g},{allmean.max():.4g}] "
45
+ f"score.max range [{allmax.min():.4g},{allmax.max():.4g}] total zero-experts={zerocnt}")
46
+ print("shapes ok:", all(scores[L].shape==(256,) for L in scores),
47
+ " finite:", all(torch.isfinite(scores[L]).all() for L in scores))
code/reap_worker.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Compute TRUE REAP expert-importance scores for a set of MoE layers on ONE gpu.
3
+ score_e = saliency_e * relerr_e
4
+ saliency_e = sum_t g_{t,e} * ||f_e(x_t)||_2 (teacher expert output L2 norm)
5
+ relerr_e = param-weighted 2-bit AQLM reconstruction error of expert e
6
+ Writes /data/inkling-quant/reap_tmp/layer_{L}.pt = fp32 tensor[256].
7
+ """
8
+ import os, sys, time, json, traceback
9
+ import torch
10
+ import torch.nn.functional as F
11
+ sys.path.insert(0, "/data/inkling-quant/tools")
12
+ import ink_common as ic
13
+
14
+ DEV = "cuda"
15
+ R, S, K = 256, 2, 6 # routed, shared(sink), top-k
16
+ ROUTE_SCALE = 8.0
17
+ N13 = 6144 * 6144
18
+ N2 = 6144 * 3072
19
+ TMP = "/data/inkling-quant/reap_tmp"
20
+ os.makedirs(TMP, exist_ok=True)
21
+
22
+
23
+ def gate_weights(x_f32, gw, bias, scale):
24
+ """Replicate InklingGate.select_experts. Returns (topi[T,6] long, rw[T,6] fp32)."""
25
+ logits = x_f32 @ gw.T # [T,258] fp32
26
+ sel = torch.sigmoid(logits[:, :R]) + bias.view(1, -1) # [T,256]
27
+ _, topi = sel.topk(K, dim=1) # [T,6]
28
+ routed_raw = torch.gather(logits[:, :R], 1, topi) # [T,6]
29
+ sink_raw = logits[:, R:R + S] # [T,2]
30
+ active = torch.cat([routed_raw, sink_raw], dim=1) # [T,8]
31
+ lp = torch.minimum(active, torch.zeros_like(active)) - torch.log1p(torch.exp(-active.abs()))
32
+ lp = lp - lp.max(1, keepdim=True).values
33
+ e = torch.exp(lp)
34
+ w = e / e.sum(1, keepdim=True) * scale # [T,8]
35
+ return topi, w[:, :K].contiguous()
36
+
37
+
38
+ def process_layer(L, reader, treader):
39
+ t0 = time.time()
40
+ a = torch.load(f"{__import__('os').environ.get('INK_ACTS','/data/inkling-quant/acts')}/acts_layer_{L}.pt", map_location="cpu")
41
+ n = int(a["n"])
42
+ x_f16 = a["x"][:n].to(DEV) # [T,6144] fp16
43
+ x_f32 = x_f16.float()
44
+
45
+ gw = reader.get(f"model.llm.layers.{L}.mlp.gate.weight").to(DEV).float()
46
+ bias = reader.get(f"model.llm.layers.{L}.mlp.gate.bias").to(DEV).float()
47
+ gs = float(reader.get(f"model.llm.layers.{L}.mlp.gate.global_scale")[0])
48
+ scale = ROUTE_SCALE * gs
49
+
50
+ topi, rw = gate_weights(x_f32, gw, bias, scale) # [T,6],[T,6]
51
+ del x_f32, gw, bias
52
+
53
+ # Full BF16 TEACHER weights for this layer (from BF16_DIR).
54
+ w13 = treader.get(ic._pfx(L, "w13")).to(DEV).to(torch.bfloat16) # [256,6144,6144]
55
+ w2 = treader.get(ic._pfx(L, "w2")).to(DEV).to(torch.bfloat16) # [256,6144,3072]
56
+
57
+ # AQLM parts (w13 CONVERGED, w2).
58
+ p13 = torch.load(f"{__import__('os').environ.get('INK_CONV_REF','/data/inkling-quant/parts_conv')}/layer_{L}_w13.pt", map_location="cpu")
59
+ p2 = torch.load(f"/data/inkling-quant/parts/layer_{L}_w2.pt", map_location="cpu")
60
+
61
+ saliency = torch.zeros(R, dtype=torch.float32, device=DEV)
62
+ relerr = torch.zeros(R, dtype=torch.float32, device=DEV)
63
+ ntok = torch.zeros(R, dtype=torch.long, device=DEV)
64
+
65
+ for e in range(R):
66
+ # ---- saliency ----
67
+ slot_mask = (topi == e) # [T,6] bool
68
+ tok_mask = slot_mask.any(1) # [T]
69
+ idx = tok_mask.nonzero(as_tuple=True)[0]
70
+ ne = idx.numel()
71
+ ntok[e] = ne
72
+ if ne > 0:
73
+ g_e = (rw * slot_mask).sum(1)[idx] # [ne] fp32
74
+ xe = x_f16.index_select(0, idx) # [ne,6144] fp16
75
+ we = w13[e] # [6144,6144] bf16
76
+ gate_e = we[0::2].to(torch.bfloat16) # [3072,6144]
77
+ up_e = we[1::2].to(torch.bfloat16) # [3072,6144]
78
+ xt = xe.to(torch.bfloat16).T # [6144,ne]
79
+ ge = gate_e @ xt # [3072,ne]
80
+ ue = up_e @ xt
81
+ h = F.silu(ge.float()) * ue.float() # [3072,ne] fp32
82
+ fe = w2[e].float() @ h # [6144,ne] fp32
83
+ norms = fe.norm(dim=0) # [ne]
84
+ saliency[e] = (g_e * norms).sum()
85
+ del xe, xt, ge, ue, h, fe, norms, g_e
86
+
87
+ # ---- relerr ----
88
+ W13 = w13[e].float()
89
+ dq13 = ic.aqlm_dequant_expert(p13, "w13", e, device=DEV).float()
90
+ rel13 = torch.linalg.norm(W13 - dq13) / torch.linalg.norm(W13).clamp_min(1e-12)
91
+ del W13, dq13
92
+ W2 = w2[e].float()
93
+ dq2 = ic.aqlm_dequant_expert(p2, "w2", e, device=DEV).float()
94
+ rel2 = torch.linalg.norm(W2 - dq2) / torch.linalg.norm(W2).clamp_min(1e-12)
95
+ del W2, dq2
96
+ relerr[e] = (rel13 * N13 + rel2 * N2) / (N13 + N2)
97
+
98
+ score = (saliency * relerr).cpu()
99
+ out = {
100
+ "score": score,
101
+ "saliency": saliency.cpu(),
102
+ "relerr": relerr.cpu(),
103
+ "ntok": ntok.cpu(),
104
+ }
105
+ torch.save(out, f"{TMP}/layer_{L}.pt")
106
+ dt = time.time() - t0
107
+ nz = int((ntok > 0).sum())
108
+ print(f"[L{L}] done {dt:.1f}s score mean={score.mean():.4g} max={score.max():.4g} "
109
+ f"| saliency max={saliency.max():.4g} | relerr mean={relerr.mean():.4f} "
110
+ f"| experts_with_tokens={nz}/256", flush=True)
111
+ del w13, w2, x_f16, topi, rw, saliency, relerr, ntok
112
+ torch.cuda.empty_cache()
113
+
114
+
115
+ def main():
116
+ layers = [int(x) for x in sys.argv[1].split(",")]
117
+ reader = ic.ShardReader(ic.NVFP4_DIR) # gate params
118
+ treader = ic.ShardReader(ic.BF16_DIR) # bf16 teacher experts
119
+ print(f"[worker pid={os.getpid()} gpu={os.environ.get('CUDA_VISIBLE_DEVICES')}] layers={layers}", flush=True)
120
+ for L in layers:
121
+ try:
122
+ process_layer(L, reader, treader)
123
+ except Exception:
124
+ print(f"[L{L}] ERROR\n{traceback.format_exc()}", flush=True)
125
+
126
+
127
+ if __name__ == "__main__":
128
+ main()
code/run_capture_acts.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Capture per-layer activation samples (routed hidden inputs x + expert ids)
3
+ over the calib corpus, for offline Hessian construction (Phase E converge).
4
+ Dumps /data/inkling-quant/acts/acts_layer_{L}.pt atexit (per TP worker; the
5
+ residual stream is replicated so any worker's dump is complete).
6
+ """
7
+ import json, os
8
+
9
+ def main():
10
+ os.environ.setdefault("INK_ACT_DIR", "/data/inkling-quant/acts")
11
+ os.environ.setdefault("INK_ACT_CAP", "24000")
12
+ os.environ.setdefault("VLLM_USE_DEEP_GEMM", "0")
13
+ from vllm import LLM, SamplingParams
14
+ prompts = [json.loads(l)["text"] for l in open("/data/inkling-quant/calib.jsonl")]
15
+ print(f"loaded {len(prompts)} calib prompts; capturing acts (cap {os.environ['INK_ACT_CAP']}/layer)")
16
+ llm = LLM(model="/data/inkling-nvfp4", tensor_parallel_size=4, max_model_len=8192,
17
+ gpu_memory_utilization=0.9, enforce_eager=True, trust_remote_code=True)
18
+ llm.generate(prompts, SamplingParams(max_tokens=1, temperature=0))
19
+ print("done; acts dump atexit.")
20
+
21
+ if __name__ == "__main__":
22
+ main()
code/run_capture_acts_heldout.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Capture per-layer activation samples over the HELD-OUT corpus (heldout.jsonl),
3
+ into /data/inkling-quant/acts_heldout/. Same hook as run_capture_acts.py but a
4
+ disjoint text set + a cap BELOW the held-out token count (25723) so EVERY layer
5
+ hits the cap mid-run and dumps via the safe incremental path (no atexit truncation).
6
+ GPUs 4-7 only (set via CUDA_VISIBLE_DEVICES before launch)."""
7
+ import json, os
8
+
9
+ def main():
10
+ os.environ.setdefault("INK_ACT_DIR", "/data/inkling-quant/acts_heldout")
11
+ os.environ.setdefault("INK_ACT_CAP", "16000")
12
+ os.environ.setdefault("VLLM_USE_DEEP_GEMM", "0")
13
+ from vllm import LLM, SamplingParams
14
+ prompts = [json.loads(l)["text"] for l in open("/data/inkling-quant/heldout.jsonl")]
15
+ print(f"loaded {len(prompts)} held-out prompts; capturing acts "
16
+ f"(cap {os.environ['INK_ACT_CAP']}/layer) -> {os.environ['INK_ACT_DIR']}")
17
+ llm = LLM(model="/data/inkling-nvfp4", tensor_parallel_size=4, max_model_len=8192,
18
+ gpu_memory_utilization=0.9, enforce_eager=True, trust_remote_code=True)
19
+ llm.generate(prompts, SamplingParams(max_tokens=1, temperature=0))
20
+ print("done; residual layers dump atexit.")
21
+
22
+ if __name__ == "__main__":
23
+ main()
code/run_capture_acts_mm.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Multimodal activation capture for Inkling v3 PV.
3
+
4
+ Inkling is inkling_mm_model (vision hmlp + audio dMel towers -> 6144-dim, feeding
5
+ the SAME MoE backbone as text). A text-only PV pass would leave the image/audio-
6
+ routed experts uncalibrated and could DEGRADE multimodal performance. This driver
7
+ feeds image+text and audio+text prompts through vLLM so the acts hook (INK_ACT_DIR)
8
+ captures MoE activations on multimodal tokens too.
9
+
10
+ Modality is chosen by INK_MM=image|audio. Writes acts to INK_ACT_DIR (keep image
11
+ and audio in SEPARATE dirs so pv_v3 can merge text+image+audio per expert):
12
+
13
+ # image acts (chest X-rays + radiology-style prompt)
14
+ INK_MM=image INK_ACT_DIR=/data/inkling-quant/acts_v3_img INK_ACT_CAP=40000 \
15
+ CUDA_VISIBLE_DEVICES=4,5,6,7 python run_capture_acts_mm.py
16
+ # audio acts (LibriSpeech + transcribe prompt)
17
+ INK_MM=audio INK_ACT_DIR=/data/inkling-quant/acts_v3_audio INK_ACT_CAP=40000 \
18
+ CUDA_VISIBLE_DEVICES=4,5,6,7 python run_capture_acts_mm.py
19
+
20
+ Placeholders (from the model): image=<|content_image|>, audio=<|content_audio_input|>.
21
+ Holds out the last HOLDOUT items of each modality (INK_MM_HOLDOUT dir) for mm ppl.
22
+ """
23
+ import os, glob, json, wave, random
24
+ import numpy as np
25
+
26
+ IMG_DIR = "/data/inkling-quant/corpus_v3/raw/radiology/images"
27
+ LS_DIR = "/data/asr/datasets-asr-normalized/librispeech"
28
+ IMG_PROMPTS = [
29
+ "<|content_image|>\nDescribe the findings in this chest radiograph.",
30
+ "<|content_image|>\nReport any abnormalities visible in this chest X-ray.",
31
+ "<|content_image|>\nWhat pathology, if any, is present in this image?",
32
+ ]
33
+ AUD_PROMPT = "<|content_audio_input|>\nTranscribe the spoken audio."
34
+
35
+ def load_images(n, off=0):
36
+ """Prefer the multimodal manifest (path,domain,prompt across all image domains);
37
+ fall back to the chest-X-ray glob if no manifest is given."""
38
+ from PIL import Image
39
+ manifest = os.environ.get("INK_MM_MANIFEST")
40
+ out = []
41
+ if manifest and os.path.exists(manifest):
42
+ rows = [json.loads(l) for l in open(manifest)][off:off+n]
43
+ for r in rows:
44
+ try:
45
+ out.append((r["prompt"], {"image": Image.open(r["path"]).convert("RGB")}))
46
+ except Exception as e:
47
+ print(" img skip", os.path.basename(r.get("path","?")), str(e)[:50])
48
+ return out
49
+ fs = sorted(glob.glob(f"{IMG_DIR}/*.png"))[off:off+n]
50
+ for i, f in enumerate(fs):
51
+ try:
52
+ out.append((IMG_PROMPTS[i % len(IMG_PROMPTS)], {"image": Image.open(f).convert("RGB")}))
53
+ except Exception as e:
54
+ print(" img skip", os.path.basename(f), str(e)[:50])
55
+ return out
56
+
57
+ def load_audio(n, off=0):
58
+ fs = sorted(glob.glob(f"{LS_DIR}/dev-clean/*.wav"))[off:off+n]
59
+ out = []
60
+ for f in fs:
61
+ try:
62
+ w = wave.open(f); sr = w.getframerate()
63
+ a = np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16).astype(np.float32) / 32768.0
64
+ out.append((AUD_PROMPT, {"audio": (a, sr)}))
65
+ except Exception as e:
66
+ print(" aud skip", os.path.basename(f), str(e)[:50])
67
+ return out
68
+
69
+ def main():
70
+ mm = os.environ.get("INK_MM", "image")
71
+ os.environ.setdefault("INK_ACT_DIR", f"/data/inkling-quant/acts_v3_{mm}")
72
+ os.environ.setdefault("INK_ACT_CAP", "40000")
73
+ os.environ.setdefault("VLLM_USE_DEEP_GEMM", "0")
74
+ n = int(os.environ.get("INK_MM_N", "300"))
75
+ off = int(os.environ.get("INK_MM_OFFSET", "0")) # disjoint held-out slice
76
+ items = load_images(n, off) if mm == "image" else load_audio(n, off)
77
+ print(f"INK_MM={mm} items={len(items)} -> {os.environ['INK_ACT_DIR']} (cap {os.environ['INK_ACT_CAP']})", flush=True)
78
+ if not items:
79
+ raise SystemExit(f"no {mm} inputs found")
80
+ from vllm import LLM, SamplingParams
81
+ llm = LLM(model="/data/inkling-nvfp4", tensor_parallel_size=4, max_model_len=8192,
82
+ gpu_memory_utilization=0.9, enforce_eager=True, trust_remote_code=True,
83
+ limit_mm_per_prompt={mm: 1})
84
+ reqs = [{"prompt": p, "multi_modal_data": d} for p, d in items]
85
+ llm.generate(reqs, SamplingParams(max_tokens=1, temperature=0))
86
+ print("done; acts dump atexit.", flush=True)
87
+
88
+ if __name__ == "__main__":
89
+ main()
code/run_capture_acts_v3.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """v3 activation capture — parameterized corpus / out-dir / per-layer cap.
3
+
4
+ The v2 PV starvation came from INK_ACT_CAP=24000 tokens/layer (the "~25k" that
5
+ overfit). v3 raises the cap AND uses the bigger disjoint corpora:
6
+
7
+ # TRAIN acts (what PV tunes on) — big cap for cold-expert coverage
8
+ INK_CORPUS=/data/inkling-quant/calib_v3.jsonl INK_ACT_DIR=/data/inkling-quant/acts_v3 \
9
+ INK_ACT_CAP=80000 CUDA_VISIBLE_DEVICES=4,5,6,7 python run_capture_acts_v3.py
10
+
11
+ # VAL acts (PV early-stop signal) — disjoint corpus, smaller cap is fine
12
+ INK_CORPUS=/data/inkling-quant/heldout_v3.jsonl INK_ACT_DIR=/data/inkling-quant/acts_v3_heldout \
13
+ INK_ACT_CAP=24000 CUDA_VISIBLE_DEVICES=4,5,6,7 python run_capture_acts_v3.py
14
+
15
+ Disk: ~ cap * 6144 * 2 bytes * 64 layers (e.g. cap=80000 -> ~63 GB). Check df first.
16
+ """
17
+ import json, os
18
+
19
+ def main():
20
+ corpus = os.environ.setdefault("INK_CORPUS", "/data/inkling-quant/calib_v3.jsonl")
21
+ os.environ.setdefault("INK_ACT_DIR", "/data/inkling-quant/acts_v3")
22
+ os.environ.setdefault("INK_ACT_CAP", "80000")
23
+ os.environ.setdefault("VLLM_USE_DEEP_GEMM", "0")
24
+ from vllm import LLM, SamplingParams
25
+ prompts = [json.loads(l)["text"] for l in open(corpus)]
26
+ print(f"corpus={corpus} n={len(prompts)} -> {os.environ['INK_ACT_DIR']} "
27
+ f"(cap {os.environ['INK_ACT_CAP']}/layer)", flush=True)
28
+ llm = LLM(model="/data/inkling-nvfp4", tensor_parallel_size=4, max_model_len=8192,
29
+ gpu_memory_utilization=0.9, enforce_eager=True, trust_remote_code=True)
30
+ llm.generate(prompts, SamplingParams(max_tokens=1, temperature=0))
31
+ print("done; acts dump atexit.", flush=True)
32
+
33
+ if __name__ == "__main__":
34
+ main()
code/run_route_stats.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Run the calib corpus through Inkling-NVFP4 offline (vLLM LLM) with
3
+ INK_ROUTE_STATS set, so moe.py accumulates per-layer per-expert routing
4
+ frequency + weight mass. Prefill dominates -> every calib token exercises
5
+ routing. Stats dumped atexit to $INK_ROUTE_STATS (in each TP worker; the
6
+ gate is replicated so any worker's dump is complete).
7
+ """
8
+ import json, os
9
+
10
+ def main():
11
+ os.environ.setdefault("INK_ROUTE_STATS", "/data/inkling-quant/route_stats.pt")
12
+ os.environ.setdefault("VLLM_USE_DEEP_GEMM", "0")
13
+ from vllm import LLM, SamplingParams
14
+ CALIB = "/data/inkling-quant/calib.jsonl"
15
+ prompts = [json.loads(l)["text"] for l in open(CALIB)]
16
+ print(f"loaded {len(prompts)} calib prompts")
17
+ llm = LLM(
18
+ model="/data/inkling-nvfp4",
19
+ tensor_parallel_size=4,
20
+ max_model_len=8192,
21
+ gpu_memory_utilization=0.9,
22
+ enforce_eager=True,
23
+ trust_remote_code=True,
24
+ )
25
+ sp = SamplingParams(max_tokens=1, temperature=0) # prefill routing only
26
+ out = llm.generate(prompts, sp)
27
+ print(f"processed {len(out)} prompts; route stats dump atexit.")
28
+
29
+ if __name__ == "__main__":
30
+ main()
code/solve_tier.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Byte-exact expert tiering solver for the Inkling hybrid.
3
+ Ranks experts globally by routing mass; upgrades the highest-mass experts from
4
+ AQLM (cold) to NVFP4 (hot) until the expert VRAM budget is spent, with per-layer
5
+ floor/cap. Reports achievable hot% + avg bpw for a target deployment budget.
6
+
7
+ Deployment target = 4x96GB RTX6000 = 384GB (we emulate on B200; ship for 384GB).
8
+ """
9
+ import argparse, json, math
10
+ import torch
11
+ import ink_common as ic
12
+
13
+ # exact per-expert shapes (in params)
14
+ W13 = (6144, 6144); W2 = (6144, 3072)
15
+ P13 = W13[0]*W13[1]; P2 = W2[0]*W2[1]
16
+ GDIM = 8
17
+
18
+ def nvfp4_bytes():
19
+ # 4-bit packed + fp8 block-16 scale + fp32 scale2 (negligible)
20
+ def lin(o,i): return o*i*0.5 + o*(i/16)*1.0 # u8 packed + f8 scale
21
+ return lin(*W13) + lin(*W2)
22
+
23
+ def aqlm_bytes(w13_books, w2_books, book2_entries):
24
+ code_b = 2 # book0 int16 (65536)
25
+ b2 = 1 if book2_entries <= 256 else 2
26
+ def proj(o,i,books):
27
+ groups = o*(i//GDIM)
28
+ by = groups*code_b + (groups*b2*(books-1) if books>1 else 0)
29
+ return by + o*2 # + fp16 per-row scale
30
+ return proj(*W13, w13_books) + proj(*W2, w2_books)
31
+
32
+ def main():
33
+ ap = argparse.ArgumentParser()
34
+ ap.add_argument("--budget-gb", type=float, default=384.0)
35
+ ap.add_argument("--nonexpert-gb", type=float, default=40.0)
36
+ ap.add_argument("--kv-gb", type=float, default=11.0) # ~512k fp8
37
+ ap.add_argument("--workspace-gb", type=float, default=25.0)
38
+ ap.add_argument("--w13-books", type=int, default=1)
39
+ ap.add_argument("--w2-books", type=int, default=2)
40
+ ap.add_argument("--book2-entries", type=int, default=65536)
41
+ ap.add_argument("--floor", type=int, default=4) # min hot per layer
42
+ ap.add_argument("--cap", type=int, default=220) # max hot per layer
43
+ ap.add_argument("--save", default="")
44
+ ap.add_argument("--route-stats", default="/data/inkling-quant/route_stats.pt")
45
+ ap.add_argument("--scores", default="", help="optional pt {L:tensor[256]} (e.g. REAP) to rank by instead of routing mass")
46
+ a = ap.parse_args()
47
+
48
+ st = torch.load(a.route_stats)
49
+ layers = sorted(st.keys()); E = ic.N_EXPERTS
50
+ mass = {L: st[L]["wmass"].clone() for L in layers} # router-weight mass per expert
51
+ if a.scores:
52
+ sc = torch.load(a.scores)
53
+ mass = {L: sc[L].clone().float() for L in layers} # rank by supplied scores (REAP)
54
+ print(f"ranking by --scores {a.scores} (overrides routing mass)")
55
+
56
+ hotB = nvfp4_bytes(); coldB = aqlm_bytes(a.w13_books, a.w2_books, a.book2_entries)
57
+ n_exp = len(layers)*E
58
+ expert_budget = (a.budget_gb - a.nonexpert_gb - a.kv_gb - a.workspace_gb) * 1e9
59
+ all_cold = n_exp * coldB
60
+ print(f"per-expert: NVFP4(hot)={hotB/1e6:.1f}MB AQLM(cold w13={a.w13_books}b/w2={a.w2_books}b@{a.book2_entries})={coldB/1e6:.1f}MB")
61
+ print(f"experts total={n_exp} all-cold={all_cold/1e9:.1f}GB expert budget={expert_budget/1e9:.1f}GB")
62
+ spare = expert_budget - all_cold
63
+ if spare < 0:
64
+ print(f"INFEASIBLE: all-cold ({all_cold/1e9:.1f}GB) exceeds budget ({expert_budget/1e9:.1f}GB) by {-spare/1e9:.1f}GB. Need cheaper cold scheme.")
65
+ return
66
+ max_upgrades = int(spare // (hotB - coldB))
67
+ print(f"spare after all-cold={spare/1e9:.1f}GB -> up to {max_upgrades} hot upgrades ({100*max_upgrades/n_exp:.1f}% hot)")
68
+
69
+ # global rank by mass, apply floor first (guarantee floor hot per layer), then fill by mass under cap
70
+ assign = {L: torch.zeros(E, dtype=torch.bool) for L in layers}
71
+ used = 0
72
+ # floor
73
+ for L in layers:
74
+ top = torch.argsort(mass[L], descending=True)[:a.floor]
75
+ assign[L][top] = True; used += a.floor
76
+ # remaining budget by global mass ranking (respect cap)
77
+ pool = []
78
+ for L in layers:
79
+ order = torch.argsort(mass[L], descending=True).tolist()
80
+ for rank,e in enumerate(order):
81
+ if not assign[L][e]:
82
+ pool.append((float(mass[L][e]), L, e, rank))
83
+ pool.sort(reverse=True)
84
+ for m,L,e,rank in pool:
85
+ if used >= max_upgrades: break
86
+ if int(assign[L].sum()) >= a.cap: continue
87
+ assign[L][e] = True; used += 1
88
+ hot_total = used
89
+ # per-band report
90
+ def band(lo,hi): return sum(int(assign[L].sum()) for L in layers if lo<=L<=hi)/max(1,len([L for L in layers if lo<=L<=hi]))
91
+ print(f"\nHOT experts assigned: {hot_total} ({100*hot_total/n_exp:.1f}%)")
92
+ print(f"per-layer mean hot: early(2-15)={band(2,15):.0f} mid(30-45)={band(30,45):.0f} deep(55-65)={band(55,65):.0f} (of 256)")
93
+ avg_bpw = ((hot_total*hotB + (n_exp-hot_total)*coldB)*8) / (n_exp*(P13+P2))
94
+ print(f"avg expert bpw={avg_bpw:.2f} total expert VRAM={((hot_total*hotB+(n_exp-hot_total)*coldB))/1e9:.1f}GB")
95
+ if a.save:
96
+ out = {str(L): sorted(torch.nonzero(assign[L]).flatten().tolist()) for L in layers}
97
+ json.dump({"hot_experts": out, "config": vars(a), "hot_total": hot_total,
98
+ "hot_bytes": hotB, "cold_bytes": coldB}, open(a.save,"w"))
99
+ print("saved assignment ->", a.save)
100
+
101
+ if __name__ == "__main__":
102
+ main()
code/v1_vs_v2_heldout.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch, json, random, statistics as st
2
+ import ink_common as ic
3
+ random.seed(0); dev="cuda"
4
+ # v1 cold = converged w13 (parts_conv) + INIT w2 (parts); v2 cold = converged w13 + CONVERGED w2 (parts_conv)
5
+ tc=ic.ShardReader(ic.BF16_DIR)
6
+ def eout(W13,W2,x):
7
+ g=W13[0::2]; u=W13[1::2]
8
+ h=torch.nn.functional.silu(x@g.t())*(x@u.t()); return h@W2.t()
9
+ layers=[3,8,15,22,30,38,45,52,58,63]
10
+ v1=[]; v2=[]
11
+ for L in layers:
12
+ a=torch.load(f"/data/inkling-quant/acts_heldout/acts_layer_{L}.pt",map_location=dev)
13
+ x=a["x"].float(); ids=a["ids"]
14
+ # cold = experts not hot in v2 assign (same set for both comparisons; tiering handled separately)
15
+ hot=set(json.load(open("/data/inkling-quant/assign_512k_v2.json"))["hot_experts"][str(L)])
16
+ cold=[e for e in range(256) if e not in hot]
17
+ c13=torch.load(f"/data/inkling-quant/parts_conv/layer_{L}_w13.pt",map_location="cpu") # converged w13 (both)
18
+ w2_init=torch.load(f"/data/inkling-quant/parts/layer_{L}_w2.pt",map_location="cpu") # v1 w2 (init)
19
+ w2_conv=torch.load(f"/data/inkling-quant/parts_conv/layer_{L}_w2.pt",map_location="cpu")# v2 w2 (converged)
20
+ W13=None
21
+ for e in random.sample(cold,min(12,len(cold))):
22
+ m=(ids==e).any(1)
23
+ if int(m.sum())<8: continue
24
+ xe=x[m][:2048]
25
+ ft=eout(ic.teacher_expert(tc,L,"w13",e,device=dev).float(),ic.teacher_expert(tc,L,"w2",e,device=dev).float(),xe); nt=ft.norm()
26
+ Wc13=ic.aqlm_dequant_expert(c13,"w13",e,device=dev).float()
27
+ v1.append(((eout(Wc13,ic.aqlm_dequant_expert(w2_init,"w2",e,device=dev).float(),xe)-ft).norm()/nt).item())
28
+ v2.append(((eout(Wc13,ic.aqlm_dequant_expert(w2_conv,"w2",e,device=dev).float(),xe)-ft).norm()/nt).item())
29
+ a=st.mean(v1); b=st.mean(v2)
30
+ print(f"HELD-OUT per-expert output rel-err over {len(v1)} cold experts:")
31
+ print(f" v1 cold (conv-w13 + INIT-w2): {a:.4f}")
32
+ print(f" v2 cold (conv-w13 + CONVERGED-w2): {b:.4f}")
33
+ print(f" v2 vs v1 on HELD-OUT: {100*(1-b/a):+.1f}% -> {'v2 cold experts BETTER (w2-converge generalizes)' if b < a*0.995 else 'no held-out gain'}")
code/validate_gate_ckpt.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Validate /data/inkling-512k-gate:
3
+ (a) all shards referenced by the output index exist + non-weight files copied;
4
+ (b) output index present & its referenced shards all exist;
5
+ (c) cold expert w13 dequant rel_err vs BF16 teacher ~0.30-0.32 (carries AQLM err);
6
+ (d) hot expert unchanged vs /data/inkling-nvfp4 (rel_err ~0.09, byte-identical).
7
+ """
8
+ import json, os, torch, filecmp
9
+ import ink_common as ic
10
+ from safetensors import safe_open
11
+
12
+ OUT = "/data/inkling-512k-gate"
13
+ SRC = ic.NVFP4_DIR
14
+ ASSIGN = "/data/inkling-quant/assign_512k.json"
15
+
16
+
17
+ def main():
18
+ dev = "cuda"
19
+ ok = True
20
+ # ---- (a)/(b) file inventory ----
21
+ idxp = f"{OUT}/model.safetensors.index.json"
22
+ assert os.path.exists(idxp), "output index missing"
23
+ wm = json.load(open(idxp))["weight_map"]
24
+ shards = sorted(set(wm.values()))
25
+ missing = [s for s in shards if not os.path.exists(f"{OUT}/{s}")]
26
+ print(f"[a/b] index references {len(shards)} shards; missing: {missing}")
27
+ ok &= not missing
28
+ n_st = len([f for f in os.listdir(OUT) if f.endswith('.safetensors')])
29
+ print(f"[a] .safetensors files in output: {n_st}")
30
+ SKIP = {".cache"} # HF download cache dir, intentionally not copied
31
+ nonweight = [f for f in os.listdir(SRC) if not f.endswith('.safetensors') and f not in SKIP]
32
+ miss_nw = [f for f in nonweight if not os.path.exists(f"{OUT}/{f}")]
33
+ print(f"[a] non-weight source files: {len(nonweight)}; missing in output: {miss_nw}")
34
+ ok &= not miss_nw
35
+
36
+ hot = json.load(open(ASSIGN))["hot_experts"]
37
+ L = 10
38
+ hot_L = set(hot[str(L)])
39
+ cold_e = next(e for e in range(ic.N_EXPERTS) if e not in hot_L)
40
+ hot_e = sorted(hot_L)[0]
41
+
42
+ tR = ic.ShardReader(ic.BF16_DIR)
43
+ oR = ic.ShardReader(OUT) # the new checkpoint
44
+ nR = ic.ShardReader(SRC) # original nvfp4
45
+
46
+ # ---- (c) cold expert now carries AQLM error ----
47
+ proj = "w13"
48
+ teach = ic.teacher_expert(tR, L, proj, cold_e, dev).float()
49
+ coldW = ic.dequant_nvfp4_expert(oR, L, proj, cold_e, dev).float()
50
+ cold_err = ((coldW - teach).norm() / teach.norm()).item()
51
+ print(f"[c] COLD e{cold_e} {proj} rel_err vs teacher = {cold_err:.4f} (expect 0.30-0.32)")
52
+ ok &= 0.29 <= cold_err <= 0.33
53
+
54
+ # ---- (d) hot expert unchanged ----
55
+ teach_h = ic.teacher_expert(tR, L, proj, hot_e, dev).float()
56
+ hotW = ic.dequant_nvfp4_expert(oR, L, proj, hot_e, dev).float()
57
+ hot_err = ((hotW - teach_h).norm() / teach_h.norm()).item()
58
+ print(f"[d] HOT e{hot_e} {proj} rel_err vs teacher = {hot_err:.4f} (expect ~0.09)")
59
+ ok &= 0.07 <= hot_err <= 0.11
60
+ # byte-identical hot slice vs original nvfp4 (all three tensors)
61
+ p = f"model.llm.layers.{L}.mlp.experts.{proj}_weight"
62
+ for suf in ["", ".scale", ".scale2"]:
63
+ a = nR.get(p + suf)[hot_e]
64
+ b = oR.get(p + suf)[hot_e]
65
+ same = torch.equal(a, b)
66
+ print(f"[d] HOT byte-identical {p+suf}[{hot_e}] vs original nvfp4: {same}")
67
+ ok &= same
68
+ # sanity: cold slice DIFFERS from original nvfp4
69
+ diff = not torch.equal(nR.get(p)[cold_e], oR.get(p)[cold_e])
70
+ print(f"[c] COLD packed[{cold_e}] differs from original nvfp4: {diff}")
71
+ ok &= diff
72
+
73
+ # ---- (e) layer 2 (full-precision bf16 special layer) ----
74
+ L2 = 2
75
+ hot2 = set(json.load(open(ASSIGN))["hot_experts"]["2"])
76
+ c2 = next(e for e in range(ic.N_EXPERTS) if e not in hot2)
77
+ h2 = sorted(hot2)[0]
78
+ p2 = f"model.llm.layers.{L2}.mlp.experts.w13_weight"
79
+ print(f"\n[e] layer2 w13_weight dtype in output: {oR.get(p2).dtype} (expect bfloat16, unpacked)")
80
+ t2 = ic.teacher_expert(tR, L2, "w13", c2, dev).float()
81
+ got2 = oR.get(p2)[c2].to(dev).float()
82
+ e2 = ((got2 - t2).norm() / t2.norm()).item()
83
+ print(f"[e] layer2 COLD e{c2} w13 rel_err vs teacher = {e2:.4f} (expect ~0.30, pure AQLM bf16)")
84
+ ok &= 0.28 <= e2 <= 0.32
85
+ same2 = torch.equal(nR.get(p2)[h2], oR.get(p2)[h2])
86
+ print(f"[e] layer2 HOT e{h2} w13 byte-identical vs original: {same2}")
87
+ ok &= same2
88
+
89
+ print("\nVALIDATION:", "PASS" if ok else "FAIL")
90
+ return 0 if ok else 1
91
+
92
+
93
+ if __name__ == "__main__":
94
+ raise SystemExit(main())
code/validate_hybrid.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Validate the assembled /data/inkling-512k-hybrid checkpoint before upload."""
3
+ import json, os, glob
4
+ import torch
5
+ from safetensors import safe_open
6
+ import build_hybrid as B
7
+ import ink_common as ic
8
+
9
+ OUT = B.OUT
10
+
11
+ def validate():
12
+ ok = True
13
+ idx = json.load(open(f"{OUT}/model.safetensors.index.json"))
14
+ wm = idx["weight_map"]
15
+ total = idx["metadata"]["total_size"]
16
+ ref_shards = sorted(set(wm.values()))
17
+ disk_shards = sorted(os.path.basename(p) for p in glob.glob(f"{OUT}/model-*.safetensors"))
18
+
19
+ print(f"[chk] index tensors: {len(wm)} total_size: {total/1e9:.1f} GB")
20
+ print(f"[chk] shards referenced: {len(ref_shards)} on disk: {len(disk_shards)}")
21
+
22
+ missing = [s for s in ref_shards if not os.path.exists(f"{OUT}/{s}")]
23
+ extra = [s for s in disk_shards if s not in set(ref_shards)]
24
+ if missing:
25
+ ok = False; print(f" !! MISSING shards: {missing}")
26
+ if extra:
27
+ print(f" ~~ extra shards on disk (not in index): {extra}")
28
+ if not missing and not extra:
29
+ print(" ok: shard set matches index exactly")
30
+
31
+ # size sanity: smaller build must fit a 384 GB (4x96GB) machine.
32
+ gb = total / 1e9
33
+ # per-book uint8 cold codes (~ -38 GB) + layer-2 compressed (~ -25 GB) => ~345-355 GB
34
+ if not (330 <= gb <= 375):
35
+ ok = False; print(f" !! total size {gb:.1f} GB out of expected band (330-375)")
36
+ else:
37
+ print(f" ok: total size {gb:.1f} GB in range")
38
+ if gb < 384:
39
+ print(f" ok: total size {gb:.1f} GB < 384 GB (fits 4x96GB machine)")
40
+ else:
41
+ ok = False; print(f" !! total size {gb:.1f} GB does NOT fit 384 GB machine")
42
+
43
+ # every tensor resolvable in its shard
44
+ open_cache = {}
45
+ def keys_of(sh):
46
+ if sh not in open_cache:
47
+ open_cache[sh] = set(safe_open(f"{OUT}/{sh}", framework="pt").keys())
48
+ return open_cache[sh]
49
+ bad = [n for n, sh in wm.items() if n not in keys_of(sh)]
50
+ if bad:
51
+ ok = False; print(f" !! {len(bad)} tensors not found in their shard, e.g. {bad[:3]}")
52
+ else:
53
+ print(f" ok: all {len(wm)} tensors resolvable in their shards")
54
+
55
+ # readback: hot slabs, per-book cold codes, hot_ids from a packed layer
56
+ packed = sorted(B.packed_layers()); hot = B.hot_map()
57
+ L = packed[0]
58
+ nv = ic.ShardReader(OUT) # reads assembled output via its index
59
+ checks = {
60
+ f"model.llm.layers.{L}.mlp.experts.w13_hot_weight": ("uint8", 3),
61
+ f"model.llm.layers.{L}.mlp.experts.w13_cold_codes.0": ("int16", 3), # w13 book0 @65536
62
+ f"model.llm.layers.{L}.mlp.experts.w2_cold_codes.0": ("int16", 3), # w2 book0 @65536
63
+ f"model.llm.layers.{L}.mlp.experts.w2_cold_codes.1": ("uint8", 3), # w2 book1 @256 -> uint8
64
+ f"model.llm.layers.{L}.mlp.experts.hot_ids": ("int32", 1),
65
+ f"model.llm.layers.{L}.mlp.experts.cold_ids": ("int32", 1),
66
+ }
67
+ for name, (dt, ndim) in checks.items():
68
+ t = nv.get(name)
69
+ print(f" readback {name.split('.mlp.')[-1]}: {t.dtype} {tuple(t.shape)}")
70
+ if str(t.dtype).split(".")[-1] != dt or t.dim() != ndim:
71
+ ok = False; print(f" !! unexpected dtype/ndim (want {dt} ndim {ndim})")
72
+ # confirm uint8 book1 values are within 0..255 (lossless) and codebook has <=256 rows
73
+ c1 = nv.get(f"model.llm.layers.{L}.mlp.experts.w2_cold_codes.1")
74
+ cb1 = nv.get(f"model.llm.layers.{L}.mlp.experts.w2_cold_codebook.1")
75
+ if int(c1.max()) > cb1.shape[0] - 1:
76
+ ok = False; print(f" !! w2 book1 code {int(c1.max())} out of range for {cb1.shape[0]} entries")
77
+ else:
78
+ print(f" ok: w2 book1 uint8 codes in 0..{int(c1.max())} (codebook {cb1.shape[0]} rows)")
79
+ # hot_ids/cold_ids consistency vs assignment
80
+ hids = nv.get(f"model.llm.layers.{L}.mlp.experts.hot_ids").tolist()
81
+ cids = nv.get(f"model.llm.layers.{L}.mlp.experts.cold_ids").tolist()
82
+ if sorted(hids) != sorted(hot[L]):
83
+ ok = False; print(f" !! hot_ids mismatch layer {L}")
84
+ if sorted(hids + cids) != list(range(ic.N_EXPERTS)):
85
+ ok = False; print(f" !! hot_ids+cold_ids do not partition 0..255")
86
+ else:
87
+ print(f" ok: hot_ids({len(hids)})+cold_ids({len(cids)}) partition 0..255 for layer {L}")
88
+ # hot_weight n matches n_hot
89
+ hw = nv.get(f"model.llm.layers.{L}.mlp.experts.w13_hot_weight")
90
+ if hw.shape[0] != len(hot[L]):
91
+ ok = False; print(f" !! hot_weight expert-dim {hw.shape[0]} != n_hot {len(hot[L])}")
92
+
93
+ # layer 2 is now a hybrid layer: bf16 HOT slabs (no scales), AQLM cold, fused weights dropped
94
+ for proj in ("w13", "w2"):
95
+ hb = f"model.llm.layers.2.mlp.experts.{proj}_hot_bf16"
96
+ if hb not in wm:
97
+ ok = False; print(f" !! layer-2 {proj}_hot_bf16 MISSING")
98
+ else:
99
+ t = nv.get(hb)
100
+ print(f" layer2 {proj}_hot_bf16: {t.dtype} {tuple(t.shape)}")
101
+ if t.dtype != torch.bfloat16 or t.dim() != 3:
102
+ ok = False; print(f" !! layer-2 {proj}_hot_bf16 not bf16/3-D")
103
+ if t.shape[0] != len(hot[2]):
104
+ ok = False; print(f" !! layer-2 {proj}_hot_bf16 expert-dim {t.shape[0]} != n_hot {len(hot[2])}")
105
+ if f"{hb}.scale" in wm or f"{hb}.scale2" in wm:
106
+ ok = False; print(f" !! layer-2 {proj}_hot_bf16 unexpectedly has scale")
107
+ # cold codes present for layer 2
108
+ if f"model.llm.layers.2.mlp.experts.{proj}_cold_codes.0" not in wm:
109
+ ok = False; print(f" !! layer-2 {proj}_cold_codes.0 MISSING")
110
+ # fused uncompressed bf16 must be dropped
111
+ if f"model.llm.layers.2.mlp.experts.{proj}_weight" in wm:
112
+ ok = False; print(f" !! layer-2 {proj}_weight fused bf16 NOT dropped")
113
+ # NO layer (incl. layer 2) may carry fused expert weights in the output
114
+ built = set(hot)
115
+ leaked = [n for n in wm if B.EXP_ANY.match(n) and int(B.EXP_ANY.match(n).group(1)) in built]
116
+ if leaked:
117
+ ok = False; print(f" !! fused expert weights leaked into output: {leaked[:3]}")
118
+ else:
119
+ print(f" ok: no fused expert weights in output")
120
+
121
+ # side files
122
+ for fn in ("config.json", "hf_quant_config.json", "tokenizer.json", "README.md",
123
+ "tokenizer_config.json", "chat_template.jinja", "tiktoken/tokenizer.model"):
124
+ if not os.path.exists(f"{OUT}/{fn}"):
125
+ print(f" ~~ side file missing: {fn}")
126
+ hq = json.load(open(f"{OUT}/hf_quant_config.json"))
127
+ if "aqlm_hybrid" not in hq:
128
+ ok = False; print(" !! hf_quant_config.json missing aqlm_hybrid block")
129
+ cfg = json.load(open(f"{OUT}/config.json"))
130
+ if cfg.get("quantization_config", {}).get("quant_method") != "inkling_nvfp4_aqlm_hybrid":
131
+ ok = False; print(" !! config.json missing quantization_config.quant_method")
132
+
133
+ print(f"\n[VALIDATE] {'PASS' if ok else 'FAIL'}")
134
+ return ok
135
+
136
+ if __name__ == "__main__":
137
+ import sys
138
+ sys.exit(0 if validate() else 1)
config.json ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "InklingForConditionalGeneration"
4
+ ],
5
+ "model_type": "inkling_mm_model",
6
+ "eos_token_id": 200006,
7
+ "text_config": {
8
+ "model_max_length": 1048576,
9
+ "torch_dtype": "bfloat16",
10
+ "hidden_size": 6144,
11
+ "num_hidden_layers": 66,
12
+ "vocab_size": 201024,
13
+ "num_attention_heads": 64,
14
+ "num_key_value_heads": 8,
15
+ "head_dim": 128,
16
+ "d_rel": 16,
17
+ "rel_extent": 1024,
18
+ "q_bias": false,
19
+ "o_bias": false,
20
+ "log_scaling_n_floor": 128000,
21
+ "log_scaling_alpha": 0.1,
22
+ "rms_norm_eps": 1e-06,
23
+ "use_embed_norm": true,
24
+ "local_layer_ids": [
25
+ 0,
26
+ 1,
27
+ 2,
28
+ 3,
29
+ 4,
30
+ 6,
31
+ 7,
32
+ 8,
33
+ 9,
34
+ 10,
35
+ 12,
36
+ 13,
37
+ 14,
38
+ 15,
39
+ 16,
40
+ 18,
41
+ 19,
42
+ 20,
43
+ 21,
44
+ 22,
45
+ 24,
46
+ 25,
47
+ 26,
48
+ 27,
49
+ 28,
50
+ 30,
51
+ 31,
52
+ 32,
53
+ 33,
54
+ 34,
55
+ 36,
56
+ 37,
57
+ 38,
58
+ 39,
59
+ 40,
60
+ 42,
61
+ 43,
62
+ 44,
63
+ 45,
64
+ 46,
65
+ 48,
66
+ 49,
67
+ 50,
68
+ 51,
69
+ 52,
70
+ 54,
71
+ 55,
72
+ 56,
73
+ 57,
74
+ 58,
75
+ 60,
76
+ 61,
77
+ 62,
78
+ 63,
79
+ 64
80
+ ],
81
+ "dense_mlp_idx": 2,
82
+ "use_sconv": true,
83
+ "sconv_kernel_size": 4,
84
+ "unpadded_vocab_size": 200058,
85
+ "logits_mup_width_multiplier": 24.0,
86
+ "final_logit_softcapping": null,
87
+ "swa_head_dim": 128,
88
+ "swa_num_attention_heads": 64,
89
+ "swa_num_key_value_heads": 16,
90
+ "sliding_window_size": 512,
91
+ "n_routed_experts": 256,
92
+ "num_experts_per_tok": 6,
93
+ "n_shared_experts": 2,
94
+ "shared_expert_sink": true,
95
+ "dense_intermediate_size": 24576,
96
+ "intermediate_size": 3072,
97
+ "route_scale": 8.0,
98
+ "use_gate_bias": true,
99
+ "gate_activation": "sigmoid",
100
+ "norm_after_topk": true,
101
+ "use_global_scale": true
102
+ },
103
+ "audio_config": {
104
+ "decoder_dmodel": 6144,
105
+ "n_mel_bins": 80,
106
+ "mel_vocab_size": 16,
107
+ "bias": false,
108
+ "dmel_min_value": -7.0,
109
+ "dmel_max_value": 2.0,
110
+ "use_audio_norm": true,
111
+ "audio_mode": "dmel"
112
+ },
113
+ "vision_config": {
114
+ "vision_encoder_type": "hmlp",
115
+ "decoder_dmodel": 6144,
116
+ "patch_size": 40,
117
+ "temporal_patch_size": 2,
118
+ "n_channels": 3,
119
+ "n_layers": 4,
120
+ "use_vision_norm": true
121
+ },
122
+ "mtp_config": {
123
+ "num_nextn_predict_layers": 8,
124
+ "chain_hidden_post_norm": false,
125
+ "local_layer_ids": [
126
+ 0,
127
+ 2,
128
+ 4,
129
+ 5,
130
+ 6,
131
+ 7
132
+ ]
133
+ },
134
+ "quantization_config": {
135
+ "quant_method": "inkling_nvfp4_aqlm_hybrid",
136
+ "base_nvfp4": "thinkingmachines/Inkling-NVFP4",
137
+ "group_size": 8,
138
+ "note": "Mixed per-expert quant: hot experts ModelOpt-NVFP4, cold experts AQLM-2bit. Full spec in hf_quant_config.json['aqlm_hybrid'] and README.md. Requires a custom fused-MoE kernel; does NOT load on stock vLLM."
139
+ }
140
+ }
hf_quant_config.json ADDED
@@ -0,0 +1,1049 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "quantization": {
3
+ "quant_algo": "NVFP4",
4
+ "kv_cache_quant_algo": "none",
5
+ "exclude_modules": [
6
+ "model.audio.encoder",
7
+ "model.audio.final_norm",
8
+ "model.llm.embed",
9
+ "model.llm.embed_norm",
10
+ "model.llm.norm",
11
+ "model.llm.unembed",
12
+ "model.visual.final_norm",
13
+ "model.visual.layers.linear_0",
14
+ "model.visual.layers.linear_1",
15
+ "model.visual.layers.linear_2",
16
+ "model.visual.layers.linear_3",
17
+ "model.visual.layers.norm_0",
18
+ "model.visual.layers.norm_1",
19
+ "model.visual.layers.norm_2",
20
+ "model.llm.layers.0.attn",
21
+ "model.llm.layers.0.attn_norm",
22
+ "model.llm.layers.0.attn_sconv",
23
+ "model.llm.layers.0.mlp.global_scale",
24
+ "model.llm.layers.0.mlp.w13_dn",
25
+ "model.llm.layers.0.mlp.w2_md",
26
+ "model.llm.layers.0.mlp_norm",
27
+ "model.llm.layers.0.mlp_sconv",
28
+ "model.llm.layers.1.attn",
29
+ "model.llm.layers.1.attn_norm",
30
+ "model.llm.layers.1.attn_sconv",
31
+ "model.llm.layers.1.mlp.global_scale",
32
+ "model.llm.layers.1.mlp.w13_dn",
33
+ "model.llm.layers.1.mlp.w2_md",
34
+ "model.llm.layers.1.mlp_norm",
35
+ "model.llm.layers.1.mlp_sconv",
36
+ "model.llm.layers.10.attn",
37
+ "model.llm.layers.10.attn_norm",
38
+ "model.llm.layers.10.attn_sconv",
39
+ "model.llm.layers.10.mlp.gate",
40
+ "model.llm.layers.10.mlp.shared_experts",
41
+ "model.llm.layers.10.mlp_norm",
42
+ "model.llm.layers.10.mlp_sconv",
43
+ "model.llm.layers.11.attn",
44
+ "model.llm.layers.11.attn_norm",
45
+ "model.llm.layers.11.attn_sconv",
46
+ "model.llm.layers.11.mlp.gate",
47
+ "model.llm.layers.11.mlp.shared_experts",
48
+ "model.llm.layers.11.mlp_norm",
49
+ "model.llm.layers.11.mlp_sconv",
50
+ "model.llm.layers.12.attn",
51
+ "model.llm.layers.12.attn_norm",
52
+ "model.llm.layers.12.attn_sconv",
53
+ "model.llm.layers.12.mlp.gate",
54
+ "model.llm.layers.12.mlp.shared_experts",
55
+ "model.llm.layers.12.mlp_norm",
56
+ "model.llm.layers.12.mlp_sconv",
57
+ "model.llm.layers.13.attn",
58
+ "model.llm.layers.13.attn_norm",
59
+ "model.llm.layers.13.attn_sconv",
60
+ "model.llm.layers.13.mlp.gate",
61
+ "model.llm.layers.13.mlp.shared_experts",
62
+ "model.llm.layers.13.mlp_norm",
63
+ "model.llm.layers.13.mlp_sconv",
64
+ "model.llm.layers.14.attn",
65
+ "model.llm.layers.14.attn_norm",
66
+ "model.llm.layers.14.attn_sconv",
67
+ "model.llm.layers.14.mlp.gate",
68
+ "model.llm.layers.14.mlp.shared_experts",
69
+ "model.llm.layers.14.mlp_norm",
70
+ "model.llm.layers.14.mlp_sconv",
71
+ "model.llm.layers.15.attn",
72
+ "model.llm.layers.15.attn_norm",
73
+ "model.llm.layers.15.attn_sconv",
74
+ "model.llm.layers.15.mlp.gate",
75
+ "model.llm.layers.15.mlp.shared_experts",
76
+ "model.llm.layers.15.mlp_norm",
77
+ "model.llm.layers.15.mlp_sconv",
78
+ "model.llm.layers.16.attn",
79
+ "model.llm.layers.16.attn_norm",
80
+ "model.llm.layers.16.attn_sconv",
81
+ "model.llm.layers.16.mlp.gate",
82
+ "model.llm.layers.16.mlp.shared_experts",
83
+ "model.llm.layers.16.mlp_norm",
84
+ "model.llm.layers.16.mlp_sconv",
85
+ "model.llm.layers.17.attn",
86
+ "model.llm.layers.17.attn_norm",
87
+ "model.llm.layers.17.attn_sconv",
88
+ "model.llm.layers.17.mlp.gate",
89
+ "model.llm.layers.17.mlp.shared_experts",
90
+ "model.llm.layers.17.mlp_norm",
91
+ "model.llm.layers.17.mlp_sconv",
92
+ "model.llm.layers.18.attn",
93
+ "model.llm.layers.18.attn_norm",
94
+ "model.llm.layers.18.attn_sconv",
95
+ "model.llm.layers.18.mlp.gate",
96
+ "model.llm.layers.18.mlp.shared_experts",
97
+ "model.llm.layers.18.mlp_norm",
98
+ "model.llm.layers.18.mlp_sconv",
99
+ "model.llm.layers.19.attn",
100
+ "model.llm.layers.19.attn_norm",
101
+ "model.llm.layers.19.attn_sconv",
102
+ "model.llm.layers.19.mlp.gate",
103
+ "model.llm.layers.19.mlp.shared_experts",
104
+ "model.llm.layers.19.mlp_norm",
105
+ "model.llm.layers.19.mlp_sconv",
106
+ "model.llm.layers.2.attn",
107
+ "model.llm.layers.2.attn_norm",
108
+ "model.llm.layers.2.attn_sconv",
109
+ "model.llm.layers.2.mlp.experts",
110
+ "model.llm.layers.2.mlp.gate",
111
+ "model.llm.layers.2.mlp.shared_experts",
112
+ "model.llm.layers.2.mlp_norm",
113
+ "model.llm.layers.2.mlp_sconv",
114
+ "model.llm.layers.20.attn",
115
+ "model.llm.layers.20.attn_norm",
116
+ "model.llm.layers.20.attn_sconv",
117
+ "model.llm.layers.20.mlp.gate",
118
+ "model.llm.layers.20.mlp.shared_experts",
119
+ "model.llm.layers.20.mlp_norm",
120
+ "model.llm.layers.20.mlp_sconv",
121
+ "model.llm.layers.21.attn",
122
+ "model.llm.layers.21.attn_norm",
123
+ "model.llm.layers.21.attn_sconv",
124
+ "model.llm.layers.21.mlp.gate",
125
+ "model.llm.layers.21.mlp.shared_experts",
126
+ "model.llm.layers.21.mlp_norm",
127
+ "model.llm.layers.21.mlp_sconv",
128
+ "model.llm.layers.22.attn",
129
+ "model.llm.layers.22.attn_norm",
130
+ "model.llm.layers.22.attn_sconv",
131
+ "model.llm.layers.22.mlp.gate",
132
+ "model.llm.layers.22.mlp.shared_experts",
133
+ "model.llm.layers.22.mlp_norm",
134
+ "model.llm.layers.22.mlp_sconv",
135
+ "model.llm.layers.23.attn",
136
+ "model.llm.layers.23.attn_norm",
137
+ "model.llm.layers.23.attn_sconv",
138
+ "model.llm.layers.23.mlp.gate",
139
+ "model.llm.layers.23.mlp.shared_experts",
140
+ "model.llm.layers.23.mlp_norm",
141
+ "model.llm.layers.23.mlp_sconv",
142
+ "model.llm.layers.24.attn",
143
+ "model.llm.layers.24.attn_norm",
144
+ "model.llm.layers.24.attn_sconv",
145
+ "model.llm.layers.24.mlp.gate",
146
+ "model.llm.layers.24.mlp.shared_experts",
147
+ "model.llm.layers.24.mlp_norm",
148
+ "model.llm.layers.24.mlp_sconv",
149
+ "model.llm.layers.25.attn",
150
+ "model.llm.layers.25.attn_norm",
151
+ "model.llm.layers.25.attn_sconv",
152
+ "model.llm.layers.25.mlp.gate",
153
+ "model.llm.layers.25.mlp.shared_experts",
154
+ "model.llm.layers.25.mlp_norm",
155
+ "model.llm.layers.25.mlp_sconv",
156
+ "model.llm.layers.26.attn",
157
+ "model.llm.layers.26.attn_norm",
158
+ "model.llm.layers.26.attn_sconv",
159
+ "model.llm.layers.26.mlp.gate",
160
+ "model.llm.layers.26.mlp.shared_experts",
161
+ "model.llm.layers.26.mlp_norm",
162
+ "model.llm.layers.26.mlp_sconv",
163
+ "model.llm.layers.27.attn",
164
+ "model.llm.layers.27.attn_norm",
165
+ "model.llm.layers.27.attn_sconv",
166
+ "model.llm.layers.27.mlp.gate",
167
+ "model.llm.layers.27.mlp.shared_experts",
168
+ "model.llm.layers.27.mlp_norm",
169
+ "model.llm.layers.27.mlp_sconv",
170
+ "model.llm.layers.28.attn",
171
+ "model.llm.layers.28.attn_norm",
172
+ "model.llm.layers.28.attn_sconv",
173
+ "model.llm.layers.28.mlp.gate",
174
+ "model.llm.layers.28.mlp.shared_experts",
175
+ "model.llm.layers.28.mlp_norm",
176
+ "model.llm.layers.28.mlp_sconv",
177
+ "model.llm.layers.29.attn",
178
+ "model.llm.layers.29.attn_norm",
179
+ "model.llm.layers.29.attn_sconv",
180
+ "model.llm.layers.29.mlp.gate",
181
+ "model.llm.layers.29.mlp.shared_experts",
182
+ "model.llm.layers.29.mlp_norm",
183
+ "model.llm.layers.29.mlp_sconv",
184
+ "model.llm.layers.3.attn",
185
+ "model.llm.layers.3.attn_norm",
186
+ "model.llm.layers.3.attn_sconv",
187
+ "model.llm.layers.3.mlp.gate",
188
+ "model.llm.layers.3.mlp.shared_experts",
189
+ "model.llm.layers.3.mlp_norm",
190
+ "model.llm.layers.3.mlp_sconv",
191
+ "model.llm.layers.30.attn",
192
+ "model.llm.layers.30.attn_norm",
193
+ "model.llm.layers.30.attn_sconv",
194
+ "model.llm.layers.30.mlp.gate",
195
+ "model.llm.layers.30.mlp.shared_experts",
196
+ "model.llm.layers.30.mlp_norm",
197
+ "model.llm.layers.30.mlp_sconv",
198
+ "model.llm.layers.31.attn",
199
+ "model.llm.layers.31.attn_norm",
200
+ "model.llm.layers.31.attn_sconv",
201
+ "model.llm.layers.31.mlp.gate",
202
+ "model.llm.layers.31.mlp.shared_experts",
203
+ "model.llm.layers.31.mlp_norm",
204
+ "model.llm.layers.31.mlp_sconv",
205
+ "model.llm.layers.32.attn",
206
+ "model.llm.layers.32.attn_norm",
207
+ "model.llm.layers.32.attn_sconv",
208
+ "model.llm.layers.32.mlp.gate",
209
+ "model.llm.layers.32.mlp.shared_experts",
210
+ "model.llm.layers.32.mlp_norm",
211
+ "model.llm.layers.32.mlp_sconv",
212
+ "model.llm.layers.33.attn",
213
+ "model.llm.layers.33.attn_norm",
214
+ "model.llm.layers.33.attn_sconv",
215
+ "model.llm.layers.33.mlp.gate",
216
+ "model.llm.layers.33.mlp.shared_experts",
217
+ "model.llm.layers.33.mlp_norm",
218
+ "model.llm.layers.33.mlp_sconv",
219
+ "model.llm.layers.34.attn",
220
+ "model.llm.layers.34.attn_norm",
221
+ "model.llm.layers.34.attn_sconv",
222
+ "model.llm.layers.34.mlp.gate",
223
+ "model.llm.layers.34.mlp.shared_experts",
224
+ "model.llm.layers.34.mlp_norm",
225
+ "model.llm.layers.34.mlp_sconv",
226
+ "model.llm.layers.35.attn",
227
+ "model.llm.layers.35.attn_norm",
228
+ "model.llm.layers.35.attn_sconv",
229
+ "model.llm.layers.35.mlp.gate",
230
+ "model.llm.layers.35.mlp.shared_experts",
231
+ "model.llm.layers.35.mlp_norm",
232
+ "model.llm.layers.35.mlp_sconv",
233
+ "model.llm.layers.36.attn",
234
+ "model.llm.layers.36.attn_norm",
235
+ "model.llm.layers.36.attn_sconv",
236
+ "model.llm.layers.36.mlp.gate",
237
+ "model.llm.layers.36.mlp.shared_experts",
238
+ "model.llm.layers.36.mlp_norm",
239
+ "model.llm.layers.36.mlp_sconv",
240
+ "model.llm.layers.37.attn",
241
+ "model.llm.layers.37.attn_norm",
242
+ "model.llm.layers.37.attn_sconv",
243
+ "model.llm.layers.37.mlp.gate",
244
+ "model.llm.layers.37.mlp.shared_experts",
245
+ "model.llm.layers.37.mlp_norm",
246
+ "model.llm.layers.37.mlp_sconv",
247
+ "model.llm.layers.38.attn",
248
+ "model.llm.layers.38.attn_norm",
249
+ "model.llm.layers.38.attn_sconv",
250
+ "model.llm.layers.38.mlp.gate",
251
+ "model.llm.layers.38.mlp.shared_experts",
252
+ "model.llm.layers.38.mlp_norm",
253
+ "model.llm.layers.38.mlp_sconv",
254
+ "model.llm.layers.39.attn",
255
+ "model.llm.layers.39.attn_norm",
256
+ "model.llm.layers.39.attn_sconv",
257
+ "model.llm.layers.39.mlp.gate",
258
+ "model.llm.layers.39.mlp.shared_experts",
259
+ "model.llm.layers.39.mlp_norm",
260
+ "model.llm.layers.39.mlp_sconv",
261
+ "model.llm.layers.4.attn",
262
+ "model.llm.layers.4.attn_norm",
263
+ "model.llm.layers.4.attn_sconv",
264
+ "model.llm.layers.4.mlp.gate",
265
+ "model.llm.layers.4.mlp.shared_experts",
266
+ "model.llm.layers.4.mlp_norm",
267
+ "model.llm.layers.4.mlp_sconv",
268
+ "model.llm.layers.40.attn",
269
+ "model.llm.layers.40.attn_norm",
270
+ "model.llm.layers.40.attn_sconv",
271
+ "model.llm.layers.40.mlp.gate",
272
+ "model.llm.layers.40.mlp.shared_experts",
273
+ "model.llm.layers.40.mlp_norm",
274
+ "model.llm.layers.40.mlp_sconv",
275
+ "model.llm.layers.41.attn",
276
+ "model.llm.layers.41.attn_norm",
277
+ "model.llm.layers.41.attn_sconv",
278
+ "model.llm.layers.41.mlp.gate",
279
+ "model.llm.layers.41.mlp.shared_experts",
280
+ "model.llm.layers.41.mlp_norm",
281
+ "model.llm.layers.41.mlp_sconv",
282
+ "model.llm.layers.42.attn",
283
+ "model.llm.layers.42.attn_norm",
284
+ "model.llm.layers.42.attn_sconv",
285
+ "model.llm.layers.42.mlp.gate",
286
+ "model.llm.layers.42.mlp.shared_experts",
287
+ "model.llm.layers.42.mlp_norm",
288
+ "model.llm.layers.42.mlp_sconv",
289
+ "model.llm.layers.43.attn",
290
+ "model.llm.layers.43.attn_norm",
291
+ "model.llm.layers.43.attn_sconv",
292
+ "model.llm.layers.43.mlp.gate",
293
+ "model.llm.layers.43.mlp.shared_experts",
294
+ "model.llm.layers.43.mlp_norm",
295
+ "model.llm.layers.43.mlp_sconv",
296
+ "model.llm.layers.44.attn",
297
+ "model.llm.layers.44.attn_norm",
298
+ "model.llm.layers.44.attn_sconv",
299
+ "model.llm.layers.44.mlp.gate",
300
+ "model.llm.layers.44.mlp.shared_experts",
301
+ "model.llm.layers.44.mlp_norm",
302
+ "model.llm.layers.44.mlp_sconv",
303
+ "model.llm.layers.45.attn",
304
+ "model.llm.layers.45.attn_norm",
305
+ "model.llm.layers.45.attn_sconv",
306
+ "model.llm.layers.45.mlp.gate",
307
+ "model.llm.layers.45.mlp.shared_experts",
308
+ "model.llm.layers.45.mlp_norm",
309
+ "model.llm.layers.45.mlp_sconv",
310
+ "model.llm.layers.46.attn",
311
+ "model.llm.layers.46.attn_norm",
312
+ "model.llm.layers.46.attn_sconv",
313
+ "model.llm.layers.46.mlp.gate",
314
+ "model.llm.layers.46.mlp.shared_experts",
315
+ "model.llm.layers.46.mlp_norm",
316
+ "model.llm.layers.46.mlp_sconv",
317
+ "model.llm.layers.47.attn",
318
+ "model.llm.layers.47.attn_norm",
319
+ "model.llm.layers.47.attn_sconv",
320
+ "model.llm.layers.47.mlp.gate",
321
+ "model.llm.layers.47.mlp.shared_experts",
322
+ "model.llm.layers.47.mlp_norm",
323
+ "model.llm.layers.47.mlp_sconv",
324
+ "model.llm.layers.48.attn",
325
+ "model.llm.layers.48.attn_norm",
326
+ "model.llm.layers.48.attn_sconv",
327
+ "model.llm.layers.48.mlp.gate",
328
+ "model.llm.layers.48.mlp.shared_experts",
329
+ "model.llm.layers.48.mlp_norm",
330
+ "model.llm.layers.48.mlp_sconv",
331
+ "model.llm.layers.49.attn",
332
+ "model.llm.layers.49.attn_norm",
333
+ "model.llm.layers.49.attn_sconv",
334
+ "model.llm.layers.49.mlp.gate",
335
+ "model.llm.layers.49.mlp.shared_experts",
336
+ "model.llm.layers.49.mlp_norm",
337
+ "model.llm.layers.49.mlp_sconv",
338
+ "model.llm.layers.5.attn",
339
+ "model.llm.layers.5.attn_norm",
340
+ "model.llm.layers.5.attn_sconv",
341
+ "model.llm.layers.5.mlp.gate",
342
+ "model.llm.layers.5.mlp.shared_experts",
343
+ "model.llm.layers.5.mlp_norm",
344
+ "model.llm.layers.5.mlp_sconv",
345
+ "model.llm.layers.50.attn",
346
+ "model.llm.layers.50.attn_norm",
347
+ "model.llm.layers.50.attn_sconv",
348
+ "model.llm.layers.50.mlp.gate",
349
+ "model.llm.layers.50.mlp.shared_experts",
350
+ "model.llm.layers.50.mlp_norm",
351
+ "model.llm.layers.50.mlp_sconv",
352
+ "model.llm.layers.51.attn",
353
+ "model.llm.layers.51.attn_norm",
354
+ "model.llm.layers.51.attn_sconv",
355
+ "model.llm.layers.51.mlp.gate",
356
+ "model.llm.layers.51.mlp.shared_experts",
357
+ "model.llm.layers.51.mlp_norm",
358
+ "model.llm.layers.51.mlp_sconv",
359
+ "model.llm.layers.52.attn",
360
+ "model.llm.layers.52.attn_norm",
361
+ "model.llm.layers.52.attn_sconv",
362
+ "model.llm.layers.52.mlp.gate",
363
+ "model.llm.layers.52.mlp.shared_experts",
364
+ "model.llm.layers.52.mlp_norm",
365
+ "model.llm.layers.52.mlp_sconv",
366
+ "model.llm.layers.53.attn",
367
+ "model.llm.layers.53.attn_norm",
368
+ "model.llm.layers.53.attn_sconv",
369
+ "model.llm.layers.53.mlp.gate",
370
+ "model.llm.layers.53.mlp.shared_experts",
371
+ "model.llm.layers.53.mlp_norm",
372
+ "model.llm.layers.53.mlp_sconv",
373
+ "model.llm.layers.54.attn",
374
+ "model.llm.layers.54.attn_norm",
375
+ "model.llm.layers.54.attn_sconv",
376
+ "model.llm.layers.54.mlp.gate",
377
+ "model.llm.layers.54.mlp.shared_experts",
378
+ "model.llm.layers.54.mlp_norm",
379
+ "model.llm.layers.54.mlp_sconv",
380
+ "model.llm.layers.55.attn",
381
+ "model.llm.layers.55.attn_norm",
382
+ "model.llm.layers.55.attn_sconv",
383
+ "model.llm.layers.55.mlp.gate",
384
+ "model.llm.layers.55.mlp.shared_experts",
385
+ "model.llm.layers.55.mlp_norm",
386
+ "model.llm.layers.55.mlp_sconv",
387
+ "model.llm.layers.56.attn",
388
+ "model.llm.layers.56.attn_norm",
389
+ "model.llm.layers.56.attn_sconv",
390
+ "model.llm.layers.56.mlp.gate",
391
+ "model.llm.layers.56.mlp.shared_experts",
392
+ "model.llm.layers.56.mlp_norm",
393
+ "model.llm.layers.56.mlp_sconv",
394
+ "model.llm.layers.57.attn",
395
+ "model.llm.layers.57.attn_norm",
396
+ "model.llm.layers.57.attn_sconv",
397
+ "model.llm.layers.57.mlp.gate",
398
+ "model.llm.layers.57.mlp.shared_experts",
399
+ "model.llm.layers.57.mlp_norm",
400
+ "model.llm.layers.57.mlp_sconv",
401
+ "model.llm.layers.58.attn",
402
+ "model.llm.layers.58.attn_norm",
403
+ "model.llm.layers.58.attn_sconv",
404
+ "model.llm.layers.58.mlp.gate",
405
+ "model.llm.layers.58.mlp.shared_experts",
406
+ "model.llm.layers.58.mlp_norm",
407
+ "model.llm.layers.58.mlp_sconv",
408
+ "model.llm.layers.59.attn",
409
+ "model.llm.layers.59.attn_norm",
410
+ "model.llm.layers.59.attn_sconv",
411
+ "model.llm.layers.59.mlp.gate",
412
+ "model.llm.layers.59.mlp.shared_experts",
413
+ "model.llm.layers.59.mlp_norm",
414
+ "model.llm.layers.59.mlp_sconv",
415
+ "model.llm.layers.6.attn",
416
+ "model.llm.layers.6.attn_norm",
417
+ "model.llm.layers.6.attn_sconv",
418
+ "model.llm.layers.6.mlp.gate",
419
+ "model.llm.layers.6.mlp.shared_experts",
420
+ "model.llm.layers.6.mlp_norm",
421
+ "model.llm.layers.6.mlp_sconv",
422
+ "model.llm.layers.60.attn",
423
+ "model.llm.layers.60.attn_norm",
424
+ "model.llm.layers.60.attn_sconv",
425
+ "model.llm.layers.60.mlp.gate",
426
+ "model.llm.layers.60.mlp.shared_experts",
427
+ "model.llm.layers.60.mlp_norm",
428
+ "model.llm.layers.60.mlp_sconv",
429
+ "model.llm.layers.61.attn",
430
+ "model.llm.layers.61.attn_norm",
431
+ "model.llm.layers.61.attn_sconv",
432
+ "model.llm.layers.61.mlp.gate",
433
+ "model.llm.layers.61.mlp.shared_experts",
434
+ "model.llm.layers.61.mlp_norm",
435
+ "model.llm.layers.61.mlp_sconv",
436
+ "model.llm.layers.62.attn",
437
+ "model.llm.layers.62.attn_norm",
438
+ "model.llm.layers.62.attn_sconv",
439
+ "model.llm.layers.62.mlp.gate",
440
+ "model.llm.layers.62.mlp.shared_experts",
441
+ "model.llm.layers.62.mlp_norm",
442
+ "model.llm.layers.62.mlp_sconv",
443
+ "model.llm.layers.63.attn",
444
+ "model.llm.layers.63.attn_norm",
445
+ "model.llm.layers.63.attn_sconv",
446
+ "model.llm.layers.63.mlp.gate",
447
+ "model.llm.layers.63.mlp.shared_experts",
448
+ "model.llm.layers.63.mlp_norm",
449
+ "model.llm.layers.63.mlp_sconv",
450
+ "model.llm.layers.64.attn",
451
+ "model.llm.layers.64.attn_norm",
452
+ "model.llm.layers.64.attn_sconv",
453
+ "model.llm.layers.64.mlp.gate",
454
+ "model.llm.layers.64.mlp.shared_experts",
455
+ "model.llm.layers.64.mlp_norm",
456
+ "model.llm.layers.64.mlp_sconv",
457
+ "model.llm.layers.65.attn",
458
+ "model.llm.layers.65.attn_norm",
459
+ "model.llm.layers.65.attn_sconv",
460
+ "model.llm.layers.65.mlp.gate",
461
+ "model.llm.layers.65.mlp.shared_experts",
462
+ "model.llm.layers.65.mlp_norm",
463
+ "model.llm.layers.65.mlp_sconv",
464
+ "model.llm.layers.7.attn",
465
+ "model.llm.layers.7.attn_norm",
466
+ "model.llm.layers.7.attn_sconv",
467
+ "model.llm.layers.7.mlp.gate",
468
+ "model.llm.layers.7.mlp.shared_experts",
469
+ "model.llm.layers.7.mlp_norm",
470
+ "model.llm.layers.7.mlp_sconv",
471
+ "model.llm.layers.8.attn",
472
+ "model.llm.layers.8.attn_norm",
473
+ "model.llm.layers.8.attn_sconv",
474
+ "model.llm.layers.8.mlp.gate",
475
+ "model.llm.layers.8.mlp.shared_experts",
476
+ "model.llm.layers.8.mlp_norm",
477
+ "model.llm.layers.8.mlp_sconv",
478
+ "model.llm.layers.9.attn",
479
+ "model.llm.layers.9.attn_norm",
480
+ "model.llm.layers.9.attn_sconv",
481
+ "model.llm.layers.9.mlp.gate",
482
+ "model.llm.layers.9.mlp.shared_experts",
483
+ "model.llm.layers.9.mlp_norm",
484
+ "model.llm.layers.9.mlp_sconv"
485
+ ],
486
+ "modelopt_quant_config": {
487
+ "quant_cfg": {
488
+ "*weight_quantizer": {
489
+ "num_bits": [
490
+ 2,
491
+ 1
492
+ ],
493
+ "block_sizes": {
494
+ "-1": 16,
495
+ "type": "dynamic",
496
+ "scale_bits": [
497
+ 4,
498
+ 3
499
+ ]
500
+ },
501
+ "axis": null,
502
+ "enable": true
503
+ },
504
+ "*input_quantizer": {
505
+ "num_bits": [
506
+ 2,
507
+ 1
508
+ ],
509
+ "block_sizes": {
510
+ "-1": 16,
511
+ "type": "dynamic",
512
+ "scale_bits": [
513
+ 4,
514
+ 3
515
+ ]
516
+ },
517
+ "axis": null,
518
+ "enable": true
519
+ },
520
+ "nn.BatchNorm1d": {
521
+ "*": {
522
+ "enable": false
523
+ }
524
+ },
525
+ "nn.BatchNorm2d": {
526
+ "*": {
527
+ "enable": false
528
+ }
529
+ },
530
+ "nn.BatchNorm3d": {
531
+ "*": {
532
+ "enable": false
533
+ }
534
+ },
535
+ "nn.LeakyReLU": {
536
+ "*": {
537
+ "enable": false
538
+ }
539
+ },
540
+ "*lm_head*": {
541
+ "enable": false
542
+ },
543
+ "*proj_out.*": {
544
+ "enable": false
545
+ },
546
+ "*block_sparse_moe.gate*": {
547
+ "enable": false
548
+ },
549
+ "*router*": {
550
+ "enable": false
551
+ },
552
+ "*mlp.gate.*": {
553
+ "enable": false
554
+ },
555
+ "*mlp.shared_expert_gate.*": {
556
+ "enable": false
557
+ },
558
+ "*linear_attn.conv1d*": {
559
+ "enable": false
560
+ },
561
+ "*mixer.conv1d*": {
562
+ "enable": false
563
+ },
564
+ "*output_layer*": {
565
+ "enable": false
566
+ },
567
+ "output.*": {
568
+ "enable": false
569
+ },
570
+ "default": {
571
+ "enable": false
572
+ }
573
+ },
574
+ "algorithm": "max"
575
+ },
576
+ "group_size": 16
577
+ },
578
+ "aqlm_hybrid": {
579
+ "quant_method": "inkling_nvfp4_aqlm_hybrid",
580
+ "base_nvfp4": "thinkingmachines/Inkling-NVFP4",
581
+ "group_size": 8,
582
+ "w13_book_entries": [
583
+ 65536
584
+ ],
585
+ "w2_book_entries": [
586
+ 65536,
587
+ 256
588
+ ],
589
+ "w13_code_dtypes": [
590
+ "int16"
591
+ ],
592
+ "w2_code_dtypes": [
593
+ "int16",
594
+ "uint8"
595
+ ],
596
+ "cold_code_layout": "per-book tensors {proj}_cold_codes.{b}, dtype uint8 if book<=256 entries else int16",
597
+ "aqlm_layer_books": {
598
+ "2": {
599
+ "n_hot": 4,
600
+ "n_nvfp4": 4,
601
+ "n_cold": 252,
602
+ "packed": false,
603
+ "hot_format": "bf16"
604
+ },
605
+ "3": {
606
+ "n_hot": 4,
607
+ "n_nvfp4": 4,
608
+ "n_cold": 252,
609
+ "packed": true,
610
+ "hot_format": "nvfp4"
611
+ },
612
+ "4": {
613
+ "n_hot": 4,
614
+ "n_nvfp4": 4,
615
+ "n_cold": 252,
616
+ "packed": true,
617
+ "hot_format": "nvfp4"
618
+ },
619
+ "5": {
620
+ "n_hot": 4,
621
+ "n_nvfp4": 4,
622
+ "n_cold": 252,
623
+ "packed": true,
624
+ "hot_format": "nvfp4"
625
+ },
626
+ "6": {
627
+ "n_hot": 4,
628
+ "n_nvfp4": 4,
629
+ "n_cold": 252,
630
+ "packed": true,
631
+ "hot_format": "nvfp4"
632
+ },
633
+ "7": {
634
+ "n_hot": 4,
635
+ "n_nvfp4": 4,
636
+ "n_cold": 252,
637
+ "packed": true,
638
+ "hot_format": "nvfp4"
639
+ },
640
+ "8": {
641
+ "n_hot": 4,
642
+ "n_nvfp4": 4,
643
+ "n_cold": 252,
644
+ "packed": true,
645
+ "hot_format": "nvfp4"
646
+ },
647
+ "9": {
648
+ "n_hot": 4,
649
+ "n_nvfp4": 4,
650
+ "n_cold": 252,
651
+ "packed": true,
652
+ "hot_format": "nvfp4"
653
+ },
654
+ "10": {
655
+ "n_hot": 4,
656
+ "n_nvfp4": 4,
657
+ "n_cold": 252,
658
+ "packed": true,
659
+ "hot_format": "nvfp4"
660
+ },
661
+ "11": {
662
+ "n_hot": 4,
663
+ "n_nvfp4": 4,
664
+ "n_cold": 252,
665
+ "packed": true,
666
+ "hot_format": "nvfp4"
667
+ },
668
+ "12": {
669
+ "n_hot": 4,
670
+ "n_nvfp4": 4,
671
+ "n_cold": 252,
672
+ "packed": true,
673
+ "hot_format": "nvfp4"
674
+ },
675
+ "13": {
676
+ "n_hot": 4,
677
+ "n_nvfp4": 4,
678
+ "n_cold": 252,
679
+ "packed": true,
680
+ "hot_format": "nvfp4"
681
+ },
682
+ "14": {
683
+ "n_hot": 4,
684
+ "n_nvfp4": 4,
685
+ "n_cold": 252,
686
+ "packed": true,
687
+ "hot_format": "nvfp4"
688
+ },
689
+ "15": {
690
+ "n_hot": 4,
691
+ "n_nvfp4": 4,
692
+ "n_cold": 252,
693
+ "packed": true,
694
+ "hot_format": "nvfp4"
695
+ },
696
+ "16": {
697
+ "n_hot": 4,
698
+ "n_nvfp4": 4,
699
+ "n_cold": 252,
700
+ "packed": true,
701
+ "hot_format": "nvfp4"
702
+ },
703
+ "17": {
704
+ "n_hot": 4,
705
+ "n_nvfp4": 4,
706
+ "n_cold": 252,
707
+ "packed": true,
708
+ "hot_format": "nvfp4"
709
+ },
710
+ "18": {
711
+ "n_hot": 4,
712
+ "n_nvfp4": 4,
713
+ "n_cold": 252,
714
+ "packed": true,
715
+ "hot_format": "nvfp4"
716
+ },
717
+ "19": {
718
+ "n_hot": 4,
719
+ "n_nvfp4": 4,
720
+ "n_cold": 252,
721
+ "packed": true,
722
+ "hot_format": "nvfp4"
723
+ },
724
+ "20": {
725
+ "n_hot": 4,
726
+ "n_nvfp4": 4,
727
+ "n_cold": 252,
728
+ "packed": true,
729
+ "hot_format": "nvfp4"
730
+ },
731
+ "21": {
732
+ "n_hot": 4,
733
+ "n_nvfp4": 4,
734
+ "n_cold": 252,
735
+ "packed": true,
736
+ "hot_format": "nvfp4"
737
+ },
738
+ "22": {
739
+ "n_hot": 4,
740
+ "n_nvfp4": 4,
741
+ "n_cold": 252,
742
+ "packed": true,
743
+ "hot_format": "nvfp4"
744
+ },
745
+ "23": {
746
+ "n_hot": 4,
747
+ "n_nvfp4": 4,
748
+ "n_cold": 252,
749
+ "packed": true,
750
+ "hot_format": "nvfp4"
751
+ },
752
+ "24": {
753
+ "n_hot": 4,
754
+ "n_nvfp4": 4,
755
+ "n_cold": 252,
756
+ "packed": true,
757
+ "hot_format": "nvfp4"
758
+ },
759
+ "25": {
760
+ "n_hot": 4,
761
+ "n_nvfp4": 4,
762
+ "n_cold": 252,
763
+ "packed": true,
764
+ "hot_format": "nvfp4"
765
+ },
766
+ "26": {
767
+ "n_hot": 4,
768
+ "n_nvfp4": 4,
769
+ "n_cold": 252,
770
+ "packed": true,
771
+ "hot_format": "nvfp4"
772
+ },
773
+ "27": {
774
+ "n_hot": 4,
775
+ "n_nvfp4": 4,
776
+ "n_cold": 252,
777
+ "packed": true,
778
+ "hot_format": "nvfp4"
779
+ },
780
+ "28": {
781
+ "n_hot": 4,
782
+ "n_nvfp4": 4,
783
+ "n_cold": 252,
784
+ "packed": true,
785
+ "hot_format": "nvfp4"
786
+ },
787
+ "29": {
788
+ "n_hot": 8,
789
+ "n_nvfp4": 8,
790
+ "n_cold": 248,
791
+ "packed": true,
792
+ "hot_format": "nvfp4"
793
+ },
794
+ "30": {
795
+ "n_hot": 10,
796
+ "n_nvfp4": 10,
797
+ "n_cold": 246,
798
+ "packed": true,
799
+ "hot_format": "nvfp4"
800
+ },
801
+ "31": {
802
+ "n_hot": 11,
803
+ "n_nvfp4": 11,
804
+ "n_cold": 245,
805
+ "packed": true,
806
+ "hot_format": "nvfp4"
807
+ },
808
+ "32": {
809
+ "n_hot": 18,
810
+ "n_nvfp4": 18,
811
+ "n_cold": 238,
812
+ "packed": true,
813
+ "hot_format": "nvfp4"
814
+ },
815
+ "33": {
816
+ "n_hot": 21,
817
+ "n_nvfp4": 21,
818
+ "n_cold": 235,
819
+ "packed": true,
820
+ "hot_format": "nvfp4"
821
+ },
822
+ "34": {
823
+ "n_hot": 34,
824
+ "n_nvfp4": 34,
825
+ "n_cold": 222,
826
+ "packed": true,
827
+ "hot_format": "nvfp4"
828
+ },
829
+ "35": {
830
+ "n_hot": 23,
831
+ "n_nvfp4": 23,
832
+ "n_cold": 233,
833
+ "packed": true,
834
+ "hot_format": "nvfp4"
835
+ },
836
+ "36": {
837
+ "n_hot": 27,
838
+ "n_nvfp4": 27,
839
+ "n_cold": 229,
840
+ "packed": true,
841
+ "hot_format": "nvfp4"
842
+ },
843
+ "37": {
844
+ "n_hot": 26,
845
+ "n_nvfp4": 26,
846
+ "n_cold": 230,
847
+ "packed": true,
848
+ "hot_format": "nvfp4"
849
+ },
850
+ "38": {
851
+ "n_hot": 30,
852
+ "n_nvfp4": 30,
853
+ "n_cold": 226,
854
+ "packed": true,
855
+ "hot_format": "nvfp4"
856
+ },
857
+ "39": {
858
+ "n_hot": 37,
859
+ "n_nvfp4": 37,
860
+ "n_cold": 219,
861
+ "packed": true,
862
+ "hot_format": "nvfp4"
863
+ },
864
+ "40": {
865
+ "n_hot": 45,
866
+ "n_nvfp4": 45,
867
+ "n_cold": 211,
868
+ "packed": true,
869
+ "hot_format": "nvfp4"
870
+ },
871
+ "41": {
872
+ "n_hot": 39,
873
+ "n_nvfp4": 39,
874
+ "n_cold": 217,
875
+ "packed": true,
876
+ "hot_format": "nvfp4"
877
+ },
878
+ "42": {
879
+ "n_hot": 35,
880
+ "n_nvfp4": 35,
881
+ "n_cold": 221,
882
+ "packed": true,
883
+ "hot_format": "nvfp4"
884
+ },
885
+ "43": {
886
+ "n_hot": 39,
887
+ "n_nvfp4": 39,
888
+ "n_cold": 217,
889
+ "packed": true,
890
+ "hot_format": "nvfp4"
891
+ },
892
+ "44": {
893
+ "n_hot": 32,
894
+ "n_nvfp4": 32,
895
+ "n_cold": 224,
896
+ "packed": true,
897
+ "hot_format": "nvfp4"
898
+ },
899
+ "45": {
900
+ "n_hot": 45,
901
+ "n_nvfp4": 45,
902
+ "n_cold": 211,
903
+ "packed": true,
904
+ "hot_format": "nvfp4"
905
+ },
906
+ "46": {
907
+ "n_hot": 50,
908
+ "n_nvfp4": 50,
909
+ "n_cold": 206,
910
+ "packed": true,
911
+ "hot_format": "nvfp4"
912
+ },
913
+ "47": {
914
+ "n_hot": 45,
915
+ "n_nvfp4": 45,
916
+ "n_cold": 211,
917
+ "packed": true,
918
+ "hot_format": "nvfp4"
919
+ },
920
+ "48": {
921
+ "n_hot": 47,
922
+ "n_nvfp4": 47,
923
+ "n_cold": 209,
924
+ "packed": true,
925
+ "hot_format": "nvfp4"
926
+ },
927
+ "49": {
928
+ "n_hot": 55,
929
+ "n_nvfp4": 55,
930
+ "n_cold": 201,
931
+ "packed": true,
932
+ "hot_format": "nvfp4"
933
+ },
934
+ "50": {
935
+ "n_hot": 48,
936
+ "n_nvfp4": 48,
937
+ "n_cold": 208,
938
+ "packed": true,
939
+ "hot_format": "nvfp4"
940
+ },
941
+ "51": {
942
+ "n_hot": 59,
943
+ "n_nvfp4": 59,
944
+ "n_cold": 197,
945
+ "packed": true,
946
+ "hot_format": "nvfp4"
947
+ },
948
+ "52": {
949
+ "n_hot": 79,
950
+ "n_nvfp4": 79,
951
+ "n_cold": 177,
952
+ "packed": true,
953
+ "hot_format": "nvfp4"
954
+ },
955
+ "53": {
956
+ "n_hot": 78,
957
+ "n_nvfp4": 78,
958
+ "n_cold": 178,
959
+ "packed": true,
960
+ "hot_format": "nvfp4"
961
+ },
962
+ "54": {
963
+ "n_hot": 79,
964
+ "n_nvfp4": 79,
965
+ "n_cold": 177,
966
+ "packed": true,
967
+ "hot_format": "nvfp4"
968
+ },
969
+ "55": {
970
+ "n_hot": 84,
971
+ "n_nvfp4": 84,
972
+ "n_cold": 172,
973
+ "packed": true,
974
+ "hot_format": "nvfp4"
975
+ },
976
+ "56": {
977
+ "n_hot": 92,
978
+ "n_nvfp4": 92,
979
+ "n_cold": 164,
980
+ "packed": true,
981
+ "hot_format": "nvfp4"
982
+ },
983
+ "57": {
984
+ "n_hot": 106,
985
+ "n_nvfp4": 106,
986
+ "n_cold": 150,
987
+ "packed": true,
988
+ "hot_format": "nvfp4"
989
+ },
990
+ "58": {
991
+ "n_hot": 118,
992
+ "n_nvfp4": 118,
993
+ "n_cold": 138,
994
+ "packed": true,
995
+ "hot_format": "nvfp4"
996
+ },
997
+ "59": {
998
+ "n_hot": 107,
999
+ "n_nvfp4": 107,
1000
+ "n_cold": 149,
1001
+ "packed": true,
1002
+ "hot_format": "nvfp4"
1003
+ },
1004
+ "60": {
1005
+ "n_hot": 110,
1006
+ "n_nvfp4": 110,
1007
+ "n_cold": 146,
1008
+ "packed": true,
1009
+ "hot_format": "nvfp4"
1010
+ },
1011
+ "61": {
1012
+ "n_hot": 118,
1013
+ "n_nvfp4": 118,
1014
+ "n_cold": 138,
1015
+ "packed": true,
1016
+ "hot_format": "nvfp4"
1017
+ },
1018
+ "62": {
1019
+ "n_hot": 110,
1020
+ "n_nvfp4": 110,
1021
+ "n_cold": 146,
1022
+ "packed": true,
1023
+ "hot_format": "nvfp4"
1024
+ },
1025
+ "63": {
1026
+ "n_hot": 147,
1027
+ "n_nvfp4": 147,
1028
+ "n_cold": 109,
1029
+ "packed": true,
1030
+ "hot_format": "nvfp4"
1031
+ },
1032
+ "64": {
1033
+ "n_hot": 178,
1034
+ "n_nvfp4": 178,
1035
+ "n_cold": 78,
1036
+ "packed": true,
1037
+ "hot_format": "nvfp4"
1038
+ },
1039
+ "65": {
1040
+ "n_hot": 119,
1041
+ "n_nvfp4": 119,
1042
+ "n_cold": 137,
1043
+ "packed": true,
1044
+ "hot_format": "nvfp4"
1045
+ }
1046
+ },
1047
+ "note": "cold experts: AQLM, per-book code tensors {proj}_cold_codes.{b} (w2 book0=int16@65536, book1=uint8@256; w13 book0=int16@65536) + per-book codebooks + per-row scales. hot experts: NVFP4 (hot_format=nvfp4) except layer 2 which has NO NVFP4 base and stores bf16 hot slabs {proj}_hot_bf16 (no scale/scale2, hot_format=bf16). Its cold experts are AQLM like every other layer. hot_ids/cold_ids give the routed-expert index for each slab."
1048
+ }
1049
+ }
model-00022-of-00045.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e317fa6fff7fc7a670fb796045e691026362e6bf204989266f2b26d42568eff7
3
+ size 8578233880
model-00023-of-00045.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a0a8b03eba80f9d802da22f0010092c598eb241b9d1a562bf719bda3fab7963f
3
+ size 8352080344
model-00027-of-00045.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:88a6b29aa4c54147274b122bedc55e2bbbb4d4e3ce0687d4a132d503e60233e6
3
+ size 8490002496
model-00031-of-00045.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d5dfe68d85e7fa49b5e679fe89809436d7e63d31fbb7f7c237e1da594672512d
3
+ size 7576180336
model-00032-of-00045.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:62d0720eab719e7506b650200d60d76971b778e3fba27bf7bb707c4fc704df28
3
+ size 8364945840
model-00041-of-00045.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:28df293809977e0966741702a04939f0b5e8f58b2e8f8505ee4675b7e5b03d67
3
+ size 6977939008
model-00043-of-00045.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7a904ed8c26e9621ea4e2071791a128fd7861058657fc5fac3d0346dfa32176e
3
+ size 7737135388
model.safetensors.index.json ADDED
The diff for this file is too large to render. See raw diff
 
processor_config.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio_token": "<|unused_200053|>",
3
+ "audio_bos_token": "<|content_audio_input|>",
4
+ "dmel_max_value": 2.0,
5
+ "dmel_min_value": -7.0,
6
+ "feature_extractor": {
7
+ "audio_token_duration_s": 0.05,
8
+ "feature_extractor_type": "InklingFeatureExtractor",
9
+ "feature_size": 80,
10
+ "hop_length": 800,
11
+ "n_fft": 1600,
12
+ "padding_side": "right",
13
+ "padding_value": 0.0,
14
+ "return_attention_mask": true,
15
+ "sampling_rate": 16000,
16
+ "window_size": 1600,
17
+ "window_size_multiplier": 2.0
18
+ },
19
+ "image_processor": {
20
+ "do_convert_rgb": true,
21
+ "do_normalize": true,
22
+ "do_rescale": true,
23
+ "do_resize": true,
24
+ "image_mean": [
25
+ 0.48145466,
26
+ 0.4578275,
27
+ 0.40821073
28
+ ],
29
+ "image_processor_type": "InklingImageProcessor",
30
+ "image_std": [
31
+ 0.26862954,
32
+ 0.26130258,
33
+ 0.27577711
34
+ ],
35
+ "resample": 3,
36
+ "rescale_factor": 0.00392156862745098,
37
+ "size": {
38
+ "height": 40,
39
+ "width": 40
40
+ }
41
+ },
42
+ "image_token": "<|unused_200054|>",
43
+ "image_bos_token": "<|content_image|>",
44
+ "num_dmel_bins": 16,
45
+ "processor_class": "InklingProcessor"
46
+ }
special_tokens_map.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "additional_special_tokens": [
3
+ "<|endoftext|>",
4
+ "<|message_user|>",
5
+ "<|message_model|>",
6
+ "<|message_system|>",
7
+ "<|message_tool|>",
8
+ "<|content_text|>",
9
+ "<|content_image|>",
10
+ "<|content_model_end_sampling|>",
11
+ "<|content_thinking|>",
12
+ "<|end_message|>",
13
+ "<|content_audio_input|>",
14
+ "<|content_tool_error|>",
15
+ "<|audio|>",
16
+ "<|content_xml|>",
17
+ "<|begin_of_text|>",
18
+ "<|audio_end|>",
19
+ "<|content_invoke_tool_json|>",
20
+ "<|content_invoke_tool_text|>"
21
+ ]
22
+ }
tiktoken/tokenizer.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bc253fd2b702f7a6da7105eaa8f3463b2f1247e83614f23e5323b921088bed2a
3
+ size 3615874
tokenizer_config.json ADDED
@@ -0,0 +1,508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "199998": {
4
+ "content": "<|unused|>",
5
+ "single_word": false,
6
+ "lstrip": false,
7
+ "rstrip": false,
8
+ "normalized": false,
9
+ "special": true
10
+ },
11
+ "199999": {
12
+ "content": "<|endoftext|>",
13
+ "single_word": false,
14
+ "lstrip": false,
15
+ "rstrip": false,
16
+ "normalized": false,
17
+ "special": true
18
+ },
19
+ "200000": {
20
+ "content": "<|message_user|>",
21
+ "single_word": false,
22
+ "lstrip": false,
23
+ "rstrip": false,
24
+ "normalized": false,
25
+ "special": true
26
+ },
27
+ "200001": {
28
+ "content": "<|message_model|>",
29
+ "single_word": false,
30
+ "lstrip": false,
31
+ "rstrip": false,
32
+ "normalized": false,
33
+ "special": true
34
+ },
35
+ "200002": {
36
+ "content": "<|message_system|>",
37
+ "single_word": false,
38
+ "lstrip": false,
39
+ "rstrip": false,
40
+ "normalized": false,
41
+ "special": true
42
+ },
43
+ "200003": {
44
+ "content": "<|message_tool|>",
45
+ "single_word": false,
46
+ "lstrip": false,
47
+ "rstrip": false,
48
+ "normalized": false,
49
+ "special": true
50
+ },
51
+ "200004": {
52
+ "content": "<|content_text|>",
53
+ "single_word": false,
54
+ "lstrip": false,
55
+ "rstrip": false,
56
+ "normalized": false,
57
+ "special": true
58
+ },
59
+ "200005": {
60
+ "content": "<|content_image|>",
61
+ "single_word": false,
62
+ "lstrip": false,
63
+ "rstrip": false,
64
+ "normalized": false,
65
+ "special": true
66
+ },
67
+ "200006": {
68
+ "content": "<|content_model_end_sampling|>",
69
+ "single_word": false,
70
+ "lstrip": false,
71
+ "rstrip": false,
72
+ "normalized": false,
73
+ "special": true
74
+ },
75
+ "200007": {
76
+ "content": "<|unused_200007|>",
77
+ "single_word": false,
78
+ "lstrip": false,
79
+ "rstrip": false,
80
+ "normalized": false,
81
+ "special": true
82
+ },
83
+ "200008": {
84
+ "content": "<|content_thinking|>",
85
+ "single_word": false,
86
+ "lstrip": false,
87
+ "rstrip": false,
88
+ "normalized": false,
89
+ "special": true
90
+ },
91
+ "200009": {
92
+ "content": "<|unused_200009|>",
93
+ "single_word": false,
94
+ "lstrip": false,
95
+ "rstrip": false,
96
+ "normalized": false,
97
+ "special": true
98
+ },
99
+ "200010": {
100
+ "content": "<|end_message|>",
101
+ "single_word": false,
102
+ "lstrip": false,
103
+ "rstrip": false,
104
+ "normalized": false,
105
+ "special": true
106
+ },
107
+ "200011": {
108
+ "content": "<|unused_200011|>",
109
+ "single_word": false,
110
+ "lstrip": false,
111
+ "rstrip": false,
112
+ "normalized": false,
113
+ "special": true
114
+ },
115
+ "200012": {
116
+ "content": "<|unused_200012|>",
117
+ "single_word": false,
118
+ "lstrip": false,
119
+ "rstrip": false,
120
+ "normalized": false,
121
+ "special": true
122
+ },
123
+ "200013": {
124
+ "content": "<|unused_200013|>",
125
+ "single_word": false,
126
+ "lstrip": false,
127
+ "rstrip": false,
128
+ "normalized": false,
129
+ "special": true
130
+ },
131
+ "200014": {
132
+ "content": "<|unused_200014|>",
133
+ "single_word": false,
134
+ "lstrip": false,
135
+ "rstrip": false,
136
+ "normalized": false,
137
+ "special": true
138
+ },
139
+ "200015": {
140
+ "content": "<|unused_200015|>",
141
+ "single_word": false,
142
+ "lstrip": false,
143
+ "rstrip": false,
144
+ "normalized": false,
145
+ "special": true
146
+ },
147
+ "200016": {
148
+ "content": "<|unused_200016|>",
149
+ "single_word": false,
150
+ "lstrip": false,
151
+ "rstrip": false,
152
+ "normalized": false,
153
+ "special": true
154
+ },
155
+ "200017": {
156
+ "content": "<|unused_200017|>",
157
+ "single_word": false,
158
+ "lstrip": false,
159
+ "rstrip": false,
160
+ "normalized": false,
161
+ "special": true
162
+ },
163
+ "200018": {
164
+ "content": "<|unused_200018|>",
165
+ "single_word": false,
166
+ "lstrip": false,
167
+ "rstrip": false,
168
+ "normalized": false,
169
+ "special": true
170
+ },
171
+ "200019": {
172
+ "content": "<|unused_200019|>",
173
+ "single_word": false,
174
+ "lstrip": false,
175
+ "rstrip": false,
176
+ "normalized": false,
177
+ "special": true
178
+ },
179
+ "200020": {
180
+ "content": "<|content_audio_input|>",
181
+ "single_word": false,
182
+ "lstrip": false,
183
+ "rstrip": false,
184
+ "normalized": false,
185
+ "special": true
186
+ },
187
+ "200021": {
188
+ "content": "<|unused_200021|>",
189
+ "single_word": false,
190
+ "lstrip": false,
191
+ "rstrip": false,
192
+ "normalized": false,
193
+ "special": true
194
+ },
195
+ "200022": {
196
+ "content": "<|content_tool_error|>",
197
+ "single_word": false,
198
+ "lstrip": false,
199
+ "rstrip": false,
200
+ "normalized": false,
201
+ "special": true
202
+ },
203
+ "200023": {
204
+ "content": "<|audio|>",
205
+ "single_word": false,
206
+ "lstrip": false,
207
+ "rstrip": false,
208
+ "normalized": false,
209
+ "special": true
210
+ },
211
+ "200024": {
212
+ "content": "<|content_xml|>",
213
+ "single_word": false,
214
+ "lstrip": false,
215
+ "rstrip": false,
216
+ "normalized": false,
217
+ "special": true
218
+ },
219
+ "200025": {
220
+ "content": "<|unused_200025|>",
221
+ "single_word": false,
222
+ "lstrip": false,
223
+ "rstrip": false,
224
+ "normalized": false,
225
+ "special": true
226
+ },
227
+ "200026": {
228
+ "content": "<|unused_200026|>",
229
+ "single_word": false,
230
+ "lstrip": false,
231
+ "rstrip": false,
232
+ "normalized": false,
233
+ "special": true
234
+ },
235
+ "200027": {
236
+ "content": "<|unused_200027|>",
237
+ "single_word": false,
238
+ "lstrip": false,
239
+ "rstrip": false,
240
+ "normalized": false,
241
+ "special": true
242
+ },
243
+ "200028": {
244
+ "content": "<|begin_of_text|>",
245
+ "single_word": false,
246
+ "lstrip": false,
247
+ "rstrip": false,
248
+ "normalized": false,
249
+ "special": true
250
+ },
251
+ "200029": {
252
+ "content": "<|unused_200029|>",
253
+ "single_word": false,
254
+ "lstrip": false,
255
+ "rstrip": false,
256
+ "normalized": false,
257
+ "special": true
258
+ },
259
+ "200030": {
260
+ "content": "<|unused_200030|>",
261
+ "single_word": false,
262
+ "lstrip": false,
263
+ "rstrip": false,
264
+ "normalized": false,
265
+ "special": true
266
+ },
267
+ "200031": {
268
+ "content": "<|unused_200031|>",
269
+ "single_word": false,
270
+ "lstrip": false,
271
+ "rstrip": false,
272
+ "normalized": false,
273
+ "special": true
274
+ },
275
+ "200032": {
276
+ "content": "<|unused_200032|>",
277
+ "single_word": false,
278
+ "lstrip": false,
279
+ "rstrip": false,
280
+ "normalized": false,
281
+ "special": true
282
+ },
283
+ "200033": {
284
+ "content": "<|unused_200033|>",
285
+ "single_word": false,
286
+ "lstrip": false,
287
+ "rstrip": false,
288
+ "normalized": false,
289
+ "special": true
290
+ },
291
+ "200034": {
292
+ "content": "<|unused_200034|>",
293
+ "single_word": false,
294
+ "lstrip": false,
295
+ "rstrip": false,
296
+ "normalized": false,
297
+ "special": true
298
+ },
299
+ "200035": {
300
+ "content": "<|unused_200035|>",
301
+ "single_word": false,
302
+ "lstrip": false,
303
+ "rstrip": false,
304
+ "normalized": false,
305
+ "special": true
306
+ },
307
+ "200036": {
308
+ "content": "<|unused_200036|>",
309
+ "single_word": false,
310
+ "lstrip": false,
311
+ "rstrip": false,
312
+ "normalized": false,
313
+ "special": true
314
+ },
315
+ "200037": {
316
+ "content": "<|unused_200037|>",
317
+ "single_word": false,
318
+ "lstrip": false,
319
+ "rstrip": false,
320
+ "normalized": false,
321
+ "special": true
322
+ },
323
+ "200038": {
324
+ "content": "<|unused_200038|>",
325
+ "single_word": false,
326
+ "lstrip": false,
327
+ "rstrip": false,
328
+ "normalized": false,
329
+ "special": true
330
+ },
331
+ "200039": {
332
+ "content": "<|unused_200039|>",
333
+ "single_word": false,
334
+ "lstrip": false,
335
+ "rstrip": false,
336
+ "normalized": false,
337
+ "special": true
338
+ },
339
+ "200040": {
340
+ "content": "<|unused_200040|>",
341
+ "single_word": false,
342
+ "lstrip": false,
343
+ "rstrip": false,
344
+ "normalized": false,
345
+ "special": true
346
+ },
347
+ "200041": {
348
+ "content": "<|unused_200041|>",
349
+ "single_word": false,
350
+ "lstrip": false,
351
+ "rstrip": false,
352
+ "normalized": false,
353
+ "special": true
354
+ },
355
+ "200042": {
356
+ "content": "<|unused_200042|>",
357
+ "single_word": false,
358
+ "lstrip": false,
359
+ "rstrip": false,
360
+ "normalized": false,
361
+ "special": true
362
+ },
363
+ "200043": {
364
+ "content": "<|audio_end|>",
365
+ "single_word": false,
366
+ "lstrip": false,
367
+ "rstrip": false,
368
+ "normalized": false,
369
+ "special": true
370
+ },
371
+ "200044": {
372
+ "content": "<|unused_200044|>",
373
+ "single_word": false,
374
+ "lstrip": false,
375
+ "rstrip": false,
376
+ "normalized": false,
377
+ "special": true
378
+ },
379
+ "200045": {
380
+ "content": "<|unused_200045|>",
381
+ "single_word": false,
382
+ "lstrip": false,
383
+ "rstrip": false,
384
+ "normalized": false,
385
+ "special": true
386
+ },
387
+ "200046": {
388
+ "content": "<|unused_200046|>",
389
+ "single_word": false,
390
+ "lstrip": false,
391
+ "rstrip": false,
392
+ "normalized": false,
393
+ "special": true
394
+ },
395
+ "200047": {
396
+ "content": "<|unused_200047|>",
397
+ "single_word": false,
398
+ "lstrip": false,
399
+ "rstrip": false,
400
+ "normalized": false,
401
+ "special": true
402
+ },
403
+ "200048": {
404
+ "content": "<|unused_200048|>",
405
+ "single_word": false,
406
+ "lstrip": false,
407
+ "rstrip": false,
408
+ "normalized": false,
409
+ "special": true
410
+ },
411
+ "200049": {
412
+ "content": "<|content_invoke_tool_json|>",
413
+ "single_word": false,
414
+ "lstrip": false,
415
+ "rstrip": false,
416
+ "normalized": false,
417
+ "special": true
418
+ },
419
+ "200050": {
420
+ "content": "<|unused_200050|>",
421
+ "single_word": false,
422
+ "lstrip": false,
423
+ "rstrip": false,
424
+ "normalized": false,
425
+ "special": true
426
+ },
427
+ "200051": {
428
+ "content": "<|unused_200051|>",
429
+ "single_word": false,
430
+ "lstrip": false,
431
+ "rstrip": false,
432
+ "normalized": false,
433
+ "special": true
434
+ },
435
+ "200052": {
436
+ "content": "<|unused_200052|>",
437
+ "single_word": false,
438
+ "lstrip": false,
439
+ "rstrip": false,
440
+ "normalized": false,
441
+ "special": true
442
+ },
443
+ "200053": {
444
+ "content": "<|unused_200053|>",
445
+ "single_word": false,
446
+ "lstrip": false,
447
+ "rstrip": false,
448
+ "normalized": false,
449
+ "special": true
450
+ },
451
+ "200054": {
452
+ "content": "<|unused_200054|>",
453
+ "single_word": false,
454
+ "lstrip": false,
455
+ "rstrip": false,
456
+ "normalized": false,
457
+ "special": true
458
+ },
459
+ "200055": {
460
+ "content": "<|unused_200055|>",
461
+ "single_word": false,
462
+ "lstrip": false,
463
+ "rstrip": false,
464
+ "normalized": false,
465
+ "special": true
466
+ },
467
+ "200056": {
468
+ "content": "<|unused_200056|>",
469
+ "single_word": false,
470
+ "lstrip": false,
471
+ "rstrip": false,
472
+ "normalized": false,
473
+ "special": true
474
+ },
475
+ "200057": {
476
+ "content": "<|content_invoke_tool_text|>",
477
+ "single_word": false,
478
+ "lstrip": false,
479
+ "rstrip": false,
480
+ "normalized": false,
481
+ "special": true
482
+ }
483
+ },
484
+ "clean_up_tokenization_spaces": false,
485
+ "fix_mistral_regex": false,
486
+ "extra_special_tokens": {
487
+ "endoftext": "<|endoftext|>",
488
+ "message_user": "<|message_user|>",
489
+ "message_model": "<|message_model|>",
490
+ "message_system": "<|message_system|>",
491
+ "message_tool": "<|message_tool|>",
492
+ "content_text": "<|content_text|>",
493
+ "content_image": "<|content_image|>",
494
+ "content_model_end_sampling": "<|content_model_end_sampling|>",
495
+ "content_thinking": "<|content_thinking|>",
496
+ "end_message": "<|end_message|>",
497
+ "content_audio_input": "<|content_audio_input|>",
498
+ "content_tool_error": "<|content_tool_error|>",
499
+ "audio": "<|audio|>",
500
+ "content_xml": "<|content_xml|>",
501
+ "begin_of_text": "<|begin_of_text|>",
502
+ "audio_end": "<|audio_end|>",
503
+ "content_invoke_tool_json": "<|content_invoke_tool_json|>",
504
+ "content_invoke_tool_text": "<|content_invoke_tool_text|>"
505
+ },
506
+ "model_max_length": 1000000000000000019884624838656,
507
+ "tokenizer_class": "PreTrainedTokenizerFast"
508
+ }