mlx-bun 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +362 -0
  3. package/THIRD_PARTY_LICENSES.md +41 -0
  4. package/bin/mlx-bun.mjs +32 -0
  5. package/docs/curve-designer.html +303 -0
  6. package/docs/design/adapters-end-to-end.md +201 -0
  7. package/docs/design/compat-cli-surface-design.md +227 -0
  8. package/docs/design/docs-reorg-plan.md +152 -0
  9. package/docs/design/hlg-sampling.md +340 -0
  10. package/docs/design/memory-system.md +339 -0
  11. package/docs/design/optimization_plan.md +344 -0
  12. package/docs/design/parallel-slots.md +515 -0
  13. package/docs/design/runtime-isolation.md +247 -0
  14. package/docs/design/segmented-backward-training.md +508 -0
  15. package/docs/investigations/chunk-finetune-experiment.md +84 -0
  16. package/docs/investigations/curve-bisector-routing.md +123 -0
  17. package/docs/investigations/curve-runs/bisector-route.json +98 -0
  18. package/docs/investigations/curve-runs/bisector-trace.json +1393 -0
  19. package/docs/investigations/curve-runs/curve-terrain.json +1179 -0
  20. package/docs/investigations/curve-runs/curve-terrain.svg +42 -0
  21. package/docs/investigations/curve-terrain.html +105 -0
  22. package/docs/investigations/expert-offload-single-user-moe.md +292 -0
  23. package/docs/investigations/hlg-ab.html +34 -0
  24. package/docs/investigations/hlg-explorer.html +238 -0
  25. package/docs/investigations/hlg-figs/cliff.svg +1 -0
  26. package/docs/investigations/hlg-figs/cross.svg +1 -0
  27. package/docs/investigations/hlg-figs/edges.svg +1 -0
  28. package/docs/investigations/hlg-figs/k20-grids.svg +1 -0
  29. package/docs/investigations/hlg-figs/k20-peaks.svg +1 -0
  30. package/docs/investigations/hlg-figs/lw-curve.svg +1 -0
  31. package/docs/investigations/hlg-figs/terrain.svg +1 -0
  32. package/docs/investigations/hlg-report.html +262 -0
  33. package/docs/investigations/hlg-runs/hlg-ab.json +149 -0
  34. package/docs/investigations/hlg-runs/hlg-blind-prompts.md +87 -0
  35. package/docs/investigations/hlg-runs/hlg-blind.json +107 -0
  36. package/docs/investigations/hlg-runs/hlg-cliff.json +197 -0
  37. package/docs/investigations/hlg-runs/hlg-cross.json +505 -0
  38. package/docs/investigations/hlg-runs/hlg-edge.json +388 -0
  39. package/docs/investigations/hlg-runs/hlg-eval1.json +25 -0
  40. package/docs/investigations/hlg-runs/hlg-frontier.json +86 -0
  41. package/docs/investigations/hlg-runs/hlg-grid.json +242 -0
  42. package/docs/investigations/hlg-runs/hlg-grid2.json +343 -0
  43. package/docs/investigations/hlg-runs/hlg-lw-coarse.json +110 -0
  44. package/docs/investigations/hlg-runs/hlg-map.json +383 -0
  45. package/docs/investigations/hlg-runs/run0-original.log +159 -0
  46. package/docs/investigations/hlg-runs/runA-recommended.log +131 -0
  47. package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +26 -0
  48. package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +18 -0
  49. package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +25 -0
  50. package/docs/investigations/hlg-runs/runAD-ab-samples.log +8 -0
  51. package/docs/investigations/hlg-runs/runAE-edge-e4b.log +51 -0
  52. package/docs/investigations/hlg-runs/runAF-logit-calibration.log +10 -0
  53. package/docs/investigations/hlg-runs/runAG-eval-config2.log +16 -0
  54. package/docs/investigations/hlg-runs/runB-gain-variants.log +192 -0
  55. package/docs/investigations/hlg-runs/runC-pivots.log +162 -0
  56. package/docs/investigations/hlg-runs/runD-oetf.log +132 -0
  57. package/docs/investigations/hlg-runs/runE-eotf.log +162 -0
  58. package/docs/investigations/hlg-runs/runF-shaper-working.log +86 -0
  59. package/docs/investigations/hlg-runs/runG-shaper-sweep.log +101 -0
  60. package/docs/investigations/hlg-runs/runH-wsweep.log +176 -0
  61. package/docs/investigations/hlg-runs/runI-asweep.log +176 -0
  62. package/docs/investigations/hlg-runs/runJ-knobmap.log +49 -0
  63. package/docs/investigations/hlg-runs/runK-frontier.log +19 -0
  64. package/docs/investigations/hlg-runs/runL-widemap.log +65 -0
  65. package/docs/investigations/hlg-runs/runM-frontier-emb.log +15 -0
  66. package/docs/investigations/hlg-runs/runN-verify.log +116 -0
  67. package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +15 -0
  68. package/docs/investigations/hlg-runs/runP-grid-AxSm.log +15 -0
  69. package/docs/investigations/hlg-runs/runQ-grid-tgW.log +15 -0
  70. package/docs/investigations/hlg-runs/runR-blind-transcript.log +79 -0
  71. package/docs/investigations/hlg-runs/runS-cross-coherence.log +33 -0
  72. package/docs/investigations/hlg-runs/runT-cross-cpm5.log +33 -0
  73. package/docs/investigations/hlg-runs/runU-cross-12b.log +33 -0
  74. package/docs/investigations/hlg-runs/runV-lw-e4b.log +22 -0
  75. package/docs/investigations/hlg-runs/runW-lw-12b.log +22 -0
  76. package/docs/investigations/hlg-runs/runX-lw-cpm5.log +22 -0
  77. package/docs/investigations/hlg-runs/runY-cliff-e4b.log +26 -0
  78. package/docs/investigations/hlg-runs/runZ-cliff-12b.log +26 -0
  79. package/docs/investigations/hlg-sampling-investigation.md +970 -0
  80. package/docs/investigations/lab-build-journal.md +157 -0
  81. package/docs/investigations/pi-builtin-investigation.md +308 -0
  82. package/docs/investigations/starter-model-port-handoff.md +323 -0
  83. package/docs/planning/IDEAS.md +26 -0
  84. package/docs/planning/PRODUCT_ROADMAP.md +426 -0
  85. package/docs/planning/ResearchTopics.md +119 -0
  86. package/docs/planning/chunk-task-roadmap.md +77 -0
  87. package/docs/planning/journal.md +155 -0
  88. package/docs/reference/distribution.md +121 -0
  89. package/docs/reference/embedding.md +168 -0
  90. package/docs/reference/library-api.md +153 -0
  91. package/docs/reference/server-api.md +224 -0
  92. package/docs/reference/server-config.md +243 -0
  93. package/docs/segmented-backward-handoff.md +148 -0
  94. package/package.json +68 -0
  95. package/src/anthropic.ts +491 -0
  96. package/src/chat-template.ts +186 -0
  97. package/src/cli.ts +1075 -0
  98. package/src/config.ts +229 -0
  99. package/src/curve-sampler.ts +122 -0
  100. package/src/dataset/generators.ts +1087 -0
  101. package/src/dataset/index.ts +12 -0
  102. package/src/dataset/job.ts +81 -0
  103. package/src/dataset/llm.ts +71 -0
  104. package/src/dataset/registry.ts +528 -0
  105. package/src/download.ts +283 -0
  106. package/src/eval/README.md +86 -0
  107. package/src/eval/kl-prompts.ts +29 -0
  108. package/src/eval/kl.ts +294 -0
  109. package/src/eval/quality-db.ts +92 -0
  110. package/src/eval/runner.ts +102 -0
  111. package/src/eval/score.ts +49 -0
  112. package/src/eval/tasks/bfcl.ts +500 -0
  113. package/src/eval/tasks/gsm8k.ts +85 -0
  114. package/src/eval/tasks/hashhop.ts +109 -0
  115. package/src/eval/tasks/humaneval.ts +252 -0
  116. package/src/eval/tasks/ifeval.ts +354 -0
  117. package/src/eval/tasks/mmlu.ts +145 -0
  118. package/src/evaldb.ts +199 -0
  119. package/src/expert-offload-build.ts +77 -0
  120. package/src/expert-offload.ts +61 -0
  121. package/src/expert-trace.ts +67 -0
  122. package/src/fit.ts +254 -0
  123. package/src/generate.ts +451 -0
  124. package/src/harness-pi.ts +177 -0
  125. package/src/hf-push.ts +628 -0
  126. package/src/index.ts +22 -0
  127. package/src/jobs/db.ts +139 -0
  128. package/src/jobs/index.ts +29 -0
  129. package/src/jobs/job-entry.ts +106 -0
  130. package/src/jobs/runner.ts +304 -0
  131. package/src/jobs/sse.ts +114 -0
  132. package/src/jobs/types.ts +58 -0
  133. package/src/kv-store.ts +185 -0
  134. package/src/lora.ts +314 -0
  135. package/src/md-modules.d.ts +7 -0
  136. package/src/mlx/array.ts +237 -0
  137. package/src/mlx/autograd.ts +267 -0
  138. package/src/mlx/checkpoint.ts +116 -0
  139. package/src/mlx/compile.ts +158 -0
  140. package/src/mlx/custom-vjp.ts +140 -0
  141. package/src/mlx/ffi.ts +433 -0
  142. package/src/mlx/metal-kernel.ts +130 -0
  143. package/src/mlx/ops.ts +707 -0
  144. package/src/mmap.ts +90 -0
  145. package/src/model/batched-mask.ts +204 -0
  146. package/src/model/batched-rotating.ts +300 -0
  147. package/src/model/compiled-decode.ts +681 -0
  148. package/src/model/factory.ts +27 -0
  149. package/src/model/fingerprint.ts +33 -0
  150. package/src/model/flash-attention.ts +674 -0
  151. package/src/model/fused-decode-kernel.ts +305 -0
  152. package/src/model/fused-geglu-kernel.ts +87 -0
  153. package/src/model/gemma4-base.ts +1616 -0
  154. package/src/model/gemma4.ts +1012 -0
  155. package/src/model/generated/gemma4-12b.ts +526 -0
  156. package/src/model/generated/gemma4-26b.ts +500 -0
  157. package/src/model/generated/gemma4-e4b.ts +757 -0
  158. package/src/model/generated/index.ts +18 -0
  159. package/src/model/minicpm5.ts +278 -0
  160. package/src/model/qwen3-delta.ts +224 -0
  161. package/src/model/qwen3_5.ts +429 -0
  162. package/src/model/support.ts +46 -0
  163. package/src/native-pack.ts +152 -0
  164. package/src/pi-provider.ts +95 -0
  165. package/src/pi-terminal.ts +264 -0
  166. package/src/pi-web.ts +924 -0
  167. package/src/preflight.ts +123 -0
  168. package/src/prompt-cache.ts +113 -0
  169. package/src/quantize/allocator.ts +627 -0
  170. package/src/quantize/calibration.ts +180 -0
  171. package/src/quantize/config-writer.ts +132 -0
  172. package/src/quantize/index.ts +57 -0
  173. package/src/quantize/job.ts +136 -0
  174. package/src/quantize/quantizer.ts +462 -0
  175. package/src/quantize/safetensors-writer.ts +171 -0
  176. package/src/quantize/sensitivity.ts +234 -0
  177. package/src/registry.ts +336 -0
  178. package/src/responses.ts +774 -0
  179. package/src/safetensors.ts +158 -0
  180. package/src/sampler.ts +517 -0
  181. package/src/serve/batch-scheduler.ts +362 -0
  182. package/src/serve/generation-gateway.ts +160 -0
  183. package/src/server.ts +1891 -0
  184. package/src/spec/drafter.ts +261 -0
  185. package/src/spec/generate.ts +221 -0
  186. package/src/status-page.html +497 -0
  187. package/src/tokenizer.ts +38 -0
  188. package/src/tool-call.ts +242 -0
  189. package/src/train/dataset.ts +461 -0
  190. package/src/train/export.ts +32 -0
  191. package/src/train/forward.ts +229 -0
  192. package/src/train/index.ts +43 -0
  193. package/src/train/job.ts +148 -0
  194. package/src/train/lora-params.ts +194 -0
  195. package/src/train/loss.ts +342 -0
  196. package/src/train/merge.ts +195 -0
  197. package/src/train/optimizer.ts +176 -0
  198. package/src/train/rank.ts +101 -0
  199. package/src/train/segmented.ts +520 -0
  200. package/src/train/trainer.ts +600 -0
  201. package/src/tui.ts +161 -0
  202. package/src/vision/embedder.ts +168 -0
  203. package/src/vision/preprocess.ts +247 -0
  204. package/src/vision/prompt.ts +158 -0
  205. package/src/vision/siglip.ts +599 -0
  206. package/src/web/app.html +2471 -0
  207. package/src/web/skills/web-research/SKILL.md +44 -0
  208. package/src/web/skills.ts +59 -0
  209. package/src/web-tools.ts +426 -0
  210. package/src/weights.ts +91 -0
