mlx-bun 0.0.12 → 0.2.0

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 (226) hide show
  1. package/README.md +40 -24
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/{design → archive}/hlg-sampling.md +3 -3
  4. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  5. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  6. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  7. package/docs/archive/planning/release-notes-v0.0.13.md +77 -0
  8. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  9. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  10. package/docs/design/colibri-glm52-port.md +8 -1
  11. package/docs/design/compat-cli-surface-design.md +1 -1
  12. package/docs/design/decode-speed-program.md +1 -1
  13. package/docs/design/docs-reorg-plan.md +1 -1
  14. package/docs/design/dspark-serving-program.md +1 -1
  15. package/docs/design/dspark-speculative-decoding.md +53 -3
  16. package/docs/design/grammar-spec-batching-integration.md +1 -1
  17. package/docs/design/mlx-lm-serving-execution-seam.md +81 -0
  18. package/docs/design/orpo-dynamic-lambda.md +1 -1
  19. package/docs/design/orpo-future-enhancements.md +1 -1
  20. package/docs/design/orpo-training.md +2 -2
  21. package/docs/design/parity-tier-dag.md +1 -1
  22. package/docs/design/repo-cleanup-plan.md +46 -4
  23. package/docs/design/segmented-backward-training.md +2 -2
  24. package/docs/design/ssd-kv-cold-tier.md +127 -0
  25. package/docs/design/turboquant-weights.md +467 -0
  26. package/docs/design/w4a16-compute-precision-spike.md +1 -1
  27. package/docs/design/web-chat-redesign.md +5 -5
  28. package/docs/planning/chunk-task-roadmap.md +1 -1
  29. package/docs/planning/curve-sampler-research-plan.md +4 -4
  30. package/docs/planning/memory-docs-and-dag-plan.md +1 -1
  31. package/docs/planning/release-notes-v0.2.0.md +49 -0
  32. package/docs/planning/video-series.md +421 -0
  33. package/docs/reference/cli.md +15 -4
  34. package/docs/reference/distribution.md +1 -0
  35. package/docs/reference/features-matrix.md +3 -1
  36. package/docs/reference/library-api.md +41 -1
  37. package/docs/reference/models.md +33 -0
  38. package/docs/reference/server-api.md +86 -19
  39. package/docs/reference/server-config.md +57 -33
  40. package/package.json +2 -2
  41. package/src/anthropic.ts +92 -3
  42. package/src/chat-template.ts +34 -1
  43. package/src/cli.ts +131 -36
  44. package/src/config.ts +10 -0
  45. package/src/eval/kl.ts +27 -20
  46. package/src/eval/runner.ts +23 -4
  47. package/src/eval/tasks/bfcl.ts +2 -1
  48. package/src/eval/tasks/gsm8k.ts +2 -1
  49. package/src/eval/tasks/hashhop.ts +2 -1
  50. package/src/eval/tasks/humaneval.ts +5 -4
  51. package/src/eval/tasks/ifeval.ts +2 -1
  52. package/src/eval/tasks/mmlu.ts +2 -1
  53. package/src/expert-io.ts +3 -1
  54. package/src/expert-offload.ts +3 -1
  55. package/src/expert-trace.ts +3 -1
  56. package/src/fit.ts +24 -43
  57. package/src/flags.ts +7 -9
  58. package/src/generate.ts +107 -92
  59. package/src/grammar.ts +2 -1
  60. package/src/harness-pi.ts +1 -1
  61. package/src/index.ts +20 -0
  62. package/src/jobs/job-entry.ts +3 -2
  63. package/src/kv-scheme.ts +199 -0
  64. package/src/kv-store.ts +370 -205
  65. package/src/media-fetch.ts +19 -3
  66. package/src/memory/model.ts +2 -1
  67. package/src/memory/vault.ts +2 -1
  68. package/src/memory-plan.ts +18 -0
  69. package/src/mlx/ffi.ts +8 -11
  70. package/src/mlx/ops.ts +31 -6
  71. package/src/model/batched-mask.ts +1 -1
  72. package/src/model/batched-quant.ts +1 -1
  73. package/src/model/batched-rotating-quant.ts +43 -97
  74. package/src/model/batched-rotating-state.ts +73 -0
  75. package/src/model/batched-rotating.ts +56 -105
  76. package/src/model/batched-row-storage.ts +127 -0
  77. package/src/model/compiled-decode.ts +3 -2
  78. package/src/model/factory.ts +44 -49
  79. package/src/model/gemma4-base.ts +163 -10
  80. package/src/model/generated/gemma4-12b.ts +50 -146
  81. package/src/model/generated/gemma4-26b.ts +32 -92
  82. package/src/model/generated/gemma4-e4b.ts +26 -74
  83. package/src/model/glm52-cache.ts +19 -9
  84. package/src/model/glm52-memory.ts +28 -1
  85. package/src/model/minicpm5.ts +2 -1
  86. package/src/model/profile.ts +433 -0
  87. package/src/model/qwen3-delta.ts +98 -2
  88. package/src/model/qwen3-mrope.ts +252 -0
  89. package/src/model/qwen3_5.ts +217 -25
  90. package/src/native/frame_extract.swift +140 -0
  91. package/src/native-pack.ts +13 -6
  92. package/src/pi-web.ts +6 -5
  93. package/src/prompt-cache.ts +2 -2
  94. package/src/quantize/atomic-output.ts +38 -0
  95. package/src/quantize/calibration.ts +2 -1
  96. package/src/quantize/config-writer.ts +6 -0
  97. package/src/quantize/index.ts +28 -1
  98. package/src/quantize/job.ts +11 -0
  99. package/src/quantize/quantizer.ts +265 -100
  100. package/src/quantize/rotate.ts +603 -0
  101. package/src/quantize/safetensors-writer.ts +79 -1
  102. package/src/quantize/sensitivity.ts +41 -59
  103. package/src/quantize/weight-transform.ts +292 -0
  104. package/src/responses.ts +102 -2
  105. package/src/runtime-config.ts +67 -0
  106. package/src/sampler.ts +272 -0
  107. package/src/serve/admin-routes.ts +184 -0
  108. package/src/serve/aux-routes.ts +132 -0
  109. package/src/serve/batch-scheduler.ts +200 -87
  110. package/src/serve/completion-executor.ts +346 -0
  111. package/src/serve/completion-sink.ts +157 -0
  112. package/src/serve/discovery-routes.ts +205 -0
  113. package/src/serve/generation-gateway.ts +116 -109
  114. package/src/serve/kv-budget.ts +14 -0
  115. package/src/serve/lab-routes.ts +338 -0
  116. package/src/serve/model-admin-routes.ts +153 -0
  117. package/src/serve/prompt-response-trace.ts +147 -0
  118. package/src/serve/request-plan.ts +158 -0
  119. package/src/serve/static-routes.ts +101 -0
  120. package/src/server.ts +768 -1367
  121. package/src/spec/dflash-source.ts +2 -1
  122. package/src/spec/dspark/module-dflash.ts +1 -1
  123. package/src/spec/glm52-mtp-source.ts +8 -0
  124. package/src/spec/qwen-mtp-source.ts +431 -0
  125. package/src/spec/serve-loop.ts +36 -68
  126. package/src/ssd-cache.ts +17 -14
  127. package/src/ssd-durability.ts +195 -0
  128. package/src/train/flash-cce.ts +12 -11
  129. package/src/train/forward.ts +32 -1
  130. package/src/train/loss.ts +2 -1
  131. package/src/train/segmented.ts +4 -3
  132. package/src/train/trainer.ts +3 -2
  133. package/src/vision/preprocess.ts +1 -1
  134. package/src/vision/prompt.ts +47 -1
  135. package/src/vision/qwen3vl-preprocess.ts +409 -0
  136. package/src/vision/qwen3vl-prompt.ts +196 -0
  137. package/src/vision/qwen3vl-tower.ts +513 -0
  138. package/src/vision/video-frames.ts +148 -0
  139. package/src/web/app.js +10 -3
  140. package/src/web/src/chat.ts +12 -5
  141. package/src/weights.ts +81 -26
  142. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  143. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  144. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  145. /package/docs/{investigations → archive/investigations}/curve-runs/bisector-route.json +0 -0
  146. /package/docs/{investigations → archive/investigations}/curve-runs/bisector-trace.json +0 -0
  147. /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.json +0 -0
  148. /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.svg +0 -0
  149. /package/docs/{investigations → archive/investigations}/curve-terrain.html +0 -0
  150. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  151. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  152. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  153. /package/docs/{investigations → archive/investigations}/hlg-ab.html +0 -0
  154. /package/docs/{investigations → archive/investigations}/hlg-explorer.html +0 -0
  155. /package/docs/{investigations → archive/investigations}/hlg-figs/cliff.svg +0 -0
  156. /package/docs/{investigations → archive/investigations}/hlg-figs/cross.svg +0 -0
  157. /package/docs/{investigations → archive/investigations}/hlg-figs/edges.svg +0 -0
  158. /package/docs/{investigations → archive/investigations}/hlg-figs/k20-grids.svg +0 -0
  159. /package/docs/{investigations → archive/investigations}/hlg-figs/k20-peaks.svg +0 -0
  160. /package/docs/{investigations → archive/investigations}/hlg-figs/lw-curve.svg +0 -0
  161. /package/docs/{investigations → archive/investigations}/hlg-figs/terrain.svg +0 -0
  162. /package/docs/{investigations → archive/investigations}/hlg-report.html +0 -0
  163. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-ab.json +0 -0
  164. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind-prompts.md +0 -0
  165. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind.json +0 -0
  166. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cliff.json +0 -0
  167. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cross.json +0 -0
  168. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-edge.json +0 -0
  169. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-eval1.json +0 -0
  170. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-frontier.json +0 -0
  171. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid.json +0 -0
  172. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid2.json +0 -0
  173. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-lw-coarse.json +0 -0
  174. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-map.json +0 -0
  175. /package/docs/{investigations → archive/investigations}/hlg-runs/run0-original.log +0 -0
  176. /package/docs/{investigations → archive/investigations}/hlg-runs/runA-recommended.log +0 -0
  177. /package/docs/{investigations → archive/investigations}/hlg-runs/runAA-cliff-cpm5.log +0 -0
  178. /package/docs/{investigations → archive/investigations}/hlg-runs/runAB-grid2-e4b.log +0 -0
  179. /package/docs/{investigations → archive/investigations}/hlg-runs/runAC-peak-e4b-partial.log +0 -0
  180. /package/docs/{investigations → archive/investigations}/hlg-runs/runAD-ab-samples.log +0 -0
  181. /package/docs/{investigations → archive/investigations}/hlg-runs/runAE-edge-e4b.log +0 -0
  182. /package/docs/{investigations → archive/investigations}/hlg-runs/runAF-logit-calibration.log +0 -0
  183. /package/docs/{investigations → archive/investigations}/hlg-runs/runAG-eval-config2.log +0 -0
  184. /package/docs/{investigations → archive/investigations}/hlg-runs/runB-gain-variants.log +0 -0
  185. /package/docs/{investigations → archive/investigations}/hlg-runs/runC-pivots.log +0 -0
  186. /package/docs/{investigations → archive/investigations}/hlg-runs/runD-oetf.log +0 -0
  187. /package/docs/{investigations → archive/investigations}/hlg-runs/runE-eotf.log +0 -0
  188. /package/docs/{investigations → archive/investigations}/hlg-runs/runF-shaper-working.log +0 -0
  189. /package/docs/{investigations → archive/investigations}/hlg-runs/runG-shaper-sweep.log +0 -0
  190. /package/docs/{investigations → archive/investigations}/hlg-runs/runH-wsweep.log +0 -0
  191. /package/docs/{investigations → archive/investigations}/hlg-runs/runI-asweep.log +0 -0
  192. /package/docs/{investigations → archive/investigations}/hlg-runs/runJ-knobmap.log +0 -0
  193. /package/docs/{investigations → archive/investigations}/hlg-runs/runK-frontier.log +0 -0
  194. /package/docs/{investigations → archive/investigations}/hlg-runs/runL-widemap.log +0 -0
  195. /package/docs/{investigations → archive/investigations}/hlg-runs/runM-frontier-emb.log +0 -0
  196. /package/docs/{investigations → archive/investigations}/hlg-runs/runN-verify.log +0 -0
  197. /package/docs/{investigations → archive/investigations}/hlg-runs/runO-frontier-seed5000.log +0 -0
  198. /package/docs/{investigations → archive/investigations}/hlg-runs/runP-grid-AxSm.log +0 -0
  199. /package/docs/{investigations → archive/investigations}/hlg-runs/runQ-grid-tgW.log +0 -0
  200. /package/docs/{investigations → archive/investigations}/hlg-runs/runR-blind-transcript.log +0 -0
  201. /package/docs/{investigations → archive/investigations}/hlg-runs/runS-cross-coherence.log +0 -0
  202. /package/docs/{investigations → archive/investigations}/hlg-runs/runT-cross-cpm5.log +0 -0
  203. /package/docs/{investigations → archive/investigations}/hlg-runs/runU-cross-12b.log +0 -0
  204. /package/docs/{investigations → archive/investigations}/hlg-runs/runV-lw-e4b.log +0 -0
  205. /package/docs/{investigations → archive/investigations}/hlg-runs/runW-lw-12b.log +0 -0
  206. /package/docs/{investigations → archive/investigations}/hlg-runs/runX-lw-cpm5.log +0 -0
  207. /package/docs/{investigations → archive/investigations}/hlg-runs/runY-cliff-e4b.log +0 -0
  208. /package/docs/{investigations → archive/investigations}/hlg-runs/runZ-cliff-12b.log +0 -0
  209. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  210. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  211. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  212. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  213. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  214. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  215. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  216. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  217. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  218. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  219. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  220. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  221. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  222. /package/docs/{planning → archive/planning}/journal.md +0 -0
  223. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  224. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  225. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  226. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
