@simulatte/doppler 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -0
- package/README.md +16 -23
- package/package.json +30 -32
- package/src/adapters/adapter-registry.js +12 -1
- package/src/adapters/lora-loader.js +23 -6
- package/src/bridge/extension-client.d.ts +5 -0
- package/src/bridge/extension-client.js +40 -0
- package/src/bridge/index.d.ts +2 -1
- package/src/bridge/index.js +6 -4
- package/src/browser/browser-converter.js +31 -1
- package/src/browser/file-picker.js +6 -0
- package/src/browser/safetensors-parser-browser.js +84 -1
- package/src/browser/shard-io-browser.js +2 -2
- package/src/browser/tensor-source-download.js +8 -2
- package/src/browser/tensor-source-http.d.ts +1 -0
- package/src/browser/tensor-source-http.js +5 -1
- package/src/client/doppler-api.browser.js +20 -4
- package/src/client/doppler-api.js +19 -3
- package/src/client/doppler-provider/generation.js +12 -0
- package/src/client/doppler-provider/model-manager.d.ts +10 -0
- package/src/client/doppler-provider/model-manager.js +91 -19
- package/src/client/doppler-provider/source-runtime.d.ts +2 -1
- package/src/client/doppler-provider/source-runtime.js +132 -13
- package/src/client/doppler-registry.json +5 -20
- package/src/config/backward-registry-loader.js +17 -2
- package/src/config/execution-v0-contract-check.js +113 -15
- package/src/config/kernel-path-contract-check.js +57 -29
- package/src/config/kernel-path-loader.d.ts +5 -0
- package/src/config/kernel-path-loader.js +18 -36
- package/src/config/kernels/kernel-ref-digests.js +1 -1
- package/src/config/kernels/registry.js +14 -1
- package/src/config/kernels/registry.json +81 -5
- package/src/config/loader.d.ts +1 -1
- package/src/config/loader.js +15 -2
- package/src/config/merge-contract-check.js +66 -4
- package/src/config/merge-helpers.js +128 -7
- package/src/config/merge.d.ts +1 -0
- package/src/config/merge.js +10 -0
- package/src/config/param-validator.js +47 -2
- package/src/config/presets/kernel-paths/{gemma2-q4k-dequant-f32a.json → gemma2-q4k-dequant-f32a-nosubgroups.json} +3 -3
- package/src/config/presets/kernel-paths/gemma3-f16-fused-f32a-online-streamingprefill.json +223 -0
- package/src/config/presets/kernel-paths/{gemma3-q4k-dequant-f32a.json → gemma3-q4k-dequant-f32a-nosubgroups.json} +3 -3
- package/src/config/presets/kernel-paths/gemma3-q4k-dequant-f32w-f32a-online.json +56 -0
- package/src/config/presets/kernel-paths/lfm2-q4k-dequant-f32a-nosubgroups.json +61 -0
- package/src/config/presets/kernel-paths/registry.json +43 -8
- package/src/config/presets/models/gemma2.json +3 -2
- package/src/config/presets/models/gemma3.json +2 -0
- package/src/config/presets/models/qwen3.json +4 -3
- package/src/config/presets/models/qwen3_5.json +16 -0
- package/src/config/presets/runtime/experiments/bench/gemma3-bench-q4k.json +1 -1
- package/src/config/presets/runtime/experiments/debug/gemma3-debug-q4k.json +1 -1
- package/src/config/presets/runtime/experiments/verify/gemma3-verify.json +1 -1
- package/src/config/presets/runtime/kernels/dequant-f16-q4k.json +6 -13
- package/src/config/presets/runtime/kernels/dequant-f32-q4k.json +6 -13
- package/src/config/presets/runtime/kernels/embeddinggemma-q4k-dequant-f32a.json +37 -0
- package/src/config/presets/runtime/kernels/fused-q4k.json +6 -13
- package/src/config/presets/runtime/kernels/gemma2-q4k-dequant-f16a.json +33 -0
- package/src/config/presets/runtime/kernels/gemma2-q4k-dequant-f32a-nosubgroups.json +33 -0
- package/src/config/presets/runtime/kernels/gemma2-q4k-fused-f32a.json +33 -0
- package/src/config/presets/runtime/kernels/safe-q4k.json +6 -13
- package/src/config/presets/runtime/model/qwen3-5-layer-probe.json +52 -0
- package/src/config/presets/runtime/model/qwen3-5-linear-attn-debug.json +90 -0
- package/src/config/presets/runtime/platform/metal-apple-q4k.json +1 -1
- package/src/config/runtime.js +6 -1
- package/src/config/schema/conversion.schema.d.ts +1 -0
- package/src/config/schema/debug.schema.d.ts +5 -0
- package/src/config/schema/doppler.schema.js +16 -21
- package/src/config/schema/inference-defaults.schema.js +3 -3
- package/src/config/schema/kernel-path.schema.d.ts +5 -1
- package/src/config/schema/kernel-thresholds.schema.js +12 -4
- package/src/config/schema/manifest.schema.d.ts +3 -2
- package/src/config/schema/manifest.schema.js +17 -4
- package/src/config/schema/storage.schema.js +1 -1
- package/src/config/training-defaults.js +30 -22
- package/src/converter/conversion-plan.js +104 -11
- package/src/converter/core.d.ts +7 -0
- package/src/converter/core.js +16 -9
- package/src/converter/execution-v0-manifest.js +4 -1
- package/src/converter/index.d.ts +1 -0
- package/src/converter/index.js +1 -0
- package/src/converter/manifest-inference.js +50 -29
- package/src/converter/parsers/diffusion.js +0 -3
- package/src/converter/parsers/transformer.js +4 -0
- package/src/converter/quantization-info.js +40 -16
- package/src/converter/quantizer.js +19 -12
- package/src/converter/rope-config.js +8 -6
- package/src/converter/shard-packer.d.ts +1 -1
- package/src/converter/shard-packer.js +4 -1
- package/src/converter/tokenizer-utils.d.ts +1 -0
- package/src/converter/tokenizer-utils.js +4 -1
- package/src/debug/config.js +123 -11
- package/src/debug/reference/hf_qwen35_linear_attn_debug.py +268 -0
- package/src/debug/signals.js +7 -1
- package/src/debug/tensor.d.ts +2 -0
- package/src/debug/tensor.js +13 -2
- package/src/distribution/p2p-control-plane.js +52 -12
- package/src/distribution/p2p-observability.js +43 -7
- package/src/distribution/p2p-webrtc-browser.js +20 -0
- package/src/distribution/shard-delivery.js +83 -27
- package/src/formats/gguf/types.js +33 -16
- package/src/formats/rdrr/groups.d.ts +12 -4
- package/src/formats/rdrr/groups.js +3 -6
- package/src/formats/rdrr/parsing.d.ts +4 -0
- package/src/formats/rdrr/parsing.js +53 -3
- package/src/formats/rdrr/types.d.ts +2 -1
- package/src/gpu/command-recorder.js +86 -61
- package/src/gpu/device.d.ts +1 -0
- package/src/gpu/device.js +73 -19
- package/src/gpu/kernel-tuner/benchmarks.js +326 -316
- package/src/gpu/kernel-tuner/cache.js +71 -4
- package/src/gpu/kernel-tuner/tuner.js +22 -4
- package/src/gpu/kernels/attention.js +15 -34
- package/src/gpu/kernels/backward/adam.js +62 -58
- package/src/gpu/kernels/backward/attention_backward.js +257 -169
- package/src/gpu/kernels/backward/conv2d_backward.js +14 -1
- package/src/gpu/kernels/cast.js +191 -149
- package/src/gpu/kernels/check-stop.js +33 -44
- package/src/gpu/kernels/conv2d.js +27 -17
- package/src/gpu/kernels/cross_entropy_loss.js +21 -15
- package/src/gpu/kernels/depthwise_conv2d.js +36 -26
- package/src/gpu/kernels/dequant.js +178 -126
- package/src/gpu/kernels/energy.d.ts +3 -21
- package/src/gpu/kernels/energy.js +111 -88
- package/src/gpu/kernels/feature-check.js +1 -1
- package/src/gpu/kernels/fused_ffn.js +84 -65
- package/src/gpu/kernels/fused_matmul_residual.js +56 -33
- package/src/gpu/kernels/fused_matmul_rmsnorm.js +62 -45
- package/src/gpu/kernels/gather.js +33 -15
- package/src/gpu/kernels/gelu.js +19 -11
- package/src/gpu/kernels/grouped_pointwise_conv2d.js +33 -23
- package/src/gpu/kernels/groupnorm.js +34 -23
- package/src/gpu/kernels/index.d.ts +8 -0
- package/src/gpu/kernels/index.js +6 -0
- package/src/gpu/kernels/kv-quantize.js +5 -2
- package/src/gpu/kernels/layernorm.js +35 -19
- package/src/gpu/kernels/logit-merge.js +5 -3
- package/src/gpu/kernels/matmul-selection.js +47 -4
- package/src/gpu/kernels/matmul.d.ts +2 -0
- package/src/gpu/kernels/matmul.js +59 -40
- package/src/gpu/kernels/modulate.js +23 -15
- package/src/gpu/kernels/moe.js +221 -175
- package/src/gpu/kernels/pixel_shuffle.js +22 -14
- package/src/gpu/kernels/relu.js +18 -10
- package/src/gpu/kernels/repeat_channels.js +25 -17
- package/src/gpu/kernels/residual.js +37 -27
- package/src/gpu/kernels/rmsnorm.js +66 -43
- package/src/gpu/kernels/rope.js +3 -0
- package/src/gpu/kernels/sample.js +27 -38
- package/src/gpu/kernels/sana_linear_attention.js +18 -10
- package/src/gpu/kernels/scale.js +18 -11
- package/src/gpu/kernels/shader-cache.js +4 -2
- package/src/gpu/kernels/silu.js +120 -72
- package/src/gpu/kernels/softmax.js +44 -25
- package/src/gpu/kernels/split_qg.d.ts +50 -0
- package/src/gpu/kernels/split_qg.js +46 -0
- package/src/gpu/kernels/split_qg.wgsl +58 -0
- package/src/gpu/kernels/split_qg_f16.wgsl +62 -0
- package/src/gpu/kernels/split_qkv.js +23 -13
- package/src/gpu/kernels/transpose.js +18 -10
- package/src/gpu/kernels/transpose.wgsl +5 -3
- package/src/gpu/kernels/upsample2d.js +21 -13
- package/src/gpu/kernels/utils.js +20 -13
- package/src/gpu/partitioned-buffer-pool.js +10 -2
- package/src/gpu/perf-guards.js +2 -9
- package/src/gpu/profiler.js +27 -22
- package/src/gpu/readback-utils.d.ts +16 -0
- package/src/gpu/readback-utils.js +41 -0
- package/src/gpu/submit-tracker.js +13 -0
- package/src/gpu/uniform-cache.d.ts +1 -0
- package/src/gpu/uniform-cache.js +30 -9
- package/src/gpu/weight-buffer.d.ts +1 -1
- package/src/gpu/weight-buffer.js +1 -1
- package/src/hotswap/intent-bundle.js +6 -0
- package/src/hotswap/manifest.d.ts +10 -1
- package/src/hotswap/manifest.js +12 -2
- package/src/hotswap/runtime.js +30 -8
- package/src/index-browser.d.ts +44 -0
- package/src/index-browser.js +14 -0
- package/src/inference/browser-harness-contract-helpers.d.ts +5 -0
- package/src/inference/browser-harness-contract-helpers.js +28 -0
- package/src/inference/browser-harness-diffusion-energy-suites.d.ts +2 -0
- package/src/inference/browser-harness-diffusion-energy-suites.js +269 -0
- package/src/inference/browser-harness-model-helpers.d.ts +16 -0
- package/src/inference/browser-harness-model-helpers.js +217 -0
- package/src/inference/browser-harness-report-helpers.d.ts +7 -0
- package/src/inference/browser-harness-report-helpers.js +42 -0
- package/src/inference/browser-harness-runtime-helpers.d.ts +61 -0
- package/src/inference/browser-harness-runtime-helpers.js +415 -0
- package/src/inference/browser-harness-suite-helpers.d.ts +28 -0
- package/src/inference/browser-harness-suite-helpers.js +268 -0
- package/src/inference/browser-harness-text-helpers.d.ts +27 -0
- package/src/inference/browser-harness-text-helpers.js +788 -0
- package/src/inference/browser-harness.d.ts +8 -0
- package/src/inference/browser-harness.js +149 -1996
- package/src/inference/kv-cache/base.js +140 -94
- package/src/inference/kv-cache/tiered.js +5 -3
- package/src/inference/moe-router.js +88 -56
- package/src/inference/multi-model-network.js +5 -3
- package/src/inference/network-evolution.d.ts +11 -2
- package/src/inference/network-evolution.js +20 -21
- package/src/inference/pipelines/context.d.ts +3 -0
- package/src/inference/pipelines/context.js +142 -2
- package/src/inference/pipelines/diffusion/helpers.js +10 -2
- package/src/inference/pipelines/diffusion/pipeline.js +2 -1
- package/src/inference/pipelines/diffusion/sd3-transformer.js +10 -10
- package/src/inference/pipelines/diffusion/text-encoder-gpu.js +8 -2
- package/src/inference/pipelines/diffusion/vae.js +3 -7
- package/src/inference/pipelines/energy/pipeline.js +27 -21
- package/src/inference/pipelines/energy/quintel.d.ts +5 -0
- package/src/inference/pipelines/energy/quintel.js +11 -0
- package/src/inference/pipelines/energy-head/row-head-pipeline.js +17 -13
- package/src/inference/pipelines/structured/json-head-pipeline.js +26 -11
- package/src/inference/pipelines/text/attention/output-projection.d.ts +12 -0
- package/src/inference/pipelines/text/attention/output-projection.js +8 -0
- package/src/inference/pipelines/text/attention/projections.d.ts +10 -1
- package/src/inference/pipelines/text/attention/projections.js +192 -112
- package/src/inference/pipelines/text/attention/record.js +77 -14
- package/src/inference/pipelines/text/attention/run.js +112 -14
- package/src/inference/pipelines/text/config.js +17 -4
- package/src/inference/pipelines/text/embed.js +2 -8
- package/src/inference/pipelines/text/execution-plan.js +46 -23
- package/src/inference/pipelines/text/execution-v0-contract-helpers.d.ts +59 -0
- package/src/inference/pipelines/text/execution-v0-contract-helpers.js +937 -0
- package/src/inference/pipelines/text/execution-v0-runtime-builders.d.ts +15 -0
- package/src/inference/pipelines/text/execution-v0-runtime-builders.js +279 -0
- package/src/inference/pipelines/text/execution-v0.js +62 -1013
- package/src/inference/pipelines/text/generator-runtime.js +5 -0
- package/src/inference/pipelines/text/generator-steps.d.ts +52 -0
- package/src/inference/pipelines/text/generator-steps.js +340 -221
- package/src/inference/pipelines/text/generator.js +56 -40
- package/src/inference/pipelines/text/init.d.ts +13 -0
- package/src/inference/pipelines/text/init.js +94 -25
- package/src/inference/pipelines/text/kernel-path-auto-select.js +2 -0
- package/src/inference/pipelines/text/kernel-trace.d.ts +2 -0
- package/src/inference/pipelines/text/kernel-trace.js +6 -0
- package/src/inference/pipelines/text/layer.js +4 -9
- package/src/inference/pipelines/text/linear-attention.d.ts +15 -0
- package/src/inference/pipelines/text/linear-attention.js +113 -9
- package/src/inference/pipelines/text/logits/gpu.js +12 -7
- package/src/inference/pipelines/text/logits/index.d.ts +6 -1
- package/src/inference/pipelines/text/logits/index.js +13 -12
- package/src/inference/pipelines/text/logits/utils.d.ts +7 -0
- package/src/inference/pipelines/text/logits/utils.js +9 -0
- package/src/inference/pipelines/text/lora-apply.js +50 -32
- package/src/inference/pipelines/text/model-load.js +282 -104
- package/src/inference/pipelines/text/moe-cache.js +5 -4
- package/src/inference/pipelines/text/moe-cpu-gptoss.js +74 -69
- package/src/inference/pipelines/text/moe-cpu.js +42 -38
- package/src/inference/pipelines/text/moe-gpu.js +110 -86
- package/src/inference/pipelines/text/ops.js +90 -90
- package/src/inference/pipelines/text/probes.js +9 -9
- package/src/inference/pipelines/text/sampling.js +52 -6
- package/src/inference/pipelines/text/weights.js +17 -7
- package/src/inference/pipelines/text.js +13 -1
- package/src/inference/speculative.d.ts +2 -2
- package/src/inference/speculative.js +4 -18
- package/src/inference/test-harness.d.ts +1 -1
- package/src/inference/test-harness.js +17 -7
- package/src/inference/tokenizer.d.ts +0 -5
- package/src/inference/tokenizer.js +4 -23
- package/src/inference/tokenizers/bpe.js +9 -0
- package/src/inference/tokenizers/bundled.js +20 -0
- package/src/inference/tokenizers/sentencepiece.js +12 -0
- package/src/loader/doppler-loader.js +38 -22
- package/src/loader/dtype-utils.js +3 -44
- package/src/loader/embedding-loader.js +7 -3
- package/src/loader/experts/expert-cache.js +13 -6
- package/src/loader/experts/expert-loader.js +10 -6
- package/src/loader/final-weights-loader.js +10 -4
- package/src/loader/layer-loader.js +2 -1
- package/src/loader/loader-state.js +2 -2
- package/src/loader/memory-monitor.js +8 -0
- package/src/loader/multi-model-loader.d.ts +14 -0
- package/src/loader/multi-model-loader.js +70 -24
- package/src/loader/shard-cache.js +84 -14
- package/src/loader/shard-resolver.js +25 -3
- package/src/loader/tensors/tensor-loader.js +214 -144
- package/src/loader/tensors/tensor-reader.js +76 -19
- package/src/loader/weight-downcast.js +1 -1
- package/src/memory/buffer-pool.d.ts +9 -1
- package/src/memory/buffer-pool.js +109 -44
- package/src/memory/unified-detect.js +1 -1
- package/src/rules/inference/dtype.rules.json +5 -0
- package/src/rules/inference/kernel-path.rules.json +24 -8
- package/src/rules/kernels/split-qg.rules.json +6 -0
- package/src/rules/rule-registry.js +27 -1
- package/src/storage/backends/opfs-store.js +68 -24
- package/src/storage/downloader.js +365 -83
- package/src/storage/index.d.ts +3 -0
- package/src/storage/index.js +3 -0
- package/src/storage/preflight.d.ts +2 -2
- package/src/storage/preflight.js +24 -2
- package/src/storage/quickstart-downloader.js +11 -5
- package/src/storage/registry.js +10 -4
- package/src/storage/reports.js +1 -1
- package/src/storage/shard-manager.d.ts +15 -1
- package/src/storage/shard-manager.js +55 -6
- package/src/storage/source-artifact-store.d.ts +52 -0
- package/src/storage/source-artifact-store.js +234 -0
- package/src/tooling/command-api-constants.d.ts +9 -0
- package/src/tooling/command-api-constants.js +9 -0
- package/src/tooling/command-api-family-normalizers.d.ts +9 -0
- package/src/tooling/command-api-family-normalizers.js +343 -0
- package/src/tooling/command-api-helpers.d.ts +25 -0
- package/src/tooling/command-api-helpers.js +262 -0
- package/src/tooling/command-api.js +16 -602
- package/src/tooling/command-envelope.js +4 -1
- package/src/tooling/command-runner-shared.js +52 -18
- package/src/tooling/conversion-config-materializer.js +3 -5
- package/src/tooling/lean-execution-contract.js +150 -3
- package/src/tooling/node-browser-command-runner.js +161 -271
- package/src/tooling/node-command-runner.js +29 -3
- package/src/tooling/node-converter.js +30 -1
- package/src/tooling/node-source-runtime.d.ts +1 -1
- package/src/tooling/node-source-runtime.js +120 -3
- package/src/tooling/node-webgpu.js +24 -21
- package/src/tooling/opfs-cache.js +21 -4
- package/src/tooling/runtime-input-composition.d.ts +38 -0
- package/src/tooling/runtime-input-composition.js +86 -0
- package/src/tooling/source-runtime-bundle.d.ts +40 -5
- package/src/tooling/source-runtime-bundle.js +261 -34
- package/src/tooling/source-runtime-materializer.d.ts +6 -0
- package/src/tooling/source-runtime-materializer.js +93 -0
- package/src/training/attention-backward.js +32 -17
- package/src/training/autograd.js +80 -52
- package/src/training/checkpoint-watch.d.ts +2 -1
- package/src/training/checkpoint-watch.js +39 -6
- package/src/training/checkpoint.js +40 -11
- package/src/training/clip.js +2 -1
- package/src/training/datasets/token-batch.js +20 -8
- package/src/training/distillation/checkpoint-watch.js +1 -0
- package/src/training/distillation/student-fixture.d.ts +22 -0
- package/src/training/distillation/student-fixture.js +846 -0
- package/src/training/distillation/suite-data.d.ts +45 -0
- package/src/training/distillation/suite-data.js +189 -0
- package/src/training/lora-pipeline.js +4 -7
- package/src/training/lora.js +26 -12
- package/src/training/loss.js +5 -6
- package/src/training/objectives/cross_entropy.js +2 -5
- package/src/training/objectives/distill_kd.js +4 -8
- package/src/training/objectives/distill_triplet.js +4 -8
- package/src/training/objectives/ul_stage2_base.js +4 -8
- package/src/training/operator-command.js +2 -0
- package/src/training/optimizer.js +19 -7
- package/src/training/runner.js +2 -1
- package/src/training/suite.js +18 -978
- package/src/training/tensor-factory.d.ts +9 -0
- package/src/training/tensor-factory.js +13 -0
- package/src/training/trainer.js +3 -5
- package/src/training/ul_dataset.js +3 -5
- package/src/training/workloads.js +70 -79
- package/src/types/model.d.ts +5 -0
- package/src/version.js +1 -1
- package/tools/convert-safetensors-node.js +22 -16
- package/tools/doppler-cli.js +50 -26
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Dump intermediate values from Qwen3.5 linear attention (GatedDeltaNet) for comparison with Doppler.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
HF_HOME=/media/x/models/huggingface_cache python3 src/debug/reference/hf_qwen35_linear_attn_debug.py
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import torch
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
os.environ.setdefault("HF_HOME", "/media/x/models/huggingface_cache")
|
|
14
|
+
|
|
15
|
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
16
|
+
|
|
17
|
+
MODEL_ID = "Qwen/Qwen3.5-0.8B"
|
|
18
|
+
PROMPT = "Hello"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def stats(name, tensor):
|
|
22
|
+
t = tensor.float().detach().flatten()
|
|
23
|
+
print(f" {name}: shape={list(tensor.shape)}, "
|
|
24
|
+
f"min={t.min().item():.6f}, max={t.max().item():.6f}, "
|
|
25
|
+
f"mean={t.mean().item():.6f}, absMax={t.abs().max().item():.6f}")
|
|
26
|
+
first8 = t[:8].tolist()
|
|
27
|
+
print(f" first8: {[f'{v:.6f}' for v in first8]}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main():
|
|
31
|
+
print(f"Loading {MODEL_ID}...")
|
|
32
|
+
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, dtype=torch.float32)
|
|
33
|
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
|
|
34
|
+
model.eval()
|
|
35
|
+
|
|
36
|
+
inputs = tokenizer(PROMPT, return_tensors="pt")
|
|
37
|
+
input_ids = inputs["input_ids"]
|
|
38
|
+
print(f"Prompt: '{PROMPT}', Token IDs: {input_ids[0].tolist()}")
|
|
39
|
+
num_tokens = input_ids.shape[1]
|
|
40
|
+
|
|
41
|
+
# Dump key weight values for layer 0
|
|
42
|
+
layer0 = model.model.layers[0]
|
|
43
|
+
attn = layer0.linear_attn
|
|
44
|
+
|
|
45
|
+
print(f"\n=== Layer 0 weights ===")
|
|
46
|
+
if hasattr(attn, 'A_log'):
|
|
47
|
+
a_log = attn.A_log.detach().float()
|
|
48
|
+
a_neg_exp = -torch.exp(a_log)
|
|
49
|
+
stats("A_log", a_log)
|
|
50
|
+
stats("a_neg_exp", a_neg_exp)
|
|
51
|
+
if hasattr(attn, 'dt_bias'):
|
|
52
|
+
stats("dt_bias", attn.dt_bias.detach().float())
|
|
53
|
+
stats("conv1d.weight", attn.conv1d.weight.detach().float())
|
|
54
|
+
stats("norm.weight", attn.norm.weight.detach().float())
|
|
55
|
+
|
|
56
|
+
# Hook into the linear_attn module to capture its input and output
|
|
57
|
+
captured = {}
|
|
58
|
+
|
|
59
|
+
def hook_linear_attn_input(module, args, kwargs):
|
|
60
|
+
if len(args) > 0:
|
|
61
|
+
captured['linear_attn_input'] = args[0].detach().clone()
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def hook_linear_attn_output(module, args, kwargs, output):
|
|
65
|
+
if isinstance(output, tuple):
|
|
66
|
+
captured['linear_attn_output'] = output[0].detach().clone()
|
|
67
|
+
else:
|
|
68
|
+
captured['linear_attn_output'] = output.detach().clone()
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
# Hook into individual projection layers
|
|
72
|
+
def make_hook(name):
|
|
73
|
+
def hook(module, input, output):
|
|
74
|
+
captured[name] = output.detach().clone()
|
|
75
|
+
return hook
|
|
76
|
+
|
|
77
|
+
hooks = []
|
|
78
|
+
hooks.append(attn.register_forward_pre_hook(hook_linear_attn_input, with_kwargs=True))
|
|
79
|
+
hooks.append(attn.register_forward_hook(hook_linear_attn_output, with_kwargs=True))
|
|
80
|
+
hooks.append(attn.in_proj_qkv.register_forward_hook(make_hook('qkv_proj')))
|
|
81
|
+
hooks.append(attn.in_proj_z.register_forward_hook(make_hook('z_proj')))
|
|
82
|
+
hooks.append(attn.in_proj_a.register_forward_hook(make_hook('a_proj')))
|
|
83
|
+
hooks.append(attn.in_proj_b.register_forward_hook(make_hook('b_proj')))
|
|
84
|
+
hooks.append(attn.out_proj.register_forward_hook(make_hook('out_proj')))
|
|
85
|
+
hooks.append(attn.conv1d.register_forward_hook(make_hook('conv1d_raw')))
|
|
86
|
+
hooks.append(attn.norm.register_forward_hook(make_hook('gated_norm')))
|
|
87
|
+
|
|
88
|
+
# Also hook input_layernorm
|
|
89
|
+
hooks.append(layer0.input_layernorm.register_forward_hook(make_hook('input_layernorm')))
|
|
90
|
+
|
|
91
|
+
print(f"\n=== Running forward pass ===")
|
|
92
|
+
with torch.no_grad():
|
|
93
|
+
outputs = model(input_ids, output_hidden_states=True)
|
|
94
|
+
|
|
95
|
+
# Remove hooks
|
|
96
|
+
for h in hooks:
|
|
97
|
+
h.remove()
|
|
98
|
+
|
|
99
|
+
print(f"\n=== Captured intermediates ===")
|
|
100
|
+
for name in ['input_layernorm', 'qkv_proj', 'z_proj', 'a_proj', 'b_proj',
|
|
101
|
+
'conv1d_raw', 'gated_norm', 'linear_attn_input', 'linear_attn_output', 'out_proj']:
|
|
102
|
+
if name in captured:
|
|
103
|
+
stats(name, captured[name])
|
|
104
|
+
else:
|
|
105
|
+
print(f" {name}: NOT CAPTURED")
|
|
106
|
+
|
|
107
|
+
# Hidden states per layer
|
|
108
|
+
print(f"\n=== Hidden states per layer (last token) ===")
|
|
109
|
+
for i in range(min(6, len(outputs.hidden_states) - 1)):
|
|
110
|
+
hs = outputs.hidden_states[i + 1]
|
|
111
|
+
t = hs[0, -1] # last token
|
|
112
|
+
vals = t[:8].tolist()
|
|
113
|
+
max_abs = t.abs().max().item()
|
|
114
|
+
mean_abs = t.abs().mean().item()
|
|
115
|
+
layer_type = type(model.model.layers[i]).__name__
|
|
116
|
+
attn_type = "linear" if hasattr(model.model.layers[i], 'linear_attn') else "full"
|
|
117
|
+
print(f" Layer {i} ({attn_type}): first8={[f'{v:.4f}' for v in vals]}, "
|
|
118
|
+
f"maxAbs={max_abs:.4f}, meanAbs={mean_abs:.4f}")
|
|
119
|
+
|
|
120
|
+
# Logits
|
|
121
|
+
logits = outputs.logits[0, -1]
|
|
122
|
+
top5 = torch.topk(logits, 5)
|
|
123
|
+
print(f"\nTop-5 logits: {[(tokenizer.decode([idx.item()]), f'{val.item():.2f}') for val, idx in zip(top5.values, top5.indices)]}")
|
|
124
|
+
|
|
125
|
+
# Also trace through the linear attention manually to compare with Doppler's kernel
|
|
126
|
+
print(f"\n=== Manual linear attention trace (layer 0) ===")
|
|
127
|
+
with torch.no_grad():
|
|
128
|
+
embed = model.model.embed_tokens(input_ids)
|
|
129
|
+
normed = layer0.input_layernorm(embed)
|
|
130
|
+
stats("normed_input", normed)
|
|
131
|
+
|
|
132
|
+
qkv = attn.in_proj_qkv(normed)
|
|
133
|
+
stats("qkv", qkv)
|
|
134
|
+
|
|
135
|
+
# The HF Qwen3.5 GatedDeltaNet does conv1d on the QKV, then applies SiLU
|
|
136
|
+
# The conv1d expects [batch, channels, seq_len] format
|
|
137
|
+
qkv_t = qkv.transpose(1, 2) # [1, 6144, 1]
|
|
138
|
+
|
|
139
|
+
# Use the conv1d module directly (it has padding configured)
|
|
140
|
+
conv_raw = attn.conv1d(qkv_t)
|
|
141
|
+
stats("conv_raw (from module)", conv_raw.transpose(1, 2))
|
|
142
|
+
|
|
143
|
+
# Truncate to seq_len (causal conv padding)
|
|
144
|
+
conv_causal = conv_raw[..., :num_tokens]
|
|
145
|
+
stats("conv_causal (truncated)", conv_causal.transpose(1, 2))
|
|
146
|
+
|
|
147
|
+
# Apply SiLU
|
|
148
|
+
conv_silu = torch.nn.functional.silu(conv_causal)
|
|
149
|
+
stats("conv_silu", conv_silu.transpose(1, 2))
|
|
150
|
+
|
|
151
|
+
# Split Q, K, V
|
|
152
|
+
conv_out = conv_silu.transpose(1, 2) # [1, seq_len, 6144]
|
|
153
|
+
num_k_heads = 16
|
|
154
|
+
head_k_dim = 128
|
|
155
|
+
head_v_dim = 128
|
|
156
|
+
num_v_heads = 16
|
|
157
|
+
q_size = num_k_heads * head_k_dim # 2048
|
|
158
|
+
k_size = q_size
|
|
159
|
+
v_size = num_v_heads * head_v_dim # 2048
|
|
160
|
+
|
|
161
|
+
q = conv_out[..., :q_size]
|
|
162
|
+
k = conv_out[..., q_size:q_size + k_size]
|
|
163
|
+
v = conv_out[..., q_size + k_size:]
|
|
164
|
+
stats("Q (raw)", q)
|
|
165
|
+
stats("K (raw)", k)
|
|
166
|
+
stats("V (raw)", v)
|
|
167
|
+
|
|
168
|
+
# Reshape for per-head processing
|
|
169
|
+
# Q and K: [batch, seq, num_k_heads, head_k_dim]
|
|
170
|
+
q_heads = q.view(1, num_tokens, num_k_heads, head_k_dim)
|
|
171
|
+
k_heads = k.view(1, num_tokens, num_k_heads, head_k_dim)
|
|
172
|
+
v_heads = v.view(1, num_tokens, num_v_heads, head_v_dim)
|
|
173
|
+
|
|
174
|
+
# L2 normalize Q and K
|
|
175
|
+
eps = 1e-6
|
|
176
|
+
q_norm = torch.nn.functional.normalize(q_heads, p=2, dim=-1, eps=eps)
|
|
177
|
+
k_norm = torch.nn.functional.normalize(k_heads, p=2, dim=-1, eps=eps)
|
|
178
|
+
|
|
179
|
+
# Scale Q by 1/sqrt(head_k_dim)
|
|
180
|
+
head_scale = 1.0 / (head_k_dim ** 0.5)
|
|
181
|
+
q_scaled = q_norm * head_scale
|
|
182
|
+
|
|
183
|
+
stats("Q_normed_scaled (per-head)", q_scaled.reshape(1, num_tokens, -1))
|
|
184
|
+
stats("K_normed (per-head)", k_norm.reshape(1, num_tokens, -1))
|
|
185
|
+
|
|
186
|
+
# Projections for gating
|
|
187
|
+
z = attn.in_proj_z(normed)
|
|
188
|
+
a_out = attn.in_proj_a(normed)
|
|
189
|
+
b_out = attn.in_proj_b(normed)
|
|
190
|
+
stats("z", z)
|
|
191
|
+
stats("a", a_out)
|
|
192
|
+
stats("b", b_out)
|
|
193
|
+
|
|
194
|
+
# Compute gating values
|
|
195
|
+
a_log = attn.A_log.detach().float()
|
|
196
|
+
a_neg_exp = -torch.exp(a_log)
|
|
197
|
+
dt_bias = attn.dt_bias.detach().float()
|
|
198
|
+
|
|
199
|
+
softplus_input = a_out.squeeze(0).squeeze(0) + dt_bias
|
|
200
|
+
softplus_val = torch.nn.functional.softplus(softplus_input)
|
|
201
|
+
g = a_neg_exp * softplus_val
|
|
202
|
+
g_exp = torch.exp(g)
|
|
203
|
+
beta = torch.sigmoid(b_out.squeeze(0).squeeze(0))
|
|
204
|
+
|
|
205
|
+
stats("softplus(a + dt_bias)", softplus_val.unsqueeze(0).unsqueeze(0))
|
|
206
|
+
stats("g (decay)", g.unsqueeze(0).unsqueeze(0))
|
|
207
|
+
stats("g_exp (decay factor)", g_exp.unsqueeze(0).unsqueeze(0))
|
|
208
|
+
stats("beta (sigmoid(b))", beta.unsqueeze(0).unsqueeze(0))
|
|
209
|
+
|
|
210
|
+
# Recurrent state update (for first token, state is all zeros)
|
|
211
|
+
# state[head, kd, vd] = state * g_exp + k[kd] * delta[vd]
|
|
212
|
+
# where delta[vd] = (v[vd] - state^T @ k * beta
|
|
213
|
+
# For zero state: delta[vd] = v[vd] * beta, state = k ⊗ delta
|
|
214
|
+
state = torch.zeros(num_v_heads, head_k_dim, head_v_dim)
|
|
215
|
+
|
|
216
|
+
# Apply decay (no-op for zero state)
|
|
217
|
+
for head in range(num_v_heads):
|
|
218
|
+
state[head] *= g_exp[head].item()
|
|
219
|
+
|
|
220
|
+
k_head = k_norm[0, 0, head % num_k_heads] # broadcast q_rep
|
|
221
|
+
v_head = v_heads[0, 0, head]
|
|
222
|
+
|
|
223
|
+
# kv_mem = state @ k
|
|
224
|
+
kv_mem = state[head].t() @ k_head # [head_v_dim]
|
|
225
|
+
|
|
226
|
+
# delta = (v - kv_mem) * beta
|
|
227
|
+
delta = (v_head - kv_mem) * beta[head].item()
|
|
228
|
+
|
|
229
|
+
# state += outer(k, delta)
|
|
230
|
+
state[head] += torch.outer(k_head, delta)
|
|
231
|
+
|
|
232
|
+
# Output: out = state^T @ q
|
|
233
|
+
output_per_head = torch.zeros(1, num_tokens, num_v_heads, head_v_dim)
|
|
234
|
+
for head in range(num_v_heads):
|
|
235
|
+
q_head = q_scaled[0, 0, head % num_k_heads]
|
|
236
|
+
out_head = state[head].t() @ q_head # [head_v_dim]
|
|
237
|
+
output_per_head[0, 0, head] = out_head
|
|
238
|
+
|
|
239
|
+
raw_out = output_per_head.reshape(1, num_tokens, num_v_heads * head_v_dim)
|
|
240
|
+
stats("Recurrent output (raw)", raw_out)
|
|
241
|
+
|
|
242
|
+
# RMS norm per head + SiLU gate
|
|
243
|
+
z_reshaped = z.view(1, num_tokens, num_v_heads, head_v_dim)
|
|
244
|
+
norm_weight = attn.norm.weight.detach().float() # [head_v_dim] (shared mode)
|
|
245
|
+
rms_eps = 1e-6
|
|
246
|
+
|
|
247
|
+
for head in range(num_v_heads):
|
|
248
|
+
head_out = output_per_head[0, 0, head] # [head_v_dim]
|
|
249
|
+
mean_sq = (head_out ** 2).mean()
|
|
250
|
+
inv_rms = 1.0 / torch.sqrt(mean_sq + rms_eps)
|
|
251
|
+
z_gate = torch.nn.functional.silu(z_reshaped[0, 0, head])
|
|
252
|
+
output_per_head[0, 0, head] = head_out * inv_rms * norm_weight * z_gate
|
|
253
|
+
|
|
254
|
+
gated_out = output_per_head.reshape(1, num_tokens, num_v_heads * head_v_dim)
|
|
255
|
+
stats("After RMSNorm + SiLU gate", gated_out)
|
|
256
|
+
|
|
257
|
+
# Output projection
|
|
258
|
+
o_result = torch.nn.functional.linear(gated_out, attn.out_proj.weight)
|
|
259
|
+
stats("After out_proj", o_result)
|
|
260
|
+
|
|
261
|
+
# Compare with captured output
|
|
262
|
+
if 'linear_attn_output' in captured:
|
|
263
|
+
diff = (o_result - captured['linear_attn_output']).abs()
|
|
264
|
+
print(f"\n Diff vs captured output: maxDiff={diff.max().item():.6f}")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
if __name__ == "__main__":
|
|
268
|
+
main()
|
package/src/debug/signals.js
CHANGED
|
@@ -24,7 +24,13 @@ export function signalResult(data) {
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
export function signalError(error, details) {
|
|
27
|
-
|
|
27
|
+
if (details != null && (typeof details !== 'object' || Array.isArray(details))) {
|
|
28
|
+
throw new Error('signalError details must be an object when provided.');
|
|
29
|
+
}
|
|
30
|
+
if (details && Object.hasOwn(details, 'error')) {
|
|
31
|
+
throw new Error('signalError details.error is reserved. Pass the primary error as the first argument.');
|
|
32
|
+
}
|
|
33
|
+
console.log(`${SIGNALS.ERROR} ${JSON.stringify({ error, ...(details ?? {}) })}`);
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
|
package/src/debug/tensor.d.ts
CHANGED
package/src/debug/tensor.js
CHANGED
|
@@ -202,7 +202,13 @@ export const tensor = {
|
|
|
202
202
|
|
|
203
203
|
export async function snapshotTensor(buffer, shape, dtype = 'f32') {
|
|
204
204
|
try {
|
|
205
|
-
if (
|
|
205
|
+
if (
|
|
206
|
+
!gpuDevice
|
|
207
|
+
|| typeof gpuDevice.createBuffer !== 'function'
|
|
208
|
+
|| typeof gpuDevice.createCommandEncoder !== 'function'
|
|
209
|
+
|| !gpuDevice.queue
|
|
210
|
+
|| typeof gpuDevice.queue.submit !== 'function'
|
|
211
|
+
) {
|
|
206
212
|
throw new Error('GPU device not initialized');
|
|
207
213
|
}
|
|
208
214
|
const elementSize = dtype === 'f16' ? 2 : 4;
|
|
@@ -224,8 +230,11 @@ export async function snapshotTensor(buffer, shape, dtype = 'f32') {
|
|
|
224
230
|
staging.destroy();
|
|
225
231
|
const arr = new Float32Array(data);
|
|
226
232
|
return snapshotFromArray(arr, shape ?? [arr.length], dtype);
|
|
227
|
-
} catch {
|
|
233
|
+
} catch (error) {
|
|
234
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
228
235
|
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
error: message,
|
|
229
238
|
shape: shape ?? [0],
|
|
230
239
|
dtype,
|
|
231
240
|
stats: { min: 0, max: 0, maxAbs: 0, mean: 0, std: 0 },
|
|
@@ -241,6 +250,8 @@ export function snapshotFromArray(arr, shape, dtype = 'f32') {
|
|
|
241
250
|
const stats = computeArrayStats(arr, Math.min(arr.length, numElements));
|
|
242
251
|
|
|
243
252
|
return {
|
|
253
|
+
ok: true,
|
|
254
|
+
error: null,
|
|
244
255
|
shape,
|
|
245
256
|
dtype,
|
|
246
257
|
stats: {
|
|
@@ -38,10 +38,17 @@ function asOptionalTimestamp(value, label) {
|
|
|
38
38
|
return Math.floor(parsed);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function
|
|
41
|
+
function asOptionalNonNegativeInteger(value, label) {
|
|
42
|
+
if (value === undefined || value === null) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
42
45
|
const parsed = Number(value);
|
|
43
46
|
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
44
|
-
|
|
47
|
+
throw createP2PTransportError(
|
|
48
|
+
P2P_TRANSPORT_ERROR_CODES.payloadInvalid,
|
|
49
|
+
`${label} must be a non-negative integer when provided.`,
|
|
50
|
+
{ label }
|
|
51
|
+
);
|
|
45
52
|
}
|
|
46
53
|
return parsed;
|
|
47
54
|
}
|
|
@@ -104,12 +111,11 @@ export function normalizeControlPlaneSessionUpdate(value, label = 'p2p control-p
|
|
|
104
111
|
|
|
105
112
|
export function normalizeP2PPolicyDecision(value, label = 'p2p control-plane policy decision') {
|
|
106
113
|
if (value === undefined || value === null) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
};
|
|
114
|
+
throw createP2PTransportError(
|
|
115
|
+
P2P_TRANSPORT_ERROR_CODES.payloadInvalid,
|
|
116
|
+
`${label} must return an explicit boolean or object decision.`,
|
|
117
|
+
{ label }
|
|
118
|
+
);
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
if (typeof value === 'boolean') {
|
|
@@ -129,9 +135,40 @@ export function normalizeP2PPolicyDecision(value, label = 'p2p control-plane pol
|
|
|
129
135
|
);
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
const hasAllow = Object.prototype.hasOwnProperty.call(value, 'allow');
|
|
139
|
+
const hasDeny = Object.prototype.hasOwnProperty.call(value, 'deny');
|
|
140
|
+
if (!hasAllow && !hasDeny) {
|
|
141
|
+
throw createP2PTransportError(
|
|
142
|
+
P2P_TRANSPORT_ERROR_CODES.payloadInvalid,
|
|
143
|
+
`${label} must include allow or deny.`,
|
|
144
|
+
{ label }
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (hasAllow && typeof value.allow !== 'boolean') {
|
|
148
|
+
throw createP2PTransportError(
|
|
149
|
+
P2P_TRANSPORT_ERROR_CODES.payloadInvalid,
|
|
150
|
+
`${label}.allow must be a boolean when provided.`,
|
|
151
|
+
{ label }
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (hasDeny && typeof value.deny !== 'boolean') {
|
|
155
|
+
throw createP2PTransportError(
|
|
156
|
+
P2P_TRANSPORT_ERROR_CODES.payloadInvalid,
|
|
157
|
+
`${label}.deny must be a boolean when provided.`,
|
|
158
|
+
{ label }
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (hasAllow && hasDeny && value.allow === value.deny) {
|
|
162
|
+
throw createP2PTransportError(
|
|
163
|
+
P2P_TRANSPORT_ERROR_CODES.payloadInvalid,
|
|
164
|
+
`${label} has conflicting allow/deny values.`,
|
|
165
|
+
{ label }
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const allow = hasAllow
|
|
170
|
+
? value.allow
|
|
171
|
+
: value.deny !== true;
|
|
135
172
|
const reason = asOptionalString(value.reason, `${label}.reason`);
|
|
136
173
|
const sessionUpdate = normalizeControlPlaneSessionUpdate(
|
|
137
174
|
{
|
|
@@ -180,7 +217,10 @@ export function normalizeP2PControlPlaneConfig(config = {}) {
|
|
|
180
217
|
contractVersion: assertSupportedP2PControlPlaneContract(
|
|
181
218
|
raw.contractVersion ?? P2P_CONTROL_PLANE_CONTRACT_VERSION
|
|
182
219
|
),
|
|
183
|
-
tokenRefreshSkewMs:
|
|
220
|
+
tokenRefreshSkewMs: asOptionalNonNegativeInteger(
|
|
221
|
+
raw.tokenRefreshSkewMs,
|
|
222
|
+
'p2p.controlPlane.tokenRefreshSkewMs'
|
|
223
|
+
) ?? DEFAULT_TOKEN_REFRESH_SKEW_MS,
|
|
184
224
|
tokenProvider,
|
|
185
225
|
policyEvaluator,
|
|
186
226
|
};
|
|
@@ -12,6 +12,14 @@ function asFiniteNumber(value, fallback = 0) {
|
|
|
12
12
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function assertFiniteNumber(value, label) {
|
|
16
|
+
const parsed = Number(value);
|
|
17
|
+
if (!Number.isFinite(parsed)) {
|
|
18
|
+
throw new Error(`P2P observability ${label} must be a finite number.`);
|
|
19
|
+
}
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
function asNonNegativeInteger(value, fallback = 0) {
|
|
16
24
|
const parsed = Number(value);
|
|
17
25
|
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
@@ -69,15 +77,43 @@ function percentile(values, ratio) {
|
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
function resolveSLOTargets(options = {}) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
const hasExplicitTargets = Object.hasOwn(options, 'targets');
|
|
81
|
+
if (hasExplicitTargets && (options.targets == null || typeof options.targets !== 'object' || Array.isArray(options.targets))) {
|
|
82
|
+
throw new Error('P2P observability targets must be an object when provided.');
|
|
83
|
+
}
|
|
84
|
+
const targets = hasExplicitTargets ? options.targets : {};
|
|
85
|
+
|
|
86
|
+
const minAvailability = Object.hasOwn(targets, 'minAvailability')
|
|
87
|
+
? assertFiniteNumber(targets.minAvailability, 'targets.minAvailability')
|
|
88
|
+
: DEFAULT_SLO_TARGETS.minAvailability;
|
|
89
|
+
const minP2PHitRate = Object.hasOwn(targets, 'minP2PHitRate')
|
|
90
|
+
? assertFiniteNumber(targets.minP2PHitRate, 'targets.minP2PHitRate')
|
|
91
|
+
: DEFAULT_SLO_TARGETS.minP2PHitRate;
|
|
92
|
+
const maxHttpFallbackRate = Object.hasOwn(targets, 'maxHttpFallbackRate')
|
|
93
|
+
? assertFiniteNumber(targets.maxHttpFallbackRate, 'targets.maxHttpFallbackRate')
|
|
94
|
+
: DEFAULT_SLO_TARGETS.maxHttpFallbackRate;
|
|
95
|
+
const maxP95LatencyMs = Object.hasOwn(targets, 'maxP95LatencyMs')
|
|
96
|
+
? assertFiniteNumber(targets.maxP95LatencyMs, 'targets.maxP95LatencyMs')
|
|
97
|
+
: DEFAULT_SLO_TARGETS.maxP95LatencyMs;
|
|
98
|
+
|
|
99
|
+
if (minAvailability < 0 || minAvailability > 1) {
|
|
100
|
+
throw new Error('P2P observability targets.minAvailability must be between 0 and 1.');
|
|
101
|
+
}
|
|
102
|
+
if (minP2PHitRate < 0 || minP2PHitRate > 1) {
|
|
103
|
+
throw new Error('P2P observability targets.minP2PHitRate must be between 0 and 1.');
|
|
104
|
+
}
|
|
105
|
+
if (maxHttpFallbackRate < 0 || maxHttpFallbackRate > 1) {
|
|
106
|
+
throw new Error('P2P observability targets.maxHttpFallbackRate must be between 0 and 1.');
|
|
107
|
+
}
|
|
108
|
+
if (maxP95LatencyMs < 0) {
|
|
109
|
+
throw new Error('P2P observability targets.maxP95LatencyMs must be >= 0.');
|
|
110
|
+
}
|
|
75
111
|
|
|
76
112
|
return {
|
|
77
|
-
minAvailability
|
|
78
|
-
minP2PHitRate
|
|
79
|
-
maxHttpFallbackRate
|
|
80
|
-
maxP95LatencyMs
|
|
113
|
+
minAvailability,
|
|
114
|
+
minP2PHitRate,
|
|
115
|
+
maxHttpFallbackRate,
|
|
116
|
+
maxP95LatencyMs,
|
|
81
117
|
};
|
|
82
118
|
}
|
|
83
119
|
|
|
@@ -200,6 +200,16 @@ function assertOpenDataChannel(channel, peerId) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
function toRequestMessage(requestId, context) {
|
|
203
|
+
if (context?.contractVersion !== P2P_WEBRTC_DATA_PLANE_CONTRACT_VERSION) {
|
|
204
|
+
throw createP2PTransportError(
|
|
205
|
+
P2P_TRANSPORT_ERROR_CODES.payloadInvalid,
|
|
206
|
+
`Unexpected WebRTC data-plane contractVersion "${context?.contractVersion}".`,
|
|
207
|
+
{
|
|
208
|
+
expectedContractVersion: P2P_WEBRTC_DATA_PLANE_CONTRACT_VERSION,
|
|
209
|
+
actualContractVersion: context?.contractVersion ?? null,
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
}
|
|
203
213
|
return {
|
|
204
214
|
schemaVersion: P2P_WEBRTC_MESSAGE_SCHEMA_VERSION,
|
|
205
215
|
contractVersion: P2P_WEBRTC_DATA_PLANE_CONTRACT_VERSION,
|
|
@@ -377,6 +387,16 @@ export function createBrowserWebRTCDataPlaneTransport(config = {}) {
|
|
|
377
387
|
const maxPayloadBytes = Math.max(1, asNonNegativeInteger(config.maxPayloadBytes, DEFAULT_MAX_PAYLOAD_BYTES));
|
|
378
388
|
|
|
379
389
|
return async function webRtcDataPlaneTransport(context) {
|
|
390
|
+
if (context?.contractVersion !== P2P_WEBRTC_DATA_PLANE_CONTRACT_VERSION) {
|
|
391
|
+
throw createP2PTransportError(
|
|
392
|
+
P2P_TRANSPORT_ERROR_CODES.contractUnsupported,
|
|
393
|
+
`Unsupported p2p.webrtc contractVersion "${context?.contractVersion}". Supported: ${P2P_WEBRTC_DATA_PLANE_CONTRACT_VERSION}.`,
|
|
394
|
+
{
|
|
395
|
+
contractVersion: context?.contractVersion ?? null,
|
|
396
|
+
}
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
380
400
|
const selection = normalizePeerSelectionResult(
|
|
381
401
|
selectPeer ? await selectPeer(context) : { peerId: staticPeerId }
|
|
382
402
|
);
|