@tryhamster/gerbil 1.11.4 → 1.13.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 +104 -0
- package/dist/{architectures-DmZMEFsA.mjs → architectures-DHwj9AQD.mjs} +50 -38
- package/dist/architectures-DHwj9AQD.mjs.map +1 -0
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +11 -0
- package/dist/browser/index.js.map +1 -1
- package/dist/cli.mjs +8 -8
- package/dist/cli.mjs.map +1 -1
- package/dist/{defaults-DfGx4d1m.mjs → defaults-B0aQZJTM.mjs} +3 -2
- package/dist/defaults-B0aQZJTM.mjs.map +1 -0
- package/dist/frameworks/express.mjs +1 -1
- package/dist/frameworks/fastify.mjs +1 -1
- package/dist/frameworks/hono.mjs +1 -1
- package/dist/frameworks/next.d.mts +2 -2
- package/dist/frameworks/next.mjs +1 -1
- package/dist/frameworks/trpc.mjs +1 -1
- package/dist/gerbil-Bw6do78d.mjs +4 -0
- package/dist/{gerbil-5_80K0gA.d.mts → gerbil-CD_skiL3.d.mts} +2 -2
- package/dist/{gerbil-5_80K0gA.d.mts.map → gerbil-CD_skiL3.d.mts.map} +1 -1
- package/dist/{gerbil-CYVmU8sQ.mjs → gerbil-DAz_a4Jh.mjs} +33 -13
- package/dist/gerbil-DAz_a4Jh.mjs.map +1 -0
- package/dist/gpu/hooks.d.mts +1 -1
- package/dist/gpu/hooks.mjs +1 -1
- package/dist/gpu/index.d.mts +2 -2
- package/dist/gpu/index.mjs +5 -5
- package/dist/{gpu-CrzjQHv2.mjs → gpu-CU_Mldk0.mjs} +1959 -198
- package/dist/gpu-CU_Mldk0.mjs.map +1 -0
- package/dist/index-B3tjyDJI.d.mts.map +1 -1
- package/dist/{index-h8TDu1qm.d.mts → index-Fj2XkP-o.d.mts} +1946 -938
- package/dist/index-Fj2XkP-o.d.mts.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +6 -6
- package/dist/index.mjs.map +1 -1
- package/dist/integrations/ai-sdk.mjs +1 -1
- package/dist/integrations/langchain.mjs +1 -1
- package/dist/integrations/llamaindex.mjs +1 -1
- package/dist/integrations/mcp.d.mts +2 -2
- package/dist/integrations/mcp.mjs +4 -4
- package/dist/{mcp-CAsD7eCj.mjs → mcp-CdMvLQ_9.mjs} +3 -3
- package/dist/{mcp-CAsD7eCj.mjs.map → mcp-CdMvLQ_9.mjs.map} +1 -1
- package/dist/{moonshine-stt-BXoZaHJE.mjs → moonshine-stt-COJeK2Zb.mjs} +6348 -2000
- package/dist/moonshine-stt-COJeK2Zb.mjs.map +1 -0
- package/dist/moonshine-stt-Dp4j5JEB.mjs +4 -0
- package/dist/{one-liner-ppgw4jHH.mjs → one-liner-CMiGNWJ7.mjs} +2 -2
- package/dist/{one-liner-ppgw4jHH.mjs.map → one-liner-CMiGNWJ7.mjs.map} +1 -1
- package/dist/repl-BhaLCJFb.mjs +9 -0
- package/dist/skills/index.d.mts +4 -4
- package/dist/skills/index.mjs +3 -3
- package/dist/{skills-CQa1Gshd.mjs → skills-BXybFWlG.mjs} +2 -2
- package/dist/{skills-CQa1Gshd.mjs.map → skills-BXybFWlG.mjs.map} +1 -1
- package/dist/tune/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/architectures-DmZMEFsA.mjs.map +0 -1
- package/dist/defaults-DfGx4d1m.mjs.map +0 -1
- package/dist/gerbil-CTefAwKp.mjs +0 -4
- package/dist/gerbil-CYVmU8sQ.mjs.map +0 -1
- package/dist/gpu-CrzjQHv2.mjs.map +0 -1
- package/dist/index-h8TDu1qm.d.mts.map +0 -1
- package/dist/moonshine-stt-BXoZaHJE.mjs.map +0 -1
- package/dist/moonshine-stt-DZVnKgPO.mjs +0 -4
- package/dist/repl-C0Ew7_Z-.mjs +0 -9
|
@@ -814,1038 +814,1947 @@ declare function quantizeKaniBackbone(graph: ModelGraph, weights: Map<string, {
|
|
|
814
814
|
shape: number[];
|
|
815
815
|
}>): void;
|
|
816
816
|
//#endregion
|
|
817
|
-
//#region src/gpu/
|
|
818
|
-
/**
|
|
819
|
-
* CPU-side token sampling from logits.
|
|
820
|
-
*
|
|
821
|
-
* Applies temperature, top-k, and top-p (nucleus) filtering,
|
|
822
|
-
* then samples from the resulting probability distribution.
|
|
823
|
-
*
|
|
824
|
-
* Uses typed arrays and min-heap for zero-allocation top-K selection.
|
|
825
|
-
* For vocab_size ~152K with topK=50, this avoids creating 152K JS tuples.
|
|
826
|
-
*/
|
|
827
|
-
interface SamplingParams {
|
|
828
|
-
temperature?: number;
|
|
829
|
-
topK?: number;
|
|
830
|
-
topP?: number;
|
|
831
|
-
repetitionPenalty?: number;
|
|
832
|
-
}
|
|
833
|
-
//#endregion
|
|
834
|
-
//#region src/gpu/vision-preprocess.d.ts
|
|
835
|
-
/**
|
|
836
|
-
* Host-side vision preprocessing for the Qwen3.5 ViT.
|
|
837
|
-
*
|
|
838
|
-
* The learned position embeddings (bilinear-interpolated over the patch grid)
|
|
839
|
-
* and the 2D rotary cos/sin tables are functions of the image grid (t, h, w)
|
|
840
|
-
* ONLY — not of the model weights or pixel values. They are cheap to compute on
|
|
841
|
-
* the CPU and fed to the GPU graph as input activations, keeping the graph to
|
|
842
|
-
* weight-dependent math while staying byte-identical to HF transformers.
|
|
843
|
-
*
|
|
844
|
-
* Ports (verified against transformers 5.12 vision_utils.py + modeling_qwen3_5):
|
|
845
|
-
* - get_vision_bilinear_indices_and_weights → buildPosEmbeds()
|
|
846
|
-
* - get_vision_position_ids → buildPositionIds()
|
|
847
|
-
* - Qwen3_5VisionRotaryEmbedding → buildRotaryCosSin()
|
|
848
|
-
*
|
|
849
|
-
* Patch ordering: both the pos-embed gather and the position ids reorder patches
|
|
850
|
-
* into spatial_merge_size×spatial_merge_size groups, matching the HF image
|
|
851
|
-
* processor's output ordering, so the merger's [N,h]→[N/u,h*u] reshape lines up.
|
|
852
|
-
*/
|
|
853
|
-
interface VisionGridConfig {
|
|
854
|
-
hiddenSize: number;
|
|
855
|
-
numHeads: number;
|
|
856
|
-
numPositionEmbeddings: number;
|
|
857
|
-
spatialMergeSize: number;
|
|
858
|
-
ropeTheta?: number;
|
|
859
|
-
}
|
|
860
|
-
interface VisionPositionTensors {
|
|
861
|
-
/** [N, hidden_size] bilinear-interpolated learned position embeddings. */
|
|
862
|
-
posEmbeds: Float32Array;
|
|
863
|
-
/** [N, head_dim] rotary cosines. */
|
|
864
|
-
cos: Float32Array;
|
|
865
|
-
/** [N, head_dim] rotary sines. */
|
|
866
|
-
sin: Float32Array;
|
|
867
|
-
numPatches: number;
|
|
868
|
-
}
|
|
869
|
-
/**
|
|
870
|
-
* Build bilinear-interpolated learned position embeddings [N, hidden].
|
|
871
|
-
* posEmbedTable is the raw pos_embed.weight [num_position_embeddings, hidden].
|
|
872
|
-
*/
|
|
873
|
-
declare function buildPosEmbeds(gridTHW: [number, number, number], posEmbedTable: Float32Array, cfg: VisionGridConfig): Float32Array;
|
|
874
|
-
/**
|
|
875
|
-
* Build the reordered (row, col) position ids [N, 2] for rotary, matching
|
|
876
|
-
* get_vision_position_ids.
|
|
877
|
-
*/
|
|
878
|
-
declare function buildPositionIds(gridTHW: [number, number, number], merge: number): Int32Array;
|
|
879
|
-
/**
|
|
880
|
-
* Build rotary cos/sin tables [N, head_dim] from position ids, matching
|
|
881
|
-
* Qwen3_5VisionRotaryEmbedding + the cat((rotary, rotary)) in VisionModel.forward.
|
|
882
|
-
*
|
|
883
|
-
* rotary_pos_emb(position_ids) = (position_ids[..,None] * inv_freq).flatten(1)
|
|
884
|
-
* where inv_freq has length (head_dim/2)/2 = head_dim/4, computed over dim=head_dim/2.
|
|
885
|
-
* For each token the two position components (h, w) each produce head_dim/4 freqs,
|
|
886
|
-
* concatenated → head_dim/2, then duplicated → head_dim for cos/sin.
|
|
887
|
-
*/
|
|
888
|
-
declare function buildRotaryCosSin(positionIds: Int32Array, headDim: number, theta?: number): {
|
|
889
|
-
cos: Float32Array;
|
|
890
|
-
sin: Float32Array;
|
|
891
|
-
numPatches: number;
|
|
892
|
-
};
|
|
893
|
-
/**
|
|
894
|
-
* Build all host position tensors for a single image grid in one call.
|
|
895
|
-
*/
|
|
896
|
-
declare function buildVisionPositionTensors(gridTHW: [number, number, number], posEmbedTable: Float32Array, cfg: VisionGridConfig): VisionPositionTensors;
|
|
897
|
-
interface Gemma4VisionGridConfig {
|
|
898
|
-
hiddenSize: number;
|
|
899
|
-
numHeads: number;
|
|
900
|
-
headDim: number;
|
|
901
|
-
ropeTheta: number;
|
|
902
|
-
poolingKernelSize: number;
|
|
903
|
-
}
|
|
904
|
-
interface Gemma4VisionPositionTensors {
|
|
905
|
-
/** [N, hidden] axial position embeddings (table[0][x] + table[1][y]). */
|
|
906
|
-
posEmbeds: Float32Array;
|
|
907
|
-
/** [N, headDim] axial rotary cosines. */
|
|
908
|
-
cos: Float32Array;
|
|
909
|
-
/** [N, headDim] axial rotary sines. */
|
|
910
|
-
sin: Float32Array;
|
|
911
|
-
/** [Np, N] average-pooling matrix (1/k² in-cell, 0 elsewhere). */
|
|
912
|
-
poolMatrix: Float32Array;
|
|
913
|
-
/** number of patches N (= gridH*gridW). */
|
|
914
|
-
numPatches: number;
|
|
915
|
-
/** number of pooled (soft) tokens Np (= ceil(gridH/k)*ceil(gridW/k)). */
|
|
916
|
-
numPooled: number;
|
|
917
|
-
}
|
|
918
|
-
/**
|
|
919
|
-
* Build axial learned position embeddings [N, hidden] from the [2, posSize, hidden]
|
|
920
|
-
* table: pos[p] = table[0][x_p] + table[1][y_p]. Direct lookup, no interpolation
|
|
921
|
-
* (HF F.embedding on clamped positions).
|
|
922
|
-
*/
|
|
923
|
-
declare function buildGemma4PosEmbeds(gridH: number, gridW: number, posEmbedTable: Float32Array,
|
|
924
|
-
// [2, posSize, hidden] flattened
|
|
925
|
-
hidden: number, posSize: number): Float32Array;
|
|
926
|
-
/**
|
|
927
|
-
* Build the 2D axial rotary cos/sin tables [N, headDim].
|
|
928
|
-
* spatial_dim = headDim / 2; inv_freq[j] = 1/theta^((2j)/spatial_dim), j in [0, spatial_dim/2)
|
|
929
|
-
* per spatial dim: f = pos * inv_freq (spatial_dim/2 values); emb = cat(f, f) (spatial_dim values)
|
|
930
|
-
* cos/sin = cat([emb_x, emb_y]) → headDim values, layout [fx,fx,fy,fy].
|
|
931
|
-
* Applied with the global-half rotate_half kernel (ApplyRotaryEmb), which computes
|
|
932
|
-
* out = x*cos + rotate_half(x)*sin element-wise — exact for this layout.
|
|
933
|
-
*/
|
|
934
|
-
declare function buildGemma4RotaryCosSin(gridH: number, gridW: number, headDim: number, theta: number): {
|
|
935
|
-
cos: Float32Array;
|
|
936
|
-
sin: Float32Array;
|
|
937
|
-
};
|
|
938
|
-
/**
|
|
939
|
-
* Build the [Np, N] average-pooling matrix for k×k spatial pooling over the real
|
|
940
|
-
* (unpadded) grid, matching modeling_gemma4's kernel_idxs/one_hot pooling:
|
|
941
|
-
* cell(p) = floor(x_p/k) + ceil(gridW/k) * floor(y_p/k)
|
|
942
|
-
* poolMatrix[cell, p] = 1/k² (so pooled = poolMatrix @ hidden = mean over the k×k block)
|
|
943
|
-
* Np = ceil(gridH/k) * ceil(gridW/k). Each pooled cell averages exactly the patches
|
|
944
|
-
* that fall in it (edge cells with fewer than k² patches still divide by k², matching
|
|
945
|
-
* HF's fixed 1/k² normalization).
|
|
946
|
-
*/
|
|
947
|
-
declare function buildGemma4PoolMatrix(gridH: number, gridW: number, k: number): {
|
|
948
|
-
poolMatrix: Float32Array;
|
|
949
|
-
numPooled: number;
|
|
950
|
-
};
|
|
817
|
+
//#region src/gpu/executor.d.ts
|
|
951
818
|
/**
|
|
952
|
-
*
|
|
953
|
-
*
|
|
819
|
+
* Safari/Metal workaround: shader variant alternation.
|
|
820
|
+
* Metal caches argument buffers per compiled function. When consecutive dispatches
|
|
821
|
+
* use the same WGSL code (same Metal function), Metal reuses the previous dispatch's
|
|
822
|
+
* argument buffer, ignoring setBindGroup(). We alternate between variant 0/1 of each
|
|
823
|
+
* shader (prepending `const _MV: u32 = Xu;`) to force different Metal function
|
|
824
|
+
* specializations, preventing argument buffer reuse.
|
|
954
825
|
*/
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
/**
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
826
|
+
interface ExecutorOptions {
|
|
827
|
+
maxSeqLen: number;
|
|
828
|
+
/** KV cache kernel strategy. Defaults to "native-f16" when not specified. */
|
|
829
|
+
kvMode?: KvMode;
|
|
830
|
+
/**
|
|
831
|
+
* WebKit only: dispatches per command buffer, with at most one command
|
|
832
|
+
* buffer in flight (awaited). 1 (default) is the proven-correct floor on
|
|
833
|
+
* iPad; larger values are faster if this WebKit version keeps storage
|
|
834
|
+
* writes visible across dispatches within one submission. Sweepable via
|
|
835
|
+
* the ?group=N URL param.
|
|
836
|
+
*/
|
|
837
|
+
webkitGroupSize?: number;
|
|
963
838
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
* the patch grid is ≤ max_soft_tokens·k² patches and H,W divisible by k·patch,
|
|
967
|
-
* rescale ×1/255 (no normalize), patchify row-major into [N, 3·16·16].
|
|
968
|
-
*
|
|
969
|
-
* @param pixels row-major HWC RGB (0..255), length width*height*3.
|
|
970
|
-
*/
|
|
971
|
-
declare function preprocessImageGemma4(pixels: Float32Array | Uint8ClampedArray | Uint8Array, width: number, height: number, maxSoftTokens?: number, poolingKernelSize?: number, patchSize?: number): Gemma4PreprocessedImage;
|
|
972
|
-
interface ImageProcessorConfig {
|
|
973
|
-
patchSize: number;
|
|
974
|
-
temporalPatchSize: number;
|
|
975
|
-
mergeSize: number;
|
|
976
|
-
imageMean: [number, number, number];
|
|
977
|
-
imageStd: [number, number, number];
|
|
978
|
-
/** rescale factor applied to raw 0..255 pixels before normalization (1/255). */
|
|
979
|
-
rescaleFactor: number;
|
|
980
|
-
/** min total pixels after resize (shortest_edge). */
|
|
981
|
-
minPixels: number;
|
|
982
|
-
/** max total pixels after resize (longest_edge). */
|
|
983
|
-
maxPixels: number;
|
|
839
|
+
interface ForwardResult {
|
|
840
|
+
logits: Float32Array;
|
|
984
841
|
}
|
|
985
|
-
|
|
986
|
-
interface
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
842
|
+
/** One tensor reference of a decode-graph node, with its pool-buffer identity. */
|
|
843
|
+
interface DecodeGraphTensorRef {
|
|
844
|
+
tensor: string;
|
|
845
|
+
/** Declared shape (may contain symbolic dims "T" / "L_max"). */
|
|
846
|
+
shape: Array<number | string>;
|
|
847
|
+
/** Shape resolved at T=1, seqPos = dump position. */
|
|
848
|
+
resolvedShape: number[] | null;
|
|
849
|
+
/** Label of the GPUBuffer this tensor is bound to. Pooled activation
|
|
850
|
+
* buffers are shared by many tensors — equal labels here = aliasing. */
|
|
851
|
+
buffer: string | null;
|
|
852
|
+
bufferBytes: number | null;
|
|
853
|
+
}
|
|
854
|
+
/** One ORIGINAL (pre-fusion) graph node covered by a dispatch entry. */
|
|
855
|
+
interface DecodeGraphNode {
|
|
856
|
+
id: string;
|
|
857
|
+
opType: string;
|
|
858
|
+
inputs: DecodeGraphTensorRef[];
|
|
859
|
+
outputs: DecodeGraphTensorRef[];
|
|
860
|
+
attributes: Record<string, number | string | boolean>;
|
|
861
|
+
}
|
|
862
|
+
/** One entry of the post-fusion decode dispatch list (= one GPU dispatch). */
|
|
863
|
+
interface DecodeGraphEntry {
|
|
864
|
+
index: number;
|
|
865
|
+
nodeId: string;
|
|
866
|
+
/** Anchor node's opType (fused entries report their anchor constituent). */
|
|
867
|
+
opType: string;
|
|
868
|
+
fused: boolean;
|
|
869
|
+
/** Pre-fusion constituent node ids, execution order. Null when unfused. */
|
|
870
|
+
fusedNodeIds: string[] | null;
|
|
871
|
+
/** Storage (non-uniform) bindings the kernel declares. */
|
|
872
|
+
storageBindings: number;
|
|
873
|
+
totalBindings: number;
|
|
874
|
+
dispatchSize: [number, number, number] | null;
|
|
875
|
+
paramsBytes: number;
|
|
876
|
+
/** Uniform params as raw u32 words (f32 fields appear as their bit pattern). */
|
|
877
|
+
paramsU32: number[];
|
|
878
|
+
/** Buffer labels in bind-group order, when captured at entry creation. */
|
|
879
|
+
bindBufferLabels: string[] | null;
|
|
880
|
+
/** The original graph nodes this dispatch covers (1 for unfused entries). */
|
|
881
|
+
nodes: DecodeGraphNode[];
|
|
882
|
+
}
|
|
883
|
+
interface DecodeGraphDump {
|
|
884
|
+
dispatchesPerToken: number;
|
|
885
|
+
unfusedDispatchesPerToken: number;
|
|
886
|
+
maxStorageBuffersPerShaderStage: number;
|
|
887
|
+
/** seqPos the shapes/params were resolved at (T=1 decode step). */
|
|
888
|
+
dumpSeqPos: number;
|
|
889
|
+
/** Every GPU buffer referenced by the decode entries. */
|
|
890
|
+
buffers: Array<{
|
|
891
|
+
label: string;
|
|
892
|
+
bytes: number;
|
|
893
|
+
kind: string;
|
|
894
|
+
}>;
|
|
895
|
+
entries: DecodeGraphEntry[];
|
|
991
896
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
*/
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
*
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
/**
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
*/
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
/**
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1053
|
-
|
|
1054
|
-
|
|
897
|
+
declare class Executor {
|
|
898
|
+
private ctx;
|
|
899
|
+
private graph;
|
|
900
|
+
/** Output tensor names of this executor's graph (read-only; used by non-LM
|
|
901
|
+
* codec executors that need to locate the decoded-audio output tensor). */
|
|
902
|
+
get graphOutputs(): string[];
|
|
903
|
+
private weightBuffers;
|
|
904
|
+
private activationBuffers;
|
|
905
|
+
private ssmStateBuffers;
|
|
906
|
+
private kvCacheBuffers;
|
|
907
|
+
/** Pre-allocated input_ids buffer (maxSeqLen * 4 bytes). */
|
|
908
|
+
private inputIdsBuffer;
|
|
909
|
+
/**
|
|
910
|
+
* CPU-resident Per-Layer-Embeddings (PLE) source for Gemma 4. The PLE table
|
|
911
|
+
* (`embed_tokens_per_layer`, [vocab, L*256]) is ~1.17GB at 4-bit and is kept
|
|
912
|
+
* OFF the GPU. Each forward step we gather + dequantize only the rows for the
|
|
913
|
+
* actual input tokens and upload a tiny [T, L*256] f32 buffer. See setPleSource.
|
|
914
|
+
*/
|
|
915
|
+
private pleSource;
|
|
916
|
+
/** Reusable scratch for the dequantized PLE rows (resized on demand). */
|
|
917
|
+
private pleScratch;
|
|
918
|
+
/**
|
|
919
|
+
* One-time promise that materializes a cache-backed PLE table into heap. The
|
|
920
|
+
* table is read from CacheStorage on the FIRST forward — i.e. AFTER the GPU
|
|
921
|
+
* weight upload has completed, so the ~1.17 GB does not stack on top of the
|
|
922
|
+
* upload's transient allocations at the load-time memory high-water mark.
|
|
923
|
+
*/
|
|
924
|
+
private pleReady;
|
|
925
|
+
/**
|
|
926
|
+
* Dummy GPU buffer bound to an otherwise-aliasing storage-read-write slot.
|
|
927
|
+
* Used by RoPE-Q-only nodes (Gemma 4 KV-shared layers) whose node lists the
|
|
928
|
+
* same tensor as input and output: the RoPE kernel always declares two
|
|
929
|
+
* read_write bindings (q, k), but with num_kv_heads=0 the k slot is never
|
|
930
|
+
* touched. WebGPU still rejects two read_write bindings aliasing one buffer,
|
|
931
|
+
* so we bind this throwaway buffer to the unused k slot. Allocated lazily.
|
|
932
|
+
*/
|
|
933
|
+
private bindingScratchBuffer;
|
|
934
|
+
/** Readback buffer for logits. */
|
|
935
|
+
private logitsReadback;
|
|
936
|
+
/** GPU buffer for argmax result (1 u32). */
|
|
937
|
+
private argmaxResultBuffer;
|
|
938
|
+
/** Readback buffer for argmax result (1 u32). */
|
|
939
|
+
private argmaxReadback;
|
|
940
|
+
/** Readback ring for pipelined greedy decode (created lazily). */
|
|
941
|
+
private decodeReadbacks;
|
|
942
|
+
/** Window size K (GERBIL_DECODE_WINDOW). 0 = off. */
|
|
943
|
+
readonly decodeWindowK: number;
|
|
944
|
+
/** Readback ring for decode windows (4·K bytes per slot; created lazily). */
|
|
945
|
+
private windowReadbacks;
|
|
946
|
+
/**
|
|
947
|
+
* Per-step uniform/bind-group variants for the few seq-pos-dependent decode
|
|
948
|
+
* entries (RoPE position, KV-append offset, attention S). Keyed by nodeId;
|
|
949
|
+
* index k holds the variant used at window step k (1..K-1 — step 0 uses the
|
|
950
|
+
* entry's base uniform). Created lazily, rewritten per window pre-submit.
|
|
951
|
+
*/
|
|
952
|
+
private windowVariants;
|
|
953
|
+
/**
|
|
954
|
+
* Staging buffer for uniform param updates.
|
|
955
|
+
* Safari/Metal has weaker visibility guarantees for queue.writeBuffer() to
|
|
956
|
+
* UNIFORM buffers — early writes get dropped when hundreds are queued.
|
|
957
|
+
* Instead, we pack all params into this STORAGE staging buffer (1 writeBuffer),
|
|
958
|
+
* then use encoder.copyBufferToBuffer to distribute to each uniform buffer.
|
|
959
|
+
* Copies are GPU-sequenced and guaranteed to complete before compute passes.
|
|
960
|
+
*/
|
|
961
|
+
private uniformStagingBuffer;
|
|
962
|
+
private uniformStagingCapacity;
|
|
963
|
+
/** Dispatch entries for prefill (M>1): uses tiled matmul kernels. */
|
|
964
|
+
private dispatchEntries;
|
|
965
|
+
/** Dispatch entries for decode (M=1): uses K-parallel matvec kernels. */
|
|
966
|
+
private decodeEntries;
|
|
967
|
+
/**
|
|
968
|
+
* Column-concatenated INT4 weight buffers built by the Mamba-front fusion
|
|
969
|
+
* (qkv|a|b|z packed into one q/scales/zeros set per layer). Retained here so
|
|
970
|
+
* they outlive the fusion pass and are not garbage-collected while the fused
|
|
971
|
+
* bind groups still reference them.
|
|
972
|
+
*/
|
|
973
|
+
private mambaFrontConcatBuffers;
|
|
974
|
+
/** Per-node decode entries captured BEFORE fusion (the overlay base). */
|
|
975
|
+
private decodeEntriesUnfused;
|
|
976
|
+
/** True while a runtime adapter is applied. */
|
|
977
|
+
private loraActive;
|
|
978
|
+
/** Decode entries (unfused base + spliced LoRA dispatches). */
|
|
979
|
+
private decodeEntriesLoRA;
|
|
980
|
+
/** Prefill entries (base + spliced LoRA dispatches). */
|
|
981
|
+
private prefillEntriesLoRA;
|
|
982
|
+
/** GPU buffers owned by the current adapter (A/B factors + tmp); freed on swap. */
|
|
983
|
+
private loraBuffers;
|
|
984
|
+
/** Argmax dispatch entry (created in initBindGroups). */
|
|
985
|
+
private argmaxEntry;
|
|
986
|
+
/** True when running on Safari/WebKit (needs multi-encoder submit). */
|
|
987
|
+
readonly needsMultiEncoder: boolean;
|
|
988
|
+
private maxSeqLen;
|
|
989
|
+
private kvMode;
|
|
990
|
+
private seqPos;
|
|
991
|
+
private webkitGroupSize;
|
|
992
|
+
private readonly pagedKV;
|
|
993
|
+
private kvBlockManager;
|
|
994
|
+
/** Lockstep batch width (GERBIL_BATCH). 0 = batching off. */
|
|
995
|
+
readonly batchSize: number;
|
|
996
|
+
/** Block-table row stride — set for any paged mode. */
|
|
997
|
+
private maxBlocksPerSeq;
|
|
998
|
+
/** Block-table row the single-sequence paths target (batch prefill slot). */
|
|
999
|
+
private activeSeqSlot;
|
|
1000
|
+
/** Batched dispatch entries (built once in initBindGroups when batch on). */
|
|
1001
|
+
private batchEntries;
|
|
1002
|
+
/** Per-slot SSM/conv state pools: slot b's state at offset b*slotBytes. */
|
|
1003
|
+
private batchSsmPools;
|
|
1004
|
+
/** Batch-widened tail buffers (SliceLastRow output + logits at [N, ...]). */
|
|
1005
|
+
private batchTailBuffers;
|
|
1006
|
+
private batchPositionsBuffer;
|
|
1007
|
+
private batchSeqLensBuffer;
|
|
1008
|
+
private batchSampledBuffer;
|
|
1009
|
+
private batchReadbacks;
|
|
1010
|
+
private batchArgmaxEntry;
|
|
1011
|
+
/** JS-side authoritative per-slot token counts (positions source). */
|
|
1012
|
+
private batchSeqLens;
|
|
1013
|
+
/** Scratch for per-step positions/seq_lens uploads. */
|
|
1014
|
+
private batchPositionsScratch;
|
|
1015
|
+
private batchSeqLensScratch;
|
|
1016
|
+
/** Cached batch-resolved shapes (static: T == batchSize every step). */
|
|
1017
|
+
private batchShapes;
|
|
1018
|
+
/** True once the batched-argmax uniform has been written this session. */
|
|
1019
|
+
private batchArgmaxParamsWritten;
|
|
1020
|
+
/** True when GERBIL_BATCH_LORA=1 rode in on an active batch path. */
|
|
1021
|
+
readonly batchLoraEnabled: boolean;
|
|
1022
|
+
/** Registered adapters (expanded deltas), indexed by adapter id. */
|
|
1023
|
+
private batchAdapterDeltas;
|
|
1024
|
+
/** Per-lane adapter ids (u32; 0xFFFFFFFF = bare base). */
|
|
1025
|
+
private batchAdapterIdsBuffer;
|
|
1026
|
+
/** GPU buffers owned by the batch-LoRA overlay (factors, maps, tmp, uniforms). */
|
|
1027
|
+
private batchLoraBuffers;
|
|
1028
|
+
/** Batch entries snapshotted BEFORE the batch fusion passes (overlay base). */
|
|
1029
|
+
private batchEntriesUnfused;
|
|
1030
|
+
/** Batch entries with the per-lane LoRA dispatches spliced in. */
|
|
1031
|
+
private batchEntriesLoRA;
|
|
1032
|
+
/** True while at least one lane carries an adapter (set by setBatchLaneAdapters). */
|
|
1033
|
+
private batchLoraActive;
|
|
1034
|
+
private profileEnabled;
|
|
1035
|
+
private readonly profileData;
|
|
1036
|
+
private querySet;
|
|
1037
|
+
private queryResolveBuf;
|
|
1038
|
+
private queryReadbackBuf;
|
|
1039
|
+
constructor(ctx: GPUContext, graph: ModelGraph, options: ExecutorOptions);
|
|
1040
|
+
/**
|
|
1041
|
+
* Register the CPU-resident Gemma 4 PLE table. The quantized table is kept in
|
|
1042
|
+
* JS memory (NOT uploaded to a GPU buffer), and {@link forward} gathers +
|
|
1043
|
+
* dequantizes only the rows for the current input tokens each step, uploading a
|
|
1044
|
+
* small [T, width] f32 buffer into `targetTensor`. This is what keeps Gemma 4
|
|
1045
|
+
* mobile-viable: resident GPU memory is just the active transformer weights.
|
|
1046
|
+
*/
|
|
1047
|
+
setPleSource(src: {
|
|
1048
|
+
packed: Uint32Array;
|
|
1049
|
+
scales: Float32Array;
|
|
1050
|
+
zeros: Float32Array;
|
|
1051
|
+
width: number;
|
|
1052
|
+
groupSize: number;
|
|
1053
|
+
targetTensor: string;
|
|
1054
|
+
cache?: {
|
|
1055
|
+
cacheName: string;
|
|
1056
|
+
packedKey: string;
|
|
1057
|
+
scalesKey: string;
|
|
1058
|
+
zerosKey: string;
|
|
1059
|
+
packedLen: number;
|
|
1060
|
+
};
|
|
1061
|
+
}): void;
|
|
1062
|
+
/**
|
|
1063
|
+
* True when this model streams a CPU-resident Per-Layer-Embedding table
|
|
1064
|
+
* (Gemma 4): each decode step must gather+upload the PLE rows for the ACTUAL
|
|
1065
|
+
* input token id on the CPU before the forward runs. The GPU-chained pipelined
|
|
1066
|
+
* decode path and forwardArgmax both bypass CPU token knowledge, so they leave
|
|
1067
|
+
* `ple_embed_out` stale and must NOT be used for these models — only the
|
|
1068
|
+
* per-step forward() path (which calls streamPleRows) is correct. See generate().
|
|
1069
|
+
*/
|
|
1070
|
+
hasPleSource(): boolean;
|
|
1071
|
+
/**
|
|
1072
|
+
* Materialize a cache-backed PLE table into heap exactly once. Deferred to the
|
|
1073
|
+
* first forward (after GPU upload) so the ~1.17 GB table is not co-resident
|
|
1074
|
+
* with the upload's transient allocations during the load-time memory peak.
|
|
1075
|
+
*/
|
|
1076
|
+
private ensurePleLoaded;
|
|
1077
|
+
/**
|
|
1078
|
+
* Gather + dequantize the PLE rows for `inputIds` and upload them into the
|
|
1079
|
+
* target activation buffer. Touches only T rows (T*width floats) — the full
|
|
1080
|
+
* [vocab, width] quantized table never goes to the GPU.
|
|
1081
|
+
*/
|
|
1082
|
+
private streamPleRows;
|
|
1083
|
+
/**
|
|
1084
|
+
* Stream weights to the GPU one tensor at a time, pulling each from the
|
|
1085
|
+
* `WeightSource` only when it is about to be uploaded and dropping the
|
|
1086
|
+
* reference immediately afterward.
|
|
1087
|
+
*
|
|
1088
|
+
* This is the property that bounds peak JS heap: with a cache-backed source
|
|
1089
|
+
* (browser/mobile), only ONE tensor's bytes are materialized in heap at a time
|
|
1090
|
+
* (read from CacheStorage in `source.get()`), uploaded to its GPU buffer, then
|
|
1091
|
+
* released before the next tensor is fetched. The whole model is never co-
|
|
1092
|
+
* resident in heap. With a heap-backed source (Node/desktop), behavior matches
|
|
1093
|
+
* the old Map path (the source deletes each entry as it is consumed).
|
|
1094
|
+
*
|
|
1095
|
+
* Accepts either a `WeightSource` (new, streamed/async) or a plain Map
|
|
1096
|
+
* (back-compat for callers that build a Map directly, e.g. Kani/Moonshine).
|
|
1097
|
+
*/
|
|
1098
|
+
uploadWeights(source: WeightSource | Map<string, {
|
|
1099
|
+
data: ArrayBufferView;
|
|
1100
|
+
shape: number[];
|
|
1101
|
+
}>): Promise<void>;
|
|
1102
|
+
/**
|
|
1103
|
+
* Synchronous Map upload (Node/desktop and the Kani/Moonshine sub-executors,
|
|
1104
|
+
* which build small heap Maps). Deletes each entry as it is consumed to free
|
|
1105
|
+
* the JS-side bytes once they are GPU-resident. Safe to call from a constructor.
|
|
1106
|
+
*/
|
|
1107
|
+
uploadWeightsMap(weights: Map<string, {
|
|
1108
|
+
data: ArrayBufferView;
|
|
1109
|
+
shape: number[];
|
|
1110
|
+
}>): void;
|
|
1111
|
+
/**
|
|
1112
|
+
* Build all pipelines and bind groups. Call ONCE after uploadWeights().
|
|
1113
|
+
*
|
|
1114
|
+
* Creates two dispatch entry arrays:
|
|
1115
|
+
* - dispatchEntries: tiled matmul for prefill (any M)
|
|
1116
|
+
* - decodeEntries: K-parallel matvec for decode (M=1)
|
|
1117
|
+
*/
|
|
1118
|
+
initBindGroups(): void;
|
|
1119
|
+
/** True while a runtime LoRA overlay is applied. */
|
|
1120
|
+
get hasRuntimeAdapter(): boolean;
|
|
1121
|
+
/** Decode entries in effect for the current adapter state. */
|
|
1122
|
+
private get activeDecodeEntries();
|
|
1123
|
+
/**
|
|
1124
|
+
* Apply a runtime LoRA overlay on the STATIC base. For each delta whose target
|
|
1125
|
+
* maps to a base linear (MatMulInt4 / MatMul) with matching dims, uploads the
|
|
1126
|
+
* f32 A/B factors and splices two low-rank dispatches — `tmp = A·x`, then
|
|
1127
|
+
* `y += scaling·B·tmp` — right after that node in both the decode and prefill
|
|
1128
|
+
* paths, so the corrected output is live before any consumer reads it.
|
|
1129
|
+
*
|
|
1130
|
+
* Rebuilds the overlay from scratch each call (this is how adapters hot-swap);
|
|
1131
|
+
* the base weight buffers and bind groups are never touched or re-uploaded.
|
|
1132
|
+
* Pass an empty list (or call {@link clearRuntimeLoRA}) to drop the overlay.
|
|
1133
|
+
*/
|
|
1134
|
+
/** Find the base linear (MatMulInt4 / MatMul) node whose weight is `key`. */
|
|
1135
|
+
private findBaseLinear;
|
|
1136
|
+
/**
|
|
1137
|
+
* Expand any fused query+gate delta into separate `q_proj` + `attn_gate`
|
|
1138
|
+
* deltas so it can splice onto a deinterleaved runtime graph.
|
|
1139
|
+
*
|
|
1140
|
+
* Qwen3.5-style gated attention ships `q_proj.weight` fused as
|
|
1141
|
+
* `[2·num_heads·head_dim, hidden]` (per-head: query rows then gate rows); the
|
|
1142
|
+
* model loader splits it into two runtime linears (`q_proj`, `attn_gate`). An
|
|
1143
|
+
* adapter trained on the fused checkpoint carries a single `q_proj` delta whose
|
|
1144
|
+
* out-dim is 2× either split node, so it matches neither. When BOTH split
|
|
1145
|
+
* linears exist and the delta is exactly their combined width, split it via the
|
|
1146
|
+
* same per-head row deinterleave the loader uses; otherwise the delta passes
|
|
1147
|
+
* through untouched (e.g. a base that never split the projection).
|
|
1148
|
+
*/
|
|
1149
|
+
private expandFusedQGateDeltas;
|
|
1150
|
+
applyRuntimeLoRA(deltas: LoRADelta[]): {
|
|
1151
|
+
applied: number;
|
|
1152
|
+
skipped: number;
|
|
1153
|
+
};
|
|
1154
|
+
/** Drop the runtime LoRA overlay and free its GPU buffers. Base is untouched. */
|
|
1155
|
+
clearRuntimeLoRA(): void;
|
|
1156
|
+
/** Build the [down, up] low-rank dispatch pair for one adapted node. */
|
|
1157
|
+
private buildLoraPair;
|
|
1158
|
+
/** Copy a base entry list, splicing each node's LoRA pair in right after it. */
|
|
1159
|
+
private spliceLoraOverlay;
|
|
1160
|
+
/** Original constituent node ids a (possibly fused) decode entry covers. */
|
|
1161
|
+
private coveredNodeIds;
|
|
1162
|
+
/**
|
|
1163
|
+
* Build the decode entry list for an active adapter with SELECTIVE unfusing.
|
|
1164
|
+
* Every fused group that covers no adapted linear is kept exactly as the
|
|
1165
|
+
* no-adapter fast path runs it (full SwiGLU/dual/gated fusion, no numerics
|
|
1166
|
+
* change). Only the groups that cover an adapted node fall back to their
|
|
1167
|
+
* pre-fusion per-node dispatches (pulled from {@link decodeEntriesUnfused}),
|
|
1168
|
+
* with the low-rank `[down, up]` pair spliced right after each adapted node so
|
|
1169
|
+
* its corrected output is live before any consumer reads it. Replaying the
|
|
1170
|
+
* exact pre-fusion entries reproduces the validated unfused numerics for the
|
|
1171
|
+
* adapted groups while leaving all other groups on the fused path.
|
|
1172
|
+
*/
|
|
1173
|
+
private buildSelectiveLoraDecode;
|
|
1174
|
+
/**
|
|
1175
|
+
* Run a forward pass. Uses matvec kernels for M=1 (decode), tiled for M>1 (prefill).
|
|
1176
|
+
*
|
|
1177
|
+
* `opts.readLogits: false` skips the logits copy + readback entirely (the
|
|
1178
|
+
* returned logits array is empty) and returns right after submission — used
|
|
1179
|
+
* by the scheduler's chunked prefill, where only the FINAL chunk's logits
|
|
1180
|
+
* are ever sampled. seqPos still advances by T.
|
|
1181
|
+
*/
|
|
1182
|
+
forward(inputIds: Uint32Array, opts?: {
|
|
1183
|
+
readLogits?: boolean;
|
|
1184
|
+
}): Promise<ForwardResult>;
|
|
1185
|
+
/**
|
|
1186
|
+
* Profiling variant of the desktop dispatch path: one compute pass per dispatch,
|
|
1187
|
+
* each bracketed by timestamp queries, so we get per-op GPU time. Accumulates
|
|
1188
|
+
* into profileData by opType. Only runs under GERBIL_PROFILE with timestamp-query
|
|
1189
|
+
* support — slower than the batched path (it measures relative cost, not tok/s).
|
|
1190
|
+
*/
|
|
1191
|
+
private runProfiledDispatches;
|
|
1192
|
+
/** Per-opType GPU time (ns) + dispatch count accumulated by GERBIL_PROFILE, hottest first. */
|
|
1193
|
+
getProfile(): Array<{
|
|
1194
|
+
opType: string;
|
|
1195
|
+
ns: number;
|
|
1196
|
+
count: number;
|
|
1197
|
+
}>;
|
|
1198
|
+
/** Clear accumulated profiler data (e.g. to drop warm-up tokens). */
|
|
1199
|
+
resetProfile(): void;
|
|
1200
|
+
/** GPU dispatches per decode token (post-fusion). On mobile this drives the
|
|
1201
|
+
* submit-group count = ceil(dispatchCount / webkitGroupSize). */
|
|
1202
|
+
get decodeDispatchCount(): number;
|
|
1203
|
+
/** Device limit that gates the INT4 projection fusions (they need ≥9). If a
|
|
1204
|
+
* device caps at 8 the dual/gated/swiglu-gated INT4 fusions silently fall back,
|
|
1205
|
+
* inflating the decode dispatch count. */
|
|
1206
|
+
get maxStorageBuffers(): number;
|
|
1207
|
+
/**
|
|
1208
|
+
* Build a machine-readable dump of the post-fusion decode entry list (the
|
|
1209
|
+
* exact per-token dispatch sequence), with per-tensor pool-buffer identities
|
|
1210
|
+
* so pooled-aliasing hazards are visible offline. Debug/analysis only — never
|
|
1211
|
+
* called on any hot path; reached via the GERBIL_DUMP_GRAPH hook at the end
|
|
1212
|
+
* of {@link initBindGroups} or directly by tooling.
|
|
1213
|
+
*/
|
|
1214
|
+
dumpDecodeGraph(dumpSeqPos?: number): DecodeGraphDump;
|
|
1215
|
+
/**
|
|
1216
|
+
* Profile ONE real decode step: times the actual `decodeEntries` (the kernels
|
|
1217
|
+
* the pipelined greedy benchmark runs) with per-dispatch timestamps. Timing is
|
|
1218
|
+
* token-independent, so pass any valid id; runs un-pipelined with a synchronous
|
|
1219
|
+
* timestamp readback (measurement only, not for production decode). Argmax (one
|
|
1220
|
+
* tiny dispatch) is intentionally excluded — it is not a hotspot target.
|
|
1221
|
+
*/
|
|
1222
|
+
profileDecodeStep(tokenId: number): Promise<void>;
|
|
1223
|
+
/**
|
|
1224
|
+
* Profile ONE batched decode step (GERBIL_BATCH): times each batchEntries
|
|
1225
|
+
* dispatch with timestamp queries, accumulating GPU ns per opType into the
|
|
1226
|
+
* same profile as profileDecodeStep. Measurement-only (un-pipelined, CPU
|
|
1227
|
+
* tokens). Call after prefilling the batch slots; advances every slot by
|
|
1228
|
+
* one position like a real step.
|
|
1229
|
+
*/
|
|
1230
|
+
profileBatchDecodeStep(tokens: Uint32Array<ArrayBuffer>): Promise<void>;
|
|
1231
|
+
/**
|
|
1232
|
+
* Run a single forward pass over `inputIds` and read back the L2-normalized
|
|
1233
|
+
* embedding vector. Requires an embedding graph (one whose output tensor is
|
|
1234
|
+
* "embedding", produced by the last-token-pool + L2-norm tail).
|
|
1235
|
+
*
|
|
1236
|
+
* Always runs in a fresh-state single pass (caller should reset() first):
|
|
1237
|
+
* embeddings are non-autoregressive, so the whole sequence is one prefill.
|
|
1238
|
+
*/
|
|
1239
|
+
embed(inputIds: Uint32Array): Promise<Float32Array>;
|
|
1240
|
+
/**
|
|
1241
|
+
* Generic one-shot dispatch for a non-LM graph: run every entry once over a
|
|
1242
|
+
* single forward and read back `elemCount` f32 elements of the named output
|
|
1243
|
+
* tensor. Used to execute the NanoCodec decoder graph (codes→PCM) — its ops use
|
|
1244
|
+
* concrete lengths and it has no "logits" output, so the normal forward()
|
|
1245
|
+
* logits-readback path does not apply. Caller writes any inputs (e.g. audio_codes)
|
|
1246
|
+
* via writeInput() and reset()s first.
|
|
1247
|
+
*/
|
|
1248
|
+
runGraphOutput(outputName: string, elemCount: number): Promise<Float32Array>;
|
|
1055
1249
|
/**
|
|
1056
|
-
*
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1250
|
+
* Autoregressive step for a multi-output decoder graph (e.g. Parler-TTS's 9 codebook
|
|
1251
|
+
* lm_heads). Dispatches the whole graph ONCE at the current seqPos (so the per-layer
|
|
1252
|
+
* KVCacheAppend writes at the right cache row), reads back each named output, then
|
|
1253
|
+
* advances seqPos by `tokens` (default 1, for a prefilled prompt span pass tokens=T).
|
|
1254
|
+
* The caller writes inputs (dec_input_embed + frozen cross-attn K/V) and reset()s
|
|
1255
|
+
* before the first step. Unlike forward(), there is no single "logits" output —
|
|
1256
|
+
* the caller decodes each codebook row itself.
|
|
1059
1257
|
*/
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
}
|
|
1064
|
-
declare const OUTETTS_ASSETS: {
|
|
1065
|
-
/** Folded DAC.speech.v1.0 encoder+decoder weights (prep-dac-speech.py output). */
|
|
1066
|
-
readonly codecWeightsURL: "https://pub-0522ecec25fc412bb35a523f3e3f63c6.r2.dev/tryhamster/gerbil-outetts-assets/dac-speech-folded.safetensors";
|
|
1067
|
-
/** Base URL for the preset speaker JSONs (`{name}.json`). */
|
|
1068
|
-
readonly speakersBaseURL: "https://pub-0522ecec25fc412bb35a523f3e3f63c6.r2.dev/tryhamster/gerbil-outetts-assets/speakers";
|
|
1069
|
-
/** The default preset voice. */
|
|
1070
|
-
readonly defaultVoice: "en-female-1-neutral";
|
|
1071
|
-
};
|
|
1072
|
-
/**
|
|
1073
|
-
* Bundled OuteTTS preset voices.
|
|
1074
|
-
*
|
|
1075
|
-
* Provenance (honest labelling):
|
|
1076
|
-
* - `en-female-1-neutral` is the ONE AUTHENTIC OuteAI voice — the OuteTTS v3 bundled
|
|
1077
|
-
* default speaker (default_speakers/en-female-1-neutral), copied verbatim.
|
|
1078
|
-
* - `gerbil-voice-2..6` are DERIVED voices: distinct timbres produced by running
|
|
1079
|
-
* pitch/tempo-varied renderings of that authentic clip back through the REAL
|
|
1080
|
-
* DAC.speech encode + prosody pipeline (scripts/engine/stage-outetts-assets.py).
|
|
1081
|
-
* They are genuine DAC-encoded OuteSpeakers, but they are NOT OuteAI's own
|
|
1082
|
-
* `en-male-*` voices, so they are not labelled as such. (Earlier builds mislabelled
|
|
1083
|
-
* them as `en-male-2-neutral` etc.; renamed for honesty.)
|
|
1084
|
-
*/
|
|
1085
|
-
declare const OUTETTS_PRESET_VOICES: readonly ["en-female-1-neutral", "gerbil-voice-2", "gerbil-voice-3", "gerbil-voice-4", "gerbil-voice-5", "gerbil-voice-6"];
|
|
1086
|
-
/**
|
|
1087
|
-
* True when `repo`/`architecture` identify a text-to-speech checkpoint that must
|
|
1088
|
-
* run through a dedicated native TTS engine (KaniTTS, OuteTTS, or Parler-TTS)
|
|
1089
|
-
* rather than the single-graph base text loader. Mirrors the routing used by
|
|
1090
|
-
* `WebGPUEngine.speak()` / `Gerbil.ensureNativeTTSEngine`:
|
|
1091
|
-
* - Parler → encoder-decoder (`ParlerTTSForConditionalGeneration`)
|
|
1092
|
-
* - OuteTTS → Qwen3-0.6B codec-LM
|
|
1093
|
-
* - Kani → conv-backbone codec-LM (`KaniTTS2ForCausalLM`, or the Apache
|
|
1094
|
-
* default which is a plain `Lfm2ForCausalLM` — hence the repo-name
|
|
1095
|
-
* gate; the architecture string alone does not identify it).
|
|
1096
|
-
* At `create()` time only the repo is known (the architecture is discovered
|
|
1097
|
-
* during load), so the repo-name signals below cover all three DEFAULT_MODELS
|
|
1098
|
-
* and conventionally-named TTS repos; the architecture arg is honored when a
|
|
1099
|
-
* caller already has it.
|
|
1100
|
-
*/
|
|
1101
|
-
declare function isTTSRepo(repo?: string, architecture?: string): boolean;
|
|
1102
|
-
/** Resolve the model repo for a set of options, falling back to the defaults. */
|
|
1103
|
-
declare function resolveDefaultRepo(opts: {
|
|
1104
|
-
repo?: string;
|
|
1105
|
-
embedding?: boolean;
|
|
1106
|
-
enableVision?: boolean;
|
|
1107
|
-
}): string;
|
|
1108
|
-
//#endregion
|
|
1109
|
-
//#region src/gpu/architectures/gemma4_vision.d.ts
|
|
1110
|
-
interface Gemma4VisionGraphInfo {
|
|
1111
|
-
hiddenSize: number;
|
|
1112
|
-
numHeads: number;
|
|
1113
|
-
headDim: number;
|
|
1114
|
-
depth: number;
|
|
1115
|
-
intermediateSize: number;
|
|
1116
|
-
textHidden: number;
|
|
1117
|
-
patchSize: number;
|
|
1118
|
-
patchDim: number;
|
|
1119
|
-
poolingKernelSize: number;
|
|
1120
|
-
ropeTheta: number;
|
|
1121
|
-
rmsNormEps: number;
|
|
1122
|
-
}
|
|
1123
|
-
/**
|
|
1124
|
-
* Resolve the Gemma 4 vision dims from a raw HF config. Accepts either the
|
|
1125
|
-
* top-level config (reads `.vision_config` + `.text_config.hidden_size`) or a
|
|
1126
|
-
* bare vision_config (then `textHidden` falls back to the projector row count if
|
|
1127
|
-
* present, else hidden). Family-general — no E2B constants.
|
|
1128
|
-
*/
|
|
1129
|
-
declare function resolveGemma4VisionInfo(rawConfig: Record<string, unknown>): Gemma4VisionGraphInfo;
|
|
1130
|
-
/**
|
|
1131
|
-
* Dequantize an MLX affine-int4 weight to a plain f32 [rows, cols] matrix.
|
|
1132
|
-
* MLX packs 8 int4 values per u32 (low-nibble first); each group of `groupSize`
|
|
1133
|
-
* columns shares one scale + bias: w[r,c] = scale[r, c/gs] * q + bias[r, c/gs].
|
|
1134
|
-
* Used for the Gemma 4 multimodal projector (`embed_vision.embedding_projection`)
|
|
1135
|
-
* in MLX-4bit checkpoints, where (unlike the BF16 ViT body) the projector is int4.
|
|
1136
|
-
*/
|
|
1137
|
-
declare function dequantizeMLXProjection(packed: Uint32Array, scales: Float32Array, biases: Float32Array, rows: number, cols: number, groupSize: number): Float32Array;
|
|
1138
|
-
/**
|
|
1139
|
-
* If the Gemma 4 multimodal projector arrived as an MLX affine-int4 triplet
|
|
1140
|
-
* (`embed_vision.embedding_projection.{weight(U32), scales, biases}`), dequantize
|
|
1141
|
-
* it in-place to a plain f32 `embed_vision.embedding_projection.weight` and drop
|
|
1142
|
-
* the scales/biases, so the vision graph's plain MatMul on the projector works for
|
|
1143
|
-
* MLX-4bit checkpoints too. No-op for BF16 (HF) checkpoints (weight already f32).
|
|
1144
|
-
*/
|
|
1145
|
-
declare function dequantizeGemma4VisionProjection(weights: Map<string, {
|
|
1146
|
-
data: ArrayBufferView;
|
|
1147
|
-
shape: number[];
|
|
1148
|
-
}>, groupSize: number, rows: number, cols: number): void;
|
|
1149
|
-
/**
|
|
1150
|
-
* Patch the ClippedMatMul nodes of a Gemma 4 vision graph with the calibrated clip
|
|
1151
|
-
* scalars from the checkpoint (Gemma4ClippableLinear's per-tensor input/output
|
|
1152
|
-
* min/max buffers), then drop those scalar tensors from the weights map so the
|
|
1153
|
-
* vision executor doesn't try to upload them as GPU buffers. Call BEFORE
|
|
1154
|
-
* VisionExecutor.uploadWeights(). Missing scalars default to ±inf (clip = identity),
|
|
1155
|
-
* so a checkpoint without calibration still loads.
|
|
1156
|
-
*/
|
|
1157
|
-
declare function patchGemma4VisionClips(graph: ModelGraph, weights: Map<string, {
|
|
1158
|
-
data: ArrayBufferView;
|
|
1159
|
-
shape: number[];
|
|
1160
|
-
}>): void;
|
|
1161
|
-
/**
|
|
1162
|
-
* Build the Gemma 4 ViT graph. Shaped by symbolic "N" (number of patches, runtime)
|
|
1163
|
-
* and "Np" (number of pooled tokens = ceil(grid_h/k)·ceil(grid_w/k), runtime),
|
|
1164
|
-
* resolved from input tensor dims — like the Qwen ViT's "N"/"Nm".
|
|
1165
|
-
*/
|
|
1166
|
-
declare function generateGemma4VisionGraph(rawConfig: Record<string, unknown>): ModelGraph;
|
|
1167
|
-
//#endregion
|
|
1168
|
-
//#region src/gpu/architectures/kani_tts.d.ts
|
|
1169
|
-
/** Parsed KaniTTS2 backbone config (the LFM2 dims + the TTS-specific fields). */
|
|
1170
|
-
interface KaniConfig {
|
|
1171
|
-
textVocabSize: number;
|
|
1172
|
-
vocabSize: number;
|
|
1173
|
-
tokensPerFrame: number;
|
|
1174
|
-
audioStep: number;
|
|
1175
|
-
useLearnableRope: boolean;
|
|
1176
|
-
alphaMin: number;
|
|
1177
|
-
alphaMax: number;
|
|
1178
|
-
speakerEmbDim: number;
|
|
1179
|
-
audioTokensStart: number;
|
|
1180
|
-
startOfSpeech: number;
|
|
1181
|
-
endOfSpeech: number;
|
|
1182
|
-
codebookSize: number;
|
|
1258
|
+
runGraphStep(outputs: Array<{
|
|
1259
|
+
name: string;
|
|
1260
|
+
elemCount: number;
|
|
1261
|
+
}>, tokens?: number): Promise<Float32Array[]>;
|
|
1183
1262
|
/**
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1186
|
-
* The `KaniTTS2ForCausalLM` checkpoints (e.g. kani-tts-2-en) do; the plain
|
|
1187
|
-
* `Lfm2ForCausalLM` family (e.g. the Apache kani-tts-450m-0.2-ft / kani-tts-370m)
|
|
1188
|
-
* does NOT — those run standard sequential positions with α=1 plain RoPE.
|
|
1189
|
-
* Detected from the presence of `use_learnable_rope`/`tokens_per_frame`/the
|
|
1190
|
-
* `KaniTTS2ForCausalLM` architecture marker in config.json.
|
|
1263
|
+
* Greedy decode step: forward + GPU argmax. Returns token ID directly.
|
|
1264
|
+
* Always uses matvec kernels (M=1). Reads back 4 bytes instead of vocab_size*4.
|
|
1191
1265
|
*/
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
/**
|
|
1196
|
-
|
|
1197
|
-
* into NanoCodec codes [NUM_GROUPS, T]. Mirrors NemoAudioPlayer.get_nano_codes:
|
|
1198
|
-
* reshape [-1, 4]; codes[:,c] -= codebook_size*c; codes -= audio_tokens_start;
|
|
1199
|
-
* transpose → [4, T]. Returns a Uint32Array laid out group-major ([g*T + t]).
|
|
1200
|
-
*/
|
|
1201
|
-
declare function audioTokensToCodes(audioTokenIds: number[], cfg?: KaniConfig): {
|
|
1202
|
-
codes: Uint32Array;
|
|
1203
|
-
numFrames: number;
|
|
1204
|
-
};
|
|
1205
|
-
interface NanoCodecGraphOptions {
|
|
1206
|
-
/** Number of audio frames T (the code grid width). PCM length = T * 1764. */
|
|
1207
|
-
numFrames: number;
|
|
1208
|
-
}
|
|
1209
|
-
declare function generateNanoCodecDecoderGraph(opts: NanoCodecGraphOptions): ModelGraph;
|
|
1210
|
-
/**
|
|
1211
|
-
* Generate the KaniTTS codec-LM backbone graph (LFM2-350M body, full-vocab logits,
|
|
1212
|
-
* per-layer learnable MRoPE). Mirrors generateLfm2Graph block-for-block; the only
|
|
1213
|
-
* difference is that each attention layer rotates Q/K with the MRoPE op fed a
|
|
1214
|
-
* per-layer host cos/sin table instead of the position-counter RoPE op.
|
|
1215
|
-
*
|
|
1216
|
-
* dtype:
|
|
1217
|
-
* • undefined / "f32" — full precision (the checkpoint is bf16/f32 → f32).
|
|
1218
|
-
* • "q4" — weight-only group-wise INT4 (W4A16) for the LFM2 linear projections
|
|
1219
|
-
* (q/k/v/o, gate/up/down, short-conv in/out_proj) via MatMulInt4. The tied
|
|
1220
|
-
* embedding + LM head stay at f32 on purpose: decoder-LM heads are the most
|
|
1221
|
-
* quantization-sensitive part of a codec-LM and the embed table is the single
|
|
1222
|
-
* buffer shared by both, so quantizing it would degrade audio quality for no
|
|
1223
|
-
* load-size win that the int4 projections do not already capture. Backbone
|
|
1224
|
-
* drops from ~1.5 GB (f32) to ~0.37 GB. The NanoCodec decoder is a separate
|
|
1225
|
-
* graph and always runs f32.
|
|
1226
|
-
*/
|
|
1227
|
-
declare function generateKaniTtsGraph(rawConfig: Record<string, unknown>, dtype?: GraphDType, groupSize?: number, kvDtype?: KVDType): ModelGraph;
|
|
1228
|
-
//#endregion
|
|
1229
|
-
//#region src/gpu/architectures/moonshine.d.ts
|
|
1230
|
-
/**
|
|
1231
|
-
* Historical record of the executor-side work the keystone left open; all items
|
|
1232
|
-
* are now implemented (see the STATUS note above). Retained as exported
|
|
1233
|
-
* documentation of the dependency surface.
|
|
1234
|
-
*/
|
|
1235
|
-
declare const MOONSHINE_REMAINING_WORK: readonly ["DONE: Transpose op kernel (conv output [C,L] → encoder input [L,C]).", "DONE: GroupNorm(num_groups=1) over conv channels (weight+bias).", "DONE: Tanh elementwise op (conv1 activation).", "DONE: Interleaved-RoPE variant (Moonshine rotates adjacent dim pairs 2p/2p+1).", "DONE: No-bias LayerNorm variant (Moonshine norms are weight-only).", "DONE: Per-utterance conv graph regeneration from the concrete sample count.", "DONE: Dual-graph executor — encoder once → frozen cross-attn K/V → AR decode.", "DONE: engine.transcribe(pcm) host path (MoonshineSTT): conv → encode → AR decode → detokenize."];
|
|
1236
|
-
interface MoonshineDims {
|
|
1237
|
-
hidden_size: number;
|
|
1238
|
-
enc_layers: number;
|
|
1239
|
-
dec_layers: number;
|
|
1240
|
-
num_heads: number;
|
|
1241
|
-
num_kv_heads: number;
|
|
1242
|
-
head_dim: number;
|
|
1243
|
-
rotary_dim: number;
|
|
1244
|
-
intermediate_size: number;
|
|
1245
|
-
vocab_size: number;
|
|
1246
|
-
rope_base: number;
|
|
1247
|
-
context_length: number;
|
|
1248
|
-
ln_eps: number;
|
|
1249
|
-
}
|
|
1250
|
-
/** Pull and derive Moonshine dimensions from the raw HF config. */
|
|
1251
|
-
declare function parseMoonshineConfig(raw: Record<string, unknown>): MoonshineDims;
|
|
1252
|
-
/** Number of encoder frames produced by the conv frontend for n_samples PCM. */
|
|
1253
|
-
declare function moonshineEncoderFrames(nSamples: number): number;
|
|
1254
|
-
/**
|
|
1255
|
-
* Encoder graph: raw-waveform conv frontend + bidirectional transformer.
|
|
1256
|
-
* @param nSamples concrete PCM sample count (the conv frontend is length-static).
|
|
1257
|
-
* The output tensor "encoder_out" is [T_frames, hidden] and is consumed (after
|
|
1258
|
-
* per-layer K/V projection) as the frozen K/V for the decoder's cross-attention.
|
|
1259
|
-
*/
|
|
1260
|
-
declare function generateMoonshineEncoderGraph(raw: Record<string, unknown>, nSamples: number): ModelGraph;
|
|
1261
|
-
/**
|
|
1262
|
-
* Decoder graph: AR transformer with causal self-attn (KV-cache) + cross-attn to
|
|
1263
|
-
* the frozen encoder output. Built for a single decode step (T=1). The encoder K/V
|
|
1264
|
-
* are supplied as graph inputs "enc_k_layer{i}" / "enc_v_layer{i}" — the host
|
|
1265
|
-
* pre-projects the encoder output through each layer's encoder_attn.k_proj/v_proj
|
|
1266
|
-
* ONCE and binds them frozen for the whole decode (the CrossAttention contract).
|
|
1267
|
-
*/
|
|
1268
|
-
declare function generateMoonshineDecoderGraph(raw: Record<string, unknown>, sEnc: number): ModelGraph;
|
|
1269
|
-
//#endregion
|
|
1270
|
-
//#region src/gpu/architectures/outetts.d.ts
|
|
1271
|
-
/** Parsed OuteTTS backbone config (the Qwen3 dims). Reuses the generic Qwen path. */
|
|
1272
|
-
interface OuteTtsConfig {
|
|
1273
|
-
hidden_size: number;
|
|
1274
|
-
num_layers: number;
|
|
1275
|
-
num_heads: number;
|
|
1276
|
-
num_kv_heads: number;
|
|
1277
|
-
head_dim: number;
|
|
1278
|
-
intermediate_size: number;
|
|
1279
|
-
vocab_size: number;
|
|
1280
|
-
rope_base: number;
|
|
1281
|
-
rms_norm_eps: number;
|
|
1282
|
-
max_position_embeddings: number;
|
|
1283
|
-
}
|
|
1284
|
-
declare function parseOuteTtsConfig(rawConfig: Record<string, unknown>): OuteTtsConfig;
|
|
1285
|
-
/**
|
|
1286
|
-
* Convert the flat generated token stream (the run BETWEEN <|audio_start|> and
|
|
1287
|
-
* <|audio_end|>) into a DAC code grid [NUM_CODEBOOKS, T] (codebook-major [c*T + t]).
|
|
1288
|
-
*
|
|
1289
|
-
* Mirrors outetts v3 `extract_audio_from_tokens`: filter the stream into two ordered
|
|
1290
|
-
* lists by membership of the c1 / c2 ranges, recover the raw code
|
|
1291
|
-
* (id − base), drop the reserved index 1024, and truncate to the shorter list. The
|
|
1292
|
-
* model emits c1,c2 interleaved per frame, but the parse is membership-based (it does
|
|
1293
|
-
* not assume strict pairing) so a stray token cannot desync the grid.
|
|
1294
|
-
*/
|
|
1295
|
-
declare function audioTokensToDacCodes(tokenIds: number[]): {
|
|
1296
|
-
codes: Uint32Array;
|
|
1297
|
-
numFrames: number;
|
|
1298
|
-
};
|
|
1299
|
-
interface DacSpeechGraphOptions {
|
|
1300
|
-
/** Number of audio frames T (the code-grid width). */
|
|
1301
|
-
numFrames: number;
|
|
1266
|
+
forwardArgmax(inputIds: Uint32Array): Promise<number>;
|
|
1267
|
+
/** KV-cache positions still available for decode steps. */
|
|
1268
|
+
decodeCapacityRemaining(): number;
|
|
1269
|
+
/** Number of decode steps that may be in flight in the pipelined path. */
|
|
1270
|
+
static readonly PIPELINE_DEPTH = 2;
|
|
1302
1271
|
/**
|
|
1303
|
-
*
|
|
1304
|
-
*
|
|
1305
|
-
*
|
|
1272
|
+
* Pipelined greedy decode step (Dawn only — WebKit uses forwardArgmax).
|
|
1273
|
+
*
|
|
1274
|
+
* Encodes one full decode forward + argmax and submits WITHOUT awaiting
|
|
1275
|
+
* completion. The input token is taken from `tokenId` for the first step
|
|
1276
|
+
* after prefill; for subsequent steps (tokenId === null) the previous step's
|
|
1277
|
+
* argmax result is copied into input_ids ON THE GPU, so the decode loop
|
|
1278
|
+
* never blocks on a readback before submitting the next step. The argmax
|
|
1279
|
+
* result is copied to a per-slot readback buffer, read later (one step
|
|
1280
|
+
* behind) via readDecodeToken(slot).
|
|
1281
|
+
*
|
|
1282
|
+
* queue.writeBuffer is queue-ordered: uniform updates land after the
|
|
1283
|
+
* previously submitted step's command buffer and before this one's, so
|
|
1284
|
+
* shared uniform buffers are safe with multiple steps in flight.
|
|
1306
1285
|
*/
|
|
1307
|
-
|
|
1286
|
+
submitGreedyDecodeStep(tokenId: number | null, slot: number): void;
|
|
1287
|
+
/** Read back the token produced by the pipelined step that used `slot`. */
|
|
1288
|
+
readDecodeToken(slot: number): Promise<number>;
|
|
1308
1289
|
/**
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1311
|
-
*
|
|
1290
|
+
* K-step greedy decode window (GERBIL_DECODE_WINDOW=K — dispatch-collapse
|
|
1291
|
+
* campaign Lever 1; Dawn only).
|
|
1292
|
+
*
|
|
1293
|
+
* Records `steps` full decode forwards + argmax into ONE command buffer and
|
|
1294
|
+
* submits once. Between recorded steps the previous step's argmax result is
|
|
1295
|
+
* copied into input_ids ON THE GPU (the same 4-byte chain the pipelined path
|
|
1296
|
+
* uses across submits), so no CPU sync exists inside the window. Each step's
|
|
1297
|
+
* winning token id is copied into the slot's window readback at offset 4·k;
|
|
1298
|
+
* {@link readDecodeWindow} maps it once per window.
|
|
1299
|
+
*
|
|
1300
|
+
* Seq-pos-dependent entries (RoPE position, KV-append offset, attention S —
|
|
1301
|
+
* ~3 per full-attention layer; Mamba layers carry state and have none) get
|
|
1302
|
+
* lazily-built per-step uniform/bind-group variants, ALL written with
|
|
1303
|
+
* queue.writeBuffer BEFORE the submit (queue-ordered, so safe with a prior
|
|
1304
|
+
* window still in flight). Params are built against per-step resolved shapes
|
|
1305
|
+
* (L_max tensors resolve to seqPos+T — attention S comes from the KV shape).
|
|
1306
|
+
* Dispatch grids are encoded ONCE per window at the worst case (last step's
|
|
1307
|
+
* shapes, pos+K-1): decode grids are step-invariant in practice, and any
|
|
1308
|
+
* L_max-derived grid over-dispatches for early steps, which is safe because
|
|
1309
|
+
* kernels bound their work from uniforms.
|
|
1310
|
+
*
|
|
1311
|
+
* EOS is only observable at window granularity: up to K-1 tokens beyond EOS
|
|
1312
|
+
* are computed and discarded by the caller. KV overshoot is harmless (seqPos
|
|
1313
|
+
* governs reads) but SSM/conv state is NOT rewindable — continuing the same
|
|
1314
|
+
* context past an intra-window EOS requires a re-prefill from that point.
|
|
1315
|
+
*
|
|
1316
|
+
* Evidence base: measured FLAT on M4 Max/Metal (results.jsonl r13-E2 /
|
|
1317
|
+
* r14-E2v2 — depth-2 pipelining already hides submit+readback there);
|
|
1318
|
+
* targeted at Dawn/Vulkan where the submit roundtrip is 52µs and decode is
|
|
1319
|
+
* dispatch/driver-bound (A5000 gate, arXiv:2604.02344). See
|
|
1320
|
+
* docs/design/dispatch-collapse-campaign.md §3.
|
|
1321
|
+
*/
|
|
1322
|
+
submitGreedyDecodeWindow(firstToken: number | null, steps: number, slot: number): void;
|
|
1323
|
+
/** Get (or lazily create) entry's per-step uniform/bind-group variant. */
|
|
1324
|
+
private getWindowVariant;
|
|
1325
|
+
/** Read back the `steps` token ids produced by the window that used `slot`. */
|
|
1326
|
+
readDecodeWindow(slot: number, steps: number): Promise<Uint32Array>;
|
|
1327
|
+
reset(): void;
|
|
1328
|
+
/**
|
|
1329
|
+
* Diagnostic: dispatch ONLY the first kernel (EmbeddingInt4) using the
|
|
1330
|
+
* production bind group, pipeline, and buffers — but in isolation (1 dispatch,
|
|
1331
|
+
* no staging, fresh encoder). Compares against full forward pass to isolate
|
|
1332
|
+
* whether the issue is the bind group/pipeline or the multi-dispatch context.
|
|
1312
1333
|
*/
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
}
|
|
1319
|
-
declare function generateDacSpeechDecoderGraph(opts: DacSpeechGraphOptions): ModelGraph;
|
|
1320
|
-
/**
|
|
1321
|
-
* Final decoded PCM length (samples) for a T-frame code grid, via the DAC stage chain.
|
|
1322
|
-
* `rates` defaults to DAC.speech.v1.0 ([8,5,4,2]); pass [8,8,4,2] for Parler's dac_44khz.
|
|
1323
|
-
*/
|
|
1324
|
-
declare function dacOutputLength(numFrames: number, rates?: readonly number[]): number;
|
|
1325
|
-
declare function generateOuteTtsBackboneGraph(rawConfig: Record<string, unknown>, dtype?: GraphDType, groupSize?: number, kvDtype?: KVDType): ModelGraph;
|
|
1326
|
-
//#endregion
|
|
1327
|
-
//#region src/gpu/architectures/qwen3_5_vision.d.ts
|
|
1328
|
-
/**
|
|
1329
|
-
* Build the ViT graph. The graph is shaped by symbolic "N" (number of patches),
|
|
1330
|
-
* resolved at run time from the input tensor's first dim — exactly like the LM's
|
|
1331
|
-
* symbolic "T".
|
|
1332
|
-
*/
|
|
1333
|
-
declare function generateQwen3_5VisionGraph(rawConfig: Record<string, unknown>): ModelGraph;
|
|
1334
|
-
//#endregion
|
|
1335
|
-
//#region src/gpu/executor.d.ts
|
|
1336
|
-
/**
|
|
1337
|
-
* Safari/Metal workaround: shader variant alternation.
|
|
1338
|
-
* Metal caches argument buffers per compiled function. When consecutive dispatches
|
|
1339
|
-
* use the same WGSL code (same Metal function), Metal reuses the previous dispatch's
|
|
1340
|
-
* argument buffer, ignoring setBindGroup(). We alternate between variant 0/1 of each
|
|
1341
|
-
* shader (prepending `const _MV: u32 = Xu;`) to force different Metal function
|
|
1342
|
-
* specializations, preventing argument buffer reuse.
|
|
1343
|
-
*/
|
|
1344
|
-
interface ExecutorOptions {
|
|
1345
|
-
maxSeqLen: number;
|
|
1346
|
-
/** KV cache kernel strategy. Defaults to "native-f16" when not specified. */
|
|
1347
|
-
kvMode?: KvMode;
|
|
1334
|
+
debugFirstDispatch(inputIds: Uint32Array): Promise<{
|
|
1335
|
+
nodeId: string;
|
|
1336
|
+
opType: string;
|
|
1337
|
+
dispatchSize: [number, number, number];
|
|
1338
|
+
output: Float32Array;
|
|
1339
|
+
}>;
|
|
1348
1340
|
/**
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1352
|
-
* writes visible across dispatches within one submission. Sweepable via
|
|
1353
|
-
* the ?group=N URL param.
|
|
1341
|
+
* Diagnostic: run a single dispatch entry by index, in isolation.
|
|
1342
|
+
* Call after debugFirstDispatch() to test whether entry[1] (RMSNorm)
|
|
1343
|
+
* can read embed_out written by entry[0] (EmbeddingInt4).
|
|
1354
1344
|
*/
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
}
|
|
1360
|
-
declare class Executor {
|
|
1361
|
-
private ctx;
|
|
1362
|
-
private graph;
|
|
1363
|
-
private weightBuffers;
|
|
1364
|
-
private activationBuffers;
|
|
1365
|
-
private ssmStateBuffers;
|
|
1366
|
-
private kvCacheBuffers;
|
|
1367
|
-
/** Pre-allocated input_ids buffer (maxSeqLen * 4 bytes). */
|
|
1368
|
-
private inputIdsBuffer;
|
|
1345
|
+
debugDispatchEntry(entryIndex: number, T: number): Promise<{
|
|
1346
|
+
nodeId: string;
|
|
1347
|
+
opType: string;
|
|
1348
|
+
output: Float32Array;
|
|
1349
|
+
}>;
|
|
1369
1350
|
/**
|
|
1370
|
-
*
|
|
1371
|
-
*
|
|
1372
|
-
* OFF the GPU. Each forward step we gather + dequantize only the rows for the
|
|
1373
|
-
* actual input tokens and upload a tiny [T, L*256] f32 buffer. See setPleSource.
|
|
1351
|
+
* Diagnostic: compute the JS-side params for the first N decode entries
|
|
1352
|
+
* WITHOUT dispatching. Shows what buildParams produces.
|
|
1374
1353
|
*/
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1354
|
+
debugComputeParams(T: number, count?: number): Array<{
|
|
1355
|
+
idx: number;
|
|
1356
|
+
nodeId: string;
|
|
1357
|
+
opType: string;
|
|
1358
|
+
paramsU32: number[];
|
|
1359
|
+
dispatchSize: [number, number, number];
|
|
1360
|
+
}>;
|
|
1378
1361
|
/**
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1381
|
-
* weight upload has completed, so the ~1.17 GB does not stack on top of the
|
|
1382
|
-
* upload's transient allocations at the load-time memory high-water mark.
|
|
1362
|
+
* Diagnostic: after a forward pass, read back output tensors at several points
|
|
1363
|
+
* in the pipeline to find where data drops to zero.
|
|
1383
1364
|
*/
|
|
1384
|
-
|
|
1365
|
+
debugPipelineProbe(T: number): Promise<Array<{
|
|
1366
|
+
idx: number;
|
|
1367
|
+
nodeId: string;
|
|
1368
|
+
opType: string;
|
|
1369
|
+
tensor: string;
|
|
1370
|
+
sum: number;
|
|
1371
|
+
first4: number[];
|
|
1372
|
+
uniformParams?: number[];
|
|
1373
|
+
}>>;
|
|
1374
|
+
debugWriteBuffer(tensorName: string, data: ArrayBufferView): void;
|
|
1385
1375
|
/**
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
1388
|
-
*
|
|
1389
|
-
* read_write bindings (q, k), but with num_kv_heads=0 the k slot is never
|
|
1390
|
-
* touched. WebGPU still rejects two read_write bindings aliasing one buffer,
|
|
1391
|
-
* so we bind this throwaway buffer to the unused k slot. Allocated lazily.
|
|
1376
|
+
* Write a host-supplied activation input buffer (e.g. multimodal M-RoPE
|
|
1377
|
+
* cos/sin, spliced vision embeddings, image row-map). The buffer must be a
|
|
1378
|
+
* persistent activation tensor in the graph. Call before forward().
|
|
1392
1379
|
*/
|
|
1393
|
-
|
|
1394
|
-
/**
|
|
1395
|
-
|
|
1396
|
-
/**
|
|
1397
|
-
|
|
1398
|
-
/**
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1380
|
+
writeInput(tensorName: string, data: ArrayBufferView): void;
|
|
1381
|
+
/** Write a host activation buffer at a byte offset (for per-row decode updates). */
|
|
1382
|
+
writeInputAt(tensorName: string, data: ArrayBufferView, byteOffset: number): void;
|
|
1383
|
+
/** True if the graph has a buffer with this name (multimodal-capability probe). */
|
|
1384
|
+
hasBuffer(tensorName: string): boolean;
|
|
1385
|
+
/** Current sequence position (number of tokens processed since reset). */
|
|
1386
|
+
get currentSeqPos(): number;
|
|
1387
|
+
debugReadBuffer(tensorName: string, maxElements?: number, byteOffset?: number): Promise<Float32Array>;
|
|
1388
|
+
destroy(): void;
|
|
1402
1389
|
/**
|
|
1403
|
-
*
|
|
1404
|
-
*
|
|
1405
|
-
*
|
|
1406
|
-
*
|
|
1407
|
-
*
|
|
1408
|
-
*
|
|
1390
|
+
* Allocate activation buffers with liveness-based reuse.
|
|
1391
|
+
*
|
|
1392
|
+
* One dedicated buffer per activation tensor at full maxSeqLen is ~2.3GB for
|
|
1393
|
+
* Qwen3.5-0.8B at T=512 — over the iOS jetsam budget on its own. Instead,
|
|
1394
|
+
* a buffer returns to a size-keyed pool once its tensor's last reader has
|
|
1395
|
+
* executed, so concurrently-live tensors share a small working set.
|
|
1396
|
+
*
|
|
1397
|
+
* Graph outputs and tensors read before they are written (cross-forward
|
|
1398
|
+
* state) keep dedicated buffers. Within a forward, dispatches execute in
|
|
1399
|
+
* executionOrder on every path (single-pass Dawn, per-dispatch WebKit), and
|
|
1400
|
+
* WebGPU synchronizes hazards between dispatches, so reuse is safe.
|
|
1401
|
+
*
|
|
1402
|
+
* Caveat: debugReadBuffer() on an intermediate tensor is only meaningful
|
|
1403
|
+
* before a later op reuses its buffer (probes that stop mid-graph are fine).
|
|
1409
1404
|
*/
|
|
1410
|
-
private
|
|
1411
|
-
private
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
private decodeEntries;
|
|
1416
|
-
/** Per-node decode entries captured BEFORE fusion (the overlay base). */
|
|
1417
|
-
private decodeEntriesUnfused;
|
|
1418
|
-
/** True while a runtime adapter is applied. */
|
|
1419
|
-
private loraActive;
|
|
1420
|
-
/** Decode entries (unfused base + spliced LoRA dispatches). */
|
|
1421
|
-
private decodeEntriesLoRA;
|
|
1422
|
-
/** Prefill entries (base + spliced LoRA dispatches). */
|
|
1423
|
-
private prefillEntriesLoRA;
|
|
1424
|
-
/** GPU buffers owned by the current adapter (A/B factors + tmp); freed on swap. */
|
|
1425
|
-
private loraBuffers;
|
|
1426
|
-
/** Argmax dispatch entry (created in initBindGroups). */
|
|
1427
|
-
private argmaxEntry;
|
|
1428
|
-
/** True when running on Safari/WebKit (needs multi-encoder submit). */
|
|
1429
|
-
readonly needsMultiEncoder: boolean;
|
|
1430
|
-
private maxSeqLen;
|
|
1431
|
-
private kvMode;
|
|
1432
|
-
private seqPos;
|
|
1433
|
-
private webkitGroupSize;
|
|
1434
|
-
private profileEnabled;
|
|
1435
|
-
private readonly profileData;
|
|
1436
|
-
private querySet;
|
|
1437
|
-
private queryResolveBuf;
|
|
1438
|
-
private queryReadbackBuf;
|
|
1439
|
-
constructor(ctx: GPUContext, graph: ModelGraph, options: ExecutorOptions);
|
|
1405
|
+
private allocateActivationBuffers;
|
|
1406
|
+
private allocateSSMStateBuffers;
|
|
1407
|
+
private allocateKVCacheBuffers;
|
|
1408
|
+
/** f16 SSM-state kernels are selected on Dawn with f16 support (not WebKit). */
|
|
1409
|
+
private get usesF16Ssm();
|
|
1440
1410
|
/**
|
|
1441
|
-
*
|
|
1442
|
-
*
|
|
1443
|
-
*
|
|
1444
|
-
*
|
|
1445
|
-
*
|
|
1411
|
+
* Batched-decode state (GERBIL_BATCH=N): per-slot SSM/conv state pools, the
|
|
1412
|
+
* per-row positions/seq_lens buffers, the sampled-token buffer + readback
|
|
1413
|
+
* ring, and batch-widened buffers for the lm-head tail (SliceLastRow output
|
|
1414
|
+
* and logits become [N, ...]).
|
|
1415
|
+
*
|
|
1416
|
+
* SSM pool slot stride: the f16 MambaSSM kernel indexes its state buffer in
|
|
1417
|
+
* f16 ELEMENTS (the buffer is f32-sized with the upper half unused), so its
|
|
1418
|
+
* pool slot stride is elems*2 bytes; conv state (f32 kernels) uses the full
|
|
1419
|
+
* f32 byte size. The batched kernels compute `slot * elems` in their own
|
|
1420
|
+
* element space, so the pool stride must match each kernel's element width.
|
|
1446
1421
|
*/
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1422
|
+
private allocateBatchState;
|
|
1423
|
+
private resolveShapes;
|
|
1424
|
+
private getBuffer;
|
|
1425
|
+
/**
|
|
1426
|
+
* Batch-path buffer resolution: lm-head tail tensors use their [B, ...]
|
|
1427
|
+
* widened buffers, ssm_state tensors bind the per-slot pool, everything
|
|
1428
|
+
* else falls through to the regular buffer maps.
|
|
1429
|
+
*/
|
|
1430
|
+
private getBatchBuffer;
|
|
1431
|
+
/**
|
|
1432
|
+
* True when this node's KV traffic goes through the paged path: the flag is
|
|
1433
|
+
* on AND the node actually touches a kv_cache tensor (KVCacheAppend writes
|
|
1434
|
+
* one; self-attention reads one as its K input). SSM/conv state and
|
|
1435
|
+
* ViT/cross-attention over plain activations never match — on hybrid models
|
|
1436
|
+
* only the attention layers page, exactly as the design requires.
|
|
1437
|
+
*/
|
|
1438
|
+
private usesPagedKV;
|
|
1439
|
+
/**
|
|
1440
|
+
* Paged KV only: make sure blocks are mapped for logical positions
|
|
1441
|
+
* [0, seqPos + T) BEFORE this forward's dispatches are submitted.
|
|
1442
|
+
* queue.writeBuffer is queue-ordered, so the (rare — once per 16 tokens)
|
|
1443
|
+
* table upload always lands ahead of the submits that read it, including in
|
|
1444
|
+
* the pipelined decode path with PIPELINE_DEPTH steps in flight.
|
|
1445
|
+
*/
|
|
1446
|
+
private preparePagedKV;
|
|
1447
|
+
/** Runtime context for batched dispatches (positions live in buffers). */
|
|
1448
|
+
private batchRuntimeCtx;
|
|
1449
|
+
/**
|
|
1450
|
+
* Shapes for the batched step: every "T" dimension is the batch width, and
|
|
1451
|
+
* the lm-head tail tensors (statically [1, ...] in the graph) widen to
|
|
1452
|
+
* [B, ...] — every row IS a last row on the batch path.
|
|
1453
|
+
*/
|
|
1454
|
+
private resolveBatchShapes;
|
|
1455
|
+
/**
|
|
1456
|
+
* Build the batched dispatch-entry list: one lockstep decode step for all
|
|
1457
|
+
* batchSize rows. Row-independent ops (embedding, norms, elementwise) keep
|
|
1458
|
+
* their prefill kernels at T=B; linear layers run the batched INT4 GEMM;
|
|
1459
|
+
* every position/state-dependent op swaps to its batch-indexed variant
|
|
1460
|
+
* (paged attention + append via per-row block-table rows, RoPE via the
|
|
1461
|
+
* positions buffer, conv/SSM via the per-slot state pool).
|
|
1462
|
+
*/
|
|
1463
|
+
private buildBatchEntries;
|
|
1464
|
+
/**
|
|
1465
|
+
* Batched-path fusion driver (Phase 4). Ports the single-sequence decode
|
|
1466
|
+
* fusions onto `batchEntries`: identical math (the fused kernels reproduce
|
|
1467
|
+
* the unfused chain operation-for-operation — exact-tier GEMM reductions
|
|
1468
|
+
* keep the V2 order, elementwise prologues/epilogues keep the registry
|
|
1469
|
+
* kernels' formulas), strictly fewer dispatches. `GERBIL_BATCH_FUSE`
|
|
1470
|
+
* selects a subset for A/B measurement: unset/"all" = every pass,
|
|
1471
|
+
* "none" = none, or a comma list of pass names.
|
|
1472
|
+
*/
|
|
1473
|
+
private fuseBatchDecodeEntries;
|
|
1474
|
+
/**
|
|
1475
|
+
* Batch port of fuseSwiGLUDecodeEntries: gate MatMulInt4 + up MatMulInt4 +
|
|
1476
|
+
* elementwise SwiGLU → ONE fused batched GEMM (saves 2 dispatches per MLP
|
|
1477
|
+
* block and the gate/up activation round-trips).
|
|
1478
|
+
*/
|
|
1479
|
+
private fuseBatchSwiGLUMLPEntries;
|
|
1480
|
+
/**
|
|
1481
|
+
* Batch port of fuseDualMatVecDecodeEntries: two adjacent MatMulInt4s
|
|
1482
|
+
* sharing one input and identical K/N/group_size (q+gate, k+v) → ONE dual
|
|
1483
|
+
* batched GEMM dispatch (wid.y selects the projection — per-projection
|
|
1484
|
+
* math unchanged).
|
|
1485
|
+
*/
|
|
1486
|
+
private fuseBatchDualMatVecEntries;
|
|
1462
1487
|
/**
|
|
1463
|
-
*
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1466
|
-
* decode path and forwardArgmax both bypass CPU token knowledge, so they leave
|
|
1467
|
-
* `ple_embed_out` stale and must NOT be used for these models — only the
|
|
1468
|
-
* per-step forward() path (which calls streamPleRows) is correct. See generate().
|
|
1488
|
+
* Batch port of fuseDualKVCacheAppendEntries: adjacent K+V paged appends →
|
|
1489
|
+
* ONE BATCHED_PAGED_DUAL_KV_APPEND dispatch (pure memcpys — identical
|
|
1490
|
+
* writes, one dispatch per attention layer saved).
|
|
1469
1491
|
*/
|
|
1470
|
-
|
|
1492
|
+
private fuseBatchDualKVAppendEntries;
|
|
1471
1493
|
/**
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1474
|
-
*
|
|
1494
|
+
* Batch port of fuseGatedOProjDecodeEntries ("sigmoid": SigmoidGate →
|
|
1495
|
+
* o_proj) and fuseSwiGLUGatedProjDecodeEntries ("swiglu": Mamba SwiGLU →
|
|
1496
|
+
* out_proj): the elementwise gate is recomputed inside the GEMM's input
|
|
1497
|
+
* load with the registry kernel's exact formula — one dispatch per layer
|
|
1498
|
+
* saved plus the gated-activation round-trip.
|
|
1475
1499
|
*/
|
|
1476
|
-
private
|
|
1500
|
+
private fuseBatchGatedProjEntries;
|
|
1477
1501
|
/**
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
1502
|
+
* Batch port of fuseDualRMSNormRoPEDecodeEntries: [q_norm, k_norm, RoPE]
|
|
1503
|
+
* → ONE BATCHED_DUAL_RMSNORM_ROPE dispatch (per-row rotation position from
|
|
1504
|
+
* the positions buffer). Saves 2 dispatches per full-attention layer.
|
|
1481
1505
|
*/
|
|
1482
|
-
private
|
|
1506
|
+
private fuseBatchDualRMSNormRoPEEntries;
|
|
1483
1507
|
/**
|
|
1484
|
-
*
|
|
1485
|
-
*
|
|
1486
|
-
*
|
|
1508
|
+
* Reset ALL batch slots for a new lockstep generation: frees every
|
|
1509
|
+
* sequence's KV blocks, zeroes the single-sequence SSM state (the prefill
|
|
1510
|
+
* staging area), and clears per-slot lengths. State-pool slots need no
|
|
1511
|
+
* zeroing — finishBatchSlot overwrites a slot in full before it is read.
|
|
1512
|
+
*/
|
|
1513
|
+
resetBatch(): void;
|
|
1514
|
+
/**
|
|
1515
|
+
* Route the single-sequence forward() path at batch slot `slot`: paged KV
|
|
1516
|
+
* writes/reads use the slot's block-table row (RuntimeContext.tableBase) and
|
|
1517
|
+
* SSM state accumulates in the singleton staging buffers (zeroed).
|
|
1518
|
+
* Call once per prompt, then forward(promptIds), then finishBatchSlot().
|
|
1519
|
+
*/
|
|
1520
|
+
beginBatchSlot(slot: number): void;
|
|
1521
|
+
/**
|
|
1522
|
+
* Adopt the just-prefilled sequence into `slot`: records its length, copies
|
|
1523
|
+
* the singleton SSM/conv state into the slot's pool rows (KV already lives
|
|
1524
|
+
* in the slot's block-table row via tableBase), and zeroes the singleton
|
|
1525
|
+
* staging state for the next prefill.
|
|
1526
|
+
*/
|
|
1527
|
+
finishBatchSlot(slot: number): void;
|
|
1528
|
+
/** Per-slot token counts (prompt + generated) on the batch path. */
|
|
1529
|
+
get batchSlotLengths(): Uint32Array;
|
|
1530
|
+
/**
|
|
1531
|
+
* Submit one lockstep batched decode step (pipelined — does not await).
|
|
1532
|
+
* `tokens` feeds input_ids on the first step; pass null afterwards to chain
|
|
1533
|
+
* the previous step's sampled ids into input_ids ON the GPU. Sampled ids
|
|
1534
|
+
* land in the `slot` readback (read one step behind via readBatchTokens).
|
|
1535
|
+
*/
|
|
1536
|
+
submitBatchDecodeStep(tokens: Uint32Array<ArrayBuffer> | null, slot: number): void;
|
|
1537
|
+
/** Read the sampled ids produced by the batched step that used `slot`. */
|
|
1538
|
+
readBatchTokens(slot: number): Promise<Uint32Array>;
|
|
1539
|
+
/** Batch entries in effect for the current lane-adapter state. */
|
|
1540
|
+
private get activeBatchEntries();
|
|
1541
|
+
/** Number of adapters registered for per-lane batched decode. */
|
|
1542
|
+
get batchAdapterCount(): number;
|
|
1543
|
+
/**
|
|
1544
|
+
* Register a LoRA adapter for per-lane batched decode and return its integer
|
|
1545
|
+
* adapter id (assign lanes via {@link setBatchLaneAdapters}). Factors are
|
|
1546
|
+
* packed into per-adapted-node GPU buffers ONCE — every lane running this
|
|
1547
|
+
* adapter reads the same factors. Fused query+gate deltas are expanded with
|
|
1548
|
+
* the same deinterleave the single-sequence overlay uses.
|
|
1487
1549
|
*
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1550
|
+
* Phase-1 constraint: every adapter adapting a given linear must share that
|
|
1551
|
+
* node's LoRA rank (the packed factor arena is fixed-stride; mixed ranks are
|
|
1552
|
+
* deferred).
|
|
1553
|
+
*/
|
|
1554
|
+
registerBatchAdapter(deltas: LoRADelta[]): number;
|
|
1555
|
+
/** Drop every registered batch adapter and free the overlay's GPU buffers. */
|
|
1556
|
+
clearBatchAdapters(): void;
|
|
1557
|
+
/**
|
|
1558
|
+
* Assign each batch lane its adapter: `ids[b]` is a registered adapter id or
|
|
1559
|
+
* -1 for the bare base. Uploads the per-lane adapter_ids buffer (the
|
|
1560
|
+
* positions/seq_lens per-row pattern) and switches the step's entry list to
|
|
1561
|
+
* the LoRA overlay iff any lane carries an adapter — an all-base assignment
|
|
1562
|
+
* runs the untouched plain batch entries at zero extra cost.
|
|
1563
|
+
*/
|
|
1564
|
+
setBatchLaneAdapters(ids: ArrayLike<number>): void;
|
|
1565
|
+
/** Free the batch-LoRA overlay's GPU buffers (factors, maps, tmp, uniforms). */
|
|
1566
|
+
private destroyBatchLoraBuffers;
|
|
1567
|
+
/**
|
|
1568
|
+
* (Re)build the per-lane LoRA overlay: pack every registered adapter's
|
|
1569
|
+
* factors per adapted node, and derive `batchEntriesLoRA` from the fused
|
|
1570
|
+
* batch entry list with SELECTIVE unfusing — every fused group that covers
|
|
1571
|
+
* no adapted linear stays exactly as the plain batch path runs it; groups
|
|
1572
|
+
* covering an adapted node fall back to their pre-fusion per-node entries
|
|
1573
|
+
* (token-exact by the Phase 2/4 derivation discipline) with the per-lane
|
|
1574
|
+
* `[down, up]` gather pair spliced right after each adapted node, so the
|
|
1575
|
+
* corrected output is live before any consumer reads it.
|
|
1576
|
+
*/
|
|
1577
|
+
private rebuildBatchLoraOverlay;
|
|
1578
|
+
/** Total physical KV blocks in the paged pool (admission-control bound). */
|
|
1579
|
+
get kvTotalBlocks(): number;
|
|
1580
|
+
/** KV blocks currently unallocated. */
|
|
1581
|
+
get kvFreeBlocks(): number;
|
|
1582
|
+
/** Tokens per KV block (admission-control block-count math). */
|
|
1583
|
+
get kvBlockSizeTokens(): number;
|
|
1584
|
+
/** Blocks a single sequence may map (== ceil(maxSeqLen / blockSize)). */
|
|
1585
|
+
get kvMaxBlocksPerSeq(): number;
|
|
1586
|
+
/** Effective context length (scheduler admission math). */
|
|
1587
|
+
get maxSequenceLength(): number;
|
|
1588
|
+
/**
|
|
1589
|
+
* Retire a batch lane: free its KV blocks and zero its length so masked
|
|
1590
|
+
* steps treat the slot as empty (seq_lens[slot] = 0). The SSM pool row
|
|
1591
|
+
* needs no cleanup — re-admission overwrites it in full (finishBatchSlot).
|
|
1592
|
+
*/
|
|
1593
|
+
retireBatchSlot(slot: number): void;
|
|
1594
|
+
/**
|
|
1595
|
+
* Seed `slot`'s next input token (the first token sampled from its prefill
|
|
1596
|
+
* logits). Written into the sampled-ids buffer so the next masked step's
|
|
1597
|
+
* on-GPU chain copy propagates it into input_ids. queue.writeBuffer is
|
|
1598
|
+
* queue-ordered: it lands AFTER any in-flight step's argmax write to the
|
|
1599
|
+
* same word and BEFORE the next submitted step's chain copy — correct even
|
|
1600
|
+
* with PIPELINE_DEPTH steps in flight.
|
|
1601
|
+
*/
|
|
1602
|
+
injectBatchToken(slot: number, token: number): void;
|
|
1603
|
+
/**
|
|
1604
|
+
* Submit one MASKED batched decode step (the scheduler's step; pipelined —
|
|
1605
|
+
* does not await). Only lanes with `active[b] != 0` advance: inactive lanes
|
|
1606
|
+
* get positions=0 / seq_lens=0, which masks their KV appends (kernel guard)
|
|
1607
|
+
* and clamps their attention window to nothing; their other row-local ops
|
|
1608
|
+
* compute garbage that stays confined to their rows/state slots and is
|
|
1609
|
+
* fully overwritten on re-admission.
|
|
1494
1610
|
*
|
|
1495
|
-
*
|
|
1496
|
-
*
|
|
1611
|
+
* The step's dispatch width is dynamic: kernels take their batch dimension
|
|
1612
|
+
* from the runtime context, so only rows [0, maxActiveSlot] are dispatched
|
|
1613
|
+
* (GEMM b-tiles may cover a few dead rows past the last active one —
|
|
1614
|
+
* in-bounds stale writes that are never read). Input tokens always chain
|
|
1615
|
+
* from the sampled-ids buffer on the GPU; newly admitted lanes are seeded
|
|
1616
|
+
* via injectBatchToken. Sampled ids land in the `slot` readback (read one
|
|
1617
|
+
* step behind via readBatchTokens).
|
|
1618
|
+
*/
|
|
1619
|
+
submitBatchDecodeStepMasked(active: Uint8Array, slot: number): void;
|
|
1620
|
+
/** Write the batched-argmax uniform once per batch session (invariant). */
|
|
1621
|
+
private writeBatchArgmaxParams;
|
|
1622
|
+
private requireBatch;
|
|
1623
|
+
/**
|
|
1624
|
+
* Detect gate_proj + up_proj + SwiGLU patterns in decode entries and replace
|
|
1625
|
+
* with a single fused SwiGLUMatVec dispatch. Saves 2 dispatches per MLP block.
|
|
1497
1626
|
*/
|
|
1498
|
-
uploadWeights(source: WeightSource | Map<string, {
|
|
1499
|
-
data: ArrayBufferView;
|
|
1500
|
-
shape: number[];
|
|
1501
|
-
}>): Promise<void>;
|
|
1502
1627
|
/**
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1628
|
+
* Fuse [ResidualRMSNorm, gate MatMulInt4, up MatMulInt4, SwiGLU] → ONE
|
|
1629
|
+
* NormSwiGLUMatVec dispatch. The kernel computes the residual sum + RMS in a
|
|
1630
|
+
* prologue (workgroup 0 persists the residual sum for the downstream residual
|
|
1631
|
+
* chain) and normalizes the activation on the fly, so the norm dispatch AND
|
|
1632
|
+
* the normed tensor's write+read roundtrip disappear. INT4-only; falls back
|
|
1633
|
+
* when the device can't bind 11 storage buffers. Windows it skips fall
|
|
1634
|
+
* through to fuseSwiGLUDecodeEntries.
|
|
1506
1635
|
*/
|
|
1507
|
-
uploadWeightsMap(weights: Map<string, {
|
|
1508
|
-
data: ArrayBufferView;
|
|
1509
|
-
shape: number[];
|
|
1510
|
-
}>): void;
|
|
1511
1636
|
/**
|
|
1512
|
-
*
|
|
1637
|
+
* Fuse [ResidualRMSNorm → Mamba qkv MatVecInt4] → ONE NormMatVec dispatch.
|
|
1638
|
+
* The kernel recomputes the residual sum + RMS in a prologue (workgroup 0
|
|
1639
|
+
* persists BOTH the residual sum and the full normed vector — the latter is
|
|
1640
|
+
* live for the sibling a_proj/b_proj/z_proj), then the GEMV normalizes the
|
|
1641
|
+
* activation on the fly. Saves the ResidualRMSNorm dispatch (−1 per Mamba
|
|
1642
|
+
* layer, ×17). 10 storage bindings — fits the Dawn/Metal 10-binding adapter.
|
|
1643
|
+
* INT4-only.
|
|
1513
1644
|
*
|
|
1514
|
-
*
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1645
|
+
* OPT-IN, DEFAULT OFF (GERBIL_FUSE_NORM_QKV=1). Round r28 (2026-07-25) measured
|
|
1646
|
+
* this a −4.6% REGRESSION on M4 Max/Metal: the qkv GEMV is the widest projection
|
|
1647
|
+
* (N=6144 → 384 workgroups), so recomputing the 1024-wide residual+RMS reduction
|
|
1648
|
+
* per workgroup + normalizing activations on the fly costs far more ALU than the
|
|
1649
|
+
* single saved ResidualRMSNorm dispatch returns on a bandwidth-plateaued backend.
|
|
1650
|
+
* The −17 dispatch/token reduction still targets Vulkan/WebKit (dispatch-count-
|
|
1651
|
+
* bound) — validate on the kernel-lab A5000 rig before enabling anywhere.
|
|
1652
|
+
*/
|
|
1653
|
+
private fuseNormMatVecDecodeEntries;
|
|
1654
|
+
private fuseNormSwiGLUDecodeEntries;
|
|
1655
|
+
private fuseSwiGLUDecodeEntries;
|
|
1656
|
+
/**
|
|
1657
|
+
* Fuse the Mamba-front projections — qkv (MatVec), a (MatVec), b (MatVec),
|
|
1658
|
+
* z (MatVec) — into ONE QuadMatVecInt4 dispatch per Mamba layer. All four
|
|
1659
|
+
* read the SAME `input_layernorm` output and share K and group_size, so their
|
|
1660
|
+
* INT4 weight matrices (stored column-major, one column contiguous) are
|
|
1661
|
+
* column-concatenated into a single q/scales/zeros buffer by a plain
|
|
1662
|
+
* byte-append (a GPU buffer-to-buffer copy at init). The fused kernel runs a
|
|
1663
|
+
* single GEMV over the concatenated N and routes each output column back to
|
|
1664
|
+
* its ORIGINAL destination buffer, so downstream consumers (conv1d reads qkv,
|
|
1665
|
+
* MambaSSM reads a/b, the gated out_proj reads z) are untouched.
|
|
1666
|
+
*
|
|
1667
|
+
* Numerically identical to the three separate dispatches (same dequant, same
|
|
1668
|
+
* K-parallel reduction, same per-column sum — the concatenated weights are the
|
|
1669
|
+
* same bytes), so this is token-exact and WebKit-safe. It removes 2
|
|
1670
|
+
* dispatches per Mamba layer (3 front dispatches → 1) plus one input read and
|
|
1671
|
+
* two weight-read passes.
|
|
1672
|
+
*
|
|
1673
|
+
* Runs BEFORE fuseDualMatVecDecodeEntries so the a/b pair is still two
|
|
1674
|
+
* standalone MatVec entries here (not yet a DualMatVec). Restricted to the
|
|
1675
|
+
* Mamba front by weight-name (`.in_proj_qkv/a/b/z.`), so the attention front
|
|
1676
|
+
* (q/gate/k/v) is never matched. Bindings: A + q/s/z + 4 outs + params = 9
|
|
1677
|
+
* (fits Metal's 10-binding wall). Gate: default-on when the adapter offers ≥9
|
|
1678
|
+
* storage bindings; opt-out via GERBIL_FUSE_MAMBA_FRONT=0.
|
|
1517
1679
|
*/
|
|
1518
|
-
|
|
1519
|
-
/** True while a runtime LoRA overlay is applied. */
|
|
1520
|
-
get hasRuntimeAdapter(): boolean;
|
|
1521
|
-
/** Decode entries in effect for the current adapter state. */
|
|
1522
|
-
private get activeDecodeEntries();
|
|
1680
|
+
private fuseMambaFrontDecodeEntries;
|
|
1523
1681
|
/**
|
|
1524
|
-
*
|
|
1525
|
-
*
|
|
1526
|
-
*
|
|
1527
|
-
*
|
|
1528
|
-
*
|
|
1682
|
+
* Fuse two adjacent INT4 projections that share the same input activation and
|
|
1683
|
+
* the same K/N (e.g. q_proj+gate_proj and k_proj+v_proj in full-attention
|
|
1684
|
+
* decode) into a single DualMatVecInt4 dispatch. Reads the shared input vector
|
|
1685
|
+
* once and writes both projection outputs — removing one GPU round-trip (one
|
|
1686
|
+
* submit+drain on Safari/iOS) per fused pair.
|
|
1529
1687
|
*
|
|
1530
|
-
*
|
|
1531
|
-
*
|
|
1532
|
-
*
|
|
1688
|
+
* Numerics are identical to running the two MatVecInt4 kernels separately
|
|
1689
|
+
* (same dequant, same K-parallel reduction), so this is WebKit-safe: it reuses
|
|
1690
|
+
* the proven INT4 matvec math and only merges two writes into one dispatch.
|
|
1691
|
+
*
|
|
1692
|
+
* Must run AFTER fuseSwiGLUDecodeEntries so the MLP gate+up pair (which is
|
|
1693
|
+
* consumed by a SwiGLU node) is already collapsed and won't be matched here.
|
|
1533
1694
|
*/
|
|
1534
|
-
|
|
1535
|
-
private findBaseLinear;
|
|
1695
|
+
private fuseDualMatVecDecodeEntries;
|
|
1536
1696
|
/**
|
|
1537
|
-
*
|
|
1538
|
-
*
|
|
1697
|
+
* Fuse the adjacent K-cache and V-cache appends in each full-attention layer
|
|
1698
|
+
* into a single DualKVCacheAppend dispatch. Both are pure memcpys into f32
|
|
1699
|
+
* caches sharing the same width and dst_offset, so one dispatch with two
|
|
1700
|
+
* src/dst buffers writes both — removing one GPU round-trip per layer.
|
|
1539
1701
|
*
|
|
1540
|
-
*
|
|
1541
|
-
*
|
|
1542
|
-
*
|
|
1543
|
-
*
|
|
1544
|
-
* out-dim is 2× either split node, so it matches neither. When BOTH split
|
|
1545
|
-
* linears exist and the delta is exactly their combined width, split it via the
|
|
1546
|
-
* same per-head row deinterleave the loader uses; otherwise the delta passes
|
|
1547
|
-
* through untouched (e.g. a base that never split the projection).
|
|
1702
|
+
* Supports f32, native-f16, and packed-f16 caches (the dual kernel mirrors the
|
|
1703
|
+
* single-append kernel selected for the active kvMode). Numerically identical
|
|
1704
|
+
* to the separate appends — WebKit-safe (the packed-f16 variant is the Safari
|
|
1705
|
+
* path and uses pack2x16float, no `enable f16`).
|
|
1548
1706
|
*/
|
|
1549
|
-
private
|
|
1550
|
-
applyRuntimeLoRA(deltas: LoRADelta[]): {
|
|
1551
|
-
applied: number;
|
|
1552
|
-
skipped: number;
|
|
1553
|
-
};
|
|
1554
|
-
/** Drop the runtime LoRA overlay and free its GPU buffers. Base is untouched. */
|
|
1555
|
-
clearRuntimeLoRA(): void;
|
|
1556
|
-
/** Build the [down, up] low-rank dispatch pair for one adapted node. */
|
|
1557
|
-
private buildLoraPair;
|
|
1558
|
-
/** Copy a base entry list, splicing each node's LoRA pair in right after it. */
|
|
1559
|
-
private spliceLoraOverlay;
|
|
1560
|
-
/** Original constituent node ids a (possibly fused) decode entry covers. */
|
|
1561
|
-
private coveredNodeIds;
|
|
1707
|
+
private fuseDualKVCacheAppendEntries;
|
|
1562
1708
|
/**
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1709
|
+
* Fuse the attention SigmoidGate (attn_out * sigmoid(gate)) into the INT4
|
|
1710
|
+
* o_proj that consumes it: a GatedMatVecInt4 reads attn_out and gate directly,
|
|
1711
|
+
* applies the sigmoid gate to its input vector, and runs the projection in ONE
|
|
1712
|
+
* dispatch — removing the standalone SigmoidGate (one round-trip per
|
|
1713
|
+
* full-attention layer).
|
|
1714
|
+
*
|
|
1715
|
+
* Numerically identical to SigmoidGate→MatVecInt4 (same gate formula, same INT4
|
|
1716
|
+
* dequant + reduction). Slight extra ALU: the gated input is recomputed per
|
|
1717
|
+
* output column, but A reads hit L1 and the saved submit+drain dominates on
|
|
1718
|
+
* mobile. WebKit risk: low — reuses the proven INT4 matvec, only the A vector
|
|
1719
|
+
* is built from two reads + a sigmoid (no new reduction/barrier pattern).
|
|
1720
|
+
*
|
|
1721
|
+
* Runs after the dual fusions so it only sees the post-attention SigmoidGate.
|
|
1572
1722
|
*/
|
|
1573
|
-
private
|
|
1723
|
+
private fuseGatedOProjDecodeEntries;
|
|
1574
1724
|
/**
|
|
1575
|
-
*
|
|
1725
|
+
* Fuse a standalone SwiGLU (silu(gate) * up) into the INT4 projection that
|
|
1726
|
+
* consumes its output: a SwiGLUGatedMatVecInt4 reads gate and up directly,
|
|
1727
|
+
* builds the gated input vector, and runs the projection in ONE dispatch.
|
|
1728
|
+
* Targets the Mamba block's mamba_swiglu (silu(z) * norm_out) feeding out_proj
|
|
1729
|
+
* — one round-trip saved per linear-attention layer.
|
|
1730
|
+
*
|
|
1731
|
+
* The MLP SwiGLU is already collapsed into a SwiGLUMatVec entry by
|
|
1732
|
+
* fuseSwiGLUDecodeEntries (it has no surviving standalone SwiGLU node), so only
|
|
1733
|
+
* the Mamba SwiGLU matches here. Numerically identical to SwiGLU→MatVecInt4;
|
|
1734
|
+
* WebKit risk low (reuses the proven INT4 matvec, only the A vector changes).
|
|
1576
1735
|
*/
|
|
1577
|
-
|
|
1736
|
+
private fuseSwiGLUGatedProjDecodeEntries;
|
|
1578
1737
|
/**
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1581
|
-
*
|
|
1582
|
-
*
|
|
1738
|
+
* Fuse two adjacent per-row RMSNorms sharing hidden_size + eps into a single
|
|
1739
|
+
* DualRMSNorm dispatch (e.g. the per-head q_norm and k_norm in full-attention
|
|
1740
|
+
* decode). One workgroup still handles one row; the fused grid just spans both
|
|
1741
|
+
* inputs' rows, so each row's reduction is unchanged — numerically identical to
|
|
1742
|
+
* two separate RMSNorm dispatches. One round-trip saved per fused pair.
|
|
1743
|
+
*
|
|
1744
|
+
* WebKit risk: low — same single-workgroup reduction as the proven RMSNorm
|
|
1745
|
+
* kernel, only the row→input routing is added.
|
|
1583
1746
|
*/
|
|
1584
|
-
private runProfiledDispatches;
|
|
1585
|
-
/** Per-opType GPU time (ns) + dispatch count accumulated by GERBIL_PROFILE, hottest first. */
|
|
1586
|
-
getProfile(): Array<{
|
|
1587
|
-
opType: string;
|
|
1588
|
-
ns: number;
|
|
1589
|
-
count: number;
|
|
1590
|
-
}>;
|
|
1591
|
-
/** Clear accumulated profiler data (e.g. to drop warm-up tokens). */
|
|
1592
|
-
resetProfile(): void;
|
|
1593
|
-
/** GPU dispatches per decode token (post-fusion). On mobile this drives the
|
|
1594
|
-
* submit-group count = ceil(dispatchCount / webkitGroupSize). */
|
|
1595
|
-
get decodeDispatchCount(): number;
|
|
1596
|
-
/** Device limit that gates the INT4 projection fusions (they need ≥9). If a
|
|
1597
|
-
* device caps at 8 the dual/gated/swiglu-gated INT4 fusions silently fall back,
|
|
1598
|
-
* inflating the decode dispatch count. */
|
|
1599
|
-
get maxStorageBuffers(): number;
|
|
1600
1747
|
/**
|
|
1601
|
-
*
|
|
1602
|
-
*
|
|
1603
|
-
*
|
|
1604
|
-
*
|
|
1605
|
-
* tiny dispatch) is intentionally excluded — it is not a hotspot target.
|
|
1748
|
+
* Fuse [CausalConv1dSiLU|CausalConv1dGated → ConvStateUpdate] into one
|
|
1749
|
+
* dispatch (decode entries only). At T=1 the conv read and the rolling-state
|
|
1750
|
+
* write for a channel live in the same thread, so the fused kernel is
|
|
1751
|
+
* race-free; prefill (T>1) keeps the two-dispatch path.
|
|
1606
1752
|
*/
|
|
1607
|
-
|
|
1753
|
+
private fuseConvStateUpdateDecodeEntries;
|
|
1608
1754
|
/**
|
|
1609
|
-
*
|
|
1610
|
-
*
|
|
1611
|
-
*
|
|
1612
|
-
*
|
|
1613
|
-
*
|
|
1614
|
-
* embeddings are non-autoregressive, so the whole sequence is one prefill.
|
|
1755
|
+
* Fuse [per-head q_norm RMSNorm, k_norm RMSNorm, in-place RoPE] into ONE
|
|
1756
|
+
* DualRMSNormRoPE dispatch. Numerically identical (norm then rotate, same
|
|
1757
|
+
* math); requires head_dim <= 256 and RoPE operating in-place on exactly the
|
|
1758
|
+
* two norm outputs. Saves 2 dispatches per full-attention layer — a mobile
|
|
1759
|
+
* (WebKit submit-per-dispatch) win, desktop-neutral.
|
|
1615
1760
|
*/
|
|
1616
|
-
embed(inputIds: Uint32Array): Promise<Float32Array>;
|
|
1617
1761
|
/**
|
|
1618
|
-
*
|
|
1619
|
-
*
|
|
1620
|
-
*
|
|
1621
|
-
* concrete lengths and it has no "logits" output, so the normal forward()
|
|
1622
|
-
* logits-readback path does not apply. Caller writes any inputs (e.g. audio_codes)
|
|
1623
|
-
* via writeInput() and reset()s first.
|
|
1762
|
+
* Fuse [Add → RMSNorm] into a single ResidualRMSNorm dispatch: the add's sum
|
|
1763
|
+
* is still materialized (sum_output — downstream residual chain reads it) and
|
|
1764
|
+
* the norm result is written in the same dispatch. Numerically identical.
|
|
1624
1765
|
*/
|
|
1625
|
-
|
|
1766
|
+
private fuseAddRMSNormDecodeEntries;
|
|
1767
|
+
private fuseDualRMSNormRoPEDecodeEntries;
|
|
1768
|
+
private fuseDualRMSNormDecodeEntries;
|
|
1626
1769
|
/**
|
|
1627
|
-
*
|
|
1628
|
-
*
|
|
1629
|
-
* KVCacheAppend writes at the right cache row), reads back each named output, then
|
|
1630
|
-
* advances seqPos by `tokens` (default 1, for a prefilled prompt span pass tokens=T).
|
|
1631
|
-
* The caller writes inputs (dec_input_embed + frozen cross-attn K/V) and reset()s
|
|
1632
|
-
* before the first step. Unlike forward(), there is no single "logits" output —
|
|
1633
|
-
* the caller decodes each codebook row itself.
|
|
1770
|
+
* Gather buffer entries for a bind group, matching the kernel spec's binding layout.
|
|
1771
|
+
* Uses the pre-allocated inputIdsBuffer for the "input_ids" tensor.
|
|
1634
1772
|
*/
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1773
|
+
private gatherBuffers;
|
|
1774
|
+
/** Lazily allocate a scratch storage buffer at least `minBytes` large. */
|
|
1775
|
+
private getBindingScratchBuffer;
|
|
1776
|
+
}
|
|
1777
|
+
//#endregion
|
|
1778
|
+
//#region src/gpu/batch/scheduler.d.ts
|
|
1779
|
+
/** Options for a single scheduled request. */
|
|
1780
|
+
interface ServeOptions {
|
|
1781
|
+
/** Max new tokens (clamped to context; default 128). */
|
|
1782
|
+
maxTokens?: number;
|
|
1783
|
+
stopSequences?: string[];
|
|
1784
|
+
systemPrompt?: string;
|
|
1785
|
+
/** Greedy-only in Phase 5: temperature must be 0 (or omitted). */
|
|
1786
|
+
sampling?: {
|
|
1787
|
+
temperature?: number;
|
|
1788
|
+
};
|
|
1789
|
+
/**
|
|
1790
|
+
* Registered adapter name this request decodes with (SGMV — requires
|
|
1791
|
+
* `GERBIL_BATCH_LORA=1` and a prior `registerAdapter` call on the engine);
|
|
1792
|
+
* undefined = bare base. Requests with different adapters (or none) decode
|
|
1793
|
+
* concurrently over the shared base, each token-exact vs a solo run with
|
|
1794
|
+
* that adapter loaded.
|
|
1795
|
+
*/
|
|
1796
|
+
adapter?: string;
|
|
1797
|
+
/** Streamed per-token callback (decoded text piece). */
|
|
1798
|
+
onToken?: (token: string, meta: {
|
|
1799
|
+
requestId: number;
|
|
1800
|
+
tokenId: number;
|
|
1801
|
+
generated: number;
|
|
1802
|
+
}) => void;
|
|
1803
|
+
}
|
|
1804
|
+
/** Per-request result + serving timings (all performance.now() ms). */
|
|
1805
|
+
interface ServeResult {
|
|
1806
|
+
requestId: number;
|
|
1807
|
+
text: string;
|
|
1808
|
+
tokenIds: number[];
|
|
1809
|
+
tokensGenerated: number;
|
|
1810
|
+
finishReason: "eos" | "max_tokens" | "stop_sequence";
|
|
1811
|
+
/** The adapter name the request decoded with (echoed through), if any. */
|
|
1812
|
+
adapter?: string;
|
|
1813
|
+
/** submit() call time. */
|
|
1814
|
+
submittedAt: number;
|
|
1815
|
+
/** Admission into a batch slot (prefill start). */
|
|
1816
|
+
admittedAt: number;
|
|
1817
|
+
/** First token available (end of prefill). */
|
|
1818
|
+
firstTokenAt: number;
|
|
1819
|
+
finishedAt: number;
|
|
1820
|
+
/** Time-to-first-token from submission (queue wait + prefill). */
|
|
1821
|
+
ttftMs: number;
|
|
1822
|
+
e2eMs: number;
|
|
1823
|
+
}
|
|
1824
|
+
/** Wiring the engine hands the scheduler (see WebGPUEngine.createScheduler). */
|
|
1825
|
+
interface SchedulerHost {
|
|
1826
|
+
executor: Executor;
|
|
1827
|
+
encodeChat(messages: ChatMessage[]): number[];
|
|
1828
|
+
decodeToken(id: number): string;
|
|
1829
|
+
eosTokenId: number | null;
|
|
1830
|
+
eotTokenId: number | null;
|
|
1831
|
+
/** Vocab-prune remap for CPU-sampled tokens (identity when unpruned). */
|
|
1832
|
+
remapToken(id: number): number;
|
|
1833
|
+
/**
|
|
1834
|
+
* Resolve a registered batch-adapter name to its executor adapter id +
|
|
1835
|
+
* parsed deltas. Must throw a clear error for unregistered names (surfaced
|
|
1836
|
+
* at submit() time, before the request is queued).
|
|
1837
|
+
*/
|
|
1838
|
+
resolveAdapter(name: string): {
|
|
1839
|
+
id: number;
|
|
1840
|
+
deltas: LoRADelta[];
|
|
1841
|
+
};
|
|
1842
|
+
/** Serialize against generate()/embed() on the same engine. */
|
|
1843
|
+
acquireLock(): Promise<() => void>;
|
|
1844
|
+
}
|
|
1845
|
+
interface SchedulerStats {
|
|
1846
|
+
submitted: number;
|
|
1847
|
+
completed: number;
|
|
1848
|
+
stepsSubmitted: number;
|
|
1849
|
+
prefillChunks: number;
|
|
1850
|
+
tokensGenerated: number;
|
|
1851
|
+
/** Wall-clock ms the serving loop was live (work outstanding). */
|
|
1852
|
+
busyMs: number;
|
|
1853
|
+
peakActiveLanes: number;
|
|
1854
|
+
}
|
|
1855
|
+
declare class RequestScheduler {
|
|
1856
|
+
private readonly host;
|
|
1857
|
+
private readonly batchSize;
|
|
1858
|
+
private readonly maxSeqLen;
|
|
1859
|
+
private readonly blockSize;
|
|
1860
|
+
private readonly prefillChunk;
|
|
1861
|
+
/** Free KV blocks kept unreserved as safety headroom. */
|
|
1862
|
+
private readonly blockHeadroom;
|
|
1863
|
+
private nextId;
|
|
1864
|
+
private queue;
|
|
1865
|
+
/** slot → occupying request (prefilling or decoding). */
|
|
1866
|
+
private slots;
|
|
1867
|
+
/** Sum of blocksReserved across live (admitted, unfinished) requests. */
|
|
1868
|
+
private reservedBlocks;
|
|
1869
|
+
private prefilling;
|
|
1870
|
+
private inFlight;
|
|
1871
|
+
/**
|
|
1872
|
+
* The slot→adapter-id assignment last uploaded to the executor (null =
|
|
1873
|
+
* nothing uploaded this batch session). Steps only re-upload when the
|
|
1874
|
+
* assignment changes (admissions/retirements), and the very first upload is
|
|
1875
|
+
* skipped entirely while every lane is bare base — an adapter-free serving
|
|
1876
|
+
* session never touches the LoRA machinery.
|
|
1877
|
+
*/
|
|
1878
|
+
private lastLaneAdapterIds;
|
|
1879
|
+
private stepCounter;
|
|
1880
|
+
private loop;
|
|
1881
|
+
private closed;
|
|
1882
|
+
private stats;
|
|
1883
|
+
constructor(host: SchedulerHost, options?: {
|
|
1884
|
+
prefillChunkTokens?: number;
|
|
1885
|
+
});
|
|
1886
|
+
/**
|
|
1887
|
+
* Submit a request; resolves with the full result when it finishes.
|
|
1888
|
+
* Tokens stream through options.onToken as they are decoded.
|
|
1889
|
+
*/
|
|
1890
|
+
submit(prompt: string | ChatMessage[], options?: ServeOptions): Promise<ServeResult>;
|
|
1891
|
+
/** Resolves when every submitted request has completed. */
|
|
1892
|
+
drain(): Promise<void>;
|
|
1893
|
+
/** Stop accepting requests; resolves when in-flight work drains. */
|
|
1894
|
+
close(): Promise<void>;
|
|
1895
|
+
getStats(): SchedulerStats;
|
|
1896
|
+
private pump;
|
|
1897
|
+
private hasWork;
|
|
1898
|
+
private activeMask;
|
|
1899
|
+
/** The serving loop. Holds the engine generation lock while live. */
|
|
1900
|
+
private run;
|
|
1901
|
+
/**
|
|
1902
|
+
* Upload the slot→adapter assignment for the step about to be submitted,
|
|
1903
|
+
* when it differs from the last uploaded one. Called immediately before
|
|
1904
|
+
* every decode-step submit: `queue.writeBuffer` is queue-ordered, so steps
|
|
1905
|
+
* already in flight keep the assignment they were submitted under, and a
|
|
1906
|
+
* retiring lane's slot can never leak its adapter into the slot's next
|
|
1907
|
+
* occupant — the next occupant's assignment is re-derived from `slots`
|
|
1908
|
+
* before its first decode step. Masked (free/prefilling) lanes are pinned
|
|
1909
|
+
* to bare base; their output is discarded regardless.
|
|
1910
|
+
*/
|
|
1911
|
+
private syncLaneAdapters;
|
|
1912
|
+
/**
|
|
1913
|
+
* Admission: move the next queued request into a free slot when (a) a slot
|
|
1914
|
+
* is free, (b) no other prefill is staged (the single-sequence prefill path
|
|
1915
|
+
* stages SSM state in singleton buffers — one at a time), and (c) its
|
|
1916
|
+
* worst-case block need fits the unreserved KV pool with headroom.
|
|
1917
|
+
*/
|
|
1918
|
+
private admit;
|
|
1919
|
+
/**
|
|
1920
|
+
* Run one bounded prefill chunk for the staged request on the
|
|
1921
|
+
* single-sequence path (into its slot's block-table row via tableBase).
|
|
1922
|
+
* Intermediate chunks are submit-only; the final chunk reads logits, adopts
|
|
1923
|
+
* the singleton SSM state into the slot's pool row, seeds the lane's first
|
|
1924
|
+
* token, and flips the lane to decoding.
|
|
1925
|
+
*/
|
|
1926
|
+
private prefillChunkStep;
|
|
1927
|
+
/** Read the oldest in-flight step and deliver its tokens. */
|
|
1928
|
+
private readStep;
|
|
1929
|
+
/** Append one generated token; finish + retire the lane when terminal. */
|
|
1930
|
+
private consumeToken;
|
|
1931
|
+
/** Retire the lane immediately: free KV blocks, open the slot, resolve. */
|
|
1932
|
+
private finish;
|
|
1933
|
+
}
|
|
1934
|
+
//#endregion
|
|
1935
|
+
//#region src/gpu/batch/swarm.d.ts
|
|
1936
|
+
/** Per-request options a swarm sub-task may set (the member picks the adapter). */
|
|
1937
|
+
type SwarmTaskOptions = Omit<ServeOptions, "adapter">;
|
|
1938
|
+
/** One planned sub-task: which member decodes which prompt. */
|
|
1939
|
+
interface SwarmTask {
|
|
1940
|
+
/** Member name (a key of the swarm's `members`). */
|
|
1941
|
+
member: string;
|
|
1942
|
+
prompt: string | ChatMessage[];
|
|
1943
|
+
/** Per-task overrides merged over the run's options. */
|
|
1944
|
+
options?: SwarmTaskOptions;
|
|
1945
|
+
}
|
|
1946
|
+
/** One member's completed sub-task (plan order is preserved). */
|
|
1947
|
+
interface SwarmMemberResult {
|
|
1948
|
+
member: string;
|
|
1949
|
+
result: ServeResult;
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* Swarm configuration.
|
|
1953
|
+
*
|
|
1954
|
+
* @template R The reduced result type (defaults to the member-keyed map the
|
|
1955
|
+
* default reduce produces).
|
|
1956
|
+
*/
|
|
1957
|
+
interface SwarmOptions<R = Record<string, ServeResult>> {
|
|
1639
1958
|
/**
|
|
1640
|
-
*
|
|
1641
|
-
*
|
|
1959
|
+
* Member name → adapter source (`hf:owner/repo`, URL, or `file:./dir`), or
|
|
1960
|
+
* null for a member that decodes on the bare base. Every non-null source is
|
|
1961
|
+
* registered on the engine (factors packed into shared GPU buffers once).
|
|
1642
1962
|
*/
|
|
1643
|
-
|
|
1644
|
-
/** KV-cache positions still available for decode steps. */
|
|
1645
|
-
decodeCapacityRemaining(): number;
|
|
1646
|
-
/** Number of decode steps that may be in flight in the pipelined path. */
|
|
1647
|
-
static readonly PIPELINE_DEPTH = 2;
|
|
1963
|
+
members: Record<string, string | null>;
|
|
1648
1964
|
/**
|
|
1649
|
-
*
|
|
1650
|
-
*
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
*
|
|
1655
|
-
*
|
|
1656
|
-
|
|
1657
|
-
|
|
1965
|
+
* input → sub-tasks routed to members. Default: broadcast the input to
|
|
1966
|
+
* every member (one sub-task each, declaration order).
|
|
1967
|
+
*/
|
|
1968
|
+
plan?: (input: string, members: string[]) => SwarmTask[] | Promise<SwarmTask[]>;
|
|
1969
|
+
/**
|
|
1970
|
+
* Joined sub-results → final answer. Default: a member-keyed map of
|
|
1971
|
+
* ServeResults (a duplicated member's later tasks get `name#2`, `name#3`…).
|
|
1972
|
+
*/
|
|
1973
|
+
reduce?: (results: SwarmMemberResult[]) => R | Promise<R>;
|
|
1974
|
+
}
|
|
1975
|
+
/**
|
|
1976
|
+
* A swarm handle: registered members + plan/reduce over the engine's
|
|
1977
|
+
* scheduler. Create via {@link WebGPUEngine.createSwarm}.
|
|
1978
|
+
*/
|
|
1979
|
+
declare class Swarm<R = Record<string, ServeResult>> {
|
|
1980
|
+
private readonly scheduler;
|
|
1981
|
+
private readonly memberSources;
|
|
1982
|
+
private readonly plan;
|
|
1983
|
+
private readonly reduce;
|
|
1984
|
+
constructor(scheduler: RequestScheduler, options: SwarmOptions<R>);
|
|
1985
|
+
/** Member names, declaration order. */
|
|
1986
|
+
get members(): string[];
|
|
1987
|
+
/**
|
|
1988
|
+
* Run one input through the swarm: plan → concurrent fan-out (one batched
|
|
1989
|
+
* pass across the scheduler's lanes) → reduce.
|
|
1658
1990
|
*
|
|
1659
|
-
*
|
|
1660
|
-
*
|
|
1661
|
-
*
|
|
1991
|
+
* @param input The task input handed to the plan (default plan: broadcast
|
|
1992
|
+
* it to every member as the prompt).
|
|
1993
|
+
* @param options Per-request options applied to every sub-task (a task's
|
|
1994
|
+
* own `options` win field-by-field).
|
|
1662
1995
|
*/
|
|
1663
|
-
|
|
1664
|
-
/** Read back the token produced by the pipelined step that used `slot`. */
|
|
1665
|
-
readDecodeToken(slot: number): Promise<number>;
|
|
1666
|
-
reset(): void;
|
|
1996
|
+
run(input: string, options?: SwarmTaskOptions): Promise<R>;
|
|
1667
1997
|
/**
|
|
1668
|
-
*
|
|
1669
|
-
*
|
|
1670
|
-
* no staging, fresh encoder). Compares against full forward pass to isolate
|
|
1671
|
-
* whether the issue is the bind group/pipeline or the multi-dispatch context.
|
|
1998
|
+
* Mode-A access: decode one prompt with a single member (no plan/reduce).
|
|
1999
|
+
* Still rides the shared scheduler, so it batches with any concurrent work.
|
|
1672
2000
|
*/
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2001
|
+
generate(member: string, prompt: string | ChatMessage[], options?: SwarmTaskOptions): Promise<ServeResult>;
|
|
2002
|
+
private submitFor;
|
|
2003
|
+
}
|
|
2004
|
+
//#endregion
|
|
2005
|
+
//#region src/gpu/sampler.d.ts
|
|
2006
|
+
/**
|
|
2007
|
+
* CPU-side token sampling from logits.
|
|
2008
|
+
*
|
|
2009
|
+
* Applies temperature, top-k, and top-p (nucleus) filtering,
|
|
2010
|
+
* then samples from the resulting probability distribution.
|
|
2011
|
+
*
|
|
2012
|
+
* Uses typed arrays and min-heap for zero-allocation top-K selection.
|
|
2013
|
+
* For vocab_size ~152K with topK=50, this avoids creating 152K JS tuples.
|
|
2014
|
+
*/
|
|
2015
|
+
interface SamplingParams {
|
|
2016
|
+
temperature?: number;
|
|
2017
|
+
topK?: number;
|
|
2018
|
+
topP?: number;
|
|
2019
|
+
repetitionPenalty?: number;
|
|
1679
2020
|
/**
|
|
1680
|
-
*
|
|
1681
|
-
*
|
|
1682
|
-
*
|
|
2021
|
+
* Seed for the sampling RNG. When set, sampled decoding is reproducible:
|
|
2022
|
+
* the same params object draws a deterministic random stream (seeded on its
|
|
2023
|
+
* first use — pass a fresh options object per generation for run-to-run
|
|
2024
|
+
* reproducibility). Unset: Math.random() (non-deterministic, the default).
|
|
1683
2025
|
*/
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
2026
|
+
seed?: number;
|
|
2027
|
+
}
|
|
2028
|
+
//#endregion
|
|
2029
|
+
//#region src/gpu/vision-preprocess.d.ts
|
|
2030
|
+
/**
|
|
2031
|
+
* Host-side vision preprocessing for the Qwen3.5 ViT.
|
|
2032
|
+
*
|
|
2033
|
+
* The learned position embeddings (bilinear-interpolated over the patch grid)
|
|
2034
|
+
* and the 2D rotary cos/sin tables are functions of the image grid (t, h, w)
|
|
2035
|
+
* ONLY — not of the model weights or pixel values. They are cheap to compute on
|
|
2036
|
+
* the CPU and fed to the GPU graph as input activations, keeping the graph to
|
|
2037
|
+
* weight-dependent math while staying byte-identical to HF transformers.
|
|
2038
|
+
*
|
|
2039
|
+
* Ports (verified against transformers 5.12 vision_utils.py + modeling_qwen3_5):
|
|
2040
|
+
* - get_vision_bilinear_indices_and_weights → buildPosEmbeds()
|
|
2041
|
+
* - get_vision_position_ids → buildPositionIds()
|
|
2042
|
+
* - Qwen3_5VisionRotaryEmbedding → buildRotaryCosSin()
|
|
2043
|
+
*
|
|
2044
|
+
* Patch ordering: both the pos-embed gather and the position ids reorder patches
|
|
2045
|
+
* into spatial_merge_size×spatial_merge_size groups, matching the HF image
|
|
2046
|
+
* processor's output ordering, so the merger's [N,h]→[N/u,h*u] reshape lines up.
|
|
2047
|
+
*/
|
|
2048
|
+
interface VisionGridConfig {
|
|
2049
|
+
hiddenSize: number;
|
|
2050
|
+
numHeads: number;
|
|
2051
|
+
numPositionEmbeddings: number;
|
|
2052
|
+
spatialMergeSize: number;
|
|
2053
|
+
ropeTheta?: number;
|
|
2054
|
+
}
|
|
2055
|
+
interface VisionPositionTensors {
|
|
2056
|
+
/** [N, hidden_size] bilinear-interpolated learned position embeddings. */
|
|
2057
|
+
posEmbeds: Float32Array;
|
|
2058
|
+
/** [N, head_dim] rotary cosines. */
|
|
2059
|
+
cos: Float32Array;
|
|
2060
|
+
/** [N, head_dim] rotary sines. */
|
|
2061
|
+
sin: Float32Array;
|
|
2062
|
+
numPatches: number;
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Build bilinear-interpolated learned position embeddings [N, hidden].
|
|
2066
|
+
* posEmbedTable is the raw pos_embed.weight [num_position_embeddings, hidden].
|
|
2067
|
+
*/
|
|
2068
|
+
declare function buildPosEmbeds(gridTHW: [number, number, number], posEmbedTable: Float32Array, cfg: VisionGridConfig): Float32Array;
|
|
2069
|
+
/**
|
|
2070
|
+
* Build the reordered (row, col) position ids [N, 2] for rotary, matching
|
|
2071
|
+
* get_vision_position_ids.
|
|
2072
|
+
*/
|
|
2073
|
+
declare function buildPositionIds(gridTHW: [number, number, number], merge: number): Int32Array;
|
|
2074
|
+
/**
|
|
2075
|
+
* Build rotary cos/sin tables [N, head_dim] from position ids, matching
|
|
2076
|
+
* Qwen3_5VisionRotaryEmbedding + the cat((rotary, rotary)) in VisionModel.forward.
|
|
2077
|
+
*
|
|
2078
|
+
* rotary_pos_emb(position_ids) = (position_ids[..,None] * inv_freq).flatten(1)
|
|
2079
|
+
* where inv_freq has length (head_dim/2)/2 = head_dim/4, computed over dim=head_dim/2.
|
|
2080
|
+
* For each token the two position components (h, w) each produce head_dim/4 freqs,
|
|
2081
|
+
* concatenated → head_dim/2, then duplicated → head_dim for cos/sin.
|
|
2082
|
+
*/
|
|
2083
|
+
declare function buildRotaryCosSin(positionIds: Int32Array, headDim: number, theta?: number): {
|
|
2084
|
+
cos: Float32Array;
|
|
2085
|
+
sin: Float32Array;
|
|
2086
|
+
numPatches: number;
|
|
2087
|
+
};
|
|
2088
|
+
/**
|
|
2089
|
+
* Build all host position tensors for a single image grid in one call.
|
|
2090
|
+
*/
|
|
2091
|
+
declare function buildVisionPositionTensors(gridTHW: [number, number, number], posEmbedTable: Float32Array, cfg: VisionGridConfig): VisionPositionTensors;
|
|
2092
|
+
interface Gemma4VisionGridConfig {
|
|
2093
|
+
hiddenSize: number;
|
|
2094
|
+
numHeads: number;
|
|
2095
|
+
headDim: number;
|
|
2096
|
+
ropeTheta: number;
|
|
2097
|
+
poolingKernelSize: number;
|
|
2098
|
+
}
|
|
2099
|
+
interface Gemma4VisionPositionTensors {
|
|
2100
|
+
/** [N, hidden] axial position embeddings (table[0][x] + table[1][y]). */
|
|
2101
|
+
posEmbeds: Float32Array;
|
|
2102
|
+
/** [N, headDim] axial rotary cosines. */
|
|
2103
|
+
cos: Float32Array;
|
|
2104
|
+
/** [N, headDim] axial rotary sines. */
|
|
2105
|
+
sin: Float32Array;
|
|
2106
|
+
/** [Np, N] average-pooling matrix (1/k² in-cell, 0 elsewhere). */
|
|
2107
|
+
poolMatrix: Float32Array;
|
|
2108
|
+
/** number of patches N (= gridH*gridW). */
|
|
2109
|
+
numPatches: number;
|
|
2110
|
+
/** number of pooled (soft) tokens Np (= ceil(gridH/k)*ceil(gridW/k)). */
|
|
2111
|
+
numPooled: number;
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Build axial learned position embeddings [N, hidden] from the [2, posSize, hidden]
|
|
2115
|
+
* table: pos[p] = table[0][x_p] + table[1][y_p]. Direct lookup, no interpolation
|
|
2116
|
+
* (HF F.embedding on clamped positions).
|
|
2117
|
+
*/
|
|
2118
|
+
declare function buildGemma4PosEmbeds(gridH: number, gridW: number, posEmbedTable: Float32Array,
|
|
2119
|
+
// [2, posSize, hidden] flattened
|
|
2120
|
+
hidden: number, posSize: number): Float32Array;
|
|
2121
|
+
/**
|
|
2122
|
+
* Build the 2D axial rotary cos/sin tables [N, headDim].
|
|
2123
|
+
* spatial_dim = headDim / 2; inv_freq[j] = 1/theta^((2j)/spatial_dim), j in [0, spatial_dim/2)
|
|
2124
|
+
* per spatial dim: f = pos * inv_freq (spatial_dim/2 values); emb = cat(f, f) (spatial_dim values)
|
|
2125
|
+
* cos/sin = cat([emb_x, emb_y]) → headDim values, layout [fx,fx,fy,fy].
|
|
2126
|
+
* Applied with the global-half rotate_half kernel (ApplyRotaryEmb), which computes
|
|
2127
|
+
* out = x*cos + rotate_half(x)*sin element-wise — exact for this layout.
|
|
2128
|
+
*/
|
|
2129
|
+
declare function buildGemma4RotaryCosSin(gridH: number, gridW: number, headDim: number, theta: number): {
|
|
2130
|
+
cos: Float32Array;
|
|
2131
|
+
sin: Float32Array;
|
|
2132
|
+
};
|
|
2133
|
+
/**
|
|
2134
|
+
* Build the [Np, N] average-pooling matrix for k×k spatial pooling over the real
|
|
2135
|
+
* (unpadded) grid, matching modeling_gemma4's kernel_idxs/one_hot pooling:
|
|
2136
|
+
* cell(p) = floor(x_p/k) + ceil(gridW/k) * floor(y_p/k)
|
|
2137
|
+
* poolMatrix[cell, p] = 1/k² (so pooled = poolMatrix @ hidden = mean over the k×k block)
|
|
2138
|
+
* Np = ceil(gridH/k) * ceil(gridW/k). Each pooled cell averages exactly the patches
|
|
2139
|
+
* that fall in it (edge cells with fewer than k² patches still divide by k², matching
|
|
2140
|
+
* HF's fixed 1/k² normalization).
|
|
2141
|
+
*/
|
|
2142
|
+
declare function buildGemma4PoolMatrix(gridH: number, gridW: number, k: number): {
|
|
2143
|
+
poolMatrix: Float32Array;
|
|
2144
|
+
numPooled: number;
|
|
2145
|
+
};
|
|
2146
|
+
/**
|
|
2147
|
+
* Build all Gemma 4 vision host tensors for one image grid in one call.
|
|
2148
|
+
* `posEmbedTable` is the raw [2, posSize, hidden] flattened table.
|
|
2149
|
+
*/
|
|
2150
|
+
declare function buildGemma4VisionPositionTensors(gridH: number, gridW: number, posEmbedTable: Float32Array, posSize: number, cfg: Gemma4VisionGridConfig): Gemma4VisionPositionTensors;
|
|
2151
|
+
/** Gemma 4 image processor config (from processor_config.json). */
|
|
2152
|
+
declare const GEMMA4_IMAGE_PROCESSOR: ImageProcessorConfig;
|
|
2153
|
+
interface Gemma4PreprocessedImage {
|
|
2154
|
+
/** Flattened patches [N, 3·patch²] row-major (row-major patch grid). */
|
|
2155
|
+
patches: Float32Array;
|
|
2156
|
+
/** Patch grid (gridH, gridW). */
|
|
2157
|
+
gridHW: [number, number];
|
|
2158
|
+
}
|
|
2159
|
+
/**
|
|
2160
|
+
* Preprocess a decoded RGB image for the Gemma 4 ViT: aspect-preserving resize so
|
|
2161
|
+
* the patch grid is ≤ max_soft_tokens·k² patches and H,W divisible by k·patch,
|
|
2162
|
+
* rescale ×1/255 (no normalize), patchify row-major into [N, 3·16·16].
|
|
2163
|
+
*
|
|
2164
|
+
* @param pixels row-major HWC RGB (0..255), length width*height*3.
|
|
2165
|
+
*/
|
|
2166
|
+
declare function preprocessImageGemma4(pixels: Float32Array | Uint8ClampedArray | Uint8Array, width: number, height: number, maxSoftTokens?: number, poolingKernelSize?: number, patchSize?: number): Gemma4PreprocessedImage;
|
|
2167
|
+
interface ImageProcessorConfig {
|
|
2168
|
+
patchSize: number;
|
|
2169
|
+
temporalPatchSize: number;
|
|
2170
|
+
mergeSize: number;
|
|
2171
|
+
imageMean: [number, number, number];
|
|
2172
|
+
imageStd: [number, number, number];
|
|
2173
|
+
/** rescale factor applied to raw 0..255 pixels before normalization (1/255). */
|
|
2174
|
+
rescaleFactor: number;
|
|
2175
|
+
/** min total pixels after resize (shortest_edge). */
|
|
2176
|
+
minPixels: number;
|
|
2177
|
+
/** max total pixels after resize (longest_edge). */
|
|
2178
|
+
maxPixels: number;
|
|
2179
|
+
}
|
|
2180
|
+
declare const QWEN3_5_IMAGE_PROCESSOR: ImageProcessorConfig;
|
|
2181
|
+
interface PreprocessedImage {
|
|
2182
|
+
/** Flattened patches [N, 1536] in the spatial-merge order encodeImage expects. */
|
|
2183
|
+
patches: Float32Array;
|
|
2184
|
+
/** (t, h, w) patch grid. t=1 for a single image, h/w in patch units. */
|
|
2185
|
+
gridTHW: [number, number, number];
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Qwen2-VL smart-resize: round H and W to multiples of factor=patch*merge,
|
|
2189
|
+
* keeping aspect ratio and clamping the total pixel budget to [minPixels, maxPixels].
|
|
2190
|
+
* Matches transformers.models.qwen2_vl.image_processing.smart_resize.
|
|
2191
|
+
*/
|
|
2192
|
+
declare function smartResize(height: number, width: number, factor: number, minPixels: number, maxPixels: number): [number, number];
|
|
2193
|
+
/**
|
|
2194
|
+
* Preprocess a decoded RGB image into the [N, 1536] patch tensor + grid_thw that
|
|
2195
|
+
* `encodeImage()` expects, matching the HF Qwen2-VL image processor:
|
|
2196
|
+
* smart_resize → rescale (×1/255) → normalize → temporal-pair (×temporal_patch_size)
|
|
2197
|
+
* → patchify into spatial_merge×spatial_merge blocks → flatten to [N, C·T·P·P].
|
|
2198
|
+
*
|
|
2199
|
+
* @param pixels row-major HWC RGB, length width*height*3. Values 0..255 (default)
|
|
2200
|
+
* or already 0..1 if `rescaled` is true.
|
|
2201
|
+
* @param width source pixel width
|
|
2202
|
+
* @param height source pixel height
|
|
2203
|
+
*/
|
|
2204
|
+
declare function preprocessImage(pixels: Float32Array | Uint8ClampedArray | Uint8Array, width: number, height: number, cfg?: ImageProcessorConfig, rescaled?: boolean): PreprocessedImage;
|
|
2205
|
+
declare function buildMRoPEPositionIds(inputIds: Int32Array | Uint32Array | number[], imageGrids: Array<[number, number, number]>, imageTokenId: number, mergeSize: number): Int32Array;
|
|
2206
|
+
/**
|
|
2207
|
+
* Per-pair frequency→dimension assignment for interleaved M-RoPE, matching
|
|
2208
|
+
* Qwen3_5TextRotaryEmbedding.apply_interleaved_mrope. For pair index i in
|
|
2209
|
+
* [0, sum(section)) the position component is section-cyclic: T,H,W,T,H,W,...
|
|
2210
|
+
* but each component capped at its section count. Returns an array of length
|
|
2211
|
+
* (rope_dim/2) with values 0=T, 1=H, 2=W.
|
|
2212
|
+
*/
|
|
2213
|
+
declare function mropeFreqDims(mropeSection: [number, number, number]): Int32Array;
|
|
2214
|
+
/**
|
|
2215
|
+
* Build the interleaved-M-RoPE cos/sin tables [seq, rope_dim] from 3D position
|
|
2216
|
+
* ids, matching Qwen3_5TextRotaryEmbedding.forward:
|
|
2217
|
+
* freqs[d][i] = pos[d] * inv_freq[i], inv_freq[i] = 1/theta^(2i/rope_dim)
|
|
2218
|
+
* freq[i] picks component mropeFreqDims[i]; emb = cat(freqs, freqs).
|
|
2219
|
+
* cos/sin have length seq*rope_dim. For text-only (all 3 pos rows equal) this
|
|
2220
|
+
* reduces exactly to standard 1D partial RoPE.
|
|
2221
|
+
*
|
|
2222
|
+
* @param positionIds3 [3, seq] as produced by buildMRoPEPositionIds.
|
|
2223
|
+
* @param ropeDim number of rotated dims per head (head_dim * partial_factor).
|
|
2224
|
+
*/
|
|
2225
|
+
declare function buildMRoPECosSin(positionIds3: Int32Array, seq: number, ropeDim: number, theta: number, mropeSection: [number, number, number]): {
|
|
2226
|
+
cos: Float32Array;
|
|
2227
|
+
sin: Float32Array;
|
|
2228
|
+
};
|
|
2229
|
+
//#endregion
|
|
2230
|
+
//#region src/gpu/defaults.d.ts
|
|
2231
|
+
/**
|
|
2232
|
+
* Default model per capability. Kept in its own tiny module (no heavy imports)
|
|
2233
|
+
* so the React hooks can resolve defaults without statically pulling in the GPU
|
|
2234
|
+
* engine — they import the engine dynamically to stay light.
|
|
2235
|
+
*/
|
|
2236
|
+
declare const DEFAULT_MODELS: {
|
|
2237
|
+
/** Text generation (also the vision-capable checkpoint). */
|
|
2238
|
+
readonly text: "mlx-community/Qwen3.5-0.8B-4bit";
|
|
2239
|
+
/** Image understanding — same checkpoint, vision tower built on demand. */
|
|
2240
|
+
readonly vision: "mlx-community/Qwen3.5-0.8B-4bit";
|
|
2241
|
+
/** Text embeddings. */
|
|
2242
|
+
readonly embedding: "mlx-community/embeddinggemma-300m-4bit";
|
|
2243
|
+
/** Text-to-speech (Apache-2.0; LFM2-350M backbone + NanoCodec). Small/fast default. */
|
|
2244
|
+
readonly tts: "nineninesix/kani-tts-450m-0.2-ft";
|
|
1689
2245
|
/**
|
|
1690
|
-
*
|
|
1691
|
-
*
|
|
2246
|
+
* Selectable many-voices TTS (OuteTTS-1.0-0.6B: Qwen3-0.6B codec-LM + DAC.speech).
|
|
2247
|
+
* Pass this as the `repo`/`model` to engine.speak()/useTTS to route to OuteTTS.
|
|
1692
2248
|
*/
|
|
1693
|
-
|
|
1694
|
-
idx: number;
|
|
1695
|
-
nodeId: string;
|
|
1696
|
-
opType: string;
|
|
1697
|
-
paramsU32: number[];
|
|
1698
|
-
dispatchSize: [number, number, number];
|
|
1699
|
-
}>;
|
|
2249
|
+
readonly ttsOute: "OuteAI/OuteTTS-1.0-0.6B";
|
|
1700
2250
|
/**
|
|
1701
|
-
*
|
|
1702
|
-
*
|
|
2251
|
+
* Selectable describe-your-voice TTS (Parler-TTS-mini-v1: Flan-T5 encoder +
|
|
2252
|
+
* cross-attending decoder LM + dac_44khz). Pass this as the `repo`/`model` to
|
|
2253
|
+
* engine.speak()/useTTS with a `describeVoice` natural-language description.
|
|
1703
2254
|
*/
|
|
1704
|
-
|
|
1705
|
-
idx: number;
|
|
1706
|
-
nodeId: string;
|
|
1707
|
-
opType: string;
|
|
1708
|
-
tensor: string;
|
|
1709
|
-
sum: number;
|
|
1710
|
-
first4: number[];
|
|
1711
|
-
uniformParams?: number[];
|
|
1712
|
-
}>>;
|
|
1713
|
-
debugWriteBuffer(tensorName: string, data: ArrayBufferView): void;
|
|
2255
|
+
readonly ttsParler: "parler-tts/parler-tts-mini-v1";
|
|
1714
2256
|
/**
|
|
1715
|
-
*
|
|
1716
|
-
*
|
|
1717
|
-
*
|
|
2257
|
+
* Selectable streaming TTS (Gepard-1.0: stock Qwen3.5 full-attention codec-LM +
|
|
2258
|
+
* 32 parallel FSQ classifier heads emitting a whole audio frame per forward +
|
|
2259
|
+
* NanoCodec 21.5 fps decoder). Apache-2.0. Frame-synchronous, so audio streams
|
|
2260
|
+
* out chunk-by-chunk via speak(text, { model, onChunk }).
|
|
1718
2261
|
*/
|
|
1719
|
-
|
|
1720
|
-
/**
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
/**
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
2262
|
+
readonly ttsGepard: "nineninesix/gepard-1.0";
|
|
2263
|
+
/** Speech-to-text. */
|
|
2264
|
+
readonly stt: "UsefulSensors/moonshine-base";
|
|
2265
|
+
};
|
|
2266
|
+
declare const OUTETTS_ASSETS: {
|
|
2267
|
+
/** Folded DAC.speech.v1.0 encoder+decoder weights (prep-dac-speech.py output). */
|
|
2268
|
+
readonly codecWeightsURL: "https://pub-0522ecec25fc412bb35a523f3e3f63c6.r2.dev/tryhamster/gerbil-outetts-assets/dac-speech-folded.safetensors";
|
|
2269
|
+
/** Base URL for the preset speaker JSONs (`{name}.json`). */
|
|
2270
|
+
readonly speakersBaseURL: "https://pub-0522ecec25fc412bb35a523f3e3f63c6.r2.dev/tryhamster/gerbil-outetts-assets/speakers";
|
|
2271
|
+
/** The default preset voice. */
|
|
2272
|
+
readonly defaultVoice: "en-female-1-neutral";
|
|
2273
|
+
};
|
|
2274
|
+
/**
|
|
2275
|
+
* Bundled OuteTTS preset voices.
|
|
2276
|
+
*
|
|
2277
|
+
* Provenance (honest labelling):
|
|
2278
|
+
* - `en-female-1-neutral` is the ONE AUTHENTIC OuteAI voice — the OuteTTS v3 bundled
|
|
2279
|
+
* default speaker (default_speakers/en-female-1-neutral), copied verbatim.
|
|
2280
|
+
* - `gerbil-voice-2..6` are DERIVED voices: distinct timbres produced by running
|
|
2281
|
+
* pitch/tempo-varied renderings of that authentic clip back through the REAL
|
|
2282
|
+
* DAC.speech encode + prosody pipeline (scripts/engine/stage-outetts-assets.py).
|
|
2283
|
+
* They are genuine DAC-encoded OuteSpeakers, but they are NOT OuteAI's own
|
|
2284
|
+
* `en-male-*` voices, so they are not labelled as such. (Earlier builds mislabelled
|
|
2285
|
+
* them as `en-male-2-neutral` etc.; renamed for honesty.)
|
|
2286
|
+
*/
|
|
2287
|
+
declare const OUTETTS_PRESET_VOICES: readonly ["en-female-1-neutral", "gerbil-voice-2", "gerbil-voice-3", "gerbil-voice-4", "gerbil-voice-5", "gerbil-voice-6"];
|
|
2288
|
+
/**
|
|
2289
|
+
* True when `repo`/`architecture` identify a text-to-speech checkpoint that must
|
|
2290
|
+
* run through a dedicated native TTS engine (KaniTTS, OuteTTS, or Parler-TTS)
|
|
2291
|
+
* rather than the single-graph base text loader. Mirrors the routing used by
|
|
2292
|
+
* `WebGPUEngine.speak()` / `Gerbil.ensureNativeTTSEngine`:
|
|
2293
|
+
* - Parler → encoder-decoder (`ParlerTTSForConditionalGeneration`)
|
|
2294
|
+
* - OuteTTS → Qwen3-0.6B codec-LM
|
|
2295
|
+
* - Kani → conv-backbone codec-LM (`KaniTTS2ForCausalLM`, or the Apache
|
|
2296
|
+
* default which is a plain `Lfm2ForCausalLM` — hence the repo-name
|
|
2297
|
+
* gate; the architecture string alone does not identify it).
|
|
2298
|
+
* At `create()` time only the repo is known (the architecture is discovered
|
|
2299
|
+
* during load), so the repo-name signals below cover all three DEFAULT_MODELS
|
|
2300
|
+
* and conventionally-named TTS repos; the architecture arg is honored when a
|
|
2301
|
+
* caller already has it.
|
|
2302
|
+
*/
|
|
2303
|
+
declare function isTTSRepo(repo?: string, architecture?: string): boolean;
|
|
2304
|
+
/** Resolve the model repo for a set of options, falling back to the defaults. */
|
|
2305
|
+
declare function resolveDefaultRepo(opts: {
|
|
2306
|
+
repo?: string;
|
|
2307
|
+
embedding?: boolean;
|
|
2308
|
+
enableVision?: boolean;
|
|
2309
|
+
}): string;
|
|
2310
|
+
//#endregion
|
|
2311
|
+
//#region src/gpu/architectures/gemma4_vision.d.ts
|
|
2312
|
+
interface Gemma4VisionGraphInfo {
|
|
2313
|
+
hiddenSize: number;
|
|
2314
|
+
numHeads: number;
|
|
2315
|
+
headDim: number;
|
|
2316
|
+
depth: number;
|
|
2317
|
+
intermediateSize: number;
|
|
2318
|
+
textHidden: number;
|
|
2319
|
+
patchSize: number;
|
|
2320
|
+
patchDim: number;
|
|
2321
|
+
poolingKernelSize: number;
|
|
2322
|
+
ropeTheta: number;
|
|
2323
|
+
rmsNormEps: number;
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* Resolve the Gemma 4 vision dims from a raw HF config. Accepts either the
|
|
2327
|
+
* top-level config (reads `.vision_config` + `.text_config.hidden_size`) or a
|
|
2328
|
+
* bare vision_config (then `textHidden` falls back to the projector row count if
|
|
2329
|
+
* present, else hidden). Family-general — no E2B constants.
|
|
2330
|
+
*/
|
|
2331
|
+
declare function resolveGemma4VisionInfo(rawConfig: Record<string, unknown>): Gemma4VisionGraphInfo;
|
|
2332
|
+
/**
|
|
2333
|
+
* Dequantize an MLX affine-int4 weight to a plain f32 [rows, cols] matrix.
|
|
2334
|
+
* MLX packs 8 int4 values per u32 (low-nibble first); each group of `groupSize`
|
|
2335
|
+
* columns shares one scale + bias: w[r,c] = scale[r, c/gs] * q + bias[r, c/gs].
|
|
2336
|
+
* Used for the Gemma 4 multimodal projector (`embed_vision.embedding_projection`)
|
|
2337
|
+
* in MLX-4bit checkpoints, where (unlike the BF16 ViT body) the projector is int4.
|
|
2338
|
+
*/
|
|
2339
|
+
declare function dequantizeMLXProjection(packed: Uint32Array, scales: Float32Array, biases: Float32Array, rows: number, cols: number, groupSize: number): Float32Array;
|
|
2340
|
+
/**
|
|
2341
|
+
* If the Gemma 4 multimodal projector arrived as an MLX affine-int4 triplet
|
|
2342
|
+
* (`embed_vision.embedding_projection.{weight(U32), scales, biases}`), dequantize
|
|
2343
|
+
* it in-place to a plain f32 `embed_vision.embedding_projection.weight` and drop
|
|
2344
|
+
* the scales/biases, so the vision graph's plain MatMul on the projector works for
|
|
2345
|
+
* MLX-4bit checkpoints too. No-op for BF16 (HF) checkpoints (weight already f32).
|
|
2346
|
+
*/
|
|
2347
|
+
declare function dequantizeGemma4VisionProjection(weights: Map<string, {
|
|
2348
|
+
data: ArrayBufferView;
|
|
2349
|
+
shape: number[];
|
|
2350
|
+
}>, groupSize: number, rows: number, cols: number): void;
|
|
2351
|
+
/**
|
|
2352
|
+
* Patch the ClippedMatMul nodes of a Gemma 4 vision graph with the calibrated clip
|
|
2353
|
+
* scalars from the checkpoint (Gemma4ClippableLinear's per-tensor input/output
|
|
2354
|
+
* min/max buffers), then drop those scalar tensors from the weights map so the
|
|
2355
|
+
* vision executor doesn't try to upload them as GPU buffers. Call BEFORE
|
|
2356
|
+
* VisionExecutor.uploadWeights(). Missing scalars default to ±inf (clip = identity),
|
|
2357
|
+
* so a checkpoint without calibration still loads.
|
|
2358
|
+
*/
|
|
2359
|
+
declare function patchGemma4VisionClips(graph: ModelGraph, weights: Map<string, {
|
|
2360
|
+
data: ArrayBufferView;
|
|
2361
|
+
shape: number[];
|
|
2362
|
+
}>): void;
|
|
2363
|
+
/**
|
|
2364
|
+
* Build the Gemma 4 ViT graph. Shaped by symbolic "N" (number of patches, runtime)
|
|
2365
|
+
* and "Np" (number of pooled tokens = ceil(grid_h/k)·ceil(grid_w/k), runtime),
|
|
2366
|
+
* resolved from input tensor dims — like the Qwen ViT's "N"/"Nm".
|
|
2367
|
+
*/
|
|
2368
|
+
declare function generateGemma4VisionGraph(rawConfig: Record<string, unknown>): ModelGraph;
|
|
2369
|
+
//#endregion
|
|
2370
|
+
//#region src/gpu/architectures/gepard_tts.d.ts
|
|
2371
|
+
/** Per-channel FSQ cardinalities in head order (channel c = group⌊c/4⌋, dim c%4). */
|
|
2372
|
+
declare function gepardChannelLevels(): number[];
|
|
2373
|
+
/** Text-repetition layout config (stamped into gepard_config.json). */
|
|
2374
|
+
interface GepardTextRepetition {
|
|
2375
|
+
enabled: boolean;
|
|
2376
|
+
targetTextTokens: number;
|
|
2377
|
+
applyBelow: number;
|
|
2378
|
+
maxRepeats: number;
|
|
2379
|
+
}
|
|
2380
|
+
/** Parsed gepard_config.json — everything the host driver needs. */
|
|
2381
|
+
interface GepardConfig {
|
|
2382
|
+
/** The nested Qwen3.5 backbone config (fed to generateGepardBackboneGraph). */
|
|
2383
|
+
backboneConfig: Record<string, unknown>;
|
|
2384
|
+
/** Per-head vocab sizes in channel order (level_audio_0..31). */
|
|
2385
|
+
vocabSizes: number[];
|
|
2386
|
+
audioEmbedDim: number;
|
|
2387
|
+
startOfText: number;
|
|
2388
|
+
endOfText: number;
|
|
2389
|
+
startOfSpeech: number;
|
|
2390
|
+
textRepetition: GepardTextRepetition;
|
|
2391
|
+
/** Codec identity + geometry from the config's codec block. */
|
|
2392
|
+
codecId: string;
|
|
2393
|
+
fsqLevels: number[];
|
|
2394
|
+
sampleRate: number;
|
|
2395
|
+
}
|
|
2396
|
+
/**
|
|
2397
|
+
* Parse gepard_config.json. Head order is recovered from the numeric suffix of
|
|
2398
|
+
* each `level_audio_{i}` key (NOT the serialized key order), so the wiring is
|
|
2399
|
+
* immune to any JSON round-trip that re-sorts keys.
|
|
2400
|
+
*/
|
|
2401
|
+
declare function parseGepardConfig(raw: Record<string, unknown>): GepardConfig;
|
|
2402
|
+
/**
|
|
2403
|
+
* Build the (possibly repeated) text-id layout — mirrors the reference
|
|
2404
|
+
* TextRepeater byte-for-byte (text_repetition.py):
|
|
2405
|
+
* R == 1 → [ SOT, *text, EOT, SOS ]
|
|
2406
|
+
* R > 1 → [ SOT, *text, EOT ] × (R−1) + [ SOT, *text, EOT, SOS ]
|
|
2407
|
+
* Only the final (canonical) copy carries SOS. R is deterministic from the
|
|
2408
|
+
* text-token count: ceil(target / n) capped at maxRepeats, applied only when
|
|
2409
|
+
* n < applyBelow (short prompts derail without the extra text mass).
|
|
2410
|
+
*/
|
|
2411
|
+
declare function buildGepardTextLayout(textTokenIds: number[], cfg: GepardConfig): number[];
|
|
2412
|
+
/**
|
|
2413
|
+
* Dequantize per-channel FSQ codes [32, T] (channel-major) into the decoder
|
|
2414
|
+
* latent [32, T] f32: value = (code − L//2) / (L//2), matching
|
|
2415
|
+
* UnfoldedCodecModel.decode_from_codes / codec_ops.dequantize_codes.
|
|
2416
|
+
*/
|
|
2417
|
+
declare function gepardCodesToLatent(codes: Uint32Array, numFrames: number): Float32Array;
|
|
2418
|
+
declare function generateGepardBackboneGraph(backboneConfig: Record<string, unknown>, dtype?: GraphDType, groupSizeOverride?: number, kvDtype?: KVDType): ModelGraph;
|
|
2419
|
+
//#endregion
|
|
2420
|
+
//#region src/gpu/architectures/kani_tts.d.ts
|
|
2421
|
+
/** Parsed KaniTTS2 backbone config (the LFM2 dims + the TTS-specific fields). */
|
|
2422
|
+
interface KaniConfig {
|
|
2423
|
+
textVocabSize: number;
|
|
2424
|
+
vocabSize: number;
|
|
2425
|
+
tokensPerFrame: number;
|
|
2426
|
+
audioStep: number;
|
|
2427
|
+
useLearnableRope: boolean;
|
|
2428
|
+
alphaMin: number;
|
|
2429
|
+
alphaMax: number;
|
|
2430
|
+
speakerEmbDim: number;
|
|
2431
|
+
audioTokensStart: number;
|
|
2432
|
+
startOfSpeech: number;
|
|
2433
|
+
endOfSpeech: number;
|
|
2434
|
+
codebookSize: number;
|
|
1728
2435
|
/**
|
|
1729
|
-
*
|
|
1730
|
-
*
|
|
1731
|
-
*
|
|
1732
|
-
*
|
|
1733
|
-
*
|
|
1734
|
-
*
|
|
1735
|
-
*
|
|
1736
|
-
* Graph outputs and tensors read before they are written (cross-forward
|
|
1737
|
-
* state) keep dedicated buffers. Within a forward, dispatches execute in
|
|
1738
|
-
* executionOrder on every path (single-pass Dawn, per-dispatch WebKit), and
|
|
1739
|
-
* WebGPU synchronizes hazards between dispatches, so reuse is safe.
|
|
1740
|
-
*
|
|
1741
|
-
* Caveat: debugReadBuffer() on an intermediate tensor is only meaningful
|
|
1742
|
-
* before a later op reuses its buffer (probes that stop mid-graph are fine).
|
|
2436
|
+
* Whether the backbone uses KaniTTS2's frame-level position IDs (the 4 audio
|
|
2437
|
+
* tokens of a frame share one logical position) + learnable per-layer RoPE.
|
|
2438
|
+
* The `KaniTTS2ForCausalLM` checkpoints (e.g. kani-tts-2-en) do; the plain
|
|
2439
|
+
* `Lfm2ForCausalLM` family (e.g. the Apache kani-tts-450m-0.2-ft / kani-tts-370m)
|
|
2440
|
+
* does NOT — those run standard sequential positions with α=1 plain RoPE.
|
|
2441
|
+
* Detected from the presence of `use_learnable_rope`/`tokens_per_frame`/the
|
|
2442
|
+
* `KaniTTS2ForCausalLM` architecture marker in config.json.
|
|
1743
2443
|
*/
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
2444
|
+
frameLevelPositions: boolean;
|
|
2445
|
+
}
|
|
2446
|
+
declare function parseKaniConfig(rawConfig: Record<string, unknown>): KaniConfig;
|
|
2447
|
+
/**
|
|
2448
|
+
* Convert the model's flat audio-token stream (the IDs between start/end-of-speech)
|
|
2449
|
+
* into NanoCodec codes [NUM_GROUPS, T]. Mirrors NemoAudioPlayer.get_nano_codes:
|
|
2450
|
+
* reshape [-1, 4]; codes[:,c] -= codebook_size*c; codes -= audio_tokens_start;
|
|
2451
|
+
* transpose → [4, T]. Returns a Uint32Array laid out group-major ([g*T + t]).
|
|
2452
|
+
*/
|
|
2453
|
+
declare function audioTokensToCodes(audioTokenIds: number[], cfg?: KaniConfig): {
|
|
2454
|
+
codes: Uint32Array;
|
|
2455
|
+
numFrames: number;
|
|
2456
|
+
};
|
|
2457
|
+
interface NanoCodecGraphOptions {
|
|
2458
|
+
/** Number of audio frames T (the code grid width). PCM length = T * hop. */
|
|
2459
|
+
numFrames: number;
|
|
1749
2460
|
/**
|
|
1750
|
-
*
|
|
1751
|
-
*
|
|
2461
|
+
* Optional decoder geometry override. Defaults to the Kani 12.5 fps codec
|
|
2462
|
+
* (rates [7,7,6,3,2], input dim 16). Gepard's 21.5 fps codec is the SAME
|
|
2463
|
+
* CausalHiFiGANDecoder family with rates [8,8,4,2,2] and input dim 32.
|
|
1752
2464
|
*/
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
*
|
|
1759
|
-
*
|
|
1760
|
-
*
|
|
1761
|
-
* Numerics are identical to running the two MatVecInt4 kernels separately
|
|
1762
|
-
* (same dequant, same K-parallel reduction), so this is WebKit-safe: it reuses
|
|
1763
|
-
* the proven INT4 matvec math and only merges two writes into one dispatch.
|
|
1764
|
-
*
|
|
1765
|
-
* Must run AFTER fuseSwiGLUDecodeEntries so the MLP gate+up pair (which is
|
|
1766
|
-
* consumed by a SwiGLU node) is already collapsed and won't be matched here.
|
|
2465
|
+
geometry?: {
|
|
2466
|
+
upSampleRates: readonly number[];
|
|
2467
|
+
inputDim: number;
|
|
2468
|
+
};
|
|
2469
|
+
/**
|
|
2470
|
+
* When true the graph takes the dequantized FSQ latent directly as a host
|
|
2471
|
+
* input ("nc_latent" [inputDim, T] f32) instead of packed "audio_codes" u32 —
|
|
2472
|
+
* used by Gepard, whose 32 per-dimension codes dequantize trivially host-side.
|
|
1767
2473
|
*/
|
|
1768
|
-
|
|
2474
|
+
latentInput?: boolean;
|
|
2475
|
+
}
|
|
2476
|
+
declare function generateNanoCodecDecoderGraph(opts: NanoCodecGraphOptions): ModelGraph;
|
|
2477
|
+
/**
|
|
2478
|
+
* Generate the KaniTTS codec-LM backbone graph (LFM2-350M body, full-vocab logits,
|
|
2479
|
+
* per-layer learnable MRoPE). Mirrors generateLfm2Graph block-for-block; the only
|
|
2480
|
+
* difference is that each attention layer rotates Q/K with the MRoPE op fed a
|
|
2481
|
+
* per-layer host cos/sin table instead of the position-counter RoPE op.
|
|
2482
|
+
*
|
|
2483
|
+
* dtype:
|
|
2484
|
+
* • undefined / "f32" — full precision (the checkpoint is bf16/f32 → f32).
|
|
2485
|
+
* • "q4" — weight-only group-wise INT4 (W4A16) for the LFM2 linear projections
|
|
2486
|
+
* (q/k/v/o, gate/up/down, short-conv in/out_proj) via MatMulInt4. The tied
|
|
2487
|
+
* embedding + LM head stay at f32 on purpose: decoder-LM heads are the most
|
|
2488
|
+
* quantization-sensitive part of a codec-LM and the embed table is the single
|
|
2489
|
+
* buffer shared by both, so quantizing it would degrade audio quality for no
|
|
2490
|
+
* load-size win that the int4 projections do not already capture. Backbone
|
|
2491
|
+
* drops from ~1.5 GB (f32) to ~0.37 GB. The NanoCodec decoder is a separate
|
|
2492
|
+
* graph and always runs f32.
|
|
2493
|
+
*/
|
|
2494
|
+
declare function generateKaniTtsGraph(rawConfig: Record<string, unknown>, dtype?: GraphDType, groupSize?: number, kvDtype?: KVDType): ModelGraph;
|
|
2495
|
+
//#endregion
|
|
2496
|
+
//#region src/gpu/architectures/moonshine.d.ts
|
|
2497
|
+
/**
|
|
2498
|
+
* Historical record of the executor-side work the keystone left open; all items
|
|
2499
|
+
* are now implemented (see the STATUS note above). Retained as exported
|
|
2500
|
+
* documentation of the dependency surface.
|
|
2501
|
+
*/
|
|
2502
|
+
declare const MOONSHINE_REMAINING_WORK: readonly ["DONE: Transpose op kernel (conv output [C,L] → encoder input [L,C]).", "DONE: GroupNorm(num_groups=1) over conv channels (weight+bias).", "DONE: Tanh elementwise op (conv1 activation).", "DONE: Interleaved-RoPE variant (Moonshine rotates adjacent dim pairs 2p/2p+1).", "DONE: No-bias LayerNorm variant (Moonshine norms are weight-only).", "DONE: Per-utterance conv graph regeneration from the concrete sample count.", "DONE: Dual-graph executor — encoder once → frozen cross-attn K/V → AR decode.", "DONE: engine.transcribe(pcm) host path (MoonshineSTT): conv → encode → AR decode → detokenize."];
|
|
2503
|
+
interface MoonshineDims {
|
|
2504
|
+
hidden_size: number;
|
|
2505
|
+
enc_layers: number;
|
|
2506
|
+
dec_layers: number;
|
|
2507
|
+
num_heads: number;
|
|
2508
|
+
num_kv_heads: number;
|
|
2509
|
+
head_dim: number;
|
|
2510
|
+
rotary_dim: number;
|
|
2511
|
+
intermediate_size: number;
|
|
2512
|
+
vocab_size: number;
|
|
2513
|
+
rope_base: number;
|
|
2514
|
+
context_length: number;
|
|
2515
|
+
ln_eps: number;
|
|
2516
|
+
}
|
|
2517
|
+
/** Pull and derive Moonshine dimensions from the raw HF config. */
|
|
2518
|
+
declare function parseMoonshineConfig(raw: Record<string, unknown>): MoonshineDims;
|
|
2519
|
+
/** Number of encoder frames produced by the conv frontend for n_samples PCM. */
|
|
2520
|
+
declare function moonshineEncoderFrames(nSamples: number): number;
|
|
2521
|
+
/**
|
|
2522
|
+
* Encoder graph: raw-waveform conv frontend + bidirectional transformer.
|
|
2523
|
+
* @param nSamples concrete PCM sample count (the conv frontend is length-static).
|
|
2524
|
+
* The output tensor "encoder_out" is [T_frames, hidden] and is consumed (after
|
|
2525
|
+
* per-layer K/V projection) as the frozen K/V for the decoder's cross-attention.
|
|
2526
|
+
*/
|
|
2527
|
+
declare function generateMoonshineEncoderGraph(raw: Record<string, unknown>, nSamples: number): ModelGraph;
|
|
2528
|
+
/**
|
|
2529
|
+
* Decoder graph: AR transformer with causal self-attn (KV-cache) + cross-attn to
|
|
2530
|
+
* the frozen encoder output. Built for a single decode step (T=1). The encoder K/V
|
|
2531
|
+
* are supplied as graph inputs "enc_k_layer{i}" / "enc_v_layer{i}" — the host
|
|
2532
|
+
* pre-projects the encoder output through each layer's encoder_attn.k_proj/v_proj
|
|
2533
|
+
* ONCE and binds them frozen for the whole decode (the CrossAttention contract).
|
|
2534
|
+
*/
|
|
2535
|
+
declare function generateMoonshineDecoderGraph(raw: Record<string, unknown>, sEnc: number): ModelGraph;
|
|
2536
|
+
//#endregion
|
|
2537
|
+
//#region src/gpu/architectures/outetts.d.ts
|
|
2538
|
+
/** Parsed OuteTTS backbone config (the Qwen3 dims). Reuses the generic Qwen path. */
|
|
2539
|
+
interface OuteTtsConfig {
|
|
2540
|
+
hidden_size: number;
|
|
2541
|
+
num_layers: number;
|
|
2542
|
+
num_heads: number;
|
|
2543
|
+
num_kv_heads: number;
|
|
2544
|
+
head_dim: number;
|
|
2545
|
+
intermediate_size: number;
|
|
2546
|
+
vocab_size: number;
|
|
2547
|
+
rope_base: number;
|
|
2548
|
+
rms_norm_eps: number;
|
|
2549
|
+
max_position_embeddings: number;
|
|
2550
|
+
}
|
|
2551
|
+
declare function parseOuteTtsConfig(rawConfig: Record<string, unknown>): OuteTtsConfig;
|
|
2552
|
+
/**
|
|
2553
|
+
* Convert the flat generated token stream (the run BETWEEN <|audio_start|> and
|
|
2554
|
+
* <|audio_end|>) into a DAC code grid [NUM_CODEBOOKS, T] (codebook-major [c*T + t]).
|
|
2555
|
+
*
|
|
2556
|
+
* Mirrors outetts v3 `extract_audio_from_tokens`: filter the stream into two ordered
|
|
2557
|
+
* lists by membership of the c1 / c2 ranges, recover the raw code
|
|
2558
|
+
* (id − base), drop the reserved index 1024, and truncate to the shorter list. The
|
|
2559
|
+
* model emits c1,c2 interleaved per frame, but the parse is membership-based (it does
|
|
2560
|
+
* not assume strict pairing) so a stray token cannot desync the grid.
|
|
2561
|
+
*/
|
|
2562
|
+
declare function audioTokensToDacCodes(tokenIds: number[]): {
|
|
2563
|
+
codes: Uint32Array;
|
|
2564
|
+
numFrames: number;
|
|
2565
|
+
};
|
|
2566
|
+
interface DacSpeechGraphOptions {
|
|
2567
|
+
/** Number of audio frames T (the code-grid width). */
|
|
2568
|
+
numFrames: number;
|
|
1769
2569
|
/**
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
1772
|
-
*
|
|
1773
|
-
* src/dst buffers writes both — removing one GPU round-trip per layer.
|
|
1774
|
-
*
|
|
1775
|
-
* Supports f32, native-f16, and packed-f16 caches (the dual kernel mirrors the
|
|
1776
|
-
* single-append kernel selected for the active kvMode). Numerically identical
|
|
1777
|
-
* to the separate appends — WebKit-safe (the packed-f16 variant is the Safari
|
|
1778
|
-
* path and uses pack2x16float, no `enable f16`).
|
|
2570
|
+
* Number of RVQ codebooks. Default 2 (DAC.speech.v1.0). Parler-TTS's
|
|
2571
|
+
* dac_44khz_8kbps uses 9 — the RVQDequant kernel loops over NCB so the only
|
|
2572
|
+
* change is the codebook/out_proj tensor first-dim and the codes grid height.
|
|
1779
2573
|
*/
|
|
1780
|
-
|
|
2574
|
+
numCodebooks?: number;
|
|
1781
2575
|
/**
|
|
1782
|
-
*
|
|
1783
|
-
*
|
|
1784
|
-
*
|
|
1785
|
-
* dispatch — removing the standalone SigmoidGate (one round-trip per
|
|
1786
|
-
* full-attention layer).
|
|
1787
|
-
*
|
|
1788
|
-
* Numerically identical to SigmoidGate→MatVecInt4 (same gate formula, same INT4
|
|
1789
|
-
* dequant + reduction). Slight extra ALU: the gated input is recomputed per
|
|
1790
|
-
* output column, but A reads hit L1 and the saved submit+drain dominates on
|
|
1791
|
-
* mobile. WebKit risk: low — reuses the proven INT4 matvec, only the A vector
|
|
1792
|
-
* is built from two reads + a sigmoid (no new reduction/barrier pattern).
|
|
1793
|
-
*
|
|
1794
|
-
* Runs after the dual fusions so it only sees the post-attention SigmoidGate.
|
|
2576
|
+
* Decoder upsampling rates (the DecoderBlock strides). Default [8,5,4,2]
|
|
2577
|
+
* (DAC.speech.v1.0). Parler's dac_44khz uses [8,8,4,2] (hop 512). Channels
|
|
2578
|
+
* still halve per stage from `decoderDim`.
|
|
1795
2579
|
*/
|
|
1796
|
-
|
|
2580
|
+
decoderRates?: readonly number[];
|
|
2581
|
+
/** First-conv output channels (decoder_dim). Default 1536. */
|
|
2582
|
+
decoderDim?: number;
|
|
2583
|
+
/** Quantizer out_proj output / first-conv input dim (latent_dim). Default 1024. */
|
|
2584
|
+
latentDim?: number;
|
|
2585
|
+
}
|
|
2586
|
+
declare function generateDacSpeechDecoderGraph(opts: DacSpeechGraphOptions): ModelGraph;
|
|
2587
|
+
/**
|
|
2588
|
+
* Final decoded PCM length (samples) for a T-frame code grid, via the DAC stage chain.
|
|
2589
|
+
* `rates` defaults to DAC.speech.v1.0 ([8,5,4,2]); pass [8,8,4,2] for Parler's dac_44khz.
|
|
2590
|
+
*/
|
|
2591
|
+
declare function dacOutputLength(numFrames: number, rates?: readonly number[]): number;
|
|
2592
|
+
declare function generateOuteTtsBackboneGraph(rawConfig: Record<string, unknown>, dtype?: GraphDType, groupSize?: number, kvDtype?: KVDType): ModelGraph;
|
|
2593
|
+
//#endregion
|
|
2594
|
+
//#region src/gpu/architectures/qwen3_5_vision.d.ts
|
|
2595
|
+
/**
|
|
2596
|
+
* Build the ViT graph. The graph is shaped by symbolic "N" (number of patches),
|
|
2597
|
+
* resolved at run time from the input tensor's first dim — exactly like the LM's
|
|
2598
|
+
* symbolic "T".
|
|
2599
|
+
*/
|
|
2600
|
+
declare function generateQwen3_5VisionGraph(rawConfig: Record<string, unknown>): ModelGraph;
|
|
2601
|
+
//#endregion
|
|
2602
|
+
//#region src/gpu/gepard-tts.d.ts
|
|
2603
|
+
/**
|
|
2604
|
+
* Gepard-1.0 — native streaming text-to-speech engine for Gerbil's WebGPU backend.
|
|
2605
|
+
*
|
|
2606
|
+
* Gepard (nineninesix/gepard-1.0, Apache-2.0) is a frame-synchronous multihead
|
|
2607
|
+
* codec-LM TTS. Unlike the token-serial Kani/Oute engines (which emit one codec
|
|
2608
|
+
* token per forward), Gepard emits ONE WHOLE AUDIO FRAME per backbone forward:
|
|
2609
|
+
*
|
|
2610
|
+
* 1. BACKBONE (GPU): stock Qwen3.5 full-attention body (14 layers, hidden 1024)
|
|
2611
|
+
* run through generateGepardBackboneGraph — input is a host-written
|
|
2612
|
+
* `inputs_embeds` row, output is the last position's final-norm hidden state.
|
|
2613
|
+
* 2. OVERLAY (host): 32 parallel classifier heads (cardinalities [8,7,6,6]×8,
|
|
2614
|
+
* 216 logits total) + a binary stop head read the hidden state; the sampled
|
|
2615
|
+
* 32 FSQ codes feed back through per-channel embeddings → concat → GELU MLP
|
|
2616
|
+
* → affine-free LayerNorm → × audio_embed_scale as the next step's input.
|
|
2617
|
+
* 3. CODEC (GPU): NanoCodec 21.5 fps decoder (same validated CausalHiFiGAN
|
|
2618
|
+
* graph family as Kani's codec, rates [8,8,4,2,2], hop 1024) turns the
|
|
2619
|
+
* per-channel codes into 22.05 kHz PCM — decoded incrementally in chunks
|
|
2620
|
+
* with left-context lookback, so audio streams out while the model is
|
|
2621
|
+
* still generating (≈1.5 s of audio per 32-frame chunk).
|
|
2622
|
+
*
|
|
2623
|
+
* Mirrors gepard-inference (runner.py) semantics: adaptive text repetition,
|
|
2624
|
+
* SOS-seeded first frame, stop-head termination, independent per-head sampling
|
|
2625
|
+
* (temperature / top-k / windowed repetition penalty) in fp32. Voice cloning
|
|
2626
|
+
* (the ref_compressor speaker prefix) is NOT wired yet — the default voice ships
|
|
2627
|
+
* first; cloning is a documented follow-up.
|
|
2628
|
+
*/
|
|
2629
|
+
/** Backbone weight precision for the codec-LM. */
|
|
2630
|
+
type GepardDType = "q4" | "f32";
|
|
2631
|
+
interface GepardTTSOptions {
|
|
2632
|
+
/** Backbone repo (default nineninesix/gepard-1.0, Apache-2.0). */
|
|
2633
|
+
repo?: string;
|
|
2634
|
+
/** NanoCodec repo (default the mlx-community 21.5 fps MLX mirror). */
|
|
2635
|
+
codecRepo?: string;
|
|
2636
|
+
revision?: string;
|
|
2637
|
+
hfToken?: string;
|
|
2638
|
+
cacheDir?: string;
|
|
1797
2639
|
/**
|
|
1798
|
-
*
|
|
1799
|
-
*
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1802
|
-
*
|
|
1803
|
-
*
|
|
1804
|
-
* The MLP SwiGLU is already collapsed into a SwiGLUMatVec entry by
|
|
1805
|
-
* fuseSwiGLUDecodeEntries (it has no surviving standalone SwiGLU node), so only
|
|
1806
|
-
* the Mamba SwiGLU matches here. Numerically identical to SwiGLU→MatVecInt4;
|
|
1807
|
-
* WebKit risk low (reuses the proven INT4 matvec, only the A vector changes).
|
|
2640
|
+
* Backbone weight precision (default "q4"):
|
|
2641
|
+
* • "q4" — group-wise INT4 (W4A16) on the Qwen3.5 linear projections
|
|
2642
|
+
* (backbone ~1.05 GB f32 → ~0.13 GB). Recommended default.
|
|
2643
|
+
* • "f32" — full precision (largest, highest fidelity).
|
|
2644
|
+
* The overlay always runs f32 on the host; the codec decoder always runs f32.
|
|
1808
2645
|
*/
|
|
1809
|
-
|
|
2646
|
+
dtype?: GepardDType;
|
|
2647
|
+
/** Max KV-cache length: text layout + 1 position per audio frame. Default 2048. */
|
|
2648
|
+
maxSeqLen?: number;
|
|
2649
|
+
onProgress?: (loaded: number, total: number, message: string) => void;
|
|
2650
|
+
}
|
|
2651
|
+
/** One streamed audio chunk (decoded incrementally during generation). */
|
|
2652
|
+
interface GepardAudioChunk {
|
|
2653
|
+
/** Mono PCM in [-1, 1] for this chunk only. */
|
|
2654
|
+
pcm: Float32Array;
|
|
2655
|
+
/** Sample rate (22050). */
|
|
2656
|
+
sampleRate: number;
|
|
2657
|
+
/** Index of the first audio frame this chunk covers. */
|
|
2658
|
+
frameIndex: number;
|
|
2659
|
+
/** True for the last chunk of the utterance. */
|
|
2660
|
+
isFinal: boolean;
|
|
2661
|
+
}
|
|
2662
|
+
interface GepardSpeakOptions {
|
|
2663
|
+
/** Sampling temperature (default 0.4 — the reference production operating point). */
|
|
2664
|
+
temperature?: number;
|
|
2665
|
+
/** Top-k cutoff per head (default 0 = disabled). */
|
|
2666
|
+
topK?: number;
|
|
2667
|
+
/** Stop-head sigmoid threshold (default 0.5). */
|
|
2668
|
+
stopThreshold?: number;
|
|
2669
|
+
/** Max audio frames to generate (caps duration; default 2000, ~93 s). */
|
|
2670
|
+
maxFrames?: number;
|
|
2671
|
+
/** Per-head repetition penalty over a recent-frame window (default 1.0 = off). */
|
|
2672
|
+
repetitionPenalty?: number;
|
|
2673
|
+
/** Number of recent frames tracked for repetitionPenalty (default 32). */
|
|
2674
|
+
repetitionWindow?: number;
|
|
1810
2675
|
/**
|
|
1811
|
-
*
|
|
1812
|
-
*
|
|
1813
|
-
*
|
|
1814
|
-
*
|
|
1815
|
-
* two separate RMSNorm dispatches. One round-trip saved per fused pair.
|
|
1816
|
-
*
|
|
1817
|
-
* WebKit risk: low — same single-workgroup reduction as the proven RMSNorm
|
|
1818
|
-
* kernel, only the row→input routing is added.
|
|
2676
|
+
* Streaming callback: called with each decoded audio chunk while generation is
|
|
2677
|
+
* still running (frame-synchronous decode → real streaming, not post-hoc
|
|
2678
|
+
* chunking). The final chunk carries `isFinal: true`. The returned SpeakResult
|
|
2679
|
+
* still contains the full assembled PCM.
|
|
1819
2680
|
*/
|
|
2681
|
+
onChunk?: (chunk: GepardAudioChunk) => void;
|
|
2682
|
+
}
|
|
2683
|
+
interface GepardSpeakResult {
|
|
2684
|
+
/** Mono PCM in [-1, 1]. */
|
|
2685
|
+
pcm: Float32Array;
|
|
2686
|
+
/** Sample rate (22050). */
|
|
2687
|
+
sampleRate: number;
|
|
2688
|
+
/** Number of audio frames decoded. */
|
|
2689
|
+
frames: number;
|
|
2690
|
+
/** Audio duration in seconds. */
|
|
2691
|
+
audioSeconds: number;
|
|
2692
|
+
}
|
|
2693
|
+
declare class GepardTTS {
|
|
2694
|
+
private ctx;
|
|
2695
|
+
private loaded;
|
|
2696
|
+
private tokenizer;
|
|
2697
|
+
private cfg;
|
|
2698
|
+
private maxSeqLen;
|
|
2699
|
+
private hidden;
|
|
2700
|
+
/** Backbone executor (built once; reused across speak() calls). */
|
|
2701
|
+
private backboneExec;
|
|
2702
|
+
/** Cached codec executor for the steady-state window size (chunk + lookback). */
|
|
2703
|
+
private codecExecCache;
|
|
2704
|
+
private _destroyed;
|
|
2705
|
+
/** The selected backbone precision ("q4" or "f32"). */
|
|
2706
|
+
readonly dtype: GepardDType;
|
|
2707
|
+
private audioEmbTables;
|
|
2708
|
+
private projW0;
|
|
2709
|
+
private projB0;
|
|
2710
|
+
private projW2;
|
|
2711
|
+
private projB2;
|
|
2712
|
+
private audioEmbedScale;
|
|
2713
|
+
private headW;
|
|
2714
|
+
private headB;
|
|
2715
|
+
private stopW;
|
|
2716
|
+
private stopB;
|
|
2717
|
+
private constructor();
|
|
2718
|
+
static create(options?: GepardTTSOptions): Promise<GepardTTS>;
|
|
2719
|
+
/** NanoCodec's fixed output sample rate (Hz). */
|
|
2720
|
+
get sampleRate(): number;
|
|
2721
|
+
/** Preset voices. Gepard ships one default voice (cloning is a follow-up). */
|
|
2722
|
+
availableVoices(): string[];
|
|
2723
|
+
/** Gather text-token rows from the raw BF16 embed table → f32 [T, hidden]. */
|
|
2724
|
+
private embedTextTokens;
|
|
1820
2725
|
/**
|
|
1821
|
-
*
|
|
1822
|
-
*
|
|
1823
|
-
*
|
|
1824
|
-
* race-free; prefill (T>1) keeps the two-dispatch path.
|
|
2726
|
+
* Embed one audio frame (32 FSQ codes) through the overlay stack:
|
|
2727
|
+
* per-channel lookup → concat → Linear→GELU→Linear → affine-free LayerNorm →
|
|
2728
|
+
* × audio_embed_scale. Mirrors GepardModel._embed_audio.
|
|
1825
2729
|
*/
|
|
1826
|
-
private
|
|
2730
|
+
private embedAudioFrame;
|
|
2731
|
+
/** Stop-head probability for a hidden state. */
|
|
2732
|
+
private stopProbability;
|
|
1827
2733
|
/**
|
|
1828
|
-
*
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
1831
|
-
* two norm outputs. Saves 2 dispatches per full-attention layer — a mobile
|
|
1832
|
-
* (WebKit submit-per-dispatch) win, desktop-neutral.
|
|
2734
|
+
* Sample one audio frame: all 32 heads independently in fp32 —
|
|
2735
|
+
* windowed repetition penalty → temperature → top-k → softmax → multinomial.
|
|
2736
|
+
* Mirrors GepardRunner._sample_frame / _sample_head_logits.
|
|
1833
2737
|
*/
|
|
2738
|
+
private sampleFrame;
|
|
1834
2739
|
/**
|
|
1835
|
-
*
|
|
1836
|
-
* is
|
|
1837
|
-
*
|
|
2740
|
+
* Synthesize speech for `text`. Returns 22.05 kHz mono PCM; when
|
|
2741
|
+
* `opts.onChunk` is set, audio streams out chunk-by-chunk during generation.
|
|
2742
|
+
*
|
|
2743
|
+
* Pipeline: adaptive-repetition text layout → prefill (host-gathered text
|
|
2744
|
+
* embeddings) → frame-synchronous AR loop (one frame = 32 codes per forward,
|
|
2745
|
+
* stop head decides termination) → incremental NanoCodec decode → PCM.
|
|
1838
2746
|
*/
|
|
1839
|
-
|
|
1840
|
-
private fuseDualRMSNormRoPEDecodeEntries;
|
|
1841
|
-
private fuseDualRMSNormDecodeEntries;
|
|
2747
|
+
speak(text: string, opts?: GepardSpeakOptions): Promise<GepardSpeakResult>;
|
|
1842
2748
|
/**
|
|
1843
|
-
*
|
|
1844
|
-
*
|
|
2749
|
+
* Decode frames [from, to) of the utterance to PCM, re-decoding up to
|
|
2750
|
+
* `GEPARD_CODEC_LOOKBACK_FRAMES` of left context as warm-up (discarded).
|
|
2751
|
+
* The decoder is causal with a bounded receptive field, so chunked output
|
|
2752
|
+
* matches a monolithic decode.
|
|
1845
2753
|
*/
|
|
1846
|
-
|
|
1847
|
-
/**
|
|
1848
|
-
private
|
|
2754
|
+
decodeFrameRange(frames: Uint32Array[], from: number, to: number): Promise<Float32Array>;
|
|
2755
|
+
/** Run the codec graph for one latent window → raw PCM (pre-clamp). */
|
|
2756
|
+
private decodeLatentWindow;
|
|
2757
|
+
destroy(): void;
|
|
1849
2758
|
}
|
|
1850
2759
|
//#endregion
|
|
1851
2760
|
//#region src/gpu/moonshine-executor.d.ts
|
|
@@ -2252,6 +3161,16 @@ interface GenerateResult {
|
|
|
2252
3161
|
totalTime: number;
|
|
2253
3162
|
/** Why generation stopped. */
|
|
2254
3163
|
finishReason: "eos" | "max_tokens" | "stop_sequence";
|
|
3164
|
+
/**
|
|
3165
|
+
* Decode-only wall time in ms (excludes prefill). Set by the batched path
|
|
3166
|
+
* (generateBatch) so aggregate decode throughput is measurable directly.
|
|
3167
|
+
*/
|
|
3168
|
+
decodeTime?: number;
|
|
3169
|
+
/**
|
|
3170
|
+
* Raw generated token ids, in order. Used by parity harnesses (e.g. the
|
|
3171
|
+
* paged-KV token-exactness gate) to compare runs without decode ambiguity.
|
|
3172
|
+
*/
|
|
3173
|
+
tokenIds?: number[];
|
|
2255
3174
|
/** Thinking content if model produced it (future). */
|
|
2256
3175
|
thinking?: string;
|
|
2257
3176
|
}
|
|
@@ -2364,8 +3283,14 @@ declare class WebGPUEngine {
|
|
|
2364
3283
|
private _outeVoice;
|
|
2365
3284
|
/** Source of the runtime LoRA adapter currently applied on the static base, if any. */
|
|
2366
3285
|
private _currentAdapter;
|
|
3286
|
+
/** Named adapters registered for per-lane batched decode (GERBIL_BATCH_LORA). */
|
|
3287
|
+
private _batchAdapters;
|
|
2367
3288
|
/** Lazily-created Parler-TTS engine (Flan-T5 encoder + decoder LM + dac_44khz). */
|
|
2368
3289
|
private _parlerTTS;
|
|
3290
|
+
/** Lazily-created Gepard-1.0 engine (Qwen3.5 multihead codec-LM + NanoCodec 21.5 fps). */
|
|
3291
|
+
private _gepardTTS;
|
|
3292
|
+
/** Lazily-created continuous-batching scheduler (GERBIL_BATCH, Phase 5). */
|
|
3293
|
+
private _scheduler;
|
|
2369
3294
|
/**
|
|
2370
3295
|
* WebKit group-size probe state. When true, a candidate group size is being
|
|
2371
3296
|
* tried this page-load and must be promoted (or capped) after the FIRST
|
|
@@ -2423,6 +3348,17 @@ declare class WebGPUEngine {
|
|
|
2423
3348
|
loadAdapter(source: string | null): Promise<void>;
|
|
2424
3349
|
/** The runtime LoRA adapter currently applied on the base, or null. */
|
|
2425
3350
|
getAdapter(): string | null;
|
|
3351
|
+
/**
|
|
3352
|
+
* Register a named LoRA adapter for per-lane batched decode (SGMV phase 1,
|
|
3353
|
+
* `GERBIL_BATCH=N` + `GERBIL_BATCH_LORA=1`). The adapter's factors are
|
|
3354
|
+
* fetched and packed into the executor's shared factor buffers ONCE; any
|
|
3355
|
+
* number of batch lanes can then run it concurrently by passing its name in
|
|
3356
|
+
* `generateBatch`'s per-lane `adapters` option. Registering the same name
|
|
3357
|
+
* twice is a no-op.
|
|
3358
|
+
*/
|
|
3359
|
+
registerAdapter(name: string, source: string): Promise<void>;
|
|
3360
|
+
/** Names of the adapters registered for per-lane batched decode. */
|
|
3361
|
+
getRegisteredAdapters(): string[];
|
|
2426
3362
|
/**
|
|
2427
3363
|
* Write a coarse crash-phase breadcrumb that survives a GPU-process kill / page
|
|
2428
3364
|
* reload. The iPad harness reads `localStorage["gerbil-crash-phase"]` after a
|
|
@@ -2501,6 +3437,55 @@ declare class WebGPUEngine {
|
|
|
2501
3437
|
* the same instance awaits this one (see {@link _genQueueTail}).
|
|
2502
3438
|
*/
|
|
2503
3439
|
generate(prompt: string | ChatMessage[], options?: GenerateOptions): Promise<GenerateResult>;
|
|
3440
|
+
/**
|
|
3441
|
+
* Batched lockstep generation (GERBIL_BATCH=N — Phase 2 of the
|
|
3442
|
+
* continuous-batching campaign, Dawn only).
|
|
3443
|
+
*
|
|
3444
|
+
* Prefills each prompt sequentially into its batch slot (KV into the slot's
|
|
3445
|
+
* block-table row, SSM/conv state adopted into the slot's state-pool rows),
|
|
3446
|
+
* then decodes all N sequences in lockstep: one batched dispatch stream per
|
|
3447
|
+
* step produces one token per row. Greedy-only in Phase 2 (per-row GPU
|
|
3448
|
+
* argmax); rows that hit EOS/stop keep decoding in lockstep with their
|
|
3449
|
+
* output discarded until every row has finished — rows never interact, so
|
|
3450
|
+
* per-row streams are token-exact vs single-sequence generation.
|
|
3451
|
+
*
|
|
3452
|
+
* Requires exactly `executor.batchSize` prompts (fixed lockstep batch; the
|
|
3453
|
+
* Phase 3 scheduler will lift this).
|
|
3454
|
+
*/
|
|
3455
|
+
generateBatch(prompts: Array<string | ChatMessage[]>, options?: GenerateOptions & {
|
|
3456
|
+
maxTokensPerRow?: number[];
|
|
3457
|
+
/**
|
|
3458
|
+
* Per-lane adapter names (SGMV phase 1 — requires GERBIL_BATCH_LORA=1
|
|
3459
|
+
* and prior {@link registerAdapter} calls). `adapters[i]` selects the
|
|
3460
|
+
* adapter lane i decodes with; null/undefined = bare base. Lanes with
|
|
3461
|
+
* different adapters (or none) decode concurrently over the shared base.
|
|
3462
|
+
*/
|
|
3463
|
+
adapters?: Array<string | null | undefined>;
|
|
3464
|
+
}): Promise<GenerateResult[]>;
|
|
3465
|
+
/**
|
|
3466
|
+
* Continuous-batching scheduler (GERBIL_BATCH=N — Phase 5 of the batching
|
|
3467
|
+
* campaign, Dawn only). Returns the engine's request scheduler (created on
|
|
3468
|
+
* first call): submit many requests with independent prompts/limits and the
|
|
3469
|
+
* scheduler multiplexes them over the batch lanes with dynamic admission,
|
|
3470
|
+
* immediate lane retirement, and chunked prefill. Greedy-only; per-request
|
|
3471
|
+
* output is token-exact vs single-sequence generate().
|
|
3472
|
+
*/
|
|
3473
|
+
createScheduler(): RequestScheduler;
|
|
3474
|
+
/**
|
|
3475
|
+
* Create a swarm — the MoTA mode-B primitive (docs/research/slm-swarms.md):
|
|
3476
|
+
* named members (LoRA adapters on this engine's shared base, or null for the
|
|
3477
|
+
* bare base), an optional plan that fans an input out into per-member
|
|
3478
|
+
* sub-tasks, and an optional reduce that joins the results. `swarm.run()`
|
|
3479
|
+
* decodes every sub-task CONCURRENTLY through the continuous-batching
|
|
3480
|
+
* scheduler with per-request adapters — one batched pass, so the fan-out
|
|
3481
|
+
* costs roughly the slowest member, not the sum.
|
|
3482
|
+
*
|
|
3483
|
+
* Every non-null member source is registered via {@link registerAdapter}
|
|
3484
|
+
* (downloads in parallel; factors packed into shared GPU buffers once).
|
|
3485
|
+
* Requires `GERBIL_BATCH=N` (N >= 2) + `GERBIL_BATCH_LORA=1` on the Dawn
|
|
3486
|
+
* path; greedy-only like the scheduler underneath.
|
|
3487
|
+
*/
|
|
3488
|
+
createSwarm<R = Record<string, ServeResult>>(options: SwarmOptions<R>): Promise<Swarm<R>>;
|
|
2504
3489
|
/**
|
|
2505
3490
|
* Resolve the end-of-turn stop token id, or null if the model has none.
|
|
2506
3491
|
*
|
|
@@ -2652,6 +3637,21 @@ declare class WebGPUEngine {
|
|
|
2652
3637
|
* default description is used.
|
|
2653
3638
|
*/
|
|
2654
3639
|
describeVoice?: string;
|
|
3640
|
+
/**
|
|
3641
|
+
* Stop-head sigmoid threshold (Gepard only, default 0.5). Higher = later stop.
|
|
3642
|
+
*/
|
|
3643
|
+
stopThreshold?: number;
|
|
3644
|
+
/**
|
|
3645
|
+
* Streaming audio callback (Gepard only). Gepard is frame-synchronous, so
|
|
3646
|
+
* audio is decoded and delivered chunk-by-chunk WHILE generation runs — the
|
|
3647
|
+
* callback fires with each ~1.5 s PCM chunk (the final one has isFinal:true).
|
|
3648
|
+
*/
|
|
3649
|
+
onChunk?: (chunk: {
|
|
3650
|
+
pcm: Float32Array;
|
|
3651
|
+
sampleRate: number;
|
|
3652
|
+
frameIndex: number;
|
|
3653
|
+
isFinal: boolean;
|
|
3654
|
+
}) => void;
|
|
2655
3655
|
}): Promise<{
|
|
2656
3656
|
pcm: Float32Array;
|
|
2657
3657
|
sampleRate: number;
|
|
@@ -2666,6 +3666,14 @@ declare class WebGPUEngine {
|
|
|
2666
3666
|
private speakParler;
|
|
2667
3667
|
/** A random natural-language voice description for Parler-TTS (host template bank). */
|
|
2668
3668
|
randomVoiceDescription(): string;
|
|
3669
|
+
/**
|
|
3670
|
+
* Gepard-1.0 speak path: lazily build the Gepard engine (stock Qwen3.5
|
|
3671
|
+
* full-attention codec-LM + 32 host-side FSQ classifier heads + NanoCodec
|
|
3672
|
+
* 21.5 fps decoder) and synthesize `text`. Frame-synchronous: when `onChunk`
|
|
3673
|
+
* is set, audio streams out during generation. Single default voice for now
|
|
3674
|
+
* (voice cloning via the ref-compressor is a documented follow-up).
|
|
3675
|
+
*/
|
|
3676
|
+
private speakGepard;
|
|
2669
3677
|
/**
|
|
2670
3678
|
* OuteTTS speak path: lazily build the OuteTTS engine (loading the chosen preset
|
|
2671
3679
|
* voice's speaker JSON + the folded DAC codec weights from the mirror), rebuilding
|
|
@@ -2845,5 +3853,5 @@ declare class WebGPUEngine {
|
|
|
2845
3853
|
private _acquireGenLock;
|
|
2846
3854
|
}
|
|
2847
3855
|
//#endregion
|
|
2848
|
-
export {
|
|
2849
|
-
//# sourceMappingURL=index-
|
|
3856
|
+
export { Gemma4VisionGraphInfo as $, LoRADelta as $t, GepardTTS as A, preprocessImageGemma4 as At, generateMoonshineEncoderGraph as B, ServeOptions as Bt, TranscribeOptions as C, buildMRoPEPositionIds as Ct, GepardDType as D, buildVisionPositionTensors as Dt, GepardAudioChunk as E, buildRotaryCosSin as Et, generateDacSpeechDecoderGraph as F, SwarmOptions as Ft, generateNanoCodecDecoderGraph as G, loadKaniTTS as Gt, parseMoonshineConfig as H, Executor as Ht, generateOuteTtsBackboneGraph as I, SwarmTask as It, buildGepardTextLayout as J, quantizeKaniBackbone as Jt, parseKaniConfig as K, loadModel as Kt, parseOuteTtsConfig as L, SwarmTaskOptions as Lt, generateQwen3_5VisionGraph as M, SamplingParams as Mt, audioTokensToDacCodes as N, Swarm as Nt, GepardSpeakOptions as O, mropeFreqDims as Ot, dacOutputLength as P, SwarmMemberResult as Pt, parseGepardConfig as Q, AdapterSource as Qt, MOONSHINE_REMAINING_WORK as R, RequestScheduler as Rt, MoonshineSTTOptions as S, buildMRoPECosSin as St, MoonshineEncoderExecutor as T, buildPositionIds as Tt, audioTokensToCodes as U, LoadedKaniTTS as Ut, moonshineEncoderFrames as V, ServeResult as Vt, generateKaniTtsGraph as W, LoadedMoonshine as Wt, gepardChannelLevels as X, Tokenizer as Xt, generateGepardBackboneGraph as Y, ChatMessage as Yt, gepardCodesToLatent as Z, AdapterConfig as Zt, ParlerSpeakOptions as _, VisionPositionTensors as _t, GenerateObjectOptions as a, KaniTTS as an, DEFAULT_MODELS as at, ParlerTTSOptions as b, buildGemma4RotaryCosSin as bt, GenerateResult as c, SpeakResult as cn, isTTSRepo as ct, ObjectSchema as d, GraphDType as dn, Gemma4VisionGridConfig as dt, applyLoRAToStore as en, dequantizeGemma4VisionProjection as et, ObjectValidator as f, KVDType as fn, Gemma4VisionPositionTensors as ft, VisionInputs as g, createDefaultHFKeyMapper as gn, VisionGridConfig as gt, VisionExecutor as h, ModelCapabilities as hn, QWEN3_5_IMAGE_PROCESSOR as ht, EncodeImageResult as i, KaniDType as in, resolveGemma4VisionInfo as it, GepardTTSOptions as j, smartResize as jt, GepardSpeakResult as k, preprocessImage as kt, IntegrityCheckEntry as l, GPUDiagnosticResult as ln, resolveDefaultRepo as lt, WebGPUEngineOptions as m, ModelArchConfig as mn, PreprocessedImage as mt, AgentTool as n, fetchAdapter as nn, generateGemma4VisionGraph as nt, GenerateObjectResult as o, KaniTTSOptions as on, OUTETTS_ASSETS as ot, WebGPUEngine as p, KvMode as pn, ImageProcessorConfig as pt, GepardConfig as q, loadMoonshine as qt, EmbedOptions as r, splitFusedQGateDelta as rn, patchGemma4VisionClips as rt, GenerateOptions as s, SpeakOptions as sn, OUTETTS_PRESET_VOICES as st, AgentStep as t, buildLoRADeltas as tn, dequantizeMLXProjection as tt, IntegrityCheckResult as u, initGPU as un, GEMMA4_IMAGE_PROCESSOR as ut, ParlerSpeakResult as v, buildGemma4PoolMatrix as vt, TranscribeResult as w, buildPosEmbeds as wt, MoonshineSTT as x, buildGemma4VisionPositionTensors as xt, ParlerTTS as y, buildGemma4PosEmbeds as yt, generateMoonshineDecoderGraph as z, SchedulerStats as zt };
|
|
3857
|
+
//# sourceMappingURL=index-Fj2XkP-o.d.mts.map
|