package/src/sampler.ts CHANGED
@@ -112,6 +112,75 @@ export type Sampler = (logprobs: MlxArray, step: number) => MlxArray;
112
112
  /** (deviceTokens [n] | null, logits [1, V]) → logits [1, V]. */
113
113
  export type LogitsProcessor = (tokens: MlxArray | null, logits: MlxArray) => MlxArray;
114
114
 
115
+ /** Optional arrays captured from the same post-processor logprobs used to
116
+ * sample a token. The caller controls readback timing and owns disposal. */
117
+ export interface StepExtras {
118
+ sel: MlxArray | null;
119
+ topIdx: MlxArray | null;
120
+ topVals: MlxArray | null;
121
+ }
122
+
123
+ export interface StepSample<T> {
124
+ token: T;
125
+ extras: StepExtras | null;
126
+ }
127
+
128
+ interface StepGrammar {
129
+ readonly isTerminated: boolean;
130
+ ready(): Promise<void>;
131
+ applyMask(logits: MlxArray): MlxArray;
132
+ accept(token: number): void;
133
+ }
134
+
135
+ export interface StepSamplerOptions extends SamplerOptions, LogitsProcessorOptions {
136
+ grammar?: StepGrammar | null;
137
+ }
138
+
139
+ interface StepSamplerConfigBase {
140
+ /** The grammar owner already awaited ready(), or this sampler must do it. */
141
+ grammarWait: "external" | "before-sample";
142
+ /** Update processor history in sample(), or let the decode loop commit later. */
143
+ historyUpdate: "after-sample" | "manual";
144
+ initialHistory?: readonly number[];
145
+ captureSelectedLogprob?: boolean;
146
+ captureTopLogprobs?: number;
147
+ /** Eager-number mode can advance grammar after sampling. */
148
+ acceptGrammar?: boolean;
149
+ eosTokenIds?: readonly number[];
150
+ /** Reuse a sampler already shared with a speculative draft source. */
151
+ sampler?: Sampler;
152
+ }
153
+
154
+ export interface DeviceStepSamplerConfig extends StepSamplerConfigBase {
155
+ tokenRepresentation: "device";
156
+ grammarWait: "external";
157
+ }
158
+
159
+ export interface NumberStepSamplerConfig extends StepSamplerConfigBase {
160
+ tokenRepresentation: "number";
161
+ grammarWait: "before-sample";
162
+ }
163
+
164
+ export interface DeviceStepSampler {
165
+ readonly isPlainGreedy: boolean;
166
+ readonly needsHistory: boolean;
167
+ sample(logits: MlxArray, step: number): StepSample<MlxArray>;
168
+ seedHistory(tokens: readonly number[]): void;
169
+ commitDevice(token: MlxArray): void;
170
+ commitNumbers(tokens: readonly number[]): void;
171
+ dispose(): void;
172
+ }
173
+
174
+ export interface NumberStepSampler {
175
+ readonly isPlainGreedy: boolean;
176
+ readonly needsHistory: boolean;
177
+ sample(logits: MlxArray, step: number): Promise<StepSample<number>>;
178
+ seedHistory(tokens: readonly number[]): void;
179
+ commitDevice(token: MlxArray): void;
180
+ commitNumbers(tokens: readonly number[]): void;
181
+ dispose(): void;
182
+ }
183
+
115
184
  const GOLDEN = 0x9e3779b97f4a7c15n;