@@ -0,0 +1,176 @@
1
+ // AdamW optimizer over a flat list of MlxArray parameters.
2
+ //
3
+ // Port of mlx.optimizers.AdamW: decoupled weight decay (Loshchilov &
4
+ // Hutter), bias-corrected first/second moments. Each step REPLACES every
5
+ // param handle in place (old disposed, new array swapped in) and invokes the
6
+ // `write(i, newParam)` callback so the owning LoraWeights.a/b picks up the new
7
+ // handle for the next forward.
8
+ //
9
+ // Update (per param p with grad g, step t):
10
+ // p <- p - lr * wd * p (decoupled weight decay)
11
+ // m <- β1·m + (1-β1)·g
12
+ // v <- β2·v + (1-β2)·g²
13
+ // m̂ = m / (1 - β1^t)
14
+ // v̂ = v / (1 - β2^t)
15
+ // p <- p - lr * m̂ / (√v̂ + ε)
16
+
17
+ import { MlxArray } from "../mlx/array";
18
+ import { Dtype } from "../mlx/ffi";
19
+ import * as ops from "../mlx/ops";
20
+
21
+ export interface AdamWOptions {
22
+ lr: number;
23
+ betas?: [number, number];
24
+ eps?: number;
25
+ weightDecay?: number;
26
+ }
27
+
28
+ export class AdamW {
29
+ #params: MlxArray[];
30
+ readonly #write: (i: number, p: MlxArray) => void;
31
+ readonly #m: MlxArray[];
32
+ readonly #v: MlxArray[];
33
+ readonly #b1: number;
34
+ readonly #b2: number;
35
+ readonly #eps: number;
36
+ readonly #wd: number;
37
+ #lr: number;
38
+ #t = 0;
39
+
40
+ /** @param params live param arrays (e.g. flatParams(lora)).
41
+ * @param write callback to push a replaced param back into its owner
42
+ * (e.g. lw.a / lw.b). If omitted, only the internal array is
43
+ * updated (the caller must read params via `getParam`). */
44
+ constructor(params: MlxArray[], opts: AdamWOptions, write?: (i: number, p: MlxArray) => void) {
45
+ this.#params = [...params];
46
+ this.#write = write ?? (() => {});
47
+ this.#lr = opts.lr;
48
+ [this.#b1, this.#b2] = opts.betas ?? [0.9, 0.999];
49
+ this.#eps = opts.eps ?? 1e-8;
50
+ this.#wd = opts.weightDecay ?? 0.01;
51
+ // m, v as zeros like each param (handles 0-d params, where ops.zeros's
52
+ // empty shape buffer would trip ptr()).
53
+ this.#m = params.map(zerosLikeF32);
54
+ this.#v = params.map(zerosLikeF32);
55
+ }
56
+
57
+ /** Current parameter array at index i (after the latest step). */
58
+ getParam(i: number): MlxArray {
59
+ return this.#params[i]!;
60
+ }
61
+
62
+ set lr(value: number) {
63
+ this.#lr = value;
64
+ }
65
+ get lr(): number {
66
+ return this.#lr;
67
+ }
68
+
69
+ get step_count(): number {
70
+ return this.#t;
71
+ }
72
+
73
+ /** Apply one AdamW update. `grads[i]` is dL/dparams[i]. Takes ownership of
74
+ * the grads (they are disposed here). */
75
+ step(grads: MlxArray[]): void {
76
+ if (grads.length !== this.#params.length)
77
+ throw new Error(`AdamW.step: ${grads.length} grads for ${this.#params.length} params`);
78
+ this.#t += 1;
79
+ const bc1 = 1 - Math.pow(this.#b1, this.#t);
80
+ const bc2 = 1 - Math.pow(this.#b2, this.#t);
81
+
82
+ for (let i = 0; i < this.#params.length; i++) {
83
+ const p = this.#params[i]!;
84
+ const g0 = grads[i]!;
85
+ const g = g0.dtype === Dtype.float32 ? g0 : g0.astype(Dtype.float32);
86
+
87
+ // m <- β1·m + (1-β1)·g
88
+ const m0 = this.#m[i]!;
89
+ const mScaled = ops.mulScalar(m0, this.#b1);
90
+ const gScaled = ops.mulScalar(g, 1 - this.#b1);
91
+ const m1 = ops.add(mScaled, gScaled);
92
+ mScaled.dispose();
93
+ gScaled.dispose();
94
+ m0.dispose();
95
+ this.#m[i] = m1;
96
+
97
+ // v <- β2·v + (1-β2)·g²
98
+ const v0 = this.#v[i]!;
99
+ const g2 = ops.square(g);
100
+ const vScaled = ops.mulScalar(v0, this.#b2);
101
+ const g2Scaled = ops.mulScalar(g2, 1 - this.#b2);
102
+ const v1 = ops.add(vScaled, g2Scaled);
103
+ g2.dispose();
104
+ vScaled.dispose();
105
+ g2Scaled.dispose();
106
+ v0.dispose();
107
+ this.#v[i] = v1;
108
+
109
+ // p <- p - lr·wd·p (decoupled weight decay)
110
+ let pNext = p;
111
+ if (this.#wd !== 0) {
112
+ const decay = ops.mulScalar(p, this.#lr * this.#wd);
113
+ pNext = ops.sub(p, decay);
114
+ decay.dispose();
115
+ }
116
+
117
+ // m̂ / (√v̂ + ε)
118
+ const mHat = ops.mulScalar(m1, 1 / bc1);
119
+ const vHat = ops.mulScalar(v1, 1 / bc2);
120
+ const sqrtV = ops.sqrt(vHat);
121
+ const eps = ops.scalarLike(this.#eps, sqrtV);
122
+ const denom = ops.add(sqrtV, eps);
123
+ const update = ops.div(mHat, denom);
124
+ const lrUpdate = ops.mulScalar(update, this.#lr);
125
+ const pFinal = ops.sub(pNext, lrUpdate);
126
+
127
+ for (const a of [mHat, vHat, sqrtV, eps, denom, update, lrUpdate]) a.dispose();
128
+ if (pNext !== p) pNext.dispose();
129
+ if (g !== g0) g.dispose();
130
+ g0.dispose();
131
+ p.dispose();
132
+
133
+ this.#params[i] = pFinal;
134
+ this.#write(i, pFinal);
135
+ }
136
+ }
137
+
138
+ /** Eval all optimizer state + params (call once per step after `step`). */
139
+ evalState(): void {
140
+ ops.evalAll([...this.#params, ...this.#m, ...this.#v]);
141
+ }
142
+
143
+ dispose(): void {
144
+ for (const a of this.#m) a.dispose();
145
+ for (const a of this.#v) a.dispose();
146
+ // params are owned by their LoraWeights; not disposed here.
147
+ }
148
+ }
149
+
150
+ /** Zeros with the same shape as `p`, as float32. Uses mul-by-0 so 0-d
151
+ * params work (ops.zeros's empty shape buffer trips ptr()). */
152
+ function zerosLikeF32(p: MlxArray): MlxArray {
153
+ const f = p.dtype === Dtype.float32 ? p : p.astype(Dtype.float32);
154
+ const z = ops.mulScalar(f, 0);
155
+ if (f !== p) f.dispose();
156
+ return z;
157
+ }
158
+
159
+ /** Linear warmup → cosine decay schedule (to 10% of peak), used by DPO.
160
+ * Returns lr for a 1-based step. Port of dpo.py lr_schedule. */
161
+ export function warmupCosineSchedule(
162
+ peakLr: number,
163
+ warmupIters: number,
164
+ totalIters: number,
165
+ ): (step: number) => number {
166
+ const minLr = 0.1 * peakLr;
167
+ const warmup = Math.max(warmupIters, 1);
168
+ const remaining = Math.max(totalIters - warmupIters, 1);
169
+ return (step: number) => {
170
+ // step is 1-based; convert to 0-based for the ramp like the reference.
171
+ const s = step - 1;
172
+ if (s < warmup) return Math.min(peakLr, (peakLr * (s + 1)) / warmup);
173
+ const progress = Math.max(0, Math.min(1, (s - warmup) / remaining));
174
+ return minLr + 0.5 * (peakLr - minLr) * (1 + Math.cos(Math.PI * progress));
175
+ };
176
+ }
@@ -0,0 +1,101 @@
1
+ // Per-layer LoRA rank resolution — port of optiq/lora/sensitivity_rank.py.
2
+ //
3
+ // Produces a Map<modulePath, rank> over the model's loraTargets() filtered by
4
+ // target-module suffixes and a layer window. The three strategies:
5
+ // constant — every target gets the base rank.
6
+ // by_bits — rank * (bits / 4), clamped to >= MIN_RANK. For a uniformly
7
+ // 4-bit model (MiniCPM5-1B-OptiQ-4bit) this is identical to
8
+ // constant unless an optiq_metadata.json per-layer bits map is
9
+ // supplied.
10
+ // by_kl — rank * clip(kl / median_kl, 0.5, 2.0); falls back to by_bits
11
+ // when no KL map is present.
12
+
13
+ import type { RuntimeModel } from "../model/factory";
14
+
15
+ export type RankScaling = "constant" | "by_bits" | "by_kl";
16
+
17
+ const MIN_RANK = 2; // mlx LoRA requires rank >= 2.
18
+
19
+ /** All 7 trainable linears per transformer block (Unsloth target set). */
20
+ export const DEFAULT_TARGET_MODULES = [
21
+ "q_proj", "k_proj", "v_proj", "o_proj",
22
+ "gate_proj", "up_proj", "down_proj",
23
+ ] as const;
24
+
25
+ export interface ResolveRanksOptions {
26
+ rank: number;
27
+ rankScaling: RankScaling;
28
+ /** Suffixes (e.g. "q_proj"); a module is adapted iff its path ends with one. */
29
+ targetModules?: readonly string[];
30
+ /** -1 = all layers; else only the last N transformer blocks. */
31
+ numLayers?: number;
32
+ /** Optional per-layer bits map (optiq_metadata.json). */
33
+ bitsMap?: Record<string, number>;
34
+ /** Optional per-layer KL map. */
35
+ klMap?: Record<string, number>;
36
+ }
37
+
38
+ /** Resolve a rank per adapted module path. */
39
+ export function resolveRanks(model: RuntimeModel, opts: ResolveRanksOptions): Map<string, number> {
40
+ const targetModules = opts.targetModules ?? DEFAULT_TARGET_MODULES;
41
+ const numLayers = opts.numLayers ?? -1;
42
+ const allPaths = [...model.loraTargets().keys()];
43
+
44
+ // Total transformer-block count, for the last-N window.
45
+ const totalLayers = countLayers(allPaths);
46
+ const minLayer = numLayers < 0 ? 0 : Math.max(0, totalLayers - numLayers);
47
+
48
+ const out = new Map<string, number>();
49
+ for (const path of allPaths) {
50
+ if (!endsWithAny(path, targetModules)) continue;
51
+ const layer = layerIndex(path);
52
+ if (layer >= 0 && layer < minLayer) continue;
53
+ out.set(path, rankForLayer(path, opts));
54
+ }
55
+ return out;
56
+ }
57
+
58
+ function rankForLayer(path: string, opts: ResolveRanksOptions): number {
59
+ const base = opts.rank;
60
+ if (opts.rankScaling === "constant") return Math.max(MIN_RANK, base);
61
+
62
+ if (opts.rankScaling === "by_bits") {
63
+ const bits = opts.bitsMap?.[path];
64
+ if (bits === undefined) return Math.max(MIN_RANK, base);
65
+ return Math.max(MIN_RANK, Math.ceil(base * (bits / 4.0)));
66
+ }
67
+
68
+ if (opts.rankScaling === "by_kl") {
69
+ const kl = opts.klMap?.[path];
70
+ if (kl === undefined || !opts.klMap) {
71
+ // fall back to by_bits
72
+ return rankForLayer(path, { ...opts, rankScaling: "by_bits" });
73
+ }
74
+ const vals = Object.values(opts.klMap).sort((a, b) => a - b);
75
+ const median = vals.length ? vals[Math.floor(vals.length / 2)]! : 1.0;
76
+ const factor = Math.max(0.5, Math.min(2.0, median ? kl / median : 1.0));
77
+ return Math.max(MIN_RANK, Math.ceil(base * factor));
78
+ }
79
+
80
+ throw new Error(`unknown rank_scaling: ${opts.rankScaling}`);
81
+ }
82
+
83
+ /** Extract the transformer-block index from a module path like
84
+ * `model.layers.7.self_attn.q_proj` → 7; -1 if not found. */
85
+ function layerIndex(path: string): number {
86
+ const m = path.match(/\.layers\.(\d+)\./);
87
+ return m ? Number(m[1]) : -1;
88
+ }
89
+
90
+ function countLayers(paths: string[]): number {
91
+ let max = -1;
92
+ for (const p of paths) {
93
+ const i = layerIndex(p);
94
+ if (i > max) max = i;
95
+ }
96
+ return max + 1;
97
+ }
98
+
99
+ function endsWithAny(path: string, suffixes: readonly string[]): boolean {
100
+ return suffixes.some((s) => path.endsWith(s));
101
+ }