diff --git a/CHANGELOG.md b/CHANGELOG.md index 77e38e8fc..d5b8ce1e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,16 @@ - **Explicit SIMD128 bodies for every sidecar matmul kernel**: Q4_K, Q5_K, Q6_K, Q5_0 and Q5_1 join Q8_0 and Q4_0 with hand-written `__wasm_simd128__` paths (the K-quant int8 dots on `i32x4.dot_i16x8_s`, the Q5 fifth-bit plane via a byte swizzle); the dense FP32 GEMM gets a 4×8 register-blocked, k-blocked micro-kernel; BF16 and FP16 widen eight weights per step. BF16 and FP16 also gain NEON bodies (none existed on any tier). Results stay bit-identical in accumulation order to the portable bodies. - **Threaded dense kernels**: `skainet_fp32_matmul`, `skainet_bf16_matmul` and `skainet_fp16_matmul` now split the output over the shared row-thread pool like the packed kernels do (the GEMM over the longer side of `C`, the 16-bit kernels over columns) — the mt sidecar's pthreads in a browser, the native pool on the JVM. - **`WasmCpuOps.matmul`**: a dense 2-D `A · B` on heap FP32 operands runs the sidecar GEMM; the common `[k, n]` loop no longer serves that shape on wasm (it was the only dense path that never reached the sidecar). +- **Ternary kernels on the wasm tier**: the sidecar now exports `skainet_ternary_f32_gemv`, `skainet_ternary_lmhead_stage1` and `skainet_bitnet_gemv_tq2_0`, and `WasmTernaryKernelPack` (installed by `WasmSimd.install()`) registers them under the same `KernelDispatch` keys the JVM FFM and Android JNI ternary packs serve — exact FP32 × `BitNet-b1.58`, the fused multi-plane `BitNet-planes` lm_head and int8 × `TQ2_0`. A BitNet weight in a browser previously went to the decoding reference kernel. The portable branch of `skainet_ternary_f32.c` (MSVC and Emscripten) now holds the vendored kernel's 256-entry decode table as a compile-time constant with a SIMD128 body and threads over output rows through the shared row pool; `bitnet_gemv` gains a SIMD128 block body and the same row threading on every tier. +- **Direct `x · Wᵀ` on the JVM**: `Fp32MatmulWtKernel` / `KernelProvider.matmulFp32Wt()` (and the JVM-only `Fp32MemSegMatmulWtKernel` / `MemSegKernelProvider.matmulFp32WtMemSeg()`) expose `skainet_fp32_matmul_wt`, which the wasm sidecar has served since 0.61.0 and the JVM never bound. `NativeKernelProvider` carries it; `DefaultCpuOpsJvm.matmulWeightTransposed` multiplies a dense `[out, in]` FP32 weight in place through it — a segment-backed weight where it lies, a heap weight after one resident off-heap copy — instead of transposing the weight once into `[k, n]` and caching the copy. The wasm provider exposes the same accessor (`WasmFp32MatmulWtKernel`), and `KernelProvider.supports("matmul_wt", …)` reports it. +- **Kernel support matrix, fourth table** *Kernel packs (view dispatch)*: the ternary formats and the direct `x · Wᵀ` per platform, which the `matmul` accessors the first table introspects do not cover. +- **`engine-fp32-linear`** in `jvm-cpu-publish` and `wasm-bench`: a `Linear` layer's `x · Wᵀ` (16 tokens, 768 → 3072) through `ctx.ops.matmulWeightTransposed`; on the JVM `--provider panama` measures the cached-transpose path and `--provider ffm` the direct kernel. +- **Resident activations on the wasm tier**: `PlatformStorage.offHeapAllocator` (Kotlin/Wasm) binds `MemoryDomain.HOST_OFFHEAP` to a backend's memory; `WasmSimd.install()` binds it to the SIMD128 sidecar's linear memory (`WasmLinearStorage : Storage.OffHeap`, `WasmLinearTensorData : FloatArrayTensorData`), so `StorageCapabilities.current()` reports off-heap support in a browser and a `ForwardScope` frees sidecar activations at `reset()`. `WasmCpuOps` now keeps the outputs of `matmul`/`matmulWeightTransposed` resident and runs `add`/`subtract`/`multiply`/`divide`, `relu`/`sigmoid`/`silu`/`gelu`/`erf`/`tanh` and `sum`/`mean` in the sidecar on resident operands (transcendentals also on heap ones), reading a tensor back to the Kotlin heap only when something outside the sidecar needs it. Ambient outputs are freed when their tensor is collected (`FinalizationRegistry`); `WasmSimd.residentBudgetBytes` bounds what stays resident and `WasmCpuOps.toResident()` uploads an input once. New shared C kernels `skainet_f32_binary` / `skainet_f32_unary` / `skainet_f32_reduce_all` / `skainet_f32_reduce_axis` (SIMD128 and NEON bodies, row-pool threading) are in every native build as well. +- **`wasm-bench`**: a `laya.chain` case (`x · Wᵀ`, GELU, multiply, bias add, sum, end to end) and `operands=resident` variants of `engine-elementwise-add` and `engine-reductions-sum`. +- **Fused softmax, normalisations and attention on both tiers**: new shared C kernels `skainet_f32_softmax_rows`, `skainet_f32_layer_norm`, `skainet_f32_rms_norm` and `skainet_f32_sdpa` (`f32_fused.c`, SIMD128 and NEON bodies, row-pool threading) — one pass each where the ops layer ran several allocating scalar passes. `skainet_f32_sdpa` carries the full `scaledDotProductAttention` contract: grouped-query heads, `causal`, the sliding window with `rightContext`, per-batch `keyLengths` and the dense additive mask with its trailing-aligned broadcast. New SPI faces `Fp32SoftmaxKernel` / `Fp32NormKernel` / `Fp32AttentionKernel` behind `KernelProvider.softmaxFp32()` / `normFp32()` / `attentionFp32()` (reported by `supports("softmax" | "layer_norm" | "rms_norm" | "sdpa", ["Float32"])`), served by `NativeKernelProvider` (FFM) and `WasmKernelProvider`. On the JVM, `DefaultCpuOpsJvm` now routes `softmax`/`logSoftmax` over the last dimension (from 1024 elements) and `scaledDotProductAttention` to the native kernels when the FFM provider is loaded, replacing the scalar per-head loop; on wasmJs `WasmCpuOps` runs them in the sidecar with the outputs resident. +- **`FusedNormOps`** (`skainet-lang-core`): an optional interface an op set may implement beside `TensorOps` — `layerNorm` / `rmsNorm` over the trailing dimensions — which `LayerNormalization` consults for FP32 input before its decomposed chain. `DefaultCpuOpsJvm` (native kernel loaded) and `WasmCpuOps` implement it; recording and differentiating op sets never see it, so recorded graphs and gradients are unchanged. +- **`engine-softmax`, `engine-layer-norm`, `engine-sdpa`** in `jvm-cpu-publish` and `wasm-bench` (`--provider ffm` measures the fused kernels on the JVM, `panama`/`scalar` the common paths); the kernel support matrix's *Kernel packs* table gains rows for the three fused ops. +- **Native library no longer references libmvec**: GCC with `-ffast-math` vectorised the `expf`/`tanhf`/`erf` loops of `f32_ops.c` (and the new softmax) into glibc libmvec calls the shared library is not linked against — the symbol resolves lazily, so the first JVM call into such a kernel terminated the process (exit 127, no crash log). The transcendentals in element loops now go through opaque scalar wrappers in `skainet_simd.h`; `libskainet_kernels.so` has no `_ZGV*` reference left and stays loadable on glibc builds without libmvec. - **`wasm-bench` runs the `engine-*` scenarios** of the JVM publish harness (`engine-fp32-gemm`, `engine-kernel-matmul`, `engine-bf16-matmul`, `engine-q8-matmul`, `engine-q4-gemm`, `engine-ternary-f32-gemv`, `engine-elementwise-add`, `engine-reductions-sum/mean`) with identical shapes and metrics on every tier it measures, so the JVM and wasm numbers sit in one table. ## [0.61.0] - 2026-10-16 diff --git a/build-logic/convention/src/main/kotlin/GenerateKernelMatrixTask.kt b/build-logic/convention/src/main/kotlin/GenerateKernelMatrixTask.kt index 6964d6e02..8902f8cd2 100644 --- a/build-logic/convention/src/main/kotlin/GenerateKernelMatrixTask.kt +++ b/build-logic/convention/src/main/kotlin/GenerateKernelMatrixTask.kt @@ -128,6 +128,37 @@ abstract class GenerateKernelMatrixTask : DefaultTask() { appendLine("|===") appendLine("") } + if (module.packs.isNotEmpty()) { + appendLine("== Kernel packs (view dispatch)") + appendLine("") + appendLine( + "Kernels that are not reached through the `matmul` accessors above: the ternary formats " + + "are served by `ViewKernelPack`s keyed in `KernelDispatch` (`BitNet-b1.58` with exact " + + "FP32 activations, the fused multi-plane `BitNet-planes` lm_head, `TQ2_0` with int8 " + + "activations), and the dense `x · Wᵀ` of a `Linear` layer by the `matmulFp32Wt` " + + "accessor, which multiplies a row-major `[out, in]` weight in place instead of " + + "transposing and caching a copy. The fused row kernels — softmax / log-softmax, layer " + + "norm / RMS norm and scaled dot-product attention with its structural masks and " + + "grouped-query heads (`softmaxFp32`, `normFp32`, `attentionFp32`) — run those ops in one " + + "pass where the ops layer otherwise runs its scalar loops or the normalisation's " + + "decomposition. The scalar reference kernels serve every cell that is empty here (`—`), " + + "at reference speed; the common row-dot serves `x · Wᵀ` there.", + ) + appendLine("") + appendLine("[cols=\"$colSpec\", options=\"header\"]") + appendLine("|===") + append("| Kernel ") + module.platforms.forEach { append("| $it ") } + appendLine("") + appendLine("") + module.packs.forEach { fmt -> + append("| `${fmt.name}` ") + module.platforms.forEach { p -> append("| ${fmt.byPlatform[p] ?: "—"} ") } + appendLine("") + } + appendLine("|===") + appendLine("") + } appendLine( "See also the eager backends & kernels mindmap " + "(xref:explanation/eager-execution.adoc[]) for the narrative overview and gaps.", diff --git a/build-logic/convention/src/main/kotlin/models/KernelSupportModels.kt b/build-logic/convention/src/main/kotlin/models/KernelSupportModels.kt index 90da53887..fe8a43ebd 100644 --- a/build-logic/convention/src/main/kotlin/models/KernelSupportModels.kt +++ b/build-logic/convention/src/main/kotlin/models/KernelSupportModels.kt @@ -23,6 +23,12 @@ data class KernelSupportModule( */ val mapped: List = emptyList(), val resident: List = emptyList(), + /** + * Kernel packs (view dispatch): the ternary formats and the direct dense `x · Wᵀ`, served by + * `ViewKernelPack`s / the `matmulFp32Wt` accessor rather than the `matmul` accessors. Absent in + * older JSON. + */ + val packs: List = emptyList(), ) @Serializable diff --git a/docs/modules/ROOT/pages/contributing/benchmarks.adoc b/docs/modules/ROOT/pages/contributing/benchmarks.adoc index 69a2ed12d..09d243ec5 100644 --- a/docs/modules/ROOT/pages/contributing/benchmarks.adoc +++ b/docs/modules/ROOT/pages/contributing/benchmarks.adoc @@ -33,6 +33,7 @@ microbench tests under `skainet-backends/skainet-backend-native-cpu`: |=== | Scenario | What it exercises | Unit | Direction | engine-fp32-gemm | `ctx.ops.matmul` end-to-end FP32 square matmul | GFLOPS | higher is better +| engine-fp32-linear | `ctx.ops.matmulWeightTransposed` — a `Linear` layer's `x · Wᵀ` with the weight stored `[out, in]` (panama: cached transpose + GEMM; ffm: native direct kernel, no transposed copy) | GFLOPS | higher is better | engine-q4-gemm | `PanamaVectorQ4KMatmulKernel` F32 × Q4_K matmul at LLM shapes | GOP/s | higher is better | engine-kernel-matmul | Direct `Fp32MatmulKernel` (scalar vs panama-vector) | GFLOPS | higher is better | engine-bf16-matmul | `Bf16MatmulKernel` F32 × BF16-packed matmul (scalar vs panama-vector) | GFLOPS | higher is better @@ -40,6 +41,9 @@ microbench tests under `skainet-backends/skainet-backend-native-cpu`: | engine-elementwise-add | `ctx.ops.add` on 1M FP32 elements | M elements/s | higher is better | engine-reductions-sum | `ctx.ops.sum` on 1M FP32 elements | M elements/s | higher is better | engine-reductions-mean | `ctx.ops.mean` on 1M FP32 elements | M elements/s | higher is better +| engine-softmax | `ctx.ops.softmax` over the last dimension of `[1024, 1024]` FP32 (ffm: fused native rows; panama/scalar: the common loop) | M elements/s | higher is better +| engine-layer-norm | a `LayerNormalization` layer with affine over `[1024, 768]` FP32 (ffm: one fused native pass; panama/scalar: the decomposed elementwise chain) | M elements/s | higher is better +| engine-sdpa | `ctx.ops.scaledDotProductAttention`, causal, 12 heads × 150 × 150, head dim 64 (ffm: fused native kernel; panama/scalar: the common per-head kernel under the context's schedule) | GFLOPS (`4·h·s²·d`) | higher is better |=== Each scenario is wired up as a Phoronix test profile under diff --git a/docs/modules/ROOT/pages/explanation/eager-execution.adoc b/docs/modules/ROOT/pages/explanation/eager-execution.adoc index f251bfbe6..e0579d93e 100644 --- a/docs/modules/ROOT/pages/explanation/eager-execution.adoc +++ b/docs/modules/ROOT/pages/explanation/eager-execution.adoc @@ -29,7 +29,8 @@ mindmap Native FFM JVM only — C kernels via CMake Q4_K MemSeg zero-copy - Q6_K missing + direct x·Wᵀ, no transposed copy + ternary LUT gemv and planes lm_head Native JNI Android only — same C kernels via JNI two .so tiers, cpuinfo-gated armv8-a vs armv8.2+dotprod @@ -47,7 +48,7 @@ mindmap Android — scalar, Native-JNI Native linux — scalar, Native-cinterop Native apple — scalar, Accelerate, Native-cinterop - JS and WASM — scalar; packed weights relaid once into kernel feed order (SKEEP-007) + JS and WASM — scalar; wasmJs plus the SIMD128 sidecar pack: every matmul format, ternary, direct x·Wᵀ, resident activations, fused softmax / norms / attention (SKEEP-007) ---- == Kernel × provider (matmul, FP32 activations) @@ -56,6 +57,7 @@ mindmap |=== | Weight format | Scalar (all targets) | Panama (JVM) | Native FFM (JVM) | Native JNI (Android) | Native cinterop (K/N) | Wasm SIMD128 (wasmJs) | FP32 | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ +| FP32 `x · Wᵀ` direct (weight `[out, in]`, no transposed copy) | row-dot | ❌ (cached transpose) | ✅ | ❌ | ❌ | ✅ | BF16 | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | FP16 | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | Q8_0 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ @@ -65,9 +67,9 @@ mindmap | Q5_K | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Q5_1 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Q5_0 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ -| TQ2_0 / BitNet b1.58 (int8 activations) | ✅ | — | — | ✅ NEON | — | — -| BitNet b1.58 (exact FP32 activations, vendored NeoGPU kernel) | ✅ | ✅ FFM | — | ✅ JNI + fused lm_head | ✅ K/N cinterop | ❌ (reference) -| BITNET_PLANES (multi-plane ternary packing) | ✅ | via kernel pack | — | via kernel pack | via kernel pack | ❌ (reference) +| TQ2_0 / BitNet b1.58 (int8 activations) | ✅ | — | — | ✅ NEON | — | ✅ SIMD128 +| BitNet b1.58 (exact FP32 activations, LUT gemv) | ✅ | — | ✅ FFM (vendored NeoGPU) | ✅ JNI | ✅ K/N cinterop | ✅ SIMD128 LUT +| BITNET_PLANES (multi-plane ternary packing, fused lm_head) | ✅ | — | ✅ FFM (vendored NeoGPU) | ✅ JNI | ✅ K/N cinterop | ✅ SIMD128 LUT | Q2_K / Q3_K / Q8_K / IQ4 | ❌ (dequant to FP32 only) | ❌ | ❌ | ❌ | ❌ | ❌ |=== @@ -78,7 +80,24 @@ columns are mutually exclusive per platform rather than stacked. The Wasm SIMD12 sidecar pack (`skainet-backend-wasm-simd`), present only after `WasmSimd.install()`; every matmul kernel in it has an explicit SIMD128 body (the K-quant int8 dots on `i32x4.dot_i16x8_s`, a 4×8 register-blocked dense GEMM, eight-wide BF16/FP16 widening), and the dense kernels thread over the -sidecar's pthread pool like the packed ones when the page is cross-origin isolated. +sidecar's pthread pool like the packed ones when the page is cross-origin isolated. The ternary +kernels in the sidecar are the portable mirror of the vendored NeoGPU kernel — the same 256-entry +decode table as a compile-time constant, read four codes at a time in SIMD128 — registered under the +same dispatch keys as the FFM and JNI ternary packs (`WasmTernaryKernelPack`). + +The dense `x · Wᵀ` row is the product a `Linear` layer computes. The JVM and the wasm tier multiply +the `[out, in]` weight in place through `skainet_fp32_matmul_wt` (`KernelProvider.matmulFp32Wt`; +the JVM makes a heap weight resident off-heap once, the sidecar keeps it in its linear memory) and +never materialise the transposed copy the Panama path caches per weight. + +The fused row kernels — softmax / log-softmax over the last dimension, layer norm and RMS norm +(what `LayerNormalization` calls through the optional `FusedNormOps` hook), and scaled dot-product +attention with its structural masks and grouped-query heads — are one C pass each (`f32_fused.c`), +served by the JVM FFM provider (`softmaxFp32`, `normFp32`, `attentionFp32`) and the wasm sidecar +alike. Where neither is present the ops layer runs its common implementation: attention scalar per +head and parallel over heads through the schedule, the normalisation as its decomposed elementwise +chain. The decomposition is also what a recording or differentiating op set always sees, so the +hook changes no recorded graph and no gradient. Packed weights reach a kernel through the registry by declaring their xref:explanation/packed-weight-layout.adoc[block order]; a canonical weight is relayouted once, @@ -93,7 +112,7 @@ not per call. | *Android* | Scalar + Native-JNI | Panama (`jdk.incubator.vector`) and FFM (`java.lang.foreign`) are JDK-only — ART has neither, so Android's native tier is JNI, not a degraded JVM. Includes the NEON `bitnet_gemv` pack. | *Kotlin/Native — linux x64/arm64* | Scalar + Native-cinterop | Static archive embedded in the klib; manual `installNativeKernels()`, no ServiceLoader on K/N. | *Kotlin/Native — macOS/iOS* | Scalar + Apple Accelerate + Native-cinterop | Accelerate covers dense FP32 and reductions; cinterop covers packed quant with runtime `FEAT_DotProd` dispatch, so one archive serves A12 through M-series. -| *JS / WASM (Js, Wasi)* | Scalar; wasmJs + `skainet-backend-wasm-simd` | No SIMD in Kotlin code: packed weights are relaid once per weight into kernel feed order and served by the scalar packed kernels; dense `[out, in]` weights are multiplied in place without a transpose copy (SKEEP-007 Tier 0). On wasmJs, `WasmSimd.install()` adds the SIMD128 sidecar pack — the engine's C kernels compiled by Emscripten, with pthreads on cross-origin-isolated pages (SKEEP-007 Tier 1). +| *JS / WASM (Js, Wasi)* | Scalar; wasmJs + `skainet-backend-wasm-simd` | No SIMD in Kotlin code: packed weights are relaid once per weight into kernel feed order and served by the scalar packed kernels; dense `[out, in]` weights are multiplied in place without a transpose copy (SKEEP-007 Tier 0). On wasmJs, `WasmSimd.install()` adds the SIMD128 sidecar pack — the engine's C kernels compiled by Emscripten, with pthreads on cross-origin-isolated pages (SKEEP-007 Tier 1) — and binds `HOST_OFFHEAP` to the sidecar's linear memory, so matmul outputs, elementwise arithmetic, activations, reductions, softmax, the normalisations and attention stay resident there between kernels and cross back to the Kotlin heap only when read. |=== Packed-quant matmul works on every target: those formats have a `commonMain` scalar kernel and @@ -101,11 +120,9 @@ Packed-quant matmul works on every target: those formats have a `commonMain` sca == Gaps -* *Wasm ternary / BitNet* — the ternary gemv and planes lm-head kernels are not in the sidecar -build yet, so BitNet models in a browser run the decoding reference kernel. -* *Wasm beyond matmul* — elementwise, activations, reductions, normalisation and attention are the -common scalar Kotlin paths on wasmJs; Kotlin/Wasm has no SIMD types and no shared-memory threads, so -these follow the matmuls into the sidecar (SKEEP-007 rollout step 3). +* *Wasm beyond the fused set* — convolutions (`conv1d/2d/3d`) are still the common scalar Kotlin +path on wasmJs (im2col + the Vector API on the JVM); everything else a transformer forward pass +runs between its matmuls is in the sidecar with the activations resident in its linear memory. * *Native JNI dense FP32/BF16* — the Android JNI provider has no GEMM shim, so dense ops fall through to scalar there whichever `.so` tier loaded (SKaiNET#920). * *Dense FP32/BF16 SIMD on Kotlin/Native linux* — still the scalar floor; Apple has Accelerate diff --git a/docs/modules/ROOT/pages/explanation/kernel-selection.adoc b/docs/modules/ROOT/pages/explanation/kernel-selection.adoc index b522b0177..a7a582539 100644 --- a/docs/modules/ROOT/pages/explanation/kernel-selection.adoc +++ b/docs/modules/ROOT/pages/explanation/kernel-selection.adoc @@ -91,7 +91,7 @@ platform support is absent (a missing native library, no vector unit). | Kotlin/Native, wasm, JS | manual — e.g. `installNativeKernels()`; on wasmJs `WasmSimd.install()` (SKEEP-007) -| manual — call the pack's `install()` yourself; `WasmSimd.install()` registers the browser pack and its provider +| manual — call the pack's `install()` yourself; `WasmSimd.install()` registers the browser pack, its ternary pack and its provider |=== Without a row-major pack, a canonical packed weight is relaid **once per weight** into kernel feed diff --git a/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc b/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc index a3317ed37..f586a3483 100644 --- a/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc +++ b/docs/modules/ROOT/pages/reference/kernel-support-matrix.adoc @@ -58,4 +58,21 @@ The browser has no files to map, so the wasm tier keeps weights *resident* in th | `Q5_0` | — | — | — | — | wasm-resident |=== +== Kernel packs (view dispatch) + +Kernels that are not reached through the `matmul` accessors above: the ternary formats are served by `ViewKernelPack`s keyed in `KernelDispatch` (`BitNet-b1.58` with exact FP32 activations, the fused multi-plane `BitNet-planes` lm_head, `TQ2_0` with int8 activations), and the dense `x · Wᵀ` of a `Linear` layer by the `matmulFp32Wt` accessor, which multiplies a row-major `[out, in]` weight in place instead of transposing and caching a copy. The fused row kernels — softmax / log-softmax, layer norm / RMS norm and scaled dot-product attention with its structural masks and grouped-query heads (`softmaxFp32`, `normFp32`, `attentionFp32`) — run those ops in one pass where the ops layer otherwise runs its scalar loops or the normalisation's decomposition. The scalar reference kernels serve every cell that is empty here (`—`), at reference speed; the common row-dot serves `x · Wᵀ` there. + +[cols="1,1,1,1,1,1", options="header"] +|=== +| Kernel | JVM | Android | Native·linux | Native·apple | JS/WASM + +| `BitNet-b1.58 (FP32 activations)` | ffm-ternary | native-jni | native-cinterop | native-cinterop | wasm-simd128 +| `BitNet-planes` | ffm-ternary | native-jni | native-cinterop | native-cinterop | wasm-simd128 +| `TQ2_0 (int8 activations)` | — | native-jni | — | — | wasm-simd128 +| `Float32 x·Wᵀ direct` | native-ffm | — | — | — | wasm-simd128 +| `Float32 softmax (fused rows)` | native-ffm | — | — | — | wasm-simd128 +| `Float32 layer_norm / rms_norm (fused)` | native-ffm | — | — | — | wasm-simd128 +| `Float32 SDPA (fused, banded, GQA)` | native-ffm | — | — | — | wasm-simd128 +|=== + See also the eager backends & kernels mindmap (xref:explanation/eager-execution.adoc[]) for the narrative overview and gaps. diff --git a/docs/modules/skeep/pages/007-browser-lane.adoc b/docs/modules/skeep/pages/007-browser-lane.adoc index 087cc1de5..c5ccf4614 100644 --- a/docs/modules/skeep/pages/007-browser-lane.adoc +++ b/docs/modules/skeep/pages/007-browser-lane.adoc @@ -1,7 +1,7 @@ = SKEEP-007: The browser lane — packed SIMD kernels and quantize-at-load :description: SKaiNET proposal for making inference in a browser usable and quick: stop the scalar wasm lane from falling to the decoding reference kernel, serve dense weights without transpose copies, keep token tables narrow, add a SIMD128 kernel pack with pthreads as a sidecar wasm module, and let the SafeTensors loader quantize at load so Hugging Face checkpoints are served packed on every target. -Status: Implemented (Tiers 0, 1 and 2 in engine 0.61.0); the resident-activation step, the SKaiNET-transformers consumer and the WebGPU proposal remain open + +Status: Implemented (Tiers 0, 1 and 2 in engine 0.61.0; resident activations with sidecar elementwise, activation and reduction kernels, then fused softmax / normalisation / attention kernels on the wasm and JVM tiers, in the parity series that followed); the SKaiNET-transformers consumer and the WebGPU proposal remain open + Implementation: Tier 0 https://github.com/SKaiNET-developers/SKaiNET/pull/1352[#1352], Tier 2 quantize-at-load https://github.com/SKaiNET-developers/SKaiNET/pull/1353[#1353], Tier 1 `skainet-backend-wasm-simd` https://github.com/SKaiNET-developers/SKaiNET/pull/1354[#1354] — engine 0.61.0; the consumer work in SKaiNET-transformers follows, see <<_rollout_plan>>. + Audience: SKaiNET maintainers and contributors; SKaiNET-transformers maintainers (Laya and BERT in the browser); SKaiNET-examples maintainers (every web sample runs through this lane) + Created: 2026-10-16 + @@ -270,8 +270,10 @@ copies and the thread hand-off, which is what resident activations address next. * Whether the dense FP32 fast path in `matmul` (the `[k, n]` loop) should also route through `KernelDispatch` first, so a registered dense kernel wins without an ops override. Deferred to PR 2, where the first such kernel appears. -* Whether `TensorOps` should grow a fused `layerNorm`; the cost on the encoder is allocation rather - than arithmetic, and `ForwardScope` may already answer it. +* Whether `TensorOps` should grow a fused `layerNorm` — answered without touching `TensorOps`: the + optional `FusedNormOps` interface beside it, which `LayerNormalization` consults for FP32 input + and only the sidecar and the JVM FFM op sets implement, so recording and differentiating op sets + keep the decomposition. == References diff --git a/skainet-backends/benchmarks/jvm-cpu-publish/src/main/kotlin/sk/ainet/bench/publish/scenarios/Fp32LinearScenario.kt b/skainet-backends/benchmarks/jvm-cpu-publish/src/main/kotlin/sk/ainet/bench/publish/scenarios/Fp32LinearScenario.kt new file mode 100644 index 000000000..2600d365e --- /dev/null +++ b/skainet-backends/benchmarks/jvm-cpu-publish/src/main/kotlin/sk/ainet/bench/publish/scenarios/Fp32LinearScenario.kt @@ -0,0 +1,79 @@ +package sk.ainet.bench.publish.scenarios + +import sk.ainet.backend.api.kernel.KernelRegistry +import sk.ainet.bench.publish.runner.Scenario +import sk.ainet.context.DirectCpuExecutionContext +import sk.ainet.exec.kernel.NativeKernelProvider +import sk.ainet.exec.kernel.PanamaVectorKernelProvider +import sk.ainet.exec.kernel.ScalarKernelProvider +import sk.ainet.lang.tensor.Shape +import sk.ainet.lang.tensor.Tensor +import sk.ainet.lang.types.FP32 + +/** + * A `Linear` layer's product, `x · Wᵀ` with the weight stored `[out, in]`, through the public + * `ctx.ops.matmulWeightTransposed` path — what every projection of a transformer pays, as opposed to + * the `[k, n]` GEMM of `engine-fp32-gemm`. Shape: a 16-token prefill against a 768 → 3072 FFN + * projection. Primary metric: GFLOP/s over `2·m·n·k`. + * + * The provider selects the path: `panama` (the default) transposes the weight once into `[k, n]` + * and runs the Vector API GEMM; `ffm` runs the native direct kernel (`skainet_fp32_matmul_wt`) over a + * weight made resident off-heap, no transposed copy; `scalar` is the common row-dot. Only the chosen + * provider is registered, so the ops layer cannot fall through to another. + */ +internal class Fp32LinearScenario( + smoke: Boolean, + private val providerName: String, +) : Scenario { + override val id: String = "engine-fp32-linear" + override val suite: String = "skainet-engine" + override val primaryMetric: String = "gflops" + override val unit: String = "gflops" + override val higherIsBetter: Boolean = true + override val kernelProvider: String = providerName + + private val m: Int = 16 + private val k: Int = 768 + private val n: Int = if (smoke) 768 else 3072 + override val parameters: Map = mapOf( + "m" to m.toString(), + "k" to k.toString(), + "n" to n.toString(), + "kernel" to providerName, + ) + + private lateinit var ctx: DirectCpuExecutionContext + private lateinit var x: Tensor + private lateinit var w: Tensor + + override fun setup() { + KernelRegistry.clearForTesting() + when (providerName.lowercase()) { + "panama", "panama-vector" -> KernelRegistry.register(PanamaVectorKernelProvider) + "ffm", "native", "native-ffm" -> { + check(NativeKernelProvider.isAvailable()) { "bundled libskainet_kernels missing — the ffm provider cannot run" } + KernelRegistry.register(NativeKernelProvider) + } + "scalar" -> KernelRegistry.register(ScalarKernelProvider) + else -> error("unknown kernel provider: $providerName (use 'panama', 'ffm' or 'scalar')") + } + ctx = DirectCpuExecutionContext() + x = ctx.fromFloatArray(Shape(m, k), FP32::class, FloatArray(m * k) { ((it % 251) - 125).toFloat() / 127f }) + w = ctx.fromFloatArray(Shape(n, k), FP32::class, FloatArray(n * k) { ((it * 13 % 257) - 128).toFloat() / 127f }) + // First call: the transpose cache or the resident copy is built here, not in a measured run. + ctx.ops.matmulWeightTransposed(x, w) + } + + override fun runOnce(): Double { + val start = System.nanoTime() + val result = ctx.ops.matmulWeightTransposed(x, w) + val elapsedNs = System.nanoTime() - start + @Suppress("UNUSED_VARIABLE") val sink = result.hashCode() + val flops = 2.0 * m.toDouble() * n.toDouble() * k.toDouble() + return (flops / (elapsedNs / 1_000_000_000.0)) / 1e9 + } + + override fun teardown() { + KernelRegistry.clearForTesting() + } +} diff --git a/skainet-backends/benchmarks/jvm-cpu-publish/src/main/kotlin/sk/ainet/bench/publish/scenarios/FusedOpsScenarios.kt b/skainet-backends/benchmarks/jvm-cpu-publish/src/main/kotlin/sk/ainet/bench/publish/scenarios/FusedOpsScenarios.kt new file mode 100644 index 000000000..290456698 --- /dev/null +++ b/skainet-backends/benchmarks/jvm-cpu-publish/src/main/kotlin/sk/ainet/bench/publish/scenarios/FusedOpsScenarios.kt @@ -0,0 +1,183 @@ +package sk.ainet.bench.publish.scenarios + +import sk.ainet.backend.api.kernel.KernelRegistry +import sk.ainet.bench.publish.runner.Scenario +import sk.ainet.context.DirectCpuExecutionContext +import sk.ainet.exec.kernel.NativeKernelProvider +import sk.ainet.exec.kernel.PanamaVectorKernelProvider +import sk.ainet.exec.kernel.ScalarKernelProvider +import sk.ainet.lang.nn.normalization.LayerNormalization +import sk.ainet.lang.tensor.Shape +import sk.ainet.lang.tensor.Tensor +import sk.ainet.lang.types.FP32 + +/* + * The fused row ops through the public ops layer (SKEEP-007 fused ops): softmax, a `LayerNormalization` + * layer and scaled dot-product attention. The provider selects the path, as in [Fp32LinearScenario]: + * `ffm` runs the native fused kernels (`f32_fused.c` through FFM), `panama` and `scalar` the common + * Kotlin implementation (attention parallel over heads through the context's schedule, the + * normalisation as its decomposed elementwise chain). Only the chosen provider is registered. + */ + +private fun registerOnly(providerName: String) { + KernelRegistry.clearForTesting() + when (providerName.lowercase()) { + "panama", "panama-vector" -> KernelRegistry.register(PanamaVectorKernelProvider) + "ffm", "native", "native-ffm" -> { + check(NativeKernelProvider.isAvailable()) { "bundled libskainet_kernels missing — the ffm provider cannot run" } + KernelRegistry.register(NativeKernelProvider) + } + "scalar" -> KernelRegistry.register(ScalarKernelProvider) + else -> error("unknown kernel provider: $providerName (use 'panama', 'ffm' or 'scalar')") + } +} + +private fun pseudo(n: Int, seed: Int, scale: Float = 1f): FloatArray { + var s = seed + return FloatArray(n) { + s = s * 1103515245 + 12345 + ((s ushr 16) % 2001 - 1000) / 1000f * scale + } +} + +/** `ctx.ops.softmax` over the last dimension of `[rows, 1024]` FP32. Primary metric: million elements per second. */ +internal class SoftmaxScenario( + smoke: Boolean, + private val providerName: String, +) : Scenario { + override val id: String = "engine-softmax" + override val suite: String = "skainet-engine" + override val primaryMetric: String = "melems_per_sec" + override val unit: String = "melems_per_sec" + override val higherIsBetter: Boolean = true + override val kernelProvider: String = providerName + + private val rows: Int = if (smoke) 64 else 1024 + private val cols: Int = 1024 + override val parameters: Map = mapOf( + "rows" to rows.toString(), + "cols" to cols.toString(), + "kernel" to providerName, + ) + + private lateinit var ctx: DirectCpuExecutionContext + private lateinit var x: Tensor + + override fun setup() { + registerOnly(providerName) + ctx = DirectCpuExecutionContext() + x = ctx.fromFloatArray(Shape(rows, cols), FP32::class, pseudo(rows * cols, 3, 4f)) + ctx.ops.softmax(x, -1) + } + + override fun runOnce(): Double { + val start = System.nanoTime() + val result = ctx.ops.softmax(x, -1) + val elapsedNs = System.nanoTime() - start + @Suppress("UNUSED_VARIABLE") val sink = result.hashCode() + return (rows.toDouble() * cols / (elapsedNs / 1_000_000_000.0)) / 1e6 + } + + override fun teardown() = KernelRegistry.clearForTesting() +} + +/** A `LayerNormalization` layer with affine over `[rows, 768]` FP32. Primary metric: million elements per second. */ +internal class LayerNormScenario( + smoke: Boolean, + private val providerName: String, +) : Scenario { + override val id: String = "engine-layer-norm" + override val suite: String = "skainet-engine" + override val primaryMetric: String = "melems_per_sec" + override val unit: String = "melems_per_sec" + override val higherIsBetter: Boolean = true + override val kernelProvider: String = providerName + + private val rows: Int = if (smoke) 64 else 1024 + private val cols: Int = 768 + override val parameters: Map = mapOf( + "rows" to rows.toString(), + "cols" to cols.toString(), + "affine" to "true", + "kernel" to providerName, + ) + + private lateinit var ctx: DirectCpuExecutionContext + private lateinit var x: Tensor + private lateinit var layer: LayerNormalization + + override fun setup() { + registerOnly(providerName) + ctx = DirectCpuExecutionContext() + x = ctx.fromFloatArray(Shape(rows, cols), FP32::class, pseudo(rows * cols, 5, 2f)) + val gamma = ctx.fromFloatArray(Shape(cols), FP32::class, FloatArray(cols) { 0.5f + (it % 7) * 0.1f }) + val beta = ctx.fromFloatArray(Shape(cols), FP32::class, FloatArray(cols) { ((it % 5) - 2) * 0.05f }) + layer = LayerNormalization(intArrayOf(cols), eps = 1e-5, elementwiseAffine = true, name = "ln", initGamma = gamma, initBeta = beta) + layer.forward(x, ctx) + } + + override fun runOnce(): Double { + val start = System.nanoTime() + val result = layer.forward(x, ctx) + val elapsedNs = System.nanoTime() - start + @Suppress("UNUSED_VARIABLE") val sink = result.hashCode() + return (rows.toDouble() * cols / (elapsedNs / 1_000_000_000.0)) / 1e6 + } + + override fun teardown() = KernelRegistry.clearForTesting() +} + +/** + * `ctx.ops.scaledDotProductAttention`, causal, at an encoder's shape: 12 heads, 150 queries over 150 + * keys, head dim 64. Primary metric: GFLOP/s over `4 · heads · seqQ · seqKV · headDim` (the two + * products; the causal half that is never computed is counted, as the dense reference counts it). + */ +internal class SdpaScenario( + smoke: Boolean, + private val providerName: String, +) : Scenario { + override val id: String = "engine-sdpa" + override val suite: String = "skainet-engine" + override val primaryMetric: String = "gflops" + override val unit: String = "gflops" + override val higherIsBetter: Boolean = true + override val kernelProvider: String = providerName + + private val heads: Int = 12 + private val seq: Int = if (smoke) 32 else 150 + private val headDim: Int = 64 + override val parameters: Map = mapOf( + "batch" to "1", + "heads" to heads.toString(), + "seq" to seq.toString(), + "head_dim" to headDim.toString(), + "causal" to "true", + "kernel" to providerName, + ) + + private lateinit var ctx: DirectCpuExecutionContext + private lateinit var q: Tensor + private lateinit var k: Tensor + private lateinit var v: Tensor + + override fun setup() { + registerOnly(providerName) + ctx = DirectCpuExecutionContext() + val n = heads * seq * headDim + q = ctx.fromFloatArray(Shape(1, heads, seq, headDim), FP32::class, pseudo(n, 7)) + k = ctx.fromFloatArray(Shape(1, heads, seq, headDim), FP32::class, pseudo(n, 11)) + v = ctx.fromFloatArray(Shape(1, heads, seq, headDim), FP32::class, pseudo(n, 13)) + ctx.ops.scaledDotProductAttention(q, k, v, causal = true) + } + + override fun runOnce(): Double { + val start = System.nanoTime() + val result = ctx.ops.scaledDotProductAttention(q, k, v, causal = true) + val elapsedNs = System.nanoTime() - start + @Suppress("UNUSED_VARIABLE") val sink = result.hashCode() + val flops = 4.0 * heads * seq * seq * headDim + return (flops / (elapsedNs / 1_000_000_000.0)) / 1e9 + } + + override fun teardown() = KernelRegistry.clearForTesting() +} diff --git a/skainet-backends/benchmarks/jvm-cpu-publish/src/main/kotlin/sk/ainet/bench/publish/scenarios/ScenarioRegistry.kt b/skainet-backends/benchmarks/jvm-cpu-publish/src/main/kotlin/sk/ainet/bench/publish/scenarios/ScenarioRegistry.kt index 74a595fea..3f05d0479 100644 --- a/skainet-backends/benchmarks/jvm-cpu-publish/src/main/kotlin/sk/ainet/bench/publish/scenarios/ScenarioRegistry.kt +++ b/skainet-backends/benchmarks/jvm-cpu-publish/src/main/kotlin/sk/ainet/bench/publish/scenarios/ScenarioRegistry.kt @@ -6,6 +6,7 @@ public object ScenarioRegistry { public fun ids(): List = listOf( "engine-fp32-gemm", + "engine-fp32-linear", "engine-q4-gemm", "engine-kernel-matmul", "engine-bf16-matmul", @@ -14,10 +15,14 @@ public object ScenarioRegistry { "engine-elementwise-add", "engine-reductions-sum", "engine-reductions-mean", + "engine-softmax", + "engine-layer-norm", + "engine-sdpa", ) public fun byId(id: String, provider: String, smoke: Boolean): Scenario? = when (id) { "engine-fp32-gemm" -> Fp32GemmScenario(smoke = smoke, providerName = provider) + "engine-fp32-linear" -> Fp32LinearScenario(smoke = smoke, providerName = provider) "engine-q4-gemm" -> Q4GemmScenario(smoke = smoke, providerName = provider) "engine-kernel-matmul" -> KernelMatmulScenario(smoke = smoke, providerName = provider) "engine-bf16-matmul" -> Bf16MatmulScenario(smoke = smoke, providerName = provider) @@ -26,6 +31,9 @@ public object ScenarioRegistry { "engine-elementwise-add" -> ElementwiseAddScenario(smoke = smoke, providerName = provider) "engine-reductions-sum" -> ReductionsScenario(op = ReductionOp.SUM, smoke = smoke, providerName = provider) "engine-reductions-mean" -> ReductionsScenario(op = ReductionOp.MEAN, smoke = smoke, providerName = provider) + "engine-softmax" -> SoftmaxScenario(smoke = smoke, providerName = provider) + "engine-layer-norm" -> LayerNormScenario(smoke = smoke, providerName = provider) + "engine-sdpa" -> SdpaScenario(smoke = smoke, providerName = provider) else -> null } } diff --git a/skainet-backends/benchmarks/wasm-bench/src/wasmJsMain/kotlin/sk/ainet/bench/wasm/Main.kt b/skainet-backends/benchmarks/wasm-bench/src/wasmJsMain/kotlin/sk/ainet/bench/wasm/Main.kt index 72c130075..b1108a97b 100644 --- a/skainet-backends/benchmarks/wasm-bench/src/wasmJsMain/kotlin/sk/ainet/bench/wasm/Main.kt +++ b/skainet-backends/benchmarks/wasm-bench/src/wasmJsMain/kotlin/sk/ainet/bench/wasm/Main.kt @@ -1,6 +1,7 @@ package sk.ainet.bench.wasm import sk.ainet.context.DirectCpuExecutionContext +import sk.ainet.exec.wasm.WasmCpuOps import sk.ainet.exec.wasm.WasmKernels import sk.ainet.exec.wasm.WasmSimd import sk.ainet.lang.tensor.Shape @@ -90,8 +91,15 @@ private fun report(tier: String, case: Case, form: String, ms: Double) { /** * Three passes: the scalar lane, then the SIMD128 sidecar single-threaded, then with pthreads when * the runtime allows them (Node, or a cross-origin-isolated page). Each line carries `tier=`. + * + * `SKAINET_WASM_RESIDENT=0` in the environment (Node) runs the sidecar passes with + * `WasmSimd.residentBudgetBytes = 0`, i.e. every op output read back to the heap — the A/B for the + * resident-activation step. */ suspend fun main() { + val resident = envVar("SKAINET_WASM_RESIDENT") != "0" + if (!resident) WasmSimd.residentBudgetBytes = 0L + println("[SKAINET_PERF_WASM] residentActivations=$resident") runPass("scalar") WasmSimd.install(threads = false) runPass(WasmKernels.mode) @@ -122,4 +130,39 @@ private fun runPass(tier: String) { report(tier, case, "dense", timeMedianMs(reps) { ctx.ops.matmulWeightTransposed(x, dense) }) report(tier, case, "q8_0", timeMedianMs(reps) { ctx.ops.matmulWeightTransposed(x, packed) }) } + runChain(tier, ctx) +} + +/** + * The resident-activation case: one Laya MLP block's shape of ops between two matmuls — + * `x · Wᵢᵀ`, GELU, a gated multiply, a bias add, then a reduction that reads the result back — + * timed end to end. On the scalar lane every op allocates and walks a heap array; on the sidecar + * the intermediates stay in linear memory and only the final scalar crosses the boundary. + * + * `[SKAINET_PERF_WASM] tier= laya.chain m=150 n=2304 k=768 ops=matmulWt+gelu+mul+add+sum residentBudget= ms=` + */ +private fun runChain(tier: String, ctx: DirectCpuExecutionContext) { + val m = 150; val n = 2304; val k = 768 + val x = ctx.fromFloatArray(Shape(m, k), FP32::class, pseudo(m * k, 3)) + val w = ctx.fromFloatArray(Shape(n, k), FP32::class, pseudo(n * k, 7)) + val bias = ctx.fromFloatArray(Shape(n), FP32::class, pseudo(n, 11)) + fun chain(): Float { + val h = ctx.ops.matmulWeightTransposed(x, w) + val g = ctx.ops.gelu(h) + val s = ctx.ops.multiply(g, h) + val o = ctx.ops.add(s, bias) + return ctx.ops.sum(o).data.copyToFloatArray()[0] + } + chain() + val ms = timeMedianMs(5) { chain() } + val msText = ((ms * 100).roundToInt() / 100.0).toString() + val budget = if (ctx.ops is WasmCpuOps) WasmSimd.residentBudgetBytes else 0L + println("[SKAINET_PERF_WASM] tier=$tier laya.chain m=$m n=$n k=$k ops=matmulWt+gelu+mul+add+sum residentBudget=$budget ms=$msText") } + +@OptIn(kotlin.js.ExperimentalWasmJsInterop::class) +private fun envVar(name: String): String? = jsEnvVar(name)?.toString() + +@OptIn(kotlin.js.ExperimentalWasmJsInterop::class) +private fun jsEnvVar(name: String): JsString? = + js("(typeof process !== 'undefined' && process.env && process.env[name] != null) ? process.env[name] : null") diff --git a/skainet-backends/benchmarks/wasm-bench/src/wasmJsMain/kotlin/sk/ainet/bench/wasm/Scenarios.kt b/skainet-backends/benchmarks/wasm-bench/src/wasmJsMain/kotlin/sk/ainet/bench/wasm/Scenarios.kt index 271fc27be..d8b9aa1fe 100644 --- a/skainet-backends/benchmarks/wasm-bench/src/wasmJsMain/kotlin/sk/ainet/bench/wasm/Scenarios.kt +++ b/skainet-backends/benchmarks/wasm-bench/src/wasmJsMain/kotlin/sk/ainet/bench/wasm/Scenarios.kt @@ -4,9 +4,11 @@ import sk.ainet.backend.api.kernel.KernelDispatch import sk.ainet.backend.api.kernel.KernelKey import sk.ainet.backend.api.kernel.KernelRegistry import sk.ainet.context.DirectCpuExecutionContext +import sk.ainet.exec.wasm.WasmCpuOps import sk.ainet.lang.memory.Scope import sk.ainet.lang.memory.Storage import sk.ainet.lang.memory.TensorView +import sk.ainet.lang.nn.normalization.LayerNormalization import sk.ainet.lang.tensor.Shape import sk.ainet.lang.tensor.data.BitNetB158TensorData import sk.ainet.lang.types.FP32 @@ -48,6 +50,15 @@ internal fun engineScenarios(): List = listOf( val flops = 2.0 * n * n * n; { flops / seconds { ctx.ops.matmul(a, b) } / 1e9 } }, + WasmScenario("engine-fp32-linear", "gflops", mapOf("m" to "16", "k" to "768", "n" to "3072"), reps = 10) { + val m = 16; val k = 768; val n = 3072 + val ctx = DirectCpuExecutionContext() + val x = ctx.fromFloatArray(Shape(m, k), FP32::class, FloatArray(m * k) { ((it % 251) - 125).toFloat() / 127f }) + val w = ctx.fromFloatArray(Shape(n, k), FP32::class, FloatArray(n * k) { ((it * 13 % 257) - 128).toFloat() / 127f }) + ctx.ops.matmulWeightTransposed(x, w) // the resident upload happens here, not in a measured run + val flops = 2.0 * m * n * k; + { flops / seconds { ctx.ops.matmulWeightTransposed(x, w) } / 1e9 } + }, WasmScenario("engine-kernel-matmul", "gflops", mapOf("size" to "1024"), reps = 3) { val n = 1024 DirectCpuExecutionContext() @@ -123,6 +134,23 @@ internal fun engineScenarios(): List = listOf( val b = ctx.fromFloatArray(Shape(n), FP32::class, FloatArray(n) { 1f }); { n / seconds { ctx.ops.add(a, b) } / 1e6 } }, + // The same three ops on operands already resident in the sidecar (the shape a chain between two + // matmuls has); heap operands above stay on the common lane by design. + WasmScenario("engine-elementwise-add", "melems_per_sec", mapOf("elements" to "1000000", "operands" to "resident"), reps = 10) { + val n = 1_000_000 + val ctx = DirectCpuExecutionContext() + val ops = ctx.ops as? WasmCpuOps ?: error("resident operands need the sidecar") + val a = ops.toResident(ctx.fromFloatArray(Shape(n), FP32::class, FloatArray(n) { it.toFloat() * 0.5f })) + val b = ops.toResident(ctx.fromFloatArray(Shape(n), FP32::class, FloatArray(n) { 1f })); + { n / seconds { ctx.ops.add(a, b) } / 1e6 } + }, + WasmScenario("engine-reductions-sum", "melems_per_sec", mapOf("elements" to "1000000", "op" to "sum", "operands" to "resident"), reps = 10) { + val n = 1_000_000 + val ctx = DirectCpuExecutionContext() + val ops = ctx.ops as? WasmCpuOps ?: error("resident operands need the sidecar") + val a = ops.toResident(ctx.fromFloatArray(Shape(n), FP32::class, FloatArray(n) { (it % 1024).toFloat() * 0.25f })); + { n / seconds { ctx.ops.sum(a).data.copyToFloatArray() } / 1e6 } + }, WasmScenario("engine-reductions-sum", "melems_per_sec", mapOf("elements" to "1000000", "op" to "sum"), reps = 10) { val n = 1_000_000 val ctx = DirectCpuExecutionContext() @@ -135,6 +163,38 @@ internal fun engineScenarios(): List = listOf( val a = ctx.fromFloatArray(Shape(n), FP32::class, FloatArray(n) { (it % 1024).toFloat() * 0.25f }); { n / seconds { ctx.ops.mean(a) } / 1e6 } }, + // The fused row ops (softmax, a LayerNormalization layer, causal attention), the shapes of the JVM harness. + WasmScenario("engine-softmax", "melems_per_sec", mapOf("rows" to "1024", "cols" to "1024"), reps = 10) { + val rows = 1024; val cols = 1024 + val ctx = DirectCpuExecutionContext() + val rnd = lcg(3) + val x = ctx.fromFloatArray(Shape(rows, cols), FP32::class, FloatArray(rows * cols) { (rnd() - 0.5f) * 8f }) + ctx.ops.softmax(x, -1); + { rows.toDouble() * cols / seconds { ctx.ops.softmax(x, -1) } / 1e6 } + }, + WasmScenario("engine-layer-norm", "melems_per_sec", mapOf("rows" to "1024", "cols" to "768", "affine" to "true"), reps = 10) { + val rows = 1024; val cols = 768 + val ctx = DirectCpuExecutionContext() + val rnd = lcg(5) + val x = ctx.fromFloatArray(Shape(rows, cols), FP32::class, FloatArray(rows * cols) { (rnd() - 0.5f) * 4f }) + val gamma = ctx.fromFloatArray(Shape(cols), FP32::class, FloatArray(cols) { 0.5f + (it % 7) * 0.1f }) + val beta = ctx.fromFloatArray(Shape(cols), FP32::class, FloatArray(cols) { ((it % 5) - 2) * 0.05f }) + val layer = LayerNormalization(intArrayOf(cols), eps = 1e-5, elementwiseAffine = true, name = "ln", initGamma = gamma, initBeta = beta) + layer.forward(x, ctx); + { rows.toDouble() * cols / seconds { layer.forward(x, ctx) } / 1e6 } + }, + WasmScenario("engine-sdpa", "gflops", mapOf("batch" to "1", "heads" to "12", "seq" to "150", "head_dim" to "64", "causal" to "true"), reps = 5) { + val heads = 12; val seq = 150; val headDim = 64 + val ctx = DirectCpuExecutionContext() + val rnd = lcg(7) + val n = heads * seq * headDim + val q = ctx.fromFloatArray(Shape(1, heads, seq, headDim), FP32::class, FloatArray(n) { rnd() - 0.5f }) + val k = ctx.fromFloatArray(Shape(1, heads, seq, headDim), FP32::class, FloatArray(n) { rnd() - 0.5f }) + val v = ctx.fromFloatArray(Shape(1, heads, seq, headDim), FP32::class, FloatArray(n) { rnd() - 0.5f }) + ctx.ops.scaledDotProductAttention(q, k, v, causal = true) + val flops = 4.0 * heads * seq * seq * headDim; + { flops / seconds { ctx.ops.scaledDotProductAttention(q, k, v, causal = true) } / 1e9 } + }, ) private inline fun seconds(block: () -> Unit): Double { diff --git a/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/Fp32FusedKernels.kt b/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/Fp32FusedKernels.kt new file mode 100644 index 000000000..7081399a8 --- /dev/null +++ b/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/Fp32FusedKernels.kt @@ -0,0 +1,84 @@ +package sk.ainet.backend.api.kernel + +/** + * Row-wise softmax over dense FP32: `out[r, :] = softmax(input[r, :])` for `rows` contiguous rows of + * `cols`, or the log-softmax (`input - (log Σ exp(input - max) + max)`) when [log] is `true`. The + * SPI face of `skainet_f32_softmax_rows` (SKEEP-007 fused ops), which the wasm sidecar and the JVM + * FFM provider both serve; a provider without it returns `null` from [KernelProvider.softmaxFp32] + * and the ops layer keeps its common loop. + * + * Operands are contiguous from their offsets: `rows × cols` floats each. Implementations must not + * mutate [input] and must fully overwrite the output block; `rows == 0` or `cols == 0` is a no-op. + * `exp` and `log` are the platform's libm, so results agree with the common Kotlin loop to the + * last-ulp differences between the two. + */ +public interface Fp32SoftmaxKernel { + public fun softmaxRows( + input: FloatArray, inputOffset: Int, + out: FloatArray, outOffset: Int, + rows: Int, cols: Int, + log: Boolean, + ) +} + +/** + * Fused normalisations over the last dimension of dense FP32, one pass per row — the SPI face of + * `skainet_f32_layer_norm` and `skainet_f32_rms_norm` (SKEEP-007 fused ops): + * + * - [layerNorm]: `out[r, c] = (x[r, c] − mean_r) / sqrt(var_r + eps) · gamma[c] + beta[c]` with the + * mean and the biased variance over the row; + * - [rmsNorm]: `out[r, c] = x[r, c] / sqrt(mean(x[r, :]²) + eps) · gamma[c]`. + * + * [gamma] and [beta] are `cols` floats or `null` (the affine step is skipped). Operands are + * contiguous from their offsets; implementations must not mutate the inputs and must fully overwrite + * the `rows × cols` output block. The row sums accumulate in lanes where the kernel is vectorised, + * so results differ from the decomposed `mean`/`subtract`/`sqrt`/`divide` chain in the last bits. + */ +public interface Fp32NormKernel { + public fun layerNorm( + input: FloatArray, inputOffset: Int, + gamma: FloatArray?, beta: FloatArray?, + out: FloatArray, outOffset: Int, + rows: Int, cols: Int, + eps: Float, + ) + + public fun rmsNorm( + input: FloatArray, inputOffset: Int, + gamma: FloatArray?, + out: FloatArray, outOffset: Int, + rows: Int, cols: Int, + eps: Float, + ) +} + +/** + * Fused scaled dot-product attention over dense FP32 — the SPI face of `skainet_f32_sdpa` (SKEEP-007 + * fused ops), with the full contract of `TensorOps.scaledDotProductAttention`: + * + * `out = softmax(query · keyᵀ · scale + rules + mask) · value` + * + * [query] is `[batch, heads, seqQ, headDim]`, [key] and [value] are `[batch, kvHeads, seqKV, headDim]` + * with query head `h` reading K/V head `h / (heads / kvHeads)` (grouped-query attention), [out] is + * `[batch, heads, seqQ, headDim]`, all contiguous whole arrays. [scale] is applied as given — the + * caller resolves the `0 → 1/sqrt(headDim)` default. The structural rules are those of + * `AttentionMask`: [causal], a sliding [window] with [rightContext], and per-batch [keyLengths] + * (`[batch]` or `null`); a key outside a row's attended set scores `-1e30` and its product is + * never computed, so a fully masked row is uniform and finite. [mask] is a dense additive mask + * (or `null`) broadcast trailing-aligned over [maskDims] `= [batch | 1, heads | kvHeads | 1, + * seqQ | 1, seqKV | 1]`, added to every score. + * + * The `q · k` dot products accumulate in lanes where the kernel is vectorised; the + * probability-weighted sum of `value` accumulates per output element in ascending key order. + * Implementations must not mutate the inputs and must fully overwrite [out]. + */ +public interface Fp32AttentionKernel { + public fun sdpa( + query: FloatArray, key: FloatArray, value: FloatArray, out: FloatArray, + batch: Int, heads: Int, kvHeads: Int, + seqQ: Int, seqKV: Int, headDim: Int, + scale: Float, causal: Boolean, window: Int, rightContext: Int, + keyLengths: IntArray?, + mask: FloatArray?, maskDims: IntArray?, + ) +} diff --git a/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/Fp32MatmulWtKernel.kt b/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/Fp32MatmulWtKernel.kt new file mode 100644 index 000000000..d85d77a18 --- /dev/null +++ b/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/Fp32MatmulWtKernel.kt @@ -0,0 +1,28 @@ +package sk.ainet.backend.api.kernel + +/** + * Dense `x · Wᵀ` for a row-major `[out, in]` FP32 weight — the shape a `Linear` layer stores its + * weight in, multiplied in place: + * + * `out[i, o] = Σ_p x[i, p] · w[o, p]` + * + * Every output is one contiguous dot product over the weight's own row, so no transposed copy of + * the weight is ever made. This is the SPI face of `skainet_fp32_matmul_wt` (SKEEP-007), which the + * wasm sidecar has served since 0.61.0 and the JVM FFM provider binds as well: a tier whose + * [Fp32MatmulKernel] wants the weight input-major (`[k, n]`) otherwise pays a scattered transpose + * copy per weight and caches it — about 0.5 GB for a 22-layer encoder. + * + * Operands are contiguous: `x` is `m` rows of `k`, `w` is `n` rows of `k`, `out` is `m` rows of + * `n`, each from its offset. Implementations must not mutate `x` or `w`, must fully overwrite the + * `m × n` block of `out`, and may assume the arrays do not alias. `k == 0` zeroes the block; + * `m == 0` or `n == 0` is a no-op. Each output accumulates its products in ascending `p`, so a + * conforming kernel is bit-identical to transpose-then-GEMM with the same accumulation order. + */ +public interface Fp32MatmulWtKernel { + public fun matmulWt( + x: FloatArray, xOffset: Int, + w: FloatArray, wOffset: Int, + out: FloatArray, outOffset: Int, + m: Int, n: Int, k: Int, + ) +} diff --git a/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/KernelProvider.kt b/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/KernelProvider.kt index d1126a82f..2936d374d 100644 --- a/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/KernelProvider.kt +++ b/skainet-backends/skainet-backend-api/src/commonMain/kotlin/sk/ainet/backend/api/kernel/KernelProvider.kt @@ -103,6 +103,34 @@ public interface KernelProvider { */ public fun matmulQ5_0(): Q5_0MatmulKernel? = null + /** + * Dense `x · Wᵀ` over a row-major `[out, in]` FP32 weight ([Fp32MatmulWtKernel]), or `null` + * when this provider has no direct kernel for it and the caller should transpose once and use + * [matmulFp32]. Same fall-through pattern as [matmulQ4K]. + */ + public fun matmulFp32Wt(): Fp32MatmulWtKernel? = null + + /** + * Row-wise softmax / log-softmax over dense FP32 ([Fp32SoftmaxKernel]), or `null` when this + * provider has no fused kernel for it and the ops layer keeps its common loop. Same + * fall-through pattern as [matmulQ4K]; reported by [supports] as `"softmax"` with `["Float32"]`. + */ + public fun softmaxFp32(): Fp32SoftmaxKernel? = null + + /** + * Fused layer norm and RMS norm over the last dimension of dense FP32 ([Fp32NormKernel]), or + * `null`. Reported by [supports] as `"layer_norm"` / `"rms_norm"` with `["Float32"]`. + */ + public fun normFp32(): Fp32NormKernel? = null + + /** + * Fused scaled dot-product attention over dense FP32 ([Fp32AttentionKernel]) with the ops + * layer's structural masks (causal, sliding window, key lengths) and grouped-query heads, or + * `null` when the ops layer runs its common kernel. Reported by [supports] as `"sdpa"` with + * `["Float32"]`. + */ + public fun attentionFp32(): Fp32AttentionKernel? = null + /** * Capability query: does this provider carry a kernel for * [opName] with the given [dtypeKeys]? @@ -122,15 +150,29 @@ public interface KernelProvider { * - For ops that aren't matmul (future: SDPA, gather, RMSNorm…), * providers can override this method to declare those kernels. * - * The default body covers the four matmul accessors that exist - * on this interface today. Providers that ship additional - * kernels override and chain through `super.supports(...)` for - * the matmul base cases. + * The default body covers the matmul accessors on this interface, + * plus `"matmul_wt"` with `["Float32", "Float32"]` for the direct + * `x · Wᵀ` kernel ([matmulFp32Wt]) and the fused row kernels with a + * single `["Float32"]` key — `"softmax"` ([softmaxFp32]), + * `"layer_norm"` / `"rms_norm"` ([normFp32]) and `"sdpa"` + * ([attentionFp32]). Providers that ship additional kernels override + * and chain through `super.supports(...)` for the base cases. */ public fun supports(opName: String, dtypeKeys: List): Boolean { - if (opName != "matmul" || dtypeKeys.size != 2) return false + if (dtypeKeys.size == 1) { + if (dtypeKeys[0] != "Float32") return false + return when (opName) { + "softmax" -> softmaxFp32() != null + "layer_norm", "rms_norm" -> normFp32() != null + "sdpa" -> attentionFp32() != null + else -> false + } + } + if (dtypeKeys.size != 2) return false val (input, weight) = dtypeKeys if (input != "Float32") return false + if (opName == "matmul_wt") return weight == "Float32" && matmulFp32Wt() != null + if (opName != "matmul") return false return when (weight) { "Float32" -> matmulFp32() != null "BFloat16" -> matmulBf16() != null diff --git a/skainet-backends/skainet-backend-api/src/jvmMain/kotlin/sk/ainet/backend/api/kernel/Fp32MemSegMatmulWtKernel.kt b/skainet-backends/skainet-backend-api/src/jvmMain/kotlin/sk/ainet/backend/api/kernel/Fp32MemSegMatmulWtKernel.kt new file mode 100644 index 000000000..6b95f2711 --- /dev/null +++ b/skainet-backends/skainet-backend-api/src/jvmMain/kotlin/sk/ainet/backend/api/kernel/Fp32MemSegMatmulWtKernel.kt @@ -0,0 +1,24 @@ +package sk.ainet.backend.api.kernel + +import java.lang.foreign.MemorySegment + +/** + * [Fp32MatmulWtKernel] with the `[out, in]` FP32 weight supplied as an off-heap + * `java.lang.foreign.MemorySegment` instead of a heap `FloatArray`. JVM-only sibling, for the same + * reason [Q4KMemSegMatmulKernel] exists: a downcall cannot read heap memory on JDK 21, so the heap + * face copies the whole weight on every call — and the weight is the large operand. A weight that + * already lives in a segment (loaded off-heap, or made resident once by the ops layer) is passed + * through with no copy at all; only the `m × k` activation and the `m × n` output are staged. + * + * Same contract as [Fp32MatmulWtKernel]: contiguous operands, `out` fully overwritten, products + * accumulated in ascending `p`. The caller owns the segment's arena and keeps it alive for the + * duration of the call; the kernel retains nothing past return. + */ +public interface Fp32MemSegMatmulWtKernel { + public fun matmulWt( + x: FloatArray, xOffset: Int, + weight: MemorySegment, weightByteOffset: Long, + out: FloatArray, outOffset: Int, + m: Int, n: Int, k: Int, + ) +} diff --git a/skainet-backends/skainet-backend-api/src/jvmMain/kotlin/sk/ainet/backend/api/kernel/MemSegKernelProvider.kt b/skainet-backends/skainet-backend-api/src/jvmMain/kotlin/sk/ainet/backend/api/kernel/MemSegKernelProvider.kt index 527401a5b..c567aa11d 100644 --- a/skainet-backends/skainet-backend-api/src/jvmMain/kotlin/sk/ainet/backend/api/kernel/MemSegKernelProvider.kt +++ b/skainet-backends/skainet-backend-api/src/jvmMain/kotlin/sk/ainet/backend/api/kernel/MemSegKernelProvider.kt @@ -34,4 +34,10 @@ public interface MemSegKernelProvider { * keep compiling. */ public fun matmulQ4KMemSeg(): Q4KMemSegMatmulKernel? = null + + /** + * Dense `x · Wᵀ` with the `[out, in]` FP32 weight supplied as a `MemorySegment` + * ([Fp32MemSegMatmulWtKernel]), or `null`. Same `null` default as [matmulQ4KMemSeg]. + */ + public fun matmulFp32WtMemSeg(): Fp32MemSegMatmulWtKernel? = null } diff --git a/skainet-backends/skainet-backend-cpu/api/jvm/skainet-backend-cpu.api b/skainet-backends/skainet-backend-cpu/api/jvm/skainet-backend-cpu.api index e0b809808..1d671e936 100644 --- a/skainet-backends/skainet-backend-cpu/api/jvm/skainet-backend-cpu.api +++ b/skainet-backends/skainet-backend-cpu/api/jvm/skainet-backend-cpu.api @@ -61,12 +61,14 @@ public final class sk/ainet/exec/kernel/PanamaVectorFp16MatmulKernel : sk/ainet/ public final class sk/ainet/exec/kernel/PanamaVectorKernelProvider : sk/ainet/backend/api/kernel/KernelProvider { public static final field INSTANCE Lsk/ainet/exec/kernel/PanamaVectorKernelProvider; + public fun attentionFp32 ()Lsk/ainet/backend/api/kernel/Fp32AttentionKernel; public fun getName ()Ljava/lang/String; public fun getPriority ()I public fun isAvailable ()Z public fun matmulBf16 ()Lsk/ainet/backend/api/kernel/Bf16MatmulKernel; public fun matmulFp16 ()Lsk/ainet/backend/api/kernel/Fp16MatmulKernel; public fun matmulFp32 ()Lsk/ainet/backend/api/kernel/Fp32MatmulKernel; + public fun matmulFp32Wt ()Lsk/ainet/backend/api/kernel/Fp32MatmulWtKernel; public fun matmulQ4K ()Lsk/ainet/backend/api/kernel/Q4KMatmulKernel; public fun matmulQ4_0 ()Lsk/ainet/backend/api/kernel/Q4_0MatmulKernel; public fun matmulQ5K ()Lsk/ainet/backend/api/kernel/Q5KMatmulKernel; @@ -74,17 +76,21 @@ public final class sk/ainet/exec/kernel/PanamaVectorKernelProvider : sk/ainet/ba public fun matmulQ5_1 ()Lsk/ainet/backend/api/kernel/Q5_1MatmulKernel; public fun matmulQ6K ()Lsk/ainet/backend/api/kernel/Q6KMatmulKernel; public fun matmulQ8_0 ()Lsk/ainet/backend/api/kernel/Q8_0MatmulKernel; + public fun normFp32 ()Lsk/ainet/backend/api/kernel/Fp32NormKernel; + public fun softmaxFp32 ()Lsk/ainet/backend/api/kernel/Fp32SoftmaxKernel; public fun supports (Ljava/lang/String;Ljava/util/List;)Z } public final class sk/ainet/exec/kernel/PanamaVectorKernelProviderFactory : sk/ainet/backend/api/kernel/KernelProvider { public fun ()V + public fun attentionFp32 ()Lsk/ainet/backend/api/kernel/Fp32AttentionKernel; public fun getName ()Ljava/lang/String; public fun getPriority ()I public fun isAvailable ()Z public fun matmulBf16 ()Lsk/ainet/backend/api/kernel/Bf16MatmulKernel; public fun matmulFp16 ()Lsk/ainet/backend/api/kernel/Fp16MatmulKernel; public fun matmulFp32 ()Lsk/ainet/backend/api/kernel/Fp32MatmulKernel; + public fun matmulFp32Wt ()Lsk/ainet/backend/api/kernel/Fp32MatmulWtKernel; public fun matmulQ4K ()Lsk/ainet/backend/api/kernel/Q4KMatmulKernel; public fun matmulQ4_0 ()Lsk/ainet/backend/api/kernel/Q4_0MatmulKernel; public fun matmulQ5K ()Lsk/ainet/backend/api/kernel/Q5KMatmulKernel; @@ -92,6 +98,8 @@ public final class sk/ainet/exec/kernel/PanamaVectorKernelProviderFactory : sk/a public fun matmulQ5_1 ()Lsk/ainet/backend/api/kernel/Q5_1MatmulKernel; public fun matmulQ6K ()Lsk/ainet/backend/api/kernel/Q6KMatmulKernel; public fun matmulQ8_0 ()Lsk/ainet/backend/api/kernel/Q8_0MatmulKernel; + public fun normFp32 ()Lsk/ainet/backend/api/kernel/Fp32NormKernel; + public fun softmaxFp32 ()Lsk/ainet/backend/api/kernel/Fp32SoftmaxKernel; public fun supports (Ljava/lang/String;Ljava/util/List;)Z } @@ -162,12 +170,14 @@ public final class sk/ainet/exec/kernel/ScalarFp16MatmulKernel : sk/ainet/backen public final class sk/ainet/exec/kernel/ScalarKernelProvider : sk/ainet/backend/api/kernel/KernelProvider { public static final field INSTANCE Lsk/ainet/exec/kernel/ScalarKernelProvider; + public fun attentionFp32 ()Lsk/ainet/backend/api/kernel/Fp32AttentionKernel; public fun getName ()Ljava/lang/String; public fun getPriority ()I public fun isAvailable ()Z public fun matmulBf16 ()Lsk/ainet/backend/api/kernel/Bf16MatmulKernel; public fun matmulFp16 ()Lsk/ainet/backend/api/kernel/Fp16MatmulKernel; public fun matmulFp32 ()Lsk/ainet/backend/api/kernel/Fp32MatmulKernel; + public fun matmulFp32Wt ()Lsk/ainet/backend/api/kernel/Fp32MatmulWtKernel; public fun matmulQ4K ()Lsk/ainet/backend/api/kernel/Q4KMatmulKernel; public fun matmulQ4_0 ()Lsk/ainet/backend/api/kernel/Q4_0MatmulKernel; public fun matmulQ5K ()Lsk/ainet/backend/api/kernel/Q5KMatmulKernel; @@ -175,17 +185,21 @@ public final class sk/ainet/exec/kernel/ScalarKernelProvider : sk/ainet/backend/ public fun matmulQ5_1 ()Lsk/ainet/backend/api/kernel/Q5_1MatmulKernel; public fun matmulQ6K ()Lsk/ainet/backend/api/kernel/Q6KMatmulKernel; public fun matmulQ8_0 ()Lsk/ainet/backend/api/kernel/Q8_0MatmulKernel; + public fun normFp32 ()Lsk/ainet/backend/api/kernel/Fp32NormKernel; + public fun softmaxFp32 ()Lsk/ainet/backend/api/kernel/Fp32SoftmaxKernel; public fun supports (Ljava/lang/String;Ljava/util/List;)Z } public final class sk/ainet/exec/kernel/ScalarKernelProviderFactory : sk/ainet/backend/api/kernel/KernelProvider { public fun ()V + public fun attentionFp32 ()Lsk/ainet/backend/api/kernel/Fp32AttentionKernel; public fun getName ()Ljava/lang/String; public fun getPriority ()I public fun isAvailable ()Z public fun matmulBf16 ()Lsk/ainet/backend/api/kernel/Bf16MatmulKernel; public fun matmulFp16 ()Lsk/ainet/backend/api/kernel/Fp16MatmulKernel; public fun matmulFp32 ()Lsk/ainet/backend/api/kernel/Fp32MatmulKernel; + public fun matmulFp32Wt ()Lsk/ainet/backend/api/kernel/Fp32MatmulWtKernel; public fun matmulQ4K ()Lsk/ainet/backend/api/kernel/Q4KMatmulKernel; public fun matmulQ4_0 ()Lsk/ainet/backend/api/kernel/Q4_0MatmulKernel; public fun matmulQ5K ()Lsk/ainet/backend/api/kernel/Q5KMatmulKernel; @@ -193,6 +207,8 @@ public final class sk/ainet/exec/kernel/ScalarKernelProviderFactory : sk/ainet/b public fun matmulQ5_1 ()Lsk/ainet/backend/api/kernel/Q5_1MatmulKernel; public fun matmulQ6K ()Lsk/ainet/backend/api/kernel/Q6KMatmulKernel; public fun matmulQ8_0 ()Lsk/ainet/backend/api/kernel/Q8_0MatmulKernel; + public fun normFp32 ()Lsk/ainet/backend/api/kernel/Fp32NormKernel; + public fun softmaxFp32 ()Lsk/ainet/backend/api/kernel/Fp32SoftmaxKernel; public fun supports (Ljava/lang/String;Ljava/util/List;)Z } @@ -337,10 +353,13 @@ public class sk/ainet/exec/tensor/ops/DefaultCpuOpsBase : sk/ainet/context/sched public fun relayoutPackedWeightForKernels (Lsk/ainet/lang/tensor/Tensor;)Lsk/ainet/lang/tensor/Tensor; public fun relu (Lsk/ainet/lang/tensor/Tensor;)Lsk/ainet/lang/tensor/Tensor; protected final fun requireSameDType (Lsk/ainet/lang/tensor/Tensor;Lsk/ainet/lang/tensor/Tensor;)V + protected final fun requireSdpaOperands (Lsk/ainet/lang/tensor/Tensor;Lsk/ainet/lang/tensor/Tensor;Lsk/ainet/lang/tensor/Tensor;II)V public fun reshape (Lsk/ainet/lang/tensor/Tensor;Lsk/ainet/lang/tensor/Shape;)Lsk/ainet/lang/tensor/Tensor; protected final fun rewrapFeedOrderWeight (Lsk/ainet/lang/tensor/Tensor;)Lsk/ainet/lang/tensor/Tensor; public fun rsubScalar (Ljava/lang/Number;Lsk/ainet/lang/tensor/Tensor;)Lsk/ainet/lang/tensor/Tensor; public fun scaledDotProductAttention (Lsk/ainet/lang/tensor/Tensor;Lsk/ainet/lang/tensor/Tensor;Lsk/ainet/lang/tensor/Tensor;Lsk/ainet/lang/tensor/Tensor;FZLsk/ainet/lang/tensor/Tensor;II)Lsk/ainet/lang/tensor/Tensor; + protected final fun sdpaKeyLengths (Lsk/ainet/lang/tensor/Tensor;II)[I + protected final fun sdpaMaskDims (Lsk/ainet/lang/tensor/Tensor;IIIII)[I public fun sigmoid (Lsk/ainet/lang/tensor/Tensor;)Lsk/ainet/lang/tensor/Tensor; public fun sign (Lsk/ainet/lang/tensor/Tensor;)Lsk/ainet/lang/tensor/Tensor; public fun silu (Lsk/ainet/lang/tensor/Tensor;)Lsk/ainet/lang/tensor/Tensor; diff --git a/skainet-backends/skainet-backend-cpu/src/commonMain/kotlin/sk/ainet/exec/tensor/ops/DefaultCpuOps.kt b/skainet-backends/skainet-backend-cpu/src/commonMain/kotlin/sk/ainet/exec/tensor/ops/DefaultCpuOps.kt index f340df8e1..7e648d0e5 100644 --- a/skainet-backends/skainet-backend-cpu/src/commonMain/kotlin/sk/ainet/exec/tensor/ops/DefaultCpuOps.kt +++ b/skainet-backends/skainet-backend-cpu/src/commonMain/kotlin/sk/ainet/exec/tensor/ops/DefaultCpuOps.kt @@ -3825,30 +3825,7 @@ public open class DefaultCpuOpsBase( window: Int, rightContext: Int ): Tensor { - // Expected shapes: [batch, heads, seqQ, headDim] for Q, [batch, heads, seqKV, headDim] for K/V - require(query.rank == 4) { "SDPA: expected rank-4 query, got rank ${query.rank}" } - require(key.rank == 4) { "SDPA: expected rank-4 key, got rank ${key.rank}" } - require(value.rank == 4) { "SDPA: expected rank-4 value, got rank ${value.rank}" } - require(query.shape[0] == key.shape[0] && query.shape[0] == value.shape[0]) { - "SDPA: batch mismatch — Q=${query.shape[0]} K=${key.shape[0]} V=${value.shape[0]}" - } - require(key.shape[1] == value.shape[1]) { - "SDPA: K/V head count mismatch — K=${key.shape[1]} V=${value.shape[1]}" - } - require(key.shape[1] > 0 && query.shape[1] % key.shape[1] == 0) { - "SDPA: Q heads (${query.shape[1]}) must be a multiple of K/V heads (${key.shape[1]}) — " + - "grouped-query attention maps query head h onto K/V head h / (nHeads / nKVHeads)." - } - require(query.shape[3] == key.shape[3]) { - "SDPA: Q head_dim (${query.shape[3]}) does not match K head_dim (${key.shape[3]})" - } - require(query.shape[3] == value.shape[3]) { - "SDPA: Q head_dim (${query.shape[3]}) does not match V head_dim (${value.shape[3]})" - } - require(key.shape[2] == value.shape[2]) { - "SDPA: K seqKV (${key.shape[2]}) does not match V seqKV (${value.shape[2]})" - } - sk.ainet.lang.tensor.ops.AttentionMask.validate(window, rightContext) + requireSdpaOperands(query, key, value, window, rightContext) val batch = query.shape[0] val heads = query.shape[1] @@ -3862,15 +3839,7 @@ public open class DefaultCpuOpsBase( // attended keys (the masked exp underflows to exactly 0 either way) and keeps a fully // masked row finite. `causal` folds into the range on this path. Without lengths or a // window the kernel below runs unchanged. - val lengths: IntArray? = keyLengths?.let { kl -> - require(kl.rank == 1 && kl.shape[0] == batch) { "SDPA: keyLengths must be [batch=$batch], got ${kl.shape}" } - val raw = kl.data.copyToFloatArray() - IntArray(batch) { b -> - val len = raw[b].toInt() - require(len in 0..seqKV) { "SDPA: keyLengths[$b] = $len is outside 0..$seqKV" } - len - } - } + val lengths: IntArray? = sdpaKeyLengths(keyLengths, batch, seqKV) val structured = lengths != null || window > 0 // Grouped-query attention (SKEEP-005 phase 2): K/V are read in place through the // head-group index instead of being tiled to Q's head count upstream. Same loop @@ -3891,16 +3860,7 @@ public open class DefaultCpuOpsBase( // Dense mask broadcast, trailing-aligned like the StableHLO lowering: [b | 1, H | nKV | 1, Sq | 1, Sk | 1]. // (The former `i % size` read batch row 0 for every batch, so a [batch, 1, Sq, Sk] mask with batch > 1 // was silently wrong; shapes that worked — [1, 1, Sq, Sk], [1, 1, 1, Sk], [Sq, Sk] — index identically here.) - val maskDims = mask?.let { m -> - require(m.rank in 1..4) { "SDPA: mask must have rank 1..4, got ${m.rank}" } - val d = IntArray(4) { 1 } - for (i in 0 until m.rank) d[4 - m.rank + i] = m.shape[i] - require(d[0] == 1 || d[0] == batch) { "SDPA: mask batch (${d[0]}) must be 1 or $batch" } - require(d[1] == 1 || d[1] == heads || d[1] == key.shape[1]) { "SDPA: mask heads (${d[1]}) must be 1, ${key.shape[1]} or $heads" } - require(d[2] == 1 || d[2] == seqQ) { "SDPA: mask seqQ (${d[2]}) must be 1 or $seqQ" } - require(d[3] == 1 || d[3] == seqKV) { "SDPA: mask seqKV (${d[3]}) must be 1 or $seqKV" } - d - } + val maskDims = sdpaMaskDims(mask, batch, heads, key.shape[1], seqQ, seqKV) val outBuf = FloatArray(batch * heads * seqQ * headDim) @@ -4027,6 +3987,66 @@ public open class DefaultCpuOpsBase( return newTensor(data, query.dtype, query, key, value) } + // ── scaledDotProductAttention contract, shared with the fused-kernel overrides ───────────────── + + /** The operand and argument contract of [scaledDotProductAttention]; a platform override that hands the op to a fused kernel checks the same. */ + protected fun requireSdpaOperands(query: Tensor<*, *>, key: Tensor<*, *>, value: Tensor<*, *>, window: Int, rightContext: Int) { + // Expected shapes: [batch, heads, seqQ, headDim] for Q, [batch, heads, seqKV, headDim] for K/V + require(query.rank == 4) { "SDPA: expected rank-4 query, got rank ${query.rank}" } + require(key.rank == 4) { "SDPA: expected rank-4 key, got rank ${key.rank}" } + require(value.rank == 4) { "SDPA: expected rank-4 value, got rank ${value.rank}" } + require(query.shape[0] == key.shape[0] && query.shape[0] == value.shape[0]) { + "SDPA: batch mismatch — Q=${query.shape[0]} K=${key.shape[0]} V=${value.shape[0]}" + } + require(key.shape[1] == value.shape[1]) { + "SDPA: K/V head count mismatch — K=${key.shape[1]} V=${value.shape[1]}" + } + require(key.shape[1] > 0 && query.shape[1] % key.shape[1] == 0) { + "SDPA: Q heads (${query.shape[1]}) must be a multiple of K/V heads (${key.shape[1]}) — " + + "grouped-query attention maps query head h onto K/V head h / (nHeads / nKVHeads)." + } + require(query.shape[3] == key.shape[3]) { + "SDPA: Q head_dim (${query.shape[3]}) does not match K head_dim (${key.shape[3]})" + } + require(query.shape[3] == value.shape[3]) { + "SDPA: Q head_dim (${query.shape[3]}) does not match V head_dim (${value.shape[3]})" + } + require(key.shape[2] == value.shape[2]) { + "SDPA: K seqKV (${key.shape[2]}) does not match V seqKV (${value.shape[2]})" + } + sk.ainet.lang.tensor.ops.AttentionMask.validate(window, rightContext) + } + + /** The per-batch key-padding lengths of [scaledDotProductAttention] as ints, validated against [batch] and [seqKV]; `null` for none. */ + protected fun sdpaKeyLengths(keyLengths: Tensor?, batch: Int, seqKV: Int): IntArray? { + return keyLengths?.let { kl -> + require(kl.rank == 1 && kl.shape[0] == batch) { "SDPA: keyLengths must be [batch=$batch], got ${kl.shape}" } + val raw = kl.data.copyToFloatArray() + IntArray(batch) { b -> + val len = raw[b].toInt() + require(len in 0..seqKV) { "SDPA: keyLengths[$b] = $len is outside 0..$seqKV" } + len + } + } + } + + /** + * The dense additive mask's broadcast dims `[batch | 1, heads | kvHeads | 1, seqQ | 1, seqKV | 1]`, + * trailing-aligned like the StableHLO lowering and validated against the operand shapes; `null` for no mask. + */ + protected fun sdpaMaskDims(mask: Tensor<*, *>?, batch: Int, heads: Int, kvHeads: Int, seqQ: Int, seqKV: Int): IntArray? { + return mask?.let { m -> + require(m.rank in 1..4) { "SDPA: mask must have rank 1..4, got ${m.rank}" } + val d = IntArray(4) { 1 } + for (i in 0 until m.rank) d[4 - m.rank + i] = m.shape[i] + require(d[0] == 1 || d[0] == batch) { "SDPA: mask batch (${d[0]}) must be 1 or $batch" } + require(d[1] == 1 || d[1] == heads || d[1] == kvHeads) { "SDPA: mask heads (${d[1]}) must be 1, $kvHeads or $heads" } + require(d[2] == 1 || d[2] == seqQ) { "SDPA: mask seqQ (${d[2]}) must be 1 or $seqQ" } + require(d[3] == 1 || d[3] == seqKV) { "SDPA: mask seqKV (${d[3]}) must be 1 or $seqKV" } + d + } + } + } public class DefaultCpuOps( diff --git a/skainet-backends/skainet-backend-cpu/src/jvmMain/kotlin/sk/ainet/exec/tensor/ops/DefaultCpuOpsJvm.kt b/skainet-backends/skainet-backend-cpu/src/jvmMain/kotlin/sk/ainet/exec/tensor/ops/DefaultCpuOpsJvm.kt index 8fe1cf911..4e592bba3 100644 --- a/skainet-backends/skainet-backend-cpu/src/jvmMain/kotlin/sk/ainet/exec/tensor/ops/DefaultCpuOpsJvm.kt +++ b/skainet-backends/skainet-backend-cpu/src/jvmMain/kotlin/sk/ainet/exec/tensor/ops/DefaultCpuOpsJvm.kt @@ -6,7 +6,13 @@ import jdk.incubator.vector.VectorOperators import sk.ainet.backend.api.kernel.Bf16MatmulKernel import sk.ainet.backend.api.kernel.Fp16MatmulKernel import sk.ainet.backend.api.kernel.Fp32MatmulKernel +import sk.ainet.backend.api.kernel.Fp32MemSegMatmulWtKernel +import sk.ainet.backend.api.kernel.Fp32AttentionKernel +import sk.ainet.backend.api.kernel.Fp32NormKernel +import sk.ainet.backend.api.kernel.Fp32SoftmaxKernel +import sk.ainet.backend.api.kernel.KernelProvider import sk.ainet.backend.api.kernel.KernelRegistry +import sk.ainet.backend.api.kernel.MemSegKernelProvider import sk.ainet.backend.api.kernel.KernelServiceLoader import sk.ainet.backend.api.kernel.KernelStrictness import sk.ainet.backend.api.kernel.Q4KMatmulKernel @@ -45,7 +51,7 @@ import kotlin.math.max internal class DefaultCpuOpsJvm( dataFactory: TensorDataFactory, schedule: sk.ainet.context.schedule.Schedule = sk.ainet.context.schedule.Schedule.Sequential, -) : DefaultCpuOpsBase(dataFactory, schedule) { +) : DefaultCpuOpsBase(dataFactory, schedule), sk.ainet.lang.tensor.ops.FusedNormOps { override fun withSchedule(schedule: sk.ainet.context.schedule.Schedule): sk.ainet.lang.tensor.ops.TensorOps = if (schedule === this.schedule) this else DefaultCpuOpsJvm(dataFactory, schedule) @@ -196,12 +202,19 @@ internal class DefaultCpuOpsJvm( * tensor nothing could decode correctly (#1124). Here the kernels do exist, so the relayout * pays: once per weight, cached by the identity of its bytes. */ - /** The JVM keeps the cached transpose: its vectorised `[k, n]` kernels are the fast path (SKEEP-007 §T0.2). */ + /** + * The common row-dot is not this tier's direct path: a dense weight goes to the native `x · Wᵀ` + * kernel ([matmulDenseWeightTransposedNative]) when the FFM provider is loaded, and to the cached + * transpose plus the vectorised `[k, n]` kernels otherwise (SKEEP-007 §T0.2). + */ override fun preferDirectWeightTransposed(): Boolean = false @Suppress("UNCHECKED_CAST") override fun matmulWeightTransposed(x: Tensor, weight: Tensor): Tensor { if (weight.shape.rank != 2 || !isHeapPackedWeightForJvm(weight.data)) { + // Dense FP32 `[out, in]` weight with a native direct kernel: multiplied in place, no + // transposed copy made or cached (the weight is made resident off-heap once instead). + matmulDenseWeightTransposedNative(x, weight)?.let { return it } // Packed weights OUTSIDE the JVM relayout list (the ternary formats, #1136) go to the // common views→KernelDispatch path — whose kernels and reference are commonMain and // cannot read a MemorySegment-backed activation (SKEEP-004 forbids element access over @@ -265,6 +278,225 @@ internal class DefaultCpuOpsJvm( private val prepackedWeightsJvm: MutableList>> = mutableListOf() private val PREPACK_CACHE_LIMIT_JVM: Int = 64 + /** + * The direct `x · Wᵀ` kernel over an off-heap weight (`skainet_fp32_matmul_wt` through FFM), from + * the highest-priority available provider that carries one; `null` on a JVM without the native + * library, where the cached-transpose path below still serves. + */ + private val fp32WtKernel: Fp32MemSegMatmulWtKernel? by lazy { + ensureKernelProviders() + KernelRegistry.providers().asSequence() + .filter { it.isAvailable() } + .mapNotNull { (it as? MemSegKernelProvider)?.matmulFp32WtMemSeg() } + .firstOrNull() + } + + /** + * Dense weights made resident off-heap for [fp32WtKernel], keyed by the identity of their + * `TensorData` — the counterpart of the transposed-weight cache this path replaces, holding the + * same bytes untransposed where a downcall can read them without a per-call copy. Bounded like + * that cache; an evicted weight is simply copied again on its next use. Segments come from + * `Arena.ofAuto()`, so a dropped entry is reclaimed with the tensor that owned it. + */ + private val residentDenseWeights: MutableList> = mutableListOf() + private val RESIDENT_DENSE_CACHE_LIMIT: Int = 256 + + /** + * `x · Wᵀ` for a dense FP32 `[out, in]` weight through the native direct kernel — the JVM-side + * half of SKEEP-007 §T0.2: no transposed copy of the weight is made, the weight is read where it + * lies (a segment-backed weight as is, a heap one after a single off-heap copy), and the C kernel + * threads over output rows. `null` when the kernel is not available or the operands are not + * dense FP32 the kernel can take, in which case the caller continues to the cached transpose. + */ + @Suppress("UNCHECKED_CAST") + private fun matmulDenseWeightTransposedNative(x: Tensor, weight: Tensor): Tensor? { + if (weight.shape.rank != 2 || x.rank < 1 || x.dtype != FP32::class || weight.dtype != FP32::class) return null + val kernel = fp32WtKernel ?: return null + val n = weight.shape[0] + val k = weight.shape[1] + if (x.shape.dimensions.last() != k) return null + val resident = residentDenseWeight(weight, n.toLong() * k) ?: return null + val activation = heapActivation(x) ?: return null + val m = if (k == 0) 0 else x.shape.volume / k + val out = FloatArray(m * n) + if (m > 0 && n > 0) kernel.matmulWt(activation.first, activation.second, resident.first, resident.second, out, 0, m, n, k) + val outShape = if (x.rank == 1) Shape(n) else Shape(*(x.shape.dimensions.copyOf(x.rank - 1) + n)) + val outData = dataFactory.adoptFloatArray(outShape, x.dtype, out) as TensorData + return newTensor(outData, x.dtype, x, weight) + } + + /** + * The activation's floats and their offset on the heap; a dense segment-backed activation is + * copied (it is `m × k`, small). `MemorySegmentTensorData` specifically, not the marker + * interface: the Q4/Q8 segment types implement that too and hold packed bytes, not floats. + */ + private fun heapActivation(x: Tensor<*, *>): Pair? = when (val d = x.data) { + is FloatArrayTensorData<*> -> d.buffer to 0 + is MemorySegmentTensorData<*> -> { + val count = x.shape.volume + val arr = FloatArray(count) + java.lang.foreign.MemorySegment.copy(d.segment, java.lang.foreign.ValueLayout.JAVA_FLOAT, d.segmentByteOffset, arr, 0, count) + arr to 0 + } + else -> null + } + + /** The weight's `floats` floats as an off-heap segment and byte offset: its own segment, or the resident copy of its heap array. */ + private fun residentDenseWeight(weight: Tensor<*, *>, floats: Long): Pair? { + val bytes = floats * java.lang.Float.BYTES + when (val d = weight.data) { + is MemorySegmentTensorData<*> -> { + if (d.segmentByteOffset + bytes > d.segment.byteSize()) return null + return d.segment to d.segmentByteOffset + } + is FloatArrayTensorData<*> -> { + if (d.buffer.size.toLong() < floats) return null + synchronized(residentDenseWeights) { + residentDenseWeights.firstOrNull { it.first === d }?.let { return it.second to 0L } + val seg = Arena.ofAuto().allocate(maxOf(bytes, 4L), 64L) + if (floats > 0) { + java.lang.foreign.MemorySegment.copy(d.buffer, 0, seg, java.lang.foreign.ValueLayout.JAVA_FLOAT, 0L, floats.toInt()) + } + if (residentDenseWeights.size >= RESIDENT_DENSE_CACHE_LIMIT) residentDenseWeights.removeAt(0) + residentDenseWeights.add(Pair(d, seg)) + return seg to 0L + } + } + else -> return null + } + } + + // ── fused row kernels (SKEEP-007 fused ops): softmax, the normalisations, attention ────────── + + /** The highest-priority available provider's kernel from [pick], or `null` (the common path serves). */ + private fun fusedKernel(pick: (KernelProvider) -> K?): K? { + ensureKernelProviders() + return KernelRegistry.providers().asSequence().filter { it.isAvailable() }.mapNotNull(pick).firstOrNull() + } + + private val softmaxKernel: Fp32SoftmaxKernel? by lazy { fusedKernel { it.softmaxFp32() } } + private val normKernel: Fp32NormKernel? by lazy { fusedKernel { it.normFp32() } } + private val attentionKernel: Fp32AttentionKernel? by lazy { fusedKernel { it.attentionFp32() } } + + /** + * Below this many elements a row kernel is not worth the downcall and its two staging copies: + * the common loop over a few hundred floats finishes before the segments are allocated. + */ + private val FUSED_MIN_ELEMENTS: Int = 1024 + + /** A dense FP32 tensor's floats and the offset of element 0: the array itself, a slab window, or a copy of a segment. */ + private fun denseWindow(t: Tensor<*, *>): Pair? = when (val d = t.data) { + is FloatArrayTensorData<*> -> d.buffer to 0 + is sk.ainet.lang.tensor.data.StorageFloatTensorData<*> -> { + val s = d.storage + s.checkAlive() + s.floats!! to s.arrayOffset + } + is MemorySegmentTensorData<*> -> heapActivation(t) + else -> null + } + + /** A dense FP32 tensor as one whole array from index 0 — its own buffer when it is one, a copy otherwise. */ + private fun wholeFloats(t: Tensor<*, *>): FloatArray = when (val d = t.data) { + is FloatArrayTensorData<*> -> if (d.buffer.size == t.shape.volume) d.buffer else d.buffer.copyOf(t.shape.volume) + else -> t.data.copyToFloatArray() + } + + private fun softmaxNative(tensor: Tensor, dim: Int, log: Boolean): Tensor? { + val kernel = softmaxKernel ?: return null + if (tensor.dtype != FP32::class) return null + val rank = tensor.rank + if (rank == 0) return null + val nd = if (dim < 0) dim + rank else dim + if (nd != rank - 1) return null + val cols = tensor.shape[nd] + val n = tensor.shape.volume + if (cols <= 0 || n < FUSED_MIN_ELEMENTS) return null + val src = denseWindow(tensor) ?: return null + val out = FloatArray(n) + kernel.softmaxRows(src.first, src.second, out, 0, n / cols, cols, log) + return floatResult(tensor.shape, tensor.dtype, out, tensor) + } + + override fun softmax(tensor: Tensor, dim: Int): Tensor = + softmaxNative(tensor, dim, log = false) ?: super.softmax(tensor, dim) + + override fun logSoftmax(tensor: Tensor, dim: Int): Tensor = + softmaxNative(tensor, dim, log = true) ?: super.logSoftmax(tensor, dim) + + /** `cols` values of a norm parameter as one array, or `null` when its size does not match. */ + private fun normParam(t: Tensor<*, *>, cols: Int): FloatArray? { + if (t.shape.volume != cols) return null + return wholeFloats(t) + } + + private fun normNative( + input: Tensor, gamma: Tensor?, beta: Tensor?, eps: Float, normalizedRank: Int, rms: Boolean, + ): Tensor? { + val kernel = normKernel ?: return null + if (input.dtype != FP32::class) return null + val rank = input.rank + if (normalizedRank !in 1..rank) return null + var cols = 1 + for (i in rank - normalizedRank until rank) cols *= input.shape[i] + val n = input.shape.volume + if (cols == 0 || n == 0) return null + val src = denseWindow(input) ?: return null + val g = gamma?.let { normParam(it, cols) ?: return null } + val b = beta?.let { normParam(it, cols) ?: return null } + val out = FloatArray(n) + if (rms) kernel.rmsNorm(src.first, src.second, g, out, 0, n / cols, cols, eps) + else kernel.layerNorm(src.first, src.second, g, b, out, 0, n / cols, cols, eps) + return floatResult(input.shape, input.dtype, out, input) + } + + override fun layerNorm(input: Tensor, gamma: Tensor?, beta: Tensor?, eps: Float, normalizedRank: Int): Tensor? = + normNative(input, gamma, beta, eps, normalizedRank, rms = false) + + override fun rmsNorm(input: Tensor, gamma: Tensor?, eps: Float, normalizedRank: Int): Tensor? = + normNative(input, gamma, null, eps, normalizedRank, rms = true) + + /** + * Attention through the fused native kernel when the FFM provider carries it: the same operand + * contract and mask rules as the common implementation (which validates them here), the + * `q · k` dot products vectorised, rows threaded in the native row pool rather than through the + * schedule. Without the kernel, or for non-FP32 operands, the common implementation serves. + */ + override fun scaledDotProductAttention( + query: Tensor, + key: Tensor, + value: Tensor, + mask: Tensor?, + scale: Float, + causal: Boolean, + keyLengths: Tensor?, + window: Int, + rightContext: Int, + ): Tensor { + val kernel = attentionKernel + if (kernel == null || query.dtype != FP32::class || key.dtype != FP32::class || value.dtype != FP32::class) { + return super.scaledDotProductAttention(query, key, value, mask, scale, causal, keyLengths, window, rightContext) + } + requireSdpaOperands(query, key, value, window, rightContext) + val batch = query.shape[0] + val heads = query.shape[1] + val seqQ = query.shape[2] + val headDim = query.shape[3] + val kvHeads = key.shape[1] + val seqKV = key.shape[2] + val lengths = sdpaKeyLengths(keyLengths, batch, seqKV) + val maskDims = sdpaMaskDims(mask, batch, heads, kvHeads, seqQ, seqKV) + val effectiveScale = if (scale == 0f) (1.0 / kotlin.math.sqrt(headDim.toDouble())).toFloat() else scale + val out = FloatArray(batch * heads * seqQ * headDim) + kernel.sdpa( + wholeFloats(query), wholeFloats(key), wholeFloats(value), out, + batch, heads, kvHeads, seqQ, seqKV, headDim, + effectiveScale, causal, window, rightContext, + lengths, mask?.let { wholeFloats(it) }, maskDims, + ) + return floatResult(Shape(batch, heads, seqQ, headDim), query.dtype, out, query, key, value) + } + /** * Below this many multiply-accumulates a dense FP32 matmul is faster done directly than handed * to the tile-blocked SPI kernel, whose per-call setup dominates at these sizes. Sized to cover diff --git a/skainet-backends/skainet-backend-native-cpu/native/CMakeLists.txt b/skainet-backends/skainet-backend-native-cpu/native/CMakeLists.txt index b85ed75a3..b52521ff0 100644 --- a/skainet-backends/skainet-backend-native-cpu/native/CMakeLists.txt +++ b/skainet-backends/skainet-backend-native-cpu/native/CMakeLists.txt @@ -26,6 +26,8 @@ set(SKAINET_KERNEL_SOURCES src/q5_0_matmul.c src/q5_1_matmul.c src/skainet_ternary_f32.c + src/f32_ops.c + src/f32_fused.c ) # Vendored NeoGPU ternary LUT kernel (MIT, verbatim — src/vendor/neogpu/). diff --git a/skainet-backends/skainet-backend-native-cpu/native/include/skainet_kernels.h b/skainet-backends/skainet-backend-native-cpu/native/include/skainet_kernels.h index 60663bfc9..9797dbd1b 100644 --- a/skainet-backends/skainet-backend-native-cpu/native/include/skainet_kernels.h +++ b/skainet-backends/skainet-backend-native-cpu/native/include/skainet_kernels.h @@ -426,6 +426,10 @@ SKAINET_API void skainet_q5_1_matmul( * unpacking, after which `sdot` accumulates sixteen products per instruction * where the core has ARMv8.2 dotprod. * + * The Emscripten sidecar runs the same block in SIMD128 (`i32x4.dot_i16x8_s` on + * widened halves). Output rows are split over the shared row pool once there + * are enough of them (#1195 pattern); bit-identical to a single thread. + * * @param activation int8 codes of one token, `input_dim` of them * @param activation_scale the token's absmax scale (`absmax / 127`) * @param weight canonical TQ2_0 blocks, row-major per output row @@ -454,9 +458,10 @@ SKAINET_API void skainet_bitnet_gemv_tq2_0( * input_dim must be a multiple of 4. Unlike the int8 `bitnet_gemv` path * there is no activation quantization: results are exact. Backed by the * vendored NeoGPU LUT kernel (baseline NEON, no dotprod — the fast path for - * Cortex-A72/Pi-4 class cores); it threads internally with pthreads once - * output_dim >= 512. A portable scalar build stands in where the vendored - * file cannot compile (MSVC). + * Cortex-A72/Pi-4 class cores), driven from the shared row pool. Where the + * vendored file cannot compile (MSVC, the Emscripten sidecar) a portable + * mirror with the same compile-time LUT stands in — scalar on MSVC, SIMD128 + * in the sidecar — threaded over output rows through the same pool. */ SKAINET_API void skainet_ternary_f32_gemv( const float* input, int32_t input_offset, @@ -477,7 +482,9 @@ SKAINET_API void skainet_ternary_f32_gemv( * plane_stride_bytes == output_dim * input_dim / 4. row_scale holds raw * little-endian IEEE binary16 bit patterns, one per output row (sign * ignored — encoders store max|row| >= 0). input_dim must be a multiple - * of 4. Threads internally with pthreads at any output_dim. + * of 4. The vendored kernel threads internally with pthreads at any + * output_dim; the portable mirror (MSVC, Emscripten) threads over output + * rows through the shared row pool. */ SKAINET_API void skainet_ternary_lmhead_stage1( const float* input, int32_t input_offset, @@ -503,6 +510,75 @@ SKAINET_API void skainet_fp32_matmul_wt( int32_t m, int32_t n, int32_t k ); +/* + * Dense FP32 elementwise, activation and reduction kernels (SKEEP-007 resident + * activations; see src/f32_ops.c for the contracts). Operands are contiguous. + * + * skainet_f32_binary: out[i] = a[ia] op b[ib], op ∈ {0 add, 1 sub, 2 mul, 3 div}; + * a_mode / b_mode ∈ {0 full (ia = i), 1 scalar (ia = 0), 2 bias (ia = i % inner)}. + * skainet_f32_unary: out[i] = f(in[i]), f ∈ {0 relu, 1 sigmoid, 2 silu, + * 3 gelu (tanh form), 4 gelu (erf form), 5 erf, 6 tanh}. + * skainet_f32_reduce_all: *out = Σ in[0..n) (/ n when mean != 0). + * skainet_f32_reduce_axis: out[o*inner + x] = Σ_k in[(o*red + k)*inner + x] + * (/ red when mean != 0). + * + * Threaded over chunks of the output through the shared row pool; per-element + * results do not depend on the partition. + */ +SKAINET_API void skainet_f32_binary( + int32_t op, + const float* a, int32_t a_mode, + const float* b, int32_t b_mode, + float* out, int32_t n, int32_t inner +); + +SKAINET_API void skainet_f32_unary(int32_t op, const float* in, float* out, int32_t n); + +SKAINET_API void skainet_f32_reduce_all(const float* in, int32_t n, int32_t mean, float* out); + +SKAINET_API void skainet_f32_reduce_axis( + const float* in, int32_t outer, int32_t red, int32_t inner, int32_t mean, float* out +); + +/* + * Fused dense FP32 row kernels (SKEEP-007 resident activations, step 3; see + * src/f32_fused.c for the contracts). Operands are contiguous, row-major. + * + * skainet_f32_softmax_rows: out[r, :] = softmax(in[r, :]) per row of `cols`, + * or the log-softmax when log_form != 0. + * skainet_f32_layer_norm: out[r, c] = (in[r, c] - mean_r) / sqrt(var_r + eps) + * · gamma[c] + beta[c]; gamma / beta may be NULL. + * skainet_f32_rms_norm: out[r, c] = in[r, c] / sqrt(mean(in[r, :]²) + eps) + * · gamma[c]; gamma may be NULL. + * skainet_f32_sdpa: softmax(q · kᵀ · scale + rules + mask) · v over + * q [batch, heads, seq_q, head_dim], k / v [batch, kv_heads, seq_kv, head_dim] + * (grouped-query heads), with the structural rules of the ops layer's + * AttentionMask (causal, sliding window with right_context, per-batch key + * lengths — NULL for none) and an optional dense additive mask broadcast + * over the mask_* dims (NULL for none). `scale` is applied as given. + * + * Threaded over rows (query rows for attention) through the shared row pool. + */ +SKAINET_API void skainet_f32_softmax_rows(const float* in, float* out, int32_t rows, int32_t cols, int32_t log_form); + +SKAINET_API void skainet_f32_layer_norm( + const float* in, const float* gamma, const float* beta, float* out, + int32_t rows, int32_t cols, float eps +); + +SKAINET_API void skainet_f32_rms_norm( + const float* in, const float* gamma, float* out, int32_t rows, int32_t cols, float eps +); + +SKAINET_API void skainet_f32_sdpa( + const float* q, const float* k, const float* v, float* out, + int32_t batch, int32_t heads, int32_t kv_heads, + int32_t seq_q, int32_t seq_kv, int32_t head_dim, + float scale, int32_t causal, int32_t window, int32_t right_context, + const int32_t* lengths, + const float* mask, int32_t mask_b, int32_t mask_h, int32_t mask_q, int32_t mask_k +); + #ifdef __cplusplus } #endif diff --git a/skainet-backends/skainet-backend-native-cpu/native/include/skainet_simd.h b/skainet-backends/skainet-backend-native-cpu/native/include/skainet_simd.h index b90d3c1d7..be4e86e52 100644 --- a/skainet-backends/skainet-backend-native-cpu/native/include/skainet_simd.h +++ b/skainet-backends/skainet-backend-native-cpu/native/include/skainet_simd.h @@ -166,4 +166,24 @@ static inline void skainet_wasm_q5_fifth_bits(uint32_t qh32, v128_t* fifth_lo, v } #endif +/* + * Transcendentals called from element loops go through these opaque wrappers. GCC with + * -ffast-math and glibc's math-vector.h otherwise vectorises such a loop into libmvec's + * _ZGVbN4v_expf / _ZGVbN4v_tanhf / _ZGVbN2v_erf, which the shared library is not linked + * against (and which older glibc and aarch64 builds lack): the symbol fails to resolve at + * the first call and the loader terminates the process. Under -ffast-math GCC also narrows + * (float) erf((double) x) to erff. A non-inlined scalar call per element costs nothing next + * to the function itself; clang and MSVC never emit the vector variants, so there the + * wrappers inline away. + */ +#include +#if defined(__GNUC__) && !defined(__clang__) +# define SKAINET_LIBM_FN static __attribute__((noinline, unused)) +#else +# define SKAINET_LIBM_FN static inline +#endif +SKAINET_LIBM_FN float skainet_expf(float x) { return expf(x); } +SKAINET_LIBM_FN float skainet_tanhf(float x) { return tanhf(x); } +SKAINET_LIBM_FN double skainet_erf(double x) { return erf(x); } + #endif /* SKAINET_SIMD_H */ diff --git a/skainet-backends/skainet-backend-native-cpu/native/src/bitnet_gemv.c b/skainet-backends/skainet-backend-native-cpu/native/src/bitnet_gemv.c index e91442875..2f1878179 100644 --- a/skainet-backends/skainet-backend-native-cpu/native/src/bitnet_gemv.c +++ b/skainet-backends/skainet-backend-native-cpu/native/src/bitnet_gemv.c @@ -1,5 +1,6 @@ #include "skainet_kernels.h" #include "skainet_simd.h" +#include "skainet_row_threads.h" #include #include @@ -31,6 +32,17 @@ * than it saves, and a checkpoint that is going to be run through this kernel * ships as TQ2_0. TQ1_0's base-3 packing is likewise scalar — it is a storage * format, not a runtime one. + * + * The Emscripten sidecar has the same block body in SIMD128: the unpacking is + * identical (shift, mask) and the sixteen int8 products go through + * `i32x4.dot_i16x8_s` on widened halves, the stand-in for `sdot` every other + * int8 kernel in the sidecar uses. + * + * Output rows are independent, so they are split over the shared row pool + * (skainet_row_threads.h) when there are enough of them — the mt sidecar's + * pthreads in a browser, the native pool elsewhere. Per-row arithmetic never + * changes with the partition, so the result is bit-identical to a single + * thread's. */ #define SKAINET_TQ2_0_BLOCK_SIZE 256 @@ -160,7 +172,76 @@ static inline int32_t skainet_tq2_0_block_neon( return vaddvq_s32(acc); } -#endif /* SKAINET_HAVE_NEON */ +#define skainet_tq2_0_block_simd skainet_tq2_0_block_neon +#elif defined(SKAINET_HAVE_WASM_SIMD128) +/* + * The NEON block in SIMD128: the same two chunks × four bit pairs, codes kept in + * {0,1,2} with the Σ a bias removed by the caller. `wasm_u8x16_shr` takes a + * runtime shift, so the strips can loop over the bit pair. + */ +static inline int32_t skainet_tq2_0_block_wasm( + const uint8_t* SKAINET_RESTRICT qs, + const int8_t* SKAINET_RESTRICT act) { + const v128_t mask = wasm_u8x16_splat(3); + v128_t acc = wasm_i32x4_splat(0); + for (int32_t c = 0; c < 2; ++c) { + const v128_t lo = wasm_v128_load(qs + c * 32); + const v128_t hi = wasm_v128_load(qs + c * 32 + 16); + for (int32_t l = 0; l < 4; ++l) { + const v128_t wlo = wasm_v128_and(wasm_u8x16_shr(lo, (uint32_t) (2 * l)), mask); + const v128_t whi = wasm_v128_and(wasm_u8x16_shr(hi, (uint32_t) (2 * l)), mask); + const int8_t* a = act + c * 128 + l * 32; + acc = wasm_i32x4_add(acc, skainet_wasm_dot_u8x16_i8x16(wlo, wasm_v128_load(a))); + acc = wasm_i32x4_add(acc, skainet_wasm_dot_u8x16_i8x16(whi, wasm_v128_load(a + 16))); + } + } + return skainet_wasm_hadd_i32(acc); +} +#define skainet_tq2_0_block_simd skainet_tq2_0_block_wasm +#endif /* SKAINET_HAVE_NEON / SKAINET_HAVE_WASM_SIMD128 */ + +typedef struct { + const int8_t* act; + const uint8_t* w; + const int32_t* activation_sums; + float* out; + float activation_scale; + int32_t input_dim; + int32_t blocks_per_row; +} skainet_bitnet_gemv_job; + +/* Output rows [o_start, o_end): every block of the row, scaled and summed. */ +static void skainet_bitnet_gemv_rows(void* ctx, int32_t o_start, int32_t o_end) { + const skainet_bitnet_gemv_job* j = (const skainet_bitnet_gemv_job*) ctx; + const int8_t* act = j->act; + const int32_t input_dim = j->input_dim; + const int32_t blocks_per_row = j->blocks_per_row; + for (int32_t o = o_start; o < o_end; ++o) { + float sum = 0.0f; + for (int32_t b = 0; b < blocks_per_row; ++b) { + const uint8_t* block = j->w + (size_t)(o * blocks_per_row + b) * SKAINET_TQ2_0_BYTES_PER_BLOCK; + const int32_t first = b * SKAINET_TQ2_0_BLOCK_SIZE; + const int32_t count = (input_dim - first) < SKAINET_TQ2_0_BLOCK_SIZE + ? (input_dim - first) + : SKAINET_TQ2_0_BLOCK_SIZE; + uint16_t scale_bits; + memcpy(&scale_bits, block + 64, sizeof(scale_bits)); + const float d = skainet_bitnet_fp16(scale_bits); + int32_t partial; +#ifdef skainet_tq2_0_block_simd + if (count == SKAINET_TQ2_0_BLOCK_SIZE) { + partial = skainet_tq2_0_block_simd(block, act + first) - j->activation_sums[b]; + } else { + partial = skainet_tq2_0_block_scalar(block, act + first, count); + } +#else + partial = skainet_tq2_0_block_scalar(block, act + first, count); +#endif + sum += (float)partial * d; + } + j->out[o] = sum * j->activation_scale; + } +} SKAINET_API void skainet_bitnet_gemv_tq2_0( const int8_t* SKAINET_RESTRICT activation, int32_t activation_offset, @@ -203,30 +284,14 @@ SKAINET_API void skainet_bitnet_gemv_tq2_0( activation_sums[b] = s; } - for (int32_t o = 0; o < output_dim; ++o) { - float sum = 0.0f; - for (int32_t b = 0; b < blocks_per_row; ++b) { - const uint8_t* block = w + (size_t)(o * blocks_per_row + b) * SKAINET_TQ2_0_BYTES_PER_BLOCK; - const int32_t first = b * SKAINET_TQ2_0_BLOCK_SIZE; - const int32_t count = (input_dim - first) < SKAINET_TQ2_0_BLOCK_SIZE - ? (input_dim - first) - : SKAINET_TQ2_0_BLOCK_SIZE; - uint16_t scale_bits; - memcpy(&scale_bits, block + 64, sizeof(scale_bits)); - const float d = skainet_bitnet_fp16(scale_bits); - int32_t partial; -#if defined(SKAINET_HAVE_NEON) - if (count == SKAINET_TQ2_0_BLOCK_SIZE) { - partial = skainet_tq2_0_block_neon(block, act + first) - activation_sums[b]; - } else { - partial = skainet_tq2_0_block_scalar(block, act + first, count); - } -#else - partial = skainet_tq2_0_block_scalar(block, act + first, count); -#endif - sum += (float)partial * d; - } - output[output_offset + o] = sum * activation_scale; - } + skainet_bitnet_gemv_job job; + job.act = act; + job.w = w; + job.activation_sums = activation_sums; + job.out = output + output_offset; + job.activation_scale = activation_scale; + job.input_dim = input_dim; + job.blocks_per_row = blocks_per_row; + skainet_run_rows(skainet_bitnet_gemv_rows, &job, output_dim); free(heap_sums); } diff --git a/skainet-backends/skainet-backend-native-cpu/native/src/f32_fused.c b/skainet-backends/skainet-backend-native-cpu/native/src/f32_fused.c new file mode 100644 index 000000000..72df08d74 --- /dev/null +++ b/skainet-backends/skainet-backend-native-cpu/native/src/f32_fused.c @@ -0,0 +1,396 @@ +#include "skainet_kernels.h" +#include "skainet_simd.h" +#include "skainet_row_threads.h" + +#include +#include +#include +#include + +/* + * Fused dense FP32 row kernels: softmax, layer norm, RMS norm and scaled dot-product attention + * (SKEEP-007 resident activations, step 3). Each is one pass over its rows with the intermediates + * in registers or a per-worker scratch, where the ops-layer decomposition runs 3 to 12 allocating + * passes (a layer norm is two reductions, two broadcasts, a square, a sqrt and a divide; attention + * is a `[seqQ, seqKV]` score matrix, a mask add, a softmax and a second product). They exist for + * the wasm tier first — Kotlin/Wasm has no SIMD and no shared-memory threads — and are compiled into + * every native build, so the JVM binds the same symbols through FFM and both tiers run one code + * base for these ops. + * + * Contracts (all operands contiguous, row-major): + * + * skainet_f32_softmax_rows(in, out, rows, cols, log_form) + * out[r, :] = softmax(in[r, :]) — max-subtracted, exp, divided by the row sum; with log_form != 0 + * out[r, :] = in[r, :] - (log Σ exp(in[r, :] - max) + max). + * + * skainet_f32_layer_norm(in, gamma, beta, out, rows, cols, eps) + * out[r, c] = (in[r, c] - mean_r) / sqrt(var_r + eps) · gamma[c] + beta[c], with mean and the + * biased variance over the row; gamma and/or beta may be NULL (the affine is skipped). + * + * skainet_f32_rms_norm(in, gamma, out, rows, cols, eps) + * out[r, c] = in[r, c] / sqrt(mean(in[r, :]²) + eps) · gamma[c]; gamma may be NULL. + * + * skainet_f32_sdpa(q, k, v, out, batch, heads, kv_heads, seq_q, seq_kv, head_dim, scale, + * causal, window, right_context, lengths, mask, mask_b, mask_h, mask_q, mask_k) + * out = softmax(q · kᵀ · scale + rules + mask) · v, the ops-layer `scaledDotProductAttention`: + * q is [batch, heads, seq_q, head_dim], k and v are [batch, kv_heads, seq_kv, head_dim] with + * query head h reading K/V head h / (heads / kv_heads) (grouped-query attention), out is + * [batch, heads, seq_q, head_dim]. `scale` is applied as given (the caller resolves the + * 1/sqrt(head_dim) default). The structural rules are the ops layer's AttentionMask: a query at + * absolute position absQ = seq_kv - seq_q + qi attends to keys ki with + * ki <= absQ when causal != 0, + * absQ - window + 1 <= ki <= absQ + right_context when window > 0, + * ki < lengths[b] when lengths != NULL ([batch] int32), + * intersected; a key outside the set scores -1e30 (finite, so a fully masked row is uniform) + * and its score is never computed. `mask` (or NULL) is a dense additive mask broadcast + * trailing-aligned over [batch | 1, heads | kv_heads | 1, seq_q | 1, seq_kv | 1] as the + * mask_* dims say, added to every score. Scores and probabilities are float; the dot products + * accumulate in four lanes where a SIMD body exists, the probability-weighted sum of v + * accumulates per output element in ascending key order (the reference's order). + * + * The exp calls go through skainet_expf (skainet_simd.h): a plain scalar libm call per element, + * never a libmvec vector variant the shared library would fail to resolve. + * + * Threading: rows (a query row of one head for attention) go to the shared row pool + * (skainet_row_threads.h), so a 12-head × 150-token attention threads on the mt sidecar and the JVM + * pool, and a decode step with a handful of rows stays on the caller. Attention needs a + * `seq_kv`-float scratch per worker; it is heap-allocated once per worker range. + */ + +#if defined(SKAINET_HAVE_WASM_SIMD128) +#define SKAINET_FZ_VEC v128_t +#define SKAINET_FZ_LOAD(p) wasm_v128_load(p) +#define SKAINET_FZ_STORE(p, v) wasm_v128_store((p), (v)) +#define SKAINET_FZ_SPLAT(x) wasm_f32x4_splat(x) +#define SKAINET_FZ_ADD wasm_f32x4_add +#define SKAINET_FZ_SUB wasm_f32x4_sub +#define SKAINET_FZ_MUL wasm_f32x4_mul +#define SKAINET_FZ_MAX wasm_f32x4_max +#define SKAINET_FZ_HADD skainet_wasm_hadd_f32 +#define SKAINET_FZ_HAVE_VEC 1 +static inline float skainet_fz_hmax(v128_t v) { + const float a = wasm_f32x4_extract_lane(v, 0), b = wasm_f32x4_extract_lane(v, 1); + const float c = wasm_f32x4_extract_lane(v, 2), d = wasm_f32x4_extract_lane(v, 3); + const float ab = a > b ? a : b, cd = c > d ? c : d; + return ab > cd ? ab : cd; +} +#elif defined(SKAINET_HAVE_NEON) +#define SKAINET_FZ_VEC float32x4_t +#define SKAINET_FZ_LOAD(p) vld1q_f32(p) +#define SKAINET_FZ_STORE(p, v) vst1q_f32((p), (v)) +#define SKAINET_FZ_SPLAT(x) vdupq_n_f32(x) +#define SKAINET_FZ_ADD vaddq_f32 +#define SKAINET_FZ_SUB vsubq_f32 +#define SKAINET_FZ_MUL vmulq_f32 +#define SKAINET_FZ_MAX vmaxq_f32 +#define SKAINET_FZ_HADD skainet_neon_hadd_f32 +#define SKAINET_FZ_HAVE_VEC 1 +static inline float skainet_fz_hmax(float32x4_t v) { return vmaxvq_f32(v); } +#endif + +/* The masked score of the ops layer's AttentionMask.MASKED: finite, so exp underflows to 0. */ +#define SKAINET_SDPA_MASKED (-1.0e30f) + +/* ------------------------------------------------------------ row helpers */ + +/* Σ x[i] over [0, n), four lanes then the tail. */ +static inline float skainet_fz_sum(const float* SKAINET_RESTRICT x, int32_t n) { + float s = 0.0f; + int32_t i = 0; +#ifdef SKAINET_FZ_HAVE_VEC + SKAINET_FZ_VEC acc = SKAINET_FZ_SPLAT(0.0f); + for (; i + 4 <= n; i += 4) acc = SKAINET_FZ_ADD(acc, SKAINET_FZ_LOAD(x + i)); + s = SKAINET_FZ_HADD(acc); +#endif + for (; i < n; ++i) s += x[i]; + return s; +} + +/* Σ (x[i] - c)² over [0, n). */ +static inline float skainet_fz_sum_sq_centered(const float* SKAINET_RESTRICT x, int32_t n, float c) { + float s = 0.0f; + int32_t i = 0; +#ifdef SKAINET_FZ_HAVE_VEC + const SKAINET_FZ_VEC vc = SKAINET_FZ_SPLAT(c); + SKAINET_FZ_VEC acc = SKAINET_FZ_SPLAT(0.0f); + for (; i + 4 <= n; i += 4) { + const SKAINET_FZ_VEC d = SKAINET_FZ_SUB(SKAINET_FZ_LOAD(x + i), vc); + acc = SKAINET_FZ_ADD(acc, SKAINET_FZ_MUL(d, d)); + } + s = SKAINET_FZ_HADD(acc); +#endif + for (; i < n; ++i) { const float d = x[i] - c; s += d * d; } + return s; +} + +/* max x[i] over [0, n), n >= 1. */ +static inline float skainet_fz_max(const float* SKAINET_RESTRICT x, int32_t n) { + float m = x[0]; + int32_t i = 1; +#ifdef SKAINET_FZ_HAVE_VEC + if (n >= 4) { + SKAINET_FZ_VEC vm = SKAINET_FZ_LOAD(x); + for (i = 4; i + 4 <= n; i += 4) vm = SKAINET_FZ_MAX(vm, SKAINET_FZ_LOAD(x + i)); + m = skainet_fz_hmax(vm); + } +#endif + for (; i < n; ++i) if (x[i] > m) m = x[i]; + return m; +} + +/* Σ a[i] · b[i] over [0, n), four accumulators then the tail. */ +static inline float skainet_fz_dot(const float* SKAINET_RESTRICT a, const float* SKAINET_RESTRICT b, int32_t n) { + float s = 0.0f; + int32_t i = 0; +#ifdef SKAINET_FZ_HAVE_VEC + SKAINET_FZ_VEC acc0 = SKAINET_FZ_SPLAT(0.0f), acc1 = acc0, acc2 = acc0, acc3 = acc0; + for (; i + 16 <= n; i += 16) { + acc0 = SKAINET_FZ_ADD(acc0, SKAINET_FZ_MUL(SKAINET_FZ_LOAD(a + i), SKAINET_FZ_LOAD(b + i))); + acc1 = SKAINET_FZ_ADD(acc1, SKAINET_FZ_MUL(SKAINET_FZ_LOAD(a + i + 4), SKAINET_FZ_LOAD(b + i + 4))); + acc2 = SKAINET_FZ_ADD(acc2, SKAINET_FZ_MUL(SKAINET_FZ_LOAD(a + i + 8), SKAINET_FZ_LOAD(b + i + 8))); + acc3 = SKAINET_FZ_ADD(acc3, SKAINET_FZ_MUL(SKAINET_FZ_LOAD(a + i + 12), SKAINET_FZ_LOAD(b + i + 12))); + } + SKAINET_FZ_VEC acc = SKAINET_FZ_ADD(SKAINET_FZ_ADD(acc0, acc1), SKAINET_FZ_ADD(acc2, acc3)); + for (; i + 4 <= n; i += 4) acc = SKAINET_FZ_ADD(acc, SKAINET_FZ_MUL(SKAINET_FZ_LOAD(a + i), SKAINET_FZ_LOAD(b + i))); + s = SKAINET_FZ_HADD(acc); +#endif + for (; i < n; ++i) s += a[i] * b[i]; + return s; +} + +/* out[i] = (x[i] - shift) · scale over [0, n), then · gamma[i] + beta[i] when given. */ +static inline void skainet_fz_affine_row(const float* SKAINET_RESTRICT x, float shift, float scale, + const float* SKAINET_RESTRICT gamma, const float* SKAINET_RESTRICT beta, + float* SKAINET_RESTRICT out, int32_t n) { + int32_t i = 0; +#ifdef SKAINET_FZ_HAVE_VEC + const SKAINET_FZ_VEC vshift = SKAINET_FZ_SPLAT(shift), vscale = SKAINET_FZ_SPLAT(scale); + for (; i + 4 <= n; i += 4) { + SKAINET_FZ_VEC y = SKAINET_FZ_MUL(SKAINET_FZ_SUB(SKAINET_FZ_LOAD(x + i), vshift), vscale); + if (gamma) y = SKAINET_FZ_MUL(y, SKAINET_FZ_LOAD(gamma + i)); + if (beta) y = SKAINET_FZ_ADD(y, SKAINET_FZ_LOAD(beta + i)); + SKAINET_FZ_STORE(out + i, y); + } +#endif + for (; i < n; ++i) { + float y = (x[i] - shift) * scale; + if (gamma) y *= gamma[i]; + if (beta) y += beta[i]; + out[i] = y; + } +} + +/* out[i] += p · v[i] over [0, n). */ +static inline void skainet_fz_axpy(float p, const float* SKAINET_RESTRICT v, float* SKAINET_RESTRICT out, int32_t n) { + int32_t i = 0; +#ifdef SKAINET_FZ_HAVE_VEC + const SKAINET_FZ_VEC vp = SKAINET_FZ_SPLAT(p); + for (; i + 4 <= n; i += 4) SKAINET_FZ_STORE(out + i, SKAINET_FZ_ADD(SKAINET_FZ_LOAD(out + i), SKAINET_FZ_MUL(vp, SKAINET_FZ_LOAD(v + i)))); +#endif + for (; i < n; ++i) out[i] += p * v[i]; +} + +/* Softmax of one row over [0, n) (n >= 1): exp(x - max) / Σ, or the log form. `out` may be `x` (attention runs it in place). */ +static inline void skainet_fz_softmax_row(const float* x, float* out, int32_t n, int32_t log_form) { + const float m = skainet_fz_max(x, n); + float sum = 0.0f; + if (log_form) { + for (int32_t i = 0; i < n; ++i) sum += skainet_expf(x[i] - m); + const float lse = logf(sum) + m; + int32_t i = 0; +#ifdef SKAINET_FZ_HAVE_VEC + const SKAINET_FZ_VEC vl = SKAINET_FZ_SPLAT(lse); + for (; i + 4 <= n; i += 4) SKAINET_FZ_STORE(out + i, SKAINET_FZ_SUB(SKAINET_FZ_LOAD(x + i), vl)); +#endif + for (; i < n; ++i) out[i] = x[i] - lse; + return; + } + for (int32_t i = 0; i < n; ++i) { const float e = skainet_expf(x[i] - m); out[i] = e; sum += e; } + if (sum > 0.0f) { + const float inv = 1.0f / sum; + int32_t i = 0; +#ifdef SKAINET_FZ_HAVE_VEC + const SKAINET_FZ_VEC vi = SKAINET_FZ_SPLAT(inv); + for (; i + 4 <= n; i += 4) SKAINET_FZ_STORE(out + i, SKAINET_FZ_MUL(SKAINET_FZ_LOAD(out + i), vi)); +#endif + for (; i < n; ++i) out[i] *= inv; + } +} + +/* ---------------------------------------------------------------- softmax */ + +typedef struct { + const float* in; + float* out; + int32_t cols; + int32_t log_form; +} skainet_f32_softmax_ctx; + +static void skainet_f32_softmax_rows_fn(void* vctx, int32_t r0, int32_t r1) { + const skainet_f32_softmax_ctx* c = (const skainet_f32_softmax_ctx*) vctx; + for (int32_t r = r0; r < r1; ++r) { + skainet_fz_softmax_row(c->in + (size_t) r * c->cols, c->out + (size_t) r * c->cols, c->cols, c->log_form); + } +} + +SKAINET_API void skainet_f32_softmax_rows(const float* in, float* out, int32_t rows, int32_t cols, int32_t log_form) { + if (rows <= 0 || cols <= 0) return; + skainet_f32_softmax_ctx ctx; + ctx.in = in; ctx.out = out; ctx.cols = cols; ctx.log_form = log_form; + skainet_run_rows(skainet_f32_softmax_rows_fn, &ctx, rows); +} + +/* -------------------------------------------------------------- layer norm */ + +typedef struct { + const float* in; + const float* gamma; + const float* beta; + float* out; + int32_t cols; + float eps; + int32_t rms; +} skainet_f32_norm_ctx; + +static void skainet_f32_norm_rows_fn(void* vctx, int32_t r0, int32_t r1) { + const skainet_f32_norm_ctx* c = (const skainet_f32_norm_ctx*) vctx; + const int32_t n = c->cols; + const float inv_n = 1.0f / (float) n; + for (int32_t r = r0; r < r1; ++r) { + const float* x = c->in + (size_t) r * n; + float* out = c->out + (size_t) r * n; + if (c->rms) { + const float ms = skainet_fz_sum_sq_centered(x, n, 0.0f) * inv_n; + skainet_fz_affine_row(x, 0.0f, 1.0f / sqrtf(ms + c->eps), c->gamma, NULL, out, n); + } else { + const float mean = skainet_fz_sum(x, n) * inv_n; + const float var = skainet_fz_sum_sq_centered(x, n, mean) * inv_n; + skainet_fz_affine_row(x, mean, 1.0f / sqrtf(var + c->eps), c->gamma, c->beta, out, n); + } + } +} + +SKAINET_API void skainet_f32_layer_norm(const float* in, const float* gamma, const float* beta, float* out, + int32_t rows, int32_t cols, float eps) { + if (rows <= 0 || cols <= 0) return; + skainet_f32_norm_ctx ctx; + ctx.in = in; ctx.gamma = gamma; ctx.beta = beta; ctx.out = out; ctx.cols = cols; ctx.eps = eps; ctx.rms = 0; + skainet_run_rows(skainet_f32_norm_rows_fn, &ctx, rows); +} + +SKAINET_API void skainet_f32_rms_norm(const float* in, const float* gamma, float* out, int32_t rows, int32_t cols, float eps) { + if (rows <= 0 || cols <= 0) return; + skainet_f32_norm_ctx ctx; + ctx.in = in; ctx.gamma = gamma; ctx.beta = NULL; ctx.out = out; ctx.cols = cols; ctx.eps = eps; ctx.rms = 1; + skainet_run_rows(skainet_f32_norm_rows_fn, &ctx, rows); +} + +/* --------------------------------------------------------------- attention */ + +typedef struct { + const float* q; + const float* k; + const float* v; + float* out; + int32_t heads, kv_heads, seq_q, seq_kv, head_dim; + float scale; + int32_t causal, window, right_context; + const int32_t* lengths; + const float* mask; + int32_t mask_b, mask_h, mask_q, mask_k; +} skainet_f32_sdpa_ctx; + +/* One query row (b, h, qi): scores into `scores` (seq_kv floats), softmax, then Σ p · v. */ +static void skainet_f32_sdpa_row(const skainet_f32_sdpa_ctx* c, int32_t b, int32_t h, int32_t qi, float* SKAINET_RESTRICT scores) { + const int32_t seq_kv = c->seq_kv, d = c->head_dim; + const int32_t kvh = h / (c->heads / c->kv_heads); + const float* q = c->q + (((size_t) b * c->heads + h) * c->seq_q + qi) * d; + const float* kbase = c->k + ((size_t) b * c->kv_heads + kvh) * seq_kv * d; + const float* vbase = c->v + ((size_t) b * c->kv_heads + kvh) * seq_kv * d; + float* out = c->out + (((size_t) b * c->heads + h) * c->seq_q + qi) * d; + + /* The attended key range [first, end) of this row under the structural rules. */ + const int32_t abs_q = seq_kv - c->seq_q + qi; + int32_t first = 0, end = seq_kv; + if (c->window > 0) { + const int32_t f = abs_q - c->window + 1; + if (f > first) first = f; + const int32_t e = abs_q + c->right_context + 1; + if (e < end) end = e; + } + if (c->causal && abs_q + 1 < end) end = abs_q + 1; + if (c->lengths && c->lengths[b] < end) end = c->lengths[b]; + if (end < 0) end = 0; + if (first > end) first = end; + + /* Scores: q · k · scale inside the range, MASKED outside; never computed outside. */ + if (first > 0 || end < seq_kv) { + for (int32_t ki = 0; ki < first; ++ki) scores[ki] = SKAINET_SDPA_MASKED; + for (int32_t ki = end; ki < seq_kv; ++ki) scores[ki] = SKAINET_SDPA_MASKED; + } + for (int32_t ki = first; ki < end; ++ki) scores[ki] = skainet_fz_dot(q, kbase + (size_t) ki * d, d) * c->scale; + + /* The dense additive mask, broadcast trailing-aligned. */ + int32_t pv_first = first, pv_end = end; + if (c->mask) { + const int32_t bi = c->mask_b == 1 ? 0 : b; + const int32_t hi = c->mask_h == 1 ? 0 : (c->mask_h == c->heads ? h : kvh); + const size_t row = ((size_t) bi * c->mask_h + hi) * c->mask_q + (c->mask_q == 1 ? 0 : qi); + const float* m = c->mask + row * c->mask_k; + if (c->mask_k == 1) { + const float mv = m[0]; + for (int32_t ki = 0; ki < seq_kv; ++ki) scores[ki] += mv; + } else { + for (int32_t ki = 0; ki < seq_kv; ++ki) scores[ki] += m[ki]; + } + /* A mask can raise a key the rules excluded: weigh every key below. */ + pv_first = 0; pv_end = seq_kv; + } + if (first >= end) { + /* Every key masked: a uniform row, as the dense -1e30 mask yields. */ + pv_first = 0; pv_end = seq_kv; + } + + skainet_fz_softmax_row(scores, scores, seq_kv, 0); + + /* out = Σ_ki p[ki] · v[ki, :], ascending ki per output element. Keys the rules excluded have + * p == 0 exactly (exp underflow against a finite max), so skipping them changes nothing. */ + for (int32_t x = 0; x < d; ++x) out[x] = 0.0f; + for (int32_t ki = pv_first; ki < pv_end; ++ki) skainet_fz_axpy(scores[ki], vbase + (size_t) ki * d, out, d); +} + +static void skainet_f32_sdpa_rows_fn(void* vctx, int32_t r0, int32_t r1) { + const skainet_f32_sdpa_ctx* c = (const skainet_f32_sdpa_ctx*) vctx; + float* scores = (float*) malloc((size_t) c->seq_kv * sizeof(float)); + if (!scores) return; + const int32_t per_batch = c->heads * c->seq_q; + for (int32_t r = r0; r < r1; ++r) { + const int32_t b = r / per_batch; + const int32_t rem = r - b * per_batch; + skainet_f32_sdpa_row(c, b, rem / c->seq_q, rem % c->seq_q, scores); + } + free(scores); +} + +SKAINET_API void skainet_f32_sdpa( + const float* q, const float* k, const float* v, float* out, + int32_t batch, int32_t heads, int32_t kv_heads, + int32_t seq_q, int32_t seq_kv, int32_t head_dim, + float scale, int32_t causal, int32_t window, int32_t right_context, + const int32_t* lengths, + const float* mask, int32_t mask_b, int32_t mask_h, int32_t mask_q, int32_t mask_k +) { + if (batch <= 0 || heads <= 0 || kv_heads <= 0 || seq_q <= 0 || head_dim <= 0) return; + const int32_t rows = batch * heads * seq_q; + if (seq_kv <= 0) { + for (int32_t i = 0; i < rows * head_dim; ++i) out[i] = 0.0f; + return; + } + skainet_f32_sdpa_ctx ctx; + ctx.q = q; ctx.k = k; ctx.v = v; ctx.out = out; + ctx.heads = heads; ctx.kv_heads = kv_heads; ctx.seq_q = seq_q; ctx.seq_kv = seq_kv; ctx.head_dim = head_dim; + ctx.scale = scale; ctx.causal = causal; ctx.window = window; ctx.right_context = right_context; + ctx.lengths = lengths; + ctx.mask = mask; ctx.mask_b = mask_b; ctx.mask_h = mask_h; ctx.mask_q = mask_q; ctx.mask_k = mask_k; + skainet_run_rows(skainet_f32_sdpa_rows_fn, &ctx, rows); +} diff --git a/skainet-backends/skainet-backend-native-cpu/native/src/f32_ops.c b/skainet-backends/skainet-backend-native-cpu/native/src/f32_ops.c new file mode 100644 index 000000000..1670bcba6 --- /dev/null +++ b/skainet-backends/skainet-backend-native-cpu/native/src/f32_ops.c @@ -0,0 +1,345 @@ +#include "skainet_kernels.h" +#include "skainet_simd.h" +#include "skainet_row_threads.h" + +#include +#include +#include + +/* + * Dense FP32 elementwise, activation and reduction kernels (SKEEP-007 resident activations). + * + * These exist for the wasm tier first: Kotlin/Wasm has no SIMD and no shared-memory threads, so + * everything between two matmuls of a browser forward pass — bias add, GELU, residual add, the + * reductions of a norm — ran as scalar Kotlin on one thread while the matmuls ran in the SIMD128 + * sidecar. With these exports the activations can stay in the sidecar's linear memory from one + * op to the next (`Storage.WasmLinear`) and only cross the boundary when something outside the + * sidecar needs them. They are compiled into every native build as well, so a JVM/Android/K-N + * binding is a few lines when wanted; the JVM has Vector API bodies for all of them today. + * + * Contracts (all operands contiguous): + * + * skainet_f32_binary(op, a, a_mode, b, b_mode, out, n, inner) + * out[i] = a[ia] (op) b[ib] for i in [0, n), where an operand's index follows its mode: + * SKAINET_F32_FULL ia = i (same shape as the output) + * SKAINET_F32_SCALAR ia = 0 (a single value broadcast over the output) + * SKAINET_F32_BIAS ia = i % inner (a vector over the last dimension, `inner` long) + * — the three broadcast shapes the common ops layer takes on its fast path (same shape, + * scalar, last-dim "bias"); anything else is the caller's generic path. + * + * skainet_f32_unary(op, in, out, n) + * out[i] = f(in[i]) with f one of relu, sigmoid, silu, gelu (tanh form), gelu (erf form), + * erf, tanh. The formulas are those of the Kotlin reference ops, so results agree to the + * last-ulp differences between libm and the Kotlin math functions. The transcendental ones are + * scalar libm calls through the opaque wrappers of skainet_simd.h (no libmvec dependency); + * relu has SIMD bodies. + * + * skainet_f32_reduce_all(in, n, mean, out) *out = Σ in[i], divided by n when mean != 0 + * skainet_f32_reduce_axis(in, outer, red, inner, mean, out) + * out[o * inner + x] = Σ_k in[(o * red + k) * inner + x] (/ red when mean != 0) + * — the (outer, reduced, inner) decomposition of a reduction over one axis. + * + * Threading: the work is split into fixed-size chunks of output elements (rows for the axis + * reduction) and handed to the shared row pool when there are enough of them + * (skainet_row_threads.h), so a 1M-element op uses the mt sidecar's pthreads and a 768-wide bias + * add stays on the caller. Per-element results do not depend on the partition. The full + * reduction accumulates in four lanes, so its rounding differs from a sequential sum in the last + * bits, as the JVM Vector API path's does. + */ + +#define SKAINET_F32_FULL 0 +#define SKAINET_F32_SCALAR 1 +#define SKAINET_F32_BIAS 2 + +#define SKAINET_F32_ADD 0 +#define SKAINET_F32_SUB 1 +#define SKAINET_F32_MUL 2 +#define SKAINET_F32_DIV 3 + +#define SKAINET_F32_RELU 0 +#define SKAINET_F32_SIGMOID 1 +#define SKAINET_F32_SILU 2 +#define SKAINET_F32_GELU_TANH 3 +#define SKAINET_F32_GELU_EXACT 4 +#define SKAINET_F32_ERF 5 +#define SKAINET_F32_TANH 6 + +/* Output elements per pool "row": the pool threads at 512 rows, so 1024 → 512K elements. */ +#define SKAINET_F32_BINARY_CHUNK 1024 +/* The transcendental unaries cost ~20 ns a lane in scalar libm; thread earlier. */ +#define SKAINET_F32_UNARY_CHUNK 256 + +/* ---------------------------------------------------------------- binary */ + +typedef struct { + const float* a; + const float* b; + float* out; + int32_t n, inner; + int32_t op, a_mode, b_mode; +} skainet_f32_binary_ctx; + +/* The two SIMD shapes: lane-wise a[i] (op) b[i], and lane-wise a[i] (op) splat. */ +#if defined(SKAINET_HAVE_WASM_SIMD128) +#define SKAINET_F32_VEC v128_t +#define SKAINET_F32_LOAD(p) wasm_v128_load(p) +#define SKAINET_F32_STORE(p, v) wasm_v128_store((p), (v)) +#define SKAINET_F32_SPLAT(x) wasm_f32x4_splat(x) +#define SKAINET_F32_VADD wasm_f32x4_add +#define SKAINET_F32_VSUB wasm_f32x4_sub +#define SKAINET_F32_VMUL wasm_f32x4_mul +#define SKAINET_F32_VDIV wasm_f32x4_div +#define SKAINET_F32_VMAX wasm_f32x4_max +#define SKAINET_F32_HAVE_VEC 1 +#elif defined(SKAINET_HAVE_NEON) +#define SKAINET_F32_VEC float32x4_t +#define SKAINET_F32_LOAD(p) vld1q_f32(p) +#define SKAINET_F32_STORE(p, v) vst1q_f32((p), (v)) +#define SKAINET_F32_SPLAT(x) vdupq_n_f32(x) +#define SKAINET_F32_VADD vaddq_f32 +#define SKAINET_F32_VSUB vsubq_f32 +#define SKAINET_F32_VMUL vmulq_f32 +#define SKAINET_F32_VDIV vdivq_f32 +#define SKAINET_F32_VMAX vmaxq_f32 +#define SKAINET_F32_HAVE_VEC 1 +#endif + +static inline float skainet_f32_apply(int32_t op, float x, float y) { + switch (op) { + case SKAINET_F32_ADD: return x + y; + case SKAINET_F32_SUB: return x - y; + case SKAINET_F32_MUL: return x * y; + default: return x / y; + } +} + +#ifdef SKAINET_F32_HAVE_VEC +static inline SKAINET_F32_VEC skainet_f32_vapply(int32_t op, SKAINET_F32_VEC x, SKAINET_F32_VEC y) { + switch (op) { + case SKAINET_F32_ADD: return SKAINET_F32_VADD(x, y); + case SKAINET_F32_SUB: return SKAINET_F32_VSUB(x, y); + case SKAINET_F32_MUL: return SKAINET_F32_VMUL(x, y); + default: return SKAINET_F32_VDIV(x, y); + } +} +#endif + +/* out[i0..i1) = a[i0..i1) (op) b[i0..i1): both full. */ +static void skainet_f32_binary_full(int32_t op, const float* SKAINET_RESTRICT a, const float* SKAINET_RESTRICT b, + float* SKAINET_RESTRICT out, int32_t i0, int32_t i1) { + int32_t i = i0; +#ifdef SKAINET_F32_HAVE_VEC + for (; i + 4 <= i1; i += 4) { + SKAINET_F32_STORE(out + i, skainet_f32_vapply(op, SKAINET_F32_LOAD(a + i), SKAINET_F32_LOAD(b + i))); + } +#endif + for (; i < i1; ++i) out[i] = skainet_f32_apply(op, a[i], b[i]); +} + +/* out[i0..i1) = a[i0..i1) (op) s, or s (op) a[i0..i1) when `swap`. */ +static void skainet_f32_binary_scalar(int32_t op, const float* SKAINET_RESTRICT a, float s, int swap, + float* SKAINET_RESTRICT out, int32_t i0, int32_t i1) { + int32_t i = i0; +#ifdef SKAINET_F32_HAVE_VEC + const SKAINET_F32_VEC vs = SKAINET_F32_SPLAT(s); + for (; i + 4 <= i1; i += 4) { + const SKAINET_F32_VEC va = SKAINET_F32_LOAD(a + i); + SKAINET_F32_STORE(out + i, swap ? skainet_f32_vapply(op, vs, va) : skainet_f32_vapply(op, va, vs)); + } +#endif + for (; i < i1; ++i) out[i] = swap ? skainet_f32_apply(op, s, a[i]) : skainet_f32_apply(op, a[i], s); +} + +static void skainet_f32_binary_chunks(void* vctx, int32_t c0, int32_t c1) { + const skainet_f32_binary_ctx* c = (const skainet_f32_binary_ctx*) vctx; + const int32_t i0 = c0 * SKAINET_F32_BINARY_CHUNK; + const int32_t i1 = (c1 * SKAINET_F32_BINARY_CHUNK < c->n) ? c1 * SKAINET_F32_BINARY_CHUNK : c->n; + if (c->a_mode == SKAINET_F32_FULL && c->b_mode == SKAINET_F32_FULL) { + skainet_f32_binary_full(c->op, c->a, c->b, c->out, i0, i1); + return; + } + if (c->a_mode == SKAINET_F32_FULL && c->b_mode == SKAINET_F32_SCALAR) { + skainet_f32_binary_scalar(c->op, c->a, c->b[0], 0, c->out, i0, i1); + return; + } + if (c->a_mode == SKAINET_F32_SCALAR && c->b_mode == SKAINET_F32_FULL) { + skainet_f32_binary_scalar(c->op, c->b, c->a[0], 1, c->out, i0, i1); + return; + } + if (c->a_mode == SKAINET_F32_FULL && c->b_mode == SKAINET_F32_BIAS) { + /* Walk the chunk row by row of the last dimension so the bias is read contiguously. */ + int32_t i = i0; + while (i < i1) { + const int32_t x = i % c->inner; + const int32_t run = (c->inner - x < i1 - i) ? c->inner - x : i1 - i; + skainet_f32_binary_full(c->op, c->a + i, c->b + x, c->out + i, 0, run); + i += run; + } + return; + } + if (c->a_mode == SKAINET_F32_BIAS && c->b_mode == SKAINET_F32_FULL) { + int32_t i = i0; + while (i < i1) { + const int32_t x = i % c->inner; + const int32_t run = (c->inner - x < i1 - i) ? c->inner - x : i1 - i; + skainet_f32_binary_full(c->op, c->a + x, c->b + i, c->out + i, 0, run); + i += run; + } + return; + } + /* Remaining mode pairs (scalar×scalar, bias×bias, scalar×bias): index each side by its mode. */ + for (int32_t i = i0; i < i1; ++i) { + const int32_t ia = c->a_mode == SKAINET_F32_FULL ? i : (c->a_mode == SKAINET_F32_SCALAR ? 0 : i % c->inner); + const int32_t ib = c->b_mode == SKAINET_F32_FULL ? i : (c->b_mode == SKAINET_F32_SCALAR ? 0 : i % c->inner); + c->out[i] = skainet_f32_apply(c->op, c->a[ia], c->b[ib]); + } +} + +SKAINET_API void skainet_f32_binary( + int32_t op, + const float* a, int32_t a_mode, + const float* b, int32_t b_mode, + float* out, int32_t n, int32_t inner +) { + if (n <= 0) return; + skainet_f32_binary_ctx ctx; + ctx.a = a; ctx.b = b; ctx.out = out; + ctx.n = n; + ctx.inner = inner > 0 ? inner : 1; + ctx.op = op; ctx.a_mode = a_mode; ctx.b_mode = b_mode; + skainet_run_rows(skainet_f32_binary_chunks, &ctx, (n + SKAINET_F32_BINARY_CHUNK - 1) / SKAINET_F32_BINARY_CHUNK); +} + +/* ----------------------------------------------------------------- unary */ + +typedef struct { + const float* in; + float* out; + int32_t n; + int32_t op; +} skainet_f32_unary_ctx; + +static inline float skainet_f32_sigmoid(float x) { return 1.0f / (1.0f + skainet_expf(-x)); } + +static void skainet_f32_unary_chunks(void* vctx, int32_t c0, int32_t c1) { + const skainet_f32_unary_ctx* c = (const skainet_f32_unary_ctx*) vctx; + const float* SKAINET_RESTRICT in = c->in; + float* SKAINET_RESTRICT out = c->out; + const int32_t i0 = c0 * SKAINET_F32_UNARY_CHUNK; + const int32_t i1 = (c1 * SKAINET_F32_UNARY_CHUNK < c->n) ? c1 * SKAINET_F32_UNARY_CHUNK : c->n; + switch (c->op) { + case SKAINET_F32_RELU: { + int32_t i = i0; +#ifdef SKAINET_F32_HAVE_VEC + const SKAINET_F32_VEC zero = SKAINET_F32_SPLAT(0.0f); + for (; i + 4 <= i1; i += 4) SKAINET_F32_STORE(out + i, SKAINET_F32_VMAX(SKAINET_F32_LOAD(in + i), zero)); +#endif + for (; i < i1; ++i) out[i] = in[i] < 0.0f ? 0.0f : in[i]; + break; + } + case SKAINET_F32_SIGMOID: + for (int32_t i = i0; i < i1; ++i) out[i] = skainet_f32_sigmoid(in[i]); + break; + case SKAINET_F32_SILU: + for (int32_t i = i0; i < i1; ++i) out[i] = in[i] * skainet_f32_sigmoid(in[i]); + break; + case SKAINET_F32_GELU_TANH: + for (int32_t i = i0; i < i1; ++i) { + const float x = in[i]; + const float inner = x + 0.044715f * (x * x * x); + out[i] = 0.5f * x * (1.0f + skainet_tanhf(0.7978845608f * inner)); + } + break; + case SKAINET_F32_GELU_EXACT: + for (int32_t i = i0; i < i1; ++i) { + const double x = (double) in[i]; + out[i] = (float) (0.5 * x * (1.0 + skainet_erf(x * 0.70710678118654752440))); + } + break; + case SKAINET_F32_ERF: + for (int32_t i = i0; i < i1; ++i) out[i] = (float) skainet_erf((double) in[i]); + break; + default: /* SKAINET_F32_TANH */ + for (int32_t i = i0; i < i1; ++i) out[i] = skainet_tanhf(in[i]); + break; + } +} + +SKAINET_API void skainet_f32_unary(int32_t op, const float* in, float* out, int32_t n) { + if (n <= 0) return; + skainet_f32_unary_ctx ctx; + ctx.in = in; ctx.out = out; ctx.n = n; ctx.op = op; + skainet_run_rows(skainet_f32_unary_chunks, &ctx, (n + SKAINET_F32_UNARY_CHUNK - 1) / SKAINET_F32_UNARY_CHUNK); +} + +/* ------------------------------------------------------------- reductions */ + +SKAINET_API void skainet_f32_reduce_all(const float* in, int32_t n, int32_t mean, float* out) { + float s = 0.0f; + if (n > 0) { + int32_t i = 0; +#ifdef SKAINET_F32_HAVE_VEC + SKAINET_F32_VEC acc0 = SKAINET_F32_SPLAT(0.0f), acc1 = acc0, acc2 = acc0, acc3 = acc0; + for (; i + 16 <= n; i += 16) { + acc0 = SKAINET_F32_VADD(acc0, SKAINET_F32_LOAD(in + i)); + acc1 = SKAINET_F32_VADD(acc1, SKAINET_F32_LOAD(in + i + 4)); + acc2 = SKAINET_F32_VADD(acc2, SKAINET_F32_LOAD(in + i + 8)); + acc3 = SKAINET_F32_VADD(acc3, SKAINET_F32_LOAD(in + i + 12)); + } + const SKAINET_F32_VEC acc = SKAINET_F32_VADD(SKAINET_F32_VADD(acc0, acc1), SKAINET_F32_VADD(acc2, acc3)); +#if defined(SKAINET_HAVE_WASM_SIMD128) + s = skainet_wasm_hadd_f32(acc); +#else + s = skainet_neon_hadd_f32(acc); +#endif +#endif + for (; i < n; ++i) s += in[i]; + } + *out = (mean && n > 0) ? s / (float) n : s; +} + +typedef struct { + const float* in; + float* out; + int32_t red, inner; + int32_t mean; +} skainet_f32_reduce_axis_ctx; + +static void skainet_f32_reduce_axis_rows(void* vctx, int32_t o0, int32_t o1) { + const skainet_f32_reduce_axis_ctx* c = (const skainet_f32_reduce_axis_ctx*) vctx; + const int32_t inner = c->inner; + const int32_t red = c->red; + for (int32_t o = o0; o < o1; ++o) { + float* SKAINET_RESTRICT out = c->out + (size_t) o * inner; + const float* SKAINET_RESTRICT base = c->in + (size_t) o * red * inner; + for (int32_t x = 0; x < inner; ++x) out[x] = 0.0f; + for (int32_t k = 0; k < red; ++k) { + const float* SKAINET_RESTRICT row = base + (size_t) k * inner; + int32_t x = 0; +#ifdef SKAINET_F32_HAVE_VEC + for (; x + 4 <= inner; x += 4) SKAINET_F32_STORE(out + x, SKAINET_F32_VADD(SKAINET_F32_LOAD(out + x), SKAINET_F32_LOAD(row + x))); +#endif + for (; x < inner; ++x) out[x] += row[x]; + } + if (c->mean && red > 0) { + const float inv = 1.0f / (float) red; + int32_t x = 0; +#ifdef SKAINET_F32_HAVE_VEC + const SKAINET_F32_VEC vinv = SKAINET_F32_SPLAT(inv); + for (; x + 4 <= inner; x += 4) SKAINET_F32_STORE(out + x, SKAINET_F32_VMUL(SKAINET_F32_LOAD(out + x), vinv)); +#endif + for (; x < inner; ++x) out[x] *= inv; + } + } +} + +SKAINET_API void skainet_f32_reduce_axis(const float* in, int32_t outer, int32_t red, int32_t inner, int32_t mean, float* out) { + if (outer <= 0 || inner <= 0) return; + if (red <= 0) { + for (int32_t i = 0; i < outer * inner; ++i) out[i] = 0.0f; + return; + } + skainet_f32_reduce_axis_ctx ctx; + ctx.in = in; ctx.out = out; ctx.red = red; ctx.inner = inner; ctx.mean = mean; + skainet_run_rows(skainet_f32_reduce_axis_rows, &ctx, outer); +} diff --git a/skainet-backends/skainet-backend-native-cpu/native/src/skainet_ternary_f32.c b/skainet-backends/skainet-backend-native-cpu/native/src/skainet_ternary_f32.c index b9cf2824f..cdf4015a5 100644 --- a/skainet-backends/skainet-backend-native-cpu/native/src/skainet_ternary_f32.c +++ b/skainet-backends/skainet-backend-native-cpu/native/src/skainet_ternary_f32.c @@ -6,10 +6,13 @@ * pthread_once warm-up, so first use is race-free under any threading. * * The vendored file needs pthreads and GNU/Clang builtins, so it is only in - * the build where SKAINET_HAVE_NEOGPU_TERNARY is defined (non-MSVC). The - * fallback branch below is a portable scalar mirror with identical semantics, - * including the byte-code-3 → +2.0 decode (loaders reject code 3 at import; - * the kernel contract is "garbage in, garbage out", never a crash). + * the build where SKAINET_HAVE_NEOGPU_TERNARY is defined (non-MSVC native + * builds). The other branch below is a portable mirror with identical + * semantics, including the byte-code-3 → +2.0 decode (loaders reject code 3 + * at import; the kernel contract is "garbage in, garbage out", never a + * crash). It is what MSVC and the Emscripten sidecar compile: the same + * 256-entry LUT as a compile-time constant (no init guard to race on), a + * SIMD128 body in the sidecar, and the shared row pool for threading. */ #include "skainet_kernels.h" @@ -108,12 +111,92 @@ void skainet_ternary_lmhead_stage1( (uint32_t)input_dim); } -#else /* !SKAINET_HAVE_NEOGPU_TERNARY — portable scalar mirror (MSVC etc.) */ +#else /* !SKAINET_HAVE_NEOGPU_TERNARY — portable mirror (MSVC, Emscripten) */ -/* decode(byte, lane) = ((byte >> (lane*2)) & 3) - 1, exactly the vendored LUT - * (so byte code 3 yields +2, matching TernaryCodec.decodeBitNet). */ -static float skainet_ternary_decode(uint8_t b, int lane) { - return (float)((int)((b >> (lane * 2)) & 3) - 1); +#include "skainet_simd.h" +#include "skainet_row_threads.h" + +/* + * decode(byte, lane) = ((byte >> (lane*2)) & 3) - 1 — exactly the vendored + * LUT (so byte code 3 yields +2, matching TernaryCodec.decodeBitNet), as a + * compile-time constant: one 16-byte row per weight byte, the four decoded + * codes of its four elements. The vendored file builds the same table on + * first use behind a non-atomic flag; a constant needs no guard, which is + * what makes the row pool below safe without pthread_once. + */ +#define SKAINET_TERNARY_D(b, lane) ((float) ((int) (((b) >> ((lane) * 2)) & 3) - 1)) +#define SKAINET_TERNARY_E(b) \ + { SKAINET_TERNARY_D(b, 0), SKAINET_TERNARY_D(b, 1), SKAINET_TERNARY_D(b, 2), SKAINET_TERNARY_D(b, 3) } +#define SKAINET_TERNARY_E16(b) \ + SKAINET_TERNARY_E(b + 0), SKAINET_TERNARY_E(b + 1), SKAINET_TERNARY_E(b + 2), SKAINET_TERNARY_E(b + 3), \ + SKAINET_TERNARY_E(b + 4), SKAINET_TERNARY_E(b + 5), SKAINET_TERNARY_E(b + 6), SKAINET_TERNARY_E(b + 7), \ + SKAINET_TERNARY_E(b + 8), SKAINET_TERNARY_E(b + 9), SKAINET_TERNARY_E(b + 10), SKAINET_TERNARY_E(b + 11), \ + SKAINET_TERNARY_E(b + 12), SKAINET_TERNARY_E(b + 13), SKAINET_TERNARY_E(b + 14), SKAINET_TERNARY_E(b + 15) + +static const float skainet_ternary_lut[256][4] = { + SKAINET_TERNARY_E16(0), SKAINET_TERNARY_E16(16), SKAINET_TERNARY_E16(32), SKAINET_TERNARY_E16(48), + SKAINET_TERNARY_E16(64), SKAINET_TERNARY_E16(80), SKAINET_TERNARY_E16(96), SKAINET_TERNARY_E16(112), + SKAINET_TERNARY_E16(128), SKAINET_TERNARY_E16(144), SKAINET_TERNARY_E16(160), SKAINET_TERNARY_E16(176), + SKAINET_TERNARY_E16(192), SKAINET_TERNARY_E16(208), SKAINET_TERNARY_E16(224), SKAINET_TERNARY_E16(240), +}; + +#undef SKAINET_TERNARY_E16 +#undef SKAINET_TERNARY_E +#undef SKAINET_TERNARY_D + +/* FP16 row scale decode, same bit arithmetic as the vendored file (sign + * ignored — encoders store max|row| >= 0). */ +static inline float skainet_ternary_row_scale(uint16_t h16) { + const uint32_t eu = (h16 >> 10) & 0x1Fu; + const uint32_t mu = h16 & 0x03FFu; + const uint32_t fu = ((eu + 112u) << 23) | (mu << 13); + float rsc; + memcpy(&rsc, &fu, 4); + return rsc; +} + +typedef struct { + float* out; + const float* in; + const uint8_t* w; + int32_t row_bytes; +} skainet_ternary_gemv_job; + +/* out[o] = Σ_bi lut[w[o][bi]] · in[bi*4 .. bi*4+3] for rows [o_start, o_end). */ +static void skainet_ternary_gemv_rows(void* ctx, int32_t o_start, int32_t o_end) { + const skainet_ternary_gemv_job* j = (const skainet_ternary_gemv_job*) ctx; + const float* in = j->in; + const int32_t row_bytes = j->row_bytes; + for (int32_t o = o_start; o < o_end; ++o) { + const uint8_t* wrow = j->w + (size_t) o * row_bytes; +#ifdef SKAINET_HAVE_WASM_SIMD128 + /* One LUT row is one f32x4 of decoded codes against four activations: + * a load, a multiply and an add per weight byte. Four accumulators + * keep the adds independent; the vendored NEON body does the same. */ + v128_t acc0 = wasm_f32x4_splat(0.0f), acc1 = acc0, acc2 = acc0, acc3 = acc0; + int32_t bi = 0; + for (; bi + 4 <= row_bytes; bi += 4) { + acc0 = wasm_f32x4_add(acc0, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[wrow[bi]]), wasm_v128_load(in + (size_t) bi * 4))); + acc1 = wasm_f32x4_add(acc1, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[wrow[bi + 1]]), wasm_v128_load(in + (size_t) bi * 4 + 4))); + acc2 = wasm_f32x4_add(acc2, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[wrow[bi + 2]]), wasm_v128_load(in + (size_t) bi * 4 + 8))); + acc3 = wasm_f32x4_add(acc3, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[wrow[bi + 3]]), wasm_v128_load(in + (size_t) bi * 4 + 12))); + } + for (; bi < row_bytes; ++bi) { + acc0 = wasm_f32x4_add(acc0, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[wrow[bi]]), wasm_v128_load(in + (size_t) bi * 4))); + } + j->out[o] = skainet_wasm_hadd_f32(wasm_f32x4_add(wasm_f32x4_add(acc0, acc1), wasm_f32x4_add(acc2, acc3))); +#else + float acc = 0.0f; + for (int32_t bi = 0; bi < row_bytes; ++bi) { + const float* d = skainet_ternary_lut[wrow[bi]]; + acc += d[0] * in[bi * 4 ]; + acc += d[1] * in[bi * 4 + 1]; + acc += d[2] * in[bi * 4 + 2]; + acc += d[3] * in[bi * 4 + 3]; + } + j->out[o] = acc; +#endif + } } void skainet_ternary_f32_gemv( @@ -123,20 +206,76 @@ void skainet_ternary_f32_gemv( float* output, int32_t output_offset) { if (output_dim <= 0) return; - const float* in = input + input_offset; - const uint8_t* w = weight + weight_byte_offset; - const int32_t row_bytes = input_dim / 4; - for (int32_t n = 0; n < output_dim; n++) { - const uint8_t* wrow = w + (size_t)n * row_bytes; - float acc = 0.0f; - for (int32_t bi = 0; bi < row_bytes; bi++) { - const uint8_t b = wrow[bi]; - acc += skainet_ternary_decode(b, 0) * in[bi * 4 ]; - acc += skainet_ternary_decode(b, 1) * in[bi * 4 + 1]; - acc += skainet_ternary_decode(b, 2) * in[bi * 4 + 2]; - acc += skainet_ternary_decode(b, 3) * in[bi * 4 + 3]; + skainet_ternary_gemv_job job = { + output + output_offset, + input + input_offset, + weight + weight_byte_offset, + input_dim / 4, + }; + /* input_dim == 0: row_bytes 0, every row is written as 0.0f. */ + skainet_run_rows(skainet_ternary_gemv_rows, &job, output_dim); +} + +typedef struct { + float* out; + const float* in; + const uint8_t* p0; + const uint8_t* p1; + const uint8_t* p2; + const uint8_t* p3; + const uint16_t* row_scale; + int32_t row_bytes; +} skainet_ternary_lmhead_job; + +/* out[o] = rs[o] · Σ_bi (lut[p0] + lut[p1]/3 + lut[p2]/9 + lut[p3]/27) · in[bi*4 ..]. */ +static void skainet_ternary_lmhead_rows(void* ctx, int32_t o_start, int32_t o_end) { + const skainet_ternary_lmhead_job* j = (const skainet_ternary_lmhead_job*) ctx; + const float* in = j->in; + const int32_t row_bytes = j->row_bytes; + const float w1 = 1.0f / 3.0f, w2 = 1.0f / 9.0f, w3 = 1.0f / 27.0f; + for (int32_t o = o_start; o < o_end; ++o) { + const uint8_t* r0 = j->p0 + (size_t) o * row_bytes; + const uint8_t* r1 = j->p1 + (size_t) o * row_bytes; + const uint8_t* r2 = j->p2 + (size_t) o * row_bytes; + const uint8_t* r3 = j->p3 + (size_t) o * row_bytes; +#ifdef SKAINET_HAVE_WASM_SIMD128 + const v128_t vw1 = wasm_f32x4_splat(w1), vw2 = wasm_f32x4_splat(w2), vw3 = wasm_f32x4_splat(w3); + v128_t acc0 = wasm_f32x4_splat(0.0f), acc1 = acc0; + int32_t bi = 0; + for (; bi + 2 <= row_bytes; bi += 2) { + v128_t u0 = wasm_v128_load(skainet_ternary_lut[r0[bi]]); + u0 = wasm_f32x4_add(u0, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[r1[bi]]), vw1)); + u0 = wasm_f32x4_add(u0, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[r2[bi]]), vw2)); + u0 = wasm_f32x4_add(u0, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[r3[bi]]), vw3)); + acc0 = wasm_f32x4_add(acc0, wasm_f32x4_mul(u0, wasm_v128_load(in + (size_t) bi * 4))); + v128_t u1 = wasm_v128_load(skainet_ternary_lut[r0[bi + 1]]); + u1 = wasm_f32x4_add(u1, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[r1[bi + 1]]), vw1)); + u1 = wasm_f32x4_add(u1, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[r2[bi + 1]]), vw2)); + u1 = wasm_f32x4_add(u1, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[r3[bi + 1]]), vw3)); + acc1 = wasm_f32x4_add(acc1, wasm_f32x4_mul(u1, wasm_v128_load(in + (size_t) bi * 4 + 4))); } - output[output_offset + n] = acc; + for (; bi < row_bytes; ++bi) { + v128_t u = wasm_v128_load(skainet_ternary_lut[r0[bi]]); + u = wasm_f32x4_add(u, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[r1[bi]]), vw1)); + u = wasm_f32x4_add(u, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[r2[bi]]), vw2)); + u = wasm_f32x4_add(u, wasm_f32x4_mul(wasm_v128_load(skainet_ternary_lut[r3[bi]]), vw3)); + acc0 = wasm_f32x4_add(acc0, wasm_f32x4_mul(u, wasm_v128_load(in + (size_t) bi * 4))); + } + const float s = skainet_wasm_hadd_f32(wasm_f32x4_add(acc0, acc1)); +#else + float s = 0.0f; + for (int32_t bi = 0; bi < row_bytes; ++bi) { + const float* d0 = skainet_ternary_lut[r0[bi]]; + const float* d1 = skainet_ternary_lut[r1[bi]]; + const float* d2 = skainet_ternary_lut[r2[bi]]; + const float* d3 = skainet_ternary_lut[r3[bi]]; + for (int lane = 0; lane < 4; ++lane) { + const float u = d0[lane] + d1[lane] * w1 + d2[lane] * w2 + d3[lane] * w3; + s += u * in[bi * 4 + lane]; + } + } +#endif + j->out[o] = s * skainet_ternary_row_scale(j->row_scale[o]); } } @@ -148,34 +287,18 @@ void skainet_ternary_lmhead_stage1( float* output, int32_t output_offset) { if (output_dim <= 0) return; - const float* in = input + input_offset; const uint8_t* base = planes + planes_byte_offset; - const uint16_t* rs = row_scale + row_scale_offset; - const int32_t row_bytes = input_dim / 4; - const float pw[4] = { 1.0f, 1.0f / 3.0f, 1.0f / 9.0f, 1.0f / 27.0f }; - for (int32_t n = 0; n < output_dim; n++) { - float acc = 0.0f; - for (int32_t bi = 0; bi < row_bytes; bi++) { - for (int lane = 0; lane < 4; lane++) { - float u = 0.0f; - for (int p = 0; p < 4; p++) { - const uint8_t b = - base[(size_t)plane_stride_bytes * p + (size_t)n * row_bytes + bi]; - u += skainet_ternary_decode(b, lane) * pw[p]; - } - acc += u * in[bi * 4 + lane]; - } - } - /* FP16 row scale decode, same bit arithmetic as the vendored file - * (sign ignored — encoders store max|row| >= 0). */ - const uint32_t h16 = rs[n]; - const uint32_t eu = (h16 >> 10) & 0x1Fu; - const uint32_t mu = h16 & 0x03FFu; - const uint32_t fu = ((eu + 112u) << 23) | (mu << 13); - float rsc; - memcpy(&rsc, &fu, 4); - output[output_offset + n] = acc * rsc; - } + skainet_ternary_lmhead_job job = { + output + output_offset, + input + input_offset, + base, + base + (size_t) plane_stride_bytes, + base + (size_t) plane_stride_bytes * 2, + base + (size_t) plane_stride_bytes * 3, + row_scale + row_scale_offset, + input_dim / 4, + }; + skainet_run_rows(skainet_ternary_lmhead_rows, &job, output_dim); } #endif /* SKAINET_HAVE_NEOGPU_TERNARY */ diff --git a/skainet-backends/skainet-backend-native-cpu/src/jvmMain/kotlin/sk/ainet/exec/kernel/NativeFp32MatmulWtKernel.kt b/skainet-backends/skainet-backend-native-cpu/src/jvmMain/kotlin/sk/ainet/exec/kernel/NativeFp32MatmulWtKernel.kt new file mode 100644 index 000000000..1ec68d773 --- /dev/null +++ b/skainet-backends/skainet-backend-native-cpu/src/jvmMain/kotlin/sk/ainet/exec/kernel/NativeFp32MatmulWtKernel.kt @@ -0,0 +1,116 @@ +package sk.ainet.exec.kernel + +import java.lang.foreign.Arena +import java.lang.foreign.FunctionDescriptor +import java.lang.foreign.Linker +import java.lang.foreign.MemorySegment +import java.lang.foreign.ValueLayout +import java.lang.invoke.MethodHandle +import sk.ainet.backend.api.kernel.Fp32MatmulWtKernel +import sk.ainet.backend.api.kernel.Fp32MemSegMatmulWtKernel + +/** + * Native (FFM) `x · Wᵀ` over a row-major `[out, in]` FP32 weight — the JVM binding of + * + * void skainet_fp32_matmul_wt( + * const float* in, int32_t in_offset, + * const float* w, int32_t w_offset, + * float* out, int32_t out_offset, + * int32_t m, int32_t n, int32_t k); + * + * which the wasm sidecar has served since 0.61.0 and the JVM never bound: `DefaultCpuOpsJvm` + * transposed every dense weight once into `[k, n]` for the GEMM kernels and cached the copy. The C + * kernel computes every output as one contiguous dot product over the weight's own row (NEON or + * auto-vectorised portable body, four rows per tile), threads over output rows through the shared + * row pool, and accumulates in ascending `p` — bit-identical to transpose-then-GEMM. + * + * Two faces. The heap face ([Fp32MatmulWtKernel]) stages both operands off-heap per call, like + * [NativeFp32MatmulKernel]; for a decode step that copy is the weight, so it is the slow one. The + * segment face ([Fp32MemSegMatmulWtKernel]) takes the weight where it lies and copies only the + * `m × k` activation and the `m × n` output, which is what the ops layer uses once it has made a + * weight resident off-heap (the JVM counterpart of the sidecar's resident dense weights). + */ +internal object NativeFp32MatmulWtKernel : Fp32MatmulWtKernel, Fp32MemSegMatmulWtKernel { + + fun isAvailable(): Boolean = handle != null + + override fun matmulWt( + x: FloatArray, xOffset: Int, + w: FloatArray, wOffset: Int, + out: FloatArray, outOffset: Int, + m: Int, n: Int, k: Int, + ) { + require(m >= 0 && n >= 0 && k >= 0) { "NativeFp32MatmulWtKernel: m, n, k must be non-negative; got m=$m n=$n k=$k" } + if (m == 0 || n == 0) return + val mh = handle ?: error("NativeFp32MatmulWtKernel invoked while native library unavailable") + val wFloats = n.toLong() * k + require(wOffset >= 0 && wOffset + wFloats <= w.size) { + "NativeFp32MatmulWtKernel: weight needs $wFloats floats from $wOffset, array has ${w.size}" + } + Arena.ofConfined().use { arena -> + val align = ValueLayout.JAVA_FLOAT.byteAlignment() + val wSeg: MemorySegment = if (wFloats > 0) arena.allocate(wFloats * java.lang.Float.BYTES, align) else MemorySegment.NULL + if (wFloats > 0) MemorySegment.copy(w, wOffset, wSeg, ValueLayout.JAVA_FLOAT, 0L, wFloats.toInt()) + invoke(mh, arena, x, xOffset, wSeg, out, outOffset, m, n, k) + } + } + + override fun matmulWt( + x: FloatArray, xOffset: Int, + weight: MemorySegment, weightByteOffset: Long, + out: FloatArray, outOffset: Int, + m: Int, n: Int, k: Int, + ) { + require(m >= 0 && n >= 0 && k >= 0) { "NativeFp32MatmulWtKernel: m, n, k must be non-negative; got m=$m n=$n k=$k" } + if (m == 0 || n == 0) return + val mh = handle ?: error("NativeFp32MatmulWtKernel invoked while native library unavailable") + val wBytes = n.toLong() * k * java.lang.Float.BYTES + require(weightByteOffset >= 0 && weightByteOffset + wBytes <= weight.byteSize()) { + "NativeFp32MatmulWtKernel: weight segment too small — needs $wBytes bytes from offset " + + "$weightByteOffset, segment is ${weight.byteSize()} bytes" + } + Arena.ofConfined().use { arena -> + invoke(mh, arena, x, xOffset, if (wBytes > 0) weight.asSlice(weightByteOffset) else MemorySegment.NULL, out, outOffset, m, n, k) + } + } + + /** Stage the activation and the output, run, copy the output back. The weight is already a segment. */ + private fun invoke( + mh: MethodHandle, arena: Arena, + x: FloatArray, xOffset: Int, wSeg: MemorySegment, + out: FloatArray, outOffset: Int, + m: Int, n: Int, k: Int, + ) { + val xFloats = m.toLong() * k + val outFloats = m.toLong() * n + require(xOffset >= 0 && xOffset + xFloats <= x.size) { + "NativeFp32MatmulWtKernel: activation needs $xFloats floats from $xOffset, array has ${x.size}" + } + require(outOffset >= 0 && outOffset + outFloats <= out.size) { + "NativeFp32MatmulWtKernel: output needs $outFloats floats from $outOffset, array has ${out.size}" + } + val align = ValueLayout.JAVA_FLOAT.byteAlignment() + val xSeg: MemorySegment = if (xFloats > 0) arena.allocate(xFloats * java.lang.Float.BYTES, align) else MemorySegment.NULL + val outSeg: MemorySegment = arena.allocate(outFloats * java.lang.Float.BYTES, align) + if (xFloats > 0) MemorySegment.copy(x, xOffset, xSeg, ValueLayout.JAVA_FLOAT, 0L, xFloats.toInt()) + mh.invoke(xSeg, 0, wSeg, 0, outSeg, 0, m, n, k) + MemorySegment.copy(outSeg, ValueLayout.JAVA_FLOAT, 0L, out, outOffset, outFloats.toInt()) + } + + private val handle: MethodHandle? by lazy { + val lookup = NativeLibraryLoader.lookup() ?: return@lazy null + val symbol = lookup.find("skainet_fp32_matmul_wt").orElse(null) ?: return@lazy null + val descriptor = FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, // in + ValueLayout.JAVA_INT, // in_offset + ValueLayout.ADDRESS, // w + ValueLayout.JAVA_INT, // w_offset + ValueLayout.ADDRESS, // out + ValueLayout.JAVA_INT, // out_offset + ValueLayout.JAVA_INT, // m + ValueLayout.JAVA_INT, // n + ValueLayout.JAVA_INT, // k + ) + runCatching { Linker.nativeLinker().downcallHandle(symbol, descriptor) }.getOrNull() + } +} diff --git a/skainet-backends/skainet-backend-native-cpu/src/jvmMain/kotlin/sk/ainet/exec/kernel/NativeFusedKernels.kt b/skainet-backends/skainet-backend-native-cpu/src/jvmMain/kotlin/sk/ainet/exec/kernel/NativeFusedKernels.kt new file mode 100644 index 000000000..27d2aa39a --- /dev/null +++ b/skainet-backends/skainet-backend-native-cpu/src/jvmMain/kotlin/sk/ainet/exec/kernel/NativeFusedKernels.kt @@ -0,0 +1,231 @@ +package sk.ainet.exec.kernel + +import java.lang.foreign.Arena +import java.lang.foreign.FunctionDescriptor +import java.lang.foreign.Linker +import java.lang.foreign.MemorySegment +import java.lang.foreign.ValueLayout +import java.lang.invoke.MethodHandle +import sk.ainet.backend.api.kernel.Fp32AttentionKernel +import sk.ainet.backend.api.kernel.Fp32NormKernel +import sk.ainet.backend.api.kernel.Fp32SoftmaxKernel + +/* + * Native (FFM) bindings of the fused dense FP32 row kernels (`f32_fused.c`, SKEEP-007 fused ops): + * softmax rows, layer norm / RMS norm and banded scaled dot-product attention. The same symbols the + * wasm sidecar exports; on the JVM they replace the scalar Kotlin loops of `DefaultCpuOpsBase` + * for these ops (attention there is scalar per head, parallel over heads through the schedule; + * here the C kernel vectorises the dot products and threads over query rows in the row pool). + * Every face stages its operands off-heap per call and copies the output back, like + * [NativeFp32MatmulKernel]; the operands of these ops are activations, so there is nothing to keep + * resident. + */ + +private fun bind(symbol: String, descriptor: FunctionDescriptor): MethodHandle? { + val lookup = NativeLibraryLoader.lookup() ?: return null + val sym = lookup.find(symbol).orElse(null) ?: return null + return runCatching { Linker.nativeLinker().downcallHandle(sym, descriptor) }.getOrNull() +} + +/** [count] floats of [src] from [offset] as a fresh confined segment; `MemorySegment.NULL` for an empty count. */ +private fun stageFloats(arena: Arena, src: FloatArray, offset: Int, count: Long, what: String): MemorySegment { + require(offset >= 0 && offset + count <= src.size) { "$what needs $count floats from $offset, array has ${src.size}" } + if (count == 0L) return MemorySegment.NULL + val seg = arena.allocate(count * java.lang.Float.BYTES, ValueLayout.JAVA_FLOAT.byteAlignment()) + MemorySegment.copy(src, offset, seg, ValueLayout.JAVA_FLOAT, 0L, count.toInt()) + return seg +} + +private fun outSegment(arena: Arena, count: Long): MemorySegment = + arena.allocate(maxOf(count, 1L) * java.lang.Float.BYTES, ValueLayout.JAVA_FLOAT.byteAlignment()) + +private fun readFloats(seg: MemorySegment, dst: FloatArray, offset: Int, count: Long, what: String) { + require(offset >= 0 && offset + count <= dst.size) { "$what needs $count floats from $offset, array has ${dst.size}" } + if (count > 0) MemorySegment.copy(seg, ValueLayout.JAVA_FLOAT, 0L, dst, offset, count.toInt()) +} + +/** `skainet_f32_softmax_rows` through FFM. */ +internal object NativeFp32SoftmaxKernel : Fp32SoftmaxKernel { + + fun isAvailable(): Boolean = handle != null + + override fun softmaxRows(input: FloatArray, inputOffset: Int, out: FloatArray, outOffset: Int, rows: Int, cols: Int, log: Boolean) { + require(rows >= 0 && cols >= 0) { "NativeFp32SoftmaxKernel: rows and cols must be non-negative; got rows=$rows cols=$cols" } + if (rows == 0 || cols == 0) return + val mh = handle ?: error("NativeFp32SoftmaxKernel invoked while native library unavailable") + val n = rows.toLong() * cols + Arena.ofConfined().use { arena -> + val inSeg = stageFloats(arena, input, inputOffset, n, "NativeFp32SoftmaxKernel: input") + val outSeg = outSegment(arena, n) + mh.invoke(inSeg, outSeg, rows, cols, if (log) 1 else 0) + readFloats(outSeg, out, outOffset, n, "NativeFp32SoftmaxKernel: output") + } + } + + private val handle: MethodHandle? by lazy { + bind( + "skainet_f32_softmax_rows", + FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, // in + ValueLayout.ADDRESS, // out + ValueLayout.JAVA_INT, // rows + ValueLayout.JAVA_INT, // cols + ValueLayout.JAVA_INT, // log_form + ), + ) + } +} + +/** `skainet_f32_layer_norm` and `skainet_f32_rms_norm` through FFM. */ +internal object NativeFp32NormKernel : Fp32NormKernel { + + fun isAvailable(): Boolean = layerNormHandle != null && rmsNormHandle != null + + override fun layerNorm( + input: FloatArray, inputOffset: Int, + gamma: FloatArray?, beta: FloatArray?, + out: FloatArray, outOffset: Int, + rows: Int, cols: Int, eps: Float, + ) { + require(rows >= 0 && cols >= 0) { "NativeFp32NormKernel: rows and cols must be non-negative; got rows=$rows cols=$cols" } + if (rows == 0 || cols == 0) return + val mh = layerNormHandle ?: error("NativeFp32NormKernel invoked while native library unavailable") + val n = rows.toLong() * cols + Arena.ofConfined().use { arena -> + val inSeg = stageFloats(arena, input, inputOffset, n, "NativeFp32NormKernel: input") + val gSeg = gamma?.let { stageFloats(arena, it, 0, cols.toLong(), "NativeFp32NormKernel: gamma") } ?: MemorySegment.NULL + val bSeg = beta?.let { stageFloats(arena, it, 0, cols.toLong(), "NativeFp32NormKernel: beta") } ?: MemorySegment.NULL + val outSeg = outSegment(arena, n) + mh.invoke(inSeg, gSeg, bSeg, outSeg, rows, cols, eps) + readFloats(outSeg, out, outOffset, n, "NativeFp32NormKernel: output") + } + } + + override fun rmsNorm( + input: FloatArray, inputOffset: Int, + gamma: FloatArray?, + out: FloatArray, outOffset: Int, + rows: Int, cols: Int, eps: Float, + ) { + require(rows >= 0 && cols >= 0) { "NativeFp32NormKernel: rows and cols must be non-negative; got rows=$rows cols=$cols" } + if (rows == 0 || cols == 0) return + val mh = rmsNormHandle ?: error("NativeFp32NormKernel invoked while native library unavailable") + val n = rows.toLong() * cols + Arena.ofConfined().use { arena -> + val inSeg = stageFloats(arena, input, inputOffset, n, "NativeFp32NormKernel: input") + val gSeg = gamma?.let { stageFloats(arena, it, 0, cols.toLong(), "NativeFp32NormKernel: gamma") } ?: MemorySegment.NULL + val outSeg = outSegment(arena, n) + mh.invoke(inSeg, gSeg, outSeg, rows, cols, eps) + readFloats(outSeg, out, outOffset, n, "NativeFp32NormKernel: output") + } + } + + private val layerNormHandle: MethodHandle? by lazy { + bind( + "skainet_f32_layer_norm", + FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, // in + ValueLayout.ADDRESS, // gamma (nullable) + ValueLayout.ADDRESS, // beta (nullable) + ValueLayout.ADDRESS, // out + ValueLayout.JAVA_INT, // rows + ValueLayout.JAVA_INT, // cols + ValueLayout.JAVA_FLOAT, // eps + ), + ) + } + + private val rmsNormHandle: MethodHandle? by lazy { + bind( + "skainet_f32_rms_norm", + FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, // in + ValueLayout.ADDRESS, // gamma (nullable) + ValueLayout.ADDRESS, // out + ValueLayout.JAVA_INT, // rows + ValueLayout.JAVA_INT, // cols + ValueLayout.JAVA_FLOAT, // eps + ), + ) + } +} + +/** `skainet_f32_sdpa` through FFM. */ +internal object NativeFp32AttentionKernel : Fp32AttentionKernel { + + fun isAvailable(): Boolean = handle != null + + override fun sdpa( + query: FloatArray, key: FloatArray, value: FloatArray, out: FloatArray, + batch: Int, heads: Int, kvHeads: Int, + seqQ: Int, seqKV: Int, headDim: Int, + scale: Float, causal: Boolean, window: Int, rightContext: Int, + keyLengths: IntArray?, + mask: FloatArray?, maskDims: IntArray?, + ) { + require(batch >= 0 && heads >= 0 && kvHeads >= 0 && seqQ >= 0 && seqKV >= 0 && headDim >= 0) { + "NativeFp32AttentionKernel: dimensions must be non-negative" + } + require(kvHeads > 0 && heads % kvHeads == 0) { "NativeFp32AttentionKernel: heads ($heads) must be a multiple of kvHeads ($kvHeads)" } + require(keyLengths == null || keyLengths.size == batch) { "NativeFp32AttentionKernel: keyLengths has ${keyLengths?.size} rows, batch is $batch" } + require((mask == null) == (maskDims == null) && (maskDims == null || maskDims.size == 4)) { + "NativeFp32AttentionKernel: mask and maskDims (4 dims) go together" + } + val outCount = batch.toLong() * heads * seqQ * headDim + require(outCount <= out.size) { "NativeFp32AttentionKernel: output needs $outCount floats, array has ${out.size}" } + if (outCount == 0L) return + val mh = handle ?: error("NativeFp32AttentionKernel invoked while native library unavailable") + val qCount = batch.toLong() * heads * seqQ * headDim + val kvCount = batch.toLong() * kvHeads * seqKV * headDim + Arena.ofConfined().use { arena -> + val qSeg = stageFloats(arena, query, 0, qCount, "NativeFp32AttentionKernel: query") + val kSeg = stageFloats(arena, key, 0, kvCount, "NativeFp32AttentionKernel: key") + val vSeg = stageFloats(arena, value, 0, kvCount, "NativeFp32AttentionKernel: value") + val outSeg = outSegment(arena, outCount) + val lSeg = if (keyLengths != null && batch > 0) { + val seg = arena.allocate(batch.toLong() * Integer.BYTES, ValueLayout.JAVA_INT.byteAlignment()) + MemorySegment.copy(keyLengths, 0, seg, ValueLayout.JAVA_INT, 0L, batch) + seg + } else MemorySegment.NULL + val md = maskDims ?: intArrayOf(1, 1, 1, 1) + val mSeg = if (mask != null) { + stageFloats(arena, mask, 0, md[0].toLong() * md[1] * md[2] * md[3], "NativeFp32AttentionKernel: mask") + } else MemorySegment.NULL + mh.invoke( + qSeg, kSeg, vSeg, outSeg, + batch, heads, kvHeads, seqQ, seqKV, headDim, + scale, if (causal) 1 else 0, window, rightContext, + lSeg, mSeg, md[0], md[1], md[2], md[3], + ) + readFloats(outSeg, out, 0, outCount, "NativeFp32AttentionKernel: output") + } + } + + private val handle: MethodHandle? by lazy { + bind( + "skainet_f32_sdpa", + FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, // q + ValueLayout.ADDRESS, // k + ValueLayout.ADDRESS, // v + ValueLayout.ADDRESS, // out + ValueLayout.JAVA_INT, // batch + ValueLayout.JAVA_INT, // heads + ValueLayout.JAVA_INT, // kv_heads + ValueLayout.JAVA_INT, // seq_q + ValueLayout.JAVA_INT, // seq_kv + ValueLayout.JAVA_INT, // head_dim + ValueLayout.JAVA_FLOAT, // scale + ValueLayout.JAVA_INT, // causal + ValueLayout.JAVA_INT, // window + ValueLayout.JAVA_INT, // right_context + ValueLayout.ADDRESS, // lengths (nullable) + ValueLayout.ADDRESS, // mask (nullable) + ValueLayout.JAVA_INT, // mask_b + ValueLayout.JAVA_INT, // mask_h + ValueLayout.JAVA_INT, // mask_q + ValueLayout.JAVA_INT, // mask_k + ), + ) + } +} diff --git a/skainet-backends/skainet-backend-native-cpu/src/jvmMain/kotlin/sk/ainet/exec/kernel/NativeKernelProvider.kt b/skainet-backends/skainet-backend-native-cpu/src/jvmMain/kotlin/sk/ainet/exec/kernel/NativeKernelProvider.kt index cff68465f..14c929edd 100644 --- a/skainet-backends/skainet-backend-native-cpu/src/jvmMain/kotlin/sk/ainet/exec/kernel/NativeKernelProvider.kt +++ b/skainet-backends/skainet-backend-native-cpu/src/jvmMain/kotlin/sk/ainet/exec/kernel/NativeKernelProvider.kt @@ -3,6 +3,11 @@ package sk.ainet.exec.kernel import sk.ainet.backend.api.kernel.Bf16MatmulKernel import sk.ainet.backend.api.kernel.Fp16MatmulKernel import sk.ainet.backend.api.kernel.Fp32MatmulKernel +import sk.ainet.backend.api.kernel.Fp32MatmulWtKernel +import sk.ainet.backend.api.kernel.Fp32AttentionKernel +import sk.ainet.backend.api.kernel.Fp32MemSegMatmulWtKernel +import sk.ainet.backend.api.kernel.Fp32NormKernel +import sk.ainet.backend.api.kernel.Fp32SoftmaxKernel import sk.ainet.backend.api.kernel.KernelProvider import sk.ainet.backend.api.kernel.MemSegKernelProvider import sk.ainet.backend.api.kernel.Q4KMatmulKernel @@ -82,6 +87,13 @@ import sk.ainet.backend.api.kernel.Q8_0MatmulKernel * Every narrow-float accessor the SPI declares is wired here; a format * served natively on one side and by the JVM fallback on the other looks * like a slow kernel rather than a missing one. + * - Now: the direct `x · Wᵀ` kernel (`skainet_fp32_matmul_wt`) in + * [matmulFp32Wt] and its segment face [matmulFp32WtMemSeg], so a dense + * `[out, in]` weight is multiplied in place instead of transposed and cached. + * - Now: the fused row kernels (`f32_fused.c`) in [softmaxFp32], [normFp32] and + * [attentionFp32] — softmax, layer norm / RMS norm and banded scaled dot-product + * attention in one C pass each, the same symbols the wasm sidecar serves, so the + * ops that were scalar Kotlin on the JVM (attention, the normalisations) run native. */ public object NativeKernelProvider : KernelProvider, MemSegKernelProvider { override val name: String = "native-ffm" @@ -92,6 +104,12 @@ public object NativeKernelProvider : KernelProvider, MemSegKernelProvider { override fun matmulFp32(): Fp32MatmulKernel? = if (NativeFp32MatmulKernel.isAvailable()) NativeFp32MatmulKernel else null + override fun matmulFp32Wt(): Fp32MatmulWtKernel? = + if (NativeFp32MatmulWtKernel.isAvailable()) NativeFp32MatmulWtKernel else null + + override fun matmulFp32WtMemSeg(): Fp32MemSegMatmulWtKernel? = + if (NativeFp32MatmulWtKernel.isAvailable()) NativeFp32MatmulWtKernel else null + override fun matmulQ4K(): Q4KMatmulKernel? = if (NativeQ4KMatmulKernel.isAvailable()) NativeQ4KMatmulKernel else null @@ -121,4 +139,13 @@ public object NativeKernelProvider : KernelProvider, MemSegKernelProvider { override fun matmulQ5_1(): Q5_1MatmulKernel? = if (NativeQ5_1MatmulKernel.isAvailable()) NativeQ5_1MatmulKernel else null + + override fun softmaxFp32(): Fp32SoftmaxKernel? = + if (NativeFp32SoftmaxKernel.isAvailable()) NativeFp32SoftmaxKernel else null + + override fun normFp32(): Fp32NormKernel? = + if (NativeFp32NormKernel.isAvailable()) NativeFp32NormKernel else null + + override fun attentionFp32(): Fp32AttentionKernel? = + if (NativeFp32AttentionKernel.isAvailable()) NativeFp32AttentionKernel else null } diff --git a/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/JvmDirectWeightTransposedTest.kt b/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/JvmDirectWeightTransposedTest.kt new file mode 100644 index 000000000..806622903 --- /dev/null +++ b/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/JvmDirectWeightTransposedTest.kt @@ -0,0 +1,79 @@ +package sk.ainet.exec.kernel + +import kotlin.math.abs +import kotlin.random.Random +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import sk.ainet.backend.api.kernel.KernelRegistry +import sk.ainet.backend.api.kernel.KernelServiceLoader +import sk.ainet.context.DirectCpuExecutionContext +import sk.ainet.lang.tensor.Shape +import sk.ainet.lang.tensor.Tensor +import sk.ainet.lang.tensor.data.MemorySegmentBackedData +import sk.ainet.lang.tensor.data.MemorySegmentTensorDataFactory +import sk.ainet.lang.types.FP32 + +/** + * `x · Wᵀ` on the JVM ops layer with a dense `[out, in]` weight goes through the native direct + * kernel (`skainet_fp32_matmul_wt`) when the FFM provider is loaded — heap weights after one + * resident off-heap copy, segment-backed weights where they lie — and agrees with the + * transpose-then-matmul result it replaces, for every activation rank a `Linear` layer sees. + */ +class JvmDirectWeightTransposedTest { + + @BeforeTest + fun setUp() { + KernelRegistry.clearForTesting() + KernelServiceLoader.installAll() + assertTrue(NativeFp32MatmulWtKernel.isAvailable(), "bundled libskainet_kernels must resolve") + } + + @AfterTest + fun tearDown() = KernelRegistry.clearForTesting() + + private fun assertClose(expected: FloatArray, actual: FloatArray, what: String) { + assertEquals(expected.size, actual.size, "$what size") + var maxAbs = 0f + for (v in expected) maxAbs = maxOf(maxAbs, abs(v)) + val tol = maxAbs * 1e-5f + 1e-6f + for (i in expected.indices) { + assertTrue(abs(expected[i] - actual[i]) <= tol, "$what[$i]: ${actual[i]} vs ${expected[i]} (tol $tol)") + } + } + + private fun check(ctx: DirectCpuExecutionContext, xShape: Shape, n: Int, seed: Int, what: String) { + val rng = Random(seed) + val k = xShape.dimensions.last() + val x: Tensor = ctx.fromFloatArray(xShape, FP32::class, FloatArray(xShape.volume) { rng.nextFloat() - 0.5f }) + val w: Tensor = ctx.fromFloatArray(Shape(n, k), FP32::class, FloatArray(n * k) { rng.nextFloat() - 0.5f }) + val got = ctx.ops.matmulWeightTransposed(x, w) + val expectedShape = if (xShape.rank == 1) Shape(n) else Shape(*(xShape.dimensions.copyOf(xShape.rank - 1) + n)) + assertEquals(expectedShape, got.shape, "$what shape") + val expected = ctx.ops.matmul(x, ctx.ops.transpose(w)).data.copyToFloatArray() + assertClose(expected, got.data.copyToFloatArray(), what) + // A second call reuses the resident weight: same answer. + assertClose(expected, ctx.ops.matmulWeightTransposed(x, w).data.copyToFloatArray(), "$what again") + } + + @Test + fun heap_weights_match_transpose_then_matmul_for_every_activation_rank() { + val ctx = DirectCpuExecutionContext() + assertEquals("native-ffm", KernelRegistry.bestAvailable()?.name, "the FFM provider must win on this runner") + check(ctx, Shape(40), 70, 1, "rank-1") + check(ctx, Shape(3, 40), 70, 2, "rank-2") + check(ctx, Shape(2, 3, 40), 600, 3, "rank-3, threaded rows") + check(ctx, Shape(1, 1), 1, 4, "1×1") + } + + @Test + fun segment_backed_weights_are_read_where_they_lie() { + val ctx = DirectCpuExecutionContext(tensorDataFactory = MemorySegmentTensorDataFactory()) + val w: Tensor = ctx.fromFloatArray(Shape(5, 8), FP32::class, FloatArray(40) { it.toFloat() }) + assertTrue(w.data is MemorySegmentBackedData, "this factory stages tensors off-heap: ${w.data::class.simpleName}") + check(ctx, Shape(4, 32), 48, 5, "segment-backed") + check(ctx, Shape(32), 520, 6, "segment-backed rank-1, threaded rows") + } +} diff --git a/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/KernelSupportMatrixTest.kt b/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/KernelSupportMatrixTest.kt index 84bf97d50..fe0913e70 100644 --- a/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/KernelSupportMatrixTest.kt +++ b/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/KernelSupportMatrixTest.kt @@ -4,6 +4,7 @@ import java.io.File import kotlin.test.AfterTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue import sk.ainet.backend.api.kernel.KernelDispatch import sk.ainet.backend.api.kernel.KernelProvider import sk.ainet.lang.memory.plan.StorageCapabilities @@ -106,6 +107,40 @@ class KernelSupportMatrixTest { Tier("ffm-rowmajor", 100, setOf("JVM"), KernelDispatch.mappedServableEncodings().map { it.name }.toSet()), ) + /** + * Kernel packs (view dispatch): the ternary formats and the direct dense `x · Wᵀ` are not served + * through the `matmul` accessors the tables above introspect — the ternary kernels are + * `ViewKernelPack`s keyed in `KernelDispatch` (`FfmTernaryKernelPackFactory`, the JNI ternary pack, + * `NativeKnTernaryF32Gemv` / `NativeKnTernaryLmhead`, `WasmTernaryKernelPack`), and `x · Wᵀ` is the + * `matmulFp32Wt` accessor the ops layer consults before transposing a weight, and the fused row + * kernels (softmax, layer norm / RMS norm, banded SDPA — `softmaxFp32`, `normFp32`, + * `attentionFp32`) are what the ops layer calls instead of its scalar loops. Hand-declared per + * platform like the Android rows; the JVM cells are gated below against what the FFM provider and + * pack actually expose on this runner. Row names are the encodings as `TensorEncoding.name` spells + * them, plus the dense direct product and the fused ops. + */ + private val packRows = listOf( + TensorEncoding.BITNET_B1_58.name + " (FP32 activations)", + TensorEncoding.BITNET_PLANES.name, + TensorEncoding.TQ2_0.name + " (int8 activations)", + "Float32 x·Wᵀ direct", + "Float32 softmax (fused rows)", + "Float32 layer_norm / rms_norm (fused)", + "Float32 SDPA (fused, banded, GQA)", + ) + + private fun packTiers(): List { + val b158 = packRows[0]; val planes = packRows[1]; val tq2 = packRows[2]; val wt = packRows[3] + val softmax = packRows[4]; val norm = packRows[5]; val sdpa = packRows[6] + return listOf( + Tier("ffm-ternary", 100, setOf("JVM"), setOf(b158, planes)), + Tier("native-ffm", 100, setOf("JVM"), setOf(wt, softmax, norm, sdpa)), + Tier("native-jni", 100, setOf("Android"), setOf(b158, planes, tq2)), + Tier("native-cinterop", 100, setOf("Native·linux", "Native·apple"), setOf(b158, planes)), + Tier("wasm-simd128", 50, setOf("JS/WASM"), setOf(b158, planes, tq2, wt, softmax, norm, sdpa)), + ) + } + private fun best(fmt: String, platform: String, tiers: List): String? = tiers.filter { platform in it.platforms && fmt in it.formats }.maxByOrNull { it.priority }?.name @@ -145,6 +180,15 @@ class KernelSupportMatrixTest { .append(cells.joinToString(", ")).append("}}") sb.append(if (i == residentFormats.lastIndex) "\n" else ",\n") } + sb.append(" ],\n") + val packs = packTiers() + sb.append(" \"packs\": [\n") + packRows.forEachIndexed { i, fmt -> + val cells = platforms.mapNotNull { p -> best(fmt, p, packs)?.let { "\"$p\": \"$it\"" } } + sb.append(" {\"name\": \"").append(fmt).append("\", \"byPlatform\": {") + .append(cells.joinToString(", ")).append("}}") + sb.append(if (i == packRows.lastIndex) "\n" else ",\n") + } sb.append(" ]\n}\n") return sb.toString() } @@ -189,6 +233,19 @@ class KernelSupportMatrixTest { "native-jni-direct row", ) + // Drift gate on the JVM pack rows, where the environment allows it: the FFM provider must + // carry the direct `x · Wᵀ` kernel and the ternary pack both of its kernels. + if (NativeKernelProvider.isAvailable()) { + assertTrue(NativeKernelProvider.matmulFp32Wt() != null, "native-ffm lost its direct x·Wᵀ kernel — update packTiers()") + assertTrue( + NativeKernelProvider.supports("softmax", listOf("Float32")) && NativeKernelProvider.supports("layer_norm", listOf("Float32")) && + NativeKernelProvider.supports("rms_norm", listOf("Float32")) && NativeKernelProvider.supports("sdpa", listOf("Float32")), + "native-ffm lost a fused row kernel — update packTiers()", + ) + assertTrue(NativeTernaryF32GemvKernel.isAvailable() && NativeTernaryLmheadKernel.isAvailable(), + "ffm-ternary lost a kernel — update packTiers()") + } + val jsonText = renderJson(tiers) val outDir = File("build/generated/kernel-support").apply { mkdirs() } File(outDir, "kernel-support.json").writeText(jsonText) diff --git a/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/NativeFp32MatmulWtKernelTest.kt b/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/NativeFp32MatmulWtKernelTest.kt new file mode 100644 index 000000000..78b56d8f4 --- /dev/null +++ b/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/NativeFp32MatmulWtKernelTest.kt @@ -0,0 +1,111 @@ +package sk.ainet.exec.kernel + +import java.lang.foreign.Arena +import java.lang.foreign.MemorySegment +import java.lang.foreign.ValueLayout +import kotlin.math.abs +import kotlin.random.Random +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +/** + * [NativeFp32MatmulWtKernel] — the JVM binding of `skainet_fp32_matmul_wt` — against a plain + * row-dot reference, on both of its faces. The row-pool threshold (512 output rows) is crossed by + * one shape so the threaded partition is covered; per-row arithmetic does not depend on it. + */ +class NativeFp32MatmulWtKernelTest { + + @BeforeTest + fun checkAvailable() { + assertTrue(NativeFp32MatmulWtKernel.isAvailable(), "bundled libskainet_kernels must resolve skainet_fp32_matmul_wt") + } + + private fun reference(x: FloatArray, xOff: Int, w: FloatArray, wOff: Int, m: Int, n: Int, k: Int): FloatArray { + val out = FloatArray(m * n) + for (i in 0 until m) for (o in 0 until n) { + var acc = 0.0 + for (p in 0 until k) acc += x[xOff + i * k + p].toDouble() * w[wOff + o * k + p] + out[i * n + o] = acc.toFloat() + } + return out + } + + private fun assertClose(expected: FloatArray, actual: FloatArray, what: String) { + assertEquals(expected.size, actual.size, "$what size") + var maxAbs = 0f + for (v in expected) maxAbs = maxOf(maxAbs, abs(v)) + val tol = maxAbs * 1e-5f + 1e-6f + for (i in expected.indices) { + assertTrue(abs(expected[i] - actual[i]) <= tol, "$what[$i]: ${actual[i]} vs ${expected[i]} (tol $tol)") + } + } + + @Test + fun heap_face_matches_the_row_dot_reference_on_every_shape() { + val rng = Random(3) + // (m, n, k): a single token; a prefill against a threaded row count with a 4-lane tail; one row of weight. + for ((m, n, k) in listOf(Triple(1, 7, 5), Triple(3, 600, 37), Triple(5, 1, 64))) { + val x = FloatArray(m * k) { rng.nextFloat() - 0.5f } + val w = FloatArray(n * k) { rng.nextFloat() - 0.5f } + val out = FloatArray(m * n) + NativeFp32MatmulWtKernel.matmulWt(x, 0, w, 0, out, 0, m, n, k) + assertClose(reference(x, 0, w, 0, m, n, k), out, "wt $m×$k · ($n×$k)ᵀ") + } + } + + @Test + fun offsets_are_honoured_and_the_rest_of_the_arrays_is_left_alone() { + val rng = Random(5) + val m = 2; val n = 9; val k = 12 + val x = FloatArray(3 + m * k) { rng.nextFloat() } + val w = FloatArray(2 + n * k) { rng.nextFloat() } + val out = FloatArray(1 + m * n + 4) { 7f } + NativeFp32MatmulWtKernel.matmulWt(x, 3, w, 2, out, 1, m, n, k) + assertClose(reference(x, 3, w, 2, m, n, k), out.copyOfRange(1, 1 + m * n), "offset wt") + assertEquals(7f, out[0]) + for (i in 1 + m * n until out.size) assertEquals(7f, out[i], "tail[$i] untouched") + } + + @Test + fun k_zero_zeroes_the_block_and_empty_shapes_are_no_ops() { + val out = FloatArray(6) { 1f } + NativeFp32MatmulWtKernel.matmulWt(FloatArray(0), 0, FloatArray(0), 0, out, 0, 2, 3, 0) + assertContentEquals(FloatArray(6), out) + val untouched = FloatArray(4) { 2f } + NativeFp32MatmulWtKernel.matmulWt(FloatArray(0), 0, FloatArray(8), 0, untouched, 0, 0, 4, 2) + NativeFp32MatmulWtKernel.matmulWt(FloatArray(4), 0, FloatArray(0), 0, untouched, 0, 2, 0, 2) + assertContentEquals(FloatArray(4) { 2f }, untouched) + } + + @Test + fun segment_face_is_bit_identical_to_the_heap_face() { + val rng = Random(11) + val m = 4; val n = 530; val k = 96 + val x = FloatArray(m * k) { rng.nextFloat() - 0.5f } + val w = FloatArray(n * k) { rng.nextFloat() - 0.5f } + val viaHeap = FloatArray(m * n) + NativeFp32MatmulWtKernel.matmulWt(x, 0, w, 0, viaHeap, 0, m, n, k) + Arena.ofConfined().use { arena -> + // The weight sits 16 bytes into a larger segment: the offset is honoured and no copy is made. + val seg = arena.allocate(16L + n.toLong() * k * java.lang.Float.BYTES, 64L) + MemorySegment.copy(w, 0, seg, ValueLayout.JAVA_FLOAT, 16L, n * k) + val viaSegment = FloatArray(m * n) + NativeFp32MatmulWtKernel.matmulWt(x, 0, seg, 16L, viaSegment, 0, m, n, k) + assertContentEquals(viaHeap, viaSegment) + } + assertClose(reference(x, 0, w, 0, m, n, k), viaHeap, "threaded wt") + } + + @Test + fun the_provider_exposes_both_faces() { + assertTrue(NativeKernelProvider.isAvailable()) + assertNotNull(NativeKernelProvider.matmulFp32Wt()) + assertNotNull(NativeKernelProvider.matmulFp32WtMemSeg()) + assertTrue(NativeKernelProvider.supports("matmul_wt", listOf("Float32", "Float32"))) + assertTrue(!ScalarKernelProvider.supports("matmul_wt", listOf("Float32", "Float32")), "the scalar floor has no direct kernel") + } +} diff --git a/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/NativeFusedKernelsTest.kt b/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/NativeFusedKernelsTest.kt new file mode 100644 index 000000000..614c81e86 --- /dev/null +++ b/skainet-backends/skainet-backend-native-cpu/src/jvmTest/kotlin/sk/ainet/exec/kernel/NativeFusedKernelsTest.kt @@ -0,0 +1,219 @@ +package sk.ainet.exec.kernel + +import kotlin.math.abs +import kotlin.math.sqrt +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import sk.ainet.backend.api.kernel.KernelRegistry +import sk.ainet.backend.api.kernel.KernelServiceLoader +import sk.ainet.context.DirectCpuExecutionContext +import sk.ainet.context.schedule.Schedule +import sk.ainet.exec.tensor.ops.DefaultCpuOps +import sk.ainet.lang.nn.normalization.LayerNormalization +import sk.ainet.lang.tensor.Shape +import sk.ainet.lang.tensor.Tensor +import sk.ainet.lang.tensor.data.DenseTensorDataFactory +import sk.ainet.lang.tensor.ops.AttentionMask +import sk.ainet.lang.tensor.ops.FusedNormOps +import sk.ainet.lang.types.DType +import sk.ainet.lang.types.FP32 +import sk.ainet.lang.types.Int32 + +/** + * The fused row kernels on the JVM ops layer (`f32_fused.c` through FFM): softmax / log-softmax, + * the normalisations behind `LayerNormalization` and the `FusedNormOps` hook, and scaled + * dot-product attention with every mask rule — all routed to the native kernels when the FFM + * provider is loaded, and agreeing with the common scalar implementation within the rounding of a + * vectorised accumulation. Routing is proved by bit-identity with the kernel called directly. + */ +class NativeFusedKernelsTest { + + @BeforeTest + fun setUp() { + KernelRegistry.clearForTesting() + KernelServiceLoader.installAll() + assertTrue(NativeFp32SoftmaxKernel.isAvailable() && NativeFp32NormKernel.isAvailable() && NativeFp32AttentionKernel.isAvailable(), + "bundled libskainet_kernels must resolve the fused kernels") + } + + @AfterTest + fun tearDown() = KernelRegistry.clearForTesting() + + /** The common scalar implementation, the reference every tier is checked against. */ + private val reference = DefaultCpuOps(DenseTensorDataFactory(), Schedule.Sequential) + + private fun fill(size: Int, seed: Int, scale: Float = 1f) = FloatArray(size) { i -> (((i * 31 + seed * 17) % 23) - 11) / 11f * scale } + + private fun floats(t: Tensor<*, *>) = t.data.copyToFloatArray() + + private fun assertClose(expected: FloatArray, actual: FloatArray, what: String, rel: Float = 1e-5f, abs: Float = 1e-6f) { + assertEquals(expected.size, actual.size, "$what size") + var maxAbs = 0f + for (v in expected) maxAbs = maxOf(maxAbs, abs(v)) + val tol = maxAbs * rel + abs + for (i in expected.indices) { + assertTrue(abs(expected[i] - actual[i]) <= tol, "$what[$i]: ${actual[i]} vs ${expected[i]} (tol $tol)") + } + } + + @Test + fun softmax_and_log_softmax_run_native_and_match_the_common_loop() { + val ctx = DirectCpuExecutionContext() + val rows = 64; val cols = 256 + val arr = fill(rows * cols, 1, 4f) + val x = ctx.fromFloatArray(Shape(rows, cols), FP32::class, arr) + val sm = floats(ctx.ops.softmax(x, -1)) + val lsm = floats(ctx.ops.logSoftmax(x, -1)) + assertClose(floats(reference.softmax(x, -1)), sm, "softmax") + assertClose(floats(reference.logSoftmax(x, -1)), lsm, "logSoftmax", abs = 1e-5f) + // Routed to the kernel: bit-identical to calling it directly. + val direct = FloatArray(rows * cols) + NativeFp32SoftmaxKernel.softmaxRows(arr, 0, direct, 0, rows, cols, log = false) + assertContentEquals(direct, sm, "softmax went through the native kernel") + NativeFp32SoftmaxKernel.softmaxRows(arr, 0, direct, 0, rows, cols, log = true) + assertContentEquals(direct, lsm, "logSoftmax went through the native kernel") + // Every row sums to one. + for (r in 0 until rows) { + var s = 0.0 + for (c in 0 until cols) s += sm[r * cols + c] + assertTrue(abs(s - 1.0) < 1e-4, "row $r sums to $s") + } + // A small tensor and a non-last dim keep the common loop; the answer is the same. + val small = ctx.fromFloatArray(Shape(4, 8), FP32::class, fill(32, 2)) + assertClose(floats(reference.softmax(small, -1)), floats(ctx.ops.softmax(small, -1)), "small softmax") + assertClose(floats(reference.softmax(x, 0)), floats(ctx.ops.softmax(x, 0)), "softmax over dim 0") + } + + @Test + fun layer_norm_runs_fused_through_the_module_and_matches_the_decomposition() { + val ctx = DirectCpuExecutionContext() + assertTrue(ctx.ops is FusedNormOps, "the JVM ops carry the fused-norm hook") + val rows = 6; val cols = 768 + val xArr = fill(rows * cols, 3, 2f) + val gArr = FloatArray(cols) { 0.5f + (it % 7) * 0.1f } + val bArr = FloatArray(cols) { ((it % 5) - 2) * 0.05f } + val x = ctx.fromFloatArray(Shape(rows, cols), FP32::class, xArr) + val gamma = ctx.fromFloatArray(Shape(cols), FP32::class, gArr) + val beta = ctx.fromFloatArray(Shape(cols), FP32::class, bArr) + val ln = LayerNormalization(intArrayOf(cols), eps = 1e-5, elementwiseAffine = true, name = "ln", initGamma = gamma, initBeta = beta) + val got = floats(ln.forward(x, ctx)) + // The decomposition's arithmetic, in double. + val ref = FloatArray(rows * cols) + for (r in 0 until rows) { + var mean = 0.0 + for (c in 0 until cols) mean += xArr[r * cols + c] + mean /= cols + var v = 0.0 + for (c in 0 until cols) { val d = xArr[r * cols + c] - mean; v += d * d } + v /= cols + val inv = 1.0 / sqrt(v + 1e-5) + for (c in 0 until cols) ref[r * cols + c] = ((xArr[r * cols + c] - mean) * inv * gArr[c] + bArr[c]).toFloat() + } + assertClose(ref, got, "layerNorm", rel = 1e-4f, abs = 1e-4f) + // The hook answered, not the decomposition: bit-identical to the kernel called directly. + val direct = FloatArray(rows * cols) + NativeFp32NormKernel.layerNorm(xArr, 0, gArr, bArr, direct, 0, rows, cols, 1e-5f) + assertContentEquals(direct, got, "layerNorm went through the native kernel") + + // RMS norm through the hook. + val rms = floats(checkNotNull((ctx.ops as FusedNormOps).rmsNorm(x, gamma, 1e-6f, 1)) { "rmsNorm must be served" }) + val rmsRef = FloatArray(rows * cols) + for (r in 0 until rows) { + var ms = 0.0 + for (c in 0 until cols) { val d = xArr[r * cols + c].toDouble(); ms += d * d } + ms /= cols + val inv = 1.0 / sqrt(ms + 1e-6) + for (c in 0 until cols) rmsRef[r * cols + c] = (xArr[r * cols + c] * inv * gArr[c]).toFloat() + } + assertClose(rmsRef, rms, "rmsNorm", rel = 1e-4f, abs = 1e-4f) + + // Two trailing dims and no affine: one row of 32 per [2, 3] prefix. + val x3Arr = fill(2 * 3 * 4 * 8, 5) + val x3 = ctx.fromFloatArray(Shape(2, 3, 4, 8), FP32::class, x3Arr) + val ln2 = LayerNormalization(intArrayOf(4, 8), eps = 1e-5, elementwiseAffine = false, name = "ln2") + val got3 = floats(ln2.forward(x3, ctx)) + val ref3 = FloatArray(x3Arr.size) + for (r in 0 until 6) { + var mean = 0.0 + for (c in 0 until 32) mean += x3Arr[r * 32 + c] + mean /= 32 + var v = 0.0 + for (c in 0 until 32) { val d = x3Arr[r * 32 + c] - mean; v += d * d } + v /= 32 + val inv = 1.0 / sqrt(v + 1e-5) + for (c in 0 until 32) ref3[r * 32 + c] = ((x3Arr[r * 32 + c] - mean) * inv).toFloat() + } + assertClose(ref3, got3, "layerNorm over two dims", rel = 1e-4f, abs = 1e-4f) + } + + private class Inputs(val q: Tensor, val k: Tensor, val v: Tensor) + + private fun inputs(ctx: DirectCpuExecutionContext, batch: Int, heads: Int, kvHeads: Int, seqQ: Int, seqKV: Int, headDim: Int, seed: Int = 0) = Inputs( + ctx.fromFloatArray(Shape(batch, heads, seqQ, headDim), FP32::class, fill(batch * heads * seqQ * headDim, 1 + seed)), + ctx.fromFloatArray(Shape(batch, kvHeads, seqKV, headDim), FP32::class, fill(batch * kvHeads * seqKV * headDim, 2 + seed)), + ctx.fromFloatArray(Shape(batch, kvHeads, seqKV, headDim), FP32::class, fill(batch * kvHeads * seqKV * headDim, 3 + seed)), + ) + + @Suppress("UNCHECKED_CAST") + private fun lengthsTensor(ctx: DirectCpuExecutionContext, lengths: IntArray): Tensor = + ctx.fromIntArray(Shape(lengths.size), Int32::class, lengths) as Tensor + + @Test + fun attention_runs_native_with_every_mask_rule_and_matches_the_common_kernel() { + val ctx = DirectCpuExecutionContext() + val batch = 3 + for ((heads, kvHeads) in listOf(4 to 4, 4 to 2)) for ((seqQ, seqKV) in listOf(6 to 6, 3 to 10, 1 to 7)) for (causal in listOf(false, true)) { + val x = inputs(ctx, batch, heads, kvHeads, seqQ, seqKV, 8) + for (lengths in listOf(null, intArrayOf(seqKV, (seqKV + 1) / 2, 1))) for ((window, rightContext) in listOf(0 to 0, 3 to 0, 4 to 2)) { + val what = "heads=$heads kv=$kvHeads seqQ=$seqQ seqKV=$seqKV causal=$causal lengths=${lengths?.toList()} window=$window rc=$rightContext" + val kl = lengths?.let { lengthsTensor(ctx, it) } + val got = ctx.ops.scaledDotProductAttention(x.q, x.k, x.v, mask = null, scale = 0f, causal = causal, keyLengths = kl, window = window, rightContext = rightContext) + assertEquals(Shape(batch, heads, seqQ, 8), got.shape, what) + val want = reference.scaledDotProductAttention(x.q, x.k, x.v, mask = null, scale = 0f, causal = causal, keyLengths = kl, window = window, rightContext = rightContext) + assertClose(floats(want), floats(got), what, rel = 1e-4f, abs = 1e-5f) + // The structured rules equal the dense -1e30 mask on the fused kernel too. + val dense = ctx.fromFloatArray(Shape(batch, 1, seqQ, seqKV), FP32::class, AttentionMask.additive(batch, seqQ, seqKV, causal, window, rightContext, lengths)) + val masked = ctx.ops.scaledDotProductAttention(x.q, x.k, x.v, mask = dense, scale = 0f, causal = false) + assertClose(floats(got), floats(masked), "$what vs dense mask", rel = 1e-5f, abs = 1e-6f) + } + } + // Mask broadcast shapes: a per-key bias and a [seqQ, seqKV] matrix. + val x = inputs(ctx, 2, 4, 2, 5, 9, 16, seed = 4) + for (mask in listOf( + ctx.fromFloatArray(Shape(1, 1, 1, 9), FP32::class, fill(9, 6)), + ctx.fromFloatArray(Shape(5, 9), FP32::class, fill(45, 7)), + ctx.fromFloatArray(Shape(2, 4, 5, 9), FP32::class, fill(2 * 4 * 5 * 9, 8)), + )) { + val got = ctx.ops.scaledDotProductAttention(x.q, x.k, x.v, mask = mask, scale = 0.3f, causal = true) + val want = reference.scaledDotProductAttention(x.q, x.k, x.v, mask = mask, scale = 0.3f, causal = true) + assertClose(floats(want), floats(got), "mask ${mask.shape}", rel = 1e-4f, abs = 1e-5f) + } + } + + @Test + fun fully_padded_row_stays_finite_and_a_large_call_threads_in_the_native_pool() { + val ctx = DirectCpuExecutionContext() + val p = inputs(ctx, 2, 2, 2, 3, 5, 4) + val out = floats(ctx.ops.scaledDotProductAttention(p.q, p.k, p.v, keyLengths = lengthsTensor(ctx, intArrayOf(0, 5)))) + assertTrue(out.all { it.isFinite() }, "fully padded row produced a non-finite value") + val dense = ctx.fromFloatArray(Shape(2, 1, 3, 5), FP32::class, AttentionMask.additive(2, 3, 5, false, 0, 0, intArrayOf(0, 5))) + assertClose(floats(reference.scaledDotProductAttention(p.q, p.k, p.v, mask = dense)), out, "fully padded", rel = 1e-5f, abs = 1e-6f) + + // Laya's attention shape: 12 heads × 150 queries = 1800 rows, above the row pool's threading threshold. + val x = inputs(ctx, 1, 12, 12, 150, 150, 64, seed = 9) + val got = ctx.ops.scaledDotProductAttention(x.q, x.k, x.v, causal = true) + val want = reference.scaledDotProductAttention(x.q, x.k, x.v, causal = true) + assertClose(floats(want), floats(got), "laya shape", rel = 1e-4f, abs = 1e-5f) + // Routed to the kernel: bit-identical to calling it directly. + val direct = FloatArray(12 * 150 * 64) + NativeFp32AttentionKernel.sdpa( + floats(x.q), floats(x.k), floats(x.v), direct, + 1, 12, 12, 150, 150, 64, (1.0 / sqrt(64.0)).toFloat(), true, 0, 0, null, null, null, + ) + assertContentEquals(direct, floats(got), "attention went through the native kernel") + } +} diff --git a/skainet-backends/skainet-backend-wasm-simd/native/build-wasm.sh b/skainet-backends/skainet-backend-wasm-simd/native/build-wasm.sh index 0c9009de8..29015d920 100644 --- a/skainet-backends/skainet-backend-wasm-simd/native/build-wasm.sh +++ b/skainet-backends/skainet-backend-wasm-simd/native/build-wasm.sh @@ -11,8 +11,9 @@ # # The sources are skainet-backend-native-cpu/native/src — the same files the JVM FFM and Android JNI # packs are built from — compiled with Emscripten. Only the portable (#else) kernel bodies and the -# explicit __wasm_simd128__ bodies are used; NEON, Apple dispatch and the vendored ternary kernels -# are not part of this build. +# explicit __wasm_simd128__ bodies are used; NEON and Apple dispatch are not part of this build. +# The ternary kernels come from the portable branch of skainet_ternary_f32.c (the compile-time LUT +# with its SIMD128 body), not from the vendored NeoGPU file, which needs native pthreads and NEON. # # Toolchain: emsdk, pinned below. `EMSDK_PYTHON` must point at a Python >= 3.10 when the default # python3 is older (pyenv). Usage: @@ -44,11 +45,18 @@ SOURCES=( "$NATIVE/src/q4k_matmul.c" "$NATIVE/src/q5k_matmul.c" "$NATIVE/src/q6k_matmul.c" + "$NATIVE/src/skainet_ternary_f32.c" + "$NATIVE/src/bitnet_gemv.c" + "$NATIVE/src/f32_ops.c" + "$NATIVE/src/f32_fused.c" "$NATIVE/src/skainet_row_threads.c" "$NATIVE/src/skainet_cpu_features.c" ) EXPORTS='_malloc,_free,_skainet_fp32_matmul,_skainet_fp32_matmul_wt,_skainet_bf16_matmul,_skainet_fp16_matmul' +EXPORTS="$EXPORTS,_skainet_ternary_f32_gemv,_skainet_ternary_lmhead_stage1,_skainet_bitnet_gemv_tq2_0" +EXPORTS="$EXPORTS,_skainet_f32_binary,_skainet_f32_unary,_skainet_f32_reduce_all,_skainet_f32_reduce_axis" +EXPORTS="$EXPORTS,_skainet_f32_softmax_rows,_skainet_f32_layer_norm,_skainet_f32_rms_norm,_skainet_f32_sdpa" for f in q4_0 q5_0 q5_1 q8_0 q4k q5k q6k; do EXPORTS="$EXPORTS,_skainet_${f}_matmul,_skainet_${f}_matmul_rm" done diff --git a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/SidecarArtifacts.kt b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/SidecarArtifacts.kt index b33356e50..6eb9308aa 100644 --- a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/SidecarArtifacts.kt +++ b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/SidecarArtifacts.kt @@ -1,13 +1,13 @@ // GENERATED by native/build-wasm.sh (emsdk 4.0.14) -- do not edit. // SKEEP-007 Tier 1: the SIMD128 kernel sidecar, embedded so every consumer of this module carries it. -// sha256 st: c1a6213df16738714532551430ae508eb29154459336c5069bf27be3205ba3f3 -// sha256 mt: 590e0bfe8a35c5fa259305688d3c964c5bf3013fab611b5557dcce65d3b8b097 +// sha256 st: c325c78a06481bc4dcdd55a692a0df2567a7c5f5097c3e5827104abd63e8467f +// sha256 mt: 13d24effa27e4ac16c88b47fdb78ced78656ed821be78ea7bc3d744ef795241b package sk.ainet.exec.wasm internal object SidecarArtifacts { const val EMSDK: String = "4.0.14" - const val ST_MJS: String = "YXN5bmMgZnVuY3Rpb24gY3JlYXRlU2thaW5ldEtlcm5lbHMobW9kdWxlQXJnPXt9KXt2YXIgbW9kdWxlUnRuO3ZhciBNb2R1bGU9bW9kdWxlQXJnO3ZhciBFTlZJUk9OTUVOVF9JU19XRUI9dHlwZW9mIHdpbmRvdz09Im9iamVjdCI7dmFyIEVOVklST05NRU5UX0lTX1dPUktFUj10eXBlb2YgV29ya2VyR2xvYmFsU2NvcGUhPSJ1bmRlZmluZWQiO3ZhciBFTlZJUk9OTUVOVF9JU19OT0RFPXR5cGVvZiBwcm9jZXNzPT0ib2JqZWN0IiYmcHJvY2Vzcy52ZXJzaW9ucz8ubm9kZSYmcHJvY2Vzcy50eXBlIT0icmVuZGVyZXIiO2lmKEVOVklST05NRU5UX0lTX05PREUpe2NvbnN0e2NyZWF0ZVJlcXVpcmV9PWF3YWl0IGltcG9ydCgibW9kdWxlIik7dmFyIHJlcXVpcmU9Y3JlYXRlUmVxdWlyZShpbXBvcnQubWV0YS51cmwuc3RhcnRzV2l0aCgiZmlsZToiKT9pbXBvcnQubWV0YS51cmw6cHJvY2Vzcy5jd2QoKSsiLyIpfXZhciBhcmd1bWVudHNfPVtdO3ZhciB0aGlzUHJvZ3JhbT0iLi90aGlzLnByb2dyYW0iO3ZhciBxdWl0Xz0oc3RhdHVzLHRvVGhyb3cpPT57dGhyb3cgdG9UaHJvd307dmFyIF9zY3JpcHROYW1lPWltcG9ydC5tZXRhLnVybDt2YXIgc2NyaXB0RGlyZWN0b3J5PSIiO2Z1bmN0aW9uIGxvY2F0ZUZpbGUocGF0aCl7aWYoTW9kdWxlWyJsb2NhdGVGaWxlIl0pe3JldHVybiBNb2R1bGVbImxvY2F0ZUZpbGUiXShwYXRoLHNjcmlwdERpcmVjdG9yeSl9cmV0dXJuIHNjcmlwdERpcmVjdG9yeStwYXRofXZhciByZWFkQXN5bmMscmVhZEJpbmFyeTtpZihFTlZJUk9OTUVOVF9JU19OT0RFKXt2YXIgZnM9cmVxdWlyZSgiZnMiKTtpZihfc2NyaXB0TmFtZS5zdGFydHNXaXRoKCJmaWxlOiIpKXtzY3JpcHREaXJlY3Rvcnk9cmVxdWlyZSgicGF0aCIpLmRpcm5hbWUocmVxdWlyZSgidXJsIikuZmlsZVVSTFRvUGF0aChfc2NyaXB0TmFtZSkpKyIvIn1yZWFkQmluYXJ5PWZpbGVuYW1lPT57ZmlsZW5hbWU9aXNGaWxlVVJJKGZpbGVuYW1lKT9uZXcgVVJMKGZpbGVuYW1lKTpmaWxlbmFtZTt2YXIgcmV0PWZzLnJlYWRGaWxlU3luYyhmaWxlbmFtZSk7cmV0dXJuIHJldH07cmVhZEFzeW5jPWFzeW5jKGZpbGVuYW1lLGJpbmFyeT10cnVlKT0+e2ZpbGVuYW1lPWlzRmlsZVVSSShmaWxlbmFtZSk/bmV3IFVSTChmaWxlbmFtZSk6ZmlsZW5hbWU7dmFyIHJldD1mcy5yZWFkRmlsZVN5bmMoZmlsZW5hbWUsYmluYXJ5P3VuZGVmaW5lZDoidXRmOCIpO3JldHVybiByZXR9O2lmKHByb2Nlc3MuYXJndi5sZW5ndGg+MSl7dGhpc1Byb2dyYW09cHJvY2Vzcy5hcmd2WzFdLnJlcGxhY2UoL1xcL2csIi8iKX1hcmd1bWVudHNfPXByb2Nlc3MuYXJndi5zbGljZSgyKTtxdWl0Xz0oc3RhdHVzLHRvVGhyb3cpPT57cHJvY2Vzcy5leGl0Q29kZT1zdGF0dXM7dGhyb3cgdG9UaHJvd319ZWxzZSBpZihFTlZJUk9OTUVOVF9JU19XRUJ8fEVOVklST05NRU5UX0lTX1dPUktFUil7dHJ5e3NjcmlwdERpcmVjdG9yeT1uZXcgVVJMKCIuIixfc2NyaXB0TmFtZSkuaHJlZn1jYXRjaHt9e2lmKEVOVklST05NRU5UX0lTX1dPUktFUil7cmVhZEJpbmFyeT11cmw9Pnt2YXIgeGhyPW5ldyBYTUxIdHRwUmVxdWVzdDt4aHIub3BlbigiR0VUIix1cmwsZmFsc2UpO3hoci5yZXNwb25zZVR5cGU9ImFycmF5YnVmZmVyIjt4aHIuc2VuZChudWxsKTtyZXR1cm4gbmV3IFVpbnQ4QXJyYXkoeGhyLnJlc3BvbnNlKX19cmVhZEFzeW5jPWFzeW5jIHVybD0+e3ZhciByZXNwb25zZT1hd2FpdCBmZXRjaCh1cmwse2NyZWRlbnRpYWxzOiJzYW1lLW9yaWdpbiJ9KTtpZihyZXNwb25zZS5vayl7cmV0dXJuIHJlc3BvbnNlLmFycmF5QnVmZmVyKCl9dGhyb3cgbmV3IEVycm9yKHJlc3BvbnNlLnN0YXR1cysiIDogIityZXNwb25zZS51cmwpfX19ZWxzZXt9dmFyIG91dD1jb25zb2xlLmxvZy5iaW5kKGNvbnNvbGUpO3ZhciBlcnI9Y29uc29sZS5lcnJvci5iaW5kKGNvbnNvbGUpO3ZhciB3YXNtQmluYXJ5O3ZhciBBQk9SVD1mYWxzZTt2YXIgaXNGaWxlVVJJPWZpbGVuYW1lPT5maWxlbmFtZS5zdGFydHNXaXRoKCJmaWxlOi8vIik7dmFyIHJlYWR5UHJvbWlzZVJlc29sdmUscmVhZHlQcm9taXNlUmVqZWN0O3ZhciB3YXNtTWVtb3J5O3ZhciBIRUFQOCxIRUFQVTgsSEVBUDE2LEhFQVBVMTYsSEVBUDMyLEhFQVBVMzIsSEVBUEYzMixIRUFQRjY0O3ZhciBIRUFQNjQsSEVBUFU2NDt2YXIgcnVudGltZUluaXRpYWxpemVkPWZhbHNlO2Z1bmN0aW9uIHVwZGF0ZU1lbW9yeVZpZXdzKCl7dmFyIGI9d2FzbU1lbW9yeS5idWZmZXI7SEVBUDg9bmV3IEludDhBcnJheShiKTtIRUFQMTY9bmV3IEludDE2QXJyYXkoYik7SEVBUFU4PW5ldyBVaW50OEFycmF5KGIpO0hFQVBVMTY9bmV3IFVpbnQxNkFycmF5KGIpO0hFQVAzMj1uZXcgSW50MzJBcnJheShiKTtIRUFQVTMyPW5ldyBVaW50MzJBcnJheShiKTtIRUFQRjMyPW5ldyBGbG9hdDMyQXJyYXkoYik7SEVBUEY2ND1uZXcgRmxvYXQ2NEFycmF5KGIpO0hFQVA2ND1uZXcgQmlnSW50NjRBcnJheShiKTtIRUFQVTY0PW5ldyBCaWdVaW50NjRBcnJheShiKX1mdW5jdGlvbiBwcmVSdW4oKXtpZihNb2R1bGVbInByZVJ1biJdKXtpZih0eXBlb2YgTW9kdWxlWyJwcmVSdW4iXT09ImZ1bmN0aW9uIilNb2R1bGVbInByZVJ1biJdPVtNb2R1bGVbInByZVJ1biJdXTt3aGlsZShNb2R1bGVbInByZVJ1biJdLmxlbmd0aCl7YWRkT25QcmVSdW4oTW9kdWxlWyJwcmVSdW4iXS5zaGlmdCgpKX19Y2FsbFJ1bnRpbWVDYWxsYmFja3Mob25QcmVSdW5zKX1mdW5jdGlvbiBpbml0UnVudGltZSgpe3J1bnRpbWVJbml0aWFsaXplZD10cnVlO3dhc21FeHBvcnRzWyJjIl0oKX1mdW5jdGlvbiBwb3N0UnVuKCl7aWYoTW9kdWxlWyJwb3N0UnVuIl0pe2lmKHR5cGVvZiBNb2R1bGVbInBvc3RSdW4iXT09ImZ1bmN0aW9uIilNb2R1bGVbInBvc3RSdW4iXT1bTW9kdWxlWyJwb3N0UnVuIl1dO3doaWxlKE1vZHVsZVsicG9zdFJ1biJdLmxlbmd0aCl7YWRkT25Qb3N0UnVuKE1vZHVsZVsicG9zdFJ1biJdLnNoaWZ0KCkpfX1jYWxsUnVudGltZUNhbGxiYWNrcyhvblBvc3RSdW5zKX1mdW5jdGlvbiBhYm9ydCh3aGF0KXtNb2R1bGVbIm9uQWJvcnQiXT8uKHdoYXQpO3doYXQ9IkFib3J0ZWQoIit3aGF0KyIpIjtlcnIod2hhdCk7QUJPUlQ9dHJ1ZTt3aGF0Kz0iLiBCdWlsZCB3aXRoIC1zQVNTRVJUSU9OUyBmb3IgbW9yZSBpbmZvLiI7dmFyIGU9bmV3IFdlYkFzc2VtYmx5LlJ1bnRpbWVFcnJvcih3aGF0KTtyZWFkeVByb21pc2VSZWplY3Q/LihlKTt0aHJvdyBlfXZhciB3YXNtQmluYXJ5RmlsZTtmdW5jdGlvbiBmaW5kV2FzbUJpbmFyeSgpe2lmKE1vZHVsZVsibG9jYXRlRmlsZSJdKXtyZXR1cm4gbG9jYXRlRmlsZSgic2thaW5ldF9rZXJuZWxzX3N0Lndhc20iKX10cnl7cmV0dXJuIG5ldyBVUkwoInNrYWluZXRfa2VybmVsc19zdC53YXNtIixpbXBvcnQubWV0YS51cmwpLmhyZWZ9Y2F0Y2goZSl7cmV0dXJuICJza2FpbmV0X2tlcm5lbHNfc3Qud2FzbSJ9fWZ1bmN0aW9uIGdldEJpbmFyeVN5bmMoZmlsZSl7aWYoZmlsZT09d2FzbUJpbmFyeUZpbGUmJndhc21CaW5hcnkpe3JldHVybiBuZXcgVWludDhBcnJheSh3YXNtQmluYXJ5KX1pZihyZWFkQmluYXJ5KXtyZXR1cm4gcmVhZEJpbmFyeShmaWxlKX10aHJvdyJib3RoIGFzeW5jIGFuZCBzeW5jIGZldGNoaW5nIG9mIHRoZSB3YXNtIGZhaWxlZCJ9YXN5bmMgZnVuY3Rpb24gZ2V0V2FzbUJpbmFyeShiaW5hcnlGaWxlKXtpZighd2FzbUJpbmFyeSl7dHJ5e3ZhciByZXNwb25zZT1hd2FpdCByZWFkQXN5bmMoYmluYXJ5RmlsZSk7cmV0dXJuIG5ldyBVaW50OEFycmF5KHJlc3BvbnNlKX1jYXRjaHt9fXJldHVybiBnZXRCaW5hcnlTeW5jKGJpbmFyeUZpbGUpfWFzeW5jIGZ1bmN0aW9uIGluc3RhbnRpYXRlQXJyYXlCdWZmZXIoYmluYXJ5RmlsZSxpbXBvcnRzKXt0cnl7dmFyIGJpbmFyeT1hd2FpdCBnZXRXYXNtQmluYXJ5KGJpbmFyeUZpbGUpO3ZhciBpbnN0YW5jZT1hd2FpdCBXZWJBc3NlbWJseS5pbnN0YW50aWF0ZShiaW5hcnksaW1wb3J0cyk7cmV0dXJuIGluc3RhbmNlfWNhdGNoKHJlYXNvbil7ZXJyKGBmYWlsZWQgdG8gYXN5bmNocm9ub3VzbHkgcHJlcGFyZSB3YXNtOiAke3JlYXNvbn1gKTthYm9ydChyZWFzb24pfX1hc3luYyBmdW5jdGlvbiBpbnN0YW50aWF0ZUFzeW5jKGJpbmFyeSxiaW5hcnlGaWxlLGltcG9ydHMpe2lmKCFiaW5hcnkmJiFFTlZJUk9OTUVOVF9JU19OT0RFKXt0cnl7dmFyIHJlc3BvbnNlPWZldGNoKGJpbmFyeUZpbGUse2NyZWRlbnRpYWxzOiJzYW1lLW9yaWdpbiJ9KTt2YXIgaW5zdGFudGlhdGlvblJlc3VsdD1hd2FpdCBXZWJBc3NlbWJseS5pbnN0YW50aWF0ZVN0cmVhbWluZyhyZXNwb25zZSxpbXBvcnRzKTtyZXR1cm4gaW5zdGFudGlhdGlvblJlc3VsdH1jYXRjaChyZWFzb24pe2Vycihgd2FzbSBzdHJlYW1pbmcgY29tcGlsZSBmYWlsZWQ6ICR7cmVhc29ufWApO2VycigiZmFsbGluZyBiYWNrIHRvIEFycmF5QnVmZmVyIGluc3RhbnRpYXRpb24iKX19cmV0dXJuIGluc3RhbnRpYXRlQXJyYXlCdWZmZXIoYmluYXJ5RmlsZSxpbXBvcnRzKX1mdW5jdGlvbiBnZXRXYXNtSW1wb3J0cygpe3JldHVybnthOndhc21JbXBvcnRzfX1hc3luYyBmdW5jdGlvbiBjcmVhdGVXYXNtKCl7ZnVuY3Rpb24gcmVjZWl2ZUluc3RhbmNlKGluc3RhbmNlLG1vZHVsZSl7d2FzbUV4cG9ydHM9aW5zdGFuY2UuZXhwb3J0czt3YXNtRXhwb3J0cz1hcHBseVNpZ25hdHVyZUNvbnZlcnNpb25zKHdhc21FeHBvcnRzKTt3YXNtTWVtb3J5PXdhc21FeHBvcnRzWyJiIl07TW9kdWxlWyJ3YXNtTWVtb3J5Il09d2FzbU1lbW9yeTt1cGRhdGVNZW1vcnlWaWV3cygpO2Fzc2lnbldhc21FeHBvcnRzKHdhc21FeHBvcnRzKTtyZXR1cm4gd2FzbUV4cG9ydHN9ZnVuY3Rpb24gcmVjZWl2ZUluc3RhbnRpYXRpb25SZXN1bHQocmVzdWx0KXtyZXR1cm4gcmVjZWl2ZUluc3RhbmNlKHJlc3VsdFsiaW5zdGFuY2UiXSl9dmFyIGluZm89Z2V0V2FzbUltcG9ydHMoKTtpZihNb2R1bGVbImluc3RhbnRpYXRlV2FzbSJdKXtyZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUscmVqZWN0KT0+e01vZHVsZVsiaW5zdGFudGlhdGVXYXNtIl0oaW5mbywobW9kLGluc3QpPT57cmVzb2x2ZShyZWNlaXZlSW5zdGFuY2UobW9kLGluc3QpKX0pfSl9d2FzbUJpbmFyeUZpbGU/Pz1maW5kV2FzbUJpbmFyeSgpO3ZhciByZXN1bHQ9YXdhaXQgaW5zdGFudGlhdGVBc3luYyh3YXNtQmluYXJ5LHdhc21CaW5hcnlGaWxlLGluZm8pO3ZhciBleHBvcnRzPXJlY2VpdmVJbnN0YW50aWF0aW9uUmVzdWx0KHJlc3VsdCk7cmV0dXJuIGV4cG9ydHN9Y2xhc3MgRXhpdFN0YXR1c3tuYW1lPSJFeGl0U3RhdHVzIjtjb25zdHJ1Y3RvcihzdGF0dXMpe3RoaXMubWVzc2FnZT1gUHJvZ3JhbSB0ZXJtaW5hdGVkIHdpdGggZXhpdCgke3N0YXR1c30pYDt0aGlzLnN0YXR1cz1zdGF0dXN9fXZhciBjYWxsUnVudGltZUNhbGxiYWNrcz1jYWxsYmFja3M9Pnt3aGlsZShjYWxsYmFja3MubGVuZ3RoPjApe2NhbGxiYWNrcy5zaGlmdCgpKE1vZHVsZSl9fTt2YXIgb25Qb3N0UnVucz1bXTt2YXIgYWRkT25Qb3N0UnVuPWNiPT5vblBvc3RSdW5zLnB1c2goY2IpO3ZhciBvblByZVJ1bnM9W107dmFyIGFkZE9uUHJlUnVuPWNiPT5vblByZVJ1bnMucHVzaChjYik7dmFyIG5vRXhpdFJ1bnRpbWU9dHJ1ZTt2YXIgZ2V0SGVhcE1heD0oKT0+NDI5NDkwMTc2MDt2YXIgYWxpZ25NZW1vcnk9KHNpemUsYWxpZ25tZW50KT0+TWF0aC5jZWlsKHNpemUvYWxpZ25tZW50KSphbGlnbm1lbnQ7dmFyIGdyb3dNZW1vcnk9c2l6ZT0+e3ZhciBvbGRIZWFwU2l6ZT13YXNtTWVtb3J5LmJ1ZmZlci5ieXRlTGVuZ3RoO3ZhciBwYWdlcz0oc2l6ZS1vbGRIZWFwU2l6ZSs2NTUzNSkvNjU1MzZ8MDt0cnl7d2FzbU1lbW9yeS5ncm93KHBhZ2VzKTt1cGRhdGVNZW1vcnlWaWV3cygpO3JldHVybiAxfWNhdGNoKGUpe319O2Z1bmN0aW9uIF9lbXNjcmlwdGVuX3Jlc2l6ZV9oZWFwKHJlcXVlc3RlZFNpemUpe3JlcXVlc3RlZFNpemU+Pj49MDt2YXIgb2xkU2l6ZT1IRUFQVTgubGVuZ3RoO3ZhciBtYXhIZWFwU2l6ZT1nZXRIZWFwTWF4KCk7aWYocmVxdWVzdGVkU2l6ZT5tYXhIZWFwU2l6ZSl7cmV0dXJuIGZhbHNlfWZvcih2YXIgY3V0RG93bj0xO2N1dERvd248PTQ7Y3V0RG93bio9Mil7dmFyIG92ZXJHcm93bkhlYXBTaXplPW9sZFNpemUqKDErLjIvY3V0RG93bik7b3Zlckdyb3duSGVhcFNpemU9TWF0aC5taW4ob3Zlckdyb3duSGVhcFNpemUscmVxdWVzdGVkU2l6ZSsxMDA2NjMyOTYpO3ZhciBuZXdTaXplPU1hdGgubWluKG1heEhlYXBTaXplLGFsaWduTWVtb3J5KE1hdGgubWF4KHJlcXVlc3RlZFNpemUsb3Zlckdyb3duSGVhcFNpemUpLDY1NTM2KSk7dmFyIHJlcGxhY2VtZW50PWdyb3dNZW1vcnkobmV3U2l6ZSk7aWYocmVwbGFjZW1lbnQpe3JldHVybiB0cnVlfX1yZXR1cm4gZmFsc2V9e2lmKE1vZHVsZVsibm9FeGl0UnVudGltZSJdKW5vRXhpdFJ1bnRpbWU9TW9kdWxlWyJub0V4aXRSdW50aW1lIl07aWYoTW9kdWxlWyJwcmludCJdKW91dD1Nb2R1bGVbInByaW50Il07aWYoTW9kdWxlWyJwcmludEVyciJdKWVycj1Nb2R1bGVbInByaW50RXJyIl07aWYoTW9kdWxlWyJ3YXNtQmluYXJ5Il0pd2FzbUJpbmFyeT1Nb2R1bGVbIndhc21CaW5hcnkiXTtpZihNb2R1bGVbImFyZ3VtZW50cyJdKWFyZ3VtZW50c189TW9kdWxlWyJhcmd1bWVudHMiXTtpZihNb2R1bGVbInRoaXNQcm9ncmFtIl0pdGhpc1Byb2dyYW09TW9kdWxlWyJ0aGlzUHJvZ3JhbSJdO2lmKE1vZHVsZVsicHJlSW5pdCJdKXtpZih0eXBlb2YgTW9kdWxlWyJwcmVJbml0Il09PSJmdW5jdGlvbiIpTW9kdWxlWyJwcmVJbml0Il09W01vZHVsZVsicHJlSW5pdCJdXTt3aGlsZShNb2R1bGVbInByZUluaXQiXS5sZW5ndGg+MCl7TW9kdWxlWyJwcmVJbml0Il0uc2hpZnQoKSgpfX19TW9kdWxlWyJ3YXNtTWVtb3J5Il09d2FzbU1lbW9yeTt2YXIgX3NrYWluZXRfZnAzMl9tYXRtdWwsX3NrYWluZXRfZnAzMl9tYXRtdWxfd3QsX3NrYWluZXRfYmYxNl9tYXRtdWwsX3NrYWluZXRfZnAxNl9tYXRtdWwsX3NrYWluZXRfcTRfMF9tYXRtdWwsX3NrYWluZXRfcTRfMF9tYXRtdWxfcm0sX3NrYWluZXRfcTVfMF9tYXRtdWwsX21hbGxvYyxfZnJlZSxfc2thaW5ldF9xNV8wX21hdG11bF9ybSxfc2thaW5ldF9xNV8xX21hdG11bCxfc2thaW5ldF9xNV8xX21hdG11bF9ybSxfc2thaW5ldF9xOF8wX21hdG11bCxfc2thaW5ldF9xOF8wX21hdG11bF9ybSxfc2thaW5ldF9xNGtfbWF0bXVsLF9za2FpbmV0X3E0a19tYXRtdWxfcm0sX3NrYWluZXRfcTVrX21hdG11bCxfc2thaW5ldF9xNWtfbWF0bXVsX3JtLF9za2FpbmV0X3E2a19tYXRtdWwsX3NrYWluZXRfcTZrX21hdG11bF9ybTtmdW5jdGlvbiBhc3NpZ25XYXNtRXhwb3J0cyh3YXNtRXhwb3J0cyl7TW9kdWxlWyJfc2thaW5ldF9mcDMyX21hdG11bCJdPV9za2FpbmV0X2ZwMzJfbWF0bXVsPXdhc21FeHBvcnRzWyJkIl07TW9kdWxlWyJfc2thaW5ldF9mcDMyX21hdG11bF93dCJdPV9za2FpbmV0X2ZwMzJfbWF0bXVsX3d0PXdhc21FeHBvcnRzWyJlIl07TW9kdWxlWyJfc2thaW5ldF9iZjE2X21hdG11bCJdPV9za2FpbmV0X2JmMTZfbWF0bXVsPXdhc21FeHBvcnRzWyJmIl07TW9kdWxlWyJfc2thaW5ldF9mcDE2X21hdG11bCJdPV9za2FpbmV0X2ZwMTZfbWF0bXVsPXdhc21FeHBvcnRzWyJnIl07TW9kdWxlWyJfc2thaW5ldF9xNF8wX21hdG11bCJdPV9za2FpbmV0X3E0XzBfbWF0bXVsPXdhc21FeHBvcnRzWyJoIl07TW9kdWxlWyJfc2thaW5ldF9xNF8wX21hdG11bF9ybSJdPV9za2FpbmV0X3E0XzBfbWF0bXVsX3JtPXdhc21FeHBvcnRzWyJpIl07TW9kdWxlWyJfc2thaW5ldF9xNV8wX21hdG11bCJdPV9za2FpbmV0X3E1XzBfbWF0bXVsPXdhc21FeHBvcnRzWyJqIl07TW9kdWxlWyJfbWFsbG9jIl09X21hbGxvYz13YXNtRXhwb3J0c1siayJdO01vZHVsZVsiX2ZyZWUiXT1fZnJlZT13YXNtRXhwb3J0c1sibCJdO01vZHVsZVsiX3NrYWluZXRfcTVfMF9tYXRtdWxfcm0iXT1fc2thaW5ldF9xNV8wX21hdG11bF9ybT13YXNtRXhwb3J0c1sibSJdO01vZHVsZVsiX3NrYWluZXRfcTVfMV9tYXRtdWwiXT1fc2thaW5ldF9xNV8xX21hdG11bD13YXNtRXhwb3J0c1sibiJdO01vZHVsZVsiX3NrYWluZXRfcTVfMV9tYXRtdWxfcm0iXT1fc2thaW5ldF9xNV8xX21hdG11bF9ybT13YXNtRXhwb3J0c1sibyJdO01vZHVsZVsiX3NrYWluZXRfcThfMF9tYXRtdWwiXT1fc2thaW5ldF9xOF8wX21hdG11bD13YXNtRXhwb3J0c1sicCJdO01vZHVsZVsiX3NrYWluZXRfcThfMF9tYXRtdWxfcm0iXT1fc2thaW5ldF9xOF8wX21hdG11bF9ybT13YXNtRXhwb3J0c1sicSJdO01vZHVsZVsiX3NrYWluZXRfcTRrX21hdG11bCJdPV9za2FpbmV0X3E0a19tYXRtdWw9d2FzbUV4cG9ydHNbInIiXTtNb2R1bGVbIl9za2FpbmV0X3E0a19tYXRtdWxfcm0iXT1fc2thaW5ldF9xNGtfbWF0bXVsX3JtPXdhc21FeHBvcnRzWyJzIl07TW9kdWxlWyJfc2thaW5ldF9xNWtfbWF0bXVsIl09X3NrYWluZXRfcTVrX21hdG11bD13YXNtRXhwb3J0c1sidCJdO01vZHVsZVsiX3NrYWluZXRfcTVrX21hdG11bF9ybSJdPV9za2FpbmV0X3E1a19tYXRtdWxfcm09d2FzbUV4cG9ydHNbInUiXTtNb2R1bGVbIl9za2FpbmV0X3E2a19tYXRtdWwiXT1fc2thaW5ldF9xNmtfbWF0bXVsPXdhc21FeHBvcnRzWyJ2Il07TW9kdWxlWyJfc2thaW5ldF9xNmtfbWF0bXVsX3JtIl09X3NrYWluZXRfcTZrX21hdG11bF9ybT13YXNtRXhwb3J0c1sidyJdfXZhciB3YXNtSW1wb3J0cz17YTpfZW1zY3JpcHRlbl9yZXNpemVfaGVhcH07ZnVuY3Rpb24gYXBwbHlTaWduYXR1cmVDb252ZXJzaW9ucyh3YXNtRXhwb3J0cyl7d2FzbUV4cG9ydHM9T2JqZWN0LmFzc2lnbih7fSx3YXNtRXhwb3J0cyk7dmFyIG1ha2VXcmFwcGVyX3BwPWY9PmEwPT5mKGEwKT4+PjA7dmFyIG1ha2VXcmFwcGVyX3A9Zj0+KCk9PmYoKT4+PjA7d2FzbUV4cG9ydHNbImsiXT1tYWtlV3JhcHBlcl9wcCh3YXNtRXhwb3J0c1siayJdKTt3YXNtRXhwb3J0c1siX2Vtc2NyaXB0ZW5fc3RhY2tfYWxsb2MiXT1tYWtlV3JhcHBlcl9wcCh3YXNtRXhwb3J0c1siX2Vtc2NyaXB0ZW5fc3RhY2tfYWxsb2MiXSk7d2FzbUV4cG9ydHNbImVtc2NyaXB0ZW5fc3RhY2tfZ2V0X2N1cnJlbnQiXT1tYWtlV3JhcHBlcl9wKHdhc21FeHBvcnRzWyJlbXNjcmlwdGVuX3N0YWNrX2dldF9jdXJyZW50Il0pO3JldHVybiB3YXNtRXhwb3J0c31mdW5jdGlvbiBydW4oKXtwcmVSdW4oKTtmdW5jdGlvbiBkb1J1bigpe01vZHVsZVsiY2FsbGVkUnVuIl09dHJ1ZTtpZihBQk9SVClyZXR1cm47aW5pdFJ1bnRpbWUoKTtyZWFkeVByb21pc2VSZXNvbHZlPy4oTW9kdWxlKTtNb2R1bGVbIm9uUnVudGltZUluaXRpYWxpemVkIl0/LigpO3Bvc3RSdW4oKX1pZihNb2R1bGVbInNldFN0YXR1cyJdKXtNb2R1bGVbInNldFN0YXR1cyJdKCJSdW5uaW5nLi4uIik7c2V0VGltZW91dCgoKT0+e3NldFRpbWVvdXQoKCk9Pk1vZHVsZVsic2V0U3RhdHVzIl0oIiIpLDEpO2RvUnVuKCl9LDEpfWVsc2V7ZG9SdW4oKX19dmFyIHdhc21FeHBvcnRzO3dhc21FeHBvcnRzPWF3YWl0IChjcmVhdGVXYXNtKCkpO3J1bigpO2lmKHJ1bnRpbWVJbml0aWFsaXplZCl7bW9kdWxlUnRuPU1vZHVsZX1lbHNle21vZHVsZVJ0bj1uZXcgUHJvbWlzZSgocmVzb2x2ZSxyZWplY3QpPT57cmVhZHlQcm9taXNlUmVzb2x2ZT1yZXNvbHZlO3JlYWR5UHJvbWlzZVJlamVjdD1yZWplY3R9KX0KO3JldHVybiBtb2R1bGVSdG59ZXhwb3J0IGRlZmF1bHQgY3JlYXRlU2thaW5ldEtlcm5lbHM7Cg==" - const val ST_WASM: String = "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" - const val MT_MJS: String = "YXN5bmMgZnVuY3Rpb24gY3JlYXRlU2thaW5ldEtlcm5lbHMobW9kdWxlQXJnPXt9KXt2YXIgbW9kdWxlUnRuO3ZhciBNb2R1bGU9bW9kdWxlQXJnO3ZhciBFTlZJUk9OTUVOVF9JU19XRUI9dHlwZW9mIHdpbmRvdz09Im9iamVjdCI7dmFyIEVOVklST05NRU5UX0lTX1dPUktFUj10eXBlb2YgV29ya2VyR2xvYmFsU2NvcGUhPSJ1bmRlZmluZWQiO3ZhciBFTlZJUk9OTUVOVF9JU19OT0RFPXR5cGVvZiBwcm9jZXNzPT0ib2JqZWN0IiYmcHJvY2Vzcy52ZXJzaW9ucz8ubm9kZSYmcHJvY2Vzcy50eXBlIT0icmVuZGVyZXIiO3ZhciBFTlZJUk9OTUVOVF9JU19QVEhSRUFEPUVOVklST05NRU5UX0lTX1dPUktFUiYmc2VsZi5uYW1lPy5zdGFydHNXaXRoKCJlbS1wdGhyZWFkIik7aWYoRU5WSVJPTk1FTlRfSVNfTk9ERSl7Y29uc3R7Y3JlYXRlUmVxdWlyZX09YXdhaXQgaW1wb3J0KCJtb2R1bGUiKTt2YXIgcmVxdWlyZT1jcmVhdGVSZXF1aXJlKGltcG9ydC5tZXRhLnVybC5zdGFydHNXaXRoKCJmaWxlOiIpP2ltcG9ydC5tZXRhLnVybDpwcm9jZXNzLmN3ZCgpKyIvIik7dmFyIHdvcmtlcl90aHJlYWRzPXJlcXVpcmUoIndvcmtlcl90aHJlYWRzIik7Z2xvYmFsLldvcmtlcj13b3JrZXJfdGhyZWFkcy5Xb3JrZXI7RU5WSVJPTk1FTlRfSVNfV09SS0VSPSF3b3JrZXJfdGhyZWFkcy5pc01haW5UaHJlYWQ7RU5WSVJPTk1FTlRfSVNfUFRIUkVBRD1FTlZJUk9OTUVOVF9JU19XT1JLRVImJndvcmtlcl90aHJlYWRzWyJ3b3JrZXJEYXRhIl09PSJlbS1wdGhyZWFkIn12YXIgYXJndW1lbnRzXz1bXTt2YXIgdGhpc1Byb2dyYW09Ii4vdGhpcy5wcm9ncmFtIjt2YXIgcXVpdF89KHN0YXR1cyx0b1Rocm93KT0+e3Rocm93IHRvVGhyb3d9O3ZhciBfc2NyaXB0TmFtZT1pbXBvcnQubWV0YS51cmw7dmFyIHNjcmlwdERpcmVjdG9yeT0iIjtmdW5jdGlvbiBsb2NhdGVGaWxlKHBhdGgpe2lmKE1vZHVsZVsibG9jYXRlRmlsZSJdKXtyZXR1cm4gTW9kdWxlWyJsb2NhdGVGaWxlIl0ocGF0aCxzY3JpcHREaXJlY3RvcnkpfXJldHVybiBzY3JpcHREaXJlY3RvcnkrcGF0aH12YXIgcmVhZEFzeW5jLHJlYWRCaW5hcnk7aWYoRU5WSVJPTk1FTlRfSVNfTk9ERSl7dmFyIGZzPXJlcXVpcmUoImZzIik7aWYoX3NjcmlwdE5hbWUuc3RhcnRzV2l0aCgiZmlsZToiKSl7c2NyaXB0RGlyZWN0b3J5PXJlcXVpcmUoInBhdGgiKS5kaXJuYW1lKHJlcXVpcmUoInVybCIpLmZpbGVVUkxUb1BhdGgoX3NjcmlwdE5hbWUpKSsiLyJ9cmVhZEJpbmFyeT1maWxlbmFtZT0+e2ZpbGVuYW1lPWlzRmlsZVVSSShmaWxlbmFtZSk/bmV3IFVSTChmaWxlbmFtZSk6ZmlsZW5hbWU7dmFyIHJldD1mcy5yZWFkRmlsZVN5bmMoZmlsZW5hbWUpO3JldHVybiByZXR9O3JlYWRBc3luYz1hc3luYyhmaWxlbmFtZSxiaW5hcnk9dHJ1ZSk9PntmaWxlbmFtZT1pc0ZpbGVVUkkoZmlsZW5hbWUpP25ldyBVUkwoZmlsZW5hbWUpOmZpbGVuYW1lO3ZhciByZXQ9ZnMucmVhZEZpbGVTeW5jKGZpbGVuYW1lLGJpbmFyeT91bmRlZmluZWQ6InV0ZjgiKTtyZXR1cm4gcmV0fTtpZihwcm9jZXNzLmFyZ3YubGVuZ3RoPjEpe3RoaXNQcm9ncmFtPXByb2Nlc3MuYXJndlsxXS5yZXBsYWNlKC9cXC9nLCIvIil9YXJndW1lbnRzXz1wcm9jZXNzLmFyZ3Yuc2xpY2UoMik7cXVpdF89KHN0YXR1cyx0b1Rocm93KT0+e3Byb2Nlc3MuZXhpdENvZGU9c3RhdHVzO3Rocm93IHRvVGhyb3d9fWVsc2UgaWYoRU5WSVJPTk1FTlRfSVNfV0VCfHxFTlZJUk9OTUVOVF9JU19XT1JLRVIpe3RyeXtzY3JpcHREaXJlY3Rvcnk9bmV3IFVSTCgiLiIsX3NjcmlwdE5hbWUpLmhyZWZ9Y2F0Y2h7fWlmKCFFTlZJUk9OTUVOVF9JU19OT0RFKXtpZihFTlZJUk9OTUVOVF9JU19XT1JLRVIpe3JlYWRCaW5hcnk9dXJsPT57dmFyIHhocj1uZXcgWE1MSHR0cFJlcXVlc3Q7eGhyLm9wZW4oIkdFVCIsdXJsLGZhbHNlKTt4aHIucmVzcG9uc2VUeXBlPSJhcnJheWJ1ZmZlciI7eGhyLnNlbmQobnVsbCk7cmV0dXJuIG5ldyBVaW50OEFycmF5KHhoci5yZXNwb25zZSl9fXJlYWRBc3luYz1hc3luYyB1cmw9Pnt2YXIgcmVzcG9uc2U9YXdhaXQgZmV0Y2godXJsLHtjcmVkZW50aWFsczoic2FtZS1vcmlnaW4ifSk7aWYocmVzcG9uc2Uub2spe3JldHVybiByZXNwb25zZS5hcnJheUJ1ZmZlcigpfXRocm93IG5ldyBFcnJvcihyZXNwb25zZS5zdGF0dXMrIiA6ICIrcmVzcG9uc2UudXJsKX19fWVsc2V7fXZhciBkZWZhdWx0UHJpbnQ9Y29uc29sZS5sb2cuYmluZChjb25zb2xlKTt2YXIgZGVmYXVsdFByaW50RXJyPWNvbnNvbGUuZXJyb3IuYmluZChjb25zb2xlKTtpZihFTlZJUk9OTUVOVF9JU19OT0RFKXt2YXIgdXRpbHM9cmVxdWlyZSgidXRpbCIpO3ZhciBzdHJpbmdpZnk9YT0+dHlwZW9mIGE9PSJvYmplY3QiP3V0aWxzLmluc3BlY3QoYSk6YTtkZWZhdWx0UHJpbnQ9KC4uLmFyZ3MpPT5mcy53cml0ZVN5bmMoMSxhcmdzLm1hcChzdHJpbmdpZnkpLmpvaW4oIiAiKSsiXG4iKTtkZWZhdWx0UHJpbnRFcnI9KC4uLmFyZ3MpPT5mcy53cml0ZVN5bmMoMixhcmdzLm1hcChzdHJpbmdpZnkpLmpvaW4oIiAiKSsiXG4iKX12YXIgb3V0PWRlZmF1bHRQcmludDt2YXIgZXJyPWRlZmF1bHRQcmludEVycjt2YXIgd2FzbUJpbmFyeTt2YXIgd2FzbU1vZHVsZTt2YXIgQUJPUlQ9ZmFsc2U7dmFyIEVYSVRTVEFUVVM7dmFyIGlzRmlsZVVSST1maWxlbmFtZT0+ZmlsZW5hbWUuc3RhcnRzV2l0aCgiZmlsZTovLyIpO2Z1bmN0aW9uIGdyb3dNZW1WaWV3cygpe2lmKHdhc21NZW1vcnkuYnVmZmVyIT1IRUFQOC5idWZmZXIpe3VwZGF0ZU1lbW9yeVZpZXdzKCl9fXZhciByZWFkeVByb21pc2VSZXNvbHZlLHJlYWR5UHJvbWlzZVJlamVjdDtpZihFTlZJUk9OTUVOVF9JU19OT0RFJiZFTlZJUk9OTUVOVF9JU19QVEhSRUFEKXt2YXIgcGFyZW50UG9ydD13b3JrZXJfdGhyZWFkc1sicGFyZW50UG9ydCJdO3BhcmVudFBvcnQub24oIm1lc3NhZ2UiLG1zZz0+Z2xvYmFsLm9ubWVzc2FnZT8uKHtkYXRhOm1zZ30pKTtPYmplY3QuYXNzaWduKGdsb2JhbFRoaXMse3NlbGY6Z2xvYmFsLHBvc3RNZXNzYWdlOm1zZz0+cGFyZW50UG9ydFsicG9zdE1lc3NhZ2UiXShtc2cpfSk7cHJvY2Vzcy5vbigidW5jYXVnaHRFeGNlcHRpb24iLGVycj0+e3Bvc3RNZXNzYWdlKHtjbWQ6InVuY2F1Z2h0RXhjZXB0aW9uIixlcnJvcjplcnJ9KTtwcm9jZXNzLmV4aXQoMSl9KX12YXIgc3RhcnRXb3JrZXI7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7dmFyIGluaXRpYWxpemVkSlM9ZmFsc2U7c2VsZi5vbnVuaGFuZGxlZHJlamVjdGlvbj1lPT57dGhyb3cgZS5yZWFzb258fGV9O2Z1bmN0aW9uIGhhbmRsZU1lc3NhZ2UoZSl7dHJ5e3ZhciBtc2dEYXRhPWVbImRhdGEiXTt2YXIgY21kPW1zZ0RhdGEuY21kO2lmKGNtZD09PSJsb2FkIil7bGV0IG1lc3NhZ2VRdWV1ZT1bXTtzZWxmLm9ubWVzc2FnZT1lPT5tZXNzYWdlUXVldWUucHVzaChlKTtzdGFydFdvcmtlcj0oKT0+e3Bvc3RNZXNzYWdlKHtjbWQ6ImxvYWRlZCJ9KTtmb3IobGV0IG1zZyBvZiBtZXNzYWdlUXVldWUpe2hhbmRsZU1lc3NhZ2UobXNnKX1zZWxmLm9ubWVzc2FnZT1oYW5kbGVNZXNzYWdlfTtmb3IoY29uc3QgaGFuZGxlciBvZiBtc2dEYXRhLmhhbmRsZXJzKXtpZighTW9kdWxlW2hhbmRsZXJdfHxNb2R1bGVbaGFuZGxlcl0ucHJveHkpe01vZHVsZVtoYW5kbGVyXT0oLi4uYXJncyk9Pntwb3N0TWVzc2FnZSh7Y21kOiJjYWxsSGFuZGxlciIsaGFuZGxlcixhcmdzfSl9O2lmKGhhbmRsZXI9PSJwcmludCIpb3V0PU1vZHVsZVtoYW5kbGVyXTtpZihoYW5kbGVyPT0icHJpbnRFcnIiKWVycj1Nb2R1bGVbaGFuZGxlcl19fXdhc21NZW1vcnk9bXNnRGF0YS53YXNtTWVtb3J5O3VwZGF0ZU1lbW9yeVZpZXdzKCk7d2FzbU1vZHVsZT1tc2dEYXRhLndhc21Nb2R1bGU7Y3JlYXRlV2FzbSgpO3J1bigpfWVsc2UgaWYoY21kPT09InJ1biIpe2VzdGFibGlzaFN0YWNrU3BhY2UobXNnRGF0YS5wdGhyZWFkX3B0cik7X19lbXNjcmlwdGVuX3RocmVhZF9pbml0KG1zZ0RhdGEucHRocmVhZF9wdHIsMCwwLDEsMCwwKTtQVGhyZWFkLnRocmVhZEluaXRUTFMoKTtfX2Vtc2NyaXB0ZW5fdGhyZWFkX21haWxib3hfYXdhaXQobXNnRGF0YS5wdGhyZWFkX3B0cik7aWYoIWluaXRpYWxpemVkSlMpe2luaXRpYWxpemVkSlM9dHJ1ZX10cnl7aW52b2tlRW50cnlQb2ludChtc2dEYXRhLnN0YXJ0X3JvdXRpbmUsbXNnRGF0YS5hcmcpfWNhdGNoKGV4KXtpZihleCE9InVud2luZCIpe3Rocm93IGV4fX19ZWxzZSBpZihtc2dEYXRhLnRhcmdldD09PSJzZXRpbW1lZGlhdGUiKXt9ZWxzZSBpZihjbWQ9PT0iY2hlY2tNYWlsYm94Iil7aWYoaW5pdGlhbGl6ZWRKUyl7Y2hlY2tNYWlsYm94KCl9fWVsc2UgaWYoY21kKXtlcnIoYHdvcmtlcjogcmVjZWl2ZWQgdW5rbm93biBjb21tYW5kICR7Y21kfWApO2Vycihtc2dEYXRhKX19Y2F0Y2goZXgpe19fZW1zY3JpcHRlbl90aHJlYWRfY3Jhc2hlZCgpO3Rocm93IGV4fX1zZWxmLm9ubWVzc2FnZT1oYW5kbGVNZXNzYWdlfXZhciB3YXNtTWVtb3J5O3ZhciBIRUFQOCxIRUFQVTgsSEVBUDE2LEhFQVBVMTYsSEVBUDMyLEhFQVBVMzIsSEVBUEYzMixIRUFQRjY0O3ZhciBIRUFQNjQsSEVBUFU2NDt2YXIgcnVudGltZUluaXRpYWxpemVkPWZhbHNlO2Z1bmN0aW9uIHVwZGF0ZU1lbW9yeVZpZXdzKCl7dmFyIGI9d2FzbU1lbW9yeS5idWZmZXI7SEVBUDg9bmV3IEludDhBcnJheShiKTtIRUFQMTY9bmV3IEludDE2QXJyYXkoYik7SEVBUFU4PW5ldyBVaW50OEFycmF5KGIpO0hFQVBVMTY9bmV3IFVpbnQxNkFycmF5KGIpO0hFQVAzMj1uZXcgSW50MzJBcnJheShiKTtIRUFQVTMyPW5ldyBVaW50MzJBcnJheShiKTtIRUFQRjMyPW5ldyBGbG9hdDMyQXJyYXkoYik7SEVBUEY2ND1uZXcgRmxvYXQ2NEFycmF5KGIpO0hFQVA2ND1uZXcgQmlnSW50NjRBcnJheShiKTtIRUFQVTY0PW5ldyBCaWdVaW50NjRBcnJheShiKX1mdW5jdGlvbiBpbml0TWVtb3J5KCl7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7cmV0dXJufWlmKE1vZHVsZVsid2FzbU1lbW9yeSJdKXt3YXNtTWVtb3J5PU1vZHVsZVsid2FzbU1lbW9yeSJdfWVsc2V7dmFyIElOSVRJQUxfTUVNT1JZPU1vZHVsZVsiSU5JVElBTF9NRU1PUlkiXXx8NjcxMDg4NjQ7d2FzbU1lbW9yeT1uZXcgV2ViQXNzZW1ibHkuTWVtb3J5KHtpbml0aWFsOklOSVRJQUxfTUVNT1JZLzY1NTM2LG1heGltdW06NjU1MzYsc2hhcmVkOnRydWV9KX11cGRhdGVNZW1vcnlWaWV3cygpfWZ1bmN0aW9uIHByZVJ1bigpe2lmKE1vZHVsZVsicHJlUnVuIl0pe2lmKHR5cGVvZiBNb2R1bGVbInByZVJ1biJdPT0iZnVuY3Rpb24iKU1vZHVsZVsicHJlUnVuIl09W01vZHVsZVsicHJlUnVuIl1dO3doaWxlKE1vZHVsZVsicHJlUnVuIl0ubGVuZ3RoKXthZGRPblByZVJ1bihNb2R1bGVbInByZVJ1biJdLnNoaWZ0KCkpfX1jYWxsUnVudGltZUNhbGxiYWNrcyhvblByZVJ1bnMpfWZ1bmN0aW9uIGluaXRSdW50aW1lKCl7cnVudGltZUluaXRpYWxpemVkPXRydWU7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRClyZXR1cm4gc3RhcnRXb3JrZXIoKTt3YXNtRXhwb3J0c1sibyJdKCl9ZnVuY3Rpb24gcG9zdFJ1bigpe2lmKEVOVklST05NRU5UX0lTX1BUSFJFQUQpe3JldHVybn1pZihNb2R1bGVbInBvc3RSdW4iXSl7aWYodHlwZW9mIE1vZHVsZVsicG9zdFJ1biJdPT0iZnVuY3Rpb24iKU1vZHVsZVsicG9zdFJ1biJdPVtNb2R1bGVbInBvc3RSdW4iXV07d2hpbGUoTW9kdWxlWyJwb3N0UnVuIl0ubGVuZ3RoKXthZGRPblBvc3RSdW4oTW9kdWxlWyJwb3N0UnVuIl0uc2hpZnQoKSl9fWNhbGxSdW50aW1lQ2FsbGJhY2tzKG9uUG9zdFJ1bnMpfWZ1bmN0aW9uIGFib3J0KHdoYXQpe01vZHVsZVsib25BYm9ydCJdPy4od2hhdCk7d2hhdD0iQWJvcnRlZCgiK3doYXQrIikiO2Vycih3aGF0KTtBQk9SVD10cnVlO3doYXQrPSIuIEJ1aWxkIHdpdGggLXNBU1NFUlRJT05TIGZvciBtb3JlIGluZm8uIjt2YXIgZT1uZXcgV2ViQXNzZW1ibHkuUnVudGltZUVycm9yKHdoYXQpO3JlYWR5UHJvbWlzZVJlamVjdD8uKGUpO3Rocm93IGV9dmFyIHdhc21CaW5hcnlGaWxlO2Z1bmN0aW9uIGZpbmRXYXNtQmluYXJ5KCl7aWYoTW9kdWxlWyJsb2NhdGVGaWxlIl0pe3JldHVybiBsb2NhdGVGaWxlKCJza2FpbmV0X2tlcm5lbHNfbXQud2FzbSIpfXRyeXtyZXR1cm4gbmV3IFVSTCgic2thaW5ldF9rZXJuZWxzX210Lndhc20iLGltcG9ydC5tZXRhLnVybCkuaHJlZn1jYXRjaChlKXtyZXR1cm4gInNrYWluZXRfa2VybmVsc19tdC53YXNtIn19ZnVuY3Rpb24gZ2V0QmluYXJ5U3luYyhmaWxlKXtpZihmaWxlPT13YXNtQmluYXJ5RmlsZSYmd2FzbUJpbmFyeSl7cmV0dXJuIG5ldyBVaW50OEFycmF5KHdhc21CaW5hcnkpfWlmKHJlYWRCaW5hcnkpe3JldHVybiByZWFkQmluYXJ5KGZpbGUpfXRocm93ImJvdGggYXN5bmMgYW5kIHN5bmMgZmV0Y2hpbmcgb2YgdGhlIHdhc20gZmFpbGVkIn1hc3luYyBmdW5jdGlvbiBnZXRXYXNtQmluYXJ5KGJpbmFyeUZpbGUpe2lmKCF3YXNtQmluYXJ5KXt0cnl7dmFyIHJlc3BvbnNlPWF3YWl0IHJlYWRBc3luYyhiaW5hcnlGaWxlKTtyZXR1cm4gbmV3IFVpbnQ4QXJyYXkocmVzcG9uc2UpfWNhdGNoe319cmV0dXJuIGdldEJpbmFyeVN5bmMoYmluYXJ5RmlsZSl9YXN5bmMgZnVuY3Rpb24gaW5zdGFudGlhdGVBcnJheUJ1ZmZlcihiaW5hcnlGaWxlLGltcG9ydHMpe3RyeXt2YXIgYmluYXJ5PWF3YWl0IGdldFdhc21CaW5hcnkoYmluYXJ5RmlsZSk7dmFyIGluc3RhbmNlPWF3YWl0IFdlYkFzc2VtYmx5Lmluc3RhbnRpYXRlKGJpbmFyeSxpbXBvcnRzKTtyZXR1cm4gaW5zdGFuY2V9Y2F0Y2gocmVhc29uKXtlcnIoYGZhaWxlZCB0byBhc3luY2hyb25vdXNseSBwcmVwYXJlIHdhc206ICR7cmVhc29ufWApO2Fib3J0KHJlYXNvbil9fWFzeW5jIGZ1bmN0aW9uIGluc3RhbnRpYXRlQXN5bmMoYmluYXJ5LGJpbmFyeUZpbGUsaW1wb3J0cyl7aWYoIWJpbmFyeSYmIUVOVklST05NRU5UX0lTX05PREUpe3RyeXt2YXIgcmVzcG9uc2U9ZmV0Y2goYmluYXJ5RmlsZSx7Y3JlZGVudGlhbHM6InNhbWUtb3JpZ2luIn0pO3ZhciBpbnN0YW50aWF0aW9uUmVzdWx0PWF3YWl0IFdlYkFzc2VtYmx5Lmluc3RhbnRpYXRlU3RyZWFtaW5nKHJlc3BvbnNlLGltcG9ydHMpO3JldHVybiBpbnN0YW50aWF0aW9uUmVzdWx0fWNhdGNoKHJlYXNvbil7ZXJyKGB3YXNtIHN0cmVhbWluZyBjb21waWxlIGZhaWxlZDogJHtyZWFzb259YCk7ZXJyKCJmYWxsaW5nIGJhY2sgdG8gQXJyYXlCdWZmZXIgaW5zdGFudGlhdGlvbiIpfX1yZXR1cm4gaW5zdGFudGlhdGVBcnJheUJ1ZmZlcihiaW5hcnlGaWxlLGltcG9ydHMpfWZ1bmN0aW9uIGdldFdhc21JbXBvcnRzKCl7YXNzaWduV2FzbUltcG9ydHMoKTtyZXR1cm57YTp3YXNtSW1wb3J0c319YXN5bmMgZnVuY3Rpb24gY3JlYXRlV2FzbSgpe2Z1bmN0aW9uIHJlY2VpdmVJbnN0YW5jZShpbnN0YW5jZSxtb2R1bGUpe3dhc21FeHBvcnRzPWluc3RhbmNlLmV4cG9ydHM7d2FzbUV4cG9ydHM9YXBwbHlTaWduYXR1cmVDb252ZXJzaW9ucyh3YXNtRXhwb3J0cyk7cmVnaXN0ZXJUTFNJbml0KHdhc21FeHBvcnRzWyJLIl0pO3dhc21UYWJsZT13YXNtRXhwb3J0c1sicSJdO3dhc21Nb2R1bGU9bW9kdWxlO2Fzc2lnbldhc21FeHBvcnRzKHdhc21FeHBvcnRzKTtyZXR1cm4gd2FzbUV4cG9ydHN9ZnVuY3Rpb24gcmVjZWl2ZUluc3RhbnRpYXRpb25SZXN1bHQocmVzdWx0KXtyZXR1cm4gcmVjZWl2ZUluc3RhbmNlKHJlc3VsdFsiaW5zdGFuY2UiXSxyZXN1bHRbIm1vZHVsZSJdKX12YXIgaW5mbz1nZXRXYXNtSW1wb3J0cygpO2lmKE1vZHVsZVsiaW5zdGFudGlhdGVXYXNtIl0pe3JldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSxyZWplY3QpPT57TW9kdWxlWyJpbnN0YW50aWF0ZVdhc20iXShpbmZvLChtb2QsaW5zdCk9PntyZXNvbHZlKHJlY2VpdmVJbnN0YW5jZShtb2QsaW5zdCkpfSl9KX1pZihFTlZJUk9OTUVOVF9JU19QVEhSRUFEKXt2YXIgaW5zdGFuY2U9bmV3IFdlYkFzc2VtYmx5Lkluc3RhbmNlKHdhc21Nb2R1bGUsZ2V0V2FzbUltcG9ydHMoKSk7cmV0dXJuIHJlY2VpdmVJbnN0YW5jZShpbnN0YW5jZSx3YXNtTW9kdWxlKX13YXNtQmluYXJ5RmlsZT8/PWZpbmRXYXNtQmluYXJ5KCk7dmFyIHJlc3VsdD1hd2FpdCBpbnN0YW50aWF0ZUFzeW5jKHdhc21CaW5hcnksd2FzbUJpbmFyeUZpbGUsaW5mbyk7dmFyIGV4cG9ydHM9cmVjZWl2ZUluc3RhbnRpYXRpb25SZXN1bHQocmVzdWx0KTtyZXR1cm4gZXhwb3J0c31jbGFzcyBFeGl0U3RhdHVze25hbWU9IkV4aXRTdGF0dXMiO2NvbnN0cnVjdG9yKHN0YXR1cyl7dGhpcy5tZXNzYWdlPWBQcm9ncmFtIHRlcm1pbmF0ZWQgd2l0aCBleGl0KCR7c3RhdHVzfSlgO3RoaXMuc3RhdHVzPXN0YXR1c319dmFyIHRlcm1pbmF0ZVdvcmtlcj13b3JrZXI9Pnt3b3JrZXIudGVybWluYXRlKCk7d29ya2VyLm9ubWVzc2FnZT1lPT57fX07dmFyIGNsZWFudXBUaHJlYWQ9cHRocmVhZF9wdHI9Pnt2YXIgd29ya2VyPVBUaHJlYWQucHRocmVhZHNbcHRocmVhZF9wdHJdO1BUaHJlYWQucmV0dXJuV29ya2VyVG9Qb29sKHdvcmtlcil9O3ZhciBjYWxsUnVudGltZUNhbGxiYWNrcz1jYWxsYmFja3M9Pnt3aGlsZShjYWxsYmFja3MubGVuZ3RoPjApe2NhbGxiYWNrcy5zaGlmdCgpKE1vZHVsZSl9fTt2YXIgb25QcmVSdW5zPVtdO3ZhciBhZGRPblByZVJ1bj1jYj0+b25QcmVSdW5zLnB1c2goY2IpO3ZhciBydW5EZXBlbmRlbmNpZXM9MDt2YXIgZGVwZW5kZW5jaWVzRnVsZmlsbGVkPW51bGw7dmFyIHJlbW92ZVJ1bkRlcGVuZGVuY3k9aWQ9PntydW5EZXBlbmRlbmNpZXMtLTtNb2R1bGVbIm1vbml0b3JSdW5EZXBlbmRlbmNpZXMiXT8uKHJ1bkRlcGVuZGVuY2llcyk7aWYocnVuRGVwZW5kZW5jaWVzPT0wKXtpZihkZXBlbmRlbmNpZXNGdWxmaWxsZWQpe3ZhciBjYWxsYmFjaz1kZXBlbmRlbmNpZXNGdWxmaWxsZWQ7ZGVwZW5kZW5jaWVzRnVsZmlsbGVkPW51bGw7Y2FsbGJhY2soKX19fTt2YXIgYWRkUnVuRGVwZW5kZW5jeT1pZD0+e3J1bkRlcGVuZGVuY2llcysrO01vZHVsZVsibW9uaXRvclJ1bkRlcGVuZGVuY2llcyJdPy4ocnVuRGVwZW5kZW5jaWVzKX07dmFyIHNwYXduVGhyZWFkPXRocmVhZFBhcmFtcz0+e3ZhciB3b3JrZXI9UFRocmVhZC5nZXROZXdXb3JrZXIoKTtpZighd29ya2VyKXtyZXR1cm4gNn1QVGhyZWFkLnJ1bm5pbmdXb3JrZXJzLnB1c2god29ya2VyKTtQVGhyZWFkLnB0aHJlYWRzW3RocmVhZFBhcmFtcy5wdGhyZWFkX3B0cl09d29ya2VyO3dvcmtlci5wdGhyZWFkX3B0cj10aHJlYWRQYXJhbXMucHRocmVhZF9wdHI7dmFyIG1zZz17Y21kOiJydW4iLHN0YXJ0X3JvdXRpbmU6dGhyZWFkUGFyYW1zLnN0YXJ0Um91dGluZSxhcmc6dGhyZWFkUGFyYW1zLmFyZyxwdGhyZWFkX3B0cjp0aHJlYWRQYXJhbXMucHRocmVhZF9wdHJ9O2lmKEVOVklST05NRU5UX0lTX05PREUpe3dvcmtlci51bnJlZigpfXdvcmtlci5wb3N0TWVzc2FnZShtc2csdGhyZWFkUGFyYW1zLnRyYW5zZmVyTGlzdCk7cmV0dXJuIDB9O3ZhciBydW50aW1lS2VlcGFsaXZlQ291bnRlcj0wO3ZhciBrZWVwUnVudGltZUFsaXZlPSgpPT5ub0V4aXRSdW50aW1lfHxydW50aW1lS2VlcGFsaXZlQ291bnRlcj4wO3ZhciBzdGFja1NhdmU9KCk9Pl9lbXNjcmlwdGVuX3N0YWNrX2dldF9jdXJyZW50KCk7dmFyIHN0YWNrUmVzdG9yZT12YWw9Pl9fZW1zY3JpcHRlbl9zdGFja19yZXN0b3JlKHZhbCk7dmFyIHN0YWNrQWxsb2M9c3o9Pl9fZW1zY3JpcHRlbl9zdGFja19hbGxvYyhzeik7dmFyIHByb3h5VG9NYWluVGhyZWFkPShmdW5jSW5kZXgsZW1Bc21BZGRyLHN5bmMsLi4uY2FsbEFyZ3MpPT57dmFyIHNlcmlhbGl6ZWROdW1DYWxsQXJncz1jYWxsQXJncy5sZW5ndGgqMjt2YXIgc3A9c3RhY2tTYXZlKCk7dmFyIGFyZ3M9c3RhY2tBbGxvYyhzZXJpYWxpemVkTnVtQ2FsbEFyZ3MqOCk7dmFyIGI9YXJncz4+PjM7Zm9yKHZhciBpPTA7aTxjYWxsQXJncy5sZW5ndGg7aSsrKXt2YXIgYXJnPWNhbGxBcmdzW2ldO2lmKHR5cGVvZiBhcmc9PSJiaWdpbnQiKXsoZ3Jvd01lbVZpZXdzKCksSEVBUDY0KVtiKzIqaT4+PjBdPTFuOyhncm93TWVtVmlld3MoKSxIRUFQNjQpW2IrMippKzE+Pj4wXT1hcmd9ZWxzZXsoZ3Jvd01lbVZpZXdzKCksSEVBUDY0KVtiKzIqaT4+PjBdPTBuOyhncm93TWVtVmlld3MoKSxIRUFQRjY0KVtiKzIqaSsxPj4+MF09YXJnfX12YXIgcnRuPV9fZW1zY3JpcHRlbl9ydW5fanNfb25fbWFpbl90aHJlYWQoZnVuY0luZGV4LGVtQXNtQWRkcixzZXJpYWxpemVkTnVtQ2FsbEFyZ3MsYXJncyxzeW5jKTtzdGFja1Jlc3RvcmUoc3ApO3JldHVybiBydG59O2Z1bmN0aW9uIF9wcm9jX2V4aXQoY29kZSl7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRClyZXR1cm4gcHJveHlUb01haW5UaHJlYWQoMCwwLDEsY29kZSk7RVhJVFNUQVRVUz1jb2RlO2lmKCFrZWVwUnVudGltZUFsaXZlKCkpe1BUaHJlYWQudGVybWluYXRlQWxsVGhyZWFkcygpO01vZHVsZVsib25FeGl0Il0/Lihjb2RlKTtBQk9SVD10cnVlfXF1aXRfKGNvZGUsbmV3IEV4aXRTdGF0dXMoY29kZSkpfWZ1bmN0aW9uIGV4aXRPbk1haW5UaHJlYWQocmV0dXJuQ29kZSl7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRClyZXR1cm4gcHJveHlUb01haW5UaHJlYWQoMSwwLDAscmV0dXJuQ29kZSk7X2V4aXQocmV0dXJuQ29kZSl9dmFyIGV4aXRKUz0oc3RhdHVzLGltcGxpY2l0KT0+e0VYSVRTVEFUVVM9c3RhdHVzO2lmKEVOVklST05NRU5UX0lTX1BUSFJFQUQpe2V4aXRPbk1haW5UaHJlYWQoc3RhdHVzKTt0aHJvdyJ1bndpbmQifV9wcm9jX2V4aXQoc3RhdHVzKX07dmFyIF9leGl0PWV4aXRKUzt2YXIgUFRocmVhZD17dW51c2VkV29ya2VyczpbXSxydW5uaW5nV29ya2VyczpbXSx0bHNJbml0RnVuY3Rpb25zOltdLHB0aHJlYWRzOnt9LGluaXQoKXtpZighRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7UFRocmVhZC5pbml0TWFpblRocmVhZCgpfX0saW5pdE1haW5UaHJlYWQoKXt2YXIgcHRocmVhZFBvb2xTaXplPTQ7d2hpbGUocHRocmVhZFBvb2xTaXplLS0pe1BUaHJlYWQuYWxsb2NhdGVVbnVzZWRXb3JrZXIoKX1hZGRPblByZVJ1bihhc3luYygpPT57dmFyIHB0aHJlYWRQb29sUmVhZHk9UFRocmVhZC5sb2FkV2FzbU1vZHVsZVRvQWxsV29ya2VycygpO2FkZFJ1bkRlcGVuZGVuY3koImxvYWRpbmctd29ya2VycyIpO2F3YWl0IHB0aHJlYWRQb29sUmVhZHk7cmVtb3ZlUnVuRGVwZW5kZW5jeSgibG9hZGluZy13b3JrZXJzIil9KX0sdGVybWluYXRlQWxsVGhyZWFkczooKT0+e2Zvcih2YXIgd29ya2VyIG9mIFBUaHJlYWQucnVubmluZ1dvcmtlcnMpe3Rlcm1pbmF0ZVdvcmtlcih3b3JrZXIpfWZvcih2YXIgd29ya2VyIG9mIFBUaHJlYWQudW51c2VkV29ya2Vycyl7dGVybWluYXRlV29ya2VyKHdvcmtlcil9UFRocmVhZC51bnVzZWRXb3JrZXJzPVtdO1BUaHJlYWQucnVubmluZ1dvcmtlcnM9W107UFRocmVhZC5wdGhyZWFkcz17fX0scmV0dXJuV29ya2VyVG9Qb29sOndvcmtlcj0+e3ZhciBwdGhyZWFkX3B0cj13b3JrZXIucHRocmVhZF9wdHI7ZGVsZXRlIFBUaHJlYWQucHRocmVhZHNbcHRocmVhZF9wdHJdO1BUaHJlYWQudW51c2VkV29ya2Vycy5wdXNoKHdvcmtlcik7UFRocmVhZC5ydW5uaW5nV29ya2Vycy5zcGxpY2UoUFRocmVhZC5ydW5uaW5nV29ya2Vycy5pbmRleE9mKHdvcmtlciksMSk7d29ya2VyLnB0aHJlYWRfcHRyPTA7X19lbXNjcmlwdGVuX3RocmVhZF9mcmVlX2RhdGEocHRocmVhZF9wdHIpfSx0aHJlYWRJbml0VExTKCl7UFRocmVhZC50bHNJbml0RnVuY3Rpb25zLmZvckVhY2goZj0+ZigpKX0sbG9hZFdhc21Nb2R1bGVUb1dvcmtlcjp3b3JrZXI9Pm5ldyBQcm9taXNlKG9uRmluaXNoZWRMb2FkaW5nPT57d29ya2VyLm9ubWVzc2FnZT1lPT57dmFyIGQ9ZVsiZGF0YSJdO3ZhciBjbWQ9ZC5jbWQ7aWYoZC50YXJnZXRUaHJlYWQmJmQudGFyZ2V0VGhyZWFkIT1fcHRocmVhZF9zZWxmKCkpe3ZhciB0YXJnZXRXb3JrZXI9UFRocmVhZC5wdGhyZWFkc1tkLnRhcmdldFRocmVhZF07aWYodGFyZ2V0V29ya2VyKXt0YXJnZXRXb3JrZXIucG9zdE1lc3NhZ2UoZCxkLnRyYW5zZmVyTGlzdCl9ZWxzZXtlcnIoYEludGVybmFsIGVycm9yISBXb3JrZXIgc2VudCBhIG1lc3NhZ2UgIiR7Y21kfSIgdG8gdGFyZ2V0IHB0aHJlYWQgJHtkLnRhcmdldFRocmVhZH0sIGJ1dCB0aGF0IHRocmVhZCBubyBsb25nZXIgZXhpc3RzIWApfXJldHVybn1pZihjbWQ9PT0iY2hlY2tNYWlsYm94Iil7Y2hlY2tNYWlsYm94KCl9ZWxzZSBpZihjbWQ9PT0ic3Bhd25UaHJlYWQiKXtzcGF3blRocmVhZChkKX1lbHNlIGlmKGNtZD09PSJjbGVhbnVwVGhyZWFkIil7Y2FsbFVzZXJDYWxsYmFjaygoKT0+Y2xlYW51cFRocmVhZChkLnRocmVhZCkpfWVsc2UgaWYoY21kPT09ImxvYWRlZCIpe3dvcmtlci5sb2FkZWQ9dHJ1ZTtpZihFTlZJUk9OTUVOVF9JU19OT0RFJiYhd29ya2VyLnB0aHJlYWRfcHRyKXt3b3JrZXIudW5yZWYoKX1vbkZpbmlzaGVkTG9hZGluZyh3b3JrZXIpfWVsc2UgaWYoZC50YXJnZXQ9PT0ic2V0aW1tZWRpYXRlIil7d29ya2VyLnBvc3RNZXNzYWdlKGQpfWVsc2UgaWYoY21kPT09InVuY2F1Z2h0RXhjZXB0aW9uIil7d29ya2VyLm9uZXJyb3IoZC5lcnJvcil9ZWxzZSBpZihjbWQ9PT0iY2FsbEhhbmRsZXIiKXtNb2R1bGVbZC5oYW5kbGVyXSguLi5kLmFyZ3MpfWVsc2UgaWYoY21kKXtlcnIoYHdvcmtlciBzZW50IGFuIHVua25vd24gY29tbWFuZCAke2NtZH1gKX19O3dvcmtlci5vbmVycm9yPWU9Pnt2YXIgbWVzc2FnZT0id29ya2VyIHNlbnQgYW4gZXJyb3IhIjtlcnIoYCR7bWVzc2FnZX0gJHtlLmZpbGVuYW1lfToke2UubGluZW5vfTogJHtlLm1lc3NhZ2V9YCk7dGhyb3cgZX07aWYoRU5WSVJPTk1FTlRfSVNfTk9ERSl7d29ya2VyLm9uKCJtZXNzYWdlIixkYXRhPT53b3JrZXIub25tZXNzYWdlKHtkYXRhfSkpO3dvcmtlci5vbigiZXJyb3IiLGU9Pndvcmtlci5vbmVycm9yKGUpKX12YXIgaGFuZGxlcnM9W107dmFyIGtub3duSGFuZGxlcnM9WyJvbkV4aXQiLCJvbkFib3J0IiwicHJpbnQiLCJwcmludEVyciJdO2Zvcih2YXIgaGFuZGxlciBvZiBrbm93bkhhbmRsZXJzKXtpZihNb2R1bGUucHJvcGVydHlJc0VudW1lcmFibGUoaGFuZGxlcikpe2hhbmRsZXJzLnB1c2goaGFuZGxlcil9fXdvcmtlci5wb3N0TWVzc2FnZSh7Y21kOiJsb2FkIixoYW5kbGVycyx3YXNtTWVtb3J5LHdhc21Nb2R1bGV9KX0pLGFzeW5jIGxvYWRXYXNtTW9kdWxlVG9BbGxXb3JrZXJzKCl7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7cmV0dXJufWxldCBwdGhyZWFkUG9vbFJlYWR5PVByb21pc2UuYWxsKFBUaHJlYWQudW51c2VkV29ya2Vycy5tYXAoUFRocmVhZC5sb2FkV2FzbU1vZHVsZVRvV29ya2VyKSk7cmV0dXJuIHB0aHJlYWRQb29sUmVhZHl9LGFsbG9jYXRlVW51c2VkV29ya2VyKCl7dmFyIHdvcmtlcjtpZihNb2R1bGVbIm1haW5TY3JpcHRVcmxPckJsb2IiXSl7dmFyIHB0aHJlYWRNYWluSnM9TW9kdWxlWyJtYWluU2NyaXB0VXJsT3JCbG9iIl07aWYodHlwZW9mIHB0aHJlYWRNYWluSnMhPSJzdHJpbmciKXtwdGhyZWFkTWFpbkpzPVVSTC5jcmVhdGVPYmplY3RVUkwocHRocmVhZE1haW5Kcyl9d29ya2VyPW5ldyBXb3JrZXIoRU5WSVJPTk1FTlRfSVNfTk9ERSYmdHlwZW9mIHB0aHJlYWRNYWluSnM9PSJzdHJpbmciJiZwdGhyZWFkTWFpbkpzLnN0YXJ0c1dpdGgoImRhdGE6Iik/bmV3IFVSTChwdGhyZWFkTWFpbkpzKTpwdGhyZWFkTWFpbkpzLHt0eXBlOiJtb2R1bGUiLHdvcmtlckRhdGE6ImVtLXB0aHJlYWQiLG5hbWU6ImVtLXB0aHJlYWQifSl9ZWxzZSB3b3JrZXI9bmV3IFdvcmtlcihuZXcgVVJMKCJza2FpbmV0X2tlcm5lbHNfbXQubWpzIixpbXBvcnQubWV0YS51cmwpLHt0eXBlOiJtb2R1bGUiLHdvcmtlckRhdGE6ImVtLXB0aHJlYWQiLG5hbWU6ImVtLXB0aHJlYWQifSk7UFRocmVhZC51bnVzZWRXb3JrZXJzLnB1c2god29ya2VyKX0sZ2V0TmV3V29ya2VyKCl7aWYoUFRocmVhZC51bnVzZWRXb3JrZXJzLmxlbmd0aD09MCl7UFRocmVhZC5hbGxvY2F0ZVVudXNlZFdvcmtlcigpO1BUaHJlYWQubG9hZFdhc21Nb2R1bGVUb1dvcmtlcihQVGhyZWFkLnVudXNlZFdvcmtlcnNbMF0pfXJldHVybiBQVGhyZWFkLnVudXNlZFdvcmtlcnMucG9wKCl9fTt2YXIgb25Qb3N0UnVucz1bXTt2YXIgYWRkT25Qb3N0UnVuPWNiPT5vblBvc3RSdW5zLnB1c2goY2IpO2Z1bmN0aW9uIGVzdGFibGlzaFN0YWNrU3BhY2UocHRocmVhZF9wdHIpe3ZhciBzdGFja0hpZ2g9KGdyb3dNZW1WaWV3cygpLEhFQVBVMzIpW3B0aHJlYWRfcHRyKzUyPj4+Mj4+PjBdO3ZhciBzdGFja1NpemU9KGdyb3dNZW1WaWV3cygpLEhFQVBVMzIpW3B0aHJlYWRfcHRyKzU2Pj4+Mj4+PjBdO3ZhciBzdGFja0xvdz1zdGFja0hpZ2gtc3RhY2tTaXplO19lbXNjcmlwdGVuX3N0YWNrX3NldF9saW1pdHMoc3RhY2tIaWdoLHN0YWNrTG93KTtzdGFja1Jlc3RvcmUoc3RhY2tIaWdoKX12YXIgd2FzbVRhYmxlTWlycm9yPVtdO3ZhciB3YXNtVGFibGU7dmFyIGdldFdhc21UYWJsZUVudHJ5PWZ1bmNQdHI9Pnt2YXIgZnVuYz13YXNtVGFibGVNaXJyb3JbZnVuY1B0cl07aWYoIWZ1bmMpe3dhc21UYWJsZU1pcnJvcltmdW5jUHRyXT1mdW5jPXdhc21UYWJsZS5nZXQoZnVuY1B0cil9cmV0dXJuIGZ1bmN9O3ZhciBpbnZva2VFbnRyeVBvaW50PShwdHIsYXJnKT0+e3J1bnRpbWVLZWVwYWxpdmVDb3VudGVyPTA7bm9FeGl0UnVudGltZT0wO3ZhciByZXN1bHQ9Z2V0V2FzbVRhYmxlRW50cnkocHRyKShhcmcpO2Z1bmN0aW9uIGZpbmlzaChyZXN1bHQpe2lmKGtlZXBSdW50aW1lQWxpdmUoKSl7RVhJVFNUQVRVUz1yZXN1bHQ7cmV0dXJufV9fZW1zY3JpcHRlbl90aHJlYWRfZXhpdChyZXN1bHQpfWZpbmlzaChyZXN1bHQpfTt2YXIgbm9FeGl0UnVudGltZT10cnVlO3ZhciByZWdpc3RlclRMU0luaXQ9dGxzSW5pdEZ1bmM9PlBUaHJlYWQudGxzSW5pdEZ1bmN0aW9ucy5wdXNoKHRsc0luaXRGdW5jKTtmdW5jdGlvbiBwdGhyZWFkQ3JlYXRlUHJveGllZChwdGhyZWFkX3B0cixhdHRyLHN0YXJ0Um91dGluZSxhcmcpe2lmKEVOVklST05NRU5UX0lTX1BUSFJFQUQpcmV0dXJuIHByb3h5VG9NYWluVGhyZWFkKDIsMCwxLHB0aHJlYWRfcHRyLGF0dHIsc3RhcnRSb3V0aW5lLGFyZyk7cmV0dXJuIF9fX3B0aHJlYWRfY3JlYXRlX2pzKHB0aHJlYWRfcHRyLGF0dHIsc3RhcnRSb3V0aW5lLGFyZyl9dmFyIF9lbXNjcmlwdGVuX2hhc190aHJlYWRpbmdfc3VwcG9ydD0oKT0+dHlwZW9mIFNoYXJlZEFycmF5QnVmZmVyIT0idW5kZWZpbmVkIjtmdW5jdGlvbiBfX19wdGhyZWFkX2NyZWF0ZV9qcyhwdGhyZWFkX3B0cixhdHRyLHN0YXJ0Um91dGluZSxhcmcpe3B0aHJlYWRfcHRyPj4+PTA7YXR0cj4+Pj0wO3N0YXJ0Um91dGluZT4+Pj0wO2FyZz4+Pj0wO2lmKCFfZW1zY3JpcHRlbl9oYXNfdGhyZWFkaW5nX3N1cHBvcnQoKSl7cmV0dXJuIDZ9dmFyIHRyYW5zZmVyTGlzdD1bXTt2YXIgZXJyb3I9MDtpZihFTlZJUk9OTUVOVF9JU19QVEhSRUFEJiYodHJhbnNmZXJMaXN0Lmxlbmd0aD09PTB8fGVycm9yKSl7cmV0dXJuIHB0aHJlYWRDcmVhdGVQcm94aWVkKHB0aHJlYWRfcHRyLGF0dHIsc3RhcnRSb3V0aW5lLGFyZyl9aWYoZXJyb3IpcmV0dXJuIGVycm9yO3ZhciB0aHJlYWRQYXJhbXM9e3N0YXJ0Um91dGluZSxwdGhyZWFkX3B0cixhcmcsdHJhbnNmZXJMaXN0fTtpZihFTlZJUk9OTUVOVF9JU19QVEhSRUFEKXt0aHJlYWRQYXJhbXMuY21kPSJzcGF3blRocmVhZCI7cG9zdE1lc3NhZ2UodGhyZWFkUGFyYW1zLHRyYW5zZmVyTGlzdCk7cmV0dXJuIDB9cmV0dXJuIHNwYXduVGhyZWFkKHRocmVhZFBhcmFtcyl9ZnVuY3Rpb24gX19lbXNjcmlwdGVuX2luaXRfbWFpbl90aHJlYWRfanModGIpe3RiPj4+PTA7X19lbXNjcmlwdGVuX3RocmVhZF9pbml0KHRiLCFFTlZJUk9OTUVOVF9JU19XT1JLRVIsMSwhRU5WSVJPTk1FTlRfSVNfV0VCLDY1NTM2LGZhbHNlKTtQVGhyZWFkLnRocmVhZEluaXRUTFMoKX12YXIgaGFuZGxlRXhjZXB0aW9uPWU9PntpZihlIGluc3RhbmNlb2YgRXhpdFN0YXR1c3x8ZT09InVud2luZCIpe3JldHVybiBFWElUU1RBVFVTfXF1aXRfKDEsZSl9O3ZhciBtYXliZUV4aXQ9KCk9PntpZigha2VlcFJ1bnRpbWVBbGl2ZSgpKXt0cnl7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7aWYoX3B0aHJlYWRfc2VsZigpKV9fZW1zY3JpcHRlbl90aHJlYWRfZXhpdChFWElUU1RBVFVTKTtyZXR1cm59X2V4aXQoRVhJVFNUQVRVUyl9Y2F0Y2goZSl7aGFuZGxlRXhjZXB0aW9uKGUpfX19O3ZhciBjYWxsVXNlckNhbGxiYWNrPWZ1bmM9PntpZihBQk9SVCl7cmV0dXJufXRyeXtmdW5jKCk7bWF5YmVFeGl0KCl9Y2F0Y2goZSl7aGFuZGxlRXhjZXB0aW9uKGUpfX07ZnVuY3Rpb24gX19lbXNjcmlwdGVuX3RocmVhZF9tYWlsYm94X2F3YWl0KHB0aHJlYWRfcHRyKXtwdGhyZWFkX3B0cj4+Pj0wO2lmKHR5cGVvZiBBdG9taWNzLndhaXRBc3luYz09PSJmdW5jdGlvbiIpe3ZhciB3YWl0PUF0b21pY3Mud2FpdEFzeW5jKChncm93TWVtVmlld3MoKSxIRUFQMzIpLHB0aHJlYWRfcHRyPj4+MixwdGhyZWFkX3B0cik7d2FpdC52YWx1ZS50aGVuKGNoZWNrTWFpbGJveCk7dmFyIHdhaXRpbmdBc3luYz1wdGhyZWFkX3B0cisxMjg7QXRvbWljcy5zdG9yZSgoZ3Jvd01lbVZpZXdzKCksSEVBUDMyKSx3YWl0aW5nQXN5bmM+Pj4yLDEpfX12YXIgY2hlY2tNYWlsYm94PSgpPT5jYWxsVXNlckNhbGxiYWNrKCgpPT57dmFyIHB0aHJlYWRfcHRyPV9wdGhyZWFkX3NlbGYoKTtpZihwdGhyZWFkX3B0cil7X19lbXNjcmlwdGVuX3RocmVhZF9tYWlsYm94X2F3YWl0KHB0aHJlYWRfcHRyKTtfX2Vtc2NyaXB0ZW5fY2hlY2tfbWFpbGJveCgpfX0pO2Z1bmN0aW9uIF9fZW1zY3JpcHRlbl9ub3RpZnlfbWFpbGJveF9wb3N0bWVzc2FnZSh0YXJnZXRUaHJlYWQsY3VyclRocmVhZElkKXt0YXJnZXRUaHJlYWQ+Pj49MDtjdXJyVGhyZWFkSWQ+Pj49MDtpZih0YXJnZXRUaHJlYWQ9PWN1cnJUaHJlYWRJZCl7c2V0VGltZW91dChjaGVja01haWxib3gpfWVsc2UgaWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7cG9zdE1lc3NhZ2Uoe3RhcmdldFRocmVhZCxjbWQ6ImNoZWNrTWFpbGJveCJ9KX1lbHNle3ZhciB3b3JrZXI9UFRocmVhZC5wdGhyZWFkc1t0YXJnZXRUaHJlYWRdO2lmKCF3b3JrZXIpe3JldHVybn13b3JrZXIucG9zdE1lc3NhZ2Uoe2NtZDoiY2hlY2tNYWlsYm94In0pfX12YXIgcHJveGllZEpTQ2FsbEFyZ3M9W107ZnVuY3Rpb24gX19lbXNjcmlwdGVuX3JlY2VpdmVfb25fbWFpbl90aHJlYWRfanMoZnVuY0luZGV4LGVtQXNtQWRkcixjYWxsaW5nVGhyZWFkLG51bUNhbGxBcmdzLGFyZ3Mpe2VtQXNtQWRkcj4+Pj0wO2NhbGxpbmdUaHJlYWQ+Pj49MDthcmdzPj4+PTA7bnVtQ2FsbEFyZ3MvPTI7cHJveGllZEpTQ2FsbEFyZ3MubGVuZ3RoPW51bUNhbGxBcmdzO3ZhciBiPWFyZ3M+Pj4zO2Zvcih2YXIgaT0wO2k8bnVtQ2FsbEFyZ3M7aSsrKXtpZigoZ3Jvd01lbVZpZXdzKCksSEVBUDY0KVtiKzIqaT4+PjBdKXtwcm94aWVkSlNDYWxsQXJnc1tpXT0oZ3Jvd01lbVZpZXdzKCksSEVBUDY0KVtiKzIqaSsxPj4+MF19ZWxzZXtwcm94aWVkSlNDYWxsQXJnc1tpXT0oZ3Jvd01lbVZpZXdzKCksSEVBUEY2NClbYisyKmkrMT4+PjBdfX12YXIgZnVuYz1wcm94aWVkRnVuY3Rpb25UYWJsZVtmdW5jSW5kZXhdO1BUaHJlYWQuY3VycmVudFByb3hpZWRPcGVyYXRpb25DYWxsZXJUaHJlYWQ9Y2FsbGluZ1RocmVhZDt2YXIgcnRuPWZ1bmMoLi4ucHJveGllZEpTQ2FsbEFyZ3MpO1BUaHJlYWQuY3VycmVudFByb3hpZWRPcGVyYXRpb25DYWxsZXJUaHJlYWQ9MDtyZXR1cm4gcnRufWZ1bmN0aW9uIF9fZW1zY3JpcHRlbl90aHJlYWRfY2xlYW51cCh0aHJlYWQpe3RocmVhZD4+Pj0wO2lmKCFFTlZJUk9OTUVOVF9JU19QVEhSRUFEKWNsZWFudXBUaHJlYWQodGhyZWFkKTtlbHNlIHBvc3RNZXNzYWdlKHtjbWQ6ImNsZWFudXBUaHJlYWQiLHRocmVhZH0pfWZ1bmN0aW9uIF9fZW1zY3JpcHRlbl90aHJlYWRfc2V0X3N0cm9uZ3JlZih0aHJlYWQpe3RocmVhZD4+Pj0wO2lmKEVOVklST05NRU5UX0lTX05PREUpe1BUaHJlYWQucHRocmVhZHNbdGhyZWFkXS5yZWYoKX19dmFyIF9lbXNjcmlwdGVuX2dldF9ub3c9KCk9PnBlcmZvcm1hbmNlLnRpbWVPcmlnaW4rcGVyZm9ybWFuY2Uubm93KCk7dmFyIF9lbXNjcmlwdGVuX2NoZWNrX2Jsb2NraW5nX2FsbG93ZWQ9KCk9Pnt9O3ZhciBydW50aW1lS2VlcGFsaXZlUHVzaD0oKT0+e3J1bnRpbWVLZWVwYWxpdmVDb3VudGVyKz0xfTt2YXIgX2Vtc2NyaXB0ZW5fZXhpdF93aXRoX2xpdmVfcnVudGltZT0oKT0+e3J1bnRpbWVLZWVwYWxpdmVQdXNoKCk7dGhyb3cidW53aW5kIn07dmFyIGdldEhlYXBNYXg9KCk9PjQyOTQ5MDE3NjA7dmFyIGFsaWduTWVtb3J5PShzaXplLGFsaWdubWVudCk9Pk1hdGguY2VpbChzaXplL2FsaWdubWVudCkqYWxpZ25tZW50O3ZhciBncm93TWVtb3J5PXNpemU9Pnt2YXIgb2xkSGVhcFNpemU9d2FzbU1lbW9yeS5idWZmZXIuYnl0ZUxlbmd0aDt2YXIgcGFnZXM9KHNpemUtb2xkSGVhcFNpemUrNjU1MzUpLzY1NTM2fDA7dHJ5e3dhc21NZW1vcnkuZ3JvdyhwYWdlcyk7dXBkYXRlTWVtb3J5Vmlld3MoKTtyZXR1cm4gMX1jYXRjaChlKXt9fTtmdW5jdGlvbiBfZW1zY3JpcHRlbl9yZXNpemVfaGVhcChyZXF1ZXN0ZWRTaXplKXtyZXF1ZXN0ZWRTaXplPj4+PTA7dmFyIG9sZFNpemU9KGdyb3dNZW1WaWV3cygpLEhFQVBVOCkubGVuZ3RoO2lmKHJlcXVlc3RlZFNpemU8PW9sZFNpemUpe3JldHVybiBmYWxzZX12YXIgbWF4SGVhcFNpemU9Z2V0SGVhcE1heCgpO2lmKHJlcXVlc3RlZFNpemU+bWF4SGVhcFNpemUpe3JldHVybiBmYWxzZX1mb3IodmFyIGN1dERvd249MTtjdXREb3duPD00O2N1dERvd24qPTIpe3ZhciBvdmVyR3Jvd25IZWFwU2l6ZT1vbGRTaXplKigxKy4yL2N1dERvd24pO292ZXJHcm93bkhlYXBTaXplPU1hdGgubWluKG92ZXJHcm93bkhlYXBTaXplLHJlcXVlc3RlZFNpemUrMTAwNjYzMjk2KTt2YXIgbmV3U2l6ZT1NYXRoLm1pbihtYXhIZWFwU2l6ZSxhbGlnbk1lbW9yeShNYXRoLm1heChyZXF1ZXN0ZWRTaXplLG92ZXJHcm93bkhlYXBTaXplKSw2NTUzNikpO3ZhciByZXBsYWNlbWVudD1ncm93TWVtb3J5KG5ld1NpemUpO2lmKHJlcGxhY2VtZW50KXtyZXR1cm4gdHJ1ZX19cmV0dXJuIGZhbHNlfXZhciBwcmludENoYXJCdWZmZXJzPVtudWxsLFtdLFtdXTt2YXIgVVRGOERlY29kZXI9dHlwZW9mIFRleHREZWNvZGVyIT0idW5kZWZpbmVkIj9uZXcgVGV4dERlY29kZXI6dW5kZWZpbmVkO3ZhciBmaW5kU3RyaW5nRW5kPShoZWFwT3JBcnJheSxpZHgsbWF4Qnl0ZXNUb1JlYWQsaWdub3JlTnVsKT0+e3ZhciBtYXhJZHg9aWR4K21heEJ5dGVzVG9SZWFkO2lmKGlnbm9yZU51bClyZXR1cm4gbWF4SWR4O3doaWxlKGhlYXBPckFycmF5W2lkeF0mJiEoaWR4Pj1tYXhJZHgpKSsraWR4O3JldHVybiBpZHh9O3ZhciBVVEY4QXJyYXlUb1N0cmluZz0oaGVhcE9yQXJyYXksaWR4PTAsbWF4Qnl0ZXNUb1JlYWQsaWdub3JlTnVsKT0+e2lkeD4+Pj0wO3ZhciBlbmRQdHI9ZmluZFN0cmluZ0VuZChoZWFwT3JBcnJheSxpZHgsbWF4Qnl0ZXNUb1JlYWQsaWdub3JlTnVsKTtpZihlbmRQdHItaWR4PjE2JiZoZWFwT3JBcnJheS5idWZmZXImJlVURjhEZWNvZGVyKXtyZXR1cm4gVVRGOERlY29kZXIuZGVjb2RlKGhlYXBPckFycmF5LmJ1ZmZlciBpbnN0YW5jZW9mIEFycmF5QnVmZmVyP2hlYXBPckFycmF5LnN1YmFycmF5KGlkeCxlbmRQdHIpOmhlYXBPckFycmF5LnNsaWNlKGlkeCxlbmRQdHIpKX12YXIgc3RyPSIiO3doaWxlKGlkeDxlbmRQdHIpe3ZhciB1MD1oZWFwT3JBcnJheVtpZHgrK107aWYoISh1MCYxMjgpKXtzdHIrPVN0cmluZy5mcm9tQ2hhckNvZGUodTApO2NvbnRpbnVlfXZhciB1MT1oZWFwT3JBcnJheVtpZHgrK10mNjM7aWYoKHUwJjIyNCk9PTE5Mil7c3RyKz1TdHJpbmcuZnJvbUNoYXJDb2RlKCh1MCYzMSk8PDZ8dTEpO2NvbnRpbnVlfXZhciB1Mj1oZWFwT3JBcnJheVtpZHgrK10mNjM7aWYoKHUwJjI0MCk9PTIyNCl7dTA9KHUwJjE1KTw8MTJ8dTE8PDZ8dTJ9ZWxzZXt1MD0odTAmNyk8PDE4fHUxPDwxMnx1Mjw8NnxoZWFwT3JBcnJheVtpZHgrK10mNjN9aWYodTA8NjU1MzYpe3N0cis9U3RyaW5nLmZyb21DaGFyQ29kZSh1MCl9ZWxzZXt2YXIgY2g9dTAtNjU1MzY7c3RyKz1TdHJpbmcuZnJvbUNoYXJDb2RlKDU1Mjk2fGNoPj4xMCw1NjMyMHxjaCYxMDIzKX19cmV0dXJuIHN0cn07dmFyIHByaW50Q2hhcj0oc3RyZWFtLGN1cnIpPT57dmFyIGJ1ZmZlcj1wcmludENoYXJCdWZmZXJzW3N0cmVhbV07aWYoY3Vycj09PTB8fGN1cnI9PT0xMCl7KHN0cmVhbT09PTE/b3V0OmVycikoVVRGOEFycmF5VG9TdHJpbmcoYnVmZmVyKSk7YnVmZmVyLmxlbmd0aD0wfWVsc2V7YnVmZmVyLnB1c2goY3Vycil9fTtmdW5jdGlvbiBfZmRfd3JpdGUoZmQsaW92LGlvdmNudCxwbnVtKXtpZihFTlZJUk9OTUVOVF9JU19QVEhSRUFEKXJldHVybiBwcm94eVRvTWFpblRocmVhZCgzLDAsMSxmZCxpb3YsaW92Y250LHBudW0pO2lvdj4+Pj0wO2lvdmNudD4+Pj0wO3BudW0+Pj49MDt2YXIgbnVtPTA7Zm9yKHZhciBpPTA7aTxpb3ZjbnQ7aSsrKXt2YXIgcHRyPShncm93TWVtVmlld3MoKSxIRUFQVTMyKVtpb3Y+Pj4yPj4+MF07dmFyIGxlbj0oZ3Jvd01lbVZpZXdzKCksSEVBUFUzMilbaW92KzQ+Pj4yPj4+MF07aW92Kz04O2Zvcih2YXIgaj0wO2o8bGVuO2orKyl7cHJpbnRDaGFyKGZkLChncm93TWVtVmlld3MoKSxIRUFQVTgpW3B0citqPj4+MF0pfW51bSs9bGVufShncm93TWVtVmlld3MoKSxIRUFQVTMyKVtwbnVtPj4+Mj4+PjBdPW51bTtyZXR1cm4gMH1QVGhyZWFkLmluaXQoKTt7aW5pdE1lbW9yeSgpO2lmKE1vZHVsZVsibm9FeGl0UnVudGltZSJdKW5vRXhpdFJ1bnRpbWU9TW9kdWxlWyJub0V4aXRSdW50aW1lIl07aWYoTW9kdWxlWyJwcmludCJdKW91dD1Nb2R1bGVbInByaW50Il07aWYoTW9kdWxlWyJwcmludEVyciJdKWVycj1Nb2R1bGVbInByaW50RXJyIl07aWYoTW9kdWxlWyJ3YXNtQmluYXJ5Il0pd2FzbUJpbmFyeT1Nb2R1bGVbIndhc21CaW5hcnkiXTtpZihNb2R1bGVbImFyZ3VtZW50cyJdKWFyZ3VtZW50c189TW9kdWxlWyJhcmd1bWVudHMiXTtpZihNb2R1bGVbInRoaXNQcm9ncmFtIl0pdGhpc1Byb2dyYW09TW9kdWxlWyJ0aGlzUHJvZ3JhbSJdO2lmKE1vZHVsZVsicHJlSW5pdCJdKXtpZih0eXBlb2YgTW9kdWxlWyJwcmVJbml0Il09PSJmdW5jdGlvbiIpTW9kdWxlWyJwcmVJbml0Il09W01vZHVsZVsicHJlSW5pdCJdXTt3aGlsZShNb2R1bGVbInByZUluaXQiXS5sZW5ndGg+MCl7TW9kdWxlWyJwcmVJbml0Il0uc2hpZnQoKSgpfX19TW9kdWxlWyJ3YXNtTWVtb3J5Il09d2FzbU1lbW9yeTt2YXIgcHJveGllZEZ1bmN0aW9uVGFibGU9W19wcm9jX2V4aXQsZXhpdE9uTWFpblRocmVhZCxwdGhyZWFkQ3JlYXRlUHJveGllZCxfZmRfd3JpdGVdO3ZhciBfc2thaW5ldF9mcDMyX21hdG11bCxfc2thaW5ldF9mcDMyX21hdG11bF93dCxfc2thaW5ldF9iZjE2X21hdG11bCxfc2thaW5ldF9mcDE2X21hdG11bCxfc2thaW5ldF9xNF8wX21hdG11bCxfc2thaW5ldF9xNF8wX21hdG11bF9ybSxfc2thaW5ldF9xNV8wX21hdG11bCxfbWFsbG9jLF9mcmVlLF9za2FpbmV0X3E1XzBfbWF0bXVsX3JtLF9za2FpbmV0X3E1XzFfbWF0bXVsLF9za2FpbmV0X3E1XzFfbWF0bXVsX3JtLF9za2FpbmV0X3E4XzBfbWF0bXVsLF9za2FpbmV0X3E4XzBfbWF0bXVsX3JtLF9za2FpbmV0X3E0a19tYXRtdWwsX3NrYWluZXRfcTRrX21hdG11bF9ybSxfc2thaW5ldF9xNWtfbWF0bXVsLF9za2FpbmV0X3E1a19tYXRtdWxfcm0sX3NrYWluZXRfcTZrX21hdG11bCxfc2thaW5ldF9xNmtfbWF0bXVsX3JtLF9fZW1zY3JpcHRlbl90bHNfaW5pdCxfcHRocmVhZF9zZWxmLF9fZW1zY3JpcHRlbl90aHJlYWRfaW5pdCxfX2Vtc2NyaXB0ZW5fdGhyZWFkX2NyYXNoZWQsX19lbXNjcmlwdGVuX3J1bl9qc19vbl9tYWluX3RocmVhZCxfX2Vtc2NyaXB0ZW5fdGhyZWFkX2ZyZWVfZGF0YSxfX2Vtc2NyaXB0ZW5fdGhyZWFkX2V4aXQsX19lbXNjcmlwdGVuX2NoZWNrX21haWxib3gsX2Vtc2NyaXB0ZW5fc3RhY2tfc2V0X2xpbWl0cyxfX2Vtc2NyaXB0ZW5fc3RhY2tfcmVzdG9yZSxfX2Vtc2NyaXB0ZW5fc3RhY2tfYWxsb2MsX2Vtc2NyaXB0ZW5fc3RhY2tfZ2V0X2N1cnJlbnQ7ZnVuY3Rpb24gYXNzaWduV2FzbUV4cG9ydHMod2FzbUV4cG9ydHMpe01vZHVsZVsiX3NrYWluZXRfZnAzMl9tYXRtdWwiXT1fc2thaW5ldF9mcDMyX21hdG11bD13YXNtRXhwb3J0c1sicCJdO01vZHVsZVsiX3NrYWluZXRfZnAzMl9tYXRtdWxfd3QiXT1fc2thaW5ldF9mcDMyX21hdG11bF93dD13YXNtRXhwb3J0c1siciJdO01vZHVsZVsiX3NrYWluZXRfYmYxNl9tYXRtdWwiXT1fc2thaW5ldF9iZjE2X21hdG11bD13YXNtRXhwb3J0c1sicyJdO01vZHVsZVsiX3NrYWluZXRfZnAxNl9tYXRtdWwiXT1fc2thaW5ldF9mcDE2X21hdG11bD13YXNtRXhwb3J0c1sidCJdO01vZHVsZVsiX3NrYWluZXRfcTRfMF9tYXRtdWwiXT1fc2thaW5ldF9xNF8wX21hdG11bD13YXNtRXhwb3J0c1sidSJdO01vZHVsZVsiX3NrYWluZXRfcTRfMF9tYXRtdWxfcm0iXT1fc2thaW5ldF9xNF8wX21hdG11bF9ybT13YXNtRXhwb3J0c1sidiJdO01vZHVsZVsiX3NrYWluZXRfcTVfMF9tYXRtdWwiXT1fc2thaW5ldF9xNV8wX21hdG11bD13YXNtRXhwb3J0c1sidyJdO01vZHVsZVsiX21hbGxvYyJdPV9tYWxsb2M9d2FzbUV4cG9ydHNbIngiXTtNb2R1bGVbIl9mcmVlIl09X2ZyZWU9d2FzbUV4cG9ydHNbInkiXTtNb2R1bGVbIl9za2FpbmV0X3E1XzBfbWF0bXVsX3JtIl09X3NrYWluZXRfcTVfMF9tYXRtdWxfcm09d2FzbUV4cG9ydHNbInoiXTtNb2R1bGVbIl9za2FpbmV0X3E1XzFfbWF0bXVsIl09X3NrYWluZXRfcTVfMV9tYXRtdWw9d2FzbUV4cG9ydHNbIkEiXTtNb2R1bGVbIl9za2FpbmV0X3E1XzFfbWF0bXVsX3JtIl09X3NrYWluZXRfcTVfMV9tYXRtdWxfcm09d2FzbUV4cG9ydHNbIkIiXTtNb2R1bGVbIl9za2FpbmV0X3E4XzBfbWF0bXVsIl09X3NrYWluZXRfcThfMF9tYXRtdWw9d2FzbUV4cG9ydHNbIkMiXTtNb2R1bGVbIl9za2FpbmV0X3E4XzBfbWF0bXVsX3JtIl09X3NrYWluZXRfcThfMF9tYXRtdWxfcm09d2FzbUV4cG9ydHNbIkQiXTtNb2R1bGVbIl9za2FpbmV0X3E0a19tYXRtdWwiXT1fc2thaW5ldF9xNGtfbWF0bXVsPXdhc21FeHBvcnRzWyJFIl07TW9kdWxlWyJfc2thaW5ldF9xNGtfbWF0bXVsX3JtIl09X3NrYWluZXRfcTRrX21hdG11bF9ybT13YXNtRXhwb3J0c1siRiJdO01vZHVsZVsiX3NrYWluZXRfcTVrX21hdG11bCJdPV9za2FpbmV0X3E1a19tYXRtdWw9d2FzbUV4cG9ydHNbIkciXTtNb2R1bGVbIl9za2FpbmV0X3E1a19tYXRtdWxfcm0iXT1fc2thaW5ldF9xNWtfbWF0bXVsX3JtPXdhc21FeHBvcnRzWyJIIl07TW9kdWxlWyJfc2thaW5ldF9xNmtfbWF0bXVsIl09X3NrYWluZXRfcTZrX21hdG11bD13YXNtRXhwb3J0c1siSSJdO01vZHVsZVsiX3NrYWluZXRfcTZrX21hdG11bF9ybSJdPV9za2FpbmV0X3E2a19tYXRtdWxfcm09d2FzbUV4cG9ydHNbIkoiXTtfX2Vtc2NyaXB0ZW5fdGxzX2luaXQ9d2FzbUV4cG9ydHNbIksiXTtfcHRocmVhZF9zZWxmPXdhc21FeHBvcnRzWyJMIl07X19lbXNjcmlwdGVuX3RocmVhZF9pbml0PXdhc21FeHBvcnRzWyJNIl07X19lbXNjcmlwdGVuX3RocmVhZF9jcmFzaGVkPXdhc21FeHBvcnRzWyJOIl07X19lbXNjcmlwdGVuX3J1bl9qc19vbl9tYWluX3RocmVhZD13YXNtRXhwb3J0c1siTyJdO19fZW1zY3JpcHRlbl90aHJlYWRfZnJlZV9kYXRhPXdhc21FeHBvcnRzWyJQIl07X19lbXNjcmlwdGVuX3RocmVhZF9leGl0PXdhc21FeHBvcnRzWyJRIl07X19lbXNjcmlwdGVuX2NoZWNrX21haWxib3g9d2FzbUV4cG9ydHNbIlIiXTtfZW1zY3JpcHRlbl9zdGFja19zZXRfbGltaXRzPXdhc21FeHBvcnRzWyJTIl07X19lbXNjcmlwdGVuX3N0YWNrX3Jlc3RvcmU9d2FzbUV4cG9ydHNbIlQiXTtfX2Vtc2NyaXB0ZW5fc3RhY2tfYWxsb2M9d2FzbUV4cG9ydHNbIlUiXTtfZW1zY3JpcHRlbl9zdGFja19nZXRfY3VycmVudD13YXNtRXhwb3J0c1siViJdfXZhciB3YXNtSW1wb3J0cztmdW5jdGlvbiBhc3NpZ25XYXNtSW1wb3J0cygpe3dhc21JbXBvcnRzPXtnOl9fX3B0aHJlYWRfY3JlYXRlX2pzLGs6X19lbXNjcmlwdGVuX2luaXRfbWFpbl90aHJlYWRfanMsbjpfX2Vtc2NyaXB0ZW5fbm90aWZ5X21haWxib3hfcG9zdG1lc3NhZ2UsbDpfX2Vtc2NyaXB0ZW5fcmVjZWl2ZV9vbl9tYWluX3RocmVhZF9qcyxjOl9fZW1zY3JpcHRlbl90aHJlYWRfY2xlYW51cCxqOl9fZW1zY3JpcHRlbl90aHJlYWRfbWFpbGJveF9hd2FpdCxpOl9fZW1zY3JpcHRlbl90aHJlYWRfc2V0X3N0cm9uZ3JlZixlOl9lbXNjcmlwdGVuX2NoZWNrX2Jsb2NraW5nX2FsbG93ZWQsaDpfZW1zY3JpcHRlbl9leGl0X3dpdGhfbGl2ZV9ydW50aW1lLGI6X2Vtc2NyaXB0ZW5fZ2V0X25vdyxtOl9lbXNjcmlwdGVuX3Jlc2l6ZV9oZWFwLGY6X2V4aXQsZDpfZmRfd3JpdGUsYTp3YXNtTWVtb3J5fX1mdW5jdGlvbiBhcHBseVNpZ25hdHVyZUNvbnZlcnNpb25zKHdhc21FeHBvcnRzKXt3YXNtRXhwb3J0cz1PYmplY3QuYXNzaWduKHt9LHdhc21FeHBvcnRzKTt2YXIgbWFrZVdyYXBwZXJfcHA9Zj0+YTA9PmYoYTApPj4+MDt2YXIgbWFrZVdyYXBwZXJfcD1mPT4oKT0+ZigpPj4+MDt3YXNtRXhwb3J0c1sieCJdPW1ha2VXcmFwcGVyX3BwKHdhc21FeHBvcnRzWyJ4Il0pO3dhc21FeHBvcnRzWyJMIl09bWFrZVdyYXBwZXJfcCh3YXNtRXhwb3J0c1siTCJdKTt3YXNtRXhwb3J0c1siVSJdPW1ha2VXcmFwcGVyX3BwKHdhc21FeHBvcnRzWyJVIl0pO3dhc21FeHBvcnRzWyJWIl09bWFrZVdyYXBwZXJfcCh3YXNtRXhwb3J0c1siViJdKTtyZXR1cm4gd2FzbUV4cG9ydHN9ZnVuY3Rpb24gcnVuKCl7aWYocnVuRGVwZW5kZW5jaWVzPjApe2RlcGVuZGVuY2llc0Z1bGZpbGxlZD1ydW47cmV0dXJufWlmKEVOVklST05NRU5UX0lTX1BUSFJFQUQpe3JlYWR5UHJvbWlzZVJlc29sdmU/LihNb2R1bGUpO2luaXRSdW50aW1lKCk7cmV0dXJufXByZVJ1bigpO2lmKHJ1bkRlcGVuZGVuY2llcz4wKXtkZXBlbmRlbmNpZXNGdWxmaWxsZWQ9cnVuO3JldHVybn1mdW5jdGlvbiBkb1J1bigpe01vZHVsZVsiY2FsbGVkUnVuIl09dHJ1ZTtpZihBQk9SVClyZXR1cm47aW5pdFJ1bnRpbWUoKTtyZWFkeVByb21pc2VSZXNvbHZlPy4oTW9kdWxlKTtNb2R1bGVbIm9uUnVudGltZUluaXRpYWxpemVkIl0/LigpO3Bvc3RSdW4oKX1pZihNb2R1bGVbInNldFN0YXR1cyJdKXtNb2R1bGVbInNldFN0YXR1cyJdKCJSdW5uaW5nLi4uIik7c2V0VGltZW91dCgoKT0+e3NldFRpbWVvdXQoKCk9Pk1vZHVsZVsic2V0U3RhdHVzIl0oIiIpLDEpO2RvUnVuKCl9LDEpfWVsc2V7ZG9SdW4oKX19dmFyIHdhc21FeHBvcnRzO2lmKCFFTlZJUk9OTUVOVF9JU19QVEhSRUFEKXt3YXNtRXhwb3J0cz1hd2FpdCAoY3JlYXRlV2FzbSgpKTtydW4oKX1pZihydW50aW1lSW5pdGlhbGl6ZWQpe21vZHVsZVJ0bj1Nb2R1bGV9ZWxzZXttb2R1bGVSdG49bmV3IFByb21pc2UoKHJlc29sdmUscmVqZWN0KT0+e3JlYWR5UHJvbWlzZVJlc29sdmU9cmVzb2x2ZTtyZWFkeVByb21pc2VSZWplY3Q9cmVqZWN0fSl9CjtyZXR1cm4gbW9kdWxlUnRufWV4cG9ydCBkZWZhdWx0IGNyZWF0ZVNrYWluZXRLZXJuZWxzO3ZhciBpc1B0aHJlYWQ9Z2xvYmFsVGhpcy5zZWxmPy5uYW1lPy5zdGFydHNXaXRoKCJlbS1wdGhyZWFkIik7dmFyIGlzTm9kZT10eXBlb2YgcHJvY2Vzcz09Im9iamVjdCImJnByb2Nlc3MudmVyc2lvbnM/Lm5vZGUmJnByb2Nlc3MudHlwZSE9InJlbmRlcmVyIjtpZihpc05vZGUpaXNQdGhyZWFkPShhd2FpdCBpbXBvcnQoIndvcmtlcl90aHJlYWRzIikpLndvcmtlckRhdGE9PT0iZW0tcHRocmVhZCI7aXNQdGhyZWFkJiZjcmVhdGVTa2FpbmV0S2VybmVscygpOwo=" - const val MT_WASM: String = "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NgIAIAFBHGtBADYCACACIABBBHFBGHIiAmsiAUEgSQ0AIAAgAmohAANAIABCADcDGCAAQgA3AxAgAEIANwMIIABCADcDACAAQSBqIQAgAUEgayIBQR9LDQALCwvyAQEFfyMAQRBrIgMkACADQQA2AgwgAEEgaiIGEBogACgCFCICQQBHIQUCQCABRQ0AIAJFDQADQAJAIAJBCGpBAEEB/kgCAARAIAMgAygCDEEBajYCDCACIANBDGo2AhAMAQsgBCACIAQbIQQgAUEBayEBCyACKAIAIgJBAEchBSABRQ0BIAINAAsLAkAgBQRAIAIoAgQiAQRAIAFBADYCAAsgAkEANgIEDAELIABBADYCBAsgACACNgIUIAYQGSADKAIMIgIEQANAIANBDGpBACACEBMgAygCDCICDQALCyAEBEAgBEEMahAZCyADQRBqJAALYgEBfwJAIwUEQEGEDf4QAgANASMBIgAtAARFBEAgAEEBOgAEQZwLEA4aQZwLIwMQNiEAQZwLEA0CQCAARQ0AIAAoAiANACAAECcLIwFBADoABAsLDwtBhA3+EAIAEAcQBgALhwEBA38jAEEgayIBJAACQCAAKAIIRQRAIABBEGoiAhAOGiAAQQI2AgwgAhANIABBKGoQKAwBCyAAKAIYBEAgACgCECECIAAoAgwgASAANgIcIAEgADYCECABQRc2AhggAUEcNgIUIAEgASkCFDcDCCACIAFBCGoQHA0BCyAAEBYLIAFBIGokAAuCAQECfyAAKAIIRQRAIABBEGoQLQJAIABBKGoiACgCAEUNACAAKAIMRQ0AIABBDGoiAUGAgICAeP4zAgAaIABBCGoiAkEB/h4CABogAkH/////BxAPIAAoAgwiAEH/////B3FFDQADQCABQQAgABATIAEoAgAiAEH/////B3ENAAsLCwtlAQJ/IAAgACgCWEYEQCAAQgA3AlhBwAsoAgBBABAgDwsjAygCSEHACygCACIBQQJ0aigCACICIABGBEAgASACKAJYECALIAAoAlwiAiAAKAJYIgE2AlggASACNgJcIABCADcCWAueCQEOfwJAIAAgARA2IgJFBEAgACgCHCICIAAoAiBGBEACfyACQQF0QQEgAhsiD0ECdCEJIAAoAhgiDEUEQCAJEBEMAQsgCUFATwRAIwFBMDYCAEEADAELAkBBtBkt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" + const val ST_MJS: String = "YXN5bmMgZnVuY3Rpb24gY3JlYXRlU2thaW5ldEtlcm5lbHMobW9kdWxlQXJnPXt9KXt2YXIgbW9kdWxlUnRuO3ZhciBNb2R1bGU9bW9kdWxlQXJnO3ZhciBFTlZJUk9OTUVOVF9JU19XRUI9dHlwZW9mIHdpbmRvdz09Im9iamVjdCI7dmFyIEVOVklST05NRU5UX0lTX1dPUktFUj10eXBlb2YgV29ya2VyR2xvYmFsU2NvcGUhPSJ1bmRlZmluZWQiO3ZhciBFTlZJUk9OTUVOVF9JU19OT0RFPXR5cGVvZiBwcm9jZXNzPT0ib2JqZWN0IiYmcHJvY2Vzcy52ZXJzaW9ucz8ubm9kZSYmcHJvY2Vzcy50eXBlIT0icmVuZGVyZXIiO2lmKEVOVklST05NRU5UX0lTX05PREUpe2NvbnN0e2NyZWF0ZVJlcXVpcmV9PWF3YWl0IGltcG9ydCgibW9kdWxlIik7dmFyIHJlcXVpcmU9Y3JlYXRlUmVxdWlyZShpbXBvcnQubWV0YS51cmwuc3RhcnRzV2l0aCgiZmlsZToiKT9pbXBvcnQubWV0YS51cmw6cHJvY2Vzcy5jd2QoKSsiLyIpfXZhciBhcmd1bWVudHNfPVtdO3ZhciB0aGlzUHJvZ3JhbT0iLi90aGlzLnByb2dyYW0iO3ZhciBxdWl0Xz0oc3RhdHVzLHRvVGhyb3cpPT57dGhyb3cgdG9UaHJvd307dmFyIF9zY3JpcHROYW1lPWltcG9ydC5tZXRhLnVybDt2YXIgc2NyaXB0RGlyZWN0b3J5PSIiO2Z1bmN0aW9uIGxvY2F0ZUZpbGUocGF0aCl7aWYoTW9kdWxlWyJsb2NhdGVGaWxlIl0pe3JldHVybiBNb2R1bGVbImxvY2F0ZUZpbGUiXShwYXRoLHNjcmlwdERpcmVjdG9yeSl9cmV0dXJuIHNjcmlwdERpcmVjdG9yeStwYXRofXZhciByZWFkQXN5bmMscmVhZEJpbmFyeTtpZihFTlZJUk9OTUVOVF9JU19OT0RFKXt2YXIgZnM9cmVxdWlyZSgiZnMiKTtpZihfc2NyaXB0TmFtZS5zdGFydHNXaXRoKCJmaWxlOiIpKXtzY3JpcHREaXJlY3Rvcnk9cmVxdWlyZSgicGF0aCIpLmRpcm5hbWUocmVxdWlyZSgidXJsIikuZmlsZVVSTFRvUGF0aChfc2NyaXB0TmFtZSkpKyIvIn1yZWFkQmluYXJ5PWZpbGVuYW1lPT57ZmlsZW5hbWU9aXNGaWxlVVJJKGZpbGVuYW1lKT9uZXcgVVJMKGZpbGVuYW1lKTpmaWxlbmFtZTt2YXIgcmV0PWZzLnJlYWRGaWxlU3luYyhmaWxlbmFtZSk7cmV0dXJuIHJldH07cmVhZEFzeW5jPWFzeW5jKGZpbGVuYW1lLGJpbmFyeT10cnVlKT0+e2ZpbGVuYW1lPWlzRmlsZVVSSShmaWxlbmFtZSk/bmV3IFVSTChmaWxlbmFtZSk6ZmlsZW5hbWU7dmFyIHJldD1mcy5yZWFkRmlsZVN5bmMoZmlsZW5hbWUsYmluYXJ5P3VuZGVmaW5lZDoidXRmOCIpO3JldHVybiByZXR9O2lmKHByb2Nlc3MuYXJndi5sZW5ndGg+MSl7dGhpc1Byb2dyYW09cHJvY2Vzcy5hcmd2WzFdLnJlcGxhY2UoL1xcL2csIi8iKX1hcmd1bWVudHNfPXByb2Nlc3MuYXJndi5zbGljZSgyKTtxdWl0Xz0oc3RhdHVzLHRvVGhyb3cpPT57cHJvY2Vzcy5leGl0Q29kZT1zdGF0dXM7dGhyb3cgdG9UaHJvd319ZWxzZSBpZihFTlZJUk9OTUVOVF9JU19XRUJ8fEVOVklST05NRU5UX0lTX1dPUktFUil7dHJ5e3NjcmlwdERpcmVjdG9yeT1uZXcgVVJMKCIuIixfc2NyaXB0TmFtZSkuaHJlZn1jYXRjaHt9e2lmKEVOVklST05NRU5UX0lTX1dPUktFUil7cmVhZEJpbmFyeT11cmw9Pnt2YXIgeGhyPW5ldyBYTUxIdHRwUmVxdWVzdDt4aHIub3BlbigiR0VUIix1cmwsZmFsc2UpO3hoci5yZXNwb25zZVR5cGU9ImFycmF5YnVmZmVyIjt4aHIuc2VuZChudWxsKTtyZXR1cm4gbmV3IFVpbnQ4QXJyYXkoeGhyLnJlc3BvbnNlKX19cmVhZEFzeW5jPWFzeW5jIHVybD0+e3ZhciByZXNwb25zZT1hd2FpdCBmZXRjaCh1cmwse2NyZWRlbnRpYWxzOiJzYW1lLW9yaWdpbiJ9KTtpZihyZXNwb25zZS5vayl7cmV0dXJuIHJlc3BvbnNlLmFycmF5QnVmZmVyKCl9dGhyb3cgbmV3IEVycm9yKHJlc3BvbnNlLnN0YXR1cysiIDogIityZXNwb25zZS51cmwpfX19ZWxzZXt9dmFyIG91dD1jb25zb2xlLmxvZy5iaW5kKGNvbnNvbGUpO3ZhciBlcnI9Y29uc29sZS5lcnJvci5iaW5kKGNvbnNvbGUpO3ZhciB3YXNtQmluYXJ5O3ZhciBBQk9SVD1mYWxzZTt2YXIgaXNGaWxlVVJJPWZpbGVuYW1lPT5maWxlbmFtZS5zdGFydHNXaXRoKCJmaWxlOi8vIik7dmFyIHJlYWR5UHJvbWlzZVJlc29sdmUscmVhZHlQcm9taXNlUmVqZWN0O3ZhciB3YXNtTWVtb3J5O3ZhciBIRUFQOCxIRUFQVTgsSEVBUDE2LEhFQVBVMTYsSEVBUDMyLEhFQVBVMzIsSEVBUEYzMixIRUFQRjY0O3ZhciBIRUFQNjQsSEVBUFU2NDt2YXIgcnVudGltZUluaXRpYWxpemVkPWZhbHNlO2Z1bmN0aW9uIHVwZGF0ZU1lbW9yeVZpZXdzKCl7dmFyIGI9d2FzbU1lbW9yeS5idWZmZXI7SEVBUDg9bmV3IEludDhBcnJheShiKTtIRUFQMTY9bmV3IEludDE2QXJyYXkoYik7SEVBUFU4PW5ldyBVaW50OEFycmF5KGIpO0hFQVBVMTY9bmV3IFVpbnQxNkFycmF5KGIpO0hFQVAzMj1uZXcgSW50MzJBcnJheShiKTtIRUFQVTMyPW5ldyBVaW50MzJBcnJheShiKTtIRUFQRjMyPW5ldyBGbG9hdDMyQXJyYXkoYik7SEVBUEY2ND1uZXcgRmxvYXQ2NEFycmF5KGIpO0hFQVA2ND1uZXcgQmlnSW50NjRBcnJheShiKTtIRUFQVTY0PW5ldyBCaWdVaW50NjRBcnJheShiKX1mdW5jdGlvbiBwcmVSdW4oKXtpZihNb2R1bGVbInByZVJ1biJdKXtpZih0eXBlb2YgTW9kdWxlWyJwcmVSdW4iXT09ImZ1bmN0aW9uIilNb2R1bGVbInByZVJ1biJdPVtNb2R1bGVbInByZVJ1biJdXTt3aGlsZShNb2R1bGVbInByZVJ1biJdLmxlbmd0aCl7YWRkT25QcmVSdW4oTW9kdWxlWyJwcmVSdW4iXS5zaGlmdCgpKX19Y2FsbFJ1bnRpbWVDYWxsYmFja3Mob25QcmVSdW5zKX1mdW5jdGlvbiBpbml0UnVudGltZSgpe3J1bnRpbWVJbml0aWFsaXplZD10cnVlO3dhc21FeHBvcnRzWyJjIl0oKX1mdW5jdGlvbiBwb3N0UnVuKCl7aWYoTW9kdWxlWyJwb3N0UnVuIl0pe2lmKHR5cGVvZiBNb2R1bGVbInBvc3RSdW4iXT09ImZ1bmN0aW9uIilNb2R1bGVbInBvc3RSdW4iXT1bTW9kdWxlWyJwb3N0UnVuIl1dO3doaWxlKE1vZHVsZVsicG9zdFJ1biJdLmxlbmd0aCl7YWRkT25Qb3N0UnVuKE1vZHVsZVsicG9zdFJ1biJdLnNoaWZ0KCkpfX1jYWxsUnVudGltZUNhbGxiYWNrcyhvblBvc3RSdW5zKX1mdW5jdGlvbiBhYm9ydCh3aGF0KXtNb2R1bGVbIm9uQWJvcnQiXT8uKHdoYXQpO3doYXQ9IkFib3J0ZWQoIit3aGF0KyIpIjtlcnIod2hhdCk7QUJPUlQ9dHJ1ZTt3aGF0Kz0iLiBCdWlsZCB3aXRoIC1zQVNTRVJUSU9OUyBmb3IgbW9yZSBpbmZvLiI7dmFyIGU9bmV3IFdlYkFzc2VtYmx5LlJ1bnRpbWVFcnJvcih3aGF0KTtyZWFkeVByb21pc2VSZWplY3Q/LihlKTt0aHJvdyBlfXZhciB3YXNtQmluYXJ5RmlsZTtmdW5jdGlvbiBmaW5kV2FzbUJpbmFyeSgpe2lmKE1vZHVsZVsibG9jYXRlRmlsZSJdKXtyZXR1cm4gbG9jYXRlRmlsZSgic2thaW5ldF9rZXJuZWxzX3N0Lndhc20iKX10cnl7cmV0dXJuIG5ldyBVUkwoInNrYWluZXRfa2VybmVsc19zdC53YXNtIixpbXBvcnQubWV0YS51cmwpLmhyZWZ9Y2F0Y2goZSl7cmV0dXJuICJza2FpbmV0X2tlcm5lbHNfc3Qud2FzbSJ9fWZ1bmN0aW9uIGdldEJpbmFyeVN5bmMoZmlsZSl7aWYoZmlsZT09d2FzbUJpbmFyeUZpbGUmJndhc21CaW5hcnkpe3JldHVybiBuZXcgVWludDhBcnJheSh3YXNtQmluYXJ5KX1pZihyZWFkQmluYXJ5KXtyZXR1cm4gcmVhZEJpbmFyeShmaWxlKX10aHJvdyJib3RoIGFzeW5jIGFuZCBzeW5jIGZldGNoaW5nIG9mIHRoZSB3YXNtIGZhaWxlZCJ9YXN5bmMgZnVuY3Rpb24gZ2V0V2FzbUJpbmFyeShiaW5hcnlGaWxlKXtpZighd2FzbUJpbmFyeSl7dHJ5e3ZhciByZXNwb25zZT1hd2FpdCByZWFkQXN5bmMoYmluYXJ5RmlsZSk7cmV0dXJuIG5ldyBVaW50OEFycmF5KHJlc3BvbnNlKX1jYXRjaHt9fXJldHVybiBnZXRCaW5hcnlTeW5jKGJpbmFyeUZpbGUpfWFzeW5jIGZ1bmN0aW9uIGluc3RhbnRpYXRlQXJyYXlCdWZmZXIoYmluYXJ5RmlsZSxpbXBvcnRzKXt0cnl7dmFyIGJpbmFyeT1hd2FpdCBnZXRXYXNtQmluYXJ5KGJpbmFyeUZpbGUpO3ZhciBpbnN0YW5jZT1hd2FpdCBXZWJBc3NlbWJseS5pbnN0YW50aWF0ZShiaW5hcnksaW1wb3J0cyk7cmV0dXJuIGluc3RhbmNlfWNhdGNoKHJlYXNvbil7ZXJyKGBmYWlsZWQgdG8gYXN5bmNocm9ub3VzbHkgcHJlcGFyZSB3YXNtOiAke3JlYXNvbn1gKTthYm9ydChyZWFzb24pfX1hc3luYyBmdW5jdGlvbiBpbnN0YW50aWF0ZUFzeW5jKGJpbmFyeSxiaW5hcnlGaWxlLGltcG9ydHMpe2lmKCFiaW5hcnkmJiFFTlZJUk9OTUVOVF9JU19OT0RFKXt0cnl7dmFyIHJlc3BvbnNlPWZldGNoKGJpbmFyeUZpbGUse2NyZWRlbnRpYWxzOiJzYW1lLW9yaWdpbiJ9KTt2YXIgaW5zdGFudGlhdGlvblJlc3VsdD1hd2FpdCBXZWJBc3NlbWJseS5pbnN0YW50aWF0ZVN0cmVhbWluZyhyZXNwb25zZSxpbXBvcnRzKTtyZXR1cm4gaW5zdGFudGlhdGlvblJlc3VsdH1jYXRjaChyZWFzb24pe2Vycihgd2FzbSBzdHJlYW1pbmcgY29tcGlsZSBmYWlsZWQ6ICR7cmVhc29ufWApO2VycigiZmFsbGluZyBiYWNrIHRvIEFycmF5QnVmZmVyIGluc3RhbnRpYXRpb24iKX19cmV0dXJuIGluc3RhbnRpYXRlQXJyYXlCdWZmZXIoYmluYXJ5RmlsZSxpbXBvcnRzKX1mdW5jdGlvbiBnZXRXYXNtSW1wb3J0cygpe3JldHVybnthOndhc21JbXBvcnRzfX1hc3luYyBmdW5jdGlvbiBjcmVhdGVXYXNtKCl7ZnVuY3Rpb24gcmVjZWl2ZUluc3RhbmNlKGluc3RhbmNlLG1vZHVsZSl7d2FzbUV4cG9ydHM9aW5zdGFuY2UuZXhwb3J0czt3YXNtRXhwb3J0cz1hcHBseVNpZ25hdHVyZUNvbnZlcnNpb25zKHdhc21FeHBvcnRzKTt3YXNtTWVtb3J5PXdhc21FeHBvcnRzWyJiIl07TW9kdWxlWyJ3YXNtTWVtb3J5Il09d2FzbU1lbW9yeTt1cGRhdGVNZW1vcnlWaWV3cygpO2Fzc2lnbldhc21FeHBvcnRzKHdhc21FeHBvcnRzKTtyZXR1cm4gd2FzbUV4cG9ydHN9ZnVuY3Rpb24gcmVjZWl2ZUluc3RhbnRpYXRpb25SZXN1bHQocmVzdWx0KXtyZXR1cm4gcmVjZWl2ZUluc3RhbmNlKHJlc3VsdFsiaW5zdGFuY2UiXSl9dmFyIGluZm89Z2V0V2FzbUltcG9ydHMoKTtpZihNb2R1bGVbImluc3RhbnRpYXRlV2FzbSJdKXtyZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUscmVqZWN0KT0+e01vZHVsZVsiaW5zdGFudGlhdGVXYXNtIl0oaW5mbywobW9kLGluc3QpPT57cmVzb2x2ZShyZWNlaXZlSW5zdGFuY2UobW9kLGluc3QpKX0pfSl9d2FzbUJpbmFyeUZpbGU/Pz1maW5kV2FzbUJpbmFyeSgpO3ZhciByZXN1bHQ9YXdhaXQgaW5zdGFudGlhdGVBc3luYyh3YXNtQmluYXJ5LHdhc21CaW5hcnlGaWxlLGluZm8pO3ZhciBleHBvcnRzPXJlY2VpdmVJbnN0YW50aWF0aW9uUmVzdWx0KHJlc3VsdCk7cmV0dXJuIGV4cG9ydHN9Y2xhc3MgRXhpdFN0YXR1c3tuYW1lPSJFeGl0U3RhdHVzIjtjb25zdHJ1Y3RvcihzdGF0dXMpe3RoaXMubWVzc2FnZT1gUHJvZ3JhbSB0ZXJtaW5hdGVkIHdpdGggZXhpdCgke3N0YXR1c30pYDt0aGlzLnN0YXR1cz1zdGF0dXN9fXZhciBjYWxsUnVudGltZUNhbGxiYWNrcz1jYWxsYmFja3M9Pnt3aGlsZShjYWxsYmFja3MubGVuZ3RoPjApe2NhbGxiYWNrcy5zaGlmdCgpKE1vZHVsZSl9fTt2YXIgb25Qb3N0UnVucz1bXTt2YXIgYWRkT25Qb3N0UnVuPWNiPT5vblBvc3RSdW5zLnB1c2goY2IpO3ZhciBvblByZVJ1bnM9W107dmFyIGFkZE9uUHJlUnVuPWNiPT5vblByZVJ1bnMucHVzaChjYik7dmFyIG5vRXhpdFJ1bnRpbWU9dHJ1ZTt2YXIgZ2V0SGVhcE1heD0oKT0+NDI5NDkwMTc2MDt2YXIgYWxpZ25NZW1vcnk9KHNpemUsYWxpZ25tZW50KT0+TWF0aC5jZWlsKHNpemUvYWxpZ25tZW50KSphbGlnbm1lbnQ7dmFyIGdyb3dNZW1vcnk9c2l6ZT0+e3ZhciBvbGRIZWFwU2l6ZT13YXNtTWVtb3J5LmJ1ZmZlci5ieXRlTGVuZ3RoO3ZhciBwYWdlcz0oc2l6ZS1vbGRIZWFwU2l6ZSs2NTUzNSkvNjU1MzZ8MDt0cnl7d2FzbU1lbW9yeS5ncm93KHBhZ2VzKTt1cGRhdGVNZW1vcnlWaWV3cygpO3JldHVybiAxfWNhdGNoKGUpe319O2Z1bmN0aW9uIF9lbXNjcmlwdGVuX3Jlc2l6ZV9oZWFwKHJlcXVlc3RlZFNpemUpe3JlcXVlc3RlZFNpemU+Pj49MDt2YXIgb2xkU2l6ZT1IRUFQVTgubGVuZ3RoO3ZhciBtYXhIZWFwU2l6ZT1nZXRIZWFwTWF4KCk7aWYocmVxdWVzdGVkU2l6ZT5tYXhIZWFwU2l6ZSl7cmV0dXJuIGZhbHNlfWZvcih2YXIgY3V0RG93bj0xO2N1dERvd248PTQ7Y3V0RG93bio9Mil7dmFyIG92ZXJHcm93bkhlYXBTaXplPW9sZFNpemUqKDErLjIvY3V0RG93bik7b3Zlckdyb3duSGVhcFNpemU9TWF0aC5taW4ob3Zlckdyb3duSGVhcFNpemUscmVxdWVzdGVkU2l6ZSsxMDA2NjMyOTYpO3ZhciBuZXdTaXplPU1hdGgubWluKG1heEhlYXBTaXplLGFsaWduTWVtb3J5KE1hdGgubWF4KHJlcXVlc3RlZFNpemUsb3Zlckdyb3duSGVhcFNpemUpLDY1NTM2KSk7dmFyIHJlcGxhY2VtZW50PWdyb3dNZW1vcnkobmV3U2l6ZSk7aWYocmVwbGFjZW1lbnQpe3JldHVybiB0cnVlfX1yZXR1cm4gZmFsc2V9e2lmKE1vZHVsZVsibm9FeGl0UnVudGltZSJdKW5vRXhpdFJ1bnRpbWU9TW9kdWxlWyJub0V4aXRSdW50aW1lIl07aWYoTW9kdWxlWyJwcmludCJdKW91dD1Nb2R1bGVbInByaW50Il07aWYoTW9kdWxlWyJwcmludEVyciJdKWVycj1Nb2R1bGVbInByaW50RXJyIl07aWYoTW9kdWxlWyJ3YXNtQmluYXJ5Il0pd2FzbUJpbmFyeT1Nb2R1bGVbIndhc21CaW5hcnkiXTtpZihNb2R1bGVbImFyZ3VtZW50cyJdKWFyZ3VtZW50c189TW9kdWxlWyJhcmd1bWVudHMiXTtpZihNb2R1bGVbInRoaXNQcm9ncmFtIl0pdGhpc1Byb2dyYW09TW9kdWxlWyJ0aGlzUHJvZ3JhbSJdO2lmKE1vZHVsZVsicHJlSW5pdCJdKXtpZih0eXBlb2YgTW9kdWxlWyJwcmVJbml0Il09PSJmdW5jdGlvbiIpTW9kdWxlWyJwcmVJbml0Il09W01vZHVsZVsicHJlSW5pdCJdXTt3aGlsZShNb2R1bGVbInByZUluaXQiXS5sZW5ndGg+MCl7TW9kdWxlWyJwcmVJbml0Il0uc2hpZnQoKSgpfX19TW9kdWxlWyJ3YXNtTWVtb3J5Il09d2FzbU1lbW9yeTt2YXIgX3NrYWluZXRfZnAzMl9tYXRtdWwsX3NrYWluZXRfZnAzMl9tYXRtdWxfd3QsX3NrYWluZXRfYmYxNl9tYXRtdWwsX3NrYWluZXRfZnAxNl9tYXRtdWwsX3NrYWluZXRfcTRfMF9tYXRtdWwsX3NrYWluZXRfcTRfMF9tYXRtdWxfcm0sX3NrYWluZXRfcTVfMF9tYXRtdWwsX21hbGxvYyxfZnJlZSxfc2thaW5ldF9xNV8wX21hdG11bF9ybSxfc2thaW5ldF9xNV8xX21hdG11bCxfc2thaW5ldF9xNV8xX21hdG11bF9ybSxfc2thaW5ldF9xOF8wX21hdG11bCxfc2thaW5ldF9xOF8wX21hdG11bF9ybSxfc2thaW5ldF9xNGtfbWF0bXVsLF9za2FpbmV0X3E0a19tYXRtdWxfcm0sX3NrYWluZXRfcTVrX21hdG11bCxfc2thaW5ldF9xNWtfbWF0bXVsX3JtLF9za2FpbmV0X3E2a19tYXRtdWwsX3NrYWluZXRfcTZrX21hdG11bF9ybSxfc2thaW5ldF90ZXJuYXJ5X2YzMl9nZW12LF9za2FpbmV0X3Rlcm5hcnlfbG1oZWFkX3N0YWdlMSxfc2thaW5ldF9iaXRuZXRfZ2Vtdl90cTJfMCxfc2thaW5ldF9mMzJfYmluYXJ5LF9za2FpbmV0X2YzMl91bmFyeSxfc2thaW5ldF9mMzJfcmVkdWNlX2FsbCxfc2thaW5ldF9mMzJfcmVkdWNlX2F4aXMsX3NrYWluZXRfZjMyX3NvZnRtYXhfcm93cyxfc2thaW5ldF9mMzJfbGF5ZXJfbm9ybSxfc2thaW5ldF9mMzJfcm1zX25vcm0sX3NrYWluZXRfZjMyX3NkcGE7ZnVuY3Rpb24gYXNzaWduV2FzbUV4cG9ydHMod2FzbUV4cG9ydHMpe01vZHVsZVsiX3NrYWluZXRfZnAzMl9tYXRtdWwiXT1fc2thaW5ldF9mcDMyX21hdG11bD13YXNtRXhwb3J0c1siZCJdO01vZHVsZVsiX3NrYWluZXRfZnAzMl9tYXRtdWxfd3QiXT1fc2thaW5ldF9mcDMyX21hdG11bF93dD13YXNtRXhwb3J0c1siZSJdO01vZHVsZVsiX3NrYWluZXRfYmYxNl9tYXRtdWwiXT1fc2thaW5ldF9iZjE2X21hdG11bD13YXNtRXhwb3J0c1siZiJdO01vZHVsZVsiX3NrYWluZXRfZnAxNl9tYXRtdWwiXT1fc2thaW5ldF9mcDE2X21hdG11bD13YXNtRXhwb3J0c1siZyJdO01vZHVsZVsiX3NrYWluZXRfcTRfMF9tYXRtdWwiXT1fc2thaW5ldF9xNF8wX21hdG11bD13YXNtRXhwb3J0c1siaCJdO01vZHVsZVsiX3NrYWluZXRfcTRfMF9tYXRtdWxfcm0iXT1fc2thaW5ldF9xNF8wX21hdG11bF9ybT13YXNtRXhwb3J0c1siaSJdO01vZHVsZVsiX3NrYWluZXRfcTVfMF9tYXRtdWwiXT1fc2thaW5ldF9xNV8wX21hdG11bD13YXNtRXhwb3J0c1siaiJdO01vZHVsZVsiX21hbGxvYyJdPV9tYWxsb2M9d2FzbUV4cG9ydHNbImsiXTtNb2R1bGVbIl9mcmVlIl09X2ZyZWU9d2FzbUV4cG9ydHNbImwiXTtNb2R1bGVbIl9za2FpbmV0X3E1XzBfbWF0bXVsX3JtIl09X3NrYWluZXRfcTVfMF9tYXRtdWxfcm09d2FzbUV4cG9ydHNbIm0iXTtNb2R1bGVbIl9za2FpbmV0X3E1XzFfbWF0bXVsIl09X3NrYWluZXRfcTVfMV9tYXRtdWw9d2FzbUV4cG9ydHNbIm4iXTtNb2R1bGVbIl9za2FpbmV0X3E1XzFfbWF0bXVsX3JtIl09X3NrYWluZXRfcTVfMV9tYXRtdWxfcm09d2FzbUV4cG9ydHNbIm8iXTtNb2R1bGVbIl9za2FpbmV0X3E4XzBfbWF0bXVsIl09X3NrYWluZXRfcThfMF9tYXRtdWw9d2FzbUV4cG9ydHNbInAiXTtNb2R1bGVbIl9za2FpbmV0X3E4XzBfbWF0bXVsX3JtIl09X3NrYWluZXRfcThfMF9tYXRtdWxfcm09d2FzbUV4cG9ydHNbInEiXTtNb2R1bGVbIl9za2FpbmV0X3E0a19tYXRtdWwiXT1fc2thaW5ldF9xNGtfbWF0bXVsPXdhc21FeHBvcnRzWyJyIl07TW9kdWxlWyJfc2thaW5ldF9xNGtfbWF0bXVsX3JtIl09X3NrYWluZXRfcTRrX21hdG11bF9ybT13YXNtRXhwb3J0c1sicyJdO01vZHVsZVsiX3NrYWluZXRfcTVrX21hdG11bCJdPV9za2FpbmV0X3E1a19tYXRtdWw9d2FzbUV4cG9ydHNbInQiXTtNb2R1bGVbIl9za2FpbmV0X3E1a19tYXRtdWxfcm0iXT1fc2thaW5ldF9xNWtfbWF0bXVsX3JtPXdhc21FeHBvcnRzWyJ1Il07TW9kdWxlWyJfc2thaW5ldF9xNmtfbWF0bXVsIl09X3NrYWluZXRfcTZrX21hdG11bD13YXNtRXhwb3J0c1sidiJdO01vZHVsZVsiX3NrYWluZXRfcTZrX21hdG11bF9ybSJdPV9za2FpbmV0X3E2a19tYXRtdWxfcm09d2FzbUV4cG9ydHNbInciXTtNb2R1bGVbIl9za2FpbmV0X3Rlcm5hcnlfZjMyX2dlbXYiXT1fc2thaW5ldF90ZXJuYXJ5X2YzMl9nZW12PXdhc21FeHBvcnRzWyJ4Il07TW9kdWxlWyJfc2thaW5ldF90ZXJuYXJ5X2xtaGVhZF9zdGFnZTEiXT1fc2thaW5ldF90ZXJuYXJ5X2xtaGVhZF9zdGFnZTE9d2FzbUV4cG9ydHNbInkiXTtNb2R1bGVbIl9za2FpbmV0X2JpdG5ldF9nZW12X3RxMl8wIl09X3NrYWluZXRfYml0bmV0X2dlbXZfdHEyXzA9d2FzbUV4cG9ydHNbInoiXTtNb2R1bGVbIl9za2FpbmV0X2YzMl9iaW5hcnkiXT1fc2thaW5ldF9mMzJfYmluYXJ5PXdhc21FeHBvcnRzWyJBIl07TW9kdWxlWyJfc2thaW5ldF9mMzJfdW5hcnkiXT1fc2thaW5ldF9mMzJfdW5hcnk9d2FzbUV4cG9ydHNbIkIiXTtNb2R1bGVbIl9za2FpbmV0X2YzMl9yZWR1Y2VfYWxsIl09X3NrYWluZXRfZjMyX3JlZHVjZV9hbGw9d2FzbUV4cG9ydHNbIkMiXTtNb2R1bGVbIl9za2FpbmV0X2YzMl9yZWR1Y2VfYXhpcyJdPV9za2FpbmV0X2YzMl9yZWR1Y2VfYXhpcz13YXNtRXhwb3J0c1siRCJdO01vZHVsZVsiX3NrYWluZXRfZjMyX3NvZnRtYXhfcm93cyJdPV9za2FpbmV0X2YzMl9zb2Z0bWF4X3Jvd3M9d2FzbUV4cG9ydHNbIkUiXTtNb2R1bGVbIl9za2FpbmV0X2YzMl9sYXllcl9ub3JtIl09X3NrYWluZXRfZjMyX2xheWVyX25vcm09d2FzbUV4cG9ydHNbIkYiXTtNb2R1bGVbIl9za2FpbmV0X2YzMl9ybXNfbm9ybSJdPV9za2FpbmV0X2YzMl9ybXNfbm9ybT13YXNtRXhwb3J0c1siRyJdO01vZHVsZVsiX3NrYWluZXRfZjMyX3NkcGEiXT1fc2thaW5ldF9mMzJfc2RwYT13YXNtRXhwb3J0c1siSCJdfXZhciB3YXNtSW1wb3J0cz17YTpfZW1zY3JpcHRlbl9yZXNpemVfaGVhcH07ZnVuY3Rpb24gYXBwbHlTaWduYXR1cmVDb252ZXJzaW9ucyh3YXNtRXhwb3J0cyl7d2FzbUV4cG9ydHM9T2JqZWN0LmFzc2lnbih7fSx3YXNtRXhwb3J0cyk7dmFyIG1ha2VXcmFwcGVyX3BwPWY9PmEwPT5mKGEwKT4+PjA7dmFyIG1ha2VXcmFwcGVyX3A9Zj0+KCk9PmYoKT4+PjA7d2FzbUV4cG9ydHNbImsiXT1tYWtlV3JhcHBlcl9wcCh3YXNtRXhwb3J0c1siayJdKTt3YXNtRXhwb3J0c1siX2Vtc2NyaXB0ZW5fc3RhY2tfYWxsb2MiXT1tYWtlV3JhcHBlcl9wcCh3YXNtRXhwb3J0c1siX2Vtc2NyaXB0ZW5fc3RhY2tfYWxsb2MiXSk7d2FzbUV4cG9ydHNbImVtc2NyaXB0ZW5fc3RhY2tfZ2V0X2N1cnJlbnQiXT1tYWtlV3JhcHBlcl9wKHdhc21FeHBvcnRzWyJlbXNjcmlwdGVuX3N0YWNrX2dldF9jdXJyZW50Il0pO3JldHVybiB3YXNtRXhwb3J0c31mdW5jdGlvbiBydW4oKXtwcmVSdW4oKTtmdW5jdGlvbiBkb1J1bigpe01vZHVsZVsiY2FsbGVkUnVuIl09dHJ1ZTtpZihBQk9SVClyZXR1cm47aW5pdFJ1bnRpbWUoKTtyZWFkeVByb21pc2VSZXNvbHZlPy4oTW9kdWxlKTtNb2R1bGVbIm9uUnVudGltZUluaXRpYWxpemVkIl0/LigpO3Bvc3RSdW4oKX1pZihNb2R1bGVbInNldFN0YXR1cyJdKXtNb2R1bGVbInNldFN0YXR1cyJdKCJSdW5uaW5nLi4uIik7c2V0VGltZW91dCgoKT0+e3NldFRpbWVvdXQoKCk9Pk1vZHVsZVsic2V0U3RhdHVzIl0oIiIpLDEpO2RvUnVuKCl9LDEpfWVsc2V7ZG9SdW4oKX19dmFyIHdhc21FeHBvcnRzO3dhc21FeHBvcnRzPWF3YWl0IChjcmVhdGVXYXNtKCkpO3J1bigpO2lmKHJ1bnRpbWVJbml0aWFsaXplZCl7bW9kdWxlUnRuPU1vZHVsZX1lbHNle21vZHVsZVJ0bj1uZXcgUHJvbWlzZSgocmVzb2x2ZSxyZWplY3QpPT57cmVhZHlQcm9taXNlUmVzb2x2ZT1yZXNvbHZlO3JlYWR5UHJvbWlzZVJlamVjdD1yZWplY3R9KX0KO3JldHVybiBtb2R1bGVSdG59ZXhwb3J0IGRlZmF1bHQgY3JlYXRlU2thaW5ldEtlcm5lbHM7Cg==" + const val ST_WASM: String = "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gD8AAAAAAACAPwAAgD8AAIA/AAAAAAAAAEAAAIA/AACAPwAAAAAAAIC/AAAAQAAAgD8AQdcVCzVAAACAPwAAAAAAAIA/AAAAQAAAgD8AAAAAAAAAQAAAAEAAAIA/AAAAAAAAgL8AAIC/AAAAQABBlhYLNoC/AAAAQAAAAAAAAIA/AACAvwAAAEAAAAAAAAAAQAAAgL8AAABAAAAAAAAAgL8AAAAAAAAAQABB2xYLMUAAAAAAAACAPwAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAIC/AACAPwAAAEAAQZYXCzaAPwAAAEAAAAAAAACAPwAAgD8AAABAAAAAAAAAAEAAAIA/AAAAQAAAAAAAAIC/AAAAQAAAAEAAQdcXC3lAAAAAQAAAAAAAAIA/AAAAQAAAAEAAAAAAAAAAQAAAAEAAAABAAAAAAAAAgL8AAIC/AACAvwAAgD8AAAAAAACAvwAAgL8AAIA/AACAPwAAgL8AAIC/AACAPwAAAEAAAIC/AACAvwAAgD8AAIC/AAAAAAAAgL8AAIA/AEHaGAvqAYC/AACAPwAAgD8AAAAAAACAvwAAgD8AAABAAAAAAAAAgL8AAIA/AACAvwAAgD8AAIC/AACAPwAAAAAAAIA/AACAvwAAgD8AAIA/AACAPwAAgL8AAIA/AAAAQAAAgD8AAIC/AACAPwAAgL8AAABAAACAvwAAgD8AAAAAAAAAQAAAgL8AAIA/AACAPwAAAEAAAIC/AACAPwAAAEAAAABAAACAvwAAgD8AAIC/AACAvwAAAAAAAIA/AAAAAAAAgL8AAAAAAACAPwAAgD8AAIC/AAAAAAAAgD8AAABAAACAvwAAAAAAAIA/AACAvwBBzhoLAoA/AEHeGgsGgD8AAIA/AEHuGgsGgD8AAABAAEH+GgvSAYA/AACAvwAAgD8AAAAAAACAPwAAAAAAAIA/AAAAAAAAgD8AAIA/AACAPwAAAAAAAIA/AAAAQAAAgD8AAAAAAACAPwAAgL8AAABAAAAAAAAAgD8AAAAAAAAAQAAAAAAAAIA/AACAPwAAAEAAAAAAAACAPwAAAEAAAABAAAAAAAAAgD8AAIC/AACAvwAAgD8AAIA/AAAAAAAAgL8AAIA/AACAPwAAgD8AAIC/AACAPwAAgD8AAABAAACAvwAAgD8AAIA/AACAvwAAAAAAAIA/AACAPwBB2hwL9gGAPwAAgD8AAIA/AAAAAAAAgD8AAIA/AAAAQAAAAAAAAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAAAAAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAAEAAAIA/AACAPwAAgD8AAIC/AAAAQAAAgD8AAIA/AAAAAAAAAEAAAIA/AACAPwAAgD8AAABAAACAPwAAgD8AAABAAAAAQAAAgD8AAIA/AACAvwAAgL8AAABAAACAPwAAAAAAAIC/AAAAQAAAgD8AAIA/AACAvwAAAEAAAIA/AAAAQAAAgL8AAABAAACAPwAAgL8AAAAAAAAAQAAAgD8AQdseC/UBQAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAQAAAAAAAAABAAACAPwAAgL8AAIA/AAAAQAAAgD8AAAAAAACAPwAAAEAAAIA/AACAPwAAgD8AAABAAACAPwAAAEAAAIA/AAAAQAAAgD8AAIC/AAAAQAAAAEAAAIA/AAAAAAAAAEAAAABAAACAPwAAgD8AAABAAAAAQAAAgD8AAABAAAAAQAAAAEAAAIA/AACAvwAAgL8AAIC/AAAAQAAAAAAAAIC/AACAvwAAAEAAAIA/AACAvwAAgL8AAABAAAAAQAAAgL8AAIC/AAAAQAAAgL8AAAAAAACAvwAAAEAAQdogC+oBgL8AAABAAACAPwAAAAAAAIC/AAAAQAAAAEAAAAAAAACAvwAAAEAAAIC/AACAPwAAgL8AAABAAAAAAAAAgD8AAIC/AAAAQAAAgD8AAIA/AACAvwAAAEAAAABAAACAPwAAgL8AAABAAACAvwAAAEAAAIC/AAAAQAAAAAAAAABAAACAvwAAAEAAAIA/AAAAQAAAgL8AAABAAAAAQAAAAEAAAIC/AAAAQAAAgL8AAIC/AAAAAAAAAEAAAAAAAACAvwAAAAAAAABAAACAPwAAgL8AAAAAAAAAQAAAAEAAAIC/AAAAAAAAAEAAAIC/AEHPIgsBQABB3yILBUAAAIA/AEHvIgsFQAAAAEAAQf8iC9EBQAAAgL8AAIA/AAAAAAAAAEAAAAAAAACAPwAAAAAAAABAAACAPwAAgD8AAAAAAAAAQAAAAEAAAIA/AAAAAAAAAEAAAIC/AAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAgD8AAABAAAAAAAAAAEAAAABA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" + const val MT_MJS: String = "YXN5bmMgZnVuY3Rpb24gY3JlYXRlU2thaW5ldEtlcm5lbHMobW9kdWxlQXJnPXt9KXt2YXIgbW9kdWxlUnRuO3ZhciBNb2R1bGU9bW9kdWxlQXJnO3ZhciBFTlZJUk9OTUVOVF9JU19XRUI9dHlwZW9mIHdpbmRvdz09Im9iamVjdCI7dmFyIEVOVklST05NRU5UX0lTX1dPUktFUj10eXBlb2YgV29ya2VyR2xvYmFsU2NvcGUhPSJ1bmRlZmluZWQiO3ZhciBFTlZJUk9OTUVOVF9JU19OT0RFPXR5cGVvZiBwcm9jZXNzPT0ib2JqZWN0IiYmcHJvY2Vzcy52ZXJzaW9ucz8ubm9kZSYmcHJvY2Vzcy50eXBlIT0icmVuZGVyZXIiO3ZhciBFTlZJUk9OTUVOVF9JU19QVEhSRUFEPUVOVklST05NRU5UX0lTX1dPUktFUiYmc2VsZi5uYW1lPy5zdGFydHNXaXRoKCJlbS1wdGhyZWFkIik7aWYoRU5WSVJPTk1FTlRfSVNfTk9ERSl7Y29uc3R7Y3JlYXRlUmVxdWlyZX09YXdhaXQgaW1wb3J0KCJtb2R1bGUiKTt2YXIgcmVxdWlyZT1jcmVhdGVSZXF1aXJlKGltcG9ydC5tZXRhLnVybC5zdGFydHNXaXRoKCJmaWxlOiIpP2ltcG9ydC5tZXRhLnVybDpwcm9jZXNzLmN3ZCgpKyIvIik7dmFyIHdvcmtlcl90aHJlYWRzPXJlcXVpcmUoIndvcmtlcl90aHJlYWRzIik7Z2xvYmFsLldvcmtlcj13b3JrZXJfdGhyZWFkcy5Xb3JrZXI7RU5WSVJPTk1FTlRfSVNfV09SS0VSPSF3b3JrZXJfdGhyZWFkcy5pc01haW5UaHJlYWQ7RU5WSVJPTk1FTlRfSVNfUFRIUkVBRD1FTlZJUk9OTUVOVF9JU19XT1JLRVImJndvcmtlcl90aHJlYWRzWyJ3b3JrZXJEYXRhIl09PSJlbS1wdGhyZWFkIn12YXIgYXJndW1lbnRzXz1bXTt2YXIgdGhpc1Byb2dyYW09Ii4vdGhpcy5wcm9ncmFtIjt2YXIgcXVpdF89KHN0YXR1cyx0b1Rocm93KT0+e3Rocm93IHRvVGhyb3d9O3ZhciBfc2NyaXB0TmFtZT1pbXBvcnQubWV0YS51cmw7dmFyIHNjcmlwdERpcmVjdG9yeT0iIjtmdW5jdGlvbiBsb2NhdGVGaWxlKHBhdGgpe2lmKE1vZHVsZVsibG9jYXRlRmlsZSJdKXtyZXR1cm4gTW9kdWxlWyJsb2NhdGVGaWxlIl0ocGF0aCxzY3JpcHREaXJlY3RvcnkpfXJldHVybiBzY3JpcHREaXJlY3RvcnkrcGF0aH12YXIgcmVhZEFzeW5jLHJlYWRCaW5hcnk7aWYoRU5WSVJPTk1FTlRfSVNfTk9ERSl7dmFyIGZzPXJlcXVpcmUoImZzIik7aWYoX3NjcmlwdE5hbWUuc3RhcnRzV2l0aCgiZmlsZToiKSl7c2NyaXB0RGlyZWN0b3J5PXJlcXVpcmUoInBhdGgiKS5kaXJuYW1lKHJlcXVpcmUoInVybCIpLmZpbGVVUkxUb1BhdGgoX3NjcmlwdE5hbWUpKSsiLyJ9cmVhZEJpbmFyeT1maWxlbmFtZT0+e2ZpbGVuYW1lPWlzRmlsZVVSSShmaWxlbmFtZSk/bmV3IFVSTChmaWxlbmFtZSk6ZmlsZW5hbWU7dmFyIHJldD1mcy5yZWFkRmlsZVN5bmMoZmlsZW5hbWUpO3JldHVybiByZXR9O3JlYWRBc3luYz1hc3luYyhmaWxlbmFtZSxiaW5hcnk9dHJ1ZSk9PntmaWxlbmFtZT1pc0ZpbGVVUkkoZmlsZW5hbWUpP25ldyBVUkwoZmlsZW5hbWUpOmZpbGVuYW1lO3ZhciByZXQ9ZnMucmVhZEZpbGVTeW5jKGZpbGVuYW1lLGJpbmFyeT91bmRlZmluZWQ6InV0ZjgiKTtyZXR1cm4gcmV0fTtpZihwcm9jZXNzLmFyZ3YubGVuZ3RoPjEpe3RoaXNQcm9ncmFtPXByb2Nlc3MuYXJndlsxXS5yZXBsYWNlKC9cXC9nLCIvIil9YXJndW1lbnRzXz1wcm9jZXNzLmFyZ3Yuc2xpY2UoMik7cXVpdF89KHN0YXR1cyx0b1Rocm93KT0+e3Byb2Nlc3MuZXhpdENvZGU9c3RhdHVzO3Rocm93IHRvVGhyb3d9fWVsc2UgaWYoRU5WSVJPTk1FTlRfSVNfV0VCfHxFTlZJUk9OTUVOVF9JU19XT1JLRVIpe3RyeXtzY3JpcHREaXJlY3Rvcnk9bmV3IFVSTCgiLiIsX3NjcmlwdE5hbWUpLmhyZWZ9Y2F0Y2h7fWlmKCFFTlZJUk9OTUVOVF9JU19OT0RFKXtpZihFTlZJUk9OTUVOVF9JU19XT1JLRVIpe3JlYWRCaW5hcnk9dXJsPT57dmFyIHhocj1uZXcgWE1MSHR0cFJlcXVlc3Q7eGhyLm9wZW4oIkdFVCIsdXJsLGZhbHNlKTt4aHIucmVzcG9uc2VUeXBlPSJhcnJheWJ1ZmZlciI7eGhyLnNlbmQobnVsbCk7cmV0dXJuIG5ldyBVaW50OEFycmF5KHhoci5yZXNwb25zZSl9fXJlYWRBc3luYz1hc3luYyB1cmw9Pnt2YXIgcmVzcG9uc2U9YXdhaXQgZmV0Y2godXJsLHtjcmVkZW50aWFsczoic2FtZS1vcmlnaW4ifSk7aWYocmVzcG9uc2Uub2spe3JldHVybiByZXNwb25zZS5hcnJheUJ1ZmZlcigpfXRocm93IG5ldyBFcnJvcihyZXNwb25zZS5zdGF0dXMrIiA6ICIrcmVzcG9uc2UudXJsKX19fWVsc2V7fXZhciBkZWZhdWx0UHJpbnQ9Y29uc29sZS5sb2cuYmluZChjb25zb2xlKTt2YXIgZGVmYXVsdFByaW50RXJyPWNvbnNvbGUuZXJyb3IuYmluZChjb25zb2xlKTtpZihFTlZJUk9OTUVOVF9JU19OT0RFKXt2YXIgdXRpbHM9cmVxdWlyZSgidXRpbCIpO3ZhciBzdHJpbmdpZnk9YT0+dHlwZW9mIGE9PSJvYmplY3QiP3V0aWxzLmluc3BlY3QoYSk6YTtkZWZhdWx0UHJpbnQ9KC4uLmFyZ3MpPT5mcy53cml0ZVN5bmMoMSxhcmdzLm1hcChzdHJpbmdpZnkpLmpvaW4oIiAiKSsiXG4iKTtkZWZhdWx0UHJpbnRFcnI9KC4uLmFyZ3MpPT5mcy53cml0ZVN5bmMoMixhcmdzLm1hcChzdHJpbmdpZnkpLmpvaW4oIiAiKSsiXG4iKX12YXIgb3V0PWRlZmF1bHRQcmludDt2YXIgZXJyPWRlZmF1bHRQcmludEVycjt2YXIgd2FzbUJpbmFyeTt2YXIgd2FzbU1vZHVsZTt2YXIgQUJPUlQ9ZmFsc2U7dmFyIEVYSVRTVEFUVVM7dmFyIGlzRmlsZVVSST1maWxlbmFtZT0+ZmlsZW5hbWUuc3RhcnRzV2l0aCgiZmlsZTovLyIpO2Z1bmN0aW9uIGdyb3dNZW1WaWV3cygpe2lmKHdhc21NZW1vcnkuYnVmZmVyIT1IRUFQOC5idWZmZXIpe3VwZGF0ZU1lbW9yeVZpZXdzKCl9fXZhciByZWFkeVByb21pc2VSZXNvbHZlLHJlYWR5UHJvbWlzZVJlamVjdDtpZihFTlZJUk9OTUVOVF9JU19OT0RFJiZFTlZJUk9OTUVOVF9JU19QVEhSRUFEKXt2YXIgcGFyZW50UG9ydD13b3JrZXJfdGhyZWFkc1sicGFyZW50UG9ydCJdO3BhcmVudFBvcnQub24oIm1lc3NhZ2UiLG1zZz0+Z2xvYmFsLm9ubWVzc2FnZT8uKHtkYXRhOm1zZ30pKTtPYmplY3QuYXNzaWduKGdsb2JhbFRoaXMse3NlbGY6Z2xvYmFsLHBvc3RNZXNzYWdlOm1zZz0+cGFyZW50UG9ydFsicG9zdE1lc3NhZ2UiXShtc2cpfSk7cHJvY2Vzcy5vbigidW5jYXVnaHRFeGNlcHRpb24iLGVycj0+e3Bvc3RNZXNzYWdlKHtjbWQ6InVuY2F1Z2h0RXhjZXB0aW9uIixlcnJvcjplcnJ9KTtwcm9jZXNzLmV4aXQoMSl9KX12YXIgc3RhcnRXb3JrZXI7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7dmFyIGluaXRpYWxpemVkSlM9ZmFsc2U7c2VsZi5vbnVuaGFuZGxlZHJlamVjdGlvbj1lPT57dGhyb3cgZS5yZWFzb258fGV9O2Z1bmN0aW9uIGhhbmRsZU1lc3NhZ2UoZSl7dHJ5e3ZhciBtc2dEYXRhPWVbImRhdGEiXTt2YXIgY21kPW1zZ0RhdGEuY21kO2lmKGNtZD09PSJsb2FkIil7bGV0IG1lc3NhZ2VRdWV1ZT1bXTtzZWxmLm9ubWVzc2FnZT1lPT5tZXNzYWdlUXVldWUucHVzaChlKTtzdGFydFdvcmtlcj0oKT0+e3Bvc3RNZXNzYWdlKHtjbWQ6ImxvYWRlZCJ9KTtmb3IobGV0IG1zZyBvZiBtZXNzYWdlUXVldWUpe2hhbmRsZU1lc3NhZ2UobXNnKX1zZWxmLm9ubWVzc2FnZT1oYW5kbGVNZXNzYWdlfTtmb3IoY29uc3QgaGFuZGxlciBvZiBtc2dEYXRhLmhhbmRsZXJzKXtpZighTW9kdWxlW2hhbmRsZXJdfHxNb2R1bGVbaGFuZGxlcl0ucHJveHkpe01vZHVsZVtoYW5kbGVyXT0oLi4uYXJncyk9Pntwb3N0TWVzc2FnZSh7Y21kOiJjYWxsSGFuZGxlciIsaGFuZGxlcixhcmdzfSl9O2lmKGhhbmRsZXI9PSJwcmludCIpb3V0PU1vZHVsZVtoYW5kbGVyXTtpZihoYW5kbGVyPT0icHJpbnRFcnIiKWVycj1Nb2R1bGVbaGFuZGxlcl19fXdhc21NZW1vcnk9bXNnRGF0YS53YXNtTWVtb3J5O3VwZGF0ZU1lbW9yeVZpZXdzKCk7d2FzbU1vZHVsZT1tc2dEYXRhLndhc21Nb2R1bGU7Y3JlYXRlV2FzbSgpO3J1bigpfWVsc2UgaWYoY21kPT09InJ1biIpe2VzdGFibGlzaFN0YWNrU3BhY2UobXNnRGF0YS5wdGhyZWFkX3B0cik7X19lbXNjcmlwdGVuX3RocmVhZF9pbml0KG1zZ0RhdGEucHRocmVhZF9wdHIsMCwwLDEsMCwwKTtQVGhyZWFkLnRocmVhZEluaXRUTFMoKTtfX2Vtc2NyaXB0ZW5fdGhyZWFkX21haWxib3hfYXdhaXQobXNnRGF0YS5wdGhyZWFkX3B0cik7aWYoIWluaXRpYWxpemVkSlMpe2luaXRpYWxpemVkSlM9dHJ1ZX10cnl7aW52b2tlRW50cnlQb2ludChtc2dEYXRhLnN0YXJ0X3JvdXRpbmUsbXNnRGF0YS5hcmcpfWNhdGNoKGV4KXtpZihleCE9InVud2luZCIpe3Rocm93IGV4fX19ZWxzZSBpZihtc2dEYXRhLnRhcmdldD09PSJzZXRpbW1lZGlhdGUiKXt9ZWxzZSBpZihjbWQ9PT0iY2hlY2tNYWlsYm94Iil7aWYoaW5pdGlhbGl6ZWRKUyl7Y2hlY2tNYWlsYm94KCl9fWVsc2UgaWYoY21kKXtlcnIoYHdvcmtlcjogcmVjZWl2ZWQgdW5rbm93biBjb21tYW5kICR7Y21kfWApO2Vycihtc2dEYXRhKX19Y2F0Y2goZXgpe19fZW1zY3JpcHRlbl90aHJlYWRfY3Jhc2hlZCgpO3Rocm93IGV4fX1zZWxmLm9ubWVzc2FnZT1oYW5kbGVNZXNzYWdlfXZhciB3YXNtTWVtb3J5O3ZhciBIRUFQOCxIRUFQVTgsSEVBUDE2LEhFQVBVMTYsSEVBUDMyLEhFQVBVMzIsSEVBUEYzMixIRUFQRjY0O3ZhciBIRUFQNjQsSEVBUFU2NDt2YXIgcnVudGltZUluaXRpYWxpemVkPWZhbHNlO2Z1bmN0aW9uIHVwZGF0ZU1lbW9yeVZpZXdzKCl7dmFyIGI9d2FzbU1lbW9yeS5idWZmZXI7SEVBUDg9bmV3IEludDhBcnJheShiKTtIRUFQMTY9bmV3IEludDE2QXJyYXkoYik7SEVBUFU4PW5ldyBVaW50OEFycmF5KGIpO0hFQVBVMTY9bmV3IFVpbnQxNkFycmF5KGIpO0hFQVAzMj1uZXcgSW50MzJBcnJheShiKTtIRUFQVTMyPW5ldyBVaW50MzJBcnJheShiKTtIRUFQRjMyPW5ldyBGbG9hdDMyQXJyYXkoYik7SEVBUEY2ND1uZXcgRmxvYXQ2NEFycmF5KGIpO0hFQVA2ND1uZXcgQmlnSW50NjRBcnJheShiKTtIRUFQVTY0PW5ldyBCaWdVaW50NjRBcnJheShiKX1mdW5jdGlvbiBpbml0TWVtb3J5KCl7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7cmV0dXJufWlmKE1vZHVsZVsid2FzbU1lbW9yeSJdKXt3YXNtTWVtb3J5PU1vZHVsZVsid2FzbU1lbW9yeSJdfWVsc2V7dmFyIElOSVRJQUxfTUVNT1JZPU1vZHVsZVsiSU5JVElBTF9NRU1PUlkiXXx8NjcxMDg4NjQ7d2FzbU1lbW9yeT1uZXcgV2ViQXNzZW1ibHkuTWVtb3J5KHtpbml0aWFsOklOSVRJQUxfTUVNT1JZLzY1NTM2LG1heGltdW06NjU1MzYsc2hhcmVkOnRydWV9KX11cGRhdGVNZW1vcnlWaWV3cygpfWZ1bmN0aW9uIHByZVJ1bigpe2lmKE1vZHVsZVsicHJlUnVuIl0pe2lmKHR5cGVvZiBNb2R1bGVbInByZVJ1biJdPT0iZnVuY3Rpb24iKU1vZHVsZVsicHJlUnVuIl09W01vZHVsZVsicHJlUnVuIl1dO3doaWxlKE1vZHVsZVsicHJlUnVuIl0ubGVuZ3RoKXthZGRPblByZVJ1bihNb2R1bGVbInByZVJ1biJdLnNoaWZ0KCkpfX1jYWxsUnVudGltZUNhbGxiYWNrcyhvblByZVJ1bnMpfWZ1bmN0aW9uIGluaXRSdW50aW1lKCl7cnVudGltZUluaXRpYWxpemVkPXRydWU7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRClyZXR1cm4gc3RhcnRXb3JrZXIoKTt3YXNtRXhwb3J0c1sibyJdKCl9ZnVuY3Rpb24gcG9zdFJ1bigpe2lmKEVOVklST05NRU5UX0lTX1BUSFJFQUQpe3JldHVybn1pZihNb2R1bGVbInBvc3RSdW4iXSl7aWYodHlwZW9mIE1vZHVsZVsicG9zdFJ1biJdPT0iZnVuY3Rpb24iKU1vZHVsZVsicG9zdFJ1biJdPVtNb2R1bGVbInBvc3RSdW4iXV07d2hpbGUoTW9kdWxlWyJwb3N0UnVuIl0ubGVuZ3RoKXthZGRPblBvc3RSdW4oTW9kdWxlWyJwb3N0UnVuIl0uc2hpZnQoKSl9fWNhbGxSdW50aW1lQ2FsbGJhY2tzKG9uUG9zdFJ1bnMpfWZ1bmN0aW9uIGFib3J0KHdoYXQpe01vZHVsZVsib25BYm9ydCJdPy4od2hhdCk7d2hhdD0iQWJvcnRlZCgiK3doYXQrIikiO2Vycih3aGF0KTtBQk9SVD10cnVlO3doYXQrPSIuIEJ1aWxkIHdpdGggLXNBU1NFUlRJT05TIGZvciBtb3JlIGluZm8uIjt2YXIgZT1uZXcgV2ViQXNzZW1ibHkuUnVudGltZUVycm9yKHdoYXQpO3JlYWR5UHJvbWlzZVJlamVjdD8uKGUpO3Rocm93IGV9dmFyIHdhc21CaW5hcnlGaWxlO2Z1bmN0aW9uIGZpbmRXYXNtQmluYXJ5KCl7aWYoTW9kdWxlWyJsb2NhdGVGaWxlIl0pe3JldHVybiBsb2NhdGVGaWxlKCJza2FpbmV0X2tlcm5lbHNfbXQud2FzbSIpfXRyeXtyZXR1cm4gbmV3IFVSTCgic2thaW5ldF9rZXJuZWxzX210Lndhc20iLGltcG9ydC5tZXRhLnVybCkuaHJlZn1jYXRjaChlKXtyZXR1cm4gInNrYWluZXRfa2VybmVsc19tdC53YXNtIn19ZnVuY3Rpb24gZ2V0QmluYXJ5U3luYyhmaWxlKXtpZihmaWxlPT13YXNtQmluYXJ5RmlsZSYmd2FzbUJpbmFyeSl7cmV0dXJuIG5ldyBVaW50OEFycmF5KHdhc21CaW5hcnkpfWlmKHJlYWRCaW5hcnkpe3JldHVybiByZWFkQmluYXJ5KGZpbGUpfXRocm93ImJvdGggYXN5bmMgYW5kIHN5bmMgZmV0Y2hpbmcgb2YgdGhlIHdhc20gZmFpbGVkIn1hc3luYyBmdW5jdGlvbiBnZXRXYXNtQmluYXJ5KGJpbmFyeUZpbGUpe2lmKCF3YXNtQmluYXJ5KXt0cnl7dmFyIHJlc3BvbnNlPWF3YWl0IHJlYWRBc3luYyhiaW5hcnlGaWxlKTtyZXR1cm4gbmV3IFVpbnQ4QXJyYXkocmVzcG9uc2UpfWNhdGNoe319cmV0dXJuIGdldEJpbmFyeVN5bmMoYmluYXJ5RmlsZSl9YXN5bmMgZnVuY3Rpb24gaW5zdGFudGlhdGVBcnJheUJ1ZmZlcihiaW5hcnlGaWxlLGltcG9ydHMpe3RyeXt2YXIgYmluYXJ5PWF3YWl0IGdldFdhc21CaW5hcnkoYmluYXJ5RmlsZSk7dmFyIGluc3RhbmNlPWF3YWl0IFdlYkFzc2VtYmx5Lmluc3RhbnRpYXRlKGJpbmFyeSxpbXBvcnRzKTtyZXR1cm4gaW5zdGFuY2V9Y2F0Y2gocmVhc29uKXtlcnIoYGZhaWxlZCB0byBhc3luY2hyb25vdXNseSBwcmVwYXJlIHdhc206ICR7cmVhc29ufWApO2Fib3J0KHJlYXNvbil9fWFzeW5jIGZ1bmN0aW9uIGluc3RhbnRpYXRlQXN5bmMoYmluYXJ5LGJpbmFyeUZpbGUsaW1wb3J0cyl7aWYoIWJpbmFyeSYmIUVOVklST05NRU5UX0lTX05PREUpe3RyeXt2YXIgcmVzcG9uc2U9ZmV0Y2goYmluYXJ5RmlsZSx7Y3JlZGVudGlhbHM6InNhbWUtb3JpZ2luIn0pO3ZhciBpbnN0YW50aWF0aW9uUmVzdWx0PWF3YWl0IFdlYkFzc2VtYmx5Lmluc3RhbnRpYXRlU3RyZWFtaW5nKHJlc3BvbnNlLGltcG9ydHMpO3JldHVybiBpbnN0YW50aWF0aW9uUmVzdWx0fWNhdGNoKHJlYXNvbil7ZXJyKGB3YXNtIHN0cmVhbWluZyBjb21waWxlIGZhaWxlZDogJHtyZWFzb259YCk7ZXJyKCJmYWxsaW5nIGJhY2sgdG8gQXJyYXlCdWZmZXIgaW5zdGFudGlhdGlvbiIpfX1yZXR1cm4gaW5zdGFudGlhdGVBcnJheUJ1ZmZlcihiaW5hcnlGaWxlLGltcG9ydHMpfWZ1bmN0aW9uIGdldFdhc21JbXBvcnRzKCl7YXNzaWduV2FzbUltcG9ydHMoKTtyZXR1cm57YTp3YXNtSW1wb3J0c319YXN5bmMgZnVuY3Rpb24gY3JlYXRlV2FzbSgpe2Z1bmN0aW9uIHJlY2VpdmVJbnN0YW5jZShpbnN0YW5jZSxtb2R1bGUpe3dhc21FeHBvcnRzPWluc3RhbmNlLmV4cG9ydHM7d2FzbUV4cG9ydHM9YXBwbHlTaWduYXR1cmVDb252ZXJzaW9ucyh3YXNtRXhwb3J0cyk7cmVnaXN0ZXJUTFNJbml0KHdhc21FeHBvcnRzWyJWIl0pO3dhc21UYWJsZT13YXNtRXhwb3J0c1sicSJdO3dhc21Nb2R1bGU9bW9kdWxlO2Fzc2lnbldhc21FeHBvcnRzKHdhc21FeHBvcnRzKTtyZXR1cm4gd2FzbUV4cG9ydHN9ZnVuY3Rpb24gcmVjZWl2ZUluc3RhbnRpYXRpb25SZXN1bHQocmVzdWx0KXtyZXR1cm4gcmVjZWl2ZUluc3RhbmNlKHJlc3VsdFsiaW5zdGFuY2UiXSxyZXN1bHRbIm1vZHVsZSJdKX12YXIgaW5mbz1nZXRXYXNtSW1wb3J0cygpO2lmKE1vZHVsZVsiaW5zdGFudGlhdGVXYXNtIl0pe3JldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSxyZWplY3QpPT57TW9kdWxlWyJpbnN0YW50aWF0ZVdhc20iXShpbmZvLChtb2QsaW5zdCk9PntyZXNvbHZlKHJlY2VpdmVJbnN0YW5jZShtb2QsaW5zdCkpfSl9KX1pZihFTlZJUk9OTUVOVF9JU19QVEhSRUFEKXt2YXIgaW5zdGFuY2U9bmV3IFdlYkFzc2VtYmx5Lkluc3RhbmNlKHdhc21Nb2R1bGUsZ2V0V2FzbUltcG9ydHMoKSk7cmV0dXJuIHJlY2VpdmVJbnN0YW5jZShpbnN0YW5jZSx3YXNtTW9kdWxlKX13YXNtQmluYXJ5RmlsZT8/PWZpbmRXYXNtQmluYXJ5KCk7dmFyIHJlc3VsdD1hd2FpdCBpbnN0YW50aWF0ZUFzeW5jKHdhc21CaW5hcnksd2FzbUJpbmFyeUZpbGUsaW5mbyk7dmFyIGV4cG9ydHM9cmVjZWl2ZUluc3RhbnRpYXRpb25SZXN1bHQocmVzdWx0KTtyZXR1cm4gZXhwb3J0c31jbGFzcyBFeGl0U3RhdHVze25hbWU9IkV4aXRTdGF0dXMiO2NvbnN0cnVjdG9yKHN0YXR1cyl7dGhpcy5tZXNzYWdlPWBQcm9ncmFtIHRlcm1pbmF0ZWQgd2l0aCBleGl0KCR7c3RhdHVzfSlgO3RoaXMuc3RhdHVzPXN0YXR1c319dmFyIHRlcm1pbmF0ZVdvcmtlcj13b3JrZXI9Pnt3b3JrZXIudGVybWluYXRlKCk7d29ya2VyLm9ubWVzc2FnZT1lPT57fX07dmFyIGNsZWFudXBUaHJlYWQ9cHRocmVhZF9wdHI9Pnt2YXIgd29ya2VyPVBUaHJlYWQucHRocmVhZHNbcHRocmVhZF9wdHJdO1BUaHJlYWQucmV0dXJuV29ya2VyVG9Qb29sKHdvcmtlcil9O3ZhciBjYWxsUnVudGltZUNhbGxiYWNrcz1jYWxsYmFja3M9Pnt3aGlsZShjYWxsYmFja3MubGVuZ3RoPjApe2NhbGxiYWNrcy5zaGlmdCgpKE1vZHVsZSl9fTt2YXIgb25QcmVSdW5zPVtdO3ZhciBhZGRPblByZVJ1bj1jYj0+b25QcmVSdW5zLnB1c2goY2IpO3ZhciBydW5EZXBlbmRlbmNpZXM9MDt2YXIgZGVwZW5kZW5jaWVzRnVsZmlsbGVkPW51bGw7dmFyIHJlbW92ZVJ1bkRlcGVuZGVuY3k9aWQ9PntydW5EZXBlbmRlbmNpZXMtLTtNb2R1bGVbIm1vbml0b3JSdW5EZXBlbmRlbmNpZXMiXT8uKHJ1bkRlcGVuZGVuY2llcyk7aWYocnVuRGVwZW5kZW5jaWVzPT0wKXtpZihkZXBlbmRlbmNpZXNGdWxmaWxsZWQpe3ZhciBjYWxsYmFjaz1kZXBlbmRlbmNpZXNGdWxmaWxsZWQ7ZGVwZW5kZW5jaWVzRnVsZmlsbGVkPW51bGw7Y2FsbGJhY2soKX19fTt2YXIgYWRkUnVuRGVwZW5kZW5jeT1pZD0+e3J1bkRlcGVuZGVuY2llcysrO01vZHVsZVsibW9uaXRvclJ1bkRlcGVuZGVuY2llcyJdPy4ocnVuRGVwZW5kZW5jaWVzKX07dmFyIHNwYXduVGhyZWFkPXRocmVhZFBhcmFtcz0+e3ZhciB3b3JrZXI9UFRocmVhZC5nZXROZXdXb3JrZXIoKTtpZighd29ya2VyKXtyZXR1cm4gNn1QVGhyZWFkLnJ1bm5pbmdXb3JrZXJzLnB1c2god29ya2VyKTtQVGhyZWFkLnB0aHJlYWRzW3RocmVhZFBhcmFtcy5wdGhyZWFkX3B0cl09d29ya2VyO3dvcmtlci5wdGhyZWFkX3B0cj10aHJlYWRQYXJhbXMucHRocmVhZF9wdHI7dmFyIG1zZz17Y21kOiJydW4iLHN0YXJ0X3JvdXRpbmU6dGhyZWFkUGFyYW1zLnN0YXJ0Um91dGluZSxhcmc6dGhyZWFkUGFyYW1zLmFyZyxwdGhyZWFkX3B0cjp0aHJlYWRQYXJhbXMucHRocmVhZF9wdHJ9O2lmKEVOVklST05NRU5UX0lTX05PREUpe3dvcmtlci51bnJlZigpfXdvcmtlci5wb3N0TWVzc2FnZShtc2csdGhyZWFkUGFyYW1zLnRyYW5zZmVyTGlzdCk7cmV0dXJuIDB9O3ZhciBydW50aW1lS2VlcGFsaXZlQ291bnRlcj0wO3ZhciBrZWVwUnVudGltZUFsaXZlPSgpPT5ub0V4aXRSdW50aW1lfHxydW50aW1lS2VlcGFsaXZlQ291bnRlcj4wO3ZhciBzdGFja1NhdmU9KCk9Pl9lbXNjcmlwdGVuX3N0YWNrX2dldF9jdXJyZW50KCk7dmFyIHN0YWNrUmVzdG9yZT12YWw9Pl9fZW1zY3JpcHRlbl9zdGFja19yZXN0b3JlKHZhbCk7dmFyIHN0YWNrQWxsb2M9c3o9Pl9fZW1zY3JpcHRlbl9zdGFja19hbGxvYyhzeik7dmFyIHByb3h5VG9NYWluVGhyZWFkPShmdW5jSW5kZXgsZW1Bc21BZGRyLHN5bmMsLi4uY2FsbEFyZ3MpPT57dmFyIHNlcmlhbGl6ZWROdW1DYWxsQXJncz1jYWxsQXJncy5sZW5ndGgqMjt2YXIgc3A9c3RhY2tTYXZlKCk7dmFyIGFyZ3M9c3RhY2tBbGxvYyhzZXJpYWxpemVkTnVtQ2FsbEFyZ3MqOCk7dmFyIGI9YXJncz4+PjM7Zm9yKHZhciBpPTA7aTxjYWxsQXJncy5sZW5ndGg7aSsrKXt2YXIgYXJnPWNhbGxBcmdzW2ldO2lmKHR5cGVvZiBhcmc9PSJiaWdpbnQiKXsoZ3Jvd01lbVZpZXdzKCksSEVBUDY0KVtiKzIqaT4+PjBdPTFuOyhncm93TWVtVmlld3MoKSxIRUFQNjQpW2IrMippKzE+Pj4wXT1hcmd9ZWxzZXsoZ3Jvd01lbVZpZXdzKCksSEVBUDY0KVtiKzIqaT4+PjBdPTBuOyhncm93TWVtVmlld3MoKSxIRUFQRjY0KVtiKzIqaSsxPj4+MF09YXJnfX12YXIgcnRuPV9fZW1zY3JpcHRlbl9ydW5fanNfb25fbWFpbl90aHJlYWQoZnVuY0luZGV4LGVtQXNtQWRkcixzZXJpYWxpemVkTnVtQ2FsbEFyZ3MsYXJncyxzeW5jKTtzdGFja1Jlc3RvcmUoc3ApO3JldHVybiBydG59O2Z1bmN0aW9uIF9wcm9jX2V4aXQoY29kZSl7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRClyZXR1cm4gcHJveHlUb01haW5UaHJlYWQoMCwwLDEsY29kZSk7RVhJVFNUQVRVUz1jb2RlO2lmKCFrZWVwUnVudGltZUFsaXZlKCkpe1BUaHJlYWQudGVybWluYXRlQWxsVGhyZWFkcygpO01vZHVsZVsib25FeGl0Il0/Lihjb2RlKTtBQk9SVD10cnVlfXF1aXRfKGNvZGUsbmV3IEV4aXRTdGF0dXMoY29kZSkpfWZ1bmN0aW9uIGV4aXRPbk1haW5UaHJlYWQocmV0dXJuQ29kZSl7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRClyZXR1cm4gcHJveHlUb01haW5UaHJlYWQoMSwwLDAscmV0dXJuQ29kZSk7X2V4aXQocmV0dXJuQ29kZSl9dmFyIGV4aXRKUz0oc3RhdHVzLGltcGxpY2l0KT0+e0VYSVRTVEFUVVM9c3RhdHVzO2lmKEVOVklST05NRU5UX0lTX1BUSFJFQUQpe2V4aXRPbk1haW5UaHJlYWQoc3RhdHVzKTt0aHJvdyJ1bndpbmQifV9wcm9jX2V4aXQoc3RhdHVzKX07dmFyIF9leGl0PWV4aXRKUzt2YXIgUFRocmVhZD17dW51c2VkV29ya2VyczpbXSxydW5uaW5nV29ya2VyczpbXSx0bHNJbml0RnVuY3Rpb25zOltdLHB0aHJlYWRzOnt9LGluaXQoKXtpZighRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7UFRocmVhZC5pbml0TWFpblRocmVhZCgpfX0saW5pdE1haW5UaHJlYWQoKXt2YXIgcHRocmVhZFBvb2xTaXplPTQ7d2hpbGUocHRocmVhZFBvb2xTaXplLS0pe1BUaHJlYWQuYWxsb2NhdGVVbnVzZWRXb3JrZXIoKX1hZGRPblByZVJ1bihhc3luYygpPT57dmFyIHB0aHJlYWRQb29sUmVhZHk9UFRocmVhZC5sb2FkV2FzbU1vZHVsZVRvQWxsV29ya2VycygpO2FkZFJ1bkRlcGVuZGVuY3koImxvYWRpbmctd29ya2VycyIpO2F3YWl0IHB0aHJlYWRQb29sUmVhZHk7cmVtb3ZlUnVuRGVwZW5kZW5jeSgibG9hZGluZy13b3JrZXJzIil9KX0sdGVybWluYXRlQWxsVGhyZWFkczooKT0+e2Zvcih2YXIgd29ya2VyIG9mIFBUaHJlYWQucnVubmluZ1dvcmtlcnMpe3Rlcm1pbmF0ZVdvcmtlcih3b3JrZXIpfWZvcih2YXIgd29ya2VyIG9mIFBUaHJlYWQudW51c2VkV29ya2Vycyl7dGVybWluYXRlV29ya2VyKHdvcmtlcil9UFRocmVhZC51bnVzZWRXb3JrZXJzPVtdO1BUaHJlYWQucnVubmluZ1dvcmtlcnM9W107UFRocmVhZC5wdGhyZWFkcz17fX0scmV0dXJuV29ya2VyVG9Qb29sOndvcmtlcj0+e3ZhciBwdGhyZWFkX3B0cj13b3JrZXIucHRocmVhZF9wdHI7ZGVsZXRlIFBUaHJlYWQucHRocmVhZHNbcHRocmVhZF9wdHJdO1BUaHJlYWQudW51c2VkV29ya2Vycy5wdXNoKHdvcmtlcik7UFRocmVhZC5ydW5uaW5nV29ya2Vycy5zcGxpY2UoUFRocmVhZC5ydW5uaW5nV29ya2Vycy5pbmRleE9mKHdvcmtlciksMSk7d29ya2VyLnB0aHJlYWRfcHRyPTA7X19lbXNjcmlwdGVuX3RocmVhZF9mcmVlX2RhdGEocHRocmVhZF9wdHIpfSx0aHJlYWRJbml0VExTKCl7UFRocmVhZC50bHNJbml0RnVuY3Rpb25zLmZvckVhY2goZj0+ZigpKX0sbG9hZFdhc21Nb2R1bGVUb1dvcmtlcjp3b3JrZXI9Pm5ldyBQcm9taXNlKG9uRmluaXNoZWRMb2FkaW5nPT57d29ya2VyLm9ubWVzc2FnZT1lPT57dmFyIGQ9ZVsiZGF0YSJdO3ZhciBjbWQ9ZC5jbWQ7aWYoZC50YXJnZXRUaHJlYWQmJmQudGFyZ2V0VGhyZWFkIT1fcHRocmVhZF9zZWxmKCkpe3ZhciB0YXJnZXRXb3JrZXI9UFRocmVhZC5wdGhyZWFkc1tkLnRhcmdldFRocmVhZF07aWYodGFyZ2V0V29ya2VyKXt0YXJnZXRXb3JrZXIucG9zdE1lc3NhZ2UoZCxkLnRyYW5zZmVyTGlzdCl9ZWxzZXtlcnIoYEludGVybmFsIGVycm9yISBXb3JrZXIgc2VudCBhIG1lc3NhZ2UgIiR7Y21kfSIgdG8gdGFyZ2V0IHB0aHJlYWQgJHtkLnRhcmdldFRocmVhZH0sIGJ1dCB0aGF0IHRocmVhZCBubyBsb25nZXIgZXhpc3RzIWApfXJldHVybn1pZihjbWQ9PT0iY2hlY2tNYWlsYm94Iil7Y2hlY2tNYWlsYm94KCl9ZWxzZSBpZihjbWQ9PT0ic3Bhd25UaHJlYWQiKXtzcGF3blRocmVhZChkKX1lbHNlIGlmKGNtZD09PSJjbGVhbnVwVGhyZWFkIil7Y2FsbFVzZXJDYWxsYmFjaygoKT0+Y2xlYW51cFRocmVhZChkLnRocmVhZCkpfWVsc2UgaWYoY21kPT09ImxvYWRlZCIpe3dvcmtlci5sb2FkZWQ9dHJ1ZTtpZihFTlZJUk9OTUVOVF9JU19OT0RFJiYhd29ya2VyLnB0aHJlYWRfcHRyKXt3b3JrZXIudW5yZWYoKX1vbkZpbmlzaGVkTG9hZGluZyh3b3JrZXIpfWVsc2UgaWYoZC50YXJnZXQ9PT0ic2V0aW1tZWRpYXRlIil7d29ya2VyLnBvc3RNZXNzYWdlKGQpfWVsc2UgaWYoY21kPT09InVuY2F1Z2h0RXhjZXB0aW9uIil7d29ya2VyLm9uZXJyb3IoZC5lcnJvcil9ZWxzZSBpZihjbWQ9PT0iY2FsbEhhbmRsZXIiKXtNb2R1bGVbZC5oYW5kbGVyXSguLi5kLmFyZ3MpfWVsc2UgaWYoY21kKXtlcnIoYHdvcmtlciBzZW50IGFuIHVua25vd24gY29tbWFuZCAke2NtZH1gKX19O3dvcmtlci5vbmVycm9yPWU9Pnt2YXIgbWVzc2FnZT0id29ya2VyIHNlbnQgYW4gZXJyb3IhIjtlcnIoYCR7bWVzc2FnZX0gJHtlLmZpbGVuYW1lfToke2UubGluZW5vfTogJHtlLm1lc3NhZ2V9YCk7dGhyb3cgZX07aWYoRU5WSVJPTk1FTlRfSVNfTk9ERSl7d29ya2VyLm9uKCJtZXNzYWdlIixkYXRhPT53b3JrZXIub25tZXNzYWdlKHtkYXRhfSkpO3dvcmtlci5vbigiZXJyb3IiLGU9Pndvcmtlci5vbmVycm9yKGUpKX12YXIgaGFuZGxlcnM9W107dmFyIGtub3duSGFuZGxlcnM9WyJvbkV4aXQiLCJvbkFib3J0IiwicHJpbnQiLCJwcmludEVyciJdO2Zvcih2YXIgaGFuZGxlciBvZiBrbm93bkhhbmRsZXJzKXtpZihNb2R1bGUucHJvcGVydHlJc0VudW1lcmFibGUoaGFuZGxlcikpe2hhbmRsZXJzLnB1c2goaGFuZGxlcil9fXdvcmtlci5wb3N0TWVzc2FnZSh7Y21kOiJsb2FkIixoYW5kbGVycyx3YXNtTWVtb3J5LHdhc21Nb2R1bGV9KX0pLGFzeW5jIGxvYWRXYXNtTW9kdWxlVG9BbGxXb3JrZXJzKCl7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7cmV0dXJufWxldCBwdGhyZWFkUG9vbFJlYWR5PVByb21pc2UuYWxsKFBUaHJlYWQudW51c2VkV29ya2Vycy5tYXAoUFRocmVhZC5sb2FkV2FzbU1vZHVsZVRvV29ya2VyKSk7cmV0dXJuIHB0aHJlYWRQb29sUmVhZHl9LGFsbG9jYXRlVW51c2VkV29ya2VyKCl7dmFyIHdvcmtlcjtpZihNb2R1bGVbIm1haW5TY3JpcHRVcmxPckJsb2IiXSl7dmFyIHB0aHJlYWRNYWluSnM9TW9kdWxlWyJtYWluU2NyaXB0VXJsT3JCbG9iIl07aWYodHlwZW9mIHB0aHJlYWRNYWluSnMhPSJzdHJpbmciKXtwdGhyZWFkTWFpbkpzPVVSTC5jcmVhdGVPYmplY3RVUkwocHRocmVhZE1haW5Kcyl9d29ya2VyPW5ldyBXb3JrZXIoRU5WSVJPTk1FTlRfSVNfTk9ERSYmdHlwZW9mIHB0aHJlYWRNYWluSnM9PSJzdHJpbmciJiZwdGhyZWFkTWFpbkpzLnN0YXJ0c1dpdGgoImRhdGE6Iik/bmV3IFVSTChwdGhyZWFkTWFpbkpzKTpwdGhyZWFkTWFpbkpzLHt0eXBlOiJtb2R1bGUiLHdvcmtlckRhdGE6ImVtLXB0aHJlYWQiLG5hbWU6ImVtLXB0aHJlYWQifSl9ZWxzZSB3b3JrZXI9bmV3IFdvcmtlcihuZXcgVVJMKCJza2FpbmV0X2tlcm5lbHNfbXQubWpzIixpbXBvcnQubWV0YS51cmwpLHt0eXBlOiJtb2R1bGUiLHdvcmtlckRhdGE6ImVtLXB0aHJlYWQiLG5hbWU6ImVtLXB0aHJlYWQifSk7UFRocmVhZC51bnVzZWRXb3JrZXJzLnB1c2god29ya2VyKX0sZ2V0TmV3V29ya2VyKCl7aWYoUFRocmVhZC51bnVzZWRXb3JrZXJzLmxlbmd0aD09MCl7UFRocmVhZC5hbGxvY2F0ZVVudXNlZFdvcmtlcigpO1BUaHJlYWQubG9hZFdhc21Nb2R1bGVUb1dvcmtlcihQVGhyZWFkLnVudXNlZFdvcmtlcnNbMF0pfXJldHVybiBQVGhyZWFkLnVudXNlZFdvcmtlcnMucG9wKCl9fTt2YXIgb25Qb3N0UnVucz1bXTt2YXIgYWRkT25Qb3N0UnVuPWNiPT5vblBvc3RSdW5zLnB1c2goY2IpO2Z1bmN0aW9uIGVzdGFibGlzaFN0YWNrU3BhY2UocHRocmVhZF9wdHIpe3ZhciBzdGFja0hpZ2g9KGdyb3dNZW1WaWV3cygpLEhFQVBVMzIpW3B0aHJlYWRfcHRyKzUyPj4+Mj4+PjBdO3ZhciBzdGFja1NpemU9KGdyb3dNZW1WaWV3cygpLEhFQVBVMzIpW3B0aHJlYWRfcHRyKzU2Pj4+Mj4+PjBdO3ZhciBzdGFja0xvdz1zdGFja0hpZ2gtc3RhY2tTaXplO19lbXNjcmlwdGVuX3N0YWNrX3NldF9saW1pdHMoc3RhY2tIaWdoLHN0YWNrTG93KTtzdGFja1Jlc3RvcmUoc3RhY2tIaWdoKX12YXIgd2FzbVRhYmxlTWlycm9yPVtdO3ZhciB3YXNtVGFibGU7dmFyIGdldFdhc21UYWJsZUVudHJ5PWZ1bmNQdHI9Pnt2YXIgZnVuYz13YXNtVGFibGVNaXJyb3JbZnVuY1B0cl07aWYoIWZ1bmMpe3dhc21UYWJsZU1pcnJvcltmdW5jUHRyXT1mdW5jPXdhc21UYWJsZS5nZXQoZnVuY1B0cil9cmV0dXJuIGZ1bmN9O3ZhciBpbnZva2VFbnRyeVBvaW50PShwdHIsYXJnKT0+e3J1bnRpbWVLZWVwYWxpdmVDb3VudGVyPTA7bm9FeGl0UnVudGltZT0wO3ZhciByZXN1bHQ9Z2V0V2FzbVRhYmxlRW50cnkocHRyKShhcmcpO2Z1bmN0aW9uIGZpbmlzaChyZXN1bHQpe2lmKGtlZXBSdW50aW1lQWxpdmUoKSl7RVhJVFNUQVRVUz1yZXN1bHQ7cmV0dXJufV9fZW1zY3JpcHRlbl90aHJlYWRfZXhpdChyZXN1bHQpfWZpbmlzaChyZXN1bHQpfTt2YXIgbm9FeGl0UnVudGltZT10cnVlO3ZhciByZWdpc3RlclRMU0luaXQ9dGxzSW5pdEZ1bmM9PlBUaHJlYWQudGxzSW5pdEZ1bmN0aW9ucy5wdXNoKHRsc0luaXRGdW5jKTtmdW5jdGlvbiBwdGhyZWFkQ3JlYXRlUHJveGllZChwdGhyZWFkX3B0cixhdHRyLHN0YXJ0Um91dGluZSxhcmcpe2lmKEVOVklST05NRU5UX0lTX1BUSFJFQUQpcmV0dXJuIHByb3h5VG9NYWluVGhyZWFkKDIsMCwxLHB0aHJlYWRfcHRyLGF0dHIsc3RhcnRSb3V0aW5lLGFyZyk7cmV0dXJuIF9fX3B0aHJlYWRfY3JlYXRlX2pzKHB0aHJlYWRfcHRyLGF0dHIsc3RhcnRSb3V0aW5lLGFyZyl9dmFyIF9lbXNjcmlwdGVuX2hhc190aHJlYWRpbmdfc3VwcG9ydD0oKT0+dHlwZW9mIFNoYXJlZEFycmF5QnVmZmVyIT0idW5kZWZpbmVkIjtmdW5jdGlvbiBfX19wdGhyZWFkX2NyZWF0ZV9qcyhwdGhyZWFkX3B0cixhdHRyLHN0YXJ0Um91dGluZSxhcmcpe3B0aHJlYWRfcHRyPj4+PTA7YXR0cj4+Pj0wO3N0YXJ0Um91dGluZT4+Pj0wO2FyZz4+Pj0wO2lmKCFfZW1zY3JpcHRlbl9oYXNfdGhyZWFkaW5nX3N1cHBvcnQoKSl7cmV0dXJuIDZ9dmFyIHRyYW5zZmVyTGlzdD1bXTt2YXIgZXJyb3I9MDtpZihFTlZJUk9OTUVOVF9JU19QVEhSRUFEJiYodHJhbnNmZXJMaXN0Lmxlbmd0aD09PTB8fGVycm9yKSl7cmV0dXJuIHB0aHJlYWRDcmVhdGVQcm94aWVkKHB0aHJlYWRfcHRyLGF0dHIsc3RhcnRSb3V0aW5lLGFyZyl9aWYoZXJyb3IpcmV0dXJuIGVycm9yO3ZhciB0aHJlYWRQYXJhbXM9e3N0YXJ0Um91dGluZSxwdGhyZWFkX3B0cixhcmcsdHJhbnNmZXJMaXN0fTtpZihFTlZJUk9OTUVOVF9JU19QVEhSRUFEKXt0aHJlYWRQYXJhbXMuY21kPSJzcGF3blRocmVhZCI7cG9zdE1lc3NhZ2UodGhyZWFkUGFyYW1zLHRyYW5zZmVyTGlzdCk7cmV0dXJuIDB9cmV0dXJuIHNwYXduVGhyZWFkKHRocmVhZFBhcmFtcyl9ZnVuY3Rpb24gX19lbXNjcmlwdGVuX2luaXRfbWFpbl90aHJlYWRfanModGIpe3RiPj4+PTA7X19lbXNjcmlwdGVuX3RocmVhZF9pbml0KHRiLCFFTlZJUk9OTUVOVF9JU19XT1JLRVIsMSwhRU5WSVJPTk1FTlRfSVNfV0VCLDY1NTM2LGZhbHNlKTtQVGhyZWFkLnRocmVhZEluaXRUTFMoKX12YXIgaGFuZGxlRXhjZXB0aW9uPWU9PntpZihlIGluc3RhbmNlb2YgRXhpdFN0YXR1c3x8ZT09InVud2luZCIpe3JldHVybiBFWElUU1RBVFVTfXF1aXRfKDEsZSl9O3ZhciBtYXliZUV4aXQ9KCk9PntpZigha2VlcFJ1bnRpbWVBbGl2ZSgpKXt0cnl7aWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7aWYoX3B0aHJlYWRfc2VsZigpKV9fZW1zY3JpcHRlbl90aHJlYWRfZXhpdChFWElUU1RBVFVTKTtyZXR1cm59X2V4aXQoRVhJVFNUQVRVUyl9Y2F0Y2goZSl7aGFuZGxlRXhjZXB0aW9uKGUpfX19O3ZhciBjYWxsVXNlckNhbGxiYWNrPWZ1bmM9PntpZihBQk9SVCl7cmV0dXJufXRyeXtmdW5jKCk7bWF5YmVFeGl0KCl9Y2F0Y2goZSl7aGFuZGxlRXhjZXB0aW9uKGUpfX07ZnVuY3Rpb24gX19lbXNjcmlwdGVuX3RocmVhZF9tYWlsYm94X2F3YWl0KHB0aHJlYWRfcHRyKXtwdGhyZWFkX3B0cj4+Pj0wO2lmKHR5cGVvZiBBdG9taWNzLndhaXRBc3luYz09PSJmdW5jdGlvbiIpe3ZhciB3YWl0PUF0b21pY3Mud2FpdEFzeW5jKChncm93TWVtVmlld3MoKSxIRUFQMzIpLHB0aHJlYWRfcHRyPj4+MixwdGhyZWFkX3B0cik7d2FpdC52YWx1ZS50aGVuKGNoZWNrTWFpbGJveCk7dmFyIHdhaXRpbmdBc3luYz1wdGhyZWFkX3B0cisxMjg7QXRvbWljcy5zdG9yZSgoZ3Jvd01lbVZpZXdzKCksSEVBUDMyKSx3YWl0aW5nQXN5bmM+Pj4yLDEpfX12YXIgY2hlY2tNYWlsYm94PSgpPT5jYWxsVXNlckNhbGxiYWNrKCgpPT57dmFyIHB0aHJlYWRfcHRyPV9wdGhyZWFkX3NlbGYoKTtpZihwdGhyZWFkX3B0cil7X19lbXNjcmlwdGVuX3RocmVhZF9tYWlsYm94X2F3YWl0KHB0aHJlYWRfcHRyKTtfX2Vtc2NyaXB0ZW5fY2hlY2tfbWFpbGJveCgpfX0pO2Z1bmN0aW9uIF9fZW1zY3JpcHRlbl9ub3RpZnlfbWFpbGJveF9wb3N0bWVzc2FnZSh0YXJnZXRUaHJlYWQsY3VyclRocmVhZElkKXt0YXJnZXRUaHJlYWQ+Pj49MDtjdXJyVGhyZWFkSWQ+Pj49MDtpZih0YXJnZXRUaHJlYWQ9PWN1cnJUaHJlYWRJZCl7c2V0VGltZW91dChjaGVja01haWxib3gpfWVsc2UgaWYoRU5WSVJPTk1FTlRfSVNfUFRIUkVBRCl7cG9zdE1lc3NhZ2Uoe3RhcmdldFRocmVhZCxjbWQ6ImNoZWNrTWFpbGJveCJ9KX1lbHNle3ZhciB3b3JrZXI9UFRocmVhZC5wdGhyZWFkc1t0YXJnZXRUaHJlYWRdO2lmKCF3b3JrZXIpe3JldHVybn13b3JrZXIucG9zdE1lc3NhZ2Uoe2NtZDoiY2hlY2tNYWlsYm94In0pfX12YXIgcHJveGllZEpTQ2FsbEFyZ3M9W107ZnVuY3Rpb24gX19lbXNjcmlwdGVuX3JlY2VpdmVfb25fbWFpbl90aHJlYWRfanMoZnVuY0luZGV4LGVtQXNtQWRkcixjYWxsaW5nVGhyZWFkLG51bUNhbGxBcmdzLGFyZ3Mpe2VtQXNtQWRkcj4+Pj0wO2NhbGxpbmdUaHJlYWQ+Pj49MDthcmdzPj4+PTA7bnVtQ2FsbEFyZ3MvPTI7cHJveGllZEpTQ2FsbEFyZ3MubGVuZ3RoPW51bUNhbGxBcmdzO3ZhciBiPWFyZ3M+Pj4zO2Zvcih2YXIgaT0wO2k8bnVtQ2FsbEFyZ3M7aSsrKXtpZigoZ3Jvd01lbVZpZXdzKCksSEVBUDY0KVtiKzIqaT4+PjBdKXtwcm94aWVkSlNDYWxsQXJnc1tpXT0oZ3Jvd01lbVZpZXdzKCksSEVBUDY0KVtiKzIqaSsxPj4+MF19ZWxzZXtwcm94aWVkSlNDYWxsQXJnc1tpXT0oZ3Jvd01lbVZpZXdzKCksSEVBUEY2NClbYisyKmkrMT4+PjBdfX12YXIgZnVuYz1wcm94aWVkRnVuY3Rpb25UYWJsZVtmdW5jSW5kZXhdO1BUaHJlYWQuY3VycmVudFByb3hpZWRPcGVyYXRpb25DYWxsZXJUaHJlYWQ9Y2FsbGluZ1RocmVhZDt2YXIgcnRuPWZ1bmMoLi4ucHJveGllZEpTQ2FsbEFyZ3MpO1BUaHJlYWQuY3VycmVudFByb3hpZWRPcGVyYXRpb25DYWxsZXJUaHJlYWQ9MDtyZXR1cm4gcnRufWZ1bmN0aW9uIF9fZW1zY3JpcHRlbl90aHJlYWRfY2xlYW51cCh0aHJlYWQpe3RocmVhZD4+Pj0wO2lmKCFFTlZJUk9OTUVOVF9JU19QVEhSRUFEKWNsZWFudXBUaHJlYWQodGhyZWFkKTtlbHNlIHBvc3RNZXNzYWdlKHtjbWQ6ImNsZWFudXBUaHJlYWQiLHRocmVhZH0pfWZ1bmN0aW9uIF9fZW1zY3JpcHRlbl90aHJlYWRfc2V0X3N0cm9uZ3JlZih0aHJlYWQpe3RocmVhZD4+Pj0wO2lmKEVOVklST05NRU5UX0lTX05PREUpe1BUaHJlYWQucHRocmVhZHNbdGhyZWFkXS5yZWYoKX19dmFyIF9lbXNjcmlwdGVuX2dldF9ub3c9KCk9PnBlcmZvcm1hbmNlLnRpbWVPcmlnaW4rcGVyZm9ybWFuY2Uubm93KCk7dmFyIF9lbXNjcmlwdGVuX2NoZWNrX2Jsb2NraW5nX2FsbG93ZWQ9KCk9Pnt9O3ZhciBydW50aW1lS2VlcGFsaXZlUHVzaD0oKT0+e3J1bnRpbWVLZWVwYWxpdmVDb3VudGVyKz0xfTt2YXIgX2Vtc2NyaXB0ZW5fZXhpdF93aXRoX2xpdmVfcnVudGltZT0oKT0+e3J1bnRpbWVLZWVwYWxpdmVQdXNoKCk7dGhyb3cidW53aW5kIn07dmFyIGdldEhlYXBNYXg9KCk9PjQyOTQ5MDE3NjA7dmFyIGFsaWduTWVtb3J5PShzaXplLGFsaWdubWVudCk9Pk1hdGguY2VpbChzaXplL2FsaWdubWVudCkqYWxpZ25tZW50O3ZhciBncm93TWVtb3J5PXNpemU9Pnt2YXIgb2xkSGVhcFNpemU9d2FzbU1lbW9yeS5idWZmZXIuYnl0ZUxlbmd0aDt2YXIgcGFnZXM9KHNpemUtb2xkSGVhcFNpemUrNjU1MzUpLzY1NTM2fDA7dHJ5e3dhc21NZW1vcnkuZ3JvdyhwYWdlcyk7dXBkYXRlTWVtb3J5Vmlld3MoKTtyZXR1cm4gMX1jYXRjaChlKXt9fTtmdW5jdGlvbiBfZW1zY3JpcHRlbl9yZXNpemVfaGVhcChyZXF1ZXN0ZWRTaXplKXtyZXF1ZXN0ZWRTaXplPj4+PTA7dmFyIG9sZFNpemU9KGdyb3dNZW1WaWV3cygpLEhFQVBVOCkubGVuZ3RoO2lmKHJlcXVlc3RlZFNpemU8PW9sZFNpemUpe3JldHVybiBmYWxzZX12YXIgbWF4SGVhcFNpemU9Z2V0SGVhcE1heCgpO2lmKHJlcXVlc3RlZFNpemU+bWF4SGVhcFNpemUpe3JldHVybiBmYWxzZX1mb3IodmFyIGN1dERvd249MTtjdXREb3duPD00O2N1dERvd24qPTIpe3ZhciBvdmVyR3Jvd25IZWFwU2l6ZT1vbGRTaXplKigxKy4yL2N1dERvd24pO292ZXJHcm93bkhlYXBTaXplPU1hdGgubWluKG92ZXJHcm93bkhlYXBTaXplLHJlcXVlc3RlZFNpemUrMTAwNjYzMjk2KTt2YXIgbmV3U2l6ZT1NYXRoLm1pbihtYXhIZWFwU2l6ZSxhbGlnbk1lbW9yeShNYXRoLm1heChyZXF1ZXN0ZWRTaXplLG92ZXJHcm93bkhlYXBTaXplKSw2NTUzNikpO3ZhciByZXBsYWNlbWVudD1ncm93TWVtb3J5KG5ld1NpemUpO2lmKHJlcGxhY2VtZW50KXtyZXR1cm4gdHJ1ZX19cmV0dXJuIGZhbHNlfXZhciBwcmludENoYXJCdWZmZXJzPVtudWxsLFtdLFtdXTt2YXIgVVRGOERlY29kZXI9dHlwZW9mIFRleHREZWNvZGVyIT0idW5kZWZpbmVkIj9uZXcgVGV4dERlY29kZXI6dW5kZWZpbmVkO3ZhciBmaW5kU3RyaW5nRW5kPShoZWFwT3JBcnJheSxpZHgsbWF4Qnl0ZXNUb1JlYWQsaWdub3JlTnVsKT0+e3ZhciBtYXhJZHg9aWR4K21heEJ5dGVzVG9SZWFkO2lmKGlnbm9yZU51bClyZXR1cm4gbWF4SWR4O3doaWxlKGhlYXBPckFycmF5W2lkeF0mJiEoaWR4Pj1tYXhJZHgpKSsraWR4O3JldHVybiBpZHh9O3ZhciBVVEY4QXJyYXlUb1N0cmluZz0oaGVhcE9yQXJyYXksaWR4PTAsbWF4Qnl0ZXNUb1JlYWQsaWdub3JlTnVsKT0+e2lkeD4+Pj0wO3ZhciBlbmRQdHI9ZmluZFN0cmluZ0VuZChoZWFwT3JBcnJheSxpZHgsbWF4Qnl0ZXNUb1JlYWQsaWdub3JlTnVsKTtpZihlbmRQdHItaWR4PjE2JiZoZWFwT3JBcnJheS5idWZmZXImJlVURjhEZWNvZGVyKXtyZXR1cm4gVVRGOERlY29kZXIuZGVjb2RlKGhlYXBPckFycmF5LmJ1ZmZlciBpbnN0YW5jZW9mIEFycmF5QnVmZmVyP2hlYXBPckFycmF5LnN1YmFycmF5KGlkeCxlbmRQdHIpOmhlYXBPckFycmF5LnNsaWNlKGlkeCxlbmRQdHIpKX12YXIgc3RyPSIiO3doaWxlKGlkeDxlbmRQdHIpe3ZhciB1MD1oZWFwT3JBcnJheVtpZHgrK107aWYoISh1MCYxMjgpKXtzdHIrPVN0cmluZy5mcm9tQ2hhckNvZGUodTApO2NvbnRpbnVlfXZhciB1MT1oZWFwT3JBcnJheVtpZHgrK10mNjM7aWYoKHUwJjIyNCk9PTE5Mil7c3RyKz1TdHJpbmcuZnJvbUNoYXJDb2RlKCh1MCYzMSk8PDZ8dTEpO2NvbnRpbnVlfXZhciB1Mj1oZWFwT3JBcnJheVtpZHgrK10mNjM7aWYoKHUwJjI0MCk9PTIyNCl7dTA9KHUwJjE1KTw8MTJ8dTE8PDZ8dTJ9ZWxzZXt1MD0odTAmNyk8PDE4fHUxPDwxMnx1Mjw8NnxoZWFwT3JBcnJheVtpZHgrK10mNjN9aWYodTA8NjU1MzYpe3N0cis9U3RyaW5nLmZyb21DaGFyQ29kZSh1MCl9ZWxzZXt2YXIgY2g9dTAtNjU1MzY7c3RyKz1TdHJpbmcuZnJvbUNoYXJDb2RlKDU1Mjk2fGNoPj4xMCw1NjMyMHxjaCYxMDIzKX19cmV0dXJuIHN0cn07dmFyIHByaW50Q2hhcj0oc3RyZWFtLGN1cnIpPT57dmFyIGJ1ZmZlcj1wcmludENoYXJCdWZmZXJzW3N0cmVhbV07aWYoY3Vycj09PTB8fGN1cnI9PT0xMCl7KHN0cmVhbT09PTE/b3V0OmVycikoVVRGOEFycmF5VG9TdHJpbmcoYnVmZmVyKSk7YnVmZmVyLmxlbmd0aD0wfWVsc2V7YnVmZmVyLnB1c2goY3Vycil9fTtmdW5jdGlvbiBfZmRfd3JpdGUoZmQsaW92LGlvdmNudCxwbnVtKXtpZihFTlZJUk9OTUVOVF9JU19QVEhSRUFEKXJldHVybiBwcm94eVRvTWFpblRocmVhZCgzLDAsMSxmZCxpb3YsaW92Y250LHBudW0pO2lvdj4+Pj0wO2lvdmNudD4+Pj0wO3BudW0+Pj49MDt2YXIgbnVtPTA7Zm9yKHZhciBpPTA7aTxpb3ZjbnQ7aSsrKXt2YXIgcHRyPShncm93TWVtVmlld3MoKSxIRUFQVTMyKVtpb3Y+Pj4yPj4+MF07dmFyIGxlbj0oZ3Jvd01lbVZpZXdzKCksSEVBUFUzMilbaW92KzQ+Pj4yPj4+MF07aW92Kz04O2Zvcih2YXIgaj0wO2o8bGVuO2orKyl7cHJpbnRDaGFyKGZkLChncm93TWVtVmlld3MoKSxIRUFQVTgpW3B0citqPj4+MF0pfW51bSs9bGVufShncm93TWVtVmlld3MoKSxIRUFQVTMyKVtwbnVtPj4+Mj4+PjBdPW51bTtyZXR1cm4gMH1QVGhyZWFkLmluaXQoKTt7aW5pdE1lbW9yeSgpO2lmKE1vZHVsZVsibm9FeGl0UnVudGltZSJdKW5vRXhpdFJ1bnRpbWU9TW9kdWxlWyJub0V4aXRSdW50aW1lIl07aWYoTW9kdWxlWyJwcmludCJdKW91dD1Nb2R1bGVbInByaW50Il07aWYoTW9kdWxlWyJwcmludEVyciJdKWVycj1Nb2R1bGVbInByaW50RXJyIl07aWYoTW9kdWxlWyJ3YXNtQmluYXJ5Il0pd2FzbUJpbmFyeT1Nb2R1bGVbIndhc21CaW5hcnkiXTtpZihNb2R1bGVbImFyZ3VtZW50cyJdKWFyZ3VtZW50c189TW9kdWxlWyJhcmd1bWVudHMiXTtpZihNb2R1bGVbInRoaXNQcm9ncmFtIl0pdGhpc1Byb2dyYW09TW9kdWxlWyJ0aGlzUHJvZ3JhbSJdO2lmKE1vZHVsZVsicHJlSW5pdCJdKXtpZih0eXBlb2YgTW9kdWxlWyJwcmVJbml0Il09PSJmdW5jdGlvbiIpTW9kdWxlWyJwcmVJbml0Il09W01vZHVsZVsicHJlSW5pdCJdXTt3aGlsZShNb2R1bGVbInByZUluaXQiXS5sZW5ndGg+MCl7TW9kdWxlWyJwcmVJbml0Il0uc2hpZnQoKSgpfX19TW9kdWxlWyJ3YXNtTWVtb3J5Il09d2FzbU1lbW9yeTt2YXIgcHJveGllZEZ1bmN0aW9uVGFibGU9W19wcm9jX2V4aXQsZXhpdE9uTWFpblRocmVhZCxwdGhyZWFkQ3JlYXRlUHJveGllZCxfZmRfd3JpdGVdO3ZhciBfc2thaW5ldF9mcDMyX21hdG11bCxfc2thaW5ldF9mcDMyX21hdG11bF93dCxfc2thaW5ldF9iZjE2X21hdG11bCxfc2thaW5ldF9mcDE2X21hdG11bCxfc2thaW5ldF9xNF8wX21hdG11bCxfc2thaW5ldF9xNF8wX21hdG11bF9ybSxfc2thaW5ldF9xNV8wX21hdG11bCxfbWFsbG9jLF9mcmVlLF9za2FpbmV0X3E1XzBfbWF0bXVsX3JtLF9za2FpbmV0X3E1XzFfbWF0bXVsLF9za2FpbmV0X3E1XzFfbWF0bXVsX3JtLF9za2FpbmV0X3E4XzBfbWF0bXVsLF9za2FpbmV0X3E4XzBfbWF0bXVsX3JtLF9za2FpbmV0X3E0a19tYXRtdWwsX3NrYWluZXRfcTRrX21hdG11bF9ybSxfc2thaW5ldF9xNWtfbWF0bXVsLF9za2FpbmV0X3E1a19tYXRtdWxfcm0sX3NrYWluZXRfcTZrX21hdG11bCxfc2thaW5ldF9xNmtfbWF0bXVsX3JtLF9za2FpbmV0X3Rlcm5hcnlfZjMyX2dlbXYsX3NrYWluZXRfdGVybmFyeV9sbWhlYWRfc3RhZ2UxLF9za2FpbmV0X2JpdG5ldF9nZW12X3RxMl8wLF9za2FpbmV0X2YzMl9iaW5hcnksX3NrYWluZXRfZjMyX3VuYXJ5LF9za2FpbmV0X2YzMl9yZWR1Y2VfYWxsLF9za2FpbmV0X2YzMl9yZWR1Y2VfYXhpcyxfc2thaW5ldF9mMzJfc29mdG1heF9yb3dzLF9za2FpbmV0X2YzMl9sYXllcl9ub3JtLF9za2FpbmV0X2YzMl9ybXNfbm9ybSxfc2thaW5ldF9mMzJfc2RwYSxfX2Vtc2NyaXB0ZW5fdGxzX2luaXQsX3B0aHJlYWRfc2VsZixfX2Vtc2NyaXB0ZW5fdGhyZWFkX2luaXQsX19lbXNjcmlwdGVuX3RocmVhZF9jcmFzaGVkLF9fZW1zY3JpcHRlbl9ydW5fanNfb25fbWFpbl90aHJlYWQsX19lbXNjcmlwdGVuX3RocmVhZF9mcmVlX2RhdGEsX19lbXNjcmlwdGVuX3RocmVhZF9leGl0LF9fZW1zY3JpcHRlbl9jaGVja19tYWlsYm94LF9lbXNjcmlwdGVuX3N0YWNrX3NldF9saW1pdHMsX19lbXNjcmlwdGVuX3N0YWNrX3Jlc3RvcmUsX19lbXNjcmlwdGVuX3N0YWNrX2FsbG9jLF9lbXNjcmlwdGVuX3N0YWNrX2dldF9jdXJyZW50O2Z1bmN0aW9uIGFzc2lnbldhc21FeHBvcnRzKHdhc21FeHBvcnRzKXtNb2R1bGVbIl9za2FpbmV0X2ZwMzJfbWF0bXVsIl09X3NrYWluZXRfZnAzMl9tYXRtdWw9d2FzbUV4cG9ydHNbInAiXTtNb2R1bGVbIl9za2FpbmV0X2ZwMzJfbWF0bXVsX3d0Il09X3NrYWluZXRfZnAzMl9tYXRtdWxfd3Q9d2FzbUV4cG9ydHNbInIiXTtNb2R1bGVbIl9za2FpbmV0X2JmMTZfbWF0bXVsIl09X3NrYWluZXRfYmYxNl9tYXRtdWw9d2FzbUV4cG9ydHNbInMiXTtNb2R1bGVbIl9za2FpbmV0X2ZwMTZfbWF0bXVsIl09X3NrYWluZXRfZnAxNl9tYXRtdWw9d2FzbUV4cG9ydHNbInQiXTtNb2R1bGVbIl9za2FpbmV0X3E0XzBfbWF0bXVsIl09X3NrYWluZXRfcTRfMF9tYXRtdWw9d2FzbUV4cG9ydHNbInUiXTtNb2R1bGVbIl9za2FpbmV0X3E0XzBfbWF0bXVsX3JtIl09X3NrYWluZXRfcTRfMF9tYXRtdWxfcm09d2FzbUV4cG9ydHNbInYiXTtNb2R1bGVbIl9za2FpbmV0X3E1XzBfbWF0bXVsIl09X3NrYWluZXRfcTVfMF9tYXRtdWw9d2FzbUV4cG9ydHNbInciXTtNb2R1bGVbIl9tYWxsb2MiXT1fbWFsbG9jPXdhc21FeHBvcnRzWyJ4Il07TW9kdWxlWyJfZnJlZSJdPV9mcmVlPXdhc21FeHBvcnRzWyJ5Il07TW9kdWxlWyJfc2thaW5ldF9xNV8wX21hdG11bF9ybSJdPV9za2FpbmV0X3E1XzBfbWF0bXVsX3JtPXdhc21FeHBvcnRzWyJ6Il07TW9kdWxlWyJfc2thaW5ldF9xNV8xX21hdG11bCJdPV9za2FpbmV0X3E1XzFfbWF0bXVsPXdhc21FeHBvcnRzWyJBIl07TW9kdWxlWyJfc2thaW5ldF9xNV8xX21hdG11bF9ybSJdPV9za2FpbmV0X3E1XzFfbWF0bXVsX3JtPXdhc21FeHBvcnRzWyJCIl07TW9kdWxlWyJfc2thaW5ldF9xOF8wX21hdG11bCJdPV9za2FpbmV0X3E4XzBfbWF0bXVsPXdhc21FeHBvcnRzWyJDIl07TW9kdWxlWyJfc2thaW5ldF9xOF8wX21hdG11bF9ybSJdPV9za2FpbmV0X3E4XzBfbWF0bXVsX3JtPXdhc21FeHBvcnRzWyJEIl07TW9kdWxlWyJfc2thaW5ldF9xNGtfbWF0bXVsIl09X3NrYWluZXRfcTRrX21hdG11bD13YXNtRXhwb3J0c1siRSJdO01vZHVsZVsiX3NrYWluZXRfcTRrX21hdG11bF9ybSJdPV9za2FpbmV0X3E0a19tYXRtdWxfcm09d2FzbUV4cG9ydHNbIkYiXTtNb2R1bGVbIl9za2FpbmV0X3E1a19tYXRtdWwiXT1fc2thaW5ldF9xNWtfbWF0bXVsPXdhc21FeHBvcnRzWyJHIl07TW9kdWxlWyJfc2thaW5ldF9xNWtfbWF0bXVsX3JtIl09X3NrYWluZXRfcTVrX21hdG11bF9ybT13YXNtRXhwb3J0c1siSCJdO01vZHVsZVsiX3NrYWluZXRfcTZrX21hdG11bCJdPV9za2FpbmV0X3E2a19tYXRtdWw9d2FzbUV4cG9ydHNbIkkiXTtNb2R1bGVbIl9za2FpbmV0X3E2a19tYXRtdWxfcm0iXT1fc2thaW5ldF9xNmtfbWF0bXVsX3JtPXdhc21FeHBvcnRzWyJKIl07TW9kdWxlWyJfc2thaW5ldF90ZXJuYXJ5X2YzMl9nZW12Il09X3NrYWluZXRfdGVybmFyeV9mMzJfZ2Vtdj13YXNtRXhwb3J0c1siSyJdO01vZHVsZVsiX3NrYWluZXRfdGVybmFyeV9sbWhlYWRfc3RhZ2UxIl09X3NrYWluZXRfdGVybmFyeV9sbWhlYWRfc3RhZ2UxPXdhc21FeHBvcnRzWyJMIl07TW9kdWxlWyJfc2thaW5ldF9iaXRuZXRfZ2Vtdl90cTJfMCJdPV9za2FpbmV0X2JpdG5ldF9nZW12X3RxMl8wPXdhc21FeHBvcnRzWyJNIl07TW9kdWxlWyJfc2thaW5ldF9mMzJfYmluYXJ5Il09X3NrYWluZXRfZjMyX2JpbmFyeT13YXNtRXhwb3J0c1siTiJdO01vZHVsZVsiX3NrYWluZXRfZjMyX3VuYXJ5Il09X3NrYWluZXRfZjMyX3VuYXJ5PXdhc21FeHBvcnRzWyJPIl07TW9kdWxlWyJfc2thaW5ldF9mMzJfcmVkdWNlX2FsbCJdPV9za2FpbmV0X2YzMl9yZWR1Y2VfYWxsPXdhc21FeHBvcnRzWyJQIl07TW9kdWxlWyJfc2thaW5ldF9mMzJfcmVkdWNlX2F4aXMiXT1fc2thaW5ldF9mMzJfcmVkdWNlX2F4aXM9d2FzbUV4cG9ydHNbIlEiXTtNb2R1bGVbIl9za2FpbmV0X2YzMl9zb2Z0bWF4X3Jvd3MiXT1fc2thaW5ldF9mMzJfc29mdG1heF9yb3dzPXdhc21FeHBvcnRzWyJSIl07TW9kdWxlWyJfc2thaW5ldF9mMzJfbGF5ZXJfbm9ybSJdPV9za2FpbmV0X2YzMl9sYXllcl9ub3JtPXdhc21FeHBvcnRzWyJTIl07TW9kdWxlWyJfc2thaW5ldF9mMzJfcm1zX25vcm0iXT1fc2thaW5ldF9mMzJfcm1zX25vcm09d2FzbUV4cG9ydHNbIlQiXTtNb2R1bGVbIl9za2FpbmV0X2YzMl9zZHBhIl09X3NrYWluZXRfZjMyX3NkcGE9d2FzbUV4cG9ydHNbIlUiXTtfX2Vtc2NyaXB0ZW5fdGxzX2luaXQ9d2FzbUV4cG9ydHNbIlYiXTtfcHRocmVhZF9zZWxmPXdhc21FeHBvcnRzWyJXIl07X19lbXNjcmlwdGVuX3RocmVhZF9pbml0PXdhc21FeHBvcnRzWyJYIl07X19lbXNjcmlwdGVuX3RocmVhZF9jcmFzaGVkPXdhc21FeHBvcnRzWyJZIl07X19lbXNjcmlwdGVuX3J1bl9qc19vbl9tYWluX3RocmVhZD13YXNtRXhwb3J0c1siWiJdO19fZW1zY3JpcHRlbl90aHJlYWRfZnJlZV9kYXRhPXdhc21FeHBvcnRzWyJfIl07X19lbXNjcmlwdGVuX3RocmVhZF9leGl0PXdhc21FeHBvcnRzWyIkIl07X19lbXNjcmlwdGVuX2NoZWNrX21haWxib3g9d2FzbUV4cG9ydHNbImFhIl07X2Vtc2NyaXB0ZW5fc3RhY2tfc2V0X2xpbWl0cz13YXNtRXhwb3J0c1siYmEiXTtfX2Vtc2NyaXB0ZW5fc3RhY2tfcmVzdG9yZT13YXNtRXhwb3J0c1siY2EiXTtfX2Vtc2NyaXB0ZW5fc3RhY2tfYWxsb2M9d2FzbUV4cG9ydHNbImRhIl07X2Vtc2NyaXB0ZW5fc3RhY2tfZ2V0X2N1cnJlbnQ9d2FzbUV4cG9ydHNbImVhIl19dmFyIHdhc21JbXBvcnRzO2Z1bmN0aW9uIGFzc2lnbldhc21JbXBvcnRzKCl7d2FzbUltcG9ydHM9e2c6X19fcHRocmVhZF9jcmVhdGVfanMsazpfX2Vtc2NyaXB0ZW5faW5pdF9tYWluX3RocmVhZF9qcyxuOl9fZW1zY3JpcHRlbl9ub3RpZnlfbWFpbGJveF9wb3N0bWVzc2FnZSxsOl9fZW1zY3JpcHRlbl9yZWNlaXZlX29uX21haW5fdGhyZWFkX2pzLGM6X19lbXNjcmlwdGVuX3RocmVhZF9jbGVhbnVwLGo6X19lbXNjcmlwdGVuX3RocmVhZF9tYWlsYm94X2F3YWl0LGk6X19lbXNjcmlwdGVuX3RocmVhZF9zZXRfc3Ryb25ncmVmLGU6X2Vtc2NyaXB0ZW5fY2hlY2tfYmxvY2tpbmdfYWxsb3dlZCxoOl9lbXNjcmlwdGVuX2V4aXRfd2l0aF9saXZlX3J1bnRpbWUsYjpfZW1zY3JpcHRlbl9nZXRfbm93LG06X2Vtc2NyaXB0ZW5fcmVzaXplX2hlYXAsZjpfZXhpdCxkOl9mZF93cml0ZSxhOndhc21NZW1vcnl9fWZ1bmN0aW9uIGFwcGx5U2lnbmF0dXJlQ29udmVyc2lvbnMod2FzbUV4cG9ydHMpe3dhc21FeHBvcnRzPU9iamVjdC5hc3NpZ24oe30sd2FzbUV4cG9ydHMpO3ZhciBtYWtlV3JhcHBlcl9wcD1mPT5hMD0+ZihhMCk+Pj4wO3ZhciBtYWtlV3JhcHBlcl9wPWY9PigpPT5mKCk+Pj4wO3dhc21FeHBvcnRzWyJ4Il09bWFrZVdyYXBwZXJfcHAod2FzbUV4cG9ydHNbIngiXSk7d2FzbUV4cG9ydHNbIlciXT1tYWtlV3JhcHBlcl9wKHdhc21FeHBvcnRzWyJXIl0pO3dhc21FeHBvcnRzWyJkYSJdPW1ha2VXcmFwcGVyX3BwKHdhc21FeHBvcnRzWyJkYSJdKTt3YXNtRXhwb3J0c1siZWEiXT1tYWtlV3JhcHBlcl9wKHdhc21FeHBvcnRzWyJlYSJdKTtyZXR1cm4gd2FzbUV4cG9ydHN9ZnVuY3Rpb24gcnVuKCl7aWYocnVuRGVwZW5kZW5jaWVzPjApe2RlcGVuZGVuY2llc0Z1bGZpbGxlZD1ydW47cmV0dXJufWlmKEVOVklST05NRU5UX0lTX1BUSFJFQUQpe3JlYWR5UHJvbWlzZVJlc29sdmU/LihNb2R1bGUpO2luaXRSdW50aW1lKCk7cmV0dXJufXByZVJ1bigpO2lmKHJ1bkRlcGVuZGVuY2llcz4wKXtkZXBlbmRlbmNpZXNGdWxmaWxsZWQ9cnVuO3JldHVybn1mdW5jdGlvbiBkb1J1bigpe01vZHVsZVsiY2FsbGVkUnVuIl09dHJ1ZTtpZihBQk9SVClyZXR1cm47aW5pdFJ1bnRpbWUoKTtyZWFkeVByb21pc2VSZXNvbHZlPy4oTW9kdWxlKTtNb2R1bGVbIm9uUnVudGltZUluaXRpYWxpemVkIl0/LigpO3Bvc3RSdW4oKX1pZihNb2R1bGVbInNldFN0YXR1cyJdKXtNb2R1bGVbInNldFN0YXR1cyJdKCJSdW5uaW5nLi4uIik7c2V0VGltZW91dCgoKT0+e3NldFRpbWVvdXQoKCk9Pk1vZHVsZVsic2V0U3RhdHVzIl0oIiIpLDEpO2RvUnVuKCl9LDEpfWVsc2V7ZG9SdW4oKX19dmFyIHdhc21FeHBvcnRzO2lmKCFFTlZJUk9OTUVOVF9JU19QVEhSRUFEKXt3YXNtRXhwb3J0cz1hd2FpdCAoY3JlYXRlV2FzbSgpKTtydW4oKX1pZihydW50aW1lSW5pdGlhbGl6ZWQpe21vZHVsZVJ0bj1Nb2R1bGV9ZWxzZXttb2R1bGVSdG49bmV3IFByb21pc2UoKHJlc29sdmUscmVqZWN0KT0+e3JlYWR5UHJvbWlzZVJlc29sdmU9cmVzb2x2ZTtyZWFkeVByb21pc2VSZWplY3Q9cmVqZWN0fSl9CjtyZXR1cm4gbW9kdWxlUnRufWV4cG9ydCBkZWZhdWx0IGNyZWF0ZVNrYWluZXRLZXJuZWxzO3ZhciBpc1B0aHJlYWQ9Z2xvYmFsVGhpcy5zZWxmPy5uYW1lPy5zdGFydHNXaXRoKCJlbS1wdGhyZWFkIik7dmFyIGlzTm9kZT10eXBlb2YgcHJvY2Vzcz09Im9iamVjdCImJnByb2Nlc3MudmVyc2lvbnM/Lm5vZGUmJnByb2Nlc3MudHlwZSE9InJlbmRlcmVyIjtpZihpc05vZGUpaXNQdGhyZWFkPShhd2FpdCBpbXBvcnQoIndvcmtlcl90aHJlYWRzIikpLndvcmtlckRhdGE9PT0iZW0tcHRocmVhZCI7aXNQdGhyZWFkJiZjcmVhdGVTa2FpbmV0S2VybmVscygpOwo=" + const val MT_WASM: String = "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AIC/AAAAAAAAgD8AAIC/AACAvwAAgD8AAIA/AACAvwAAgL8AAABAAACAPwAAgL8AAIC/AACAvwAAAEAAAIC/AACAvwAAAAAAAABAAACAvwAAgL8AAIA/AAAAQAAAgL8AAIC/AAAAQAAAAEAAAIC/AACAvwAAgL8AAIC/AAAAAAAAgL8AAAAAAACAvwAAAAAAAIC/AACAPwAAgL8AAAAAAACAvwAAAEAAAIC/AAAAAAAAgL8AAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgL8AAABAAAAAAAAAAAAAAIC/AACAvwAAgD8AAAAAAACAvwAAAAAAAIA/AAAAAAAAgL8AAIA/AACAPwAAAAAAAIC/AAAAQAAAgD8AAAAAAACAvwAAgL8AAABAAAAAAAAAgL8AAAAAAAAAQAAAAAAAAIC/AACAPwAAAEAAAAAAAACAvwAAAEAAAABAAAAAAAAAgL8AAIC/AACAvwAAgD8AAIC/AAAAAAAAgL8AAIA/AACAvwAAgD8AAIC/AACAPwAAgL8AAABAAACAvwAAgD8AAIC/AACAvwAAAAAAAIA/AACAvwAAAAAAAAAAAACAPwAAgL8AAIA/AAAAAAAAgD8AAIC/AAAAQAAAAAAAAIA/AACAvwAAgL8AAIA/AACAPwAAgL8AAAAAAACAPwAAgD8AAIC/AACAPwAAgD8AAIA/AACAvwAAAEAAAIA/AACAPwAAgL8AAIC/AAAAQAAAgD8AAIC/AAAAAAAAAEAAAIA/AACAvwAAgD8AAABAAACAPwAAgL8AAABAAAAAQAAAgD8AAIC/AACAvwAAgL8AAABAAACAvwAAAAAAAIC/AAAAQAAAgL8AAIA/AACAvwAAAEAAAIC/AAAAQAAAgL8AAABAAACAvwAAgL8AAAAAAAAAQAAAgL8AAAAAAAAAAAAAAEAAAIC/AACAPwAAAAAAAABAAACAvwAAAEAAAAAAAAAAQAAAgL8AAIC/AACAPwAAAEAAAIC/AAAAAAAAgD8AAABAAACAvwAAgD8AAIA/AAAAQAAAgL8AAABAAACAPwAAAEAAAIC/AACAvwAAAEAAAABAAACAvwAAAAAAAABAAAAAQAAAgL8AAIA/AAAAQAAAAEAAAIC/AAAAQAAAAEAAAABAAACAvwAAgL8AAIC/AACAvwAAAAAAAAAAAACAvwAAgL8AAAAAAACAPwAAgL8AAIC/AAAAAAAAAEAAAIC/AACAvwAAAAAAAIC/AAAAAAAAgL8AAAAAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAAAAAACAvwAAAAAAAABAAAAAAAAAgL8AAAAAAACAvwAAgD8AAIC/AAAAAAAAAAAAAIA/AACAvwAAAAAAAIA/AACAPwAAgL8AAAAAAAAAQAAAgD8AAIC/AAAAAAAAgL8AAABAAACAvwAAAAAAAAAAAAAAQAAAgL8AAAAAAACAPwAAAEAAAIC/AAAAAAAAAEAAAABAAACAvwAAAAAAAIC/AACAvwAAAAAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAIC/AAAAAAAAAAAAAABAAACAvwAAAAAAAAAAAACAvwH2KoA/AAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAgL8AAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAAEAAAIA/AAAAAAAAAAAAAIC/AAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAACAvwAAgL8AAIA/AAAAAAAAAAAAAIC/AACAPwAAAAAAAIA/AACAvwAAgD8AAAAAAAAAQAAAgL8AAIA/AAAAAAAAgL8AAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAAAAAIA/AAAAAAAAAEAAAAAAAACAPwAAAAAAAIC/AACAPwAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAgD8AAIA/AACAPwAAAAAAAABAAACAPwAAgD8AAAAAAACAvwAAAEAAAIA/AAAAAAAAAAAAAABAAACAPwAAAAAAAIA/AAAAQAAAgD8AAAAAAAAAQAAAAEAAAIA/AAAAAAAAgL8AAIC/AAAAQAAAAAAAAAAAAACAvwAAAEAAAAAAAACAPwAAgL8AAABAAAAAAAAAAEAAAIC/AAAAQAAAAAAAAIC/AAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAgD8AAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAACAvwAAgD8AAABAAAAAAAAAAAAAAIA/AAAAQAAAAAAAAIA/AACAPwAAAEAAAAAAAAAAQAAAgD8AAABAAAAAAAAAgL8AAABAAAAAQAAAAAAAAAAAAAAAQAAAAEAAAAAAAACAPwAAAEAAAABAAAAAAAAAAEAAAABAAAAAQAAAAAAAAIC/AACAvwAAgL8AAIA/AAAAAAAAgL8AAIC/AACAPwAAgD8AAIC/AACAvwAAgD8AAABAAACAvwAAgL8AAIA/AACAvwAAAAAAAIC/AACAPwAAAAAAAAAAAACAvwAAgD8AAIA/AAAAAAAAgL8AAIA/AAAAQAAAAAAAAIC/AACAPwAAgL8AAIA/AACAvwAAgD8AAAAAAACAPwAAgL8AAIA/AACAPwAAgD8AAIC/AACAPwAAAEAAAIA/AACAvwAAgD8AAIC/AAAAQAAAgL8AAIA/AAAAAAAAAEAAAIC/AACAPwAAgD8AAABAAACAvwAAgD8AAABAAAAAQAAAgL8AAIA/AACAvwAAgL8AAAAAAACAPwAAAAAAAIC/AAAAAAAAgD8AAIA/AACAvwAAAAAAAIA/AAAAQAAAgL8AAAAAAACAPwAAgL8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AACAPwAAAAAAAAAAAACAPwAAAEAAAAAAAAAAAAAAgD8AAIC/AACAPwAAAAAAAIA/AAAAAAAAgD8AAAAAAACAPwAAgD8AAIA/AAAAAAAAgD8AAABAAACAPwAAAAAAAIA/AACAvwAAAEAAAAAAAACAPwAAAAAAAABAAAAAAAAAgD8AAIA/AAAAQAAAAAAAAIA/AAAAQAAAAEAAAAAAAACAPwAAgL8AAIC/AACAPwAAgD8AAAAAAACAvwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAAEAAAIC/AACAPwAAgD8AAIC/AAAAAAAAgD8AAIA/AAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAACAPwAAgD8AAABAAAAAAAAAgD8AAIA/AACAvwAAgD8AAIA/AACAPwAAAAAAAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AAAAQAAAgD8AAIA/AACAPwAAgL8AAABAAACAPwAAgD8AAAAAAAAAQAAAgD8AAIA/AACAPwAAAEAAAIA/AACAPwAAAEAAAABAAACAPwAAgD8AAIC/AACAvwAAAEAAAIA/AAAAAAAAgL8AAABAAACAPwAAgD8AAIC/AAAAQAAAgD8AAABAAACAvwAAAEAAAIA/AACAvwAAAAAAAABAAACAPwAAAAAAAAAAAAAAQAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAQAAAAAAAAABAAACAPwAAgL8AAIA/AAAAQAAAgD8AAAAAAACAPwAAAEAAAIA/AACAPwAAgD8AAABAAACAPwAAAEAAAIA/AAAAQAAAgD8AAIC/AAAAQAAAAEAAAIA/AAAAAAAAAEAAAABAAACAPwAAgD8AAABAAAAAQAAAgD8AAABAAAAAQAAAAEAAAIA/AACAvwAAgL8AAIC/AAAAQAAAAAAAAIC/AACAvwAAAEAAAIA/AACAvwAAgL8AAABAAAAAQAAAgL8AAIC/AAAAQAAAgL8AAAAAAACAvwAAAEAAAAAAAAAAAAAAgL8AAABAAACAPwAAAAAAAIC/AAAAQAAAAEAAAAAAAACAvwAAAEAAAIC/AACAPwAAgL8AAABAAAAAAAAAgD8AAIC/AAAAQAAAgD8AAIA/AACAvwAAAEAAAABAAACAPwAAgL8AAABAAACAvwAAAEAAAIC/AAAAQAAAAAAAAABAAACAvwAAAEAAAIA/AAAAQAAAgL8AAABAAAAAQAAAAEAAAIC/AAAAQAAAgL8AAIC/AAAAAAAAAEAAAAAAAACAvwAAAAAAAABAAACAPwAAgL8AAAAAAAAAQAAAAEAAAIC/AAAAAAAAAEAAAIC/AAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAgD8AAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAABAAACAvwAAgD8AAAAAAAAAQAAAAAAAAIA/AAAAAAAAAEAAAIA/AACAPwAAAAAAAABAAAAAQAAAgD8AAAAAAAAAQAAAgL8AAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAACAPwAAAEAAAAAAAAAAQAAAAEAAAABAAAAAAAAAAEAAAIC/AACAvwAAgD8AAABAAAAAAAAAgL8AAIA/AAAAQAAAgD8AAIC/AACAPwAAAEAAAABAAACAvwAAgD8AAABAAACAvwAAAAAAAIA/AAAAQAAAAAAAAAAAAACAPwAAAEAAAIA/AAAAAAAAgD8AAABAAAAAQAAAAAAAAIA/AAAAQAAAgL8AAIA/AACAPwAAAEAAAAAAAACAPwAAgD8AAABAAACAPwAAgD8AAIA/AAAAQAAAAEAAAIA/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" } diff --git a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmCpuOps.kt b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmCpuOps.kt index eb1df89f0..42f6a5849 100644 --- a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmCpuOps.kt +++ b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmCpuOps.kt @@ -2,25 +2,52 @@ package sk.ainet.exec.wasm import sk.ainet.context.schedule.Schedule import sk.ainet.exec.tensor.ops.DefaultCpuOpsBase +import sk.ainet.lang.memory.Scope +import sk.ainet.lang.memory.ScopeKind import sk.ainet.lang.tensor.Shape import sk.ainet.lang.tensor.Tensor import sk.ainet.lang.tensor.data.FloatArrayTensorData +import sk.ainet.lang.tensor.data.StorageFloatTensorData import sk.ainet.lang.tensor.data.TensorData import sk.ainet.lang.tensor.data.TensorDataFactory +import sk.ainet.lang.tensor.ops.FusedNormOps import sk.ainet.lang.tensor.ops.TensorOps +import sk.ainet.lang.tensor.storage.MemoryDomain import sk.ainet.lang.types.DType import sk.ainet.lang.types.FP32 +import kotlin.reflect.KClass /** - * The browser tier's `TensorOps` (SKEEP-007 §T1): [DefaultCpuOpsBase] with the dense FP32 matmuls - * (`x · Wᵀ` and `A · B`) routed through the SIMD128 sidecar. Packed weights need no override — they reach the pack through - * `KernelDispatch` like on every other tier. Everything else is the common scalar implementation, - * and so is everything here while the sidecar is not ready. + * The browser tier's `TensorOps` (SKEEP-007 §T1 and the resident-activation step): [DefaultCpuOpsBase] + * with the dense matmuls, the elementwise arithmetic, the activations and the reductions routed + * through the SIMD128 sidecar, and their outputs kept **resident** in the sidecar's linear memory + * ([WasmLinearTensorData]) so a chain of them never copies an activation back to the Kotlin heap + * between kernels. Packed weights need no override — they reach the pack through `KernelDispatch` + * like on every other tier. Everything else is the common scalar implementation, which reads a + * resident tensor through its heap mirror (one bulk download, the first time), and so is + * everything here while the sidecar is not ready. + * + * What goes to the sidecar, and when: + * + * - `matmul`, `matmulWeightTransposed` (dense FP32): always; the output is resident. + * - `add`/`subtract`/`multiply`/`divide`: when at least one operand is already resident. Two heap + * operands stay on the common path — uploading both costs more than the scalar loop saves, and + * a chain starts resident at its first matmul anyway. + * - `sigmoid`/`silu`/`gelu`/`erf`/`tanh`: always — the transcendental loop is several times the + * upload. `relu`, `sum`, `mean`: when the operand is resident. + * - `softmax`/`logSoftmax` over the last dimension, `scaledDotProductAttention` and the fused + * normalisations of [FusedNormOps] (what `LayerNormalization` calls): always — one sidecar pass + * against several allocating scalar passes, whatever the operands' home. + * + * An output is resident only while [WasmSimd.residentBudgetBytes] allows; above it the kernel still + * runs in the sidecar and the result is read back to the heap. Scoped contexts (`ForwardScope`) + * allocate the resident output from the scope, so `reset()` frees the sidecar bytes; ambient outputs + * are freed when their tensor is collected (see [WasmLinearStorage]). */ public class WasmCpuOps( dataFactory: TensorDataFactory, schedule: Schedule, -) : DefaultCpuOpsBase(dataFactory, schedule) { +) : DefaultCpuOpsBase(dataFactory, schedule), FusedNormOps { override fun withSchedule(schedule: Schedule): TensorOps = if (schedule === this.schedule) this else WasmCpuOps(dataFactory, schedule) @@ -28,65 +55,416 @@ public class WasmCpuOps( /** The sidecar computes `x · Wᵀ` from the weight's rows itself; no Kotlin-side row-dot and no transposed copy. */ override fun preferDirectWeightTransposed(): Boolean = false + // ── operands and outputs ────────────────────────────────────────────────────────────────────── + + /** A sidecar address for an operand: a resident tensor's own, or a staged copy freed after the call. */ + private class Operand(val ptr: Int, val temp: Boolean) + + private fun isResident(t: Tensor<*, *>): Boolean = (t.data as? WasmLinearTensorData<*>)?.isResident == true + + /** Dense FP32 the sidecar can take: resident, a heap array, or a slab window. */ + private fun isSidecarFloat(t: Tensor<*, *>): Boolean = + t.dtype == FP32::class && (t.data is WasmLinearTensorData<*> || t.data is FloatArrayTensorData<*> || t.data is StorageFloatTensorData<*>) + + private fun operand(t: Tensor<*, *>): Operand? = when (val d = t.data) { + is WasmLinearTensorData<*> -> if (d.isResident) Operand(d.pointer(), false) else null + is FloatArrayTensorData<*> -> stage(d.buffer, 0, t.shape.volume) + is StorageFloatTensorData<*> -> { + val s = d.storage + s.checkAlive() + stage(s.floats!!, s.arrayOffset, t.shape.volume) + } + else -> null + } + + private fun stage(src: FloatArray, offset: Int, count: Int): Operand { + val ptr = WasmKernels.alloc(maxOf(count, 1) * 4) + if (count > 0) WasmKernels.writeFloats(ptr, src, offset, count) + return Operand(ptr, true) + } + + private fun release(o: Operand?) { + if (o != null && o.temp) WasmKernels.free(o.ptr) + } + + /** A resident output allocation from the active scope (ambient: GC-managed), or `null` when the scope cannot place it in the sidecar. */ + private fun residentStorage(bytes: Long): WasmLinearStorage? { + val scope = dispatchScope() + return if (scope === Scope.Ambient) WasmLinearStorage.allocate(bytes, ScopeKind.AMBIENT, zero = false) + else scope.allocate(bytes, MemoryDomain.HOST_OFFHEAP) as? WasmLinearStorage + } + /** - * Dense `A(m,k) · B(k,n)` on heap FP32 operands runs the sidecar GEMM. The common implementation - * serves this shape with its own `[k, n]` loop before it consults the registry, so without this - * override the SIMD128 kernel was reachable only through `KernelDispatch` (batched or view - * operands) and a plain 2-D matmul stayed at scalar speed. Both operands are staged per call: - * `B` here is not necessarily a parameter (attention scores, projections of activations), so - * nothing is kept resident. A packed weight is not `FloatArrayTensorData` and keeps its path. + * Run [fill] into an output buffer of [shape]: resident when the budget allows, otherwise a + * temporary that is read back to the heap and adopted through the factory. */ @Suppress("UNCHECKED_CAST") + private inline fun output( + shape: Shape, + dtype: KClass, + inputs: Array>, + fill: (outPtr: Int) -> Unit, + ): Tensor { + val n = shape.volume + val bytes = n.toLong() * 4 + if (WasmSimd.residentBudgetBytes - WasmKernels.residentActivationBytes() >= bytes) { + residentStorage(bytes)?.let { storage -> + fill(storage.pointer()) + return newTensor(WasmLinearTensorData(shape, storage) as TensorData, dtype, *inputs) + } + } + val tmp = WasmKernels.alloc(maxOf(n, 1) * 4) + try { + fill(tmp) + val out = FloatArray(n) + if (n > 0) WasmKernels.readFloats(tmp, out, 0, n) + return floatResult(shape, dtype, out, *inputs) + } finally { + WasmKernels.free(tmp) + } + } + + /** + * A resident copy of a dense FP32 [tensor] — the explicit "upload once" for an input that many + * sidecar ops will read (a bench's operands, a prompt's embeddings), so none of them stages it + * per call. The tensor itself if it is already resident; the common data unchanged when the + * sidecar is not ready or the budget is spent. + */ + public fun toResident(tensor: Tensor): Tensor { + if (!WasmKernels.isReady || !isSidecarFloat(tensor) || isResident(tensor)) return tensor + val n = tensor.shape.volume + val o = operand(tensor) ?: return tensor + try { + val src = o.ptr + val bytes = n.toLong() * 4 + if (WasmSimd.residentBudgetBytes - WasmKernels.residentActivationBytes() < bytes) return tensor + val storage = residentStorage(bytes) ?: return tensor + WasmKernels.copy(src, storage.pointer(), n * 4) + @Suppress("UNCHECKED_CAST") + return newTensor(WasmLinearTensorData(tensor.shape, storage) as TensorData, tensor.dtype, tensor) + } finally { + release(o) + } + } + + // ── matmuls ─────────────────────────────────────────────────────────────────────────────────── + + /** + * Dense `A(m,k) · B(k,n)` on FP32 operands runs the sidecar GEMM. The common implementation + * serves this shape with its own `[k, n]` loop before it consults the registry, so without this + * override the SIMD128 kernel was reachable only through `KernelDispatch`. Resident operands are + * read in place; heap operands are staged per call (`B` is not necessarily a parameter). A packed + * weight is not dense FP32 data and keeps its path. + */ override fun matmul(a: Tensor, b: Tensor): Tensor { - if (WasmKernels.isReady && a.rank == 2 && b.rank == 2 && a.dtype == FP32::class && b.dtype == FP32::class) { - val ad = a.data as? FloatArrayTensorData<*> - val bd = b.data as? FloatArrayTensorData<*> - if (ad != null && bd != null && untransposedWeight(b) == null) { - val m = a.shape[0] - val k = a.shape[1] - val n = b.shape[1] - require(k == b.shape[0]) { "Matrix multiplication shape mismatch: ${a.shape} vs ${b.shape}" } - val out = FloatArray(m * n) - if (m > 0 && n > 0) WasmFp32MatmulKernel.matmul(ad.buffer, 0, k, bd.buffer, 0, n, out, 0, n, m, n, k) - val outData = dataFactory.adoptFloatArray(Shape(m, n), a.dtype, out) as TensorData - return newTensor(outData, a.dtype, a, b) + if (WasmKernels.isReady && a.rank == 2 && b.rank == 2 && isSidecarFloat(a) && isSidecarFloat(b) && untransposedWeight(b) == null) { + val m = a.shape[0] + val k = a.shape[1] + val n = b.shape[1] + require(k == b.shape[0]) { "Matrix multiplication shape mismatch: ${a.shape} vs ${b.shape}" } + if (m > 0 && n > 0) { + var ao: Operand? = null + var bo: Operand? = null + try { + ao = operand(a) ?: return super.matmul(a, b) + bo = operand(b) ?: return super.matmul(a, b) + val aPtr = ao.ptr + val bPtr = bo.ptr + return output(Shape(m, n), a.dtype, arrayOf(a, b)) { out -> WasmKernels.fp32Matmul(aPtr, k, bPtr, n, out, n, m, n, k) } + } finally { + release(bo) + release(ao) + } } } return super.matmul(a, b) } - @Suppress("UNCHECKED_CAST") override fun matmulWeightTransposed(x: Tensor, weight: Tensor): Tensor { - if (WasmKernels.isReady && weight.shape.rank == 2 && x.rank >= 1 && x.dtype == FP32::class && weight.dtype == FP32::class) { - val w = weight.data as? FloatArrayTensorData<*> - val a = x.data as? FloatArrayTensorData<*> - if (w != null && a != null) { - val n = weight.shape[0] - val k = weight.shape[1] - if (x.shape.dimensions.last() == k) { - val m = if (k == 0) 0 else x.shape.volume / k - val out = FloatArray(m * n) - if (m > 0 && n > 0) { - val inPtr = WasmKernels.alloc(m * k * 4) - val outPtr = WasmKernels.alloc(m * n * 4) - try { - WasmKernels.writeFloats(inPtr, a.buffer, 0, m * k) - // Resident like a packed weight: a parameter is immutable once loaded, and - // re-uploading it per call made single-token decode run at copy speed. - val wPtr = WasmKernels.denseWeightHandle(w.buffer) - WasmKernels.fp32MatmulWt(inPtr, wPtr, outPtr, m, n, k) - WasmKernels.readFloats(outPtr, out, 0, m * n) - } finally { - WasmKernels.free(outPtr) - WasmKernels.free(inPtr) + if (WasmKernels.isReady && weight.shape.rank == 2 && x.rank >= 1 && isSidecarFloat(x) && isSidecarFloat(weight)) { + val n = weight.shape[0] + val k = weight.shape[1] + if (x.shape.dimensions.last() == k) { + val m = if (k == 0) 0 else x.shape.volume / k + if (m > 0 && n > 0) { + var xo: Operand? = null + var wo: Operand? = null + try { + xo = operand(x) ?: return super.matmulWeightTransposed(x, weight) + // A heap weight is a parameter: resident after its first call (re-uploading it + // per call made single-token decode run at copy speed). Resident and slab + // weights are read where they are. + wo = when (val wd = weight.data) { + is FloatArrayTensorData<*> -> Operand(WasmKernels.denseWeightHandle(wd.buffer), false) + else -> operand(weight) ?: return super.matmulWeightTransposed(x, weight) } + val xPtr = xo.ptr + val wPtr = wo.ptr + val outShape = if (x.rank == 1) Shape(n) else Shape(*(x.shape.dimensions.copyOf(x.rank - 1) + n)) + return output(outShape, x.dtype, arrayOf(x, weight)) { out -> WasmKernels.fp32MatmulWt(xPtr, wPtr, out, m, n, k) } + } finally { + release(wo) + release(xo) } - val outShape = if (x.rank == 1) Shape(n) else Shape(*(x.shape.dimensions.copyOf(x.rank - 1) + n)) - val outData = dataFactory.adoptFloatArray(outShape, x.dtype, out) as TensorData - return newTensor(outData, x.dtype, x, weight) } } } return super.matmulWeightTransposed(x, weight) } + + // ── elementwise ─────────────────────────────────────────────────────────────────────────────── + + /** The operand's index mode against the broadcast output shape, or `null` for a broadcast the kernel does not take. */ + private fun mode(s: Shape, out: Shape): Int? = when { + s == out -> WasmKernels.F32_FULL + s.volume == 1 -> WasmKernels.F32_SCALAR + s.rank >= 1 && out.rank >= 1 && s[s.rank - 1] == out[out.rank - 1] && s.dimensions.dropLast(1).all { it == 1 } -> WasmKernels.F32_BIAS + else -> null + } + + private fun binary(op: Int, a: Tensor, b: Tensor): Tensor? { + if (!WasmKernels.isReady || a.dtype != b.dtype || !isSidecarFloat(a) || !isSidecarFloat(b)) return null + if (!isResident(a) && !isResident(b)) return null + val outShape = try { + broadcastShapes(a.shape, b.shape) + } catch (e: IllegalArgumentException) { + return null + } + val n = outShape.volume + if (n == 0) return null + val aMode = mode(a.shape, outShape) ?: return null + val bMode = mode(b.shape, outShape) ?: return null + val inner = outShape[outShape.rank - 1] + var ao: Operand? = null + var bo: Operand? = null + try { + ao = operand(a) ?: return null + bo = operand(b) ?: return null + val aPtr = ao.ptr + val bPtr = bo.ptr + return output(outShape, a.dtype, arrayOf(a, b)) { out -> WasmKernels.f32Binary(op, aPtr, aMode, bPtr, bMode, out, n, inner) } + } finally { + release(bo) + release(ao) + } + } + + override fun add(a: Tensor, b: Tensor): Tensor = + binary(WasmKernels.F32_ADD, a, b) ?: super.add(a, b) + + override fun subtract(a: Tensor, b: Tensor): Tensor = + binary(WasmKernels.F32_SUB, a, b) ?: super.subtract(a, b) + + override fun multiply(a: Tensor, b: Tensor): Tensor = + binary(WasmKernels.F32_MUL, a, b) ?: super.multiply(a, b) + + override fun divide(a: Tensor, b: Tensor): Tensor = + binary(WasmKernels.F32_DIV, a, b) ?: super.divide(a, b) + + // ── activations ─────────────────────────────────────────────────────────────────────────────── + + private fun unary(op: Int, t: Tensor, heapToo: Boolean): Tensor? { + if (!WasmKernels.isReady || !isSidecarFloat(t)) return null + if (!heapToo && !isResident(t)) return null + val n = t.shape.volume + if (n == 0) return null + val o = operand(t) ?: return null + try { + val inPtr = o.ptr + return output(t.shape, t.dtype, arrayOf(t)) { out -> WasmKernels.f32Unary(op, inPtr, out, n) } + } finally { + release(o) + } + } + + override fun relu(tensor: Tensor): Tensor = + unary(WasmKernels.F32_RELU, tensor, heapToo = false) ?: super.relu(tensor) + + override fun sigmoid(tensor: Tensor): Tensor = + unary(WasmKernels.F32_SIGMOID, tensor, heapToo = true) ?: super.sigmoid(tensor) + + override fun silu(tensor: Tensor): Tensor = + unary(WasmKernels.F32_SILU, tensor, heapToo = true) ?: super.silu(tensor) + + override fun gelu(tensor: Tensor, approximate: Boolean): Tensor = + unary(if (approximate) WasmKernels.F32_GELU_TANH else WasmKernels.F32_GELU_EXACT, tensor, heapToo = true) + ?: super.gelu(tensor, approximate) + + override fun erf(tensor: Tensor): Tensor = + unary(WasmKernels.F32_ERF, tensor, heapToo = true) ?: super.erf(tensor) + + override fun tanh(tensor: Tensor): Tensor = + unary(WasmKernels.F32_TANH, tensor, heapToo = true) ?: super.tanh(tensor) + + // ── reductions ──────────────────────────────────────────────────────────────────────────────── + + private fun reduce(t: Tensor, dim: Int?, mean: Boolean): Tensor? { + if (!WasmKernels.isReady || !isSidecarFloat(t) || !isResident(t)) return null + val rank = t.rank + val volume = t.shape.volume + if (volume == 0) return null + val o = operand(t) ?: return null + try { + val inPtr = o.ptr + if (dim == null) { + return output(Shape(), t.dtype, arrayOf(t)) { out -> WasmKernels.f32ReduceAll(inPtr, volume, mean, out) } + } + val nd = if (dim < 0) dim + rank else dim + if (nd !in 0 until rank) return null + val dims = t.shape.dimensions + var outer = 1 + for (i in 0 until nd) outer *= dims[i] + val red = dims[nd] + var inner = 1 + for (i in nd + 1 until rank) inner *= dims[i] + val outDims = IntArray(rank - 1) + var oi = 0 + for (i in 0 until rank) { if (i == nd) continue; outDims[oi++] = dims[i] } + return output(Shape(outDims), t.dtype, arrayOf(t)) { out -> WasmKernels.f32ReduceAxis(inPtr, outer, red, inner, mean, out) } + } finally { + release(o) + } + } + + override fun sum(tensor: Tensor, dim: Int?): Tensor = + reduce(tensor, dim, mean = false) ?: super.sum(tensor, dim) + + override fun mean(tensor: Tensor, dim: Int?): Tensor = + reduce(tensor, dim, mean = true) ?: super.mean(tensor, dim) + + // ── fused row kernels: softmax, the normalisations, attention ───────────────────────────────── + + private fun softmaxRows(t: Tensor, dim: Int, log: Boolean): Tensor? { + if (!WasmKernels.isReady || !isSidecarFloat(t)) return null + val rank = t.rank + if (rank == 0) return null + val nd = if (dim < 0) dim + rank else dim + if (nd != rank - 1) return null + val cols = t.shape[nd] + val n = t.shape.volume + if (cols <= 0 || n == 0) return null + val o = operand(t) ?: return null + try { + val inPtr = o.ptr + return output(t.shape, t.dtype, arrayOf(t)) { out -> WasmKernels.f32SoftmaxRows(inPtr, out, n / cols, cols, log) } + } finally { + release(o) + } + } + + override fun softmax(tensor: Tensor, dim: Int): Tensor = + softmaxRows(tensor, dim, log = false) ?: super.softmax(tensor, dim) + + override fun logSoftmax(tensor: Tensor, dim: Int): Tensor = + softmaxRows(tensor, dim, log = true) ?: super.logSoftmax(tensor, dim) + + /** A norm parameter of [cols] values as a sidecar operand: in place or staged when dense FP32, copied out otherwise (a placeholder). */ + private fun normParam(t: Tensor<*, *>, cols: Int): Operand? { + if (t.shape.volume != cols) return null + if (t.dtype == FP32::class) operand(t)?.let { return it } + return stage(t.data.copyToFloatArray(), 0, cols) + } + + private fun norm( + input: Tensor, gamma: Tensor?, beta: Tensor?, eps: Float, normalizedRank: Int, rms: Boolean, + ): Tensor? { + if (!WasmKernels.isReady || !isSidecarFloat(input)) return null + val rank = input.rank + if (normalizedRank !in 1..rank) return null + var cols = 1 + for (i in rank - normalizedRank until rank) cols *= input.shape[i] + val n = input.shape.volume + if (cols == 0 || n == 0) return null + val rows = n / cols + var io: Operand? = null + var go: Operand? = null + var bo: Operand? = null + try { + io = operand(input) ?: return null + go = gamma?.let { normParam(it, cols) ?: return null } + bo = beta?.let { normParam(it, cols) ?: return null } + val inPtr = io.ptr + val gPtr = go?.ptr ?: 0 + val bPtr = bo?.ptr ?: 0 + val inputs = listOfNotNull(input, gamma, beta).toTypedArray() + return output(input.shape, input.dtype, inputs) { out -> + if (rms) WasmKernels.f32RmsNorm(inPtr, gPtr, out, rows, cols, eps) + else WasmKernels.f32LayerNorm(inPtr, gPtr, bPtr, out, rows, cols, eps) + } + } finally { + release(bo) + release(go) + release(io) + } + } + + override fun layerNorm(input: Tensor, gamma: Tensor?, beta: Tensor?, eps: Float, normalizedRank: Int): Tensor? = + norm(input, gamma, beta, eps, normalizedRank, rms = false) + + override fun rmsNorm(input: Tensor, gamma: Tensor?, eps: Float, normalizedRank: Int): Tensor? = + norm(input, gamma, null, eps, normalizedRank, rms = true) + + /** + * Attention in one sidecar pass (`skainet_f32_sdpa`): the same operand contract and mask rules + * as the common implementation, the scores never materialised on the Kotlin heap, query rows + * threaded in the sidecar's pool. Resident operands are read in place — a chain + * `q/k/v = x · Wᵀ → sdpa → · Wₒᵀ` never leaves linear memory. + */ + override fun scaledDotProductAttention( + query: Tensor, + key: Tensor, + value: Tensor, + mask: Tensor?, + scale: Float, + causal: Boolean, + keyLengths: Tensor?, + window: Int, + rightContext: Int, + ): Tensor { + if (!WasmKernels.isReady || !isSidecarFloat(query) || !isSidecarFloat(key) || !isSidecarFloat(value) || (mask != null && !isSidecarFloat(mask))) { + return super.scaledDotProductAttention(query, key, value, mask, scale, causal, keyLengths, window, rightContext) + } + requireSdpaOperands(query, key, value, window, rightContext) + val batch = query.shape[0] + val heads = query.shape[1] + val seqQ = query.shape[2] + val headDim = query.shape[3] + val kvHeads = key.shape[1] + val seqKV = key.shape[2] + val lengths = sdpaKeyLengths(keyLengths, batch, seqKV) + val maskDims = sdpaMaskDims(mask, batch, heads, kvHeads, seqQ, seqKV) ?: intArrayOf(1, 1, 1, 1) + val effectiveScale = if (scale == 0f) (1.0 / kotlin.math.sqrt(headDim.toDouble())).toFloat() else scale + var qo: Operand? = null + var ko: Operand? = null + var vo: Operand? = null + var mo: Operand? = null + var lengthsPtr = 0 + try { + qo = operand(query) ?: return super.scaledDotProductAttention(query, key, value, mask, scale, causal, keyLengths, window, rightContext) + ko = operand(key) ?: return super.scaledDotProductAttention(query, key, value, mask, scale, causal, keyLengths, window, rightContext) + vo = operand(value) ?: return super.scaledDotProductAttention(query, key, value, mask, scale, causal, keyLengths, window, rightContext) + mo = mask?.let { operand(it) ?: return super.scaledDotProductAttention(query, key, value, mask, scale, causal, keyLengths, window, rightContext) } + if (lengths != null && batch > 0) { + lengthsPtr = WasmKernels.alloc(batch * 4) + WasmKernels.writeInts(lengthsPtr, lengths, 0, batch) + } + val qPtr = qo.ptr + val kPtr = ko.ptr + val vPtr = vo.ptr + val mPtr = mo?.ptr ?: 0 + val lPtr = lengthsPtr + return output(Shape(batch, heads, seqQ, headDim), query.dtype, arrayOf(query, key, value)) { out -> + WasmKernels.f32Sdpa( + qPtr, kPtr, vPtr, out, batch, heads, kvHeads, seqQ, seqKV, headDim, + effectiveScale, causal, window, rightContext, lPtr, mPtr, maskDims[0], maskDims[1], maskDims[2], maskDims[3], + ) + } + } finally { + if (lengthsPtr != 0) WasmKernels.free(lengthsPtr) + release(mo) + release(vo) + release(ko) + release(qo) + } + } } diff --git a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmFusedKernels.kt b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmFusedKernels.kt new file mode 100644 index 000000000..aed183a2a --- /dev/null +++ b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmFusedKernels.kt @@ -0,0 +1,128 @@ +package sk.ainet.exec.wasm + +import sk.ainet.backend.api.kernel.Fp32AttentionKernel +import sk.ainet.backend.api.kernel.Fp32NormKernel +import sk.ainet.backend.api.kernel.Fp32SoftmaxKernel + +/* + * The fused row kernels of the sidecar (`f32_fused.c`) behind the SPI faces of `KernelProvider` + * (SKEEP-007 fused ops): the same accessors the JVM FFM provider answers, for callers that hold + * heap arrays. Every face stages its operands into the sidecar per call and reads the output + * back; [WasmCpuOps] bypasses them and hands the kernels resident operands directly. + */ + +/** `skainet_f32_softmax_rows` over heap arrays. */ +public object WasmFp32SoftmaxKernel : Fp32SoftmaxKernel { + override fun softmaxRows(input: FloatArray, inputOffset: Int, out: FloatArray, outOffset: Int, rows: Int, cols: Int, log: Boolean) { + require(rows >= 0 && cols >= 0) { "rows and cols must be non-negative; got rows=$rows cols=$cols" } + if (rows == 0 || cols == 0) return + val n = rows * cols + val inPtr = WasmKernels.alloc(n * 4) + val outPtr = WasmKernels.alloc(n * 4) + try { + WasmKernels.writeFloats(inPtr, input, inputOffset, n) + WasmKernels.f32SoftmaxRows(inPtr, outPtr, rows, cols, log) + WasmKernels.readFloats(outPtr, out, outOffset, n) + } finally { + WasmKernels.free(outPtr) + WasmKernels.free(inPtr) + } + } +} + +/** `skainet_f32_layer_norm` / `skainet_f32_rms_norm` over heap arrays. */ +public object WasmFp32NormKernel : Fp32NormKernel { + override fun layerNorm( + input: FloatArray, inputOffset: Int, + gamma: FloatArray?, beta: FloatArray?, + out: FloatArray, outOffset: Int, + rows: Int, cols: Int, eps: Float, + ): Unit = run(input, inputOffset, gamma, beta, out, outOffset, rows, cols, eps, rms = false) + + override fun rmsNorm( + input: FloatArray, inputOffset: Int, + gamma: FloatArray?, + out: FloatArray, outOffset: Int, + rows: Int, cols: Int, eps: Float, + ): Unit = run(input, inputOffset, gamma, null, out, outOffset, rows, cols, eps, rms = true) + + private fun run( + input: FloatArray, inputOffset: Int, + gamma: FloatArray?, beta: FloatArray?, + out: FloatArray, outOffset: Int, + rows: Int, cols: Int, eps: Float, rms: Boolean, + ) { + require(rows >= 0 && cols >= 0) { "rows and cols must be non-negative; got rows=$rows cols=$cols" } + if (rows == 0 || cols == 0) return + require(gamma == null || gamma.size >= cols) { "gamma has ${gamma?.size} values, the row has $cols" } + require(beta == null || beta.size >= cols) { "beta has ${beta?.size} values, the row has $cols" } + val n = rows * cols + val inPtr = WasmKernels.alloc(n * 4) + val outPtr = WasmKernels.alloc(n * 4) + val gPtr = if (gamma != null) WasmKernels.alloc(cols * 4) else 0 + val bPtr = if (beta != null) WasmKernels.alloc(cols * 4) else 0 + try { + WasmKernels.writeFloats(inPtr, input, inputOffset, n) + if (gamma != null) WasmKernels.writeFloats(gPtr, gamma, 0, cols) + if (beta != null) WasmKernels.writeFloats(bPtr, beta, 0, cols) + if (rms) WasmKernels.f32RmsNorm(inPtr, gPtr, outPtr, rows, cols, eps) + else WasmKernels.f32LayerNorm(inPtr, gPtr, bPtr, outPtr, rows, cols, eps) + WasmKernels.readFloats(outPtr, out, outOffset, n) + } finally { + if (bPtr != 0) WasmKernels.free(bPtr) + if (gPtr != 0) WasmKernels.free(gPtr) + WasmKernels.free(outPtr) + WasmKernels.free(inPtr) + } + } +} + +/** `skainet_f32_sdpa` over heap arrays. */ +public object WasmFp32AttentionKernel : Fp32AttentionKernel { + override fun sdpa( + query: FloatArray, key: FloatArray, value: FloatArray, out: FloatArray, + batch: Int, heads: Int, kvHeads: Int, + seqQ: Int, seqKV: Int, headDim: Int, + scale: Float, causal: Boolean, window: Int, rightContext: Int, + keyLengths: IntArray?, + mask: FloatArray?, maskDims: IntArray?, + ) { + require(batch >= 0 && heads >= 0 && kvHeads >= 0 && seqQ >= 0 && seqKV >= 0 && headDim >= 0) { "dimensions must be non-negative" } + require(kvHeads > 0 && heads % kvHeads == 0) { "heads ($heads) must be a multiple of kvHeads ($kvHeads)" } + require(keyLengths == null || keyLengths.size == batch) { "keyLengths has ${keyLengths?.size} rows, batch is $batch" } + require((mask == null) == (maskDims == null) && (maskDims == null || maskDims.size == 4)) { "mask and maskDims (4 dims) go together" } + val outCount = batch * heads * seqQ * headDim + require(outCount <= out.size) { "output needs $outCount floats, array has ${out.size}" } + if (outCount == 0) return + val kvCount = batch * kvHeads * seqKV * headDim + val md = maskDims ?: intArrayOf(1, 1, 1, 1) + val maskCount = md[0] * md[1] * md[2] * md[3] + val qPtr = WasmKernels.alloc(outCount * 4) + val kPtr = WasmKernels.alloc(maxOf(kvCount, 1) * 4) + val vPtr = WasmKernels.alloc(maxOf(kvCount, 1) * 4) + val outPtr = WasmKernels.alloc(outCount * 4) + val lPtr = if (keyLengths != null) WasmKernels.alloc(batch * 4) else 0 + val mPtr = if (mask != null) WasmKernels.alloc(maxOf(maskCount, 1) * 4) else 0 + try { + WasmKernels.writeFloats(qPtr, query, 0, outCount) + if (kvCount > 0) { + WasmKernels.writeFloats(kPtr, key, 0, kvCount) + WasmKernels.writeFloats(vPtr, value, 0, kvCount) + } + if (keyLengths != null) WasmKernels.writeInts(lPtr, keyLengths, 0, batch) + if (mask != null && maskCount > 0) WasmKernels.writeFloats(mPtr, mask, 0, maskCount) + WasmKernels.f32Sdpa( + qPtr, kPtr, vPtr, outPtr, batch, heads, kvHeads, seqQ, seqKV, headDim, + scale, causal, window, rightContext, lPtr, mPtr, md[0], md[1], md[2], md[3], + ) + WasmKernels.readFloats(outPtr, out, 0, outCount) + } finally { + if (mPtr != 0) WasmKernels.free(mPtr) + if (lPtr != 0) WasmKernels.free(lPtr) + WasmKernels.free(outPtr) + WasmKernels.free(vPtr) + WasmKernels.free(kPtr) + WasmKernels.free(qPtr) + } + } +} diff --git a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmKernelProvider.kt b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmKernelProvider.kt index 77848742e..23b1d505f 100644 --- a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmKernelProvider.kt +++ b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmKernelProvider.kt @@ -3,6 +3,10 @@ package sk.ainet.exec.wasm import sk.ainet.backend.api.kernel.Bf16MatmulKernel import sk.ainet.backend.api.kernel.Fp16MatmulKernel import sk.ainet.backend.api.kernel.Fp32MatmulKernel +import sk.ainet.backend.api.kernel.Fp32AttentionKernel +import sk.ainet.backend.api.kernel.Fp32MatmulWtKernel +import sk.ainet.backend.api.kernel.Fp32NormKernel +import sk.ainet.backend.api.kernel.Fp32SoftmaxKernel import sk.ainet.backend.api.kernel.KernelProvider import sk.ainet.backend.api.kernel.Q4KMatmulKernel import sk.ainet.backend.api.kernel.Q4_0MatmulKernel @@ -14,8 +18,12 @@ import sk.ainet.backend.api.kernel.Q8_0MatmulKernel /** * The sidecar as a [KernelProvider] (SKEEP-007 §T1): the dense FP32 GEMM and the feed-order packed - * kernels, so `KernelPacks.install()` and `chooseQuantizedMatmulHeap` serve from SIMD128 as well. - * Priority 50 — above the scalar provider (0), the same tier the JVM's Panama provider sits at. + * kernels, so `KernelPacks.install()` and `chooseQuantizedMatmulHeap` serve from SIMD128 as well, + * plus the fused row kernels (softmax, the normalisations, attention — [WasmFp32SoftmaxKernel], + * [WasmFp32NormKernel], [WasmFp32AttentionKernel]) the JVM FFM provider exposes under the same + * accessors. Priority 50 — above the scalar provider (0), the same tier the JVM's Panama provider + * sits at. [WasmCpuOps] does not go through these heap faces: it hands the sidecar resident + * operands directly. */ public object WasmKernelProvider : KernelProvider { override val name: String = "wasm-simd128" @@ -23,8 +31,12 @@ public object WasmKernelProvider : KernelProvider { override fun isAvailable(): Boolean = WasmKernels.isReady override fun matmulFp32(): Fp32MatmulKernel = WasmFp32MatmulKernel + override fun matmulFp32Wt(): Fp32MatmulWtKernel = WasmFp32MatmulWtKernel override fun matmulBf16(): Bf16MatmulKernel = WasmBf16MatmulKernel override fun matmulFp16(): Fp16MatmulKernel = WasmFp16MatmulKernel + override fun softmaxFp32(): Fp32SoftmaxKernel = WasmFp32SoftmaxKernel + override fun normFp32(): Fp32NormKernel = WasmFp32NormKernel + override fun attentionFp32(): Fp32AttentionKernel = WasmFp32AttentionKernel override fun matmulQ8_0(): Q8_0MatmulKernel = object : Q8_0MatmulKernel { override fun matmul(input: FloatArray, inputOffset: Int, weight: ByteArray, weightByteOffset: Int, inputDim: Int, outputDim: Int, output: FloatArray, outputOffset: Int) = @@ -165,3 +177,36 @@ public object WasmFp32MatmulKernel : Fp32MatmulKernel { } } } + +/** + * `out(m,n) = x(m,k) · w(n,k)ᵀ` in the sidecar (`skainet_fp32_matmul_wt`). A whole-array weight is a + * parameter and stays resident after its first call ([WasmKernels.denseWeightHandle]); a windowed + * weight is staged per call, as the activation always is. + */ +public object WasmFp32MatmulWtKernel : Fp32MatmulWtKernel { + override fun matmulWt( + x: FloatArray, xOffset: Int, + w: FloatArray, wOffset: Int, + out: FloatArray, outOffset: Int, + m: Int, n: Int, k: Int, + ) { + require(m >= 0 && n >= 0 && k >= 0) { "m, n, k must be non-negative; got m=$m n=$n k=$k" } + if (m == 0 || n == 0) return + val resident = wOffset == 0 && w.size == n * k + val inPtr = WasmKernels.alloc(maxOf(m * k, 1) * 4) + val wPtr = if (resident) WasmKernels.denseWeightHandle(w) else WasmKernels.alloc(maxOf(n * k, 1) * 4) + val outPtr = WasmKernels.alloc(m * n * 4) + try { + if (k > 0) { + WasmKernels.writeFloats(inPtr, x, xOffset, m * k) + if (!resident) WasmKernels.writeFloats(wPtr, w, wOffset, n * k) + } + WasmKernels.fp32MatmulWt(inPtr, wPtr, outPtr, m, n, k) + WasmKernels.readFloats(outPtr, out, outOffset, m * n) + } finally { + WasmKernels.free(outPtr) + if (!resident) WasmKernels.free(wPtr) + WasmKernels.free(inPtr) + } + } +} diff --git a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmKernels.kt b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmKernels.kt index 19b00a64c..54950d0d9 100644 --- a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmKernels.kt +++ b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmKernels.kt @@ -51,6 +51,7 @@ public object WasmKernels { loadEmbeddedSidecar(mjs, wasm).await() } modeName = if (mt) "simd128-mt" else "simd128-st" + jsResidentState(mod()) } /** Drop the sidecar and every cached weight; the next [initialize] starts over. Tests only. */ @@ -138,6 +139,70 @@ public object WasmKernels { } } + /** Copy [count] bytes from sidecar memory at [srcPtr] into [dst]. */ + public fun readBytes(srcPtr: Int, dst: ByteArray, dstOffset: Int, count: Int) { + val m = mod() + var done = 0 + while (done < count) { + val n = minOf(count - done, CHUNK_BYTES) + withScopedMemoryAllocator { allocator -> + val base = allocator.allocate(n) + jsCopyOut(m, wasmMemory, srcPtr + done, base.address.toInt(), n) + var p: Pointer = base + var i = 0 + while (i + 4 <= n) { + val v = p.loadInt() + val o = dstOffset + done + i + dst[o] = (v and 0xFF).toByte() + dst[o + 1] = ((v ushr 8) and 0xFF).toByte() + dst[o + 2] = ((v ushr 16) and 0xFF).toByte() + dst[o + 3] = ((v ushr 24) and 0xFF).toByte() + p = p + 4 + i += 4 + } + while (i < n) { + dst[dstOffset + done + i] = p.loadByte() + p = p + 1 + i++ + } + } + done += n + } + } + + /** Copy [bytes] bytes inside sidecar memory from [srcPtr] to [dstPtr]. */ + public fun copy(srcPtr: Int, dstPtr: Int, bytes: Int) { + if (bytes > 0) jsCopyWithin(mod(), srcPtr, dstPtr, bytes) + } + + /** Zero [bytes] bytes of sidecar memory at [ptr]. */ + public fun zero(ptr: Int, bytes: Int) { + if (bytes > 0) jsZero(mod(), ptr, bytes) + } + + // ── resident activations (Storage.WasmLinear) ───────────────────────────────────────────────── + + /** + * Sidecar bytes currently held by resident activations ([WasmLinearStorage]), scoped and + * ambient — the figure [WasmSimd.residentBudgetBytes] is checked against. + */ + public fun residentActivationBytes(): Long = if (isReady) jsResidentBytes(mod()).toLong() else 0L + + /** Allocate [bytes] for a resident activation; counted in [residentActivationBytes]. */ + internal fun allocResident(bytes: Int): Int = jsResidentAlloc(mod(), bytes) + + /** Free a resident activation's bytes and stop counting them. */ + internal fun freeResident(ptr: Int, bytes: Int): Unit = jsResidentFree(mod(), ptr, bytes) + + /** + * Register an ambient (GC-managed) resident allocation with the sidecar's `FinalizationRegistry`: + * the returned token is held by the Kotlin storage object, and when that object is collected the + * registry frees the sidecar bytes. Explicit `close()` must [untrackResident] first. + */ + internal fun trackResident(ptr: Int, bytes: Int): JsAny = jsResidentTrack(mod(), ptr, bytes) + + internal fun untrackResident(token: JsAny): Unit = jsResidentUntrack(mod(), token) + // ── resident weights ────────────────────────────────────────────────────────────────────────── private class Resident(val ptr: Int, val bytes: Int) @@ -213,6 +278,99 @@ public object WasmKernels { public fun narrowFloatMatmul(fnName: String, aPtr: Int, aStride: Int, bPtr: Int, bByteStride: Int, cPtr: Int, cStride: Int, m: Int, n: Int, k: Int): Unit = jsNarrowFloatMatmul(mod(), fnName, aPtr, aStride, bPtr, bByteStride, cPtr, cStride, m, n, k) + /** + * `skainet_ternary_f32_gemv`: one FP32 activation row against a sequentially packed BitNet b1.58 + * weight (`inputDim / 4` bytes per output row), per-tensor scale not applied. + */ + public fun ternaryF32Gemv(inPtr: Int, weightPtr: Int, inputDim: Int, outputDim: Int, outPtr: Int): Unit = + jsTernaryF32Gemv(mod(), inPtr, weightPtr, inputDim, outputDim, outPtr) + + /** + * `skainet_ternary_lmhead_stage1`: four planes at [planesPtr] (each [planeStrideBytes] apart) with + * the fused weights `{1, ⅓, ⅑, 1/27}`, times the FP16 row scales at [rowScalePtr]. + */ + public fun ternaryLmheadStage1(inPtr: Int, planesPtr: Int, planeStrideBytes: Int, rowScalePtr: Int, inputDim: Int, outputDim: Int, outPtr: Int): Unit = + jsTernaryLmheadStage1(mod(), inPtr, planesPtr, planeStrideBytes, rowScalePtr, inputDim, outputDim, outPtr) + + /** `skainet_bitnet_gemv_tq2_0`: int8 activation codes with one absmax scale against TQ2_0 blocks. */ + public fun bitnetGemvTq2_0(activationPtr: Int, activationScale: Float, weightPtr: Int, inputDim: Int, outputDim: Int, outPtr: Int): Unit = + jsBitnetGemvTq2_0(mod(), activationPtr, activationScale, weightPtr, inputDim, outputDim, outPtr) + + /** Operand index modes of [f32Binary] (`skainet_f32_binary`). */ + public const val F32_FULL: Int = 0 + public const val F32_SCALAR: Int = 1 + public const val F32_BIAS: Int = 2 + + /** Operators of [f32Binary]. */ + public const val F32_ADD: Int = 0 + public const val F32_SUB: Int = 1 + public const val F32_MUL: Int = 2 + public const val F32_DIV: Int = 3 + + /** Functions of [f32Unary] (`skainet_f32_unary`). */ + public const val F32_RELU: Int = 0 + public const val F32_SIGMOID: Int = 1 + public const val F32_SILU: Int = 2 + public const val F32_GELU_TANH: Int = 3 + public const val F32_GELU_EXACT: Int = 4 + public const val F32_ERF: Int = 5 + public const val F32_TANH: Int = 6 + + /** `skainet_f32_binary`: `out[i] = a[ia] op b[ib]` over [n] outputs, each side indexed by its mode (full, scalar, last-dim bias of [inner]). */ + public fun f32Binary(op: Int, aPtr: Int, aMode: Int, bPtr: Int, bMode: Int, outPtr: Int, n: Int, inner: Int): Unit = + jsF32Binary(mod(), op, aPtr, aMode, bPtr, bMode, outPtr, n, inner) + + /** `skainet_f32_unary`: `out[i] = f(in[i])`. */ + public fun f32Unary(op: Int, inPtr: Int, outPtr: Int, n: Int): Unit = jsF32Unary(mod(), op, inPtr, outPtr, n) + + /** `skainet_f32_reduce_all`: the sum (or mean) of [n] floats into the single float at [outPtr]. */ + public fun f32ReduceAll(inPtr: Int, n: Int, mean: Boolean, outPtr: Int): Unit = + jsF32ReduceAll(mod(), inPtr, n, if (mean) 1 else 0, outPtr) + + /** `skainet_f32_reduce_axis`: sum (or mean) over the middle of an `(outer, red, inner)` decomposition. */ + public fun f32ReduceAxis(inPtr: Int, outer: Int, red: Int, inner: Int, mean: Boolean, outPtr: Int): Unit = + jsF32ReduceAxis(mod(), inPtr, outer, red, inner, if (mean) 1 else 0, outPtr) + + /** `skainet_f32_softmax_rows`: row-wise softmax (or log-softmax when [log]) over [rows] rows of [cols]. */ + public fun f32SoftmaxRows(inPtr: Int, outPtr: Int, rows: Int, cols: Int, log: Boolean): Unit = + jsF32SoftmaxRows(mod(), inPtr, outPtr, rows, cols, if (log) 1 else 0) + + /** `skainet_f32_layer_norm` over [rows] rows of [cols]; [gammaPtr] / [betaPtr] `0` skip the affine. */ + public fun f32LayerNorm(inPtr: Int, gammaPtr: Int, betaPtr: Int, outPtr: Int, rows: Int, cols: Int, eps: Float): Unit = + jsF32LayerNorm(mod(), inPtr, gammaPtr, betaPtr, outPtr, rows, cols, eps) + + /** `skainet_f32_rms_norm` over [rows] rows of [cols]; [gammaPtr] `0` skips the scale. */ + public fun f32RmsNorm(inPtr: Int, gammaPtr: Int, outPtr: Int, rows: Int, cols: Int, eps: Float): Unit = + jsF32RmsNorm(mod(), inPtr, gammaPtr, outPtr, rows, cols, eps) + + /** + * `skainet_f32_sdpa`: fused scaled dot-product attention with the ops layer's structural masks + * (causal, sliding window with right context, per-batch key lengths at [lengthsPtr] or `0`) and + * an optional dense additive mask at [maskPtr] (`0` for none) broadcast over the four mask dims. + */ + public fun f32Sdpa( + qPtr: Int, kPtr: Int, vPtr: Int, outPtr: Int, + batch: Int, heads: Int, kvHeads: Int, seqQ: Int, seqKV: Int, headDim: Int, + scale: Float, causal: Boolean, window: Int, rightContext: Int, + lengthsPtr: Int, maskPtr: Int, maskB: Int, maskH: Int, maskQ: Int, maskK: Int, + ): Unit = jsF32Sdpa( + mod(), qPtr, kPtr, vPtr, outPtr, batch, heads, kvHeads, seqQ, seqKV, headDim, + scale, if (causal) 1 else 0, window, rightContext, lengthsPtr, maskPtr, maskB, maskH, maskQ, maskK, + ) + + /** Write [count] ints of [src] from [srcOffset] at [dstPtr], little-endian (the sidecar's int32). */ + public fun writeInts(dstPtr: Int, src: IntArray, srcOffset: Int, count: Int) { + val bytes = ByteArray(count * 4) + for (i in 0 until count) { + val v = src[srcOffset + i] + bytes[i * 4] = v.toByte() + bytes[i * 4 + 1] = (v shr 8).toByte() + bytes[i * 4 + 2] = (v shr 16).toByte() + bytes[i * 4 + 3] = (v shr 24).toByte() + } + writeBytes(dstPtr, bytes, 0, count * 4) + } + // ── diagnostics ─────────────────────────────────────────────────────────────────────────────── /** @@ -227,10 +385,11 @@ public object WasmKernels { val crossOriginIsolated: Boolean, val hardwareConcurrency: Int, val residentWeightBytes: Long, + val residentActivationBytes: Long = 0L, ) { override fun toString(): String = "mode=$mode simd128=$simd128 threads=$threads crossOriginIsolated=$crossOriginIsolated " + - "cores=$hardwareConcurrency residentWeightBytes=$residentWeightBytes" + "cores=$hardwareConcurrency residentWeightBytes=$residentWeightBytes residentActivationBytes=$residentActivationBytes" } /** Current sidecar capabilities; `mode == "none"` before [initialize]. */ @@ -241,6 +400,7 @@ public object WasmKernels { crossOriginIsolated = jsCrossOriginIsolated(), hardwareConcurrency = jsHardwareConcurrency(), residentWeightBytes = if (isReady) residentWeightBytes() else 0L, + residentActivationBytes = residentActivationBytes(), ) } @@ -301,6 +461,112 @@ private fun jsFp32MatmulWt(mod: JsAny, inPtr: Int, wPtr: Int, outPtr: Int, m: In private fun jsNarrowFloatMatmul(mod: JsAny, fnName: String, aPtr: Int, aStride: Int, bPtr: Int, bByteStride: Int, cPtr: Int, cStride: Int, m: Int, n: Int, k: Int): Unit = js("mod[fnName](aPtr, 0, aStride, bPtr, 0, bByteStride, cPtr, 0, cStride, m, n, k)") +private fun jsTernaryF32Gemv(mod: JsAny, inPtr: Int, weightPtr: Int, inputDim: Int, outputDim: Int, outPtr: Int): Unit = + js("mod._skainet_ternary_f32_gemv(inPtr, 0, weightPtr, 0, inputDim, outputDim, outPtr, 0)") + +private fun jsTernaryLmheadStage1(mod: JsAny, inPtr: Int, planesPtr: Int, planeStrideBytes: Int, rowScalePtr: Int, inputDim: Int, outputDim: Int, outPtr: Int): Unit = + js("mod._skainet_ternary_lmhead_stage1(inPtr, 0, planesPtr, 0, planeStrideBytes, rowScalePtr, 0, inputDim, outputDim, outPtr, 0)") + +private fun jsBitnetGemvTq2_0(mod: JsAny, activationPtr: Int, activationScale: Float, weightPtr: Int, inputDim: Int, outputDim: Int, outPtr: Int): Unit = + js("mod._skainet_bitnet_gemv_tq2_0(activationPtr, 0, activationScale, weightPtr, 0, inputDim, outputDim, outPtr, 0)") + +private fun jsCopyWithin(mod: JsAny, srcPtr: Int, dstPtr: Int, bytes: Int): Unit = + js("new Uint8Array(mod.wasmMemory.buffer).copyWithin(dstPtr, srcPtr, srcPtr + bytes)") + +private fun jsZero(mod: JsAny, ptr: Int, bytes: Int): Unit = + js("new Uint8Array(mod.wasmMemory.buffer, ptr, bytes).fill(0)") + +// Resident-activation accounting lives on the module object: the byte count and, where the runtime +// has one, a FinalizationRegistry that frees an ambient allocation when its Kotlin storage object has +// been collected (Kotlin/Wasm objects are traced by the same GC as the token they hold). +private fun jsResidentState(mod: JsAny): JsAny = js( + """ + (function() { + if (!mod.__skainetResident) { + var st = { bytes: 0, registry: null }; + if (typeof FinalizationRegistry !== 'undefined') { + st.registry = new FinalizationRegistry(function (h) { mod._free(h.ptr); st.bytes -= h.bytes; }); + } + mod.__skainetResident = st; + } + return mod.__skainetResident; + })() + """, +) + +private fun jsResidentBytes(mod: JsAny): Double = js("(mod.__skainetResident ? mod.__skainetResident.bytes : 0)") + +private fun jsResidentAlloc(mod: JsAny, bytes: Int): Int = js( + """ + (function() { + var st = mod.__skainetResident; + var p = mod._malloc(bytes); + st.bytes += bytes; + return p; + })() + """, +) + +private fun jsResidentFree(mod: JsAny, ptr: Int, bytes: Int): Unit = js( + """ + (function() { + var st = mod.__skainetResident; + mod._free(ptr); + st.bytes -= bytes; + })() + """, +) + +private fun jsResidentTrack(mod: JsAny, ptr: Int, bytes: Int): JsAny = js( + """ + (function() { + var st = mod.__skainetResident; + var token = {}; + if (st.registry) st.registry.register(token, { ptr: ptr, bytes: bytes }, token); + return token; + })() + """, +) + +private fun jsResidentUntrack(mod: JsAny, token: JsAny): Unit = js( + """ + (function() { + var st = mod.__skainetResident; + if (st && st.registry) st.registry.unregister(token); + })() + """, +) + +private fun jsF32Binary(mod: JsAny, op: Int, aPtr: Int, aMode: Int, bPtr: Int, bMode: Int, outPtr: Int, n: Int, inner: Int): Unit = + js("mod._skainet_f32_binary(op, aPtr, aMode, bPtr, bMode, outPtr, n, inner)") + +private fun jsF32Unary(mod: JsAny, op: Int, inPtr: Int, outPtr: Int, n: Int): Unit = + js("mod._skainet_f32_unary(op, inPtr, outPtr, n)") + +private fun jsF32ReduceAll(mod: JsAny, inPtr: Int, n: Int, mean: Int, outPtr: Int): Unit = + js("mod._skainet_f32_reduce_all(inPtr, n, mean, outPtr)") + +private fun jsF32ReduceAxis(mod: JsAny, inPtr: Int, outer: Int, red: Int, inner: Int, mean: Int, outPtr: Int): Unit = + js("mod._skainet_f32_reduce_axis(inPtr, outer, red, inner, mean, outPtr)") + +private fun jsF32SoftmaxRows(mod: JsAny, inPtr: Int, outPtr: Int, rows: Int, cols: Int, log: Int): Unit = + js("mod._skainet_f32_softmax_rows(inPtr, outPtr, rows, cols, log)") + +private fun jsF32LayerNorm(mod: JsAny, inPtr: Int, gammaPtr: Int, betaPtr: Int, outPtr: Int, rows: Int, cols: Int, eps: Float): Unit = + js("mod._skainet_f32_layer_norm(inPtr, gammaPtr, betaPtr, outPtr, rows, cols, eps)") + +private fun jsF32RmsNorm(mod: JsAny, inPtr: Int, gammaPtr: Int, outPtr: Int, rows: Int, cols: Int, eps: Float): Unit = + js("mod._skainet_f32_rms_norm(inPtr, gammaPtr, outPtr, rows, cols, eps)") + +private fun jsF32Sdpa( + mod: JsAny, qPtr: Int, kPtr: Int, vPtr: Int, outPtr: Int, + batch: Int, heads: Int, kvHeads: Int, seqQ: Int, seqKV: Int, headDim: Int, + scale: Float, causal: Int, window: Int, rightContext: Int, + lengthsPtr: Int, maskPtr: Int, maskB: Int, maskH: Int, maskQ: Int, maskK: Int, +): Unit = js( + "mod._skainet_f32_sdpa(qPtr, kPtr, vPtr, outPtr, batch, heads, kvHeads, seqQ, seqKV, headDim, scale, causal, window, rightContext, lengthsPtr, maskPtr, maskB, maskH, maskQ, maskK)" +) + private fun jsCrossOriginIsolated(): Boolean = js("typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated === true") private fun jsHardwareConcurrency(): Int = js( diff --git a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmLinearStorage.kt b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmLinearStorage.kt new file mode 100644 index 000000000..648109758 --- /dev/null +++ b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmLinearStorage.kt @@ -0,0 +1,191 @@ +@file:OptIn(kotlin.js.ExperimentalWasmJsInterop::class) + +package sk.ainet.exec.wasm + +import sk.ainet.lang.memory.OffHeapAllocator +import sk.ainet.lang.memory.Owner +import sk.ainet.lang.memory.ScopeKind +import sk.ainet.lang.memory.Storage +import sk.ainet.lang.memory.StorageId +import sk.ainet.lang.memory.trace.NoopTraceSink +import sk.ainet.lang.memory.trace.TraceEvent +import sk.ainet.lang.memory.trace.TraceSink +import sk.ainet.lang.tensor.Shape +import sk.ainet.lang.tensor.TensorId +import sk.ainet.lang.tensor.data.FloatArrayTensorData +import sk.ainet.lang.types.DType + +/** + * The wasm tier's [Storage.OffHeap]: a region of the SIMD128 sidecar's linear memory + * (SKEEP-007 resident activations). Bytes here are read and written by the sidecar's kernels in + * place; the Kotlin side sees them through [copyInto]/[copyFrom] (bulk copies across the module + * boundary), never element by element. This is the binding `PlatformStorage.offHeapAllocator` + * receives from [WasmSimd.install], so a `ForwardScope` or `ModelScope` asking for + * `MemoryDomain.HOST_OFFHEAP` gets sidecar memory that its `reset()`/`close()` frees. + * + * Lifetime. A scoped storage ([ScopeKind.FORWARD] / [ScopeKind.MODEL]) is freed exactly once, by its + * scope through [close]. An ambient storage ([ScopeKind.AMBIENT], the default for op outputs in a + * notebook-style forward pass) has no scope to free it, so it is registered with a + * `FinalizationRegistry` in the sidecar glue: when this Kotlin object is collected the registry + * frees the sidecar bytes — the same GC-managed lifetime a heap `FloatArray` has, one hop later. + * Finalization is not prompt, which is why the ops layer also keeps resident activations under + * [WasmSimd.residentBudgetBytes] and falls back to heap outputs above it. + */ +public class WasmLinearStorage private constructor( + override val id: StorageId, + private val base: Int, + override val sizeBytes: Long, + override val owner: Owner, + override val debugOrigin: TensorId?, + override val sink: TraceSink, + private val token: JsAny?, + private val mutable: Boolean, +) : Storage.OffHeap() { + + override val isMutable: Boolean get() = (owner as? Owner.Alias)?.parent?.isMutable ?: mutable + + /** The byte address in sidecar memory — kernels take it once per call. Throws when closed. */ + public fun pointer(): Int { + checkAlive() + return base + } + + override fun slice(offsetBytes: Long, lengthBytes: Long): WasmLinearStorage { + checkAlive() + require(offsetBytes >= 0 && lengthBytes >= 0 && offsetBytes + lengthBytes <= sizeBytes) { + "slice [$offsetBytes, ${offsetBytes + lengthBytes}) outside $sizeBytes bytes" + } + return WasmLinearStorage(StorageId.next(), base + offsetBytes.toInt(), lengthBytes, Owner.Alias(this), debugOrigin, sink, null, mutable) + } + + override fun copyInto(dest: ByteArray, destOffset: Int, offset: Long, length: Int) { + checkAlive() + require(offset >= 0 && length >= 0 && offset + length <= sizeBytes) { "copyInto [$offset, ${offset + length}) outside $sizeBytes bytes" } + WasmKernels.readBytes(base + offset.toInt(), dest, destOffset, length) + } + + override fun copyFrom(src: ByteArray, srcOffset: Int, offset: Long, length: Int) { + checkAlive() + require(isMutable) { "storage $id is not mutable" } + require(offset >= 0 && length >= 0 && offset + length <= sizeBytes) { "copyFrom [$offset, ${offset + length}) outside $sizeBytes bytes" } + WasmKernels.writeBytes(base + offset.toInt(), src, srcOffset, length) + } + + override fun onClose() { + if (owner !is Owner.Owned || !WasmKernels.isReady) return + token?.let { WasmKernels.untrackResident(it) } + WasmKernels.freeResident(base, sizeBytes.toInt()) + } + + override fun toString(): String = + "WasmLinear($id, $sizeBytes B @$base, $owner${debugOrigin?.let { ", $it" } ?: ""}${if (isAlive) "" else ", closed"})" + + /** The hook [WasmSimd.install] hands to `PlatformStorage.offHeapAllocator`. */ + public object Allocator : OffHeapAllocator { + override fun allocate(bytes: Long, scope: ScopeKind, origin: TensorId?, sink: TraceSink): Storage = + WasmLinearStorage.allocate(bytes, scope, origin, sink) + } + + public companion object { + /** + * Allocate [bytes] bytes of sidecar memory, zeroed unless [zero] is `false` (an op output that + * is fully written next), owned by a scope of kind [scope]. Requires the sidecar to be up. + */ + public fun allocate( + bytes: Long, + scope: ScopeKind = ScopeKind.AMBIENT, + origin: TensorId? = null, + sink: TraceSink = NoopTraceSink, + zero: Boolean = true, + ): WasmLinearStorage { + require(bytes in 0..Int.MAX_VALUE.toLong()) { "sidecar storage is limited to 2 GB per allocation, requested $bytes bytes" } + check(WasmKernels.isReady) { "WasmSimd.install() has not completed: no sidecar memory to allocate from" } + val size = bytes.toInt() + val ptr = WasmKernels.allocResident(size) + if (zero) WasmKernels.zero(ptr, size) + val token = if (scope == ScopeKind.AMBIENT) WasmKernels.trackResident(ptr, size) else null + val s = WasmLinearStorage(StorageId.next(), ptr, bytes, Owner.Owned(scope), origin, sink, token, true) + if (sink.isEnabled) sink.emit(TraceEvent.Allocation(s.id.value, scope, bytes, origin)) + return s + } + } +} + +/** + * Dense FP32 tensor data whose bytes live in the sidecar ([WasmLinearStorage]) — what the ops of + * [WasmCpuOps] hand out, so a chain `matmul → gelu → multiply → add` never copies an activation back + * to the Kotlin heap between kernels. + * + * It is a [FloatArrayTensorData] so every common fast path keeps working on it: the first access to + * [buffer] (or to an element, or [copyToFloatArray], or [view]) downloads the whole tensor once into + * a heap mirror that is kept for the tensor's life. A sidecar kernel never triggers that — it reads + * [pointer] — and the download is the same single bulk copy the matmul output paid before resident + * activations existed. Ops do not mutate their inputs, so the mirror and the sidecar copy agree; + * [set] writes both. + */ +public class WasmLinearTensorData( + initialShape: Shape, + public val storage: WasmLinearStorage, +) : FloatArrayTensorData { + + override val shape: Shape = Shape(initialShape.dimensions.copyOf()) + private val strides: IntArray = IntArray(shape.rank).also { st -> + var stride = 1 + for (i in shape.rank - 1 downTo 0) { + st[i] = stride + stride *= shape.dimensions[i] + } + } + private var mirror: FloatArray? = null + + init { + require(storage.sizeBytes >= shape.volume.toLong() * 4) { + "storage holds ${storage.sizeBytes} bytes, shape $shape needs ${shape.volume.toLong() * 4}" + } + } + + /** `true` while the sidecar bytes are alive: kernels use [pointer] and nothing is downloaded. */ + public val isResident: Boolean get() = storage.isAlive + + /** The sidecar address of element 0. Throws when the storage is closed. */ + public fun pointer(): Int = storage.pointer() + + /** `true` once a heap mirror has been downloaded (diagnostics and tests). */ + public val isMirrored: Boolean get() = mirror != null + + override val buffer: FloatArray + get() { + storage.checkAlive() + mirror?.let { return it } + val n = shape.volume + val arr = FloatArray(n) + if (n > 0) WasmKernels.readFloats(storage.pointer(), arr, 0, n) + mirror = arr + return arr + } + + private fun flatIndex(indices: IntArray): Int { + require(indices.size == shape.dimensions.size) { + "Number of indices (${indices.size}) must match tensor dimensions (${shape.dimensions.size})" + } + var flat = 0 + for (i in indices.indices) { + val idx = indices[i] + require(idx >= 0 && idx < shape.dimensions[i]) { "Index $idx out of bounds for dimension $i with size ${shape.dimensions[i]}" } + flat += idx * strides[i] + } + return flat + } + + override fun get(vararg indices: Int): Float = buffer[flatIndex(indices)] + + override fun set(vararg indices: Int, value: Float) { + storage.checkAlive() + require(storage.isMutable) { "storage ${storage.id} is not mutable" } + val flat = flatIndex(indices) + mirror?.let { it[flat] = value } + WasmKernels.writeFloats(storage.pointer() + flat * 4, floatArrayOf(value), 0, 1) + } + + override fun copyToFloatArray(): FloatArray = buffer.copyOf() +} diff --git a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmSimd.kt b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmSimd.kt index 6038f5afd..01f7cdee4 100644 --- a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmSimd.kt +++ b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmSimd.kt @@ -3,6 +3,7 @@ package sk.ainet.exec.wasm import sk.ainet.backend.api.kernel.KernelDispatch import sk.ainet.backend.api.kernel.KernelRegistry import sk.ainet.exec.tensor.ops.WasmCpuOpsOverride +import sk.ainet.lang.memory.PlatformStorage /** * Entry point of the browser kernel pack (SKEEP-007 §T1). @@ -13,13 +14,24 @@ import sk.ainet.exec.tensor.ops.WasmCpuOpsOverride * ``` * * [install] loads the SIMD128 sidecar (pthreads build when the page is cross-origin isolated), - * registers [WasmKernelProvider] and [WasmSimdKernelPack], and makes every context built afterwards - * use [WasmCpuOps]. Without it — or before it completes — the engine runs the common scalar lane, - * so an application may start rendering before the sidecar is up and simply get faster later. + * registers [WasmKernelProvider], [WasmSimdKernelPack] and [WasmTernaryKernelPack], binds + * `MemoryDomain.HOST_OFFHEAP` to the sidecar's linear memory ([WasmLinearStorage], so activations stay + * resident between kernels) and makes every context built afterwards use [WasmCpuOps]. Without it — + * or before it completes — the engine runs the common scalar lane, so an application may start + * rendering before the sidecar is up and simply get faster later. */ public object WasmSimd { public val isInstalled: Boolean get() = WasmKernels.isReady && WasmCpuOpsOverride.factory != null + /** + * How many sidecar bytes resident activations may hold before [WasmCpuOps] returns heap outputs + * instead (the kernels still run in the sidecar; only the result is copied back). Ambient + * activations are freed when their tensors are collected, which is not prompt, so this is the + * bound on what a long notebook-style session can pin in linear memory. 768 MB by default; the + * wasm32 sidecar has 4 GB in total, shared with the resident weights. + */ + public var residentBudgetBytes: Long = 768L * 1024 * 1024 + public suspend fun install(url: String? = null, threads: Boolean? = null) { WasmKernels.initialize(url, threads) KernelRegistry.register(WasmKernelProvider) @@ -27,6 +39,8 @@ public object WasmSimd { // the auto-install on the next dispatch picks the sidecar provider and this pack's kernels. KernelDispatch.clearForTesting() WasmSimdKernelPack.install() + WasmTernaryKernelPack.install() + PlatformStorage.offHeapAllocator = WasmLinearStorage.Allocator WasmCpuOpsOverride.factory = { factory, schedule -> WasmCpuOps(factory, schedule) } // The same one-line banner the JVM factory prints, so a console shows which tier runs. println("[SKaiNET] Using SIMD128 sidecar kernels (${WasmKernels.features()})") @@ -38,6 +52,7 @@ public object WasmSimd { /** Back to the scalar lane; frees every resident weight. Tests and model switching. */ public fun uninstall() { WasmCpuOpsOverride.factory = null + PlatformStorage.offHeapAllocator = null KernelDispatch.clearForTesting() WasmKernels.clearForTesting() } diff --git a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmTernaryKernelPack.kt b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmTernaryKernelPack.kt new file mode 100644 index 000000000..2e348c795 --- /dev/null +++ b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsMain/kotlin/sk/ainet/exec/wasm/WasmTernaryKernelPack.kt @@ -0,0 +1,124 @@ +package sk.ainet.exec.wasm + +import sk.ainet.backend.api.kernel.BitNetGemvNative +import sk.ainet.backend.api.kernel.TernaryF32GemvNative +import sk.ainet.backend.api.kernel.TernaryF32KernelPack +import sk.ainet.backend.api.kernel.TernaryKernelPacks +import sk.ainet.backend.api.kernel.TernaryLmheadNative +import sk.ainet.backend.api.kernel.TernaryPlanesKernelPack +import sk.ainet.backend.api.kernel.ViewKernelPack + +/** + * The ternary kernels of the browser pack — the wasm sibling of the JVM `FfmTernaryKernelPackFactory` + * and the Android JNI ternary pack. Three native faces over the sidecar's exports: + * + * - [WasmTernaryF32Gemv] — `skainet_ternary_f32_gemv`, exact FP32 activations × `BITNET_B1_58`; + * - [WasmTernaryLmhead] — `skainet_ternary_lmhead_stage1`, the fused four-plane `BITNET_PLANES` lm_head; + * - [WasmBitNetGemv] — `skainet_bitnet_gemv_tq2_0`, int8 activations × `TQ2_0`. + * + * Each hands its weight to the sidecar **once** ([WasmKernels.weightHandle]) and stages the + * activation and output per call, like every other kernel in this module. Without this pack a + * BitNet weight in a browser went to the decoding reference kernel — correct, and hours per + * projection at model size. Installed by [WasmSimd.install] after the sidecar is up; there is no + * `ServiceLoader` on wasm. + */ +public object WasmTernaryKernelPack : ViewKernelPack { + override val name: String = "wasm-simd128-ternary" + + override fun install() { + if (!WasmKernels.isReady) return + TernaryF32KernelPack.install(WasmTernaryF32Gemv) + TernaryPlanesKernelPack.install(WasmTernaryLmhead) + TernaryKernelPacks.install(WasmBitNetGemv) + } +} + +/** `skainet_ternary_f32_gemv` in the sidecar: the sequential b1.58 payload, scale applied by the pack. */ +public object WasmTernaryF32Gemv : TernaryF32GemvNative { + override val name: String get() = "wasm-simd128" + + override fun gemvPacked( + activation: FloatArray, activationOffset: Int, + weight: ByteArray, weightByteOffset: Int, + inputDim: Int, outputDim: Int, + out: FloatArray, outOffset: Int, + ) { + require(inputDim % 4 == 0) { "WasmTernaryF32Gemv: inputDim must be a multiple of 4; got $inputDim" } + if (outputDim == 0) return + if (inputDim == 0) { + out.fill(0f, outOffset, outOffset + outputDim) + return + } + val weightPtr = WasmKernels.weightHandle(weight, 0, weight.size) + weightByteOffset + val inPtr = WasmKernels.alloc(inputDim * 4) + val outPtr = WasmKernels.alloc(outputDim * 4) + try { + WasmKernels.writeFloats(inPtr, activation, activationOffset, inputDim) + WasmKernels.ternaryF32Gemv(inPtr, weightPtr, inputDim, outputDim, outPtr) + WasmKernels.readFloats(outPtr, out, outOffset, outputDim) + } finally { + WasmKernels.free(outPtr) + WasmKernels.free(inPtr) + } + } +} + +/** `skainet_ternary_lmhead_stage1` in the sidecar: planes and row scales read from the resident weight. */ +public object WasmTernaryLmhead : TernaryLmheadNative { + override val name: String get() = "wasm-simd128" + + override fun lmheadStage1( + activation: FloatArray, activationOffset: Int, + weight: ByteArray, planesByteOffset: Int, + planeStrideBytes: Int, rowScaleByteOffset: Int, + inputDim: Int, outputDim: Int, + out: FloatArray, outOffset: Int, + ) { + require(inputDim % 4 == 0) { "WasmTernaryLmhead: inputDim must be a multiple of 4; got $inputDim" } + require(rowScaleByteOffset % 2 == 0) { "WasmTernaryLmhead: rowScaleByteOffset must be 2-byte aligned; got $rowScaleByteOffset" } + if (outputDim == 0) return + val weightPtr = WasmKernels.weightHandle(weight, 0, weight.size) + val inPtr = WasmKernels.alloc(maxOf(inputDim, 1) * 4) + val outPtr = WasmKernels.alloc(outputDim * 4) + try { + if (inputDim > 0) WasmKernels.writeFloats(inPtr, activation, activationOffset, inputDim) + WasmKernels.ternaryLmheadStage1( + inPtr, weightPtr + planesByteOffset, planeStrideBytes, weightPtr + rowScaleByteOffset, + inputDim, outputDim, outPtr, + ) + WasmKernels.readFloats(outPtr, out, outOffset, outputDim) + } finally { + WasmKernels.free(outPtr) + WasmKernels.free(inPtr) + } + } +} + +/** `skainet_bitnet_gemv_tq2_0` in the sidecar: one token's int8 codes against resident TQ2_0 blocks. */ +public object WasmBitNetGemv : BitNetGemvNative { + override val name: String get() = "wasm-simd128" + + override fun gemvTq2_0( + activation: ByteArray, activationOffset: Int, activationScale: Float, + weight: ByteArray, weightByteOffset: Int, + inputDim: Int, outputDim: Int, + out: FloatArray, outOffset: Int, + ) { + if (outputDim == 0) return + if (inputDim == 0) { + out.fill(0f, outOffset, outOffset + outputDim) + return + } + val weightPtr = WasmKernels.weightHandle(weight, 0, weight.size) + weightByteOffset + val actPtr = WasmKernels.alloc(inputDim) + val outPtr = WasmKernels.alloc(outputDim * 4) + try { + WasmKernels.writeBytes(actPtr, activation, activationOffset, inputDim) + WasmKernels.bitnetGemvTq2_0(actPtr, activationScale, weightPtr, inputDim, outputDim, outPtr) + WasmKernels.readFloats(outPtr, out, outOffset, outputDim) + } finally { + WasmKernels.free(outPtr) + WasmKernels.free(actPtr) + } + } +} diff --git a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsTest/kotlin/sk/ainet/exec/wasm/WasmSimdParityTest.kt b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsTest/kotlin/sk/ainet/exec/wasm/WasmSimdParityTest.kt index 31c96f6eb..755a14733 100644 --- a/skainet-backends/skainet-backend-wasm-simd/src/wasmJsTest/kotlin/sk/ainet/exec/wasm/WasmSimdParityTest.kt +++ b/skainet-backends/skainet-backend-wasm-simd/src/wasmJsTest/kotlin/sk/ainet/exec/wasm/WasmSimdParityTest.kt @@ -1,11 +1,32 @@ package sk.ainet.exec.wasm import kotlinx.coroutines.test.runTest +import sk.ainet.backend.api.kernel.BitNetGemvKernel import sk.ainet.backend.api.kernel.KernelDispatch import sk.ainet.backend.api.kernel.KernelRegistry import sk.ainet.backend.api.kernel.KernelStrictness import sk.ainet.backend.api.kernel.NoSuchKernelException +import sk.ainet.backend.api.kernel.TernaryF32GemvKernel +import sk.ainet.backend.api.kernel.TernaryPlanesMatmulKernel import sk.ainet.context.DirectCpuExecutionContext +import sk.ainet.context.ScopedExecutionContext +import sk.ainet.lang.memory.ForwardScope +import sk.ainet.lang.memory.PlatformStorage +import sk.ainet.lang.memory.StorageClosedException +import sk.ainet.lang.memory.plan.StorageCapabilities +import sk.ainet.lang.tensor.data.DenseFloatArrayTensorData +import sk.ainet.lang.tensor.storage.MemoryDomain +import sk.ainet.lang.memory.I8Absmax +import sk.ainet.lang.memory.Scope +import sk.ainet.lang.memory.Storage +import sk.ainet.lang.memory.TensorView +import sk.ainet.lang.memory.TernaryBlockDecoder +import sk.ainet.lang.memory.TernaryCodec +import sk.ainet.lang.memory.trace.RecordingTraceSink +import sk.ainet.lang.memory.trace.TraceEvent +import sk.ainet.lang.tensor.data.BitNetB158TensorData +import sk.ainet.lang.tensor.data.BitNetPlanesTensorData +import sk.ainet.lang.tensor.storage.TensorEncoding import sk.ainet.exec.kernel.ScalarBf16MatmulKernel import sk.ainet.exec.kernel.ScalarFp16MatmulKernel import sk.ainet.exec.kernel.ScalarQ4_KMatmulKernel @@ -23,7 +44,12 @@ import sk.ainet.lang.tensor.data.Q8_0BlockTensorData import sk.ainet.lang.tensor.data.Q8_0Quantizer import sk.ainet.lang.tensor.data.TensorData import sk.ainet.lang.tensor.matmulWeightTransposed +import sk.ainet.lang.types.DType import sk.ainet.lang.types.FP32 +import sk.ainet.lang.types.Int32 +import sk.ainet.lang.nn.normalization.LayerNormalization +import sk.ainet.lang.tensor.ops.AttentionMask +import sk.ainet.lang.tensor.ops.FusedNormOps import kotlin.math.abs import kotlin.math.sqrt import kotlin.random.Random @@ -36,8 +62,8 @@ import kotlin.test.assertTrue /** * SKEEP-007 §T1: the SIMD128 sidecar agrees with the common scalar lane on every path it takes - * over — row-major packed matmuls (Q8_0, Q4_0, Q4_K via the pack), dense `x · Wᵀ`, and the dense - * GEMM — and the wiring makes a freshly built context use [WasmCpuOps]. + * over — row-major packed matmuls (Q8_0, Q4_0, Q4_K via the pack), dense `x · Wᵀ`, the dense GEMM + * and the three ternary kernels — and the wiring makes a freshly built context use [WasmCpuOps]. * * Runs on Node (`wasmJsNodeTest`): the sidecar is loaded from the module's own resources. Lane * sums differ from the sequential scalar accumulation in the last bits, so comparisons are @@ -70,6 +96,8 @@ class WasmSimdParityTest { WasmSimd.uninstall() } + private fun heapFloats(view: TensorView): FloatArray = checkNotNull((view.storage as Storage.Heap).floats) + @Suppress("UNCHECKED_CAST") private fun q8(ctx: DirectCpuExecutionContext, n: Int, k: Int, seed: Int): Tensor = ctx.fromData(Q8_0Quantizer.quantize(pseudo(n * k, seed, 2f), Shape(n, k)) as TensorData, FP32::class) @@ -282,5 +310,312 @@ class WasmSimdParityTest { val direct = FloatArray(m * n) WasmFp32MatmulKernel.matmul(x, 0, k, b, 0, n, direct, 0, n, m, n, k) assertClose(refMm, direct, "provider gemm") + // The direct `x · Wᵀ` provider kernel: whole-array weight (resident) and a windowed one (staged). + val provider = checkNotNull(KernelRegistry.bestAvailable()) + val wt = checkNotNull(provider.matmulFp32Wt()) + assertTrue(provider.supports("matmul_wt", listOf("Float32", "Float32"))) + val directWt = FloatArray(m * n) + wt.matmulWt(x, 0, w, 0, directWt, 0, m, n, k) + assertClose(refWt, directWt, "provider x·Wᵀ") + val window = FloatArray(1 + n * k + 3).also { w.copyInto(it, 1) } + val windowed = FloatArray(2 + m * n) + wt.matmulWt(x, 0, window, 1, windowed, 2, m, n, k) + assertClose(refWt, windowed.copyOfRange(2, 2 + m * n), "windowed x·Wᵀ") + } + + /** + * The ternary kernels reach the sidecar through the same `KernelDispatch` keys the JVM FFM and + * Android JNI packs serve, and agree with the Kotlin reference kernels. Row counts above the row + * pool's threshold (512) so the mt sidecar splits the rows over its workers. + */ + @Test + fun `ternary weights run on the sidecar and match the reference kernels`() = runTest { + WasmSimd.install() + val rng = Random(31) + + // BITNET_B1_58, exact FP32 activations, two rows so the pack's row loop runs. + run { + val k = 256; val n = 600; val rows = 2 + val values = FloatArray(n * k) { (rng.nextInt(3) - 1) * 0.5f } + val w = BitNetB158TensorData.fromFloats(Shape(n, k), values).packedView + val a = TensorView.dense(Storage.Heap.wrap(FloatArray(rows * k) { rng.nextFloat() - 0.5f }), Shape(rows, k), FP32) + val out = TensorView.dense(Storage.Heap.floats(rows * n), Shape(rows, n), FP32) + val sink = RecordingTraceSink() + KernelDispatch.matmul(a, w, out, Scope.Ambient, sink) + assertEquals("ternary_f32_gemv/wasm-simd128", sink.eventsOf().single().kernel) + val ref = TensorView.dense(Storage.Heap.floats(rows * n), Shape(rows, n), FP32) + TernaryF32GemvKernel(TernaryF32GemvKernel.keyFor()).run(listOf(a, w), ref) + assertClose(heapFloats(ref), heapFloats(out), "ternary f32 gemv", rel = 1e-4f) + } + + // BITNET_PLANES: the fused four-plane lm_head, two calls per row, against the 8-plane reference. + run { + val k = 64; val n = 520 + val values = FloatArray(n * k) { (rng.nextFloat() - 0.5f) * 2f } + val w = BitNetPlanesTensorData.fromFloats(Shape(n, k), values).packedView + val a = TensorView.dense(Storage.Heap.wrap(FloatArray(k) { rng.nextFloat() - 0.5f }), Shape(1, k), FP32) + val out = TensorView.dense(Storage.Heap.floats(n), Shape(1, n), FP32) + val sink = RecordingTraceSink() + KernelDispatch.matmul(a, w, out, Scope.Ambient, sink) + assertEquals("ternary_planes_matmul/wasm-simd128", sink.eventsOf().single().kernel) + val ref = TensorView.dense(Storage.Heap.floats(n), Shape(1, n), FP32) + TernaryPlanesMatmulKernel(TernaryPlanesMatmulKernel.keyFor()).run(listOf(a, w), ref) + assertClose(heapFloats(ref), heapFloats(out), "ternary planes lm_head", rel = 1e-4f) + } + + // TQ2_0 with int8 activations: integer block sums, so the match is exact up to the scale products. + run { + val k = 768; val n = 600 + val values = FloatArray(n * k) { (rng.nextInt(3) - 1) * 0.5f } + val w = TensorView.packed( + Storage.Heap.wrap(TernaryCodec.encode(TensorEncoding.TQ2_0, values)), Shape(n, k), + TensorEncoding.TQ2_0, TernaryBlockDecoder(TensorEncoding.TQ2_0), + ) + val a = I8Absmax.requantize( + TensorView.dense(Storage.Heap.wrap(FloatArray(k) { rng.nextFloat() - 0.5f }), Shape(1, k), FP32), Scope.Ambient, + ) + val out = TensorView.dense(Storage.Heap.floats(n), Shape(1, n), FP32) + val sink = RecordingTraceSink() + KernelDispatch.matmul(a, w, out, Scope.Ambient, sink) + assertEquals("bitnet_gemv/wasm-simd128", sink.eventsOf().single().kernel) + val ref = TensorView.dense(Storage.Heap.floats(n), Shape(1, n), FP32) + BitNetGemvKernel(BitNetGemvKernel.keyFor(w.format)).run(listOf(a, w), ref) + assertClose(heapFloats(ref), heapFloats(out), "bitnet gemv tq2_0") + } + assertTrue(WasmKernels.residentWeightBytes() > 0, "the ternary weights stay resident") + } + + /** + * Resident activations: a matmul's output stays in the sidecar, the elementwise ops, activations + * and reductions that follow read it in place and leave their result there too, and every value + * agrees with the common scalar lane. Only the final read materialises a heap mirror. + */ + @Test + fun `an activation chain stays resident in the sidecar and matches the scalar lane`() = runTest { + val scalar = DirectCpuExecutionContext() + val m = 7; val n = 40; val k = 24 + val x = pseudo(m * k, 41) + val w = pseudo(n * k, 43) + val bias = pseudo(n, 47) + val xs = scalar.fromFloatArray(Shape(m, k), FP32::class, x) + val ws = scalar.fromFloatArray(Shape(n, k), FP32::class, w) + val bs = scalar.fromFloatArray(Shape(n), FP32::class, bias) + val two = scalar.fromFloatArray(Shape(1), FP32::class, floatArrayOf(2f)) + fun chain(ctx: DirectCpuExecutionContext, x: Tensor, w: Tensor, b: Tensor, c: Tensor, probe: (String, Tensor) -> Unit): Map { + val ops = ctx.ops + val out = LinkedHashMap() + fun keep(name: String, t: Tensor) { probe(name, t); out[name] = t.data.copyToFloatArray() } + val h = ops.matmulWeightTransposed(x, w); probe("h", h) + val g = ops.gelu(h); probe("gelu", g) + val ge = ops.gelu(h, approximate = false); keep("geluExact", ge) + val s = ops.multiply(g, h); probe("mul", s) + val o = ops.add(s, b); probe("addBias", o) + keep("biasAdd", ops.add(b, s)) + keep("subScalar", ops.subtract(o, c)) + keep("scalarSub", ops.subtract(c, o)) + keep("div", ops.divide(o, ops.add(ops.relu(o), c))) + keep("relu", ops.relu(o)) + keep("sigmoid", ops.sigmoid(o)) + keep("silu", ops.silu(o)) + keep("tanh", ops.tanh(o)) + keep("erf", ops.erf(o)) + keep("sumAll", ops.sum(o)) + keep("meanAll", ops.mean(o)) + keep("sumLast", ops.sum(o, -1)) + keep("meanFirst", ops.mean(o, 0)) + keep("out", o) + return out + } + val ref = chain(scalar, xs, ws, bs, two) { _, _ -> } + + WasmSimd.install() + val fast = DirectCpuExecutionContext() + val xf = fast.fromFloatArray(Shape(m, k), FP32::class, x) + val wf = fast.fromFloatArray(Shape(n, k), FP32::class, w) + val bf = fast.fromFloatArray(Shape(n), FP32::class, bias) + val cf = fast.fromFloatArray(Shape(1), FP32::class, floatArrayOf(2f)) + val before = WasmKernels.residentActivationBytes() + val got = chain(fast, xf, wf, bf, cf) { name, t -> + val d = t.data + assertIs>(d, "$name should be resident") + assertTrue(d.isResident, "$name alive") + if (name == "h" || name == "gelu" || name == "mul" || name == "addBias") assertTrue(!d.isMirrored, "$name was not downloaded") + } + assertTrue(WasmKernels.residentActivationBytes() > before, "resident bytes grew") + for ((name, values) in ref) { + assertClose(values, checkNotNull(got[name]) { name }, name) + } + // Shapes of the reductions follow the common lane. + assertEquals(m, got.getValue("sumLast").size) + assertEquals(n, got.getValue("meanFirst").size) + assertEquals(1, got.getValue("sumAll").size) + } + + @Suppress("UNCHECKED_CAST") + private fun lengthsTensor(ctx: DirectCpuExecutionContext, lengths: IntArray): Tensor = + ctx.fromIntArray(Shape(lengths.size), Int32::class, lengths) as Tensor + + @Test + fun `softmax, the normalisations and attention run fused in the sidecar and match the scalar lane`() = runTest { + val scalar = DirectCpuExecutionContext() + val rows = 16; val cols = 96 + val xArr = pseudo(rows * cols, 51, 3f) + val gArr = FloatArray(cols) { 0.5f + (it % 7) * 0.1f } + val bArr = FloatArray(cols) { ((it % 5) - 2) * 0.05f } + val batch = 2; val heads = 4; val kvHeads = 2; val seqQ = 5; val seqKV = 9; val headDim = 16 + val qArr = pseudo(batch * heads * seqQ * headDim, 53) + val kArr = pseudo(batch * kvHeads * seqKV * headDim, 55) + val vArr = pseudo(batch * kvHeads * seqKV * headDim, 57) + val lengths = intArrayOf(seqKV, 4) + val maskArr = pseudo(seqQ * seqKV, 59) + val wqArr = pseudo(cols * cols, 61) + + fun run(ctx: DirectCpuExecutionContext, probe: (String, Tensor) -> Unit): Map { + val ops = ctx.ops + val out = LinkedHashMap() + fun keep(name: String, t: Tensor) { probe(name, t); out[name] = t.data.copyToFloatArray() } + val x = ctx.fromFloatArray(Shape(rows, cols), FP32::class, xArr) + keep("softmax", ops.softmax(x, -1)) + keep("logSoftmax", ops.logSoftmax(x, -1)) + keep("softmaxDim0", ops.softmax(x, 0)) + val gamma = ctx.fromFloatArray(Shape(cols), FP32::class, gArr) + val beta = ctx.fromFloatArray(Shape(cols), FP32::class, bArr) + val ln = LayerNormalization(intArrayOf(cols), eps = 1e-5, elementwiseAffine = true, name = "ln", initGamma = gamma, initBeta = beta) + keep("layerNorm", ln.forward(x, ctx)) + val lnPlain = LayerNormalization(intArrayOf(cols), eps = 1e-5, elementwiseAffine = false, name = "lnPlain") + keep("layerNormPlain", lnPlain.forward(x, ctx)) + (ops as? FusedNormOps)?.rmsNorm(x, gamma, 1e-6f, 1)?.let { keep("rmsNorm", it) } + // On the sidecar the operands are made resident first (as a projection's output is), so + // attention reads them in place: the probe checks they were never downloaded. + val wasm = ops as? WasmCpuOps + fun resident(t: Tensor) = wasm?.toResident(t) ?: t + val q = resident(ctx.fromFloatArray(Shape(batch, heads, seqQ, headDim), FP32::class, qArr)) + val k = resident(ctx.fromFloatArray(Shape(batch, kvHeads, seqKV, headDim), FP32::class, kArr)) + val v = resident(ctx.fromFloatArray(Shape(batch, kvHeads, seqKV, headDim), FP32::class, vArr)) + keep("sdpa", ops.scaledDotProductAttention(q, k, v)) + probe("q", q); probe("k", k); probe("v", v) + keep("sdpaCausal", ops.scaledDotProductAttention(q, k, v, causal = true)) + keep("sdpaLengths", ops.scaledDotProductAttention(q, k, v, causal = true, keyLengths = lengthsTensor(ctx, lengths))) + keep("sdpaWindow", ops.scaledDotProductAttention(q, k, v, keyLengths = lengthsTensor(ctx, lengths), window = 3, rightContext = 1)) + val mask = ctx.fromFloatArray(Shape(seqQ, seqKV), FP32::class, maskArr) + keep("sdpaMask", ops.scaledDotProductAttention(q, k, v, mask = mask, scale = 0.3f)) + val dense = ctx.fromFloatArray(Shape(batch, 1, seqQ, seqKV), FP32::class, AttentionMask.additive(batch, seqQ, seqKV, true, 3, 1, lengths)) + keep("sdpaDense", ops.scaledDotProductAttention(q, k, v, mask = dense)) + keep("sdpaStructured", ops.scaledDotProductAttention(q, k, v, causal = true, keyLengths = lengthsTensor(ctx, lengths), window = 3, rightContext = 1)) + keep("sdpaPadded", ops.scaledDotProductAttention(q, k, v, keyLengths = lengthsTensor(ctx, intArrayOf(0, seqKV)))) + // A projection's output goes straight into the normalisation without a download. + val wq = ctx.fromFloatArray(Shape(cols, cols), FP32::class, wqArr) + val proj = ops.matmulWeightTransposed(x, wq) + keep("projNorm", lnPlain.forward(proj, ctx)) + probe("proj", proj) + return out + } + val ref = run(scalar) { _, _ -> } + assertTrue(ref.containsKey("rmsNorm").not() || scalar.ops !is FusedNormOps, "the scalar lane has no fused-norm hook") + + WasmSimd.install() + val fast = DirectCpuExecutionContext() + assertIs(fast.ops) + val got = run(fast) { name, t -> + if (name == "softmaxDim0") return@run + val d = t.data + assertIs>(d, "$name should be resident") + if (name == "proj" || name == "q" || name == "k" || name == "v") assertTrue(!d.isMirrored, "$name was not downloaded") + } + for ((name, values) in ref) { + val (rel, abs) = if (name.startsWith("sdpa") || name.contains("Norm")) 1e-4f to 1e-5f else 1e-5f to 1e-6f + val actual = checkNotNull(got[name]) { name } + assertEquals(values.size, actual.size, "$name size") + var maxAbs = 0f + for (v in values) maxAbs = maxOf(maxAbs, kotlin.math.abs(v)) + val tol = maxAbs * rel + abs + for (i in values.indices) assertTrue(kotlin.math.abs(values[i] - actual[i]) <= tol, "$name[$i]: ${actual[i]} vs ${values[i]} (tol $tol)") + } + assertTrue(got.containsKey("rmsNorm"), "rmsNorm served by the sidecar") + // The structured rules equal the dense -1e30 mask on the sidecar kernel. + val a = got.getValue("sdpaStructured"); val b = got.getValue("sdpaDense") + for (i in a.indices) assertTrue(abs(a[i] - b[i]) <= 1e-6f, "structured vs dense [$i]") + assertTrue(got.getValue("sdpaPadded").all { it.isFinite() }, "fully padded row is finite") + } + + @Test + fun `heap operands keep the common path while transcendental activations and matmul outputs go resident`() = runTest { + WasmSimd.install() + val ctx = DirectCpuExecutionContext() + assertIs(ctx.ops) + val a = ctx.fromFloatArray(Shape(3, 5), FP32::class, pseudo(15, 1)) + val b = ctx.fromFloatArray(Shape(3, 5), FP32::class, pseudo(15, 2)) + // Two heap operands: no upload, the common scalar lane answers on the heap. + assertIs>(ctx.ops.add(a, b).data) + assertIs>(ctx.ops.relu(a).data) + assertIs>(ctx.ops.sum(a).data) + // A transcendental on a heap operand is worth the upload; its output is resident. + val g = ctx.ops.gelu(a) + assertIs>(g.data) + // ... and from there the chain is resident, including a heap partner. + assertIs>(ctx.ops.add(g, b).data) + assertIs>(ctx.ops.relu(g).data) + assertIs>(ctx.ops.sum(g).data) + // Explicit upload once. + val r = (ctx.ops as WasmCpuOps).toResident(a) + assertIs>(r.data) + assertClose(a.data.copyToFloatArray(), r.data.copyToFloatArray(), "toResident") + assertIs>(ctx.ops.add(r, b).data) + // A resident tensor reads like a heap one: elements, the view, and a common op that has no override. + val gd = g.data as WasmLinearTensorData<*> + val expected = DirectCpuExecutionContext().let { s -> s.ops.gelu(s.fromFloatArray(Shape(3, 5), FP32::class, pseudo(15, 1))).data.copyToFloatArray() } + assertClose(expected, g.data.copyToFloatArray(), "gelu via mirror") + assertEquals(expected[7], gd.get(1, 2)) + assertTrue(gd.isMirrored) + assertClose(expected, ctx.ops.transpose(ctx.ops.transpose(g)).data.copyToFloatArray(), "common op on a resident tensor") + } + + @Test + fun `scoped resident outputs are freed at reset and the budget falls back to the heap`() = runTest { + WasmSimd.install() + assertTrue(StorageCapabilities.current().supportsOffHeap, "the hook binds HOST_OFFHEAP while installed") + val scope = ForwardScope(slabFloats = 0) + val ctx = ScopedExecutionContext(DirectCpuExecutionContext(), scope) + val x = ctx.fromFloatArray(Shape(4, 8), FP32::class, pseudo(32, 9)) + val before = WasmKernels.residentActivationBytes() + val y = ctx.ops.gelu(x) + val yd = assertIs>(y.data) + assertEquals(sk.ainet.lang.memory.ScopeKind.FORWARD, yd.storage.scope) + assertEquals(before + 32 * 4, WasmKernels.residentActivationBytes()) + val values = y.data.copyToFloatArray() + scope.reset() + assertEquals(before, WasmKernels.residentActivationBytes(), "reset freed the sidecar bytes") + assertTrue(!yd.isResident) + assertFailsWith { y.data.copyToFloatArray() } + assertEquals(32, values.size) + + // Budget: the kernel still runs in the sidecar, the result comes back to the heap. + val saved = WasmSimd.residentBudgetBytes + try { + WasmSimd.residentBudgetBytes = 0L + val plain = DirectCpuExecutionContext() + val h = plain.fromFloatArray(Shape(4, 8), FP32::class, pseudo(32, 9)) + val z = plain.ops.gelu(h) + assertIs>(z.data) + assertClose(values, z.data.copyToFloatArray(), "budget fallback") + } finally { + WasmSimd.residentBudgetBytes = saved + } + + // The storage itself: zeroed allocation, byte round trip, slice, close. + val s = assertIs(PlatformStorage.allocate(64, MemoryDomain.HOST_OFFHEAP)) + val zeros = ByteArray(64) { 1 } + s.copyInto(zeros) + assertTrue(zeros.all { it == 0.toByte() }, "allocated zeroed") + val bytes = ByteArray(64) { it.toByte() } + s.copyFrom(bytes) + val back = ByteArray(16) + s.slice(8, 16).copyInto(back) + assertTrue(back.contentEquals(bytes.copyOfRange(8, 24)), "slice reads the sub-range") + val held = WasmKernels.residentActivationBytes() + s.close() + assertEquals(held - 64, WasmKernels.residentActivationBytes()) + WasmSimd.uninstall() + assertTrue(!StorageCapabilities.current().supportsOffHeap, "heap only once uninstalled") } } diff --git a/skainet-lang/skainet-lang-core/api/jvm/skainet-lang-core.api b/skainet-lang/skainet-lang-core/api/jvm/skainet-lang-core.api index c2564589a..fc585c61e 100644 --- a/skainet-lang/skainet-lang-core/api/jvm/skainet-lang-core.api +++ b/skainet-lang/skainet-lang-core/api/jvm/skainet-lang-core.api @@ -6430,6 +6430,11 @@ public final class sk/ainet/lang/tensor/ops/FlattenOperation : sk/ainet/lang/ten public fun validateInputs (Ljava/util/List;)Lsk/ainet/lang/tensor/ops/ValidationResult; } +public abstract interface class sk/ainet/lang/tensor/ops/FusedNormOps { + public abstract fun layerNorm (Lsk/ainet/lang/tensor/Tensor;Lsk/ainet/lang/tensor/Tensor;Lsk/ainet/lang/tensor/Tensor;FI)Lsk/ainet/lang/tensor/Tensor; + public abstract fun rmsNorm (Lsk/ainet/lang/tensor/Tensor;Lsk/ainet/lang/tensor/Tensor;FI)Lsk/ainet/lang/tensor/Tensor; +} + public final class sk/ainet/lang/tensor/ops/GeluOperation : sk/ainet/lang/tensor/ops/BaseOperation { public static final field APPROXIMATE Ljava/lang/String; public static final field Companion Lsk/ainet/lang/tensor/ops/GeluOperation$Companion; diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/nn/normalization/LayerNormalization.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/nn/normalization/LayerNormalization.kt index 41c79be5d..4cf92e544 100644 --- a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/nn/normalization/LayerNormalization.kt +++ b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/nn/normalization/LayerNormalization.kt @@ -86,7 +86,20 @@ public class LayerNormalization( require(normDims.contentEquals(normalizedShape)) { "Input shape ${normDims.contentToString()} doesn't match normalized shape ${normalizedShape.contentToString()}" } - + + // A backend with a one-pass kernel (SKEEP-007 fused ops: the wasm sidecar, the JVM FFM + // provider) serves the FP32 layer in one call through the optional FusedNormOps hook. + // Any other op set — including recording and differentiating ones — never implements + // it and runs the decomposition below unchanged. + if (input.dtype == FP32::class) { + val fused = input.ops as? sk.ainet.lang.tensor.ops.FusedNormOps + if (fused != null) { + val gamma = if (elementwiseAffine) params[0].value else null + val beta = if (elementwiseAffine) params[1].value else null + fused.layerNorm(input, gamma, beta, eps.toFloat(), normalizedShape.size)?.let { return@withForwardHooks it } + } + } + // Compute the numerically-sensitive normalization (mean / variance / // std / divide) in f32, regardless of the model dtype. This is standard // LayerNorm practice — PyTorch and JAX upcast fp16/bf16 LayerNorm to f32 diff --git a/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/ops/FusedNormOps.kt b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/ops/FusedNormOps.kt new file mode 100644 index 000000000..6e8c7add5 --- /dev/null +++ b/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/ops/FusedNormOps.kt @@ -0,0 +1,40 @@ +package sk.ainet.lang.tensor.ops + +import sk.ainet.lang.tensor.Tensor +import sk.ainet.lang.types.DType + +/** + * Optional fused normalisations a [TensorOps] implementation may offer beside the interface — + * the hook `LayerNormalization` (and an RMS-norm module) consults before running the decomposed + * `mean` / `subtract` / `multiply` / `sqrt` / `divide` chain, so a backend with a one-pass kernel + * (the SIMD128 sidecar on wasmJs, the native FFM provider on the JVM) serves the layer in one + * call and the others keep the chain unchanged. Not part of [TensorOps]: an op set that does not + * implement this interface — a recording or differentiating one — never sees the fused call, and + * the recorded graph and the gradients keep their decomposed form. + * + * Both functions return `null` when the operands are not something the fused path takes (a dtype + * other than FP32, parameters that are not dense, a kernel that is not loaded), and the caller + * continues with the decomposition. The normalised dimensions are the trailing [normalizedRank] + * dimensions of [input], flattened into one row; `gamma` and `beta` have exactly that shape. + */ +public interface FusedNormOps { + /** + * `(input − mean) / sqrt(var + eps) · gamma + beta` over each row of the trailing + * [normalizedRank] dimensions, with the biased variance; [gamma] / [beta] `null` skip the affine. + */ + public fun layerNorm( + input: Tensor, + gamma: Tensor?, + beta: Tensor?, + eps: Float, + normalizedRank: Int, + ): Tensor? + + /** `input / sqrt(mean(input²) + eps) · gamma` over each row of the trailing [normalizedRank] dimensions. */ + public fun rmsNorm( + input: Tensor, + gamma: Tensor?, + eps: Float, + normalizedRank: Int, + ): Tensor? +} diff --git a/skainet-lang/skainet-lang-core/src/wasmJsMain/kotlin/sk/ainet/lang/memory/PlatformStorage.web.kt b/skainet-lang/skainet-lang-core/src/wasmJsMain/kotlin/sk/ainet/lang/memory/PlatformStorage.web.kt index 59d28c3d2..cafa9643b 100644 --- a/skainet-lang/skainet-lang-core/src/wasmJsMain/kotlin/sk/ainet/lang/memory/PlatformStorage.web.kt +++ b/skainet-lang/skainet-lang-core/src/wasmJsMain/kotlin/sk/ainet/lang/memory/PlatformStorage.web.kt @@ -5,24 +5,51 @@ import sk.ainet.lang.tensor.TensorId import sk.ainet.lang.tensor.storage.MemoryDomain /** - * JS / Wasm: linear memory only — no off-heap, no mmap (SKEEP-003 §4.8.4). Off-heap requests resolve - * to the heap (the planner records the fallback); files arrive through fetch/range requests into - * heap slabs, so [mapFile] is unsupported here. + * A binding of [MemoryDomain.HOST_OFFHEAP] for Kotlin/Wasm, installed by a backend that owns + * memory outside the Kotlin heap — the SIMD128 kernel sidecar's linear memory + * (`skainet-backend-wasm-simd`, `WasmSimd.install()`), where activations can stay resident between + * kernels instead of crossing the boundary per op. This module cannot depend on that backend, so + * the binding is a hook, exactly like the ops override in `skainet-backend-cpu`. + */ +public fun interface OffHeapAllocator { + /** Allocate [bytes] zeroed bytes off the Kotlin heap, owned by a scope of kind [scope]. */ + public fun allocate(bytes: Long, scope: ScopeKind, origin: TensorId?, sink: TraceSink): Storage +} + +/** + * JS / Wasm: the Kotlin heap, plus whatever [offHeapAllocator] a backend installs for + * [MemoryDomain.HOST_OFFHEAP] (SKEEP-003 §4.8.4, SKEEP-007). With no allocator installed off-heap + * requests resolve to the heap (the planner records the fallback); with one they go off the Kotlin + * heap into the backend's memory and [supports] says so, so the planner can place activations + * there. Files arrive through fetch/range requests into heap slabs, so [mapFile] is unsupported. */ public actual object PlatformStorage { - public actual fun supports(domain: MemoryDomain): Boolean = domain == MemoryDomain.HOST_HEAP + /** + * The off-heap binding, or `null` for the heap fallback. Set by `WasmSimd.install()`, cleared + * by its `uninstall()`; a consumer with its own off-heap memory may set it as well. + */ + public var offHeapAllocator: OffHeapAllocator? = null + + public actual fun supports(domain: MemoryDomain): Boolean = + domain == MemoryDomain.HOST_HEAP || (domain == MemoryDomain.HOST_OFFHEAP && offHeapAllocator != null) public actual val supportsMappedFiles: Boolean get() = false public actual fun allocate(bytes: Long, domain: MemoryDomain, scope: ScopeKind, origin: TensorId?, sink: TraceSink): Storage { - require(bytes in 0..Int.MAX_VALUE.toLong()) { "heap storage is limited to 2 GB per array, requested $bytes bytes" } - return when (domain) { + require(bytes >= 0) { "bytes must be >= 0" } + when (domain) { MemoryDomain.MMAP_FILE, MemoryDomain.DEVICE_LOCAL -> throw IllegalArgumentException("$domain is not allocatable on this target") - else -> Storage.Heap.bytes(bytes.toInt(), scope, origin, sink) // HOST_OFFHEAP / PINNED / UNIFIED fall back to the heap + MemoryDomain.HOST_OFFHEAP, MemoryDomain.HOST_PINNED, MemoryDomain.UNIFIED -> { + offHeapAllocator?.let { return it.allocate(bytes, scope, origin, sink) } + } + MemoryDomain.HOST_HEAP -> Unit } + require(bytes <= Int.MAX_VALUE.toLong()) { "heap storage is limited to 2 GB per array, requested $bytes bytes" } + return Storage.Heap.bytes(bytes.toInt(), scope, origin, sink) } public actual fun mapFile(path: String, fileOffset: Long, length: Long, scope: ScopeKind, origin: TensorId?, sink: TraceSink): Storage = throw UnsupportedOperationException("memory-mapped files are not available on this target (use the suspend RandomAccessSource into heap slabs)") - public val info: PlatformStorageInfo = PlatformStorageInfo("heap (fallback)", "none") + public val info: PlatformStorageInfo + get() = PlatformStorageInfo(if (offHeapAllocator != null) "backend linear memory (installed allocator)" else "heap (fallback)", "none") }