116
185
 
117
186
  function stepKey(seed: number, step: number): MlxArray {
@@ -717,6 +786,209 @@ export function makeLogitsProcessors(opts: LogitsProcessorOptions = {}): LogitsP
717
786
  return out;
718
787
  }
719
788
 
789
+ /** Whether a whole [B,V] row can use the scheduler's vectorized argmax path. */
790
+ export function isPlainGreedy(
791
+ opts: StepSamplerOptions,
792
+ hasProcessors = makeLogitsProcessors(opts).length > 0,
793
+ ): boolean {
794
+ return (opts.temperature ?? 0) === 0 && !opts.curve && !hasProcessors && !opts.grammar;
795
+ }
796
+
797
+ export function stepExtrasArrays(extras: StepExtras | null): MlxArray[] {
798
+ return extras
799
+ ? [extras.sel, extras.topIdx, extras.topVals].filter(
800
+ (array): array is MlxArray => array !== null,
801
+ )
802
+ : [];
803
+ }
804
+
805
+ export function disposeStepExtras(extras: StepExtras | null): void {
806
+ if (!extras) return;
807
+ extras.sel?.dispose();
808
+ extras.topIdx?.dispose();
809
+ extras.topVals?.dispose();
810
+ }
811
+
812
+ export function makeStepSampler(
813
+ options: StepSamplerOptions,
814
+ config: DeviceStepSamplerConfig,
815
+ ): DeviceStepSampler;
816
+ export function makeStepSampler(
817
+ options: StepSamplerOptions,
818
+ config: NumberStepSamplerConfig,
819
+ ): NumberStepSampler;
820
+ export function makeStepSampler(
821
+ options: StepSamplerOptions,
822
+ config: DeviceStepSamplerConfig | NumberStepSamplerConfig,
823
+ ): DeviceStepSampler | NumberStepSampler {
824
+ const sampler = config.sampler ?? makeSampler(options);
825
+ const processors = makeLogitsProcessors(options);
826
+ const grammar = options.grammar ?? null;
827
+ const captureSelected = config.captureSelectedLogprob === true;
828
+ const captureTop = Math.max(0, config.captureTopLogprobs ?? 0);
829
+ const capture = captureSelected || captureTop > 0;
830
+ let history: MlxArray | null = null;
831
+
832
+ const seedHistory = (tokens: readonly number[]): void => {
833
+ if (processors.length === 0) return;
834
+ const next = ops.fromInt32([...tokens], [tokens.length]);
835
+ const previous = history;
836
+ history = next;
837
+ previous?.dispose();
838
+ };
839
+
840
+ const commitDevice = (token: MlxArray): void => {
841
+ if (processors.length === 0) return;
842
+ const one = ops.reshape(token, [1]);
843
+ if (!history) {
844
+ history = one;
845
+ return;
846
+ }
847
+ const previous = history;
848
+ try {
849
+ history = ops.concatAxis([previous, one], 0);
850
+ previous.dispose();
851
+ } finally {
852
+ one.dispose();
853
+ }
854
+ };
855
+
856
+ const commitNumbers = (tokens: readonly number[]): void => {
857
+ if (tokens.length === 0 || processors.length === 0) return;
858
+ const next = ops.fromInt32([...tokens], [tokens.length]);
859
+ if (!history) {
860
+ history = next;
861
+ return;
862
+ }
863
+ const previous = history;
864
+ try {
865
+ history = ops.concatAxis([previous, next], 0);
866
+ previous.dispose();
867
+ } finally {
868
+ next.dispose();
869
+ }
870
+ };
871
+
872
+ const sampleDevice = (input: MlxArray, step: number): StepSample<MlxArray> => {
873
+ const vocab = input.shape[input.shape.length - 1]!;
874
+ let current = input.shape.length === 2 ? input : ops.reshape(input, [1, vocab]);
875
+ let ownsCurrent = current !== input;
876
+ let logprobs: MlxArray | null = null;
877
+ let token: MlxArray | null = null;
878
+ let extras: StepExtras | null = null;
879
+ try {
880
+ for (const processor of processors) {
881
+ const next = processor(history, current);
882
+ if (ownsCurrent && next !== current) current.dispose();
883
+ current = next;
884
+ ownsCurrent = current !== input;
885
+ }
886
+ if (grammar && !grammar.isTerminated) {
887
+ const next = grammar.applyMask(current);
888
+ if (ownsCurrent && next !== current) current.dispose();
889
+ current = next;
890
+ ownsCurrent = current !== input;
891
+ }
892
+ logprobs = toLogprobs(current);
893
+ if (ownsCurrent) current.dispose();
894
+ current = input;
895
+ ownsCurrent = false;
896
+ token = sampler(logprobs, step);
897
+
898
+ if (capture) {
899
+ extras = { sel: null, topIdx: null, topVals: null };
900
+ if (captureSelected) {
901
+ const idx = ops.reshape(token, [1, 1]);
902
+ try {
903
+ extras.sel = ops.takeAlongAxis(logprobs, idx, -1);
904
+ } finally {
905
+ idx.dispose();
906
+ }
907
+ }
908
+ if (captureTop > 0) {
909
+ const k = Math.min(captureTop, vocab);
910
+ let neg: MlxArray | null = null;
911
+ let partition: MlxArray | null = null;
912
+ let view: MlxArray | null = null;
913
+ try {
914
+ neg = ops.neg(logprobs);
915
+ partition = ops.argpartitionAxis(neg, k - 1, -1);
916
+ view = partition.slice([0, 0], [1, k]);
917
+ extras.topIdx = ops.contiguous(view);
918
+ extras.topVals = ops.takeAlongAxis(logprobs, extras.topIdx, -1);
919
+ } finally {
920
+ neg?.dispose();
921
+ partition?.dispose();
922
+ view?.dispose();
923
+ }
924
+ }
925
+ }
926
+
927
+ logprobs.dispose();
928
+ logprobs = null;
929
+ const result = { token, extras };
930
+ token = null;
931
+ extras = null;
932
+ return result;
933
+ } finally {
934
+ if (ownsCurrent) current.dispose();
935
+ logprobs?.dispose();
936
+ token?.dispose();
937
+ disposeStepExtras(extras);
938
+ }
939
+ };
940
+
941
+ if (config.initialHistory) seedHistory(config.initialHistory);
942
+
943
+ const common = {
944
+ isPlainGreedy: isPlainGreedy(options, processors.length > 0),
945
+ needsHistory: processors.length > 0,
946
+ seedHistory,
947
+ commitDevice,
948
+ commitNumbers,
949
+ dispose: () => {
950
+ history?.dispose();
951
+ history = null;
952
+ },
953
+ };
954
+
955
+ if (config.tokenRepresentation === "device") {
956
+ return {
957
+ ...common,
958
+ sample(logits, step) {
959
+ const result = sampleDevice(logits, step);
960
+ if (config.historyUpdate === "after-sample") commitDevice(result.token);
961
+ return result;
962
+ },
963
+ };
964
+ }
965
+
966
+ return {
967
+ ...common,
968
+ async sample(logits, step) {
969
+ if (grammar && !grammar.isTerminated) await grammar.ready();
970
+ const result = sampleDevice(logits, step);
971
+ let tokenArray: MlxArray | null = result.token;
972
+ try {
973
+ // Read the sampler's integer result directly. Adding an astype here
974
+ // would append work behind the next dispatched decode step.
975
+ const token = ops.itemUint32(tokenArray);
976
+ tokenArray.dispose();
977
+ tokenArray = null;
978
+ if (config.acceptGrammar && grammar && !config.eosTokenIds?.includes(token))
979
+ grammar.accept(token);
980
+ if (config.historyUpdate === "after-sample") commitNumbers([token]);
981
+ return { token, extras: result.extras };
982
+ } catch (error) {
983
+ disposeStepExtras(result.extras);
984
+ throw error;
985
+ } finally {
986
+ tokenArray?.dispose();
987
+ }
988
+ },
989
+ };
990
+ }
991
+
720
992
  /** logits [1, V] → logprobs [1, V] (logits - logsumexp). */
