mlx-bun 0.4.0 → 1.0.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.
- package/README.md +1453 -88
- package/THIRD_PARTY_NOTICES.md +882 -0
- package/bin/mlx-bun.convert.mjs +4 -0
- package/bin/mlx-bun.fuse.mjs +4 -0
- package/bin/mlx-bun.generate.mjs +4 -0
- package/bin/mlx-bun.lora.mjs +4 -0
- package/bin/mlx-bun.mjs +13 -21
- package/bin/mlx-bun.server.mjs +4 -0
- package/bin/mlx-bun.upload.mjs +4 -0
- package/dist/web/app.js +9584 -0
- package/package.json +50 -53
- package/src/cli/ane-install.ts +76 -0
- package/src/cli/args.ts +179 -0
- package/src/cli/engine-entry.ts +68 -0
- package/src/cli/inference.ts +214 -0
- package/src/cli/job-entry.ts +97 -0
- package/src/cli/library-host.ts +117 -0
- package/src/cli/main.ts +57 -0
- package/src/cli/memory-engine.ts +163 -0
- package/src/cli/memory.ts +36 -0
- package/src/cli/mlx-lm-aliases.ts +309 -0
- package/src/cli/model-choice.ts +33 -0
- package/src/cli/model-selection.ts +84 -0
- package/src/cli/module-host.ts +11 -0
- package/src/cli/module-verbs.ts +50 -0
- package/src/cli/numerical-policy.ts +12 -0
- package/src/cli/perplexity.ts +89 -0
- package/src/cli/serve-host.ts +423 -0
- package/src/cli/serve-isolated.ts +217 -0
- package/src/cli/serve-options.ts +106 -0
- package/src/cli/serve-state.ts +205 -0
- package/src/cli/serve.ts +500 -0
- package/src/cli/served-model-host.ts +102 -0
- package/src/cli/server-entry.ts +148 -0
- package/src/cli/serving-unit.ts +177 -0
- package/src/{tui.ts → cli/terminal.ts} +13 -8
- package/src/cli/worker-adapters.ts +34 -0
- package/src/cli/worker-entry.ts +272 -0
- package/src/cli/worker-unit.ts +179 -0
- package/src/engine/adapter-operation.ts +26 -0
- package/src/engine/cache-services.ts +114 -0
- package/src/engine/completion.ts +39 -0
- package/src/engine/generation-gateway.ts +358 -0
- package/src/engine/index.ts +59 -0
- package/src/engine/kv-budget.ts +120 -0
- package/src/engine/media-preparation.ts +232 -0
- package/src/engine/model-binding.ts +24 -0
- package/src/engine/model-host.ts +513 -0
- package/src/engine/model-serving.ts +89 -0
- package/src/{serve → engine}/preparation.ts +10 -8
- package/src/{serve → engine}/session-completion-engine.ts +6 -6
- package/src/engine/telemetry.ts +109 -0
- package/src/hub/downloads.ts +116 -0
- package/src/jobs/db.ts +16 -7
- package/src/jobs/events.ts +29 -0
- package/src/jobs/executable.ts +5 -0
- package/src/jobs/host.ts +51 -0
- package/src/jobs/in-process.ts +55 -0
- package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
- package/src/jobs/runner.ts +150 -231
- package/src/jobs/service.ts +75 -0
- package/src/jobs/sse.ts +59 -23
- package/src/jobs/worker-memory.ts +46 -0
- package/src/jobs/worker-process.ts +163 -0
- package/src/jobs/worker-supervisor.ts +258 -0
- package/src/modules.ts +58 -0
- package/src/publishing/catalog-hub.ts +18 -0
- package/src/publishing/credentials.ts +31 -0
- package/src/publishing/upload.ts +37 -0
- package/src/residency/leased-adapters.ts +21 -0
- package/src/residency/local-records.ts +16 -0
- package/src/residency/model-residency.ts +405 -0
- package/src/residency/record-index.ts +28 -0
- package/src/residency/resident-estimate.ts +50 -0
- package/src/{anthropic.ts → server/anthropic.ts} +3 -81
- package/src/server/cache-routes.ts +36 -0
- package/src/{serve → server}/chat-request.ts +12 -10
- package/src/{serve → server}/chat-stage.ts +27 -22
- package/src/{client.ts → server/client.ts} +28 -2
- package/src/{serve → server}/completion-executor.ts +42 -42
- package/src/server/decision-body.ts +55 -0
- package/src/server/decision-routes.ts +134 -0
- package/src/server/decision-wire.ts +228 -0
- package/src/{serve → server}/discovery-routes.ts +42 -83
- package/src/server/generated-token-history.ts +57 -0
- package/src/{serve → server}/http.ts +25 -13
- package/src/{serve → server}/inference-request.ts +5 -5
- package/src/server/job-routes.ts +20 -0
- package/src/server/media-prompt.ts +63 -0
- package/src/server/memory-completion-client.ts +46 -0
- package/src/server/model-routes.ts +192 -0
- package/src/{serve → server}/openai-wire.ts +3 -2
- package/src/{serve → server}/pipeline.ts +1 -1
- package/src/server/prompt-builder.ts +13 -0
- package/src/server/prompt-contracts.ts +17 -0
- package/src/server/proxy-routes.ts +187 -0
- package/src/server/publishing-routes.ts +41 -0
- package/src/{serve → server}/request-plan.ts +8 -8
- package/src/{serve → server}/request-prep.ts +82 -68
- package/src/{serve → server}/responses-client.ts +30 -13
- package/src/{responses.ts → server/responses.ts} +12 -93
- package/src/server/routes.ts +205 -0
- package/src/server/start.ts +72 -0
- package/src/server/status-routes.ts +108 -0
- package/src/{serve → server}/text-completion-stage.ts +25 -4
- package/src/server/text-prompt.ts +12 -0
- package/src/{serve → server}/token-streams.ts +36 -38
- package/src/server/worker-routes.ts +311 -0
- package/src/storage/paths.ts +61 -0
- package/src/web/assets.ts +37 -0
- package/src/web/{src → browser}/api.ts +5 -23
- package/src/web/browser/chat-host.ts +17 -0
- package/src/web/browser/installed-panels.ts +6 -0
- package/src/web/browser/main.ts +55 -0
- package/src/web/browser/markdown.ts +279 -0
- package/src/web/browser/memory-panel.ts +9 -0
- package/src/web/browser/palette.ts +64 -0
- package/src/web/browser/protocol.ts +13 -0
- package/src/web/browser/shell.ts +467 -0
- package/src/web/{src → browser}/status.ts +32 -36
- package/src/web/build.ts +51 -0
- package/src/web/public/app.html +1054 -0
- package/src/web/{sw.js → public/sw.js} +4 -3
- package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
- package/src/web/{vendor → public/vendor}/hljs.js +2 -2
- package/THIRD_PARTY_LICENSES.md +0 -51
- package/docs/README.md +0 -94
- package/docs/archive/hlg-sampling.md +0 -350
- package/docs/archive/investigations/audio-input-plan.md +0 -356
- package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
- package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
- package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
- package/docs/archive/investigations/curve-bisector-routing.md +0 -123
- package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
- package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
- package/docs/archive/investigations/dspark-handoff.md +0 -224
- package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
- package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
- package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
- package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
- package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
- package/docs/archive/investigations/lab-build-journal.md +0 -157
- package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
- package/docs/archive/investigations/openjarvis-fit.md +0 -198
- package/docs/archive/investigations/openwiki-evaluation.md +0 -249
- package/docs/archive/investigations/optimization_plan.md +0 -346
- package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
- package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
- package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
- package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
- package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
- package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
- package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
- package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
- package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
- package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
- package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
- package/docs/archive/planning/chat-ui-vision.md +0 -50
- package/docs/archive/planning/chunk-task-roadmap.md +0 -77
- package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
- package/docs/archive/planning/journal.md +0 -316
- package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
- package/docs/archive/planning/pi-integration-review.md +0 -178
- package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
- package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
- package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
- package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
- package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
- package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
- package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
- package/docs/archive/planning/web-ui-pass-plan.md +0 -213
- package/docs/archive/planning/website-readme-pass-plan.md +0 -224
- package/docs/design/batching.md +0 -1841
- package/docs/design/decode-speed-program.md +0 -2349
- package/docs/design/dreaming-nightly-pipeline.md +0 -684
- package/docs/design/generic-model-support.md +0 -705
- package/docs/design/kv-cache.md +0 -824
- package/docs/design/orpo-training.md +0 -680
- package/docs/design/speculative-decoding.md +0 -1990
- package/docs/design/turboquant.md +0 -2212
- package/docs/design/unified-engine-frontier-plan.md +0 -1880
- package/docs/design/web-chat-redesign.md +0 -695
- package/docs/planning/PRODUCT_ROADMAP.md +0 -594
- package/docs/planning/ResearchTopics.md +0 -119
- package/docs/planning/release-notes-v0.4.0.md +0 -45
- package/docs/planning/video-series.md +0 -421
- package/docs/reference/benchmarks.md +0 -3613
- package/docs/reference/cli.md +0 -585
- package/docs/reference/distribution.md +0 -355
- package/docs/reference/environment.md +0 -398
- package/docs/reference/glossary.md +0 -39
- package/docs/reference/library-api.md +0 -674
- package/docs/reference/memory.md +0 -260
- package/docs/reference/models.md +0 -334
- package/docs/reference/server-api.md +0 -1775
- package/docs/reference/server-config.md +0 -846
- package/docs/reference/training.md +0 -462
- package/docs/reference/troubleshooting.md +0 -158
- package/src/admission-plan.ts +0 -18
- package/src/audio/conformer.ts +0 -647
- package/src/audio/decode.ts +0 -133
- package/src/audio/features.ts +0 -202
- package/src/audio/transcode.ts +0 -67
- package/src/backends/mlx/assistant-target.ts +0 -46
- package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
- package/src/backends/mlx/autoregressive.ts +0 -108
- package/src/backends/mlx/batch-group.ts +0 -1790
- package/src/backends/mlx/cache-attachment.ts +0 -67
- package/src/backends/mlx/cache-layout.ts +0 -31
- package/src/backends/mlx/checkpoint-state.ts +0 -53
- package/src/backends/mlx/continuation-persistence.ts +0 -106
- package/src/backends/mlx/continuation-request.ts +0 -43
- package/src/backends/mlx/continuation.ts +0 -77
- package/src/backends/mlx/diffusion.ts +0 -39
- package/src/backends/mlx/draft-target.ts +0 -32
- package/src/backends/mlx/evaluation.ts +0 -105
- package/src/backends/mlx/fill-append.ts +0 -45
- package/src/backends/mlx/full-kv-row-append.ts +0 -32
- package/src/backends/mlx/full-kv-row-donor.ts +0 -54
- package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
- package/src/backends/mlx/gateway-binding.ts +0 -130
- package/src/backends/mlx/graph.ts +0 -38
- package/src/backends/mlx/kv-codec.ts +0 -9
- package/src/backends/mlx/kv-maintenance.ts +0 -133
- package/src/backends/mlx/kv-tensor-rows.ts +0 -212
- package/src/backends/mlx/memory-client.ts +0 -189
- package/src/backends/mlx/methods.ts +0 -90
- package/src/backends/mlx/model-prompt.ts +0 -164
- package/src/backends/mlx/model-serving.ts +0 -79
- package/src/backends/mlx/prefill-cohort.ts +0 -89
- package/src/backends/mlx/prefill-rows.ts +0 -200
- package/src/backends/mlx/prefill.ts +0 -33
- package/src/backends/mlx/projected-context-rows.ts +0 -72
- package/src/backends/mlx/request-policy.ts +0 -20
- package/src/backends/mlx/request-state-policy.ts +0 -41
- package/src/backends/mlx/rollback.ts +0 -89
- package/src/backends/mlx/row-sampling.ts +0 -49
- package/src/backends/mlx/serial-executor.ts +0 -242
- package/src/backends/mlx/speculative-group.ts +0 -359
- package/src/backends/mlx/speculative-round.ts +0 -161
- package/src/backends/mlx/speculative.ts +0 -114
- package/src/backends/mlx/state-rows.ts +0 -104
- package/src/backends/mlx/state-views.ts +0 -46
- package/src/backends/mlx/target-layout-capability.ts +0 -35
- package/src/backends/mlx/text-engine.ts +0 -73
- package/src/backends/mlx/transitioning-kv-rows.ts +0 -165
- package/src/chat-template.ts +0 -382
- package/src/cli.ts +0 -3168
- package/src/config.ts +0 -399
- package/src/contracts/completion.ts +0 -16
- package/src/contracts/execution.ts +0 -60
- package/src/contracts/generation.ts +0 -103
- package/src/contracts/host.ts +0 -7
- package/src/contracts/pi-provider.ts +0 -30
- package/src/contracts/pi.ts +0 -259
- package/src/contracts/prefix-cache.ts +0 -23
- package/src/contracts/resources.ts +0 -25
- package/src/contracts/sampling.ts +0 -23
- package/src/contracts/scheduling.ts +0 -45
- package/src/contracts/tsconfig.json +0 -14
- package/src/dataset/generators.ts +0 -1087
- package/src/dataset/index.ts +0 -12
- package/src/dataset/job.ts +0 -81
- package/src/dataset/llm.ts +0 -71
- package/src/dataset/registry.ts +0 -528
- package/src/diffusion/diffusion-generate.ts +0 -507
- package/src/download.ts +0 -505
- package/src/embed.ts +0 -77
- package/src/engine/admission.ts +0 -59
- package/src/engine/cancellation.ts +0 -38
- package/src/engine/engine.ts +0 -56
- package/src/engine/execution-coordinator.ts +0 -70
- package/src/engine/execution-plan.ts +0 -53
- package/src/engine/resources.ts +0 -53
- package/src/engine/scheduler.ts +0 -72
- package/src/engine/session.ts +0 -247
- package/src/eval/README.md +0 -130
- package/src/eval/ifeval.ts +0 -81
- package/src/eval/kl-prompts.ts +0 -29
- package/src/eval/kl.ts +0 -590
- package/src/eval/perplexity.ts +0 -164
- package/src/eval/quality-db.ts +0 -92
- package/src/eval/reference-logits.ts +0 -207
- package/src/eval/runner.ts +0 -124
- package/src/eval/score.ts +0 -49
- package/src/eval/tasks/bfcl.ts +0 -535
- package/src/eval/tasks/gsm8k.ts +0 -98
- package/src/eval/tasks/hashhop.ts +0 -139
- package/src/eval/tasks/humaneval.ts +0 -275
- package/src/eval/tasks/ifeval.ts +0 -525
- package/src/eval/tasks/mmlu.ts +0 -157
- package/src/eval/ultrafeedback.ts +0 -60
- package/src/evaldb.ts +0 -199
- package/src/expert-io.ts +0 -393
- package/src/expert-offload-build.ts +0 -77
- package/src/expert-offload.ts +0 -76
- package/src/expert-residency.ts +0 -1134
- package/src/expert-usage.ts +0 -415
- package/src/fill/echo-index.ts +0 -188
- package/src/fill/fill-session.ts +0 -452
- package/src/fill/proposal.ts +0 -68
- package/src/fill/schema-rows.ts +0 -506
- package/src/fill/tool-boundary.ts +0 -103
- package/src/fit.ts +0 -249
- package/src/generate.ts +0 -1346
- package/src/grammar.ts +0 -591
- package/src/harness-pi.ts +0 -184
- package/src/hf-push.ts +0 -628
- package/src/hub-rest.ts +0 -242
- package/src/index.ts +0 -63
- package/src/inference/denoising.ts +0 -17
- package/src/inference/draft-acceptance.ts +0 -41
- package/src/inference/draft-output.ts +0 -49
- package/src/inference/graph.ts +0 -33
- package/src/inference/prefill.ts +0 -39
- package/src/inference/rollback.ts +0 -7
- package/src/jobs/index.ts +0 -31
- package/src/jobs/job-entry.ts +0 -107
- package/src/jobs/task-client.ts +0 -19
- package/src/jobs/types.ts +0 -2
- package/src/kv-scheme.ts +0 -257
- package/src/kv-store.ts +0 -1099
- package/src/lab/README.md +0 -18
- package/src/lab/curve/curve-designer.html +0 -303
- package/src/lab/curve/curve-sampler.ts +0 -122
- package/src/lab/expert-trace/expert-trace.ts +0 -69
- package/src/lab/paged-kv/paged-kv-rows.ts +0 -83
- package/src/lab/paged-kv/paged-kv.ts +0 -270
- package/src/library.ts +0 -40
- package/src/lora.ts +0 -408
- package/src/md-modules.d.ts +0 -49
- package/src/media-fetch.ts +0 -442
- package/src/memory/article.ts +0 -558
- package/src/memory/chunk-validation.ts +0 -150
- package/src/memory/chunk.ts +0 -318
- package/src/memory/cluster.ts +0 -351
- package/src/memory/coverage.ts +0 -134
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +0 -253
- package/src/memory/entity.ts +0 -203
- package/src/memory/gate.ts +0 -187
- package/src/memory/history.ts +0 -152
- package/src/memory/ingest.ts +0 -335
- package/src/memory/model.ts +0 -183
- package/src/memory/normalize.ts +0 -671
- package/src/memory/parse.ts +0 -22
- package/src/memory/pipeline.ts +0 -275
- package/src/memory/prompts.ts +0 -65
- package/src/memory/query.ts +0 -334
- package/src/memory/reconcile.ts +0 -734
- package/src/memory/redirect.ts +0 -108
- package/src/memory/reindex.ts +0 -325
- package/src/memory/resolve.ts +0 -307
- package/src/memory/rest.ts +0 -245
- package/src/memory/route.ts +0 -272
- package/src/memory/schedule.ts +0 -165
- package/src/memory/stages.ts +0 -777
- package/src/memory/synthesize.ts +0 -1638
- package/src/memory/tools.ts +0 -749
- package/src/memory/vault.ts +0 -1194
- package/src/memory/wikify.ts +0 -1064
- package/src/mlx/array.ts +0 -388
- package/src/mlx/autograd.ts +0 -304
- package/src/mlx/checkpoint.ts +0 -149
- package/src/mlx/compile.ts +0 -168
- package/src/mlx/custom-vjp.ts +0 -188
- package/src/mlx/ffi.ts +0 -512
- package/src/mlx/materialize.ts +0 -23
- package/src/mlx/metal-kernel.ts +0 -159
- package/src/mlx/ops.ts +0 -1021
- package/src/mlx/quantized-rows.ts +0 -36
- package/src/mlx/token-bitmask.ts +0 -52
- package/src/mlx/turboquant-kv-decode.ts +0 -82
- package/src/mlx/turboquant-kv-inverse.ts +0 -122
- package/src/mlx/turboquant-ops.ts +0 -861
- package/src/mlx/turboquant-tables.ts +0 -237
- package/src/mmap.ts +0 -90
- package/src/model/artifact-identity.ts +0 -73
- package/src/model/batched-kv.ts +0 -235
- package/src/model/batched-mask.ts +0 -279
- package/src/model/batched-quant.ts +0 -212
- package/src/model/batched-quantized-kv.ts +0 -144
- package/src/model/batched-rotating-quant.ts +0 -353
- package/src/model/batched-rotating-state.ts +0 -149
- package/src/model/batched-rotating.ts +0 -338
- package/src/model/batched-row-storage.ts +0 -179
- package/src/model/batched-ssm.ts +0 -52
- package/src/model/batched-turboquant-kv.ts +0 -93
- package/src/model/compiled-decode.ts +0 -712
- package/src/model/decoded-kv-donor.ts +0 -12
- package/src/model/delayed-quantized-kv.ts +0 -72
- package/src/model/delayed-rotating-quantized-kv.ts +0 -93
- package/src/model/delayed-turboquant-kv.ts +0 -55
- package/src/model/diffusion-gemma.ts +0 -1026
- package/src/model/factory.ts +0 -189
- package/src/model/fingerprint.ts +0 -33
- package/src/model/flash-attention.ts +0 -694
- package/src/model/full-prefill-padding.ts +0 -56
- package/src/model/full-prefill-row.ts +0 -105
- package/src/model/gemma4-base.ts +0 -2351
- package/src/model/gemma4.ts +0 -1315
- package/src/model/generated/gemma4-12b.ts +0 -429
- package/src/model/generated/gemma4-26b.ts +0 -448
- package/src/model/generated/gemma4-e4b.ts +0 -717
- package/src/model/generated/index.ts +0 -18
- package/src/model/glm52-atlas.ts +0 -511
- package/src/model/glm52-cache.ts +0 -792
- package/src/model/glm52-config.ts +0 -262
- package/src/model/glm52-container.ts +0 -305
- package/src/model/glm52-coupling.ts +0 -388
- package/src/model/glm52-dsa.ts +0 -606
- package/src/model/glm52-expert-layout.ts +0 -253
- package/src/model/glm52-layout.ts +0 -152
- package/src/model/glm52-memory.ts +0 -500
- package/src/model/glm52-mla.ts +0 -792
- package/src/model/glm52-moe.ts +0 -223
- package/src/model/glm52-pilot.ts +0 -537
- package/src/model/glm52-reference.ts +0 -445
- package/src/model/glm52-residency.ts +0 -557
- package/src/model/glm52-resident-weights.ts +0 -135
- package/src/model/glm52-streamed-experts.ts +0 -453
- package/src/model/glm52-streamed-metal.ts +0 -610
- package/src/model/glm52-weights.ts +0 -503
- package/src/model/glm52.ts +0 -1132
- package/src/model/implementation.ts +0 -69
- package/src/model/kv-attention-view.ts +0 -67
- package/src/model/minicpm5.ts +0 -376
- package/src/model/profile.ts +0 -443
- package/src/model/qwen-append.ts +0 -12
- package/src/model/qwen-conv.ts +0 -52
- package/src/model/qwen3-delta-state.ts +0 -74
- package/src/model/qwen3-delta.ts +0 -451
- package/src/model/qwen3-moe.ts +0 -404
- package/src/model/qwen3-mrope.ts +0 -252
- package/src/model/qwen3.ts +0 -277
- package/src/model/qwen3_5-checkpoint.ts +0 -164
- package/src/model/qwen3_5.ts +0 -819
- package/src/model/rotating-kv-donor.ts +0 -35
- package/src/model/rotating-kv-layout.ts +0 -262
- package/src/model/rotating-row-transaction.ts +0 -35
- package/src/model/speculative-rotating-kv.ts +0 -101
- package/src/model/ssm-prefill-padding.ts +0 -39
- package/src/model/support.ts +0 -102
- package/src/model/trellis-balanced-scatter.ts +0 -51
- package/src/model/trellis-linear.ts +0 -672
- package/src/model/trellis-shared-m.ts +0 -151
- package/src/model/trellis-shared-scatter.ts +0 -144
- package/src/model/trellis-splitk-prefill.ts +0 -158
- package/src/model/trellis-tiled-prefill.ts +0 -136
- package/src/model/trellis-vector-expand.ts +0 -62
- package/src/model/trellis-wide-prefill.ts +0 -94
- package/src/model/turboquant-codec.ts +0 -83
- package/src/model/universal/archs.ts +0 -332
- package/src/model/universal/dense.ts +0 -612
- package/src/model/universal/modules.ts +0 -170
- package/src/model/universal/rope.ts +0 -381
- package/src/native/expert_io.c +0 -523
- package/src/native/frame_extract.swift +0 -140
- package/src/native-pack.ts +0 -162
- package/src/pi-provider.ts +0 -53
- package/src/pi-session.ts +0 -81
- package/src/pi-terminal.ts +0 -274
- package/src/pi-web.ts +0 -1932
- package/src/preflight.ts +0 -158
- package/src/prompt-cache.ts +0 -460
- package/src/quantize/allocator.ts +0 -627
- package/src/quantize/atomic-output.ts +0 -38
- package/src/quantize/calibration.ts +0 -181
- package/src/quantize/config-writer.ts +0 -145
- package/src/quantize/index.ts +0 -84
- package/src/quantize/job.ts +0 -162
- package/src/quantize/quantizer.ts +0 -666
- package/src/quantize/rotate.ts +0 -603
- package/src/quantize/safetensors-writer.ts +0 -249
- package/src/quantize/sensitivity.ts +0 -216
- package/src/quantize/trellis.ts +0 -586
- package/src/quantize/weight-transform.ts +0 -292
- package/src/registry.ts +0 -602
- package/src/runtime-config.ts +0 -81
- package/src/safetensors.ts +0 -158
- package/src/sampler.ts +0 -1011
- package/src/serve/admin-routes.ts +0 -184
- package/src/serve/assets/curve-terrain.html +0 -105
- package/src/serve/assets/training-inference-map.html +0 -575
- package/src/serve/aux-routes.ts +0 -132
- package/src/serve/batch-scheduler.ts +0 -8
- package/src/serve/checkpoint-identity.ts +0 -64
- package/src/serve/generation-gateway.ts +0 -496
- package/src/serve/isolate.ts +0 -609
- package/src/serve/kv-budget.ts +0 -14
- package/src/serve/lab-routes.ts +0 -341
- package/src/serve/lane-registry.ts +0 -45
- package/src/serve/model-admin-routes.ts +0 -155
- package/src/serve/model-binding.ts +0 -43
- package/src/serve/model-host.ts +0 -511
- package/src/serve/parent-application.ts +0 -56
- package/src/serve/prompt-response-trace.ts +0 -147
- package/src/serve/response-lease.ts +0 -24
- package/src/serve/session-search.ts +0 -326
- package/src/serve/static-routes.ts +0 -101
- package/src/server.ts +0 -1314
- package/src/spec/assistant-rows.ts +0 -92
- package/src/spec/assistant-source.ts +0 -126
- package/src/spec/cache-identity.ts +0 -22
- package/src/spec/deepspec-rows.ts +0 -29
- package/src/spec/deepspec-source.ts +0 -145
- package/src/spec/dflash-source.ts +0 -149
- package/src/spec/drafter.ts +0 -304
- package/src/spec/dspark/ab-stats.ts +0 -136
- package/src/spec/dspark/calibration.ts +0 -112
- package/src/spec/dspark/data-dflash.ts +0 -156
- package/src/spec/dspark/data.ts +0 -201
- package/src/spec/dspark/deepspec-module.ts +0 -776
- package/src/spec/dspark/generate-dflash.ts +0 -245
- package/src/spec/dspark/generate.ts +0 -290
- package/src/spec/dspark/loader.ts +0 -23
- package/src/spec/dspark/loss.ts +0 -213
- package/src/spec/dspark/module-dflash.ts +0 -560
- package/src/spec/dspark/module.ts +0 -540
- package/src/spec/dspark/quantize-drafter.ts +0 -91
- package/src/spec/dspark/sample.ts +0 -112
- package/src/spec/generate.ts +0 -276
- package/src/spec/glm52-mtp-graph.ts +0 -29
- package/src/spec/glm52-mtp-rows.ts +0 -132
- package/src/spec/glm52-mtp-source.ts +0 -317
- package/src/spec/glm52-mtp-state.ts +0 -33
- package/src/spec/ngram-source.ts +0 -204
- package/src/spec/projected-draft-rows.ts +0 -73
- package/src/spec/qwen-mtp-module.ts +0 -171
- package/src/spec/qwen-mtp-rows.ts +0 -219
- package/src/spec/qwen-mtp-source.ts +0 -383
- package/src/spec/qwen-mtp-state.ts +0 -30
- package/src/spec/serve-loop.ts +0 -585
- package/src/spec/source.ts +0 -215
- package/src/spec/two-model-rows.ts +0 -61
- package/src/spec/two-model.ts +0 -113
- package/src/ssd-cache.ts +0 -372
- package/src/ssd-durability.ts +0 -211
- package/src/tokenizer.ts +0 -335
- package/src/tool-approvals.ts +0 -131
- package/src/tool-call.ts +0 -695
- package/src/train/dataset.ts +0 -484
- package/src/train/diffusion-lora.ts +0 -216
- package/src/train/export.ts +0 -32
- package/src/train/flash-cce.ts +0 -1234
- package/src/train/forward.ts +0 -266
- package/src/train/fuse.ts +0 -243
- package/src/train/index.ts +0 -46
- package/src/train/job.ts +0 -205
- package/src/train/lora-params.ts +0 -254
- package/src/train/loss.ts +0 -1591
- package/src/train/merge.ts +0 -195
- package/src/train/optimizer.ts +0 -183
- package/src/train/prefix-shared.ts +0 -490
- package/src/train/rank.ts +0 -137
- package/src/train/segmented.ts +0 -1828
- package/src/train/steel-qmm-header.ts +0 -1461
- package/src/train/trainer.ts +0 -1221
- package/src/train/watch.ts +0 -542
- package/src/vision/diffusion-vision.ts +0 -620
- package/src/vision/embedder.ts +0 -168
- package/src/vision/preprocess.ts +0 -247
- package/src/vision/prompt.ts +0 -373
- package/src/vision/qwen3vl-preprocess.ts +0 -409
- package/src/vision/qwen3vl-prompt.ts +0 -196
- package/src/vision/qwen3vl-tower.ts +0 -555
- package/src/vision/siglip.ts +0 -599
- package/src/vision/video-frames.ts +0 -148
- package/src/web/app.html +0 -2143
- package/src/web/app.js +0 -5703
- package/src/web/skills/memory/SKILL.md +0 -120
- package/src/web/skills/web-research/SKILL.md +0 -46
- package/src/web/skills.ts +0 -97
- package/src/web/src/adapters-panel.ts +0 -288
- package/src/web/src/assistant.ts +0 -451
- package/src/web/src/chat.ts +0 -1057
- package/src/web/src/composer.ts +0 -959
- package/src/web/src/dataset.ts +0 -132
- package/src/web/src/finetune.ts +0 -267
- package/src/web/src/hub.ts +0 -428
- package/src/web/src/main.ts +0 -98
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +0 -572
- package/src/web/src/model-picker.ts +0 -159
- package/src/web/src/palette.ts +0 -346
- package/src/web/src/protocol.ts +0 -26
- package/src/web/src/quantize.ts +0 -167
- package/src/web/src/rag.ts +0 -274
- package/src/web/src/sessions.ts +0 -469
- package/src/web/src/shell.ts +0 -804
- package/src/web/src/ui-catalog.ts +0 -78
- package/src/web/vendor/README.md +0 -61
- package/src/web-assets.ts +0 -47
- package/src/web-tools.ts +0 -478
- package/src/weights.ts +0 -255
- /package/src/{serve → server}/completion-sink.ts +0 -0
- /package/src/web/{icon.svg → public/icon.svg} +0 -0
- /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
- /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
package/src/model/gemma4.ts
DELETED
|
@@ -1,1315 +0,0 @@
|
|
|
1
|
-
// Gemma 4 text model — line-for-line port of mlx-lm's gemma4_text.py
|
|
2
|
-
// (oracle venv site-packages), covering the paths our target models
|
|
3
|
-
// exercise: 12B (dense), e4b (per-layer-input embeddings + KV-shared
|
|
4
|
-
// layers), 26B-A4B (MoE block: router + gather_qmm experts).
|
|
5
|
-
//
|
|
6
|
-
// Parity notes (see PLAN.md Phase 2 findings):
|
|
7
|
-
// - SDPA scale is 1.0 (Gemma4 normalizes q/k instead).
|
|
8
|
-
// - Full-attention layers: global_head_dim 512, 1 global KV head,
|
|
9
|
-
// attention_k_eq_v (V = same projection as K, with un-scaled RMS norm);
|
|
10
|
-
// ProportionalRoPE rotates only partial_rotary_factor·dims dims.
|
|
11
|
-
// - Python-float scalars promote weakly to the array dtype.
|
|
12
|
-
// - Replicate mlx python helper implementations exactly (x**3 is
|
|
13
|
-
// mx.power, not x·x·x — they round differently in bf16).
|
|
14
|
-
//
|
|
15
|
-
// Masks: ports base.py create_attention_mask/create_causal_mask. Sliding
|
|
16
|
-
// layers use a plain (non-rotating) cache + window masks — numerically
|
|
17
|
-
// identical to mlx-lm's RotatingKVCache, at the cost of unbounded cache
|
|
18
|
-
// growth past the window (memory optimization deferred).
|
|
19
|
-
|
|
20
|
-
import type { ModelConfig } from "../config";
|
|
21
|
-
import { quantFor } from "../config";
|
|
22
|
-
import type { Weights } from "../weights";
|
|
23
|
-
import { MlxArray } from "../mlx/array";
|
|
24
|
-
import { Dtype } from "../mlx/ffi";
|
|
25
|
-
import * as ops from "../mlx/ops";
|
|
26
|
-
import { isExpertTracing, recordRouting } from "../lab/expert-trace/expert-trace";
|
|
27
|
-
|
|
28
|
-
// The shared, config-independent machinery lives in gemma4-base.ts
|
|
29
|
-
// (Phase B extraction); this file keeps the architecture-specific
|
|
30
|
-
// assembly that Phase C generates per model. Re-export the base so
|
|
31
|
-
// existing importers keep one entry point.
|
|
32
|
-
export * from "./gemma4-base";
|
|
33
|
-
import {
|
|
34
|
-
argmaxLastPosition,
|
|
35
|
-
bidirMask,
|
|
36
|
-
disposing,
|
|
37
|
-
isCompiledTrace,
|
|
38
|
-
KVCache,
|
|
39
|
-
logitSoftcap,
|
|
40
|
-
LoraState,
|
|
41
|
-
QuantizedEmbedding,
|
|
42
|
-
QuantizedKVCache,
|
|
43
|
-
QuantizedLinear,
|
|
44
|
-
QuantizedSwitchLinear,
|
|
45
|
-
RMSNorm,
|
|
46
|
-
RotatingKVCache,
|
|
47
|
-
RotatingQuantizedKVCache,
|
|
48
|
-
quantizedSdpa,
|
|
49
|
-
type Cache,
|
|
50
|
-
type Mask,
|
|
51
|
-
type SharedKv,
|
|
52
|
-
captureRopeOffsets,
|
|
53
|
-
type LoraWeights,
|
|
54
|
-
} from "./gemma4-base";
|
|
55
|
-
import { Checkpoint } from "../mlx/checkpoint";
|
|
56
|
-
import { flashAttention, getTrainingAttn, flashSupported } from "./flash-attention";
|
|
57
|
-
import { unrotateValues as tqUnrotateValues } from "../mlx/turboquant-ops";
|
|
58
|
-
import { CompiledFunction } from "../mlx/compile";
|
|
59
|
-
import { flagOn } from "../runtime-config";
|
|
60
|
-
|
|
61
|
-
/** Verbatim port of mlx_lm/models/gemma4_text.py:
|
|
62
|
-
* `@partial(mx.compile, shapeless=True) def geglu(gate, x): return nn.gelu_approx(gate) * x`
|
|
63
|
-
* The oracle @mx.compile's this and uses it for EVERY MLP shape (dense MLP
|
|
64
|
-
* __call__ line 114, and the SwitchGLU GeGLU activation line 149-150). Our
|
|
65
|
-
* default MLP runs the SAME math UNFUSED (standalone ops.geluApprox + ops.mul),
|
|
66
|
-
* a different dispatched kernel set + the per-op host tax the MiniCPM5 swiglu
|
|
67
|
-
* fix measured at ~5%. This closure fuses geluApprox's ~9 element-wise ops + the
|
|
68
|
-
* final mul into one traced-once/replayed graph, matching the oracle's kernel
|
|
69
|
-
* set. `geluApprox` is composed op-for-op from the same primitives as the
|
|
70
|
-
* oracle's nn.gelu_approx (0.5·x·(1+tanh(√(2/π)(x+0.044715·x³)))), so the
|
|
71
|
-
* compiled graph is the oracle's geglu graph. Autograd-safe (verified: the vjp
|
|
72
|
-
* through this closure is bit-identical to the plain composition). */
|
|
73
|
-
/** True when gemma's geglu activation runs through the compiled closure (matching
|
|
74
|
-
* the oracle's @mx.compile geglu) instead of the spelled-out ops.geluApprox+ops.mul.
|
|
75
|
-
* DEFAULT ON: compiled geglu is bit-exact to mlx-lm's @mx.compile geglu (same
|
|
76
|
-
* libmlx graph → same kernel) AND dispatches one fused kernel set instead of the
|
|
77
|
-
* ~9-op path. `MLX_BUN_COMPILED_GEGLU=0` (--compiled-activations off) selects the
|
|
78
|
-
* uncompiled composition — same L1 parity, slower (the A/B opt-out). */
|
|
79
|
-
export function compiledGegluActive(): boolean {
|
|
80
|
-
return flagOn("MLX_BUN_COMPILED_GEGLU", true);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
let _gegluClosure: CompiledFunction | null = null;
|
|
84
|
-
export function compiledGeglu(gate: MlxArray, up: MlxArray): MlxArray {
|
|
85
|
-
if (!_gegluClosure) {
|
|
86
|
-
_gegluClosure = new CompiledFunction((inputs) => {
|
|
87
|
-
const g = inputs[0]!, u = inputs[1]!; // nn.gelu_approx(gate) * x
|
|
88
|
-
const act = ops.geluApprox(g);
|
|
89
|
-
const out = ops.mul(act, u); act.dispose();
|
|
90
|
-
return [out];
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
return _gegluClosure.apply([gate, up])[0]!;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** gemma4_text.py `@partial(mx.compile) def logit_softcap(softcap, x):
|
|
97
|
-
* return mx.tanh(x / softcap) * softcap`
|
|
98
|
-
* The oracle fuses divide+tanh+multiply into ONE kernel; our default
|
|
99
|
-
* `logitSoftcap` runs them UNFUSED (standalone div + tanh + mul → a separate
|
|
100
|
-
* `vn_Tanh` + `vsn_Divide`). Keyed by cap so the softcap is baked as a graph
|
|
101
|
-
* constant (matching the oracle's weak-scalar → the `…Divide…Tanh…Multiply…_VC_`
|
|
102
|
-
* kernel). The default (matches mlx-lm's dispatched kernel set). */
|
|
103
|
-
const _softcapClosures = new Map<number, CompiledFunction>();
|
|
104
|
-
export function compiledLogitSoftcap(x: MlxArray, cap: number): MlxArray {
|
|
105
|
-
let fn = _softcapClosures.get(cap);
|
|
106
|
-
if (!fn) {
|
|
107
|
-
fn = new CompiledFunction((inputs) => {
|
|
108
|
-
const xx = inputs[0]!; // tanh(x / softcap) * softcap
|
|
109
|
-
const capArr = ops.scalarLike(cap, xx);
|
|
110
|
-
const scaled = ops.div(xx, capArr);
|
|
111
|
-
const t = ops.tanh(scaled); scaled.dispose();
|
|
112
|
-
const out = ops.mul(t, capArr); t.dispose(); capArr.dispose();
|
|
113
|
-
return [out];
|
|
114
|
-
});
|
|
115
|
-
_softcapClosures.set(cap, fn);
|
|
116
|
-
}
|
|
117
|
-
return fn.apply([x])[0]!;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Optional gradient-checkpointing context for the training forward. When set,
|
|
121
|
-
* forwardLayers wraps each layer in a Checkpoint so its interior activations
|
|
122
|
-
* are recomputed in the backward pass. `byLayer` maps a layer index to that
|
|
123
|
-
* layer's trainable LoRA weights, partitioned into the attention sub-block
|
|
124
|
-
* (q/k/v/o_proj) and the MLP sub-block (gate/up/down_proj + per-layer gate/
|
|
125
|
-
* projection) — re-swapped inside the closure so they are explicit checkpoint
|
|
126
|
-
* inputs (required because the autograd primals are disposed before the
|
|
127
|
-
* recompute). When `splitMlp` is set, the layer is wrapped in TWO checkpoints
|
|
128
|
-
* (attn sub-block + MLP sub-block) with the post-attn residual `hMid` as the
|
|
129
|
-
* boundary, so the backward recompute holds `max(attn,MLP)+hMid` instead of the
|
|
130
|
-
* whole layer (the intra-layer MLP split, lever 4). `keepAlive` collects the
|
|
131
|
-
* Checkpoint objects; the trainer disposes them after value_and_grad. */
|
|
132
|
-
export interface LayerLoras {
|
|
133
|
-
attn: LoraWeights[];
|
|
134
|
-
mlp: LoraWeights[];
|
|
135
|
-
}
|
|
136
|
-
export interface GradCheckpointCtx {
|
|
137
|
-
byLayer: Map<number, LayerLoras>;
|
|
138
|
-
splitMlp: boolean;
|
|
139
|
-
keepAlive: Checkpoint[];
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Shared prompt-prefix plan (lever 7, e4b — docs/design/orpo-training.md). When
|
|
143
|
-
// set, Attention ropes the concatenated [prompt(P); chosenResp(Rc); rejectedResp(Rr)]
|
|
144
|
-
// sequence BLOCK-WISE — prompt at offset 0, EACH response reset to logical offset
|
|
145
|
-
// P — instead of the uniform cache.offset, so ONE forward over the concat is
|
|
146
|
-
// bit-exact with the two separate [prompt;resp] forwards (each response sees the
|
|
147
|
-
// prompt's RoPE positions, not shifted-by-the-other-response). The matching
|
|
148
|
-
// block-sparse + LOGICAL-window attention mask rides in via the prefix cache's
|
|
149
|
-
// makeMask (src/train/prefix-shared.ts). Set around the single forward
|
|
150
|
-
// (single-threaded), cleared after; null → the normal uniform-offset rope (every
|
|
151
|
-
// other forward is untouched).
|
|
152
|
-
export interface GemmaPrefixPlan { P: number; Rc: number; Rr: number }
|
|
153
|
-
let _gemmaPrefixPlan: GemmaPrefixPlan | null = null;
|
|
154
|
-
export function setGemmaPrefixPlan(p: GemmaPrefixPlan | null): void {
|
|
155
|
-
_gemmaPrefixPlan = p;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
class Attention {
|
|
159
|
-
readonly isSliding: boolean;
|
|
160
|
-
readonly windowSize: number;
|
|
161
|
-
readonly useKEqV: boolean;
|
|
162
|
-
readonly hasKv: boolean;
|
|
163
|
-
readonly headDim: number;
|
|
164
|
-
readonly nHeads: number;
|
|
165
|
-
readonly nKvHeads: number;
|
|
166
|
-
readonly ropeBase: number | null;
|
|
167
|
-
readonly ropeFreqs: MlxArray | null;
|
|
168
|
-
readonly qProj: QuantizedLinear;
|
|
169
|
-
readonly kProj: QuantizedLinear | null;
|
|
170
|
-
readonly vProj: QuantizedLinear | null;
|
|
171
|
-
readonly oProj: QuantizedLinear;
|
|
172
|
-
readonly qNorm: RMSNorm;
|
|
173
|
-
readonly kNorm: RMSNorm | null;
|
|
174
|
-
readonly vNorm: RMSNorm | null;
|
|
175
|
-
|
|
176
|
-
constructor(
|
|
177
|
-
weights: Weights, config: ModelConfig, prefix: string, layerType: string,
|
|
178
|
-
hasKv = true,
|
|
179
|
-
) {
|
|
180
|
-
const t = config.text;
|
|
181
|
-
this.hasKv = hasKv;
|
|
182
|
-
this.isSliding = layerType === "sliding_attention";
|
|
183
|
-
this.windowSize = t.slidingWindow;
|
|
184
|
-
this.headDim = this.isSliding ? t.headDim : t.globalHeadDim;
|
|
185
|
-
this.nHeads = t.numAttentionHeads;
|
|
186
|
-
this.useKEqV = t.attentionKEqV && !this.isSliding;
|
|
187
|
-
this.nKvHeads = this.useKEqV ? t.numGlobalKeyValueHeads : t.numKeyValueHeads;
|
|
188
|
-
|
|
189
|
-
const rp = t.ropeParameters[this.isSliding ? "sliding_attention" : "full_attention"]!;
|
|
190
|
-
if (rp.ropeType === "default") {
|
|
191
|
-
this.ropeBase = rp.ropeTheta;
|
|
192
|
-
this.ropeFreqs = null;
|
|
193
|
-
} else if (rp.ropeType === "proportional") {
|
|
194
|
-
// Port of rope_utils.ProportionalRoPE.__init__. The rotated freqs
|
|
195
|
-
// MUST be computed on-device in f32 (arange/dims, then base**x)
|
|
196
|
-
// exactly like the reference — computing them host-side in f64 and
|
|
197
|
-
// rounding to f32 lands 17/64 of them 1 ulp off (f64 pow ≠ f32
|
|
198
|
-
// powf), a latent knife-edge that Phase 10's tiled values exposed
|
|
199
|
-
// as a 1-ulp q-rope divergence at layer 11 (Phase 2 porting rule:
|
|
200
|
-
// replicate the helper's IMPLEMENTATION, not its formula).
|
|
201
|
-
const rotated = Math.floor(this.headDim * rp.partialRotaryFactor);
|
|
202
|
-
const n = this.headDim / 2;
|
|
203
|
-
const exponentsRaw = ops.arange(0, rotated, 2, Dtype.float32);
|
|
204
|
-
const dims = ops.scalarLike(this.headDim, exponentsRaw);
|
|
205
|
-
const exponents = ops.div(exponentsRaw, dims);
|
|
206
|
-
const base = ops.scalarLike(rp.ropeTheta, exponents);
|
|
207
|
-
let rotFreqs = ops.pow(base, exponents);
|
|
208
|
-
if (rp.factor !== 1.0) {
|
|
209
|
-
const f = ops.scalarLike(rp.factor, rotFreqs);
|
|
210
|
-
rotFreqs = disposing(rotFreqs, ops.mul(f, rotFreqs));
|
|
211
|
-
f.dispose();
|
|
212
|
-
}
|
|
213
|
-
this.ropeBase = null;
|
|
214
|
-
const tailLen = n - rotated / 2;
|
|
215
|
-
if (tailLen > 0) {
|
|
216
|
-
const tail = MlxArray.fromFloat32(
|
|
217
|
-
new Float32Array(tailLen).fill(Infinity), [tailLen],
|
|
218
|
-
);
|
|
219
|
-
this.ropeFreqs = ops.concatAxis([rotFreqs, tail], 0);
|
|
220
|
-
tail.dispose();
|
|
221
|
-
} else {
|
|
222
|
-
this.ropeFreqs = rotFreqs;
|
|
223
|
-
}
|
|
224
|
-
for (const a of [exponentsRaw, dims, exponents, base])
|
|
225
|
-
a.dispose();
|
|
226
|
-
if (this.ropeFreqs !== rotFreqs) rotFreqs.dispose();
|
|
227
|
-
} else {
|
|
228
|
-
throw new Error(`unsupported rope_type ${rp.ropeType}`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
this.qProj = QuantizedLinear.load(weights, `${prefix}.q_proj`, config);
|
|
232
|
-
this.kProj = hasKv ? QuantizedLinear.load(weights, `${prefix}.k_proj`, config) : null;
|
|
233
|
-
this.vProj = hasKv && !this.useKEqV
|
|
234
|
-
? QuantizedLinear.load(weights, `${prefix}.v_proj`, config) : null;
|
|
235
|
-
this.oProj = QuantizedLinear.load(weights, `${prefix}.o_proj`, config);
|
|
236
|
-
this.qNorm = new RMSNorm(weights.tensor(`${prefix}.q_norm.weight`), t.rmsNormEps);
|
|
237
|
-
this.kNorm = hasKv
|
|
238
|
-
? new RMSNorm(weights.tensor(`${prefix}.k_norm.weight`), t.rmsNormEps) : null;
|
|
239
|
-
this.vNorm = hasKv ? new RMSNorm(null, t.rmsNormEps) : null;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/** A number offset takes the static fast::rope; an array offset (set by
|
|
243
|
-
* compiled-decode trace adapters) takes the dynamic variant — same
|
|
244
|
-
* kernel, offset read from the array (bit-exactness asserted in
|
|
245
|
-
* tests/unit/compile.test.ts). */
|
|
246
|
-
rope(x: MlxArray, offset: number | MlxArray): MlxArray {
|
|
247
|
-
return typeof offset === "number"
|
|
248
|
-
? ops.rope(x, this.headDim, this.ropeBase, offset, this.ropeFreqs)
|
|
249
|
-
: ops.ropeDynamic(x, this.headDim, this.ropeBase, offset, this.ropeFreqs);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/** Block-wise RoPE for the prefix-shared concat [prompt; chosen; rejected] along
|
|
253
|
-
* the sequence axis (axis 2 of [B,H,T,D]): prompt at offset 0, EACH response
|
|
254
|
-
* reset to logical offset P. RoPE is per-token, so roping each contiguous block
|
|
255
|
-
* at its scalar offset and concatenating == roping with per-token position-ids
|
|
256
|
-
* [0..P-1, P..P+Rc-1, P..P+Rr-1]. Uses this.rope per block so the layer's
|
|
257
|
-
* proportional/ropeFreqs + sliding-vs-full geometry applies identically to the
|
|
258
|
-
* two-forward path. Caller disposes the input. */
|
|
259
|
-
ropeBlocks(x: MlxArray, plan: GemmaPrefixPlan): MlxArray {
|
|
260
|
-
const { P, Rc, Rr } = plan;
|
|
261
|
-
const [B, H, , D] = x.shape as [number, number, number, number];
|
|
262
|
-
const blocks = [
|
|
263
|
-
{ start: 0, len: P, off: 0 },
|
|
264
|
-
{ start: P, len: Rc, off: P },
|
|
265
|
-
{ start: P + Rc, len: Rr, off: P },
|
|
266
|
-
].filter((b) => b.len > 0);
|
|
267
|
-
const parts = blocks.map((b) => {
|
|
268
|
-
const sl = x.slice([0, 0, b.start, 0], [B, H, b.start + b.len, D]);
|
|
269
|
-
const r = this.rope(sl, b.off);
|
|
270
|
-
sl.dispose();
|
|
271
|
-
return r;
|
|
272
|
-
});
|
|
273
|
-
const out = ops.concatAxis(parts, 2);
|
|
274
|
-
for (const p of parts) p.dispose();
|
|
275
|
-
return out;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/** Returns the attention output plus the fetched KV (for KV-shared
|
|
279
|
-
* sharer layers and the speculative drafter). The SharedKv arrays are
|
|
280
|
-
* owned by the caller (forwardLayers) and disposed after the pass. */
|
|
281
|
-
forward(
|
|
282
|
-
x: MlxArray, mask: Mask, cache: Cache | null, sharedIn: SharedKv | null,
|
|
283
|
-
): { out: MlxArray; shared: SharedKv } {
|
|
284
|
-
const [B, L] = x.shape as [number, number, number];
|
|
285
|
-
|
|
286
|
-
let q = this.qProj.forward(x);
|
|
287
|
-
q = disposing(q, ops.reshape(q, [B, L, this.nHeads, this.headDim]));
|
|
288
|
-
q = disposing(q, this.qNorm.forward(q));
|
|
289
|
-
|
|
290
|
-
let shared: SharedKv;
|
|
291
|
-
if (!this.hasKv) {
|
|
292
|
-
if (!sharedIn) throw new Error("KV-shared layer received no shared KV");
|
|
293
|
-
shared = sharedIn;
|
|
294
|
-
} else {
|
|
295
|
-
if (!cache) throw new Error("donor layer requires a cache");
|
|
296
|
-
let k = this.kProj!.forward(x);
|
|
297
|
-
k = disposing(k, ops.reshape(k, [B, L, this.nKvHeads, this.headDim]));
|
|
298
|
-
let v: MlxArray;
|
|
299
|
-
if (this.vProj) {
|
|
300
|
-
v = this.vProj.forward(x);
|
|
301
|
-
v = disposing(v, ops.reshape(v, [B, L, this.nKvHeads, this.headDim]));
|
|
302
|
-
} else {
|
|
303
|
-
v = k; // attention_k_eq_v: shared projection; norms differ below
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const offset = cache.offset;
|
|
307
|
-
// Own the pre-write positions through every query and shared-KV
|
|
308
|
-
// consumer. Advancing a row cache may replace and release its borrowed
|
|
309
|
-
// offset array before Q's RoPE is built. The fetched shared state owns
|
|
310
|
-
// this retained handle and releases it with K/V at the end of the pass.
|
|
311
|
-
const offsetArr = captureRopeOffsets(cache);
|
|
312
|
-
|
|
313
|
-
const kNormed = this.kNorm!.forward(k);
|
|
314
|
-
const kT = ops.transposeAxes(kNormed, [0, 2, 1, 3]);
|
|
315
|
-
kNormed.dispose();
|
|
316
|
-
const kRoped = _gemmaPrefixPlan
|
|
317
|
-
? this.ropeBlocks(kT, _gemmaPrefixPlan)
|
|
318
|
-
: this.rope(kT, offsetArr ?? offset);
|
|
319
|
-
kT.dispose();
|
|
320
|
-
|
|
321
|
-
const vNormed = this.vNorm!.forward(v);
|
|
322
|
-
const vT = ops.transposeAxes(vNormed, [0, 2, 1, 3]);
|
|
323
|
-
vNormed.dispose();
|
|
324
|
-
if (v !== k) v.dispose();
|
|
325
|
-
k.dispose();
|
|
326
|
-
|
|
327
|
-
if (cache.attentionState) {
|
|
328
|
-
const attention = cache.attentionState.appendAndFetch(kRoped, vT);
|
|
329
|
-
kRoped.dispose(); vT.dispose();
|
|
330
|
-
shared = { kind: "view", attention, offset, offsetArr };
|
|
331
|
-
} else if (cache.quantizedAttention) {
|
|
332
|
-
const quantized = cache.quantizedAttention;
|
|
333
|
-
const [kq, vq] = quantized.updateAndFetchQuantized(kRoped, vT);
|
|
334
|
-
kRoped.dispose();
|
|
335
|
-
vT.dispose();
|
|
336
|
-
shared = {
|
|
337
|
-
kind: "quant", keys: kq, values: vq, offset,
|
|
338
|
-
groupSize: quantized.groupSize, bits: quantized.bits,
|
|
339
|
-
offsetArr,
|
|
340
|
-
};
|
|
341
|
-
} else if (cache.rotatedValueAttention) {
|
|
342
|
-
// Deferred-V read: values stay rotated; the attention output is
|
|
343
|
-
// un-rotated below (shared.vRotated) — including by KV-shared
|
|
344
|
-
// consumer layers, which see the flag through sharedIn.
|
|
345
|
-
const [keys, values] = cache.rotatedValueAttention.updateAndFetchDeferredV(kRoped, vT);
|
|
346
|
-
kRoped.dispose();
|
|
347
|
-
vT.dispose();
|
|
348
|
-
shared = { kind: "plain", keys, values, offset, offsetArr, vRotated: true,
|
|
349
|
-
restoreValues: cache.rotatedValueAttention.captureValueTransform?.() };
|
|
350
|
-
} else {
|
|
351
|
-
const [keys, values] = cache.updateAndFetch(kRoped, vT);
|
|
352
|
-
kRoped.dispose();
|
|
353
|
-
vT.dispose();
|
|
354
|
-
shared = { kind: "plain", keys, values, offset, offsetArr };
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
359
|
-
q = disposing(q, _gemmaPrefixPlan
|
|
360
|
-
? this.ropeBlocks(q, _gemmaPrefixPlan)
|
|
361
|
-
: this.rope(q, shared.offsetArr ?? shared.offset));
|
|
362
|
-
|
|
363
|
-
let attn: MlxArray;
|
|
364
|
-
const ta = getTrainingAttn();
|
|
365
|
-
if (shared.kind === "view") {
|
|
366
|
-
attn = shared.attention.attend(q, 1.0, mask);
|
|
367
|
-
} else if (shared.kind === "quant") {
|
|
368
|
-
attn = quantizedSdpa(q, shared.keys, shared.values, 1.0, mask, shared.groupSize, shared.bits);
|
|
369
|
-
} else if (
|
|
370
|
-
ta === "flash" && flashSupported(q) && (mask.mode === "causal" || mask.mode === "array")
|
|
371
|
-
) {
|
|
372
|
-
// Training, OPT-IN flash path (MLX_BUN_TRAIN_ATTN=flash) for O(L)-memory
|
|
373
|
-
// long context. NOTE the default (ops.sdpa, below) is the CORRECT and
|
|
374
|
-
// verified route: its dQ/dK/dV vjps are exact (finite-difference checked;
|
|
375
|
-
// it's what mlx-lm's tuner differentiates). The two historical bugs were
|
|
376
|
-
// in THIS flash kernel (spurious dK transpose, divergent dQ barrier),
|
|
377
|
-
// both fixed and gated by tests/unit/flash-attention.test.ts vs the ops.sdpa
|
|
378
|
-
// oracle — see flash-attention.ts's routing comment. Flash also remains
|
|
379
|
-
// Gemma-guarded in the trainer (e4b >=2K SIGTRAP not yet re-validated).
|
|
380
|
-
// Full layers → window 0 (pure causal); sliding layers → their window.
|
|
381
|
-
const window = this.isSliding ? this.windowSize : 0;
|
|
382
|
-
attn = flashAttention(q, shared.keys, shared.values, 1.0, true, window);
|
|
383
|
-
} else {
|
|
384
|
-
attn = ops.sdpa(q, shared.keys, shared.values, 1.0, mask.mode, mask.arr);
|
|
385
|
-
}
|
|
386
|
-
q.dispose();
|
|
387
|
-
if (shared.kind === "plain" && shared.vRotated) {
|
|
388
|
-
// Attention is linear in V: one InvFWHT on the [B,H,L,D] output
|
|
389
|
-
// replaces one per cached token (TurboQuant deferred-V).
|
|
390
|
-
attn = disposing(attn, shared.restoreValues ? shared.restoreValues(attn) : tqUnrotateValues(attn));
|
|
391
|
-
}
|
|
392
|
-
const attnT = ops.transposeAxes(attn, [0, 2, 1, 3]);
|
|
393
|
-
attn.dispose();
|
|
394
|
-
const merged = ops.reshape(attnT, [B, L, -1]);
|
|
395
|
-
attnT.dispose();
|
|
396
|
-
const out = this.oProj.forward(merged);
|
|
397
|
-
merged.dispose();
|
|
398
|
-
return { out, shared };
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
class MLP {
|
|
403
|
-
readonly gate: QuantizedLinear;
|
|
404
|
-
readonly up: QuantizedLinear;
|
|
405
|
-
readonly down: QuantizedLinear;
|
|
406
|
-
|
|
407
|
-
constructor(weights: Weights, config: ModelConfig, prefix: string) {
|
|
408
|
-
this.gate = QuantizedLinear.load(weights, `${prefix}.gate_proj`, config);
|
|
409
|
-
this.up = QuantizedLinear.load(weights, `${prefix}.up_proj`, config);
|
|
410
|
-
this.down = QuantizedLinear.load(weights, `${prefix}.down_proj`, config);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
forward(x: MlxArray): MlxArray {
|
|
414
|
-
const g = this.gate.forward(x);
|
|
415
|
-
const u = this.up.forward(x);
|
|
416
|
-
let h: MlxArray;
|
|
417
|
-
if (compiledGegluActive() && !isCompiledTrace()) {
|
|
418
|
-
// Oracle geglu(gate_proj(x), up_proj(x)): @mx.compile'd gelu_approx·mul in
|
|
419
|
-
// one kernel set (the default; = mlx-lm). Autograd-safe → covers training.
|
|
420
|
-
h = compiledGeglu(g, u);
|
|
421
|
-
g.dispose();
|
|
422
|
-
u.dispose();
|
|
423
|
-
} else {
|
|
424
|
-
const act = ops.geluApprox(g);
|
|
425
|
-
g.dispose();
|
|
426
|
-
h = ops.mul(act, u);
|
|
427
|
-
act.dispose();
|
|
428
|
-
u.dispose();
|
|
429
|
-
}
|
|
430
|
-
const out = this.down.forward(h);
|
|
431
|
-
h.dispose();
|
|
432
|
-
return out;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// --- MoE block (26B-A4B) — port of reference Router/Experts/SwitchGLU ----
|
|
437
|
-
// (gemma4_text.py + switch_layers.py in the oracle venv). The checkpoint
|
|
438
|
-
// ships pre-stacked switch_glu tensors [experts, out, in/packed]; only the
|
|
439
|
-
// quantized path exists here (all our targets are OptiQ quants).
|
|
440
|
-
|
|
441
|
-
/** Port of reference Router: norm -> scale -> project -> top-k -> renormalize. */
|
|
442
|
-
class Router {
|
|
443
|
-
readonly proj: QuantizedLinear;
|
|
444
|
-
/** scale · hidden_size^-0.5, precomputed (identical ops to the per-call
|
|
445
|
-
* product in the reference: bf16 array × weak scalar). */
|
|
446
|
-
readonly normWeight: MlxArray;
|
|
447
|
-
readonly perExpertScale: MlxArray;
|
|
448
|
-
readonly eps: number;
|
|
449
|
-
readonly numExperts: number;
|
|
450
|
-
readonly topK: number;
|
|
451
|
-
/** Decoder layer index (parsed from the weight prefix) — for E0 tracing. */
|
|
452
|
-
readonly layerIdx: number;
|
|
453
|
-
|
|
454
|
-
constructor(weights: Weights, config: ModelConfig, prefix: string) {
|
|
455
|
-
const t = config.text;
|
|
456
|
-
this.layerIdx = Number(prefix.match(/layers\.(\d+)/)?.[1] ?? -1);
|
|
457
|
-
this.proj = QuantizedLinear.load(weights, `${prefix}.proj`, config);
|
|
458
|
-
// weights.tensor() returns the Weights-owned cached array — do NOT
|
|
459
|
-
// dispose it (a second model over the same Weights would get a dead
|
|
460
|
-
// handle; Weights.dispose() frees it at end of life)
|
|
461
|
-
const scale = weights.tensor(`${prefix}.scale`);
|
|
462
|
-
this.normWeight = ops.mulScalar(scale, Math.pow(t.hiddenSize, -0.5));
|
|
463
|
-
this.perExpertScale = weights.tensor(`${prefix}.per_expert_scale`);
|
|
464
|
-
this.eps = t.rmsNormEps;
|
|
465
|
-
this.numExperts = t.numExperts;
|
|
466
|
-
this.topK = t.topKExperts;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
/** x [B, L, H] → { indices [B, L, k] (uint32), weights [B, L, k] }. */
|
|
470
|
-
forward(x: MlxArray): { indices: MlxArray; weights: MlxArray } {
|
|
471
|
-
const normed = ops.rmsNorm(x, this.normWeight, this.eps);
|
|
472
|
-
const scores = this.proj.forward(normed);
|
|
473
|
-
normed.dispose();
|
|
474
|
-
|
|
475
|
-
const part = ops.argpartitionAxis(scores, this.numExperts - this.topK, -1);
|
|
476
|
-
const [B, L] = scores.shape as [number, number, number];
|
|
477
|
-
let indices: MlxArray;
|
|
478
|
-
if (isCompiledTrace()) {
|
|
479
|
-
const start = ops.fromInt32([this.numExperts - this.topK], [1]);
|
|
480
|
-
indices = ops.sliceDynamic(part, start, [2], [B, L, this.topK]);
|
|
481
|
-
start.dispose();
|
|
482
|
-
} else {
|
|
483
|
-
indices = part.slice([0, 0, this.numExperts - this.topK], [B, L, this.numExperts]);
|
|
484
|
-
}
|
|
485
|
-
part.dispose();
|
|
486
|
-
|
|
487
|
-
let w = ops.takeAlongAxis(scores, indices, -1);
|
|
488
|
-
scores.dispose();
|
|
489
|
-
w = disposing(w, ops.softmaxAxis(w, -1, false));
|
|
490
|
-
const gathered = ops.takeAxis(this.perExpertScale, indices, 0);
|
|
491
|
-
w = disposing(w, ops.mul(w, gathered));
|
|
492
|
-
gathered.dispose();
|
|
493
|
-
if (isExpertTracing()) recordRouting(this.layerIdx, indices);
|
|
494
|
-
return { indices, weights: w };
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/** Port of switch_layers.SwitchGLU (geglu activation), quantized path. */
|
|
499
|
-
class SwitchGLU {
|
|
500
|
-
readonly gate: QuantizedSwitchLinear;
|
|
501
|
-
readonly up: QuantizedSwitchLinear;
|
|
502
|
-
readonly down: QuantizedSwitchLinear;
|
|
503
|
-
|
|
504
|
-
constructor(weights: Weights, config: ModelConfig, prefix: string) {
|
|
505
|
-
this.gate = QuantizedSwitchLinear.load(weights, `${prefix}.gate_proj`, config);
|
|
506
|
-
this.up = QuantizedSwitchLinear.load(weights, `${prefix}.up_proj`, config);
|
|
507
|
-
this.down = QuantizedSwitchLinear.load(weights, `${prefix}.down_proj`, config);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/** x [B, L, H], indices [B, L, k] → [B, L, k, H]. */
|
|
511
|
-
forward(x: MlxArray, indices: MlxArray): MlxArray {
|
|
512
|
-
// x → [B, L, 1, 1, H] (reference expand_dims(x, (-2, -3)))
|
|
513
|
-
let h = ops.expandDims(x, -2);
|
|
514
|
-
h = disposing(h, ops.expandDims(h, -3));
|
|
515
|
-
|
|
516
|
-
// Sort tokens by expert when there are many rows (reference threshold).
|
|
517
|
-
const doSort = indices.size >= 64;
|
|
518
|
-
let idx = indices;
|
|
519
|
-
let invOrder: MlxArray | null = null;
|
|
520
|
-
let order: MlxArray | null = null;
|
|
521
|
-
if (doSort) {
|
|
522
|
-
// _gather_sort: flatten indices, argsort, gather rows by order // k
|
|
523
|
-
const k = indices.shape[indices.ndim - 1]!;
|
|
524
|
-
const idxFlat = ops.reshape(indices, [indices.size]);
|
|
525
|
-
order = ops.argsortAxis(idxFlat, 0);
|
|
526
|
-
invOrder = ops.argsortAxis(order, 0);
|
|
527
|
-
const kScalar = ops.scalarLike(k, order);
|
|
528
|
-
const rowIdx = ops.floorDivide(order, kScalar);
|
|
529
|
-
kScalar.dispose();
|
|
530
|
-
const [, , , , H] = h.shape as number[];
|
|
531
|
-
const flat = ops.reshape(h, [-1, 1, H!]);
|
|
532
|
-
h.dispose();
|
|
533
|
-
h = ops.takeAxis(flat, rowIdx, 0);
|
|
534
|
-
flat.dispose();
|
|
535
|
-
rowIdx.dispose();
|
|
536
|
-
idx = ops.takeAxis(idxFlat, order, 0);
|
|
537
|
-
idxFlat.dispose();
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
const xUp = this.up.forward(h, idx, doSort);
|
|
541
|
-
const xGate = this.gate.forward(h, idx, doSort);
|
|
542
|
-
h.dispose();
|
|
543
|
-
// GeGLU activation: gelu_approx(gate) · up (same composition as MLP). The
|
|
544
|
-
// oracle SwitchGLU uses the SAME @mx.compile'd geglu (activation=GeGLU(),
|
|
545
|
-
// switch_layers.py) → the compiled path routes through the shared closure.
|
|
546
|
-
let mid: MlxArray;
|
|
547
|
-
if (compiledGegluActive() && !isCompiledTrace()) {
|
|
548
|
-
mid = compiledGeglu(xGate, xUp);
|
|
549
|
-
xGate.dispose();
|
|
550
|
-
xUp.dispose();
|
|
551
|
-
} else {
|
|
552
|
-
const act = ops.geluApprox(xGate);
|
|
553
|
-
xGate.dispose();
|
|
554
|
-
mid = ops.mul(act, xUp);
|
|
555
|
-
act.dispose();
|
|
556
|
-
xUp.dispose();
|
|
557
|
-
}
|
|
558
|
-
let y = this.down.forward(mid, idx, doSort);
|
|
559
|
-
mid.dispose();
|
|
560
|
-
if (idx !== indices) idx.dispose();
|
|
561
|
-
|
|
562
|
-
if (doSort) {
|
|
563
|
-
// _scatter_unsort: restore row order, unflatten to indices.shape
|
|
564
|
-
y = disposing(y, ops.takeAxis(y, invOrder!, 0));
|
|
565
|
-
invOrder!.dispose();
|
|
566
|
-
order!.dispose();
|
|
567
|
-
const [B, L, k] = indices.shape as number[];
|
|
568
|
-
const H = y.shape[y.ndim - 1]!;
|
|
569
|
-
y = disposing(y, ops.reshape(y, [B!, L!, k!, 1, H]));
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// squeeze(-2)
|
|
573
|
-
const shape = y.shape;
|
|
574
|
-
shape.splice(shape.length - 2, 1);
|
|
575
|
-
return disposing(y, ops.reshape(y, shape));
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
/** Port of reference Experts: switch_glu then top-k weighted sum. */
|
|
580
|
-
class Experts {
|
|
581
|
-
readonly switchGlu: SwitchGLU;
|
|
582
|
-
|
|
583
|
-
constructor(weights: Weights, config: ModelConfig, prefix: string) {
|
|
584
|
-
this.switchGlu = new SwitchGLU(weights, config, `${prefix}.switch_glu`);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
forward(x: MlxArray, indices: MlxArray, weights: MlxArray): MlxArray {
|
|
588
|
-
const w = ops.expandDims(weights, -1);
|
|
589
|
-
const y = this.switchGlu.forward(x, indices);
|
|
590
|
-
const wy = ops.mul(w, y);
|
|
591
|
-
w.dispose();
|
|
592
|
-
y.dispose();
|
|
593
|
-
return disposing(wy, ops.sumAxis(wy, -2, false));
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
export class DecoderLayer {
|
|
598
|
-
readonly attn: Attention;
|
|
599
|
-
readonly mlp: MLP;
|
|
600
|
-
readonly inputNorm: RMSNorm;
|
|
601
|
-
readonly postAttnNorm: RMSNorm;
|
|
602
|
-
readonly preFfNorm: RMSNorm;
|
|
603
|
-
readonly postFfNorm: RMSNorm;
|
|
604
|
-
readonly layerScalar: MlxArray | null;
|
|
605
|
-
readonly layerType: string;
|
|
606
|
-
// 26B-A4B MoE block (dense MLP + routed experts in parallel branches)
|
|
607
|
-
readonly router: Router | null;
|
|
608
|
-
readonly experts: Experts | null;
|
|
609
|
-
readonly postFfNorm1: RMSNorm | null;
|
|
610
|
-
readonly postFfNorm2: RMSNorm | null;
|
|
611
|
-
readonly preFfNorm2: RMSNorm | null;
|
|
612
|
-
// e2b/e4b per-layer input gating
|
|
613
|
-
readonly perLayerGate: QuantizedLinear | null;
|
|
614
|
-
readonly perLayerProjection: QuantizedLinear | null;
|
|
615
|
-
readonly postPerLayerNorm: RMSNorm | null;
|
|
616
|
-
|
|
617
|
-
constructor(weights: Weights, config: ModelConfig, prefixBase: string, idx: number, hasKv: boolean) {
|
|
618
|
-
const prefix = `${prefixBase}.layers.${idx}`;
|
|
619
|
-
const t = config.text;
|
|
620
|
-
this.layerType = t.layerTypes[idx]!;
|
|
621
|
-
this.attn = new Attention(weights, config, `${prefix}.self_attn`, this.layerType, hasKv);
|
|
622
|
-
this.mlp = new MLP(weights, config, `${prefix}.mlp`);
|
|
623
|
-
const norm = (n: string) => new RMSNorm(weights.tensor(`${prefix}.${n}.weight`), t.rmsNormEps);
|
|
624
|
-
this.inputNorm = norm("input_layernorm");
|
|
625
|
-
this.postAttnNorm = norm("post_attention_layernorm");
|
|
626
|
-
this.preFfNorm = norm("pre_feedforward_layernorm");
|
|
627
|
-
this.postFfNorm = norm("post_feedforward_layernorm");
|
|
628
|
-
this.layerScalar = weights.has(`${prefix}.layer_scalar`)
|
|
629
|
-
? weights.tensor(`${prefix}.layer_scalar`)
|
|
630
|
-
: null;
|
|
631
|
-
if (t.enableMoeBlock) {
|
|
632
|
-
this.router = new Router(weights, config, `${prefix}.router`);
|
|
633
|
-
this.experts = new Experts(weights, config, `${prefix}.experts`);
|
|
634
|
-
this.postFfNorm1 = norm("post_feedforward_layernorm_1");
|
|
635
|
-
this.postFfNorm2 = norm("post_feedforward_layernorm_2");
|
|
636
|
-
this.preFfNorm2 = norm("pre_feedforward_layernorm_2");
|
|
637
|
-
} else {
|
|
638
|
-
this.router = this.experts = null;
|
|
639
|
-
this.postFfNorm1 = this.postFfNorm2 = this.preFfNorm2 = null;
|
|
640
|
-
}
|
|
641
|
-
if (t.hiddenSizePerLayerInput > 0) {
|
|
642
|
-
this.perLayerGate = QuantizedLinear.load(weights, `${prefix}.per_layer_input_gate`, config);
|
|
643
|
-
this.perLayerProjection = QuantizedLinear.load(weights, `${prefix}.per_layer_projection`, config);
|
|
644
|
-
this.postPerLayerNorm = norm("post_per_layer_input_norm");
|
|
645
|
-
} else {
|
|
646
|
-
this.perLayerGate = this.perLayerProjection = null;
|
|
647
|
-
this.postPerLayerNorm = null;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
forward(
|
|
652
|
-
x: MlxArray, mask: Mask, cache: Cache | null,
|
|
653
|
-
sharedIn: SharedKv | null, perLayerInput: MlxArray | null,
|
|
654
|
-
): { h: MlxArray; shared: SharedKv } {
|
|
655
|
-
// Composed from the two sub-blocks (the intra-layer min-cut is the post-attn
|
|
656
|
-
// residual `hMid` [1,T,hidden]). Behaviour is identical to the spelled-out
|
|
657
|
-
// body; the split lets the checkpoint path rematerialize attn and MLP
|
|
658
|
-
// separately (peak attn+MLP → max(attn,MLP)+hMid). `forwardMlp` borrows hMid.
|
|
659
|
-
const { h: hMid, shared } = this.forwardAttn(x, mask, cache, sharedIn);
|
|
660
|
-
const h = this.forwardMlp(hMid, perLayerInput);
|
|
661
|
-
hMid.dispose();
|
|
662
|
-
return { h, shared };
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
/** Attention sub-block: inputNorm → attn → postAttnNorm → residual add.
|
|
666
|
-
* Returns the post-attn residual stream `h` [1,T,hidden] (the intra-layer
|
|
667
|
-
* boundary) and the attention's fetched/produced K/V (`shared`). `x` is
|
|
668
|
-
* borrowed (never disposed). The e4b donor-KV threading lives entirely here. */
|
|
669
|
-
forwardAttn(
|
|
670
|
-
x: MlxArray, mask: Mask, cache: Cache | null, sharedIn: SharedKv | null,
|
|
671
|
-
): { h: MlxArray; shared: SharedKv } {
|
|
672
|
-
let h = this.inputNorm.forward(x);
|
|
673
|
-
const { out, shared } = this.attn.forward(h, mask, cache, sharedIn);
|
|
674
|
-
h.dispose();
|
|
675
|
-
h = out;
|
|
676
|
-
h = disposing(h, this.postAttnNorm.forward(h));
|
|
677
|
-
h = disposing(h, ops.add(x, h));
|
|
678
|
-
return { h, shared };
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
/** MLP sub-block: FFN (dense or MoE) + postFfNorm + residual add, then the
|
|
682
|
-
* e4b per-layer-input gate and layer scalar. `hMid` (the attn sub-block's
|
|
683
|
-
* output) is BORROWED — the caller owns/disposes it (it is the checkpoint
|
|
684
|
-
* boundary leaf), so this never disposes it. */
|
|
685
|
-
forwardMlp(hMid: MlxArray, perLayerInput: MlxArray | null): MlxArray {
|
|
686
|
-
let f: MlxArray;
|
|
687
|
-
if (this.router && this.experts) {
|
|
688
|
-
// MoE: dense MLP and routed experts as parallel branches off hMid
|
|
689
|
-
// (reference DecoderLayer, enable_moe path)
|
|
690
|
-
let h1 = this.preFfNorm.forward(hMid);
|
|
691
|
-
h1 = disposing(h1, this.mlp.forward(h1));
|
|
692
|
-
h1 = disposing(h1, this.postFfNorm1!.forward(h1));
|
|
693
|
-
|
|
694
|
-
const { indices, weights: topKWeights } = this.router.forward(hMid);
|
|
695
|
-
let h2 = this.preFfNorm2!.forward(hMid);
|
|
696
|
-
h2 = disposing(h2, this.experts.forward(h2, indices, topKWeights));
|
|
697
|
-
h2 = disposing(h2, this.postFfNorm2!.forward(h2));
|
|
698
|
-
indices.dispose();
|
|
699
|
-
topKWeights.dispose();
|
|
700
|
-
|
|
701
|
-
f = ops.add(h1, h2);
|
|
702
|
-
h1.dispose();
|
|
703
|
-
h2.dispose();
|
|
704
|
-
} else {
|
|
705
|
-
f = this.preFfNorm.forward(hMid);
|
|
706
|
-
f = disposing(f, this.mlp.forward(f));
|
|
707
|
-
}
|
|
708
|
-
f = disposing(f, this.postFfNorm.forward(f));
|
|
709
|
-
let h = ops.add(hMid, f); // hMid borrowed — NOT disposed here
|
|
710
|
-
f.dispose();
|
|
711
|
-
|
|
712
|
-
// per-layer input gating (reference gemma4_text DecoderLayer). gelu(gate)·pli
|
|
713
|
-
// is the second GeGLU site — the bit-exact spelled-out gelu+mul.
|
|
714
|
-
if (this.perLayerGate && this.perLayerProjection && this.postPerLayerNorm && perLayerInput) {
|
|
715
|
-
const res2 = h;
|
|
716
|
-
let gate = this.perLayerGate.forward(h);
|
|
717
|
-
gate = disposing(gate, ops.geluApprox(gate));
|
|
718
|
-
gate = disposing(gate, ops.mul(gate, perLayerInput));
|
|
719
|
-
gate = disposing(gate, this.perLayerProjection.forward(gate));
|
|
720
|
-
gate = disposing(gate, this.postPerLayerNorm.forward(gate));
|
|
721
|
-
h = ops.add(res2, gate);
|
|
722
|
-
res2.dispose();
|
|
723
|
-
gate.dispose();
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
if (this.layerScalar) h = disposing(h, ops.mul(h, this.layerScalar));
|
|
727
|
-
return h;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
export class Gemma4Model {
|
|
732
|
-
readonly config: ModelConfig;
|
|
733
|
-
/** Total weight-shard bytes (for the conditional wired-limit scope). */
|
|
734
|
-
readonly weightsBytes: number;
|
|
735
|
-
/** Weight-name prefix ("language_model.model" or "model"). */
|
|
736
|
-
readonly prefixBase: string;
|
|
737
|
-
/** Active LoRA adapters for the current generation (see LoraState). */
|
|
738
|
-
readonly loraState = new LoraState();
|
|
739
|
-
readonly embed: QuantizedEmbedding;
|
|
740
|
-
readonly layers: DecoderLayer[];
|
|
741
|
-
readonly finalNorm: RMSNorm;
|
|
742
|
-
readonly embedScale: number;
|
|
743
|
-
readonly windowSize: number;
|
|
744
|
-
/** Donor count: layers [0, numDonors) own caches; the rest share. */
|
|
745
|
-
readonly numDonors: number;
|
|
746
|
-
/** layer idx → donor layer idx whose fetched KV it consumes (self for donors). */
|
|
747
|
-
readonly previousKvs: number[];
|
|
748
|
-
/** layer idx → cache index (donors only; -1 for sharers). */
|
|
749
|
-
readonly cacheIndex: number[];
|
|
750
|
-
/** Set by the trainer to enable gradient checkpointing in forwardLayers
|
|
751
|
-
* (null = off; the non-checkpointed path is untouched). */
|
|
752
|
-
gradCkpt: GradCheckpointCtx | null = null;
|
|
753
|
-
/** Optional per-forward hidden-state tap for DSpark's DFlash H_ctx (paper
|
|
754
|
-
* Eq 2). null = off → byte-identical forward. `layers`: residual-output
|
|
755
|
-
* indices to capture (0..nLayers-1 = post-layer residual; nLayers =
|
|
756
|
-
* post-finalNorm). `pos`: if set, capture only [1,1,H] at that seq position
|
|
757
|
-
* (live decode); else the full [1,L,H] (offline regen). The caller owns and
|
|
758
|
-
* disposes the captured arrays. Additive/read-only → parity untouched. */
|
|
759
|
-
hiddenTap: { layers: Set<number>; pos?: number; captured: Map<number, MlxArray> } | null = null;
|
|
760
|
-
// e2b/e4b per-layer input machinery
|
|
761
|
-
readonly perLayerEmbed: QuantizedEmbedding | null;
|
|
762
|
-
readonly perLayerModelProjection: QuantizedLinear | null;
|
|
763
|
-
readonly perLayerProjectionNorm: RMSNorm | null;
|
|
764
|
-
readonly perLayerWidth: number;
|
|
765
|
-
|
|
766
|
-
constructor(weights: Weights, config: ModelConfig) {
|
|
767
|
-
const t = config.text;
|
|
768
|
-
if (t.hiddenSize === 0 || !config.text.tieWordEmbeddings)
|
|
769
|
-
throw new Error("only tied-embedding text configs are supported");
|
|
770
|
-
|
|
771
|
-
this.config = config;
|
|
772
|
-
this.weightsBytes = [...weights.shards.files.values()]
|
|
773
|
-
.reduce((a, f) => a + f.mmap.size, 0);
|
|
774
|
-
// gemma4_unified prefixes weights with language_model.; gemma4_text doesn't
|
|
775
|
-
const prefixBase = weights.has("language_model.model.embed_tokens.weight")
|
|
776
|
-
? "language_model.model" : "model";
|
|
777
|
-
this.prefixBase = prefixBase;
|
|
778
|
-
this.embed = QuantizedEmbedding.load(weights, `${prefixBase}.embed_tokens`, config);
|
|
779
|
-
|
|
780
|
-
this.numDonors = t.numHiddenLayers - t.numKvSharedLayers;
|
|
781
|
-
this.layers = Array.from(
|
|
782
|
-
{ length: t.numHiddenLayers },
|
|
783
|
-
(_, i) => new DecoderLayer(weights, config, prefixBase, i, i < this.numDonors),
|
|
784
|
-
);
|
|
785
|
-
// previous_kvs (reference Gemma4TextModel): sharer j uses the LAST
|
|
786
|
-
// donor of its own layer type
|
|
787
|
-
this.previousKvs = this.layers.map((_, i) => i);
|
|
788
|
-
if (t.numKvSharedLayers > 0) {
|
|
789
|
-
const lastByType: Record<string, number> = {};
|
|
790
|
-
for (let i = 0; i < this.numDonors; i++)
|
|
791
|
-
lastByType[this.layers[i]!.layerType] = i;
|
|
792
|
-
for (let j = this.numDonors; j < this.layers.length; j++) {
|
|
793
|
-
const donor = lastByType[this.layers[j]!.layerType];
|
|
794
|
-
if (donor === undefined)
|
|
795
|
-
throw new Error(`no donor layer of type ${this.layers[j]!.layerType}`);
|
|
796
|
-
this.previousKvs[j] = donor;
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
this.cacheIndex = this.layers.map((_, i) => (i < this.numDonors ? i : -1));
|
|
800
|
-
|
|
801
|
-
this.finalNorm = new RMSNorm(
|
|
802
|
-
weights.tensor(`${prefixBase}.norm.weight`),
|
|
803
|
-
t.rmsNormEps,
|
|
804
|
-
);
|
|
805
|
-
this.embedScale = Math.sqrt(t.hiddenSize);
|
|
806
|
-
this.windowSize = t.slidingWindow;
|
|
807
|
-
|
|
808
|
-
if (t.hiddenSizePerLayerInput > 0) {
|
|
809
|
-
this.perLayerEmbed = QuantizedEmbedding.load(weights, `${prefixBase}.embed_tokens_per_layer`, config);
|
|
810
|
-
this.perLayerModelProjection = QuantizedLinear.load(weights, `${prefixBase}.per_layer_model_projection`, config);
|
|
811
|
-
this.perLayerProjectionNorm = new RMSNorm(
|
|
812
|
-
weights.tensor(`${prefixBase}.per_layer_projection_norm.weight`), t.rmsNormEps,
|
|
813
|
-
);
|
|
814
|
-
this.perLayerWidth = t.hiddenSizePerLayerInput;
|
|
815
|
-
} else {
|
|
816
|
-
this.perLayerEmbed = this.perLayerModelProjection = null;
|
|
817
|
-
this.perLayerProjectionNorm = null;
|
|
818
|
-
this.perLayerWidth = 0;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/** LoRA-mountable linears, keyed by weight-file module path: optiq
|
|
823
|
-
* mount.py's 7 target suffixes PLUS the e2b/e4b per-layer-input
|
|
824
|
-
* projections — mlx-lm's trainer targets those on e4b and optiq's
|
|
825
|
-
* mount silently drops their trained weights (deviation documented in
|
|
826
|
-
* PLAN Phase 8 findings; we apply every adapter weight we can map).
|
|
827
|
-
* Expert pools stay non-targets (LoRASwitchLinear is future work). */
|
|
828
|
-
loraTargets(): Map<string, QuantizedLinear> {
|
|
829
|
-
const out = new Map<string, QuantizedLinear>();
|
|
830
|
-
for (let i = 0; i < this.layers.length; i++) {
|
|
831
|
-
const l = this.layers[i]!;
|
|
832
|
-
const p = `${this.prefixBase}.layers.${i}`;
|
|
833
|
-
const add = (suffix: string, lin: QuantizedLinear | null) => {
|
|
834
|
-
if (lin) out.set(`${p}.${suffix}`, lin);
|
|
835
|
-
};
|
|
836
|
-
add("self_attn.q_proj", l.attn.qProj);
|
|
837
|
-
add("self_attn.k_proj", l.attn.kProj);
|
|
838
|
-
add("self_attn.v_proj", l.attn.vProj);
|
|
839
|
-
add("self_attn.o_proj", l.attn.oProj);
|
|
840
|
-
add("mlp.gate_proj", l.mlp.gate);
|
|
841
|
-
add("mlp.up_proj", l.mlp.up);
|
|
842
|
-
add("mlp.down_proj", l.mlp.down);
|
|
843
|
-
add("per_layer_input_gate", l.perLayerGate);
|
|
844
|
-
add("per_layer_projection", l.perLayerProjection);
|
|
845
|
-
}
|
|
846
|
-
return out;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
/** Caches for donor layers only (sharers consume donors' fetched KV). */
|
|
850
|
-
makeCache(): Cache[] {
|
|
851
|
-
return this.layers.slice(0, this.numDonors).map((l) =>
|
|
852
|
-
l.layerType === "sliding_attention"
|
|
853
|
-
? new RotatingKVCache(this.windowSize)
|
|
854
|
-
: new KVCache(),
|
|
855
|
-
);
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
/** Computed model constants must retain their constructor arithmetic when
|
|
859
|
-
* a restored request enters compiled decode without a prefill first. */
|
|
860
|
-
materializeGraphConstants(): void {
|
|
861
|
-
const constants = this.layers.flatMap(layer => layer.attn.ropeFreqs ? [layer.attn.ropeFreqs] : []);
|
|
862
|
-
if (constants.length) ops.evalAll(constants);
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
/** ids [1, L] → final-norm hidden states [1, L, hidden]. */
|
|
866
|
-
forwardHidden(ids: MlxArray, cache: Cache[]): MlxArray {
|
|
867
|
-
let h = this.embed.encode(ids);
|
|
868
|
-
h = disposing(h, ops.mulScalar(h, this.embedScale));
|
|
869
|
-
return this.forwardLayers(h, cache, null, ids);
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
/** Per-layer inputs (reference _get_per_layer_inputs +
|
|
873
|
-
* _project_per_layer_inputs): [1, L, nLayers, perLayerWidth]. */
|
|
874
|
-
protected computePerLayerInputs(ids: MlxArray, hScaled: MlxArray): MlxArray {
|
|
875
|
-
const t = this.config.text;
|
|
876
|
-
const B = ids.shape[0]!;
|
|
877
|
-
const L = ids.shape[1]!;
|
|
878
|
-
let pli = this.perLayerEmbed!.encode(ids); // [B, L, nLayers*width]
|
|
879
|
-
pli = disposing(pli, ops.mulScalar(pli, Math.sqrt(this.perLayerWidth)));
|
|
880
|
-
pli = disposing(pli, ops.reshape(pli, [B, L, t.numHiddenLayers, this.perLayerWidth]));
|
|
881
|
-
|
|
882
|
-
let proj = this.perLayerModelProjection!.forward(hScaled);
|
|
883
|
-
proj = disposing(proj, ops.mulScalar(proj, 1 / Math.sqrt(t.hiddenSize)));
|
|
884
|
-
proj = disposing(proj, ops.reshape(proj, [B, L, t.numHiddenLayers, this.perLayerWidth]));
|
|
885
|
-
proj = disposing(proj, this.perLayerProjectionNorm!.forward(proj));
|
|
886
|
-
|
|
887
|
-
let combined = ops.add(proj, pli);
|
|
888
|
-
proj.dispose();
|
|
889
|
-
pli.dispose();
|
|
890
|
-
combined = disposing(combined, ops.mulScalar(combined, Math.SQRT1_2));
|
|
891
|
-
return combined;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
/** Pre-merged (unscaled) input embeddings → hidden states. Used by the
|
|
895
|
-
* multimodal (vision/audio) path; `bidir` (bool [L]) marks image tokens,
|
|
896
|
-
* which attend bidirectionally among themselves (use_bidirectional_attention:
|
|
897
|
-
* "vision" — text stays causal; audio prompts pass NO bidir mask, and a
|
|
898
|
-
* mixed image+audio prompt drops the image overlay too, §3.3 Q1 of
|
|
899
|
-
* docs/design/generic-model-support.md). `ids` ([1, L], the spliced token ids)
|
|
900
|
-
* is required for per-layer-input models (e2b/e4b): multimodal soft-token
|
|
901
|
-
* positions get token 0's per-layer embedding, and their tower content
|
|
902
|
-
* enters only through the projection term (the merged hidden). Matches
|
|
903
|
-
* optiq's frontend: `zeroed = where(text_mask, input_ids, 0)`.
|
|
904
|
-
*
|
|
905
|
-
* `multimodal` (bool [L]) is the per-layer-input ZEROING mask — the UNION
|
|
906
|
-
* of multimodal soft tokens (image | audio, §3.3 Q2), decoupled from the
|
|
907
|
-
* attention overlay: an audio-only prompt has no bidir mask but its audio
|
|
908
|
-
* positions must still be zeroed. When null (the legacy vision call
|
|
909
|
-
* shape), the zeroing mask falls back to `bidir` — vision passes the same
|
|
910
|
-
* image mask for both roles, so that path is unchanged. */
|
|
911
|
-
forwardEmbeddings(
|
|
912
|
-
embeds: MlxArray, cache: Cache[], bidir: MlxArray | null,
|
|
913
|
-
ids: MlxArray | null = null, multimodal: MlxArray | null = null,
|
|
914
|
-
): MlxArray {
|
|
915
|
-
const h = ops.mulScalar(embeds, this.embedScale);
|
|
916
|
-
if (this.perLayerWidth === 0) return this.forwardLayers(h, cache, bidir, null);
|
|
917
|
-
|
|
918
|
-
if (!ids)
|
|
919
|
-
throw new Error("per-layer-input multimodal models require token ids");
|
|
920
|
-
const zeroing = multimodal ?? bidir;
|
|
921
|
-
let plIds = ids;
|
|
922
|
-
if (zeroing) {
|
|
923
|
-
const cond = ops.reshape(zeroing, ids.shape);
|
|
924
|
-
const zero = ops.zeros(ids.shape, ids.dtype);
|
|
925
|
-
plIds = ops.where(cond, zero, ids);
|
|
926
|
-
cond.dispose();
|
|
927
|
-
zero.dispose();
|
|
928
|
-
}
|
|
929
|
-
const out = this.forwardLayers(h, cache, bidir, plIds);
|
|
930
|
-
if (plIds !== ids) plIds.dispose();
|
|
931
|
-
return out;
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
/** Store layer `i`'s residual stream `h` ([1,L,H]) if the hidden tap requests
|
|
935
|
-
* it. Copies (astype bf16, optional slice) so the loop's `h.dispose()` can't
|
|
936
|
-
* free the capture. No-op and no graph ops when the tap is null → the forward
|
|
937
|
-
* is byte-identical to an untapped run. (DSpark DFlash H_ctx.) */
|
|
938
|
-
protected captureLayer(i: number, h: MlxArray): void {
|
|
939
|
-
const tap = this.hiddenTap;
|
|
940
|
-
if (!tap || !tap.layers.has(i)) return;
|
|
941
|
-
const H = h.shape[2]!;
|
|
942
|
-
const src = tap.pos !== undefined ? h.slice([0, tap.pos, 0], [1, tap.pos + 1, H]) : h;
|
|
943
|
-
const copy = src.astype(this.embed.scales.dtype); // bf16, matches regen dump dtype
|
|
944
|
-
if (src !== h) src.dispose();
|
|
945
|
-
tap.captured.set(i, copy);
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
/** Consumes h. */
|
|
949
|
-
protected forwardLayers(
|
|
950
|
-
h0: MlxArray, cache: Cache[], bidir: MlxArray | null, ids: MlxArray | null,
|
|
951
|
-
): MlxArray {
|
|
952
|
-
const B = h0.shape[0]!;
|
|
953
|
-
const L = h0.shape[1]!;
|
|
954
|
-
let h = h0;
|
|
955
|
-
|
|
956
|
-
// one mask per layer type, computed from the first cache of that type
|
|
957
|
-
const masks = new Map<string, Mask>();
|
|
958
|
-
for (let i = 0; i < this.numDonors; i++) {
|
|
959
|
-
const type = this.layers[i]!.layerType;
|
|
960
|
-
if (!masks.has(type)) {
|
|
961
|
-
const window = type === "sliding_attention" ? this.windowSize : null;
|
|
962
|
-
if (bidir && L > 1) {
|
|
963
|
-
const c = cache[i]!;
|
|
964
|
-
if (c.offset !== 0)
|
|
965
|
-
throw new Error("bidirectional image masks require offset-0 prefill");
|
|
966
|
-
masks.set(type, bidirMask(L, window, bidir));
|
|
967
|
-
} else {
|
|
968
|
-
masks.set(type, cache[i]!.makeMask(L, window));
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
// per-layer inputs (e2b/e4b)
|
|
974
|
-
let perLayer: MlxArray | null = null;
|
|
975
|
-
if (this.perLayerWidth > 0) {
|
|
976
|
-
if (!ids) throw new Error("per-layer-input models require token ids");
|
|
977
|
-
perLayer = this.computePerLayerInputs(ids, h);
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
const intermediates: (SharedKv | null)[] = Array(this.layers.length).fill(null);
|
|
981
|
-
for (let i = 0; i < this.layers.length; i++) {
|
|
982
|
-
const layer = this.layers[i]!;
|
|
983
|
-
const ci = this.cacheIndex[i]!;
|
|
984
|
-
const sharedIn = ci === -1 ? (intermediates[this.previousKvs[i]!] ?? null) : null;
|
|
985
|
-
let pls: MlxArray | null = null;
|
|
986
|
-
if (perLayer) {
|
|
987
|
-
if (isCompiledTrace()) {
|
|
988
|
-
const start = ops.fromInt32([i], [1]);
|
|
989
|
-
pls = ops.sliceDynamic(perLayer, start, [2], [B, L, 1, this.perLayerWidth]);
|
|
990
|
-
start.dispose();
|
|
991
|
-
} else {
|
|
992
|
-
pls = perLayer.slice([0, 0, i, 0], [B, L, i + 1, this.perLayerWidth]);
|
|
993
|
-
}
|
|
994
|
-
const r = ops.reshape(pls, [B, L, this.perLayerWidth]);
|
|
995
|
-
pls.dispose();
|
|
996
|
-
pls = r;
|
|
997
|
-
}
|
|
998
|
-
const mask = masks.get(layer.layerType)!;
|
|
999
|
-
const layerCache = ci === -1 ? null : cache[ci]!;
|
|
1000
|
-
const { h: next, shared } = this.gradCkpt
|
|
1001
|
-
? this.runCheckpointedLayer(i, layer, h, mask, layerCache, sharedIn, pls)
|
|
1002
|
-
: layer.forward(h, mask, layerCache, sharedIn, pls);
|
|
1003
|
-
pls?.dispose();
|
|
1004
|
-
h.dispose();
|
|
1005
|
-
h = next;
|
|
1006
|
-
this.captureLayer(i, h); // DFlash H_ctx tap (no-op unless hiddenTap set)
|
|
1007
|
-
intermediates[i] = shared;
|
|
1008
|
-
}
|
|
1009
|
-
perLayer?.dispose();
|
|
1010
|
-
// dispose donor-fetched KV (each donor's shared entry exactly once)
|
|
1011
|
-
for (let i = 0; i < this.numDonors; i++) {
|
|
1012
|
-
const s = intermediates[i];
|
|
1013
|
-
if (!s) continue;
|
|
1014
|
-
Gemma4Model.disposeSharedKv(s);
|
|
1015
|
-
}
|
|
1016
|
-
for (const m of masks.values()) m.arr?.dispose();
|
|
1017
|
-
|
|
1018
|
-
h = disposing(h, this.finalNorm.forward(h));
|
|
1019
|
-
this.captureLayer(this.layers.length, h); // post-finalNorm sentinel (index = nLayers)
|
|
1020
|
-
return h;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
// --- Segmented-backward support (docs/design/orpo-training.md
|
|
1024
|
-
// §4 Phase B). These are ADDITIVE — forwardLayers is untouched. The segmented
|
|
1025
|
-
// driver builds masks + per-layer inputs ONCE, then drives runLayerRange per
|
|
1026
|
-
// segment, threading the KV-shared donor K/V across segment boundaries.
|
|
1027
|
-
|
|
1028
|
-
/** Donor layer indices that KV-sharing sharers reuse (e4b: {22,23} — the last
|
|
1029
|
-
* donor of each attention type; empty for dense models). Their fetched K/V
|
|
1030
|
-
* must survive across segment boundaries (saved as detached boundaries). */
|
|
1031
|
-
get reusedDonors(): Set<number> {
|
|
1032
|
-
return new Set(this.previousKvs.filter((d, i) => d !== i));
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
/** Per-layer-type training masks (offset-0, full-sequence, no bidir) — built
|
|
1036
|
-
* once, shared across all segments. Mirrors forwardLayers' mask construction.
|
|
1037
|
-
* Caller owns the returned arrays (dispose each mask.arr). */
|
|
1038
|
-
makeTrainingMasks(cache: Cache[], L: number): Map<string, Mask> {
|
|
1039
|
-
const masks = new Map<string, Mask>();
|
|
1040
|
-
for (let i = 0; i < this.numDonors; i++) {
|
|
1041
|
-
const type = this.layers[i]!.layerType;
|
|
1042
|
-
if (!masks.has(type)) {
|
|
1043
|
-
const window = type === "sliding_attention" ? this.windowSize : null;
|
|
1044
|
-
masks.set(type, cache[i]!.makeMask(L, window));
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
return masks;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
/** e2b/e4b training entry: the SCALED input embedding (the first segment
|
|
1051
|
-
* boundary) plus the per-layer-input tensor [B,L,nLayers,width] (a detached
|
|
1052
|
-
* boundary, sliced per layer inside runLayerRange). perLayer is null for dense
|
|
1053
|
-
* models. Caller owns both. */
|
|
1054
|
-
embedForSegmented(ids: MlxArray): { hScaled: MlxArray; perLayer: MlxArray | null } {
|
|
1055
|
-
let h = this.embed.encode(ids);
|
|
1056
|
-
h = disposing(h, ops.mulScalar(h, this.embedScale));
|
|
1057
|
-
const perLayer = this.perLayerWidth > 0 ? this.computePerLayerInputs(ids, h) : null;
|
|
1058
|
-
return { hScaled: h, perLayer };
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
/** Dispose a fetched donor K/V (plain or quantized) — mirrors forwardLayers'
|
|
1062
|
-
* donor cleanup. */
|
|
1063
|
-
private static disposeSharedKv(s: SharedKv): void {
|
|
1064
|
-
s.offsetArr?.dispose();
|
|
1065
|
-
if (s.kind === "view") {
|
|
1066
|
-
s.attention.dispose();
|
|
1067
|
-
} else if (s.kind === "plain") {
|
|
1068
|
-
s.keys.dispose();
|
|
1069
|
-
s.values.dispose();
|
|
1070
|
-
} else {
|
|
1071
|
-
for (const t of [s.keys, s.values])
|
|
1072
|
-
for (const a of [t.packed, t.scales, t.biases]) a.dispose();
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
/** Run decoder layers `[aIdx, bIdx)` on hidden `h`, returning the residual
|
|
1077
|
-
* stream after the last layer in the range (NO finalNorm) plus the fetched
|
|
1078
|
-
* K/V of any reused-donor layers in the range (`donorKvOut`, for later
|
|
1079
|
-
* segments). The input `h` is NEVER disposed (the caller / vjp owns the
|
|
1080
|
-
* boundary leaf); intra-range hidden intermediates and per-layer slices ARE
|
|
1081
|
-
* disposed. Non-reused donors' K/V are disposed; reused donors' K/V are
|
|
1082
|
-
* returned (caller threads + disposes). NOT gradient-checkpointed — the
|
|
1083
|
-
* segmentation IS the memory strategy.
|
|
1084
|
-
*
|
|
1085
|
-
* @param masks per-layer-type masks (makeTrainingMasks), shared, caller-owned.
|
|
1086
|
-
* @param perLayer the [B,L,nLayers,width] tensor (caller-owned) or null.
|
|
1087
|
-
* @param donorKvIn fetched K/V of reused donors from EARLIER segments, keyed
|
|
1088
|
-
* by donor layer index (caller-owned; not disposed here). */
|
|
1089
|
-
runLayerRange(
|
|
1090
|
-
h: MlxArray, aIdx: number, bIdx: number, cache: Cache[],
|
|
1091
|
-
masks: Map<string, Mask>, perLayer: MlxArray | null,
|
|
1092
|
-
donorKvIn: Map<number, SharedKv>,
|
|
1093
|
-
): { h: MlxArray; donorKvOut: Map<number, SharedKv> } {
|
|
1094
|
-
const donorKvOut = new Map<number, SharedKv>();
|
|
1095
|
-
if (aIdx >= bIdx) return { h, donorKvOut };
|
|
1096
|
-
const B = h.shape[0]!;
|
|
1097
|
-
const L = h.shape[1]!;
|
|
1098
|
-
const reused = this.reusedDonors;
|
|
1099
|
-
let cur = h;
|
|
1100
|
-
for (let i = aIdx; i < bIdx; i++) {
|
|
1101
|
-
const layer = this.layers[i]!;
|
|
1102
|
-
const ci = this.cacheIndex[i]!;
|
|
1103
|
-
const isDonor = ci !== -1;
|
|
1104
|
-
let sharedIn: SharedKv | null = null;
|
|
1105
|
-
if (!isDonor) {
|
|
1106
|
-
const donor = this.previousKvs[i]!;
|
|
1107
|
-
sharedIn = donorKvOut.get(donor) ?? donorKvIn.get(donor) ?? null;
|
|
1108
|
-
if (!sharedIn) throw new Error(`runLayerRange: donor ${donor} K/V not available for sharer ${i}`);
|
|
1109
|
-
}
|
|
1110
|
-
let pls: MlxArray | null = null;
|
|
1111
|
-
if (perLayer) {
|
|
1112
|
-
const sl = perLayer.slice([0, 0, i, 0], [B, L, i + 1, this.perLayerWidth]);
|
|
1113
|
-
pls = ops.reshape(sl, [B, L, this.perLayerWidth]);
|
|
1114
|
-
sl.dispose();
|
|
1115
|
-
}
|
|
1116
|
-
const mask = masks.get(layer.layerType)!;
|
|
1117
|
-
const { h: next, shared } = layer.forward(cur, mask, isDonor ? cache[ci]! : null, sharedIn, pls);
|
|
1118
|
-
pls?.dispose();
|
|
1119
|
-
if (i > aIdx) cur.dispose(); // keep the input boundary leaf; drop interiors
|
|
1120
|
-
cur = next;
|
|
1121
|
-
if (isDonor) {
|
|
1122
|
-
if (reused.has(i)) donorKvOut.set(i, shared);
|
|
1123
|
-
else Gemma4Model.disposeSharedKv(shared);
|
|
1124
|
-
}
|
|
1125
|
-
// sharer: `shared` === sharedIn (owned by donorKvIn/Out) — do not dispose.
|
|
1126
|
-
}
|
|
1127
|
-
return { h: cur, donorKvOut };
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
/** Run one layer under gradient checkpointing: its interior activations are
|
|
1131
|
-
* dropped and recomputed in the backward pass. The layer's LoRA params, h,
|
|
1132
|
-
* the mask array (if any), pls, and a sharer's donor KV are threaded as
|
|
1133
|
-
* explicit checkpoint inputs (the closure must be pure and self-contained —
|
|
1134
|
-
* it re-runs during the backward, and captured autograd primals would be
|
|
1135
|
-
* stale). A donor layer also emits its fetched K/V as checkpoint outputs so
|
|
1136
|
-
* the (retained, small) tensors reach the sharer layers downstream. */
|
|
1137
|
-
private runCheckpointedLayer(
|
|
1138
|
-
i: number, layer: DecoderLayer, h: MlxArray, mask: Mask,
|
|
1139
|
-
layerCache: Cache | null, sharedIn: SharedKv | null, pls: MlxArray | null,
|
|
1140
|
-
): { h: MlxArray; shared: SharedKv | null } {
|
|
1141
|
-
const ctx = this.gradCkpt!;
|
|
1142
|
-
const ll = ctx.byLayer.get(i) ?? { attn: [], mlp: [] };
|
|
1143
|
-
return ctx.splitMlp
|
|
1144
|
-
? this.runSplitCheckpointedLayer(layer, h, mask, layerCache, sharedIn, pls, ll, ctx)
|
|
1145
|
-
: this.runSingleCheckpointedLayer(layer, h, mask, layerCache, sharedIn, pls, ll, ctx);
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
/** One Checkpoint over the whole layer (the original path). */
|
|
1149
|
-
private runSingleCheckpointedLayer(
|
|
1150
|
-
layer: DecoderLayer, h: MlxArray, mask: Mask,
|
|
1151
|
-
layerCache: Cache | null, sharedIn: SharedKv | null, pls: MlxArray | null,
|
|
1152
|
-
ll: LayerLoras, ctx: GradCheckpointCtx,
|
|
1153
|
-
): { h: MlxArray; shared: SharedKv | null } {
|
|
1154
|
-
const lws = [...ll.attn, ...ll.mlp];
|
|
1155
|
-
const isSharer = layerCache === null;
|
|
1156
|
-
const sIn = isSharer && sharedIn && sharedIn.kind === "plain" ? sharedIn : null;
|
|
1157
|
-
const hasMaskArr = mask.arr !== null;
|
|
1158
|
-
const hasPls = pls !== null;
|
|
1159
|
-
const maskMode = mask.mode;
|
|
1160
|
-
|
|
1161
|
-
// Input order: [h, maskArr?, pls?, sharedInK?, sharedInV?, (lora_a, lora_b)*]
|
|
1162
|
-
const inputs: MlxArray[] = [h];
|
|
1163
|
-
if (hasMaskArr) inputs.push(mask.arr!);
|
|
1164
|
-
if (hasPls) inputs.push(pls!);
|
|
1165
|
-
if (sIn) inputs.push(sIn.keys, sIn.values);
|
|
1166
|
-
for (const lw of lws) inputs.push(lw.a, lw.b);
|
|
1167
|
-
|
|
1168
|
-
const ck = new Checkpoint((ins) => {
|
|
1169
|
-
let k = 0;
|
|
1170
|
-
const hIn = ins[k++]!;
|
|
1171
|
-
const mArr = hasMaskArr ? ins[k++]! : null;
|
|
1172
|
-
const plsIn = hasPls ? ins[k++]! : null;
|
|
1173
|
-
const sharedInRebuilt: SharedKv | null = sIn
|
|
1174
|
-
? { kind: "plain", keys: ins[k++]!, values: ins[k++]!, offset: 0 }
|
|
1175
|
-
: null;
|
|
1176
|
-
const saved = lws.map((lw) => [lw.a, lw.b] as [MlxArray, MlxArray]);
|
|
1177
|
-
for (const lw of lws) { lw.a = ins[k++]!; lw.b = ins[k++]!; }
|
|
1178
|
-
try {
|
|
1179
|
-
const out = layer.forward(hIn, { mode: maskMode, arr: mArr }, layerCache, sharedInRebuilt, plsIn);
|
|
1180
|
-
if (!isSharer && out.shared && out.shared.kind === "plain")
|
|
1181
|
-
return [out.h, out.shared.keys, out.shared.values];
|
|
1182
|
-
return [out.h];
|
|
1183
|
-
} finally {
|
|
1184
|
-
lws.forEach((lw, j) => { lw.a = saved[j]![0]; lw.b = saved[j]![1]; });
|
|
1185
|
-
}
|
|
1186
|
-
});
|
|
1187
|
-
ctx.keepAlive.push(ck);
|
|
1188
|
-
|
|
1189
|
-
const outs = ck.apply(inputs);
|
|
1190
|
-
const shared: SharedKv | null =
|
|
1191
|
-
!isSharer && outs.length === 3
|
|
1192
|
-
? { kind: "plain", keys: outs[1]!, values: outs[2]!, offset: 0 }
|
|
1193
|
-
: null;
|
|
1194
|
-
return { h: outs[0]!, shared };
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
/** Intra-layer MLP split (lever 4): TWO checkpoints — the attention sub-block
|
|
1198
|
-
* (forwardAttn: inputNorm → attn → residual add, threading the donor K/V) and
|
|
1199
|
-
* the MLP sub-block (forwardMlp: FFN + per-layer gate) — with the post-attn
|
|
1200
|
-
* residual `hMid` as the boundary. The backward recompute holds only one
|
|
1201
|
-
* sub-block's activations at a time, dropping per-layer peak from `attn+MLP`
|
|
1202
|
-
* to `max(attn,MLP)+hMid`. Attn LoRA / pls / donor-KV go to the attn ck; MLP
|
|
1203
|
-
* LoRA to the MLP ck. */
|
|
1204
|
-
private runSplitCheckpointedLayer(
|
|
1205
|
-
layer: DecoderLayer, h: MlxArray, mask: Mask,
|
|
1206
|
-
layerCache: Cache | null, sharedIn: SharedKv | null, pls: MlxArray | null,
|
|
1207
|
-
ll: LayerLoras, ctx: GradCheckpointCtx,
|
|
1208
|
-
): { h: MlxArray; shared: SharedKv | null } {
|
|
1209
|
-
const isSharer = layerCache === null;
|
|
1210
|
-
const sIn = isSharer && sharedIn && sharedIn.kind === "plain" ? sharedIn : null;
|
|
1211
|
-
const hasMaskArr = mask.arr !== null;
|
|
1212
|
-
const hasPls = pls !== null;
|
|
1213
|
-
const maskMode = mask.mode;
|
|
1214
|
-
const attnLws = ll.attn;
|
|
1215
|
-
const mlpLws = ll.mlp;
|
|
1216
|
-
|
|
1217
|
-
// --- attn sub-block checkpoint: [h, maskArr?, sharedInK?, sharedInV?, attnLora*]
|
|
1218
|
-
const attnInputs: MlxArray[] = [h];
|
|
1219
|
-
if (hasMaskArr) attnInputs.push(mask.arr!);
|
|
1220
|
-
if (sIn) attnInputs.push(sIn.keys, sIn.values);
|
|
1221
|
-
for (const lw of attnLws) attnInputs.push(lw.a, lw.b);
|
|
1222
|
-
|
|
1223
|
-
const attnCk = new Checkpoint((ins) => {
|
|
1224
|
-
let k = 0;
|
|
1225
|
-
const hIn = ins[k++]!;
|
|
1226
|
-
const mArr = hasMaskArr ? ins[k++]! : null;
|
|
1227
|
-
const sharedInRebuilt: SharedKv | null = sIn
|
|
1228
|
-
? { kind: "plain", keys: ins[k++]!, values: ins[k++]!, offset: 0 }
|
|
1229
|
-
: null;
|
|
1230
|
-
const saved = attnLws.map((lw) => [lw.a, lw.b] as [MlxArray, MlxArray]);
|
|
1231
|
-
for (const lw of attnLws) { lw.a = ins[k++]!; lw.b = ins[k++]!; }
|
|
1232
|
-
try {
|
|
1233
|
-
const out = layer.forwardAttn(hIn, { mode: maskMode, arr: mArr }, layerCache, sharedInRebuilt);
|
|
1234
|
-
if (!isSharer && out.shared && out.shared.kind === "plain")
|
|
1235
|
-
return [out.h, out.shared.keys, out.shared.values];
|
|
1236
|
-
return [out.h];
|
|
1237
|
-
} finally {
|
|
1238
|
-
attnLws.forEach((lw, j) => { lw.a = saved[j]![0]; lw.b = saved[j]![1]; });
|
|
1239
|
-
}
|
|
1240
|
-
});
|
|
1241
|
-
ctx.keepAlive.push(attnCk);
|
|
1242
|
-
const attnOuts = attnCk.apply(attnInputs);
|
|
1243
|
-
const hMid = attnOuts[0]!;
|
|
1244
|
-
const shared: SharedKv | null =
|
|
1245
|
-
!isSharer && attnOuts.length === 3
|
|
1246
|
-
? { kind: "plain", keys: attnOuts[1]!, values: attnOuts[2]!, offset: 0 }
|
|
1247
|
-
: null;
|
|
1248
|
-
|
|
1249
|
-
// --- MLP sub-block checkpoint: [hMid, pls?, mlpLora*] -> [hOut]
|
|
1250
|
-
const mlpInputs: MlxArray[] = [hMid];
|
|
1251
|
-
if (hasPls) mlpInputs.push(pls!);
|
|
1252
|
-
for (const lw of mlpLws) mlpInputs.push(lw.a, lw.b);
|
|
1253
|
-
|
|
1254
|
-
const mlpCk = new Checkpoint((ins) => {
|
|
1255
|
-
let k = 0;
|
|
1256
|
-
const hMidIn = ins[k++]!;
|
|
1257
|
-
const plsIn = hasPls ? ins[k++]! : null;
|
|
1258
|
-
const saved = mlpLws.map((lw) => [lw.a, lw.b] as [MlxArray, MlxArray]);
|
|
1259
|
-
for (const lw of mlpLws) { lw.a = ins[k++]!; lw.b = ins[k++]!; }
|
|
1260
|
-
try {
|
|
1261
|
-
return [layer.forwardMlp(hMidIn, plsIn)];
|
|
1262
|
-
} finally {
|
|
1263
|
-
mlpLws.forEach((lw, j) => { lw.a = saved[j]![0]; lw.b = saved[j]![1]; });
|
|
1264
|
-
}
|
|
1265
|
-
});
|
|
1266
|
-
ctx.keepAlive.push(mlpCk);
|
|
1267
|
-
const mlpOuts = mlpCk.apply(mlpInputs);
|
|
1268
|
-
hMid.dispose(); // boundary leaf consumed by the MLP ck (mlx retains it in-graph)
|
|
1269
|
-
|
|
1270
|
-
return { h: mlpOuts[0]!, shared };
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
/** hidden [1, L, hidden] → softcapped logits [1, L, vocab]. */
|
|
1274
|
-
logitsFromHidden(h: MlxArray): MlxArray {
|
|
1275
|
-
let logits = this.embed.asLinear(h);
|
|
1276
|
-
const softcap = this.config.text.finalLogitSoftcapping;
|
|
1277
|
-
if (softcap !== null)
|
|
1278
|
-
logits = disposing(logits, compiledGegluActive() && !isCompiledTrace()
|
|
1279
|
-
? compiledLogitSoftcap(logits, softcap) // fused, matches @mx.compile logit_softcap
|
|
1280
|
-
: logitSoftcap(logits, softcap));
|
|
1281
|
-
return logits;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
/** tokens → logits [1, L, vocab] (compat path used by parity tests). */
|
|
1285
|
-
forward(tokens: number[] | MlxArray, cache: Cache[]): MlxArray {
|
|
1286
|
-
const ids = Array.isArray(tokens)
|
|
1287
|
-
? ops.fromInt32(tokens, [1, tokens.length])
|
|
1288
|
-
: tokens;
|
|
1289
|
-
const h = this.forwardHidden(ids, cache);
|
|
1290
|
-
if (Array.isArray(tokens)) ids.dispose();
|
|
1291
|
-
const logits = this.logitsFromHidden(h);
|
|
1292
|
-
h.dispose();
|
|
1293
|
-
return logits;
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
/** Greedy generation (parity harness); returns generated token ids. */
|
|
1297
|
-
generate(promptTokens: number[], maxTokens: number, eosIds: number[] = []): number[] {
|
|
1298
|
-
const cache = this.makeCache();
|
|
1299
|
-
const out: number[] = [];
|
|
1300
|
-
try {
|
|
1301
|
-
let tokens = promptTokens;
|
|
1302
|
-
for (let step = 0; step < maxTokens; step++) {
|
|
1303
|
-
const logits = this.forward(tokens, cache);
|
|
1304
|
-
const next = argmaxLastPosition(logits);
|
|
1305
|
-
logits.dispose();
|
|
1306
|
-
if (eosIds.includes(next)) break;
|
|
1307
|
-
out.push(next);
|
|
1308
|
-
tokens = [next];
|
|
1309
|
-
}
|
|
1310
|
-
} finally {
|
|
1311
|
-
for (const c of cache) c.dispose();
|
|
1312
|
-
}
|
|
1313
|
-
return out;
|
|
1314
|
-
}
|
|
1315
|
-
}
|