721
993
  export function toLogprobs(logits: MlxArray): MlxArray {
722
994
  const lse = ops.logsumexpAxis(logits, -1, true);
@@ -0,0 +1,184 @@
1
+ import type { JobStore } from "../jobs";
2
+
3
+ export interface AdminRouteDeps {
4
+ ensureJobs(): Promise<JobStore>;
5
+ invalidateLibrary(): void;
6
+ }
7
+
8
+ export type AdminRoute =
9
+ | { kind: "hf-token-get" }
10
+ | { kind: "hf-token-set" }
11
+ | { kind: "tool-approvals-get" }
12
+ | { kind: "tool-approvals-delete" }
13
+ | { kind: "push"; artifactKind: "quantize" | "finetune" | "dataset" }
14
+ | { kind: "gc-plan" }
15
+ | { kind: "gc-execute" }
16
+ | { kind: "jobs-list" }
17
+ | { kind: "job-get"; jobId: string; stream: boolean };
18
+
19
+ export function matchAdminRoute(method: string, pathname: string): AdminRoute | null {
20
+ switch (`${method} ${pathname}`) {
21
+ case "GET /api/settings/hf-token": return { kind: "hf-token-get" };
22
+ case "POST /api/settings/hf-token": return { kind: "hf-token-set" };
23
+ case "GET /api/settings/tool-approvals": return { kind: "tool-approvals-get" };
24
+ case "DELETE /api/settings/tool-approvals": return { kind: "tool-approvals-delete" };
25
+ case "GET /api/gc/plan": return { kind: "gc-plan" };
26
+ case "POST /api/gc/execute": return { kind: "gc-execute" };
27
+ case "GET /api/jobs": return { kind: "jobs-list" };
28
+ }
29
+
30
+ if (method === "POST") {
31
+ const push = pathname.match(/^\/api\/(quantize|finetune|dataset)\/push$/);
32
+ if (push) {
33
+ return {
34
+ kind: "push",
35
+ artifactKind: push[1] as "quantize" | "finetune" | "dataset",
36
+ };
37
+ }
38
+ }
39
+
40
+ if (method === "GET") {
41
+ const job = pathname.match(/^\/api\/jobs\/([^/]+?)(\/stream)?$/);
42
+ if (job) {
43
+ return {
44
+ kind: "job-get",
45
+ jobId: job[1]!,
46
+ stream: !!job[2],
47
+ };
48
+ }
49
+ }
50
+
51
+ return null;
52
+ }
53
+
54
+ export async function handleAdminRoute(
55
+ url: URL,
56
+ request: Request,
57
+ deps: AdminRouteDeps,
58
+ ): Promise<Response | null> {
59
+ const route = matchAdminRoute(request.method, url.pathname);
60
+ if (!route) return null;
61
+
62
+ switch (route.kind) {
63
+ case "hf-token-get": {
64
+ const { hasHfToken } = await import("../hf-push");
65
+ return Response.json({ ok: true, hasToken: hasHfToken() });
66
+ }
67
+ case "hf-token-set": {
68
+ const body = (await request.json().catch(() => ({}))) as { token?: string };
69
+ if (!body.token)
70
+ return Response.json({ ok: false, error: "token required" }, { status: 400 });
71
+ const { saveHfToken } = await import("../hf-push");
72
+ saveHfToken(body.token);
73
+ return Response.json({ ok: true });
74
+ }
75
+ case "tool-approvals-get": {
76
+ const { listAlwaysAllowedTools } = await import("../tool-approvals");
77
+ return Response.json({ ok: true, alwaysAllow: listAlwaysAllowedTools() });
78
+ }
79
+ case "tool-approvals-delete": {
80
+ const body = (await request.json().catch(() => ({}))) as { tool?: string };
81
+ if (!body.tool)
82
+ return Response.json({ ok: false, error: "tool required" }, { status: 400 });
83
+ const { revokeToolAlwaysAllowed } = await import("../tool-approvals");
84
+ const file = revokeToolAlwaysAllowed(body.tool);
85
+ return Response.json({ ok: true, alwaysAllow: Object.keys(file.allows).sort() });
86
+ }
87
+ case "push": {
88
+ const body = (await request.json().catch(() => ({}))) as {
89
+ job_id?: string;
90
+ repo_id?: string;
91
+ private?: boolean;
92
+ source_path?: string;
93
+ };
94
+ if (!body.repo_id)
95
+ return Response.json({ ok: false, error: "repo_id required" }, { status: 400 });
96
+ const { getHfToken, uploadFolder } = await import("../hf-push");
97
+ const token = getHfToken();
98
+ if (!token) {
99
+ return Response.json(
100
+ { ok: false, error: "no HF token saved — add one in Settings → Hugging Face" },
101
+ { status: 400 },
102
+ );
103
+ }
104
+ const store = await deps.ensureJobs();
105
+ let dir = body.source_path;
106
+ if (!dir && body.job_id) dir = store.get(body.job_id)?.output_path ?? undefined;
107
+ if (!dir) {
108
+ return Response.json(
109
+ { ok: false, error: "no source dir (pass job_id or source_path)" },
110
+ { status: 400 },
111
+ );
112
+ }
113
+ try {
114
+ const result = await uploadFolder(dir, body.repo_id, {
115
+ repoType: route.artifactKind === "dataset" ? "dataset" : "model",
116
+ private: !!body.private,
117
+ token,
118
+ });
119
+ return Response.json({ ok: true, url: result.url });
120
+ } catch (error) {
121
+ return Response.json({ ok: false, error: (error as Error).message }, { status: 400 });
122
+ }
123
+ }
124
+ case "gc-plan": {
125
+ const { planGc } = await import("../registry");
126
+ const plans = planGc().filter(
127
+ (plan) => plan.pruneSnapshots.length || plan.skippedSnapshots.length || plan.deadBlobs.length,
128
+ );
129
+ const superseded = plans.map((plan) => ({
130
+ repo_id: plan.repoId,
131
+ prune_snapshots: plan.pruneSnapshots.length,
132
+ skipped_snapshots: plan.skippedSnapshots.length,
133
+ dead_blobs: plan.deadBlobs.length,
134
+ reclaim_bytes: plan.reclaimBytes,
135
+ }));
136
+ const reclaim_bytes = plans.reduce((total, plan) => total + plan.reclaimBytes, 0);
137
+ return Response.json({ ok: true, superseded, reclaim_bytes });
138
+ }
139
+ case "gc-execute": {
140
+ const body = (await request.json().catch(() => ({}))) as { yes?: boolean };
141
+ if (body.yes !== true) {
142
+ return Response.json(
143
+ { ok: false, error: "pass {\"yes\": true} to confirm deletion" },
144
+ { status: 400 },
145
+ );
146
+ }
147
+ const { planGc, executeGc, Registry } = await import("../registry");
148
+ const plans = planGc().filter(
149
+ (plan) => plan.pruneSnapshots.length || plan.skippedSnapshots.length || plan.deadBlobs.length,
150
+ );
151
+ const result = executeGc(plans);
152
+ const registry = new Registry();
153
+ try {
154
+ await registry.scan();
155
+ } finally {
156
+ registry.close();
157
+ }
158
+ deps.invalidateLibrary();
159
+ return Response.json({
160
+ ok: true,
161
+ snapshots: result.snapshots,
162
+ blobs: result.blobs,
163
+ reclaimed_bytes: result.reclaimedBytes,
164
+ });
165
+ }
166
+ case "jobs-list": {
167
+ const store = await deps.ensureJobs();
168
+ const limit = Number(url.searchParams.get("limit") ?? "50");
169
+ const kind = url.searchParams.get("kind") ?? undefined;
170
+ return Response.json({ ok: true, jobs: store.recent(limit, kind) });
171
+ }
172
+ case "job-get": {
173
+ const store = await deps.ensureJobs();
174
+ if (route.stream) {
175
+ const { streamJobResponse } = await import("../jobs");
176
+ return streamJobResponse(store, route.jobId);
177
+ }
178
+ const job = store.get(route.jobId);
179
+ if (!job)
180
+ return Response.json({ ok: false, error: "job not found" }, { status: 404 });
181
+ return Response.json({ ok: true, job });
182
+ }
183
+ }
184
+ }
@@ -0,0 +1,132 @@
1
+ export type AuxiliaryRoute =
2
+ | "memory-status"
3
+ | "memory-list"
4
+ | "memory-search"
5
+ | "memory-article"
6
+ | "memory-links"
7
+ | "memory-history"
8
+ | "memory-diff"
9
+ | "memory-init"
10
+ | "memory-synthesize"
11
+ | "hub-local"
12
+ | "hub-search"
13
+ | "hub-download"
14
+ | "hub-serve"
15
+ | "sessions-search"
16
+ | "sessions-export";
17
+
18
+ /** Match request-only routes that do not depend on the loaded model or
19
+ * createServer's live generation state. */
20
+ export function matchAuxiliaryRoute(method: string, pathname: string): AuxiliaryRoute | null {
21
+ switch (`${method} ${pathname}`) {
22
+ case "GET /api/memory/status": return "memory-status";
23
+ case "GET /api/memory/list": return "memory-list";
24
+ case "GET /api/memory/search": return "memory-search";
25
+ case "GET /api/memory/article": return "memory-article";
26
+ case "GET /api/memory/links": return "memory-links";
27
+ case "GET /api/memory/history": return "memory-history";
28
+ case "GET /api/memory/diff": return "memory-diff";
29
+ case "POST /api/memory/init": return "memory-init";
30
+ case "GET /v1/memory/synthesize": return "memory-synthesize";
31
+ case "GET /api/hub/local": return "hub-local";
32
+ case "GET /api/hub/search": return "hub-search";
33
+ case "POST /api/hub/download": return "hub-download";
34
+ case "POST /api/hub/serve": return "hub-serve";
35
+ case "GET /api/sessions/search": return "sessions-search";
36
+ case "GET /api/sessions/export": return "sessions-export";
37
+ default: return null;
38
+ }
39
+ }
40
+
41
+ export async function handleAuxiliaryRoute(
42
+ url: URL,
43
+ request: Request,
44
+ ): Promise<Response | null> {
45
+ switch (matchAuxiliaryRoute(request.method, url.pathname)) {
46
+ case "memory-status": {
47
+ const { handleMemoryStatus } = await import("../memory/rest");
48
+ return handleMemoryStatus();
49
+ }
50
+ case "memory-list": {
51
+ const { handleMemoryList } = await import("../memory/rest");
52
+ return handleMemoryList();
53
+ }
54
+ case "memory-search": {
55
+ const { handleMemorySearch } = await import("../memory/rest");
56
+ return handleMemorySearch(url);
57
+ }
58
+ case "memory-article": {
59
+ const { handleMemoryArticle } = await import("../memory/rest");
60
+ return handleMemoryArticle(url);
61
+ }
62
+ case "memory-links": {
63
+ const { handleMemoryLinks } = await import("../memory/rest");
64
+ return handleMemoryLinks(url);
65
+ }
66
+ case "memory-history": {
67
+ const { handleMemoryHistory } = await import("../memory/rest");
68
+ return handleMemoryHistory(url);
69
+ }
70
+ case "memory-diff": {
71
+ const { handleMemoryDiff } = await import("../memory/rest");
72
+ return handleMemoryDiff(url);
73
+ }
74
+ case "memory-init": {
75
+ const { handleMemoryInit } = await import("../memory/rest");
76
+ return handleMemoryInit(request);
77
+ }
78
+ case "memory-synthesize": {
79
+ const dryRun = url.searchParams.get("dry") === "1";
80
+ const { runSynthesis } = await import("../memory/pipeline");
81
+ const encoder = new TextEncoder();
82
+ const stream = new ReadableStream({
83
+ async start(controller) {
84
+ const send = (event: unknown) =>
85
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
86
+ try {
87
+ const summary = await runSynthesis({ dryRun }, (event) => send(event));
88
+ send({ type: "summary", ...summary });
89
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
90
+ } catch (error) {
91
+ send({ type: "error", message: (error as Error).message });
92
+ } finally {
93
+ controller.close();
94
+ }
95
+ },
96
+ });
97
+ return new Response(stream, {
98
+ headers: {
99
+ "content-type": "text/event-stream",
100
+ "cache-control": "no-cache",
101
+ connection: "keep-alive",
102
+ },
103
+ });
104
+ }
105
+ case "hub-local": {
106
+ const { handleHubLocal } = await import("../hub-rest");
107
+ return handleHubLocal();
108
+ }
109
+ case "hub-search": {
110
+ const { handleHubSearch } = await import("../hub-rest");
111
+ return handleHubSearch(url);
112
+ }
113
+ case "hub-download": {
114
+ const { handleHubDownload } = await import("../hub-rest");
115
+ return handleHubDownload(request);
116
+ }
117
+ case "hub-serve": {
118
+ const { handleHubServe } = await import("../hub-rest");
119
+ return handleHubServe(request);
120
+ }
121
+ case "sessions-search": {
122
+ const { handleSessionsSearch } = await import("./session-search");
123
+ return handleSessionsSearch(url);
124
+ }
125
+ case "sessions-export": {
126
+ const { handleSessionsExport } = await import("./session-search");
127
+ return handleSessionsExport(url);
128
+ }
129
+ default:
130
+ return null;
131
+ }
132
+ }