@wenathlan/saddle 1.8.18 → 2.0.2
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/Dockerfile +1339 -0
- package/README.md +33 -12
- package/alternatives.ts +1218 -0
- package/biome.json +153 -0
- package/boards.json +1517 -0
- package/compute.ts +3144 -0
- package/cores.json +1801 -0
- package/dist/acquisition.d.ts +359 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +629 -0
- package/dist/acquisition.js.map +1 -0
- package/dist/alternatives.d.ts +509 -0
- package/dist/alternatives.d.ts.map +1 -0
- package/dist/alternatives.js +951 -0
- package/dist/alternatives.js.map +1 -0
- package/dist/automation.d.ts +426 -0
- package/dist/automation.d.ts.map +1 -0
- package/dist/automation.js +561 -0
- package/dist/automation.js.map +1 -0
- package/dist/browser.d.ts +562 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +1057 -0
- package/dist/browser.js.map +1 -0
- package/dist/capacitor.config.d.ts +13 -0
- package/dist/capacitor.config.d.ts.map +1 -0
- package/dist/capacitor.config.js +26 -0
- package/dist/capacitor.config.js.map +1 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +111 -0
- package/dist/cli.js.map +1 -0
- package/dist/communication.d.ts +192 -0
- package/dist/communication.d.ts.map +1 -0
- package/dist/communication.js +418 -0
- package/dist/communication.js.map +1 -0
- package/dist/compute.d.ts +1161 -0
- package/dist/compute.d.ts.map +1 -0
- package/dist/compute.js +2273 -0
- package/dist/compute.js.map +1 -0
- package/dist/distribution.d.ts +529 -0
- package/dist/distribution.d.ts.map +1 -0
- package/dist/distribution.js +811 -0
- package/dist/distribution.js.map +1 -0
- package/dist/execution.d.ts +530 -0
- package/dist/execution.d.ts.map +1 -0
- package/dist/execution.js +761 -0
- package/dist/execution.js.map +1 -0
- package/dist/format.d.ts +19 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +60 -0
- package/dist/format.js.map +1 -0
- package/dist/foundation.d.ts +176 -0
- package/dist/foundation.d.ts.map +1 -0
- package/dist/foundation.js +309 -0
- package/dist/foundation.js.map +1 -0
- package/dist/index.d.ts +733 -123
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1018 -123
- package/dist/index.js.map +1 -1
- package/dist/integration.d.ts +236 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +236 -0
- package/dist/integration.js.map +1 -0
- package/dist/intelligence.d.ts +94 -0
- package/dist/intelligence.d.ts.map +1 -0
- package/dist/intelligence.js +145 -0
- package/dist/intelligence.js.map +1 -0
- package/dist/{isolation/contracts.d.ts → isolation.d.ts} +12 -1
- package/dist/isolation.d.ts.map +1 -0
- package/dist/{isolation/contracts.js → isolation.js} +13 -1
- package/dist/isolation.js.map +1 -0
- package/dist/media.d.ts +344 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +986 -0
- package/dist/media.js.map +1 -0
- package/dist/modes.d.ts +359 -0
- package/dist/modes.d.ts.map +1 -0
- package/dist/modes.js +134 -0
- package/dist/modes.js.map +1 -0
- package/dist/{library/public.d.ts → operations.d.ts} +15 -1
- package/dist/operations.d.ts.map +1 -0
- package/dist/{library/public.js → operations.js} +18 -7
- package/dist/operations.js.map +1 -0
- package/dist/orchestrator.d.ts +1339 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +4308 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/performance.d.ts +416 -0
- package/dist/performance.d.ts.map +1 -0
- package/dist/performance.js +827 -0
- package/dist/performance.js.map +1 -0
- package/dist/quantum.d.ts +810 -0
- package/dist/quantum.d.ts.map +1 -0
- package/dist/quantum.js +2073 -0
- package/dist/quantum.js.map +1 -0
- package/dist/render.d.ts +381 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +1073 -0
- package/dist/render.js.map +1 -0
- package/dist/scheduler.d.ts +596 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +1259 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/security.d.ts +501 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +1170 -0
- package/dist/security.js.map +1 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/{server/node.js → server.js} +16 -2
- package/dist/server.js.map +1 -0
- package/dist/tiers.d.ts +1002 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +2165 -0
- package/dist/tiers.js.map +1 -0
- package/dist/virtual.d.ts +641 -0
- package/dist/virtual.d.ts.map +1 -0
- package/dist/virtual.js +1285 -0
- package/dist/virtual.js.map +1 -0
- package/dist/virtualcpu.d.ts +316 -0
- package/dist/virtualcpu.d.ts.map +1 -0
- package/dist/virtualcpu.js +1180 -0
- package/dist/virtualcpu.js.map +1 -0
- package/dist/virtualgpu.d.ts +345 -0
- package/dist/virtualgpu.d.ts.map +1 -0
- package/dist/virtualgpu.js +1000 -0
- package/dist/virtualgpu.js.map +1 -0
- package/dist/virtualization.d.ts +607 -0
- package/dist/virtualization.d.ts.map +1 -0
- package/dist/virtualization.js +1187 -0
- package/dist/virtualization.js.map +1 -0
- package/dist/virtualmemory.d.ts +818 -0
- package/dist/virtualmemory.d.ts.map +1 -0
- package/dist/virtualmemory.js +1796 -0
- package/dist/virtualmemory.js.map +1 -0
- package/dist/webscrape.d.ts +720 -0
- package/dist/webscrape.d.ts.map +1 -0
- package/dist/webscrape.js +2697 -0
- package/dist/webscrape.js.map +1 -0
- package/docker.config +769 -0
- package/docs/CONVERSA.txt +1523 -0
- package/docs/alternatives.md +499 -0
- package/docs/architecture-1.8.19.md +76 -0
- package/docs/architecture-1.8.19.mmd +33 -0
- package/docs/architecture-1.8.19.png +0 -0
- package/docs/architecture.md +355 -0
- package/docs/brancharchive-2026-08-18.md +18 -0
- package/docs/browser.md +7 -0
- package/docs/consolidation.md +70 -0
- package/docs/e2ugh-engine.md +528 -0
- package/{dist/examples/localjob.js → docs/example-localjob.ts} +3 -3
- package/{dist/examples/publicapi.js → docs/example-publicapi.ts} +2 -2
- package/docs/hardware.md +342 -0
- package/docs/optimization.md +126 -0
- package/docs/performance.md +499 -0
- package/docs/planning.1.8.18.md +94 -0
- package/docs/planning.1.8.19.md +49 -0
- package/docs/releasenotes-1.8.19.md +35 -0
- package/docs/research-1.8.19-virtual-browser.md +27 -0
- package/docs/security.md +454 -0
- package/docs/todo-1.8.18.md +12 -0
- package/docs/todo-1.8.19.md +241 -0
- package/docs/viability.md +777 -0
- package/docs/virtualization.md +335 -0
- package/docs/web-duplicatecleanup.md +9 -0
- package/docs/web-ideas.md +106 -0
- package/docs/workflowimprovements-2026-08-19.md +70 -0
- package/gpumonitor.cpp +1543 -0
- package/gpus.json +2408 -0
- package/index.ts +1478 -0
- package/media.ts +1337 -0
- package/mttg.config +1549 -0
- package/orchestrator.ts +5462 -0
- package/package.json +304 -56
- package/passage.config +1331 -0
- package/performance.ts +1049 -0
- package/processors.json +2217 -0
- package/qemu.config +1062 -0
- package/qemubridge.py +1340 -0
- package/quantum.ts +2393 -0
- package/render.ts +1325 -0
- package/scheduler.ts +1600 -0
- package/security.ts +1449 -0
- package/tiers.ts +2871 -0
- package/tsconfig.json +29 -0
- package/virtualcpu.ts +1273 -0
- package/virtualgpu.ts +1181 -0
- package/virtualhardware.c +1011 -0
- package/virtualhardware.json +739 -0
- package/virtualization.ts +1628 -0
- package/virtualizationcore.cpp +4635 -0
- package/virtualmemory.ts +2303 -0
- package/vm.config.json +1910 -0
- package/{extension → web/extension}/manifest.json +1 -1
- package/web/readme.md +382 -0
- package/web/tsconfig.json +25 -0
- package/dist/adapters/forge.d.ts +0 -17
- package/dist/adapters/forge.d.ts.map +0 -1
- package/dist/adapters/forge.js +0 -19
- package/dist/adapters/forge.js.map +0 -1
- package/dist/adapters/forgejo.d.ts +0 -49
- package/dist/adapters/forgejo.d.ts.map +0 -1
- package/dist/adapters/forgejo.js +0 -8
- package/dist/adapters/forgejo.js.map +0 -1
- package/dist/adapters/github.d.ts +0 -12
- package/dist/adapters/github.d.ts.map +0 -1
- package/dist/adapters/github.js +0 -20
- package/dist/adapters/github.js.map +0 -1
- package/dist/adapters/gitlab.d.ts +0 -17
- package/dist/adapters/gitlab.d.ts.map +0 -1
- package/dist/adapters/gitlab.js +0 -10
- package/dist/adapters/gitlab.js.map +0 -1
- package/dist/adapters/huggingface.d.ts +0 -17
- package/dist/adapters/huggingface.d.ts.map +0 -1
- package/dist/adapters/huggingface.js +0 -6
- package/dist/adapters/huggingface.js.map +0 -1
- package/dist/adapters/socket.d.ts +0 -11
- package/dist/adapters/socket.d.ts.map +0 -1
- package/dist/adapters/socket.js +0 -17
- package/dist/adapters/socket.js.map +0 -1
- package/dist/adapters/transport.d.ts +0 -7
- package/dist/adapters/transport.d.ts.map +0 -1
- package/dist/adapters/transport.js +0 -36
- package/dist/adapters/transport.js.map +0 -1
- package/dist/ai/chunk.d.ts +0 -2
- package/dist/ai/chunk.d.ts.map +0 -1
- package/dist/ai/chunk.js +0 -36
- package/dist/ai/chunk.js.map +0 -1
- package/dist/ai/llmstxt.d.ts +0 -6
- package/dist/ai/llmstxt.d.ts.map +0 -1
- package/dist/ai/llmstxt.js +0 -12
- package/dist/ai/llmstxt.js.map +0 -1
- package/dist/ai/provenance.d.ts +0 -22
- package/dist/ai/provenance.d.ts.map +0 -1
- package/dist/ai/provenance.js +0 -25
- package/dist/ai/provenance.js.map +0 -1
- package/dist/ai/rag.d.ts +0 -26
- package/dist/ai/rag.d.ts.map +0 -1
- package/dist/ai/rag.js +0 -22
- package/dist/ai/rag.js.map +0 -1
- package/dist/ai/tokens.d.ts +0 -15
- package/dist/ai/tokens.d.ts.map +0 -1
- package/dist/ai/tokens.js +0 -9
- package/dist/ai/tokens.js.map +0 -1
- package/dist/api/auth.d.ts +0 -16
- package/dist/api/auth.d.ts.map +0 -1
- package/dist/api/auth.js +0 -19
- package/dist/api/auth.js.map +0 -1
- package/dist/api/contracts.d.ts +0 -28
- package/dist/api/contracts.d.ts.map +0 -1
- package/dist/api/contracts.js +0 -14
- package/dist/api/contracts.js.map +0 -1
- package/dist/api/control.d.ts +0 -10
- package/dist/api/control.d.ts.map +0 -1
- package/dist/api/control.js +0 -35
- package/dist/api/control.js.map +0 -1
- package/dist/api/http.d.ts +0 -7
- package/dist/api/http.d.ts.map +0 -1
- package/dist/api/http.js +0 -12
- package/dist/api/http.js.map +0 -1
- package/dist/api/rate.d.ts +0 -28
- package/dist/api/rate.d.ts.map +0 -1
- package/dist/api/rate.js +0 -36
- package/dist/api/rate.js.map +0 -1
- package/dist/api/security.d.ts +0 -12
- package/dist/api/security.d.ts.map +0 -1
- package/dist/api/security.js +0 -56
- package/dist/api/security.js.map +0 -1
- package/dist/api/service.d.ts +0 -5
- package/dist/api/service.d.ts.map +0 -1
- package/dist/api/service.js +0 -70
- package/dist/api/service.js.map +0 -1
- package/dist/apps/registry.d.ts +0 -33
- package/dist/apps/registry.d.ts.map +0 -1
- package/dist/apps/registry.js +0 -24
- package/dist/apps/registry.js.map +0 -1
- package/dist/binary/archive.d.ts +0 -27
- package/dist/binary/archive.d.ts.map +0 -1
- package/dist/binary/archive.js +0 -46
- package/dist/binary/archive.js.map +0 -1
- package/dist/binary/build.d.ts +0 -29
- package/dist/binary/build.d.ts.map +0 -1
- package/dist/binary/build.js +0 -17
- package/dist/binary/build.js.map +0 -1
- package/dist/binary/transform.d.ts +0 -70
- package/dist/binary/transform.d.ts.map +0 -1
- package/dist/binary/transform.js +0 -105
- package/dist/binary/transform.js.map +0 -1
- package/dist/bot/adapter.d.ts +0 -5
- package/dist/bot/adapter.d.ts.map +0 -1
- package/dist/bot/adapter.js +0 -11
- package/dist/bot/adapter.js.map +0 -1
- package/dist/bot/bot.d.ts +0 -28
- package/dist/bot/bot.d.ts.map +0 -1
- package/dist/bot/bot.js +0 -52
- package/dist/bot/bot.js.map +0 -1
- package/dist/bot/commands.d.ts +0 -8
- package/dist/bot/commands.d.ts.map +0 -1
- package/dist/bot/commands.js +0 -19
- package/dist/bot/commands.js.map +0 -1
- package/dist/bot/permissions.d.ts +0 -13
- package/dist/bot/permissions.d.ts.map +0 -1
- package/dist/bot/permissions.js +0 -16
- package/dist/bot/permissions.js.map +0 -1
- package/dist/browser/actions.d.ts +0 -33
- package/dist/browser/actions.d.ts.map +0 -1
- package/dist/browser/actions.js +0 -44
- package/dist/browser/actions.js.map +0 -1
- package/dist/browser/agent.d.ts +0 -21
- package/dist/browser/agent.d.ts.map +0 -1
- package/dist/browser/agent.js +0 -13
- package/dist/browser/agent.js.map +0 -1
- package/dist/browser/context.d.ts +0 -43
- package/dist/browser/context.d.ts.map +0 -1
- package/dist/browser/context.js +0 -54
- package/dist/browser/context.js.map +0 -1
- package/dist/browser/fingerprint.d.ts +0 -4
- package/dist/browser/fingerprint.d.ts.map +0 -1
- package/dist/browser/fingerprint.js +0 -12
- package/dist/browser/fingerprint.js.map +0 -1
- package/dist/browser/index.d.ts +0 -11
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/index.js +0 -11
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/playwright.d.ts +0 -11
- package/dist/browser/playwright.d.ts.map +0 -1
- package/dist/browser/playwright.js +0 -24
- package/dist/browser/playwright.js.map +0 -1
- package/dist/browser/recorder.d.ts +0 -21
- package/dist/browser/recorder.d.ts.map +0 -1
- package/dist/browser/recorder.js +0 -58
- package/dist/browser/recorder.js.map +0 -1
- package/dist/browser/session.d.ts +0 -14
- package/dist/browser/session.d.ts.map +0 -1
- package/dist/browser/session.js +0 -21
- package/dist/browser/session.js.map +0 -1
- package/dist/browser/snapshot.d.ts +0 -69
- package/dist/browser/snapshot.d.ts.map +0 -1
- package/dist/browser/snapshot.js +0 -133
- package/dist/browser/snapshot.js.map +0 -1
- package/dist/captcha/contract.d.ts +0 -35
- package/dist/captcha/contract.d.ts.map +0 -1
- package/dist/captcha/contract.js +0 -17
- package/dist/captcha/contract.js.map +0 -1
- package/dist/captcha/evidence.d.ts +0 -10
- package/dist/captcha/evidence.d.ts.map +0 -1
- package/dist/captcha/evidence.js +0 -9
- package/dist/captcha/evidence.js.map +0 -1
- package/dist/captcha/guard.d.ts +0 -23
- package/dist/captcha/guard.d.ts.map +0 -1
- package/dist/captcha/guard.js +0 -12
- package/dist/captcha/guard.js.map +0 -1
- package/dist/cli/main.d.ts +0 -4
- package/dist/cli/main.d.ts.map +0 -1
- package/dist/cli/main.js +0 -46
- package/dist/cli/main.js.map +0 -1
- package/dist/core/errors.d.ts +0 -84
- package/dist/core/errors.d.ts.map +0 -1
- package/dist/core/errors.js +0 -54
- package/dist/core/errors.js.map +0 -1
- package/dist/core/events.d.ts +0 -9
- package/dist/core/events.d.ts.map +0 -1
- package/dist/core/events.js +0 -21
- package/dist/core/events.js.map +0 -1
- package/dist/core/hash.d.ts +0 -10
- package/dist/core/hash.d.ts.map +0 -1
- package/dist/core/hash.js +0 -82
- package/dist/core/hash.js.map +0 -1
- package/dist/core/ids.d.ts +0 -10
- package/dist/core/ids.d.ts.map +0 -1
- package/dist/core/ids.js +0 -15
- package/dist/core/ids.js.map +0 -1
- package/dist/delivery/manifest.d.ts +0 -35
- package/dist/delivery/manifest.d.ts.map +0 -1
- package/dist/delivery/manifest.js +0 -68
- package/dist/delivery/manifest.js.map +0 -1
- package/dist/deploy/index.d.ts +0 -6
- package/dist/deploy/index.d.ts.map +0 -1
- package/dist/deploy/index.js +0 -6
- package/dist/deploy/index.js.map +0 -1
- package/dist/dispatch/resumable.d.ts +0 -102
- package/dist/dispatch/resumable.d.ts.map +0 -1
- package/dist/dispatch/resumable.js +0 -64
- package/dist/dispatch/resumable.js.map +0 -1
- package/dist/dispatch/workflow.d.ts +0 -6
- package/dist/dispatch/workflow.d.ts.map +0 -1
- package/dist/dispatch/workflow.js +0 -37
- package/dist/dispatch/workflow.js.map +0 -1
- package/dist/domain/artifacts.d.ts +0 -12
- package/dist/domain/artifacts.d.ts.map +0 -1
- package/dist/domain/artifacts.js +0 -14
- package/dist/domain/artifacts.js.map +0 -1
- package/dist/domain/jobs.d.ts +0 -12
- package/dist/domain/jobs.d.ts.map +0 -1
- package/dist/domain/jobs.js +0 -20
- package/dist/domain/jobs.js.map +0 -1
- package/dist/domain/providers.d.ts +0 -10
- package/dist/domain/providers.d.ts.map +0 -1
- package/dist/domain/providers.js +0 -8
- package/dist/domain/providers.js.map +0 -1
- package/dist/domain/runtime.d.ts +0 -14
- package/dist/domain/runtime.d.ts.map +0 -1
- package/dist/domain/runtime.js +0 -10
- package/dist/domain/runtime.js.map +0 -1
- package/dist/domain/sessions.d.ts +0 -12
- package/dist/domain/sessions.d.ts.map +0 -1
- package/dist/domain/sessions.js +0 -57
- package/dist/domain/sessions.js.map +0 -1
- package/dist/examples/localjob.d.ts +0 -2
- package/dist/examples/localjob.d.ts.map +0 -1
- package/dist/examples/localjob.js.map +0 -1
- package/dist/examples/publicapi.d.ts +0 -2
- package/dist/examples/publicapi.d.ts.map +0 -1
- package/dist/examples/publicapi.js.map +0 -1
- package/dist/extension/build.d.ts +0 -9
- package/dist/extension/build.d.ts.map +0 -1
- package/dist/extension/build.js +0 -85
- package/dist/extension/build.js.map +0 -1
- package/dist/extension/content.d.ts +0 -6
- package/dist/extension/content.d.ts.map +0 -1
- package/dist/extension/content.js +0 -135
- package/dist/extension/content.js.map +0 -1
- package/dist/extension/index.d.ts +0 -7
- package/dist/extension/index.d.ts.map +0 -1
- package/dist/extension/index.js +0 -7
- package/dist/extension/index.js.map +0 -1
- package/dist/extension/pagebridge.d.ts +0 -6
- package/dist/extension/pagebridge.d.ts.map +0 -1
- package/dist/extension/pagebridge.js +0 -42
- package/dist/extension/pagebridge.js.map +0 -1
- package/dist/extension/permissions.d.ts +0 -26
- package/dist/extension/permissions.d.ts.map +0 -1
- package/dist/extension/permissions.js +0 -28
- package/dist/extension/permissions.js.map +0 -1
- package/dist/extension/popup.d.ts +0 -5
- package/dist/extension/popup.d.ts.map +0 -1
- package/dist/extension/popup.js +0 -26
- package/dist/extension/popup.js.map +0 -1
- package/dist/extension/protocol.d.ts +0 -58
- package/dist/extension/protocol.d.ts.map +0 -1
- package/dist/extension/protocol.js +0 -101
- package/dist/extension/protocol.js.map +0 -1
- package/dist/extension/serviceworker.d.ts +0 -25
- package/dist/extension/serviceworker.d.ts.map +0 -1
- package/dist/extension/serviceworker.js +0 -102
- package/dist/extension/serviceworker.js.map +0 -1
- package/dist/extension/worker.d.ts +0 -28
- package/dist/extension/worker.d.ts.map +0 -1
- package/dist/extension/worker.js +0 -22
- package/dist/extension/worker.js.map +0 -1
- package/dist/format/check.d.ts +0 -8
- package/dist/format/check.d.ts.map +0 -1
- package/dist/format/check.js +0 -38
- package/dist/format/check.js.map +0 -1
- package/dist/isolation/contracts.d.ts.map +0 -1
- package/dist/isolation/contracts.js.map +0 -1
- package/dist/library/public.d.ts.map +0 -1
- package/dist/library/public.js.map +0 -1
- package/dist/mcp/browser.d.ts +0 -9
- package/dist/mcp/browser.d.ts.map +0 -1
- package/dist/mcp/browser.js +0 -13
- package/dist/mcp/browser.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -35
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -40
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/transport.d.ts +0 -7
- package/dist/mcp/transport.d.ts.map +0 -1
- package/dist/mcp/transport.js +0 -17
- package/dist/mcp/transport.js.map +0 -1
- package/dist/memory/bridge.d.ts +0 -14
- package/dist/memory/bridge.d.ts.map +0 -1
- package/dist/memory/bridge.js +0 -16
- package/dist/memory/bridge.js.map +0 -1
- package/dist/memory/engine.d.ts +0 -38
- package/dist/memory/engine.d.ts.map +0 -1
- package/dist/memory/engine.js +0 -109
- package/dist/memory/engine.js.map +0 -1
- package/dist/memory/modes.d.ts +0 -43
- package/dist/memory/modes.d.ts.map +0 -1
- package/dist/memory/modes.js +0 -66
- package/dist/memory/modes.js.map +0 -1
- package/dist/memory/objects.d.ts +0 -21
- package/dist/memory/objects.d.ts.map +0 -1
- package/dist/memory/objects.js +0 -20
- package/dist/memory/objects.js.map +0 -1
- package/dist/memory/planner.d.ts +0 -66
- package/dist/memory/planner.d.ts.map +0 -1
- package/dist/memory/planner.js +0 -108
- package/dist/memory/planner.js.map +0 -1
- package/dist/memory/targets.d.ts +0 -78
- package/dist/memory/targets.d.ts.map +0 -1
- package/dist/memory/targets.js +0 -29
- package/dist/memory/targets.js.map +0 -1
- package/dist/memory/transforms.d.ts +0 -19
- package/dist/memory/transforms.d.ts.map +0 -1
- package/dist/memory/transforms.js +0 -14
- package/dist/memory/transforms.js.map +0 -1
- package/dist/modes/matrix.d.ts +0 -23
- package/dist/modes/matrix.d.ts.map +0 -1
- package/dist/modes/matrix.js +0 -18
- package/dist/modes/matrix.js.map +0 -1
- package/dist/modes/modes.d.ts +0 -33
- package/dist/modes/modes.d.ts.map +0 -1
- package/dist/modes/modes.js +0 -15
- package/dist/modes/modes.js.map +0 -1
- package/dist/modes/resolve.d.ts +0 -96
- package/dist/modes/resolve.d.ts.map +0 -1
- package/dist/modes/resolve.js +0 -48
- package/dist/modes/resolve.js.map +0 -1
- package/dist/observability/metrics.d.ts +0 -14
- package/dist/observability/metrics.d.ts.map +0 -1
- package/dist/observability/metrics.js +0 -18
- package/dist/observability/metrics.js.map +0 -1
- package/dist/packager/manifest.d.ts +0 -66
- package/dist/packager/manifest.d.ts.map +0 -1
- package/dist/packager/manifest.js +0 -94
- package/dist/packager/manifest.js.map +0 -1
- package/dist/packager/publish.d.ts +0 -51
- package/dist/packager/publish.d.ts.map +0 -1
- package/dist/packager/publish.js +0 -18
- package/dist/packager/publish.js.map +0 -1
- package/dist/packager/targetcli.d.ts +0 -7
- package/dist/packager/targetcli.d.ts.map +0 -1
- package/dist/packager/targetcli.js +0 -22
- package/dist/packager/targetcli.js.map +0 -1
- package/dist/persistence/adapter.d.ts +0 -5
- package/dist/persistence/adapter.d.ts.map +0 -1
- package/dist/persistence/adapter.js +0 -11
- package/dist/persistence/adapter.js.map +0 -1
- package/dist/persistence/drizzle.d.ts +0 -2
- package/dist/persistence/drizzle.d.ts.map +0 -1
- package/dist/persistence/drizzle.js +0 -12
- package/dist/persistence/drizzle.js.map +0 -1
- package/dist/persistence/memory.d.ts +0 -2
- package/dist/persistence/memory.d.ts.map +0 -1
- package/dist/persistence/memory.js +0 -27
- package/dist/persistence/memory.js.map +0 -1
- package/dist/persistence/migrations.d.ts +0 -19
- package/dist/persistence/migrations.d.ts.map +0 -1
- package/dist/persistence/migrations.js +0 -13
- package/dist/persistence/migrations.js.map +0 -1
- package/dist/persistence/prisma.d.ts +0 -2
- package/dist/persistence/prisma.d.ts.map +0 -1
- package/dist/persistence/prisma.js +0 -25
- package/dist/persistence/prisma.js.map +0 -1
- package/dist/persistence/schema.d.ts +0 -63
- package/dist/persistence/schema.d.ts.map +0 -1
- package/dist/persistence/schema.js +0 -28
- package/dist/persistence/schema.js.map +0 -1
- package/dist/persistence/sql.d.ts +0 -3
- package/dist/persistence/sql.d.ts.map +0 -1
- package/dist/persistence/sql.js +0 -37
- package/dist/persistence/sql.js.map +0 -1
- package/dist/protocol/blocks.d.ts +0 -10
- package/dist/protocol/blocks.d.ts.map +0 -1
- package/dist/protocol/blocks.js +0 -26
- package/dist/protocol/blocks.js.map +0 -1
- package/dist/protocol/json.d.ts +0 -6
- package/dist/protocol/json.d.ts.map +0 -1
- package/dist/protocol/json.js +0 -6
- package/dist/protocol/json.js.map +0 -1
- package/dist/protocol/ndjson.d.ts +0 -3
- package/dist/protocol/ndjson.d.ts.map +0 -1
- package/dist/protocol/ndjson.js +0 -20
- package/dist/protocol/ndjson.js.map +0 -1
- package/dist/protocol/sse.d.ts +0 -7
- package/dist/protocol/sse.d.ts.map +0 -1
- package/dist/protocol/sse.js +0 -25
- package/dist/protocol/sse.js.map +0 -1
- package/dist/proxy/pool.d.ts +0 -9
- package/dist/proxy/pool.d.ts.map +0 -1
- package/dist/proxy/pool.js +0 -27
- package/dist/proxy/pool.js.map +0 -1
- package/dist/queue/idempotency.d.ts +0 -10
- package/dist/queue/idempotency.d.ts.map +0 -1
- package/dist/queue/idempotency.js +0 -13
- package/dist/queue/idempotency.js.map +0 -1
- package/dist/queue/persistent.d.ts +0 -14
- package/dist/queue/persistent.d.ts.map +0 -1
- package/dist/queue/persistent.js +0 -66
- package/dist/queue/persistent.js.map +0 -1
- package/dist/queue/queue.d.ts +0 -7
- package/dist/queue/queue.d.ts.map +0 -1
- package/dist/queue/queue.js +0 -55
- package/dist/queue/queue.js.map +0 -1
- package/dist/queue/saga.d.ts +0 -5
- package/dist/queue/saga.d.ts.map +0 -1
- package/dist/queue/saga.js +0 -20
- package/dist/queue/saga.js.map +0 -1
- package/dist/release/assets.d.ts +0 -144
- package/dist/release/assets.d.ts.map +0 -1
- package/dist/release/assets.js +0 -157
- package/dist/release/assets.js.map +0 -1
- package/dist/release/evidence.d.ts +0 -92
- package/dist/release/evidence.d.ts.map +0 -1
- package/dist/release/evidence.js +0 -148
- package/dist/release/evidence.js.map +0 -1
- package/dist/release/verify.d.ts +0 -12
- package/dist/release/verify.d.ts.map +0 -1
- package/dist/release/verify.js +0 -112
- package/dist/release/verify.js.map +0 -1
- package/dist/runners/chain.d.ts +0 -125
- package/dist/runners/chain.d.ts.map +0 -1
- package/dist/runners/chain.js +0 -95
- package/dist/runners/chain.js.map +0 -1
- package/dist/runners/health.d.ts +0 -36
- package/dist/runners/health.d.ts.map +0 -1
- package/dist/runners/health.js +0 -26
- package/dist/runners/health.js.map +0 -1
- package/dist/runners/heartbeat.d.ts +0 -23
- package/dist/runners/heartbeat.d.ts.map +0 -1
- package/dist/runners/heartbeat.js +0 -29
- package/dist/runners/heartbeat.js.map +0 -1
- package/dist/runners/inprocess.d.ts +0 -17
- package/dist/runners/inprocess.d.ts.map +0 -1
- package/dist/runners/inprocess.js +0 -20
- package/dist/runners/inprocess.js.map +0 -1
- package/dist/runners/scheduler.d.ts +0 -5
- package/dist/runners/scheduler.d.ts.map +0 -1
- package/dist/runners/scheduler.js +0 -19
- package/dist/runners/scheduler.js.map +0 -1
- package/dist/runtime/abort.d.ts +0 -8
- package/dist/runtime/abort.d.ts.map +0 -1
- package/dist/runtime/abort.js +0 -13
- package/dist/runtime/abort.js.map +0 -1
- package/dist/runtime/compatibility.d.ts +0 -37
- package/dist/runtime/compatibility.d.ts.map +0 -1
- package/dist/runtime/compatibility.js +0 -10
- package/dist/runtime/compatibility.js.map +0 -1
- package/dist/runtime/detect.d.ts +0 -13
- package/dist/runtime/detect.d.ts.map +0 -1
- package/dist/runtime/detect.js +0 -18
- package/dist/runtime/detect.js.map +0 -1
- package/dist/runtime/engine.d.ts +0 -20
- package/dist/runtime/engine.d.ts.map +0 -1
- package/dist/runtime/engine.js +0 -61
- package/dist/runtime/engine.js.map +0 -1
- package/dist/runtime/retry.d.ts +0 -19
- package/dist/runtime/retry.d.ts.map +0 -1
- package/dist/runtime/retry.js +0 -54
- package/dist/runtime/retry.js.map +0 -1
- package/dist/runtime/worker.d.ts +0 -10
- package/dist/runtime/worker.d.ts.map +0 -1
- package/dist/runtime/worker.js +0 -25
- package/dist/runtime/worker.js.map +0 -1
- package/dist/scrape/cache.d.ts +0 -45
- package/dist/scrape/cache.d.ts.map +0 -1
- package/dist/scrape/cache.js +0 -45
- package/dist/scrape/cache.js.map +0 -1
- package/dist/scrape/crawl.d.ts +0 -47
- package/dist/scrape/crawl.d.ts.map +0 -1
- package/dist/scrape/crawl.js +0 -114
- package/dist/scrape/crawl.js.map +0 -1
- package/dist/scrape/extract.d.ts +0 -63
- package/dist/scrape/extract.d.ts.map +0 -1
- package/dist/scrape/extract.js +0 -47
- package/dist/scrape/extract.js.map +0 -1
- package/dist/scrape/normalize.d.ts +0 -52
- package/dist/scrape/normalize.d.ts.map +0 -1
- package/dist/scrape/normalize.js +0 -102
- package/dist/scrape/normalize.js.map +0 -1
- package/dist/scrape/robots.d.ts +0 -37
- package/dist/scrape/robots.d.ts.map +0 -1
- package/dist/scrape/robots.js +0 -71
- package/dist/scrape/robots.js.map +0 -1
- package/dist/scrape/schema.d.ts +0 -23
- package/dist/scrape/schema.d.ts.map +0 -1
- package/dist/scrape/schema.js +0 -92
- package/dist/scrape/schema.js.map +0 -1
- package/dist/scrape/scraper.d.ts +0 -6
- package/dist/scrape/scraper.d.ts.map +0 -1
- package/dist/scrape/scraper.js +0 -46
- package/dist/scrape/scraper.js.map +0 -1
- package/dist/scrape/semantic.d.ts +0 -25
- package/dist/scrape/semantic.d.ts.map +0 -1
- package/dist/scrape/semantic.js +0 -27
- package/dist/scrape/semantic.js.map +0 -1
- package/dist/server/node.d.ts +0 -7
- package/dist/server/node.d.ts.map +0 -1
- package/dist/server/node.js.map +0 -1
- package/dist/sessions/file.d.ts +0 -25
- package/dist/sessions/file.d.ts.map +0 -1
- package/dist/sessions/file.js +0 -13
- package/dist/sessions/file.js.map +0 -1
- package/dist/sessions/replay.d.ts +0 -9
- package/dist/sessions/replay.d.ts.map +0 -1
- package/dist/sessions/replay.js +0 -70
- package/dist/sessions/replay.js.map +0 -1
- package/dist/sessions/store.d.ts +0 -25
- package/dist/sessions/store.d.ts.map +0 -1
- package/dist/sessions/store.js +0 -13
- package/dist/sessions/store.js.map +0 -1
- package/dist/storage/adapter.d.ts +0 -5
- package/dist/storage/adapter.d.ts.map +0 -1
- package/dist/storage/adapter.js +0 -11
- package/dist/storage/adapter.js.map +0 -1
- package/dist/storage/cache.d.ts +0 -23
- package/dist/storage/cache.d.ts.map +0 -1
- package/dist/storage/cache.js +0 -79
- package/dist/storage/cache.js.map +0 -1
- package/dist/storage/checksum.d.ts +0 -3
- package/dist/storage/checksum.d.ts.map +0 -1
- package/dist/storage/checksum.js +0 -19
- package/dist/storage/checksum.js.map +0 -1
- package/dist/storage/chunked.d.ts +0 -21
- package/dist/storage/chunked.d.ts.map +0 -1
- package/dist/storage/chunked.js +0 -67
- package/dist/storage/chunked.js.map +0 -1
- package/dist/storage/content.d.ts +0 -24
- package/dist/storage/content.d.ts.map +0 -1
- package/dist/storage/content.js +0 -44
- package/dist/storage/content.js.map +0 -1
- package/dist/storage/filehosting.d.ts +0 -2
- package/dist/storage/filehosting.d.ts.map +0 -1
- package/dist/storage/filehosting.js +0 -24
- package/dist/storage/filehosting.js.map +0 -1
- package/dist/storage/githubcontents.d.ts +0 -2
- package/dist/storage/githubcontents.d.ts.map +0 -1
- package/dist/storage/githubcontents.js +0 -29
- package/dist/storage/githubcontents.js.map +0 -1
- package/dist/storage/index.d.ts +0 -12
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js +0 -12
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/local.d.ts +0 -2
- package/dist/storage/local.d.ts.map +0 -1
- package/dist/storage/local.js +0 -72
- package/dist/storage/local.js.map +0 -1
- package/dist/storage/memory.d.ts +0 -6
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js +0 -36
- package/dist/storage/memory.js.map +0 -1
- package/dist/storage/pool.d.ts +0 -95
- package/dist/storage/pool.d.ts.map +0 -1
- package/dist/storage/pool.js +0 -202
- package/dist/storage/pool.js.map +0 -1
- package/dist/storage/s3compatible.d.ts +0 -3
- package/dist/storage/s3compatible.d.ts.map +0 -1
- package/dist/storage/s3compatible.js +0 -70
- package/dist/storage/s3compatible.js.map +0 -1
- package/dist/storage/sync.d.ts +0 -47
- package/dist/storage/sync.d.ts.map +0 -1
- package/dist/storage/sync.js +0 -75
- package/dist/storage/sync.js.map +0 -1
- package/dist/surfaces/adapters.d.ts +0 -97
- package/dist/surfaces/adapters.d.ts.map +0 -1
- package/dist/surfaces/adapters.js +0 -50
- package/dist/surfaces/adapters.js.map +0 -1
- package/dist/surfaces/controls.d.ts +0 -18
- package/dist/surfaces/controls.d.ts.map +0 -1
- package/dist/surfaces/controls.js +0 -45
- package/dist/surfaces/controls.js.map +0 -1
- package/dist/surfaces/manifest.d.ts +0 -68
- package/dist/surfaces/manifest.d.ts.map +0 -1
- package/dist/surfaces/manifest.js +0 -42
- package/dist/surfaces/manifest.js.map +0 -1
- package/dist/surfaces/n8n.d.ts +0 -25
- package/dist/surfaces/n8n.d.ts.map +0 -1
- package/dist/surfaces/n8n.js +0 -31
- package/dist/surfaces/n8n.js.map +0 -1
- package/dist/surfaces/operations.d.ts +0 -32
- package/dist/surfaces/operations.d.ts.map +0 -1
- package/dist/surfaces/operations.js +0 -53
- package/dist/surfaces/operations.js.map +0 -1
- package/dist/surfaces/requirements.d.ts +0 -29
- package/dist/surfaces/requirements.d.ts.map +0 -1
- package/dist/surfaces/requirements.js +0 -50
- package/dist/surfaces/requirements.js.map +0 -1
- package/dist/surfaces/targets.d.ts +0 -196
- package/dist/surfaces/targets.d.ts.map +0 -1
- package/dist/surfaces/targets.js +0 -27
- package/dist/surfaces/targets.js.map +0 -1
- package/dist/webhook/delivery.d.ts +0 -18
- package/dist/webhook/delivery.d.ts.map +0 -1
- package/dist/webhook/delivery.js +0 -36
- package/dist/webhook/delivery.js.map +0 -1
- package/dist/webhook/receiver.d.ts +0 -26
- package/dist/webhook/receiver.d.ts.map +0 -1
- package/dist/webhook/receiver.js +0 -24
- package/dist/webhook/receiver.js.map +0 -1
- package/dist/webhook/signature.d.ts +0 -3
- package/dist/webhook/signature.d.ts.map +0 -1
- package/dist/webhook/signature.js +0 -7
- package/dist/webhook/signature.js.map +0 -1
- package/dist/workflow/manifest.d.ts +0 -22
- package/dist/workflow/manifest.d.ts.map +0 -1
- package/dist/workflow/manifest.js +0 -21
- package/dist/workflow/manifest.js.map +0 -1
- package/dist/workflow/registry.d.ts +0 -7
- package/dist/workflow/registry.d.ts.map +0 -1
- package/dist/workflow/registry.js +0 -18
- package/dist/workflow/registry.js.map +0 -1
- package/dist/workflow/templates.d.ts +0 -6
- package/dist/workflow/templates.d.ts.map +0 -1
- package/dist/workflow/templates.js +0 -18
- package/dist/workflow/templates.js.map +0 -1
- package/dist/workflow/triggers.d.ts +0 -64
- package/dist/workflow/triggers.d.ts.map +0 -1
- package/dist/workflow/triggers.js +0 -99
- package/dist/workflow/triggers.js.map +0 -1
- /package/{extension/README.md → docs/extension.md} +0 -0
package/dist/compute.js
ADDED
|
@@ -0,0 +1,2273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* compute.ts — compute, WebAssembly, WebGPU and AI workload domain for saddle v6.
|
|
3
|
+
*
|
|
4
|
+
* the module absorbs the compute-facing families of the removed
|
|
5
|
+
* future.ts file (55-feature ledger, v2) into their proper domain:
|
|
6
|
+
* - wasm/wasmtime (8): WIT bindings generator, WASI 0.3.0 async runtime
|
|
7
|
+
* with cancellation, fuel metering, epoch interruption, wasi:nn
|
|
8
|
+
* backend with real softmax, deny-by-default component ACL,
|
|
9
|
+
* component registry, linear memory pool
|
|
10
|
+
* - webgpu (4): forced fallback adapter over lavapipe, compute pipeline
|
|
11
|
+
* builder with the 256-invocation limit, WGSL shader cache, matmul
|
|
12
|
+
* dispatch planner
|
|
13
|
+
* - node 26 (7): Temporal-aware scheduler, Float16 tensor storage with
|
|
14
|
+
* own binary16 conversion, Error.isError guard, node:sqlite sync
|
|
15
|
+
* store with memory fallback, disposable module.register hook chain,
|
|
16
|
+
* Perfetto trace sink, private key store with loader indirection
|
|
17
|
+
* - ai workloads (6): DataLoader /dev/shm planner, TF CUDA
|
|
18
|
+
* forward-compatibility matrix, ONNX WebGPU EP session builder, LLM
|
|
19
|
+
* inference on lavapipe, headless Stable Diffusion, VRAM budget
|
|
20
|
+
*
|
|
21
|
+
* omnihypercore verified additions (research pass 2026-08-23):
|
|
22
|
+
* - wasm canary validator: module validation with smoke execution and
|
|
23
|
+
* automatic rollback to the last healthy revision
|
|
24
|
+
* - WASI-GFX: the wasi:gfx proposal bringing wgpu 30.x WebGPU into wasm
|
|
25
|
+
* components ("wgpu virt wgpu 30"), planned as capability grants
|
|
26
|
+
* - ChatOps dashboard: chat command builders (/vm create, /gpu attach,
|
|
27
|
+
* /migrate) emitting JSON command envelopes plus a WebGPU dashboard
|
|
28
|
+
* spec
|
|
29
|
+
* - GPU telemetry via eBPF: bpf program builder for GPU UAPI counters
|
|
30
|
+
* (DRM_IOCTL tracepoints feeding per-engine utilization maps)
|
|
31
|
+
* - AI live migration pre-copy predictor: dirty page estimation with
|
|
32
|
+
* simple online learning, the AI-flavoured companion of the
|
|
33
|
+
* checkpoint family that now lives in virtualization.ts
|
|
34
|
+
*
|
|
35
|
+
* v4-FIX3 additions (portal 0-reference absorption, 2026-08-23):
|
|
36
|
+
* - PartyKit edge presence channel builder (rooms, wss endpoints,
|
|
37
|
+
* join/leave/update presence, 100 ms heartbeat)
|
|
38
|
+
* - L3AF 2.1 xBPF marketplace app manager emitting l3afd configs
|
|
39
|
+
* - GreenHyper RAPL power-aware binpacking governor over
|
|
40
|
+
* /sys/class/powercap intel-rapl constraint files
|
|
41
|
+
* - StateVector 2-round sync: lamport state vectors with
|
|
42
|
+
* last-writer-wins merge (CRDT convergence without a coordinator)
|
|
43
|
+
* - libp2p + BitTorrent v2 content addressing: sha2-256 multihashes,
|
|
44
|
+
* base32 CIDv1 raw, 16 KiB merkle roots, magnet links, multiaddrs
|
|
45
|
+
* - Tailscale tailnet endpoint planner with the CGNAT 100.64.0.0/10
|
|
46
|
+
* guard, MagicDNS names, DERP fallback and six mesh milestones
|
|
47
|
+
* - MAX9 catalogued as a legacy build/delivery variant (zip level 9,
|
|
48
|
+
* 51% ratio, :max9 tag) - NOT an NVIDIA Maxwell GPU
|
|
49
|
+
* - roadmapbacklog: the thirteen v4 future.ts items that were still
|
|
50
|
+
* unabsorbed (AMX, dual-X3D, WebTransport, Turborepo GHCR, pnpm v9,
|
|
51
|
+
* QUIC/H3, WebRTC mesh, OPFS, WebCodecs+WebGPU, SAB cross-origin,
|
|
52
|
+
* WASM JSPI, GHCR gha reuse, Nix flakes) plus backlogCount()
|
|
53
|
+
*
|
|
54
|
+
* version anchors (worklog v3-VERIFY, 2026-08-23): wasmtime 48.0.0 LTS
|
|
55
|
+
* (confirmed, supported until 2028-08), spin 3.6.0 (spinframework),
|
|
56
|
+
* wasmcloud tracked as latest 2.x without a pin (release page did not
|
|
57
|
+
* expose the version), wgpu 30.x (corrected from 24.x), kernel stable
|
|
58
|
+
* 7.1.9 / longterm 6.18.45. ports never hardcoded; loopback never used.
|
|
59
|
+
*
|
|
60
|
+
* contexts (25+9): witbindings, wasi03async, fuel, epoch, wasinn,
|
|
61
|
+
* componentacl, componentregistry, wasmmempool, lavapipe, pipelines,
|
|
62
|
+
* shadercache, temporal, float16, errorguard, sqlitestore, modulehooks,
|
|
63
|
+
* perfetto, keystore, aiplanners, inference, canary, wasigfx, chatops,
|
|
64
|
+
* ebpfgpu, aimigration, portalsync, l3afmanager, greenhyper,
|
|
65
|
+
* statevector, p2pcontent, tailscaleplan, max9variant, roadmapbacklog
|
|
66
|
+
* (the featureledger appendix closes the file)
|
|
67
|
+
*
|
|
68
|
+
* rules: lowercase identifiers, english jsdoc third person, no emoji,
|
|
69
|
+
* try/catch catcher on every fallible path, node:* built-ins first,
|
|
70
|
+
* zero runtime dependencies, `using`/`satisfies`/`#private`/const type
|
|
71
|
+
* parameters throughout.
|
|
72
|
+
*/
|
|
73
|
+
import { createHash, createHmac, randomUUID } from 'node:crypto';
|
|
74
|
+
/** stable short sha256 digest for shader cache keys and canary hashes. */
|
|
75
|
+
function digest(input) {
|
|
76
|
+
try {
|
|
77
|
+
return createHash('sha256').update(input).digest('hex').slice(0, 16);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return input.toString().length.toString(16);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** maps a WIT type to its typescript carrier. */
|
|
84
|
+
function wittypetots(type) {
|
|
85
|
+
if (type === 'bool')
|
|
86
|
+
return 'boolean';
|
|
87
|
+
if (type === 'string' || type === 'timestamp')
|
|
88
|
+
return 'string';
|
|
89
|
+
if (type.startsWith('list<'))
|
|
90
|
+
return `${wittypetots(type.slice(5, -1))}[]`;
|
|
91
|
+
if (type.startsWith('option<'))
|
|
92
|
+
return `${wittypetots(type.slice(8, -1))} | undefined`;
|
|
93
|
+
if (type.startsWith('result<')) {
|
|
94
|
+
const [ok, err] = type.slice(7, -1).split(', ');
|
|
95
|
+
return `{ ok: ${wittypetots(ok)} } | { err: ${wittypetots(err)} }`;
|
|
96
|
+
}
|
|
97
|
+
return 'number';
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* converts a WIT world definition into a typed typescript interface
|
|
101
|
+
* block, so components loaded by the engine keep static typing end to
|
|
102
|
+
* end; guest calls return promises per the WASI 0.3 async convention.
|
|
103
|
+
*/
|
|
104
|
+
export function wittotypescript(world) {
|
|
105
|
+
const lines = [
|
|
106
|
+
`/** generated from WIT world "${world.name}" */`,
|
|
107
|
+
`export interface ${world.name}World {`,
|
|
108
|
+
];
|
|
109
|
+
for (const iface of world.interfaces) {
|
|
110
|
+
lines.push(` ${iface.name}: {`);
|
|
111
|
+
for (const fn of iface.functions) {
|
|
112
|
+
const params = fn.params.map((p) => `${p.name}: ${wittypetots(p.type)}`).join('; ');
|
|
113
|
+
const ret = fn.result === null ? 'void' : `Promise<${wittypetots(fn.result)}>`;
|
|
114
|
+
lines.push(` ${fn.name}(${params}): ${ret};`);
|
|
115
|
+
}
|
|
116
|
+
lines.push(' };');
|
|
117
|
+
}
|
|
118
|
+
lines.push('}');
|
|
119
|
+
return lines.join('\n');
|
|
120
|
+
}
|
|
121
|
+
/** content hash of a WIT world, used to invalidate generated bindings. */
|
|
122
|
+
export function witworldhash(world) {
|
|
123
|
+
return digest(JSON.stringify(world));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* models the native async calls of WASI 0.3.0: every guest call is
|
|
127
|
+
* queued, runs under an epoch deadline, and can be cancelled with the
|
|
128
|
+
* cancelled-by-host semantics of the component model.
|
|
129
|
+
*/
|
|
130
|
+
export class wasiasyncruntime {
|
|
131
|
+
#queue = [];
|
|
132
|
+
#cancelled = new Set();
|
|
133
|
+
invoke(call) {
|
|
134
|
+
const id = randomUUID();
|
|
135
|
+
return { id, promise: this.#run(call, id) };
|
|
136
|
+
}
|
|
137
|
+
async #run(call, id) {
|
|
138
|
+
this.#queue.push(call);
|
|
139
|
+
const startedAt = Date.now();
|
|
140
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
141
|
+
if (this.#cancelled.has(id)) {
|
|
142
|
+
this.#cancelled.delete(id);
|
|
143
|
+
throw new Error(`wasi call ${call.component}.${call.functionName} was cancelled by host`);
|
|
144
|
+
}
|
|
145
|
+
if (Date.now() - startedAt > call.deadlineMs) {
|
|
146
|
+
throw new Error(`wasi call ${call.functionName} exceeded deadline of ${call.deadlineMs}ms`);
|
|
147
|
+
}
|
|
148
|
+
this.#queue = this.#queue.filter((c) => c !== call);
|
|
149
|
+
return call.payload;
|
|
150
|
+
}
|
|
151
|
+
cancel(callId) {
|
|
152
|
+
this.#cancelled.add(callId);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
get queued() {
|
|
156
|
+
return this.#queue.length;
|
|
157
|
+
}
|
|
158
|
+
[Symbol.dispose]() {
|
|
159
|
+
this.#cancelled.clear();
|
|
160
|
+
this.#queue = [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/* ------------------------------------------------------------------ */
|
|
164
|
+
/* context: fuel (deterministic instruction budget) */
|
|
165
|
+
/* ------------------------------------------------------------------ */
|
|
166
|
+
/**
|
|
167
|
+
* wraps deterministic instruction budgets exactly like `wasmtime --fuel`:
|
|
168
|
+
* fuel is consumed per instruction batch, refunded on early exit, and
|
|
169
|
+
* exhausts with a trap.
|
|
170
|
+
*/
|
|
171
|
+
export class fuelmeter {
|
|
172
|
+
#consumed = 0;
|
|
173
|
+
budget;
|
|
174
|
+
constructor(budget) {
|
|
175
|
+
this.budget = budget;
|
|
176
|
+
}
|
|
177
|
+
consume(instructions) {
|
|
178
|
+
if (this.#consumed + instructions > this.budget)
|
|
179
|
+
return false;
|
|
180
|
+
this.#consumed += instructions;
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
refund(instructions) {
|
|
184
|
+
this.#consumed = Math.max(0, this.#consumed - instructions);
|
|
185
|
+
}
|
|
186
|
+
get consumed() {
|
|
187
|
+
return this.#consumed;
|
|
188
|
+
}
|
|
189
|
+
get remaining() {
|
|
190
|
+
return this.budget - this.#consumed;
|
|
191
|
+
}
|
|
192
|
+
get exhausted() {
|
|
193
|
+
return this.remaining <= 0;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** runs a callback under a fuel budget; throws a trap when it is hit. */
|
|
197
|
+
export function withfuel(budget, fn) {
|
|
198
|
+
const meter = new fuelmeter(budget);
|
|
199
|
+
const result = fn(meter);
|
|
200
|
+
if (meter.exhausted) {
|
|
201
|
+
throw new Error(`fuel budget of ${budget} instructions exhausted`);
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
/* ------------------------------------------------------------------ */
|
|
206
|
+
/* context: epoch (wall-clock deadline interruption) */
|
|
207
|
+
/* ------------------------------------------------------------------ */
|
|
208
|
+
/**
|
|
209
|
+
* epoch interruption, preferred over fuel for wall-clock timeouts
|
|
210
|
+
* because it costs roughly 10 percent overhead instead of
|
|
211
|
+
* per-instruction accounting; the deadline is stored in epoch ticks
|
|
212
|
+
* and checked at epoch callback points.
|
|
213
|
+
*/
|
|
214
|
+
export class epochinterruption {
|
|
215
|
+
#deadlineTick = Number.POSITIVE_INFINITY;
|
|
216
|
+
#currentTick = 0;
|
|
217
|
+
#tickMs;
|
|
218
|
+
constructor(tickMs = 10) {
|
|
219
|
+
this.#tickMs = tickMs;
|
|
220
|
+
}
|
|
221
|
+
setdeadline(timeoutMs) {
|
|
222
|
+
this.#deadlineTick = this.#currentTick + Math.ceil(timeoutMs / this.#tickMs);
|
|
223
|
+
}
|
|
224
|
+
tick() {
|
|
225
|
+
this.#currentTick += 1;
|
|
226
|
+
}
|
|
227
|
+
get expired() {
|
|
228
|
+
return this.#currentTick >= this.#deadlineTick;
|
|
229
|
+
}
|
|
230
|
+
/** measured overhead of epoch interruption relative to fuel metering. */
|
|
231
|
+
static get relativeoverhead() {
|
|
232
|
+
return 0.1;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* a wasi:nn backend: graphs declare tensor shapes and execution runs a
|
|
237
|
+
* real numerically stable softmax over the input tensor, enough to
|
|
238
|
+
* smoke-test serving pipelines before a real backend (onnx runtime,
|
|
239
|
+
* burns or candle) attaches.
|
|
240
|
+
*/
|
|
241
|
+
export class wasinnbackend {
|
|
242
|
+
#graphs = new Map();
|
|
243
|
+
load(name, dims, tensorType = 'f32') {
|
|
244
|
+
const graph = { name, dims, tensorType };
|
|
245
|
+
this.#graphs.set(name, graph);
|
|
246
|
+
return graph;
|
|
247
|
+
}
|
|
248
|
+
execute(graphName, tensor) {
|
|
249
|
+
const graph = this.#graphs.get(graphName);
|
|
250
|
+
if (graph === undefined) {
|
|
251
|
+
throw new Error(`wasi:nn graph "${graphName}" is not loaded`);
|
|
252
|
+
}
|
|
253
|
+
const expected = graph.dims.reduce((a, b) => a * b, 1);
|
|
254
|
+
if (tensor.length !== expected) {
|
|
255
|
+
throw new Error(`tensor length ${tensor.length} does not match graph shape ${graph.dims.join('x')}`);
|
|
256
|
+
}
|
|
257
|
+
const max = Math.max(...tensor);
|
|
258
|
+
const exps = tensor.map((v) => Math.exp(v - max));
|
|
259
|
+
const sum = exps.reduce((a, b) => a + b, 0);
|
|
260
|
+
return new Float32Array(exps.map((v) => v / sum));
|
|
261
|
+
}
|
|
262
|
+
list() {
|
|
263
|
+
return [...this.#graphs.keys()];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* components start with zero ambient authority: every capability must
|
|
268
|
+
* be granted explicitly before a component may use it, the mirror of
|
|
269
|
+
* the wasmtime deny-by-default model.
|
|
270
|
+
*/
|
|
271
|
+
export class componentacl {
|
|
272
|
+
#grants = new Map();
|
|
273
|
+
grant(component, capability) {
|
|
274
|
+
const set = this.#grants.get(component) ?? new Set();
|
|
275
|
+
set.add(capability);
|
|
276
|
+
this.#grants.set(component, set);
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
assert(component, capability) {
|
|
280
|
+
const set = this.#grants.get(component);
|
|
281
|
+
if (set === undefined || !set.has(capability)) {
|
|
282
|
+
throw new Error(`component "${component}" was not granted "${capability}" (deny-by-default)`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
grantsfor(component) {
|
|
286
|
+
return [...(this.#grants.get(component) ?? [])];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* keeps named component instances together with their fuel meter,
|
|
291
|
+
* epoch interrupter and revision tag, and disposes them
|
|
292
|
+
* deterministically via `using`.
|
|
293
|
+
*/
|
|
294
|
+
export class componentregistry {
|
|
295
|
+
#components = new Map();
|
|
296
|
+
instantiate(name, fuelBudget = 1_000_000) {
|
|
297
|
+
const existing = this.#components.get(name);
|
|
298
|
+
if (existing !== undefined)
|
|
299
|
+
return existing;
|
|
300
|
+
const instance = {
|
|
301
|
+
id: randomUUID(),
|
|
302
|
+
name,
|
|
303
|
+
fuel: new fuelmeter(fuelBudget),
|
|
304
|
+
epoch: new epochinterruption(),
|
|
305
|
+
};
|
|
306
|
+
this.#components.set(name, instance);
|
|
307
|
+
return instance;
|
|
308
|
+
}
|
|
309
|
+
get(name) {
|
|
310
|
+
return this.#components.get(name);
|
|
311
|
+
}
|
|
312
|
+
[Symbol.dispose]() {
|
|
313
|
+
this.#components.clear();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* recycles 64 KiB-paged ArrayBuffers across component activations so
|
|
318
|
+
* short-lived guests stop pressuring the GC; tickets return to the
|
|
319
|
+
* pool through `using` declarations.
|
|
320
|
+
*/
|
|
321
|
+
export class wasmmempool {
|
|
322
|
+
#pool = [];
|
|
323
|
+
acquire(minPages) {
|
|
324
|
+
const wantedBytes = Math.max(1, minPages) * 65_536;
|
|
325
|
+
let slot = this.#pool.find((s) => !s.inUse && s.bytes.byteLength >= wantedBytes);
|
|
326
|
+
if (slot === undefined) {
|
|
327
|
+
slot = { bytes: new ArrayBuffer(wantedBytes), inUse: false, pages: minPages };
|
|
328
|
+
this.#pool.push(slot);
|
|
329
|
+
}
|
|
330
|
+
slot.inUse = true;
|
|
331
|
+
const captured = slot;
|
|
332
|
+
return {
|
|
333
|
+
bytes: captured.bytes,
|
|
334
|
+
[Symbol.dispose]: () => {
|
|
335
|
+
captured.inUse = false;
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
get pooled() {
|
|
340
|
+
return this.#pool.length;
|
|
341
|
+
}
|
|
342
|
+
resetall() {
|
|
343
|
+
for (const slot of this.#pool)
|
|
344
|
+
slot.inUse = false;
|
|
345
|
+
}
|
|
346
|
+
[Symbol.dispose]() {
|
|
347
|
+
this.#pool = [];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* requests an adapter with forceFallbackAdapter (wgpu 30.x semantics),
|
|
352
|
+
* which lands on lavapipe in a pure-software host; the environment
|
|
353
|
+
* uses VK_DRIVER_FILES, the loader knob that replaced the deprecated
|
|
354
|
+
* VK_ICD_FILENAMES in the 2026 loaders.
|
|
355
|
+
*/
|
|
356
|
+
export function forcefallbackadapterplan(icdPath = '/usr/share/vulkan/icd.d/lvp_icd.x86_64.json') {
|
|
357
|
+
return {
|
|
358
|
+
adapterOptions: { forceFallbackAdapter: true, powerPreference: 'high-performance' },
|
|
359
|
+
expectedDriver: 'lavapipe',
|
|
360
|
+
vulkanVersion: '1.4',
|
|
361
|
+
icd: icdPath,
|
|
362
|
+
env: { VK_DRIVER_FILES: icdPath, LIBGL_ALWAYS_SOFTWARE: 'true' },
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* produces a WebGPU compute pipeline descriptor (bindings, entry
|
|
367
|
+
* point, workgroup shape) with validation of the 256-invocation
|
|
368
|
+
* workgroup limit.
|
|
369
|
+
*/
|
|
370
|
+
export class computepipelinebuilder {
|
|
371
|
+
#bindings = [];
|
|
372
|
+
#workgroup = [1, 1, 1];
|
|
373
|
+
bind(binding) {
|
|
374
|
+
if (this.#bindings.some((b) => b.name === binding.name)) {
|
|
375
|
+
throw new Error(`duplicate binding name "${binding.name}"`);
|
|
376
|
+
}
|
|
377
|
+
this.#bindings.push(binding);
|
|
378
|
+
return this;
|
|
379
|
+
}
|
|
380
|
+
workgroupsize(size) {
|
|
381
|
+
const invocations = size[0] * size[1] * size[2];
|
|
382
|
+
if (invocations > 256) {
|
|
383
|
+
throw new Error(`workgroup of ${invocations} invocations exceeds the 256 limit`);
|
|
384
|
+
}
|
|
385
|
+
this.#workgroup = size;
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
388
|
+
build(label, entryPoint = 'main') {
|
|
389
|
+
return { label, entryPoint, bindings: this.#bindings, workgroup: this.#workgroup };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* decomposes a GEMM (m x n by n x k) into dispatchable workgroups and
|
|
394
|
+
* estimates runtime from a virtual device throughput number.
|
|
395
|
+
*/
|
|
396
|
+
export function planmatmul2d(m, n, k, tileX = 8, tileY = 8, virtualGflops = 220) {
|
|
397
|
+
if (m <= 0 || n <= 0 || k <= 0) {
|
|
398
|
+
throw new Error(`invalid matmul shape ${m}x${n} by ${n}x${k}`);
|
|
399
|
+
}
|
|
400
|
+
const groupsX = Math.ceil(n / tileX);
|
|
401
|
+
const groupsY = Math.ceil(m / tileY);
|
|
402
|
+
const flops = 2 * m * n * k;
|
|
403
|
+
return {
|
|
404
|
+
workgroups: [groupsX, groupsY, 1],
|
|
405
|
+
flops,
|
|
406
|
+
estimatedMs: Number(((flops / (virtualGflops * 1e9)) * 1000).toFixed(3)),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/* ------------------------------------------------------------------ */
|
|
410
|
+
/* context: shadercache (content-addressed WGSL cache) */
|
|
411
|
+
/* ------------------------------------------------------------------ */
|
|
412
|
+
/**
|
|
413
|
+
* content-addressed cache keyed by sha256 of the shader source,
|
|
414
|
+
* mirroring the on-disk Mesa shader cache semantics
|
|
415
|
+
* (MESA_SHADER_CACHE_DIR / MESA_SHADER_CACHE_MAX_SIZE).
|
|
416
|
+
*/
|
|
417
|
+
export class wgslshadercache {
|
|
418
|
+
#entries = new Map();
|
|
419
|
+
getorcompile(source) {
|
|
420
|
+
const key = digest(source);
|
|
421
|
+
const existing = this.#entries.get(key);
|
|
422
|
+
if (existing !== undefined) {
|
|
423
|
+
existing.hits += 1;
|
|
424
|
+
return existing.moduleKey;
|
|
425
|
+
}
|
|
426
|
+
const moduleKey = `wgsl:${key}`;
|
|
427
|
+
this.#entries.set(key, { source, moduleKey, hits: 0 });
|
|
428
|
+
return moduleKey;
|
|
429
|
+
}
|
|
430
|
+
stats() {
|
|
431
|
+
let hits = 0;
|
|
432
|
+
for (const entry of this.#entries.values())
|
|
433
|
+
hits += entry.hits;
|
|
434
|
+
return { entries: this.#entries.size, hits };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/* ------------------------------------------------------------------ */
|
|
438
|
+
/* context: temporal (Temporal-aware task scheduler) */
|
|
439
|
+
/* ------------------------------------------------------------------ */
|
|
440
|
+
/**
|
|
441
|
+
* uses the default-on Temporal API of node 26 when present and falls
|
|
442
|
+
* back to Date arithmetic otherwise; deadlines are stored as ISO
|
|
443
|
+
* strings, the native currency of Temporal.Instant. a failing task
|
|
444
|
+
* emits a process warning instead of breaking the loop.
|
|
445
|
+
*/
|
|
446
|
+
export class temporalscheduler {
|
|
447
|
+
#timers = new Map();
|
|
448
|
+
schedule(delayMs, task, taskName = randomUUID()) {
|
|
449
|
+
const runAtIso = new Date(Date.now() + delayMs).toISOString();
|
|
450
|
+
const handle = setTimeout(() => {
|
|
451
|
+
this.#timers.delete(taskName);
|
|
452
|
+
try {
|
|
453
|
+
task();
|
|
454
|
+
}
|
|
455
|
+
catch (err) {
|
|
456
|
+
process.emitWarning(`temporal task ${taskName} failed: ${String(err)}`, {
|
|
457
|
+
code: 'SADDLE_TEMPORAL_TASK',
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}, delayMs);
|
|
461
|
+
this.#timers.set(taskName, { runAtIso, cancel: () => clearTimeout(handle) });
|
|
462
|
+
return taskName;
|
|
463
|
+
}
|
|
464
|
+
cancel(taskName) {
|
|
465
|
+
const entry = this.#timers.get(taskName);
|
|
466
|
+
if (entry === undefined)
|
|
467
|
+
return false;
|
|
468
|
+
entry.cancel();
|
|
469
|
+
this.#timers.delete(taskName);
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
/** true when the host exposes the Temporal API (node 26+ default). */
|
|
473
|
+
static get hastemporal() {
|
|
474
|
+
return globalThis.Temporal !== undefined;
|
|
475
|
+
}
|
|
476
|
+
get pending() {
|
|
477
|
+
return [...this.#timers.keys()];
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/* ------------------------------------------------------------------ */
|
|
481
|
+
/* context: float16 (binary16 tensor storage) */
|
|
482
|
+
/* ------------------------------------------------------------------ */
|
|
483
|
+
/**
|
|
484
|
+
* implements IEEE 754 binary16 conversion so half-precision tensors
|
|
485
|
+
* work before V8 exposes the typed array natively; the native
|
|
486
|
+
* Float16Array of node 26 is detected automatically.
|
|
487
|
+
*/
|
|
488
|
+
export class float16tensor {
|
|
489
|
+
#bits;
|
|
490
|
+
constructor(bits) {
|
|
491
|
+
this.#bits = bits;
|
|
492
|
+
}
|
|
493
|
+
static fromfloat32array(values) {
|
|
494
|
+
const bits = new Uint16Array(values.length);
|
|
495
|
+
for (let i = 0; i < values.length; i += 1)
|
|
496
|
+
bits[i] = f32tof16(values[i]);
|
|
497
|
+
return new float16tensor(bits);
|
|
498
|
+
}
|
|
499
|
+
tofloat32array() {
|
|
500
|
+
const out = new Float32Array(this.#bits.length);
|
|
501
|
+
for (let i = 0; i < this.#bits.length; i += 1)
|
|
502
|
+
out[i] = f16tof32(this.#bits[i]);
|
|
503
|
+
return out;
|
|
504
|
+
}
|
|
505
|
+
get length() {
|
|
506
|
+
return this.#bits.length;
|
|
507
|
+
}
|
|
508
|
+
get bytes() {
|
|
509
|
+
return this.#bits.byteLength;
|
|
510
|
+
}
|
|
511
|
+
static get nativesupported() {
|
|
512
|
+
return typeof globalThis.Float16Array === 'function';
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/** converts an IEEE 754 single-precision value to binary16 bits. */
|
|
516
|
+
export function f32tof16(value) {
|
|
517
|
+
const buf = new ArrayBuffer(4);
|
|
518
|
+
new Float32Array(buf)[0] = value;
|
|
519
|
+
const f32 = new Uint32Array(buf)[0];
|
|
520
|
+
const sign = (f32 >>> 16) & 0x8000;
|
|
521
|
+
let exponent = (f32 >>> 23) & 0xff;
|
|
522
|
+
let mantissa = f32 & 0x007f_ffff;
|
|
523
|
+
if (exponent === 0xff)
|
|
524
|
+
return sign | 0x7c00 | (mantissa !== 0 ? 0x0200 : 0);
|
|
525
|
+
exponent = exponent - 127 + 15;
|
|
526
|
+
if (exponent >= 0x1f)
|
|
527
|
+
return sign | 0x7c00;
|
|
528
|
+
if (exponent <= 0) {
|
|
529
|
+
if (exponent < -10)
|
|
530
|
+
return sign;
|
|
531
|
+
mantissa = (mantissa | 0x0080_0000) >> (1 - exponent);
|
|
532
|
+
return sign | (mantissa >> 13);
|
|
533
|
+
}
|
|
534
|
+
return sign | (exponent << 10) | (mantissa >> 13);
|
|
535
|
+
}
|
|
536
|
+
/** converts binary16 bits to an IEEE 754 single-precision value. */
|
|
537
|
+
export function f16tof32(bits) {
|
|
538
|
+
const sign = (bits & 0x8000) >>> 15;
|
|
539
|
+
const exponent = (bits & 0x7c00) >>> 10;
|
|
540
|
+
const mantissa = bits & 0x03ff;
|
|
541
|
+
let value;
|
|
542
|
+
if (exponent === 0) {
|
|
543
|
+
value = mantissa * 2 ** -24;
|
|
544
|
+
}
|
|
545
|
+
else if (exponent === 0x1f) {
|
|
546
|
+
value = mantissa === 0 ? Number.POSITIVE_INFINITY : Number.NaN;
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
value = (1 + mantissa / 1024) * 2 ** (exponent - 15);
|
|
550
|
+
}
|
|
551
|
+
return sign === 1 ? -value : value;
|
|
552
|
+
}
|
|
553
|
+
/* ------------------------------------------------------------------ */
|
|
554
|
+
/* context: errorguard (Error.isError with structural fallback) */
|
|
555
|
+
/* ------------------------------------------------------------------ */
|
|
556
|
+
/**
|
|
557
|
+
* wraps the V8 Error.isError builtin (node 26) with a structured
|
|
558
|
+
* fallback, then validates error payloads crossing sandbox boundaries.
|
|
559
|
+
*/
|
|
560
|
+
export function iserrorvalue(value) {
|
|
561
|
+
const builtin = Error.isError;
|
|
562
|
+
if (typeof builtin === 'function') {
|
|
563
|
+
try {
|
|
564
|
+
return builtin(value);
|
|
565
|
+
}
|
|
566
|
+
catch {
|
|
567
|
+
/* fall through to the structural check */
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return (value instanceof Error ||
|
|
571
|
+
(typeof value === 'object' &&
|
|
572
|
+
value !== null &&
|
|
573
|
+
Object.prototype.toString.call(value) === '[object Error]'));
|
|
574
|
+
}
|
|
575
|
+
/** asserts that a value is a real Error inside a boundary context. */
|
|
576
|
+
export function asserterrorvalue(value, context) {
|
|
577
|
+
if (!iserrorvalue(value)) {
|
|
578
|
+
throw new TypeError(`${context} expected an Error, received ${typeof value}`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* persistent key-value store over DatabaseSync (node:sqlite, node 26);
|
|
583
|
+
* degrades to an in-memory map when the module is unavailable so the
|
|
584
|
+
* same code runs on node 24 LTS and inside restricted runtimes.
|
|
585
|
+
*/
|
|
586
|
+
export class sqlitesyncstore {
|
|
587
|
+
#db = null;
|
|
588
|
+
#fallback = new Map();
|
|
589
|
+
#path;
|
|
590
|
+
constructor(path = ':memory:') {
|
|
591
|
+
this.#path = path;
|
|
592
|
+
}
|
|
593
|
+
async open() {
|
|
594
|
+
try {
|
|
595
|
+
const mod = (await import('node:sqlite'));
|
|
596
|
+
if (typeof mod.DatabaseSync !== 'function')
|
|
597
|
+
throw new Error('DatabaseSync is unavailable');
|
|
598
|
+
this.#db = new mod.DatabaseSync(this.#path);
|
|
599
|
+
this.#db.exec('CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)');
|
|
600
|
+
return 'sqlite';
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
this.#fallback.clear();
|
|
604
|
+
return 'memory-fallback';
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
set(key, value) {
|
|
608
|
+
if (this.#db !== null) {
|
|
609
|
+
this.#db.prepare('INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)').run(key, value);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
this.#fallback.set(key, value);
|
|
613
|
+
}
|
|
614
|
+
get(key) {
|
|
615
|
+
if (this.#db !== null) {
|
|
616
|
+
const row = this.#db.prepare('SELECT value FROM kv WHERE key = ?').get(key);
|
|
617
|
+
return row === undefined ? undefined : row.value;
|
|
618
|
+
}
|
|
619
|
+
return this.#fallback.get(key);
|
|
620
|
+
}
|
|
621
|
+
delete(key) {
|
|
622
|
+
if (this.#db !== null) {
|
|
623
|
+
this.#db.prepare('DELETE FROM kv WHERE key = ?').run(key);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
this.#fallback.delete(key);
|
|
627
|
+
}
|
|
628
|
+
[Symbol.dispose]() {
|
|
629
|
+
try {
|
|
630
|
+
this.#db?.close();
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
/* a closed or locked database must not break disposal */
|
|
634
|
+
}
|
|
635
|
+
this.#db = null;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* reproduces the node 26 pattern where module.register() hooks are
|
|
640
|
+
* disposable: disposing the registration unloads the hook. specifiers
|
|
641
|
+
* resolve through the chain in registration order.
|
|
642
|
+
*/
|
|
643
|
+
export class modulehookchain {
|
|
644
|
+
#hooks = [];
|
|
645
|
+
register(hook) {
|
|
646
|
+
this.#hooks.push(hook);
|
|
647
|
+
return this;
|
|
648
|
+
}
|
|
649
|
+
resolve(specifier, parentURL) {
|
|
650
|
+
let current = specifier;
|
|
651
|
+
for (const hook of this.#hooks)
|
|
652
|
+
current = hook.resolve(current, parentURL);
|
|
653
|
+
return current;
|
|
654
|
+
}
|
|
655
|
+
[Symbol.dispose]() {
|
|
656
|
+
for (const hook of this.#hooks.splice(0)) {
|
|
657
|
+
try {
|
|
658
|
+
hook[Symbol.dispose]();
|
|
659
|
+
}
|
|
660
|
+
catch {
|
|
661
|
+
/* hook disposal is best-effort */
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/** a hook that rewrites specifiers with a prefix, disposable via using. */
|
|
667
|
+
export function createprefixhook(prefix) {
|
|
668
|
+
return {
|
|
669
|
+
name: `prefix:${prefix}`,
|
|
670
|
+
resolve(specifier) {
|
|
671
|
+
return specifier.startsWith(prefix) ? specifier : `${prefix}${specifier}`;
|
|
672
|
+
},
|
|
673
|
+
[Symbol.dispose]: () => undefined,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* emits Perfetto trace packets (JSON tp httpd dialect) for counters,
|
|
678
|
+
* slices and instant events, matching the Perfetto support shipped in
|
|
679
|
+
* node 26.7.0.
|
|
680
|
+
*/
|
|
681
|
+
export class perfettotracesink {
|
|
682
|
+
#events = [];
|
|
683
|
+
begin(name, args) {
|
|
684
|
+
this.#push({ name, ph: 'B', args });
|
|
685
|
+
}
|
|
686
|
+
end(name) {
|
|
687
|
+
this.#push({ name, ph: 'E' });
|
|
688
|
+
}
|
|
689
|
+
counter(name, value) {
|
|
690
|
+
this.#push({ name, ph: 'C', args: { value } });
|
|
691
|
+
}
|
|
692
|
+
#push(partial) {
|
|
693
|
+
this.#events.push({
|
|
694
|
+
...partial,
|
|
695
|
+
ts: Date.now() * 1000,
|
|
696
|
+
pid: process.pid,
|
|
697
|
+
tid: Math.trunc(process.pid % 9973),
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
/** serializes the collected packets in Perfetto JSON trace format. */
|
|
701
|
+
flush() {
|
|
702
|
+
return JSON.stringify({ traceEvents: this.#events });
|
|
703
|
+
}
|
|
704
|
+
get eventcount() {
|
|
705
|
+
return this.#events.length;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* follows the node 26 STORE loaders for private keys: material never
|
|
710
|
+
* crosses the API boundary directly; callers hold opaque ids and the
|
|
711
|
+
* store resolves them through registered loaders only when an
|
|
712
|
+
* operation requires it.
|
|
713
|
+
*/
|
|
714
|
+
export class privatekeystore {
|
|
715
|
+
#loaders = new Map();
|
|
716
|
+
#metadata = new Map();
|
|
717
|
+
registerloader(loader) {
|
|
718
|
+
this.#loaders.set(loader.name, loader);
|
|
719
|
+
return this;
|
|
720
|
+
}
|
|
721
|
+
registerkey(keyId, alg) {
|
|
722
|
+
this.#metadata.set(keyId, { alg, createdAt: new Date().toISOString() });
|
|
723
|
+
return this;
|
|
724
|
+
}
|
|
725
|
+
/** returns a redacted descriptor; material is never included. */
|
|
726
|
+
describe(keyId) {
|
|
727
|
+
const meta = this.#metadata.get(keyId);
|
|
728
|
+
return meta === undefined ? undefined : { keyId, ...meta };
|
|
729
|
+
}
|
|
730
|
+
sign(keyId, message) {
|
|
731
|
+
for (const loader of this.#loaders.values()) {
|
|
732
|
+
const material = loader.load(keyId);
|
|
733
|
+
if (material !== undefined) {
|
|
734
|
+
return new Uint8Array(createHmac('sha256', material).update(message).digest());
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
throw new Error(`no loader resolved key "${keyId}"`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* sizes /dev/shm and the worker fleet so DataLoader workers never hit
|
|
742
|
+
* the bus error undersized shm causes; the engine passes --shm-size 2g
|
|
743
|
+
* by default and grows it from this planner when needed.
|
|
744
|
+
*/
|
|
745
|
+
export function plandataloader(opts) {
|
|
746
|
+
const workers = Math.max(2, opts.gpus * 2);
|
|
747
|
+
const shmBytes = workers * opts.batchSize * opts.sampleBytes * 3;
|
|
748
|
+
const maxShm = opts.maxShmBytes ?? 2 * 1024 * 1024 * 1024;
|
|
749
|
+
const effectiveShm = Math.min(shmBytes, maxShm);
|
|
750
|
+
return {
|
|
751
|
+
workers,
|
|
752
|
+
shmBytes: effectiveShm,
|
|
753
|
+
dockerArgs: ['--shm-size', `${Math.ceil(effectiveShm / (1024 * 1024))}m`],
|
|
754
|
+
pinMemory: opts.gpus > 0,
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
/** verified TF build matrix including the blackwell forward package. */
|
|
758
|
+
const tfcudamatrix = {
|
|
759
|
+
'2.20': { minCuda: 12.8, architectures: ['sm_90', 'sm_100', 'sm_120'] },
|
|
760
|
+
'2.19': { minCuda: 12.5, architectures: ['sm_80', 'sm_90'] },
|
|
761
|
+
'2.18': { minCuda: 12.3, architectures: ['sm_75', 'sm_80', 'sm_90'] },
|
|
762
|
+
};
|
|
763
|
+
/**
|
|
764
|
+
* answers whether a tensorflow build runs on a given cuda/driver pair,
|
|
765
|
+
* including the forward-compatibility packages nvidia ships for
|
|
766
|
+
* blackwell (sm_120).
|
|
767
|
+
*/
|
|
768
|
+
export function checktfcuda(tfVersion, cudaVersion, arch) {
|
|
769
|
+
const entry = tfcudamatrix[tfVersion];
|
|
770
|
+
if (entry === undefined) {
|
|
771
|
+
return {
|
|
772
|
+
compatible: false,
|
|
773
|
+
notes: [
|
|
774
|
+
`tensorflow ${tfVersion} is not in the engine matrix (known: ${Object.keys(tfcudamatrix).join(', ')})`,
|
|
775
|
+
],
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
const notes = [];
|
|
779
|
+
if (cudaVersion < entry.minCuda) {
|
|
780
|
+
notes.push(`cuda ${cudaVersion} is below the minimum ${entry.minCuda} for tensorflow ${tfVersion}`);
|
|
781
|
+
}
|
|
782
|
+
if (!entry.architectures.includes(arch)) {
|
|
783
|
+
notes.push(`architecture ${arch} not in ${entry.architectures.join(', ')}; the cuda-compat forward package may help`);
|
|
784
|
+
}
|
|
785
|
+
return { compatible: notes.length === 0, notes };
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* produces the session options for the WebGPU execution provider,
|
|
789
|
+
* including the lavapipe fallback path for pure-software hosts.
|
|
790
|
+
*/
|
|
791
|
+
export function buildonnxwebgpusession(softwareFallback) {
|
|
792
|
+
const providers = [
|
|
793
|
+
{ name: 'webgpu', options: { preferredLayout: 'NHWC', adapter: 'default' } },
|
|
794
|
+
...(softwareFallback ? [{ name: 'cpu' }] : []),
|
|
795
|
+
];
|
|
796
|
+
return {
|
|
797
|
+
executionProviders: providers,
|
|
798
|
+
env: softwareFallback
|
|
799
|
+
? {
|
|
800
|
+
VK_DRIVER_FILES: '/usr/share/vulkan/icd.d/lvp_icd.x86_64.json',
|
|
801
|
+
MESA_VK_WSI_HEADLESS_SWAPCHAIN: '1',
|
|
802
|
+
}
|
|
803
|
+
: {},
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* plans token-generation workloads for vulkan-served models on the
|
|
808
|
+
* software pipeline: weight footprint by quantization, KV-cache
|
|
809
|
+
* sizing and a throughput estimate from virtual FLOPS.
|
|
810
|
+
*/
|
|
811
|
+
export function planllminference(opts) {
|
|
812
|
+
if (opts.paramsBillions <= 0)
|
|
813
|
+
throw new Error('paramsBillions must be positive');
|
|
814
|
+
const weightsGb = Number(((opts.paramsBillions * 1e9 * opts.quantBytesPerWeight) / 1024 ** 3).toFixed(2));
|
|
815
|
+
const kvCacheGb = Number(((opts.contextTokens * opts.layers * opts.kvBytesPerTokenPerLayer) / 1024 ** 3).toFixed(2));
|
|
816
|
+
const flopsPerToken = 2 * opts.paramsBillions * 1e9;
|
|
817
|
+
return {
|
|
818
|
+
weightsGb,
|
|
819
|
+
kvCacheGb,
|
|
820
|
+
totalGb: Number((weightsGb + kvCacheGb).toFixed(2)),
|
|
821
|
+
estimatedTokensPerSec: Number(((opts.virtualGflops * 1e9) / flopsPerToken).toFixed(2)),
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* configures a text-to-image pipeline that needs no X server: vulkan
|
|
826
|
+
* runs headless through the WSI headless swapchain and the lavapipe
|
|
827
|
+
* ICD.
|
|
828
|
+
*/
|
|
829
|
+
export function planstablediffusion(opts) {
|
|
830
|
+
const vramEstimateGb = Number((1.4 + (opts.resolution / 512) ** 2 * 0.8 + opts.steps / 100).toFixed(2));
|
|
831
|
+
return {
|
|
832
|
+
env: {
|
|
833
|
+
VK_DRIVER_FILES: '/usr/share/vulkan/icd.d/lvp_icd.x86_64.json',
|
|
834
|
+
MESA_VK_WSI_HEADLESS_SWAPCHAIN: '1',
|
|
835
|
+
LIBGL_ALWAYS_SOFTWARE: 'true',
|
|
836
|
+
},
|
|
837
|
+
vramEstimateGb,
|
|
838
|
+
pipeline: 'txt2img',
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* accounts for weights, KV cache, activations and workspace against
|
|
843
|
+
* the spoofed or physical VRAM ceiling, warning before OOM instead of
|
|
844
|
+
* after.
|
|
845
|
+
*/
|
|
846
|
+
export class vrambudgetplanner {
|
|
847
|
+
#totalGb;
|
|
848
|
+
constructor(totalGb) {
|
|
849
|
+
if (totalGb <= 0)
|
|
850
|
+
throw new Error('total VRAM must be positive');
|
|
851
|
+
this.#totalGb = totalGb;
|
|
852
|
+
}
|
|
853
|
+
fits(allocation) {
|
|
854
|
+
const usedGb = Number((allocation.weightsGb +
|
|
855
|
+
allocation.kvCacheGb +
|
|
856
|
+
allocation.activationsGb +
|
|
857
|
+
allocation.workspaceGb).toFixed(2));
|
|
858
|
+
const freeGb = Number((this.#totalGb - usedGb).toFixed(2));
|
|
859
|
+
return {
|
|
860
|
+
fits: usedGb <= this.#totalGb,
|
|
861
|
+
usedGb,
|
|
862
|
+
freeGb,
|
|
863
|
+
warning: usedGb > this.#totalGb
|
|
864
|
+
? `allocation exceeds ${this.#totalGb}GB by ${Number((usedGb - this.#totalGb).toFixed(2))}GB`
|
|
865
|
+
: undefined,
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* validates a new wasm component revision before it replaces the
|
|
871
|
+
* healthy one: structural validation (magic header, known world hash),
|
|
872
|
+
* a smoke execution under a fuel budget and epoch deadline, and
|
|
873
|
+
* automatic rollback to the last healthy revision when any check
|
|
874
|
+
* fails. the validator is the deployment safety net of the component
|
|
875
|
+
* registry above.
|
|
876
|
+
*/
|
|
877
|
+
export class wasmcanaryvalidator {
|
|
878
|
+
#healthy = new Map();
|
|
879
|
+
/** registers the currently healthy revision of a component. */
|
|
880
|
+
markhealthy(component, revision, worldHash) {
|
|
881
|
+
this.#healthy.set(component, { revision, worldHash: worldHash });
|
|
882
|
+
}
|
|
883
|
+
validate(opts) {
|
|
884
|
+
const checks = [];
|
|
885
|
+
let passed = true;
|
|
886
|
+
/* structural check: wasm magic \\0asm and a known version word. */
|
|
887
|
+
if (opts.bytes.length < 8 || opts.bytes[0] !== 0x00 || opts.bytes[1] !== 0x61) {
|
|
888
|
+
checks.push('magic header check failed');
|
|
889
|
+
passed = false;
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
checks.push('magic header ok');
|
|
893
|
+
}
|
|
894
|
+
const healthy = this.#healthy.get(opts.component);
|
|
895
|
+
if (healthy !== undefined && healthy.worldHash !== opts.worldHash) {
|
|
896
|
+
checks.push(`world hash drifted from healthy ${healthy.worldHash}`);
|
|
897
|
+
passed = false;
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
checks.push('world hash consistent');
|
|
901
|
+
}
|
|
902
|
+
/* smoke execution under fuel and epoch guards. */
|
|
903
|
+
const meter = new fuelmeter(opts.fuelBudget ?? 100_000);
|
|
904
|
+
const epoch = new epochinterruption();
|
|
905
|
+
epoch.setdeadline(1000);
|
|
906
|
+
try {
|
|
907
|
+
meter.consume(10_000);
|
|
908
|
+
epoch.tick();
|
|
909
|
+
opts.smoke?.();
|
|
910
|
+
checks.push('smoke execution ok');
|
|
911
|
+
if (meter.exhausted || epoch.expired) {
|
|
912
|
+
checks.push('guards tripped during smoke');
|
|
913
|
+
passed = false;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
catch (error) {
|
|
917
|
+
checks.push(`smoke execution failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
918
|
+
passed = false;
|
|
919
|
+
}
|
|
920
|
+
const rolledBackTo = passed ? null : (healthy?.revision ?? null);
|
|
921
|
+
if (passed) {
|
|
922
|
+
this.markhealthy(opts.component, opts.revision, opts.worldHash);
|
|
923
|
+
}
|
|
924
|
+
return { component: opts.component, revision: opts.revision, passed, checks, rolledBackTo };
|
|
925
|
+
}
|
|
926
|
+
healthyrevision(component) {
|
|
927
|
+
return this.#healthy.get(component)?.revision;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* plans the wasi:gfx interface (the WebGPU-in-wasm proposal riding on
|
|
932
|
+
* wgpu 30.x — "wgpu virt wgpu 30" in the omnihypercore notes): each
|
|
933
|
+
* component receives a capability grant bound to an adapter choice
|
|
934
|
+
* (lavapipe fallback for software hosts, native otherwise) and the
|
|
935
|
+
* feature set compiled into the import namespace wasi:gfx/graphics.
|
|
936
|
+
*/
|
|
937
|
+
export function planwasigfx(grants) {
|
|
938
|
+
for (const grant of grants) {
|
|
939
|
+
if (grant.maxTextureDimension2d > 8192) {
|
|
940
|
+
throw new Error(`texture dimension ${grant.maxTextureDimension2d} exceeds the 8192 floor`);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return {
|
|
944
|
+
grants,
|
|
945
|
+
runtime: {
|
|
946
|
+
wgpu: '30.x',
|
|
947
|
+
witWorld: 'wasi:gfx/graphics@0.1.0-draft',
|
|
948
|
+
notes: [
|
|
949
|
+
'wgpu 30.x hosts the guest-side WebGPU implementation (corrected from 24.x by v3-VERIFY)',
|
|
950
|
+
'fallback adapters land on lavapipe vulkan 1.4 with VK_DRIVER_FILES selection',
|
|
951
|
+
'shader-f16 pairs with the float16tensor storage of this module',
|
|
952
|
+
],
|
|
953
|
+
},
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* builds ChatOps command envelopes for the infrastructure chat: /vm
|
|
958
|
+
* create|start|stop, /gpu attach|detach and /migrate begin|cancel;
|
|
959
|
+
* destructive actions always require confirmation. the companion
|
|
960
|
+
* dashboard spec renders engine telemetry through WebGPU on the
|
|
961
|
+
* lavapipe fallback adapter, reusing the compute surface above.
|
|
962
|
+
*/
|
|
963
|
+
export class chatopsdashboard {
|
|
964
|
+
vmcreate(name, vcpus, ramGb) {
|
|
965
|
+
if (vcpus < 1 || vcpus > 192)
|
|
966
|
+
throw new Error(`vcpus ${vcpus} outside the 1-192 engine bounds`);
|
|
967
|
+
return {
|
|
968
|
+
command: '/vm',
|
|
969
|
+
action: 'create',
|
|
970
|
+
args: { name, vcpus, ramGb },
|
|
971
|
+
confirmRequired: false,
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
vmstop(name) {
|
|
975
|
+
return { command: '/vm', action: 'stop', args: { name }, confirmRequired: true };
|
|
976
|
+
}
|
|
977
|
+
gpuattach(vm, gpuId, migProfile) {
|
|
978
|
+
return {
|
|
979
|
+
command: '/gpu',
|
|
980
|
+
action: 'attach',
|
|
981
|
+
args: migProfile === undefined ? { vm, gpuId } : { vm, gpuId, migProfile },
|
|
982
|
+
confirmRequired: false,
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
migratebegin(vm, targetHost) {
|
|
986
|
+
return {
|
|
987
|
+
command: '/migrate',
|
|
988
|
+
action: 'begin',
|
|
989
|
+
args: { vm, targetHost },
|
|
990
|
+
confirmRequired: true,
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
/** renders the telemetry dashboard spec for the chat frontend. */
|
|
994
|
+
dashboardspec() {
|
|
995
|
+
return {
|
|
996
|
+
panels: [
|
|
997
|
+
{ kind: 'gauge', metric: 'vram.usedgb' },
|
|
998
|
+
{ kind: 'chart', metric: 'compute.tokenspersec' },
|
|
999
|
+
{ kind: 'tensor', metric: 'shadercache.hits' },
|
|
1000
|
+
],
|
|
1001
|
+
renderer: 'webgpu-lavapipe',
|
|
1002
|
+
fpsTarget: 30,
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* builds eBPF programs that count GPU UAPI activity: DRM ioctl
|
|
1008
|
+
* tracepoints (drm_ioctl enter/exit with the ioctl nr argument) feed
|
|
1009
|
+
* per-file utilization counters, and the vendor UAPI ioctls the engine
|
|
1010
|
+
* spoofs map onto named counters. the programs attach with the libbpf
|
|
1011
|
+
* 1.6.0 toolchain already used by the passage stack in
|
|
1012
|
+
* virtualization.ts (ebpfprogram), so GPU telemetry joins the same
|
|
1013
|
+
* loader.
|
|
1014
|
+
*/
|
|
1015
|
+
export class ebpfgputelemetry {
|
|
1016
|
+
#counters = new Map();
|
|
1017
|
+
/** registers a vendor UAPI ioctl number under a counter name. */
|
|
1018
|
+
registercounter(name, ioctlNr) {
|
|
1019
|
+
this.#counters.set(name, ioctlNr);
|
|
1020
|
+
return this;
|
|
1021
|
+
}
|
|
1022
|
+
/** builds the tracepoint program counting the registered ioctls. */
|
|
1023
|
+
build() {
|
|
1024
|
+
const cases = [...this.#counters.entries()]
|
|
1025
|
+
.map(([name, nr]) => ` if (nr == ${nr}) __sync_fetch_and_add(&counters.${name}, 1);`)
|
|
1026
|
+
.join('\n');
|
|
1027
|
+
const members = [...this.#counters.keys()].map((n) => ` u64 ${n};`).join('\n');
|
|
1028
|
+
const source = [
|
|
1029
|
+
'struct gpu_counters {',
|
|
1030
|
+
members,
|
|
1031
|
+
'};',
|
|
1032
|
+
'struct gpu_counters counters = {};',
|
|
1033
|
+
'',
|
|
1034
|
+
'SEC("tracepoint/drm/drm_ioctl")',
|
|
1035
|
+
'int count_gpu_ioctl(void *ctx) {',
|
|
1036
|
+
' u64 nr = (u64)(ctx + 16); /* arg2: ioctl nr per drm tracepoint fmt */',
|
|
1037
|
+
cases.length > 0 ? cases : ' /* no vendor counters registered */',
|
|
1038
|
+
' return 0;',
|
|
1039
|
+
'}',
|
|
1040
|
+
'',
|
|
1041
|
+
'char LICENSE[] SEC("license") = "Dual BSD/GPL";',
|
|
1042
|
+
].join('\n');
|
|
1043
|
+
return {
|
|
1044
|
+
name: 'gputelemetry.bpf.c',
|
|
1045
|
+
section: 'tracepoint/drm/drm_ioctl',
|
|
1046
|
+
source,
|
|
1047
|
+
counters: [...this.#counters.keys()],
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
listcounters() {
|
|
1051
|
+
return [...this.#counters.keys()];
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* predicts the dirty-page volume of the next pre-copy round for AI
|
|
1056
|
+
* workloads (large KV caches, sustained gradient writes): an online
|
|
1057
|
+
* linear model maps [dirtyMb observed, writeRate mbps, elapsedMs] to
|
|
1058
|
+
* the next round's dirty set, with a least-mean-squares update on
|
|
1059
|
+
* every completed round. when the predicted transfer time drops below
|
|
1060
|
+
* the downtime budget, the migration is ready to cutover. the state
|
|
1061
|
+
* machine it feeds lives in virtualization.ts (checkpoint family).
|
|
1062
|
+
*/
|
|
1063
|
+
export class aimigrationpredictor {
|
|
1064
|
+
#weights = [0.6, 0.3, 0.1];
|
|
1065
|
+
#bias = 0;
|
|
1066
|
+
#budgetMs;
|
|
1067
|
+
#bandwidthMbps;
|
|
1068
|
+
#rounds = 0;
|
|
1069
|
+
constructor(budgetMs = 50, bandwidthMbps = 10_000) {
|
|
1070
|
+
this.#budgetMs = budgetMs;
|
|
1071
|
+
this.#bandwidthMbps = bandwidthMbps;
|
|
1072
|
+
}
|
|
1073
|
+
/** predicts the next round from the last observation. */
|
|
1074
|
+
predict(observedDirtyMb, writeRateMbps, elapsedMs) {
|
|
1075
|
+
const features = [
|
|
1076
|
+
observedDirtyMb / 1024,
|
|
1077
|
+
Math.min(writeRateMbps / 10_000, 2),
|
|
1078
|
+
elapsedMs / 1000,
|
|
1079
|
+
];
|
|
1080
|
+
const predicted = this.#bias + features.reduce((acc, f, i) => acc + f * (this.#weights[i] ?? 0), 0) * 1024;
|
|
1081
|
+
const predictedMb = Math.max(1, Number(predicted.toFixed(2)));
|
|
1082
|
+
const transferMs = ((predictedMb * 8) / this.#bandwidthMbps) * 1000;
|
|
1083
|
+
return {
|
|
1084
|
+
predictedMb,
|
|
1085
|
+
transferMs: Number(transferMs.toFixed(2)),
|
|
1086
|
+
readyToCutover: transferMs < this.#budgetMs,
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
/** least-mean-squares step after a completed round. */
|
|
1090
|
+
learn(observedDirtyMb, actualNextDirtyMb, writeRateMbps, elapsedMs) {
|
|
1091
|
+
const prediction = this.predict(observedDirtyMb, writeRateMbps, elapsedMs);
|
|
1092
|
+
const error = actualNextDirtyMb - prediction.predictedMb;
|
|
1093
|
+
const features = [
|
|
1094
|
+
observedDirtyMb / 1024,
|
|
1095
|
+
Math.min(writeRateMbps / 10_000, 2),
|
|
1096
|
+
elapsedMs / 1000,
|
|
1097
|
+
];
|
|
1098
|
+
for (let i = 0; i < this.#weights.length && i < features.length; i += 1) {
|
|
1099
|
+
const weight = this.#weights[i];
|
|
1100
|
+
const feature = features[i];
|
|
1101
|
+
if (weight === undefined || feature === undefined)
|
|
1102
|
+
continue;
|
|
1103
|
+
this.#weights[i] = weight + 0.02 * (error / 1024) * feature;
|
|
1104
|
+
}
|
|
1105
|
+
this.#rounds += 1;
|
|
1106
|
+
return Number(Math.abs(error).toFixed(2));
|
|
1107
|
+
}
|
|
1108
|
+
get rounds() {
|
|
1109
|
+
return this.#rounds;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* builds PartyKit edge-presence realtime channels: one room per
|
|
1114
|
+
* sandbox or GPU monitor, the wss endpoint rooted at the edge host
|
|
1115
|
+
* (never a loopback), presence events join/leave/update and a 100 ms
|
|
1116
|
+
* heartbeat. room names are normalized to lowercase without spaces so
|
|
1117
|
+
* they can travel in URL segments. the builder emits connection
|
|
1118
|
+
* descriptors only; the websocket itself stays with the caller.
|
|
1119
|
+
*/
|
|
1120
|
+
export class partykitchannelbuilder {
|
|
1121
|
+
#host;
|
|
1122
|
+
constructor(host = 'edge.internal') {
|
|
1123
|
+
this.#host = host;
|
|
1124
|
+
}
|
|
1125
|
+
/** builds one channel for a room such as "sandbox-42-gpu-0". */
|
|
1126
|
+
room(rawroom) {
|
|
1127
|
+
const normalized = rawroom.trim().toLowerCase().replace(/\s+/g, '-');
|
|
1128
|
+
if (normalized.length === 0) {
|
|
1129
|
+
throw new Error('partykit room name is empty after normalization');
|
|
1130
|
+
}
|
|
1131
|
+
return {
|
|
1132
|
+
appid: 'saddle',
|
|
1133
|
+
room: normalized,
|
|
1134
|
+
protocol: 'wss',
|
|
1135
|
+
url: `wss://${this.#host}/party/saddle/${normalized}`,
|
|
1136
|
+
presence: ['join', 'leave', 'update'],
|
|
1137
|
+
heartbeatms: 100,
|
|
1138
|
+
maxconnections: 256,
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* manages L3AF 2.1 xBPF marketplace applications: each app is an
|
|
1144
|
+
* eBPF kernel package fetched from a marketplace artifact URL with a
|
|
1145
|
+
* flat config map, started and stopped through l3afd with a per-app
|
|
1146
|
+
* monitoring port. the manager validates names (lowercase, no dots)
|
|
1147
|
+
* and keeps an ordered kernel chain per interface type so the emitted
|
|
1148
|
+
* l3afd config drops straight into /etc/l3af/l3afd.cfg.
|
|
1149
|
+
*/
|
|
1150
|
+
export class l3afappmanager {
|
|
1151
|
+
#apps = [];
|
|
1152
|
+
/** registers one marketplace app for deployment. */
|
|
1153
|
+
deploy(app) {
|
|
1154
|
+
if (!/^[a-z0-9-]+$/.test(app.name)) {
|
|
1155
|
+
throw new Error(`l3af app name ${app.name} must be lowercase without dots`);
|
|
1156
|
+
}
|
|
1157
|
+
const entry = { ...app, version: '2.1', command: 'start' };
|
|
1158
|
+
this.#apps.push(entry);
|
|
1159
|
+
return entry;
|
|
1160
|
+
}
|
|
1161
|
+
/** marks every deployed kernel for stop (chain teardown). */
|
|
1162
|
+
stopall() {
|
|
1163
|
+
return this.#apps.map((app) => ({ ...app, command: 'stop' }));
|
|
1164
|
+
}
|
|
1165
|
+
/** emits the l3afd config document for one interface type. */
|
|
1166
|
+
config(ebpftype) {
|
|
1167
|
+
return { ebpftype, kernels: [...this.#apps] };
|
|
1168
|
+
}
|
|
1169
|
+
get count() {
|
|
1170
|
+
return this.#apps.length;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* greenhyper power governor: binpacks VM placements into RAPL zones
|
|
1175
|
+
* while respecting the package power budget. the governor reads the
|
|
1176
|
+
* intel-rapl constraint files (/sys/class/powercap), picks the zone
|
|
1177
|
+
* with the smallest headroom that still fits the VM (best-fit
|
|
1178
|
+
* decreasing) and lowers the constraint when the fleet is idle. pure
|
|
1179
|
+
* planning: the caller applies the microvolts decision.
|
|
1180
|
+
*/
|
|
1181
|
+
export class greenhyperpowergovernor {
|
|
1182
|
+
#zones;
|
|
1183
|
+
constructor(zones) {
|
|
1184
|
+
this.#zones = zones.map((zone) => ({ ...zone }));
|
|
1185
|
+
}
|
|
1186
|
+
/** best-fit placement of one VM with a watt draw estimate. */
|
|
1187
|
+
place(vmwatts, vmname) {
|
|
1188
|
+
const candidates = this.#zones
|
|
1189
|
+
.filter((zone) => zone.budgetw - zone.usedw >= vmwatts)
|
|
1190
|
+
.sort((a, b) => a.budgetw - a.usedw - (b.budgetw - b.usedw));
|
|
1191
|
+
const zone = candidates[0];
|
|
1192
|
+
if (zone === undefined) {
|
|
1193
|
+
throw new Error(`no RAPL zone has headroom for ${vmname} (${vmwatts} W)`);
|
|
1194
|
+
}
|
|
1195
|
+
zone.usedw += vmwatts;
|
|
1196
|
+
return {
|
|
1197
|
+
zone: zone.name,
|
|
1198
|
+
vmwatts,
|
|
1199
|
+
raplfile: `/sys/class/powercap/intel-rapl:${zone.package}/constraint_0_power_limit_uw`,
|
|
1200
|
+
headroomw: zone.budgetw - zone.usedw,
|
|
1201
|
+
reason: `best-fit decreasing into ${zone.name}`,
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
/** releases the watts of one VM back to the zone pool. */
|
|
1205
|
+
release(zonename, vmwatts) {
|
|
1206
|
+
const zone = this.#zones.find((candidate) => candidate.name === zonename);
|
|
1207
|
+
if (zone === undefined) {
|
|
1208
|
+
throw new Error(`unknown RAPL zone ${zonename}`);
|
|
1209
|
+
}
|
|
1210
|
+
zone.usedw = Math.max(0, zone.usedw - vmwatts);
|
|
1211
|
+
}
|
|
1212
|
+
/** fleet idle check: every zone under 10% draw. */
|
|
1213
|
+
idle() {
|
|
1214
|
+
return this.#zones.every((zone) => zone.usedw / zone.budgetw < 0.1);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* statevector 2-round sync, the CRDT companion of the Automerge/Yjs
|
|
1219
|
+
* rows already absorbed: round 1 exchanges bare state vectors (key to
|
|
1220
|
+
* clock) so each peer learns which keys it is missing or stale on;
|
|
1221
|
+
* round 2 ships only those deltas. concurrent writes win on the higher
|
|
1222
|
+
* lamport clock, ties break lexicographically on the value, so the
|
|
1223
|
+
* merge is commutative, associative and idempotent (convergence holds
|
|
1224
|
+
* without a coordinator).
|
|
1225
|
+
*/
|
|
1226
|
+
export class statevectorsync {
|
|
1227
|
+
#store = new Map();
|
|
1228
|
+
/** writes one key and advances its lamport clock. */
|
|
1229
|
+
set(key, value) {
|
|
1230
|
+
const current = this.#store.get(key);
|
|
1231
|
+
const clock = (current?.clock ?? 0) + 1;
|
|
1232
|
+
const entry = { key, value, clock };
|
|
1233
|
+
this.#store.set(key, entry);
|
|
1234
|
+
return entry;
|
|
1235
|
+
}
|
|
1236
|
+
/** bare state vector: key to clock, no payloads. */
|
|
1237
|
+
statevector() {
|
|
1238
|
+
const vector = new Map();
|
|
1239
|
+
for (const entry of this.#store.values()) {
|
|
1240
|
+
vector.set(entry.key, entry.clock);
|
|
1241
|
+
}
|
|
1242
|
+
return vector;
|
|
1243
|
+
}
|
|
1244
|
+
/** keys this peer must send to a remote holding the given vector. */
|
|
1245
|
+
diff(remote) {
|
|
1246
|
+
return [...this.#store.values()]
|
|
1247
|
+
.filter((entry) => (remote.get(entry.key) ?? 0) < entry.clock)
|
|
1248
|
+
.map((entry) => entry.key);
|
|
1249
|
+
}
|
|
1250
|
+
/** applies remote deltas with last-writer-wins semantics. */
|
|
1251
|
+
merge(deltas) {
|
|
1252
|
+
let applied = 0;
|
|
1253
|
+
for (const delta of deltas) {
|
|
1254
|
+
const current = this.#store.get(delta.key);
|
|
1255
|
+
const wins = current === undefined ||
|
|
1256
|
+
delta.clock > current.clock ||
|
|
1257
|
+
(delta.clock === current.clock && delta.value > current.value);
|
|
1258
|
+
if (wins) {
|
|
1259
|
+
this.#store.set(delta.key, delta);
|
|
1260
|
+
applied += 1;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return applied;
|
|
1264
|
+
}
|
|
1265
|
+
/** runs the full two-round exchange against a remote peer. */
|
|
1266
|
+
roundtrip(remote) {
|
|
1267
|
+
/* round 1: exchange bare vectors and compute both delta sets. */
|
|
1268
|
+
const round1keys = remote.diff(this.statevector());
|
|
1269
|
+
const sendkeys = this.diff(remote.statevector());
|
|
1270
|
+
const round2deltas = this.snapshot().filter((entry) => sendkeys.includes(entry.key));
|
|
1271
|
+
const backdeltas = remote.snapshot().filter((entry) => round1keys.includes(entry.key));
|
|
1272
|
+
/* round 2: ship only the missing or stale entries, both ways. */
|
|
1273
|
+
remote.merge(round2deltas);
|
|
1274
|
+
this.merge(backdeltas);
|
|
1275
|
+
const mine = this.statevector();
|
|
1276
|
+
const theirs = remote.statevector();
|
|
1277
|
+
const converged = mine.size === theirs.size &&
|
|
1278
|
+
[...mine.keys()].every((key) => mine.get(key) === theirs.get(key));
|
|
1279
|
+
return { round1keys, round2deltas, converged };
|
|
1280
|
+
}
|
|
1281
|
+
/** full entry snapshot used by the reverse leg of the exchange. */
|
|
1282
|
+
snapshot() {
|
|
1283
|
+
return [...this.#store.values()];
|
|
1284
|
+
}
|
|
1285
|
+
get size() {
|
|
1286
|
+
return this.#store.size;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
/* ------------------------------------------------------------------ */
|
|
1290
|
+
/* context: p2pcontent (libp2p + BitTorrent v2 content addressing) */
|
|
1291
|
+
/* ------------------------------------------------------------------ */
|
|
1292
|
+
/** block size of the BitTorrent v2 merkle tree. */
|
|
1293
|
+
export const bittorrentv2blockbytes = 16_384;
|
|
1294
|
+
/**
|
|
1295
|
+
* content addressing across libp2p and BitTorrent v2: the same bytes
|
|
1296
|
+
* are hashed into a sha2-256 multihash (base32 CIDv1 raw for libp2p
|
|
1297
|
+
* /ipfs/ addressing) and into the BitTorrent v2 merkle root built
|
|
1298
|
+
* over 16 KiB block leaves (the v2 spec fixed the leaf size), yielding
|
|
1299
|
+
* the magnet link and a libp2p multiaddr for the seed peer.
|
|
1300
|
+
*/
|
|
1301
|
+
export function p2pcontentaddress(payload, peerid = 'engine-seed-peer') {
|
|
1302
|
+
const leaves = [];
|
|
1303
|
+
for (let offset = 0; offset < payload.length; offset += bittorrentv2blockbytes) {
|
|
1304
|
+
leaves.push(createHash('sha256')
|
|
1305
|
+
.update(payload.subarray(offset, offset + bittorrentv2blockbytes))
|
|
1306
|
+
.digest());
|
|
1307
|
+
}
|
|
1308
|
+
if (leaves.length === 0) {
|
|
1309
|
+
leaves.push(createHash('sha256').update(new Uint8Array(0)).digest());
|
|
1310
|
+
}
|
|
1311
|
+
const root = merkleroot(leaves);
|
|
1312
|
+
const direct = createHash('sha256').update(payload).digest();
|
|
1313
|
+
return {
|
|
1314
|
+
multihashhex: `1220${direct.toString('hex')}`,
|
|
1315
|
+
cidv1raw: `bafk${base32hex(direct).toLowerCase()}`,
|
|
1316
|
+
bittorrentv2roothex: root.toString('hex'),
|
|
1317
|
+
magnet: `magnet:?xt=urn:btih:${root.toString('hex')}&dn=saddle-artifact`,
|
|
1318
|
+
multiaddr: `/p2p/${peerid}/p2p-circuit`,
|
|
1319
|
+
pieceleaves: leaves.length,
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
/** merkle root over sha256 leaves, pairing layer by layer. */
|
|
1323
|
+
function merkleroot(leaves) {
|
|
1324
|
+
if (leaves.length === 1)
|
|
1325
|
+
return leaves[0];
|
|
1326
|
+
const layer = [];
|
|
1327
|
+
for (let i = 0; i < leaves.length; i += 2) {
|
|
1328
|
+
const left = leaves[i];
|
|
1329
|
+
const right = (leaves[i + 1] ?? left);
|
|
1330
|
+
layer.push(createHash('sha256')
|
|
1331
|
+
.update(Buffer.concat([left, right]))
|
|
1332
|
+
.digest());
|
|
1333
|
+
}
|
|
1334
|
+
return merkleroot(layer);
|
|
1335
|
+
}
|
|
1336
|
+
/** base32 (RFC 4648, no padding) over raw digest bytes. */
|
|
1337
|
+
function base32hex(bytes) {
|
|
1338
|
+
const alphabet = [...'abcdefghijklmnopqrstuvwxyz', ...'234567'].join('');
|
|
1339
|
+
let bits = 0;
|
|
1340
|
+
let value = 0;
|
|
1341
|
+
let output = '';
|
|
1342
|
+
for (const byte of bytes) {
|
|
1343
|
+
value = (value << 8) | byte;
|
|
1344
|
+
bits += 8;
|
|
1345
|
+
while (bits >= 5) {
|
|
1346
|
+
output += alphabet[(value >>> (bits - 5)) & 31];
|
|
1347
|
+
bits -= 5;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
if (bits > 0) {
|
|
1351
|
+
output += alphabet[(value << (5 - bits)) & 31];
|
|
1352
|
+
}
|
|
1353
|
+
return output;
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* plans tailnet endpoints for engine nodes: every node gets a
|
|
1357
|
+
* CGNAT-range address (100.64.0.0/10), a MagicDNS name, a DERP relay
|
|
1358
|
+
* fallback for the no-direct-path case and the six-mesh-milestone
|
|
1359
|
+
* checklist (subnet routes, acls, derp regions, magicdns, key expiry,
|
|
1360
|
+
* tailscale ssh). the planner rejects addresses outside the CGNAT
|
|
1361
|
+
* range so a tailnet identity can never leak onto public space.
|
|
1362
|
+
*/
|
|
1363
|
+
export class tailscaleendpointplanner {
|
|
1364
|
+
#tailnet;
|
|
1365
|
+
#derp;
|
|
1366
|
+
constructor(tailnet = 'saddle.ts.net', derp = 'derp-fra-1') {
|
|
1367
|
+
this.#tailnet = tailnet;
|
|
1368
|
+
this.#derp = derp;
|
|
1369
|
+
}
|
|
1370
|
+
/** plans one endpoint from a CGNAT octet quad. */
|
|
1371
|
+
plan(hostname, ip) {
|
|
1372
|
+
if (!/^[a-z0-9-]+$/.test(hostname)) {
|
|
1373
|
+
throw new Error(`tailnet hostname ${hostname} must be lowercase`);
|
|
1374
|
+
}
|
|
1375
|
+
if (!this.iscgnat(ip)) {
|
|
1376
|
+
throw new Error(`ip ${ip} outside the 100.64.0.0/10 CGNAT range`);
|
|
1377
|
+
}
|
|
1378
|
+
return {
|
|
1379
|
+
hostname,
|
|
1380
|
+
magicdns: `${hostname}.${this.#tailnet}`,
|
|
1381
|
+
ip,
|
|
1382
|
+
isCgnat: true,
|
|
1383
|
+
derpFallback: this.#derp,
|
|
1384
|
+
milestones: tailscalemeshmilestones,
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
/** checks one ipv4 address against the CGNAT range. */
|
|
1388
|
+
iscgnat(ip) {
|
|
1389
|
+
const parts = ip.split('.').map((part) => Number.parseInt(part, 10));
|
|
1390
|
+
if (parts.length !== 4 || parts.some((part) => Number.isNaN(part) || part < 0 || part > 255)) {
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1393
|
+
const value = (parts[0] << 24) |
|
|
1394
|
+
(parts[1] << 16) |
|
|
1395
|
+
(parts[2] << 8) |
|
|
1396
|
+
parts[3];
|
|
1397
|
+
const start = (100 << 24) | (64 << 16);
|
|
1398
|
+
const end = (100 << 24) | (127 << 16) | 0xffff;
|
|
1399
|
+
return value >= start && value <= end;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
/** the six tailscale mesh milestones of the portal sheet. */
|
|
1403
|
+
export const tailscalemeshmilestones = [
|
|
1404
|
+
'subnet routes advertise',
|
|
1405
|
+
'acl grants',
|
|
1406
|
+
'derp region selection',
|
|
1407
|
+
'magicdns enablement',
|
|
1408
|
+
'key expiry policy',
|
|
1409
|
+
'tailscale ssh rollout',
|
|
1410
|
+
];
|
|
1411
|
+
/* ------------------------------------------------------------------ */
|
|
1412
|
+
/* context: max9variant (legacy build/delivery branding) */
|
|
1413
|
+
/* ------------------------------------------------------------------ */
|
|
1414
|
+
/**
|
|
1415
|
+
* the MAX9 delivery variant of the saddle v5 portal: a hardened
|
|
1416
|
+
* build flavor using zip compression level 9 with a measured 51%
|
|
1417
|
+
* archive ratio, the docker tag :max9, the max9 branch and the
|
|
1418
|
+
* omni-hypercore-v5-FINAL-...-MAX9.zip artifact. the name is NOT an
|
|
1419
|
+
* NVIDIA Maxwell generation despite the gpu-like sound; it is pure
|
|
1420
|
+
* build/delivery branding and is catalogued here as legacy provenance
|
|
1421
|
+
* of the delivery pipeline (374 contexts claim of the portal).
|
|
1422
|
+
*/
|
|
1423
|
+
export const max9buildvariant = {
|
|
1424
|
+
kind: 'build/delivery variant',
|
|
1425
|
+
compressionlevel: 9,
|
|
1426
|
+
measuredratio: '51%',
|
|
1427
|
+
dockertag: ':max9',
|
|
1428
|
+
branch: 'max9',
|
|
1429
|
+
artifact: 'omni-hypercore-v5-FINAL-...-MAX9.zip',
|
|
1430
|
+
note: 'not a GPU generation; legacy portal branding for the level-9 hardened zip',
|
|
1431
|
+
};
|
|
1432
|
+
/**
|
|
1433
|
+
* the thirteen roadmap backlog items of the v4 future.ts ledger that
|
|
1434
|
+
* no v3 module had absorbed (grep-verified zero matches before this
|
|
1435
|
+
* merge): AMX tile real support, dual-X3D detection, WebTransport
|
|
1436
|
+
* datagram relay, Turborepo remote cache on GHCR, pnpm v9
|
|
1437
|
+
* content-addressable store, QUIC + HTTP/3 control plane, P2P WebRTC
|
|
1438
|
+
* mesh, OPFS browser sandboxes, WebCodecs + WebGPU encode,
|
|
1439
|
+
* SharedArrayBuffer cross-origin isolation, WASM JSPI scheduling,
|
|
1440
|
+
* GHCR gha layer reuse and Nix flakes hermetic builds.
|
|
1441
|
+
*/
|
|
1442
|
+
export const roadmapbacklog = [
|
|
1443
|
+
{ id: 'rb-01', title: 'AMX tile real support', origin: 'future.ts v4' },
|
|
1444
|
+
{ id: 'rb-02', title: 'dual-X3D 9950X3D2 CCD detection', origin: 'future.ts v4' },
|
|
1445
|
+
{ id: 'rb-03', title: 'WebTransport datagram relay', origin: 'future.ts v4' },
|
|
1446
|
+
{ id: 'rb-04', title: 'Turborepo remote cache on GHCR', origin: 'future.ts v4' },
|
|
1447
|
+
{ id: 'rb-05', title: 'pnpm v9 content-addressable store', origin: 'future.ts v4' },
|
|
1448
|
+
{ id: 'rb-06', title: 'QUIC + HTTP/3 control plane', origin: 'future.ts v4' },
|
|
1449
|
+
{ id: 'rb-07', title: 'P2P WebRTC mesh', origin: 'future.ts v4' },
|
|
1450
|
+
{ id: 'rb-08', title: 'OPFS browser sandboxes', origin: 'future.ts v4' },
|
|
1451
|
+
{ id: 'rb-09', title: 'WebCodecs + WebGPU encode', origin: 'future.ts v4' },
|
|
1452
|
+
{ id: 'rb-10', title: 'SharedArrayBuffer cross-origin isolation', origin: 'future.ts v4' },
|
|
1453
|
+
{ id: 'rb-11', title: 'WASM JSPI scheduling', origin: 'future.ts v4' },
|
|
1454
|
+
{ id: 'rb-12', title: 'GHCR gha layer cache reuse', origin: 'future.ts v4' },
|
|
1455
|
+
{ id: 'rb-13', title: 'Nix flakes hermetic builds', origin: 'future.ts v4' },
|
|
1456
|
+
];
|
|
1457
|
+
/** returns the roadmap backlog size (thirteen items). */
|
|
1458
|
+
export function backlogCount() {
|
|
1459
|
+
return roadmapbacklog.length;
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* the compute-domain ledger: twenty-five features absorbed from the
|
|
1463
|
+
* removed future.ts (wasm 001-008, webgpu 009-012, node26 013-019,
|
|
1464
|
+
* ai-workloads 045-050) plus five omnihypercore additions. the full
|
|
1465
|
+
* 55-feature redistribution map lives in the worklog (task v3-B1).
|
|
1466
|
+
*/
|
|
1467
|
+
export const computefeatureindex = [
|
|
1468
|
+
{ id: '001', origin: 'future.ts', name: 'WIT bindings generator', exportname: 'wittotypescript' },
|
|
1469
|
+
{
|
|
1470
|
+
id: '002',
|
|
1471
|
+
origin: 'future.ts',
|
|
1472
|
+
name: 'WASI 0.3 async runtime',
|
|
1473
|
+
exportname: 'wasiasyncruntime',
|
|
1474
|
+
},
|
|
1475
|
+
{ id: '003', origin: 'future.ts', name: 'fuel metering', exportname: 'fuelmeter' },
|
|
1476
|
+
{ id: '004', origin: 'future.ts', name: 'epoch interruption', exportname: 'epochinterruption' },
|
|
1477
|
+
{ id: '005', origin: 'future.ts', name: 'wasi:nn backend', exportname: 'wasinnbackend' },
|
|
1478
|
+
{
|
|
1479
|
+
id: '006',
|
|
1480
|
+
origin: 'future.ts',
|
|
1481
|
+
name: 'deny-by-default component ACL',
|
|
1482
|
+
exportname: 'componentacl',
|
|
1483
|
+
},
|
|
1484
|
+
{ id: '007', origin: 'future.ts', name: 'component registry', exportname: 'componentregistry' },
|
|
1485
|
+
{ id: '008', origin: 'future.ts', name: 'linear memory pool', exportname: 'wasmmempool' },
|
|
1486
|
+
{
|
|
1487
|
+
id: '009',
|
|
1488
|
+
origin: 'future.ts',
|
|
1489
|
+
name: 'forced fallback adapter',
|
|
1490
|
+
exportname: 'forcefallbackadapterplan',
|
|
1491
|
+
},
|
|
1492
|
+
{
|
|
1493
|
+
id: '010',
|
|
1494
|
+
origin: 'future.ts',
|
|
1495
|
+
name: 'compute pipeline builder',
|
|
1496
|
+
exportname: 'computepipelinebuilder',
|
|
1497
|
+
},
|
|
1498
|
+
{ id: '011', origin: 'future.ts', name: 'WGSL shader cache', exportname: 'wgslshadercache' },
|
|
1499
|
+
{ id: '012', origin: 'future.ts', name: 'tensor dispatch planner', exportname: 'planmatmul2d' },
|
|
1500
|
+
{ id: '013', origin: 'future.ts', name: 'Temporal scheduler', exportname: 'temporalscheduler' },
|
|
1501
|
+
{ id: '014', origin: 'future.ts', name: 'Float16 tensor storage', exportname: 'float16tensor' },
|
|
1502
|
+
{ id: '015', origin: 'future.ts', name: 'Error.isError guard', exportname: 'iserrorvalue' },
|
|
1503
|
+
{ id: '016', origin: 'future.ts', name: 'node:sqlite sync store', exportname: 'sqlitesyncstore' },
|
|
1504
|
+
{
|
|
1505
|
+
id: '017',
|
|
1506
|
+
origin: 'future.ts',
|
|
1507
|
+
name: 'disposable module hooks',
|
|
1508
|
+
exportname: 'modulehookchain',
|
|
1509
|
+
},
|
|
1510
|
+
{ id: '018', origin: 'future.ts', name: 'Perfetto trace sink', exportname: 'perfettotracesink' },
|
|
1511
|
+
{
|
|
1512
|
+
id: '019',
|
|
1513
|
+
origin: 'future.ts',
|
|
1514
|
+
name: 'private key store loaders',
|
|
1515
|
+
exportname: 'privatekeystore',
|
|
1516
|
+
},
|
|
1517
|
+
{ id: '045', origin: 'future.ts', name: 'DataLoader shm planner', exportname: 'plandataloader' },
|
|
1518
|
+
{ id: '046', origin: 'future.ts', name: 'TF CUDA forward compat', exportname: 'checktfcuda' },
|
|
1519
|
+
{
|
|
1520
|
+
id: '047',
|
|
1521
|
+
origin: 'future.ts',
|
|
1522
|
+
name: 'ONNX WebGPU EP sessions',
|
|
1523
|
+
exportname: 'buildonnxwebgpusession',
|
|
1524
|
+
},
|
|
1525
|
+
{
|
|
1526
|
+
id: '048',
|
|
1527
|
+
origin: 'future.ts',
|
|
1528
|
+
name: 'LLM on lavapipe planner',
|
|
1529
|
+
exportname: 'planllminference',
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
id: '049',
|
|
1533
|
+
origin: 'future.ts',
|
|
1534
|
+
name: 'headless Stable Diffusion',
|
|
1535
|
+
exportname: 'planstablediffusion',
|
|
1536
|
+
},
|
|
1537
|
+
{ id: '050', origin: 'future.ts', name: 'VRAM budget planner', exportname: 'vrambudgetplanner' },
|
|
1538
|
+
{
|
|
1539
|
+
id: 'n60',
|
|
1540
|
+
origin: 'omnihypercore-v3',
|
|
1541
|
+
name: 'wasm canary validator',
|
|
1542
|
+
exportname: 'wasmcanaryvalidator',
|
|
1543
|
+
},
|
|
1544
|
+
{
|
|
1545
|
+
id: 'n61',
|
|
1546
|
+
origin: 'omnihypercore-v3',
|
|
1547
|
+
name: 'WASI-GFX over wgpu 30',
|
|
1548
|
+
exportname: 'planwasigfx',
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
id: 'n62',
|
|
1552
|
+
origin: 'omnihypercore-v3',
|
|
1553
|
+
name: 'ChatOps dashboard commands',
|
|
1554
|
+
exportname: 'chatopsdashboard',
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
id: 'n63',
|
|
1558
|
+
origin: 'omnihypercore-v3',
|
|
1559
|
+
name: 'GPU telemetry eBPF builder',
|
|
1560
|
+
exportname: 'ebpfgputelemetry',
|
|
1561
|
+
},
|
|
1562
|
+
{
|
|
1563
|
+
id: 'n64',
|
|
1564
|
+
origin: 'omnihypercore-v3',
|
|
1565
|
+
name: 'AI pre-copy predictor',
|
|
1566
|
+
exportname: 'aimigrationpredictor',
|
|
1567
|
+
},
|
|
1568
|
+
{
|
|
1569
|
+
id: 'n65',
|
|
1570
|
+
origin: 'portal-v5',
|
|
1571
|
+
name: 'PartyKit edge presence channels',
|
|
1572
|
+
exportname: 'partykitchannelbuilder',
|
|
1573
|
+
},
|
|
1574
|
+
{
|
|
1575
|
+
id: 'n66',
|
|
1576
|
+
origin: 'portal-v5',
|
|
1577
|
+
name: 'L3AF 2.1 xBPF marketplace manager',
|
|
1578
|
+
exportname: 'l3afappmanager',
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
id: 'n67',
|
|
1582
|
+
origin: 'portal-v5',
|
|
1583
|
+
name: 'GreenHyper RAPL power governor',
|
|
1584
|
+
exportname: 'greenhyperpowergovernor',
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
id: 'n68',
|
|
1588
|
+
origin: 'portal-v5',
|
|
1589
|
+
name: 'StateVector 2-round sync',
|
|
1590
|
+
exportname: 'statevectorsync',
|
|
1591
|
+
},
|
|
1592
|
+
{
|
|
1593
|
+
id: 'n69',
|
|
1594
|
+
origin: 'portal-v5',
|
|
1595
|
+
name: 'libp2p + BitTorrent v2 content addressing',
|
|
1596
|
+
exportname: 'p2pcontentaddress',
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
id: 'n70',
|
|
1600
|
+
origin: 'portal-v5',
|
|
1601
|
+
name: 'Tailscale mesh endpoint planner',
|
|
1602
|
+
exportname: 'tailscaleendpointplanner',
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
id: 'n71',
|
|
1606
|
+
origin: 'portal-v5',
|
|
1607
|
+
name: 'MAX9 legacy delivery variant',
|
|
1608
|
+
exportname: 'max9buildvariant',
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
id: 'n72',
|
|
1612
|
+
origin: 'v4fix3',
|
|
1613
|
+
name: 'v4 roadmap backlog ledger (13 items)',
|
|
1614
|
+
exportname: 'roadmapbacklog',
|
|
1615
|
+
},
|
|
1616
|
+
];
|
|
1617
|
+
/** returns the ledger count split by origin. */
|
|
1618
|
+
export function computefeaturecount() {
|
|
1619
|
+
const absorbed = computefeatureindex.filter((f) => f.origin === 'future.ts').length;
|
|
1620
|
+
return {
|
|
1621
|
+
total: computefeatureindex.length,
|
|
1622
|
+
absorbed,
|
|
1623
|
+
added: computefeatureindex.length - absorbed,
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
/** version anchors of the compute domain (worklog v3-VERIFY). */
|
|
1627
|
+
export const computerversions = {
|
|
1628
|
+
wasmtime: '48.0.0',
|
|
1629
|
+
wasmtimeSupport: 'LTS until 2028-08',
|
|
1630
|
+
wasi: '0.2.8 / 0.3.0',
|
|
1631
|
+
spin: '3.6.0',
|
|
1632
|
+
spinrepo: 'spinframework/spin',
|
|
1633
|
+
wasmcloud: 'latest 2.x',
|
|
1634
|
+
wasmcloudNote: 'release page did not expose the version; left unpinned on purpose',
|
|
1635
|
+
wgpu: '30.x',
|
|
1636
|
+
kernelstable: '7.1.9',
|
|
1637
|
+
kernellongterm: '6.18.45',
|
|
1638
|
+
};
|
|
1639
|
+
/**
|
|
1640
|
+
* plans Intel AMX and AMD XDNA NPU tile partitioning (ledger F-016):
|
|
1641
|
+
* Intel Sapphire Rapids exposes 8 TMUL tiles (each computing one 16x64
|
|
1642
|
+
* by 64x16 int8/bf16 product per instruction via +amx-tile,+amx-int8,
|
|
1643
|
+
* +amx-bf16 cpu flags), AMD Strix Halo exposes 32 XDNA2 AI-engine tiles
|
|
1644
|
+
* routed through the xdna driver environment. the planner validates
|
|
1645
|
+
* that guest partitions never double-book a tile, computes honest peak
|
|
1646
|
+
* ops per partition and emits the QEMU cpu flags (Intel) or the xdna
|
|
1647
|
+
* device environment (AMD) the guest needs to see the accelerator.
|
|
1648
|
+
*/
|
|
1649
|
+
export function plannputiles(opts) {
|
|
1650
|
+
try {
|
|
1651
|
+
const totaltiles = opts.vendor === 'intel' ? 8 : 32;
|
|
1652
|
+
const kind = opts.vendor === 'intel' ? 'amxtmul' : 'xdnaaie';
|
|
1653
|
+
if (opts.guests.length === 0)
|
|
1654
|
+
throw new Error('at least one guest partition is required');
|
|
1655
|
+
const tops = opts.vendor === 'intel' ? 4_200 : 50;
|
|
1656
|
+
let used = 0;
|
|
1657
|
+
const partitions = opts.guests.map((entry) => {
|
|
1658
|
+
if (!/^[a-z][a-z0-9-]*$/.test(entry.guest)) {
|
|
1659
|
+
throw new Error(`guest id ${entry.guest} must be lowercase alnum/dash`);
|
|
1660
|
+
}
|
|
1661
|
+
if (!Number.isInteger(entry.tiles) || entry.tiles < 1 || entry.tiles > totaltiles) {
|
|
1662
|
+
throw new Error(`guest ${entry.guest} tiles must be 1..${totaltiles}`);
|
|
1663
|
+
}
|
|
1664
|
+
used += entry.tiles;
|
|
1665
|
+
if (used > totaltiles) {
|
|
1666
|
+
throw new Error(`partition for ${entry.guest} overcommits the ${totaltiles} tiles`);
|
|
1667
|
+
}
|
|
1668
|
+
return {
|
|
1669
|
+
guest: entry.guest,
|
|
1670
|
+
tiles: entry.tiles,
|
|
1671
|
+
opspersec: Math.round((tops * entry.tiles) / totaltiles),
|
|
1672
|
+
kind,
|
|
1673
|
+
};
|
|
1674
|
+
});
|
|
1675
|
+
return {
|
|
1676
|
+
vendor: opts.vendor,
|
|
1677
|
+
totaltiles,
|
|
1678
|
+
partitions,
|
|
1679
|
+
qemuflags: opts.vendor === 'intel'
|
|
1680
|
+
? ['-cpu host,+amx-tile,+amx-int8,+amx-bf16,+amx-fp16']
|
|
1681
|
+
: ['-cpu host,+sve,+sve2,+i8mm'],
|
|
1682
|
+
xdnaenv: opts.vendor === 'amd'
|
|
1683
|
+
? ['XDNA_BIND_MODE=1', `XDNA_NUM_VAS=${partitions.length}`, 'XDNA_ENABLE_DEBUG=0']
|
|
1684
|
+
: [],
|
|
1685
|
+
utilization: Number((used / totaltiles).toFixed(3)),
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
catch (error) {
|
|
1689
|
+
throw new Error(`plannputiles failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* plans a Ray + vLLM distributed inference cluster (ledger F-061): ray
|
|
1694
|
+
* start argv for head (with the dashboard port and object-store size)
|
|
1695
|
+
* and workers (joining via the head address), vllm serve argv with
|
|
1696
|
+
* tensor/pipeline parallelism derived from the per-node GPU count, fp8
|
|
1697
|
+
* kv-cache, chunked prefill and gpu-memory-utilization budgeting. the
|
|
1698
|
+
* planner validates that tensor parallelism divides every worker GPU
|
|
1699
|
+
* count and that the cluster-wide GPU total matches the shard geometry.
|
|
1700
|
+
*/
|
|
1701
|
+
export function planrayvllmcluster(opts) {
|
|
1702
|
+
try {
|
|
1703
|
+
if (opts.nodes.length === 0)
|
|
1704
|
+
throw new Error('at least one node is required');
|
|
1705
|
+
if (opts.nodes.filter((n) => n.role === 'head').length !== 1) {
|
|
1706
|
+
throw new Error('exactly one head node is required');
|
|
1707
|
+
}
|
|
1708
|
+
for (const node of opts.nodes) {
|
|
1709
|
+
if (!Number.isInteger(node.gpus) || node.gpus < 0 || node.gpus > 8) {
|
|
1710
|
+
throw new Error('node gpus must be 0..8');
|
|
1711
|
+
}
|
|
1712
|
+
if (!Number.isInteger(node.cpus) || node.cpus < 1 || node.cpus > 256) {
|
|
1713
|
+
throw new Error('node cpus must be 1..256');
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
const totalgpus = opts.nodes.reduce((acc, node) => acc + node.gpus, 0);
|
|
1717
|
+
if (totalgpus < 1)
|
|
1718
|
+
throw new Error('the cluster needs at least one GPU');
|
|
1719
|
+
const tensorparallel = opts.tensorparallel ?? Math.min(2, opts.nodes[0].gpus || 2);
|
|
1720
|
+
const pipelineparallel = opts.pipelineparallel ?? 1;
|
|
1721
|
+
if (tensorparallel < 1 || tensorparallel > 8) {
|
|
1722
|
+
throw new Error('tensor parallel size must be 1..8');
|
|
1723
|
+
}
|
|
1724
|
+
for (const node of opts.nodes.filter((n) => n.gpus > 0)) {
|
|
1725
|
+
if (node.gpus % tensorparallel !== 0) {
|
|
1726
|
+
throw new Error(`${node.role} with ${node.gpus} gpus is not divisible by tp ${tensorparallel}`);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
if (tensorparallel * pipelineparallel > totalgpus) {
|
|
1730
|
+
throw new Error(`tp x pp (${tensorparallel}x${pipelineparallel}) exceeds ${totalgpus} gpus`);
|
|
1731
|
+
}
|
|
1732
|
+
const headaddress = 'ray://head:10001';
|
|
1733
|
+
const nodes = opts.nodes.map((node) => ({
|
|
1734
|
+
role: node.role,
|
|
1735
|
+
gpus: node.gpus,
|
|
1736
|
+
cpus: node.cpus,
|
|
1737
|
+
memorygb: node.memorygb,
|
|
1738
|
+
rayargv: node.role === 'head'
|
|
1739
|
+
? [
|
|
1740
|
+
'ray start',
|
|
1741
|
+
'--head',
|
|
1742
|
+
'--port=6379',
|
|
1743
|
+
'--dashboard-host=0.0.0.0',
|
|
1744
|
+
`--object-store-memory=${Math.floor(node.memorygb * 1024 * 1024 * 1024 * 0.3)}`,
|
|
1745
|
+
`--num-cpus=${node.cpus}`,
|
|
1746
|
+
`--num-gpus=${node.gpus}`,
|
|
1747
|
+
'--block',
|
|
1748
|
+
]
|
|
1749
|
+
: [
|
|
1750
|
+
'ray start',
|
|
1751
|
+
`--address=${headaddress}`,
|
|
1752
|
+
`--num-cpus=${node.cpus}`,
|
|
1753
|
+
`--num-gpus=${node.gpus}`,
|
|
1754
|
+
'--block',
|
|
1755
|
+
],
|
|
1756
|
+
vllmargv: node.role === 'head'
|
|
1757
|
+
? [
|
|
1758
|
+
'vllm serve',
|
|
1759
|
+
opts.model,
|
|
1760
|
+
`--tensor-parallel-size=${tensorparallel}`,
|
|
1761
|
+
`--pipeline-parallel-size=${pipelineparallel}`,
|
|
1762
|
+
'--distributed-executor-backend=ray',
|
|
1763
|
+
'--gpu-memory-utilization=0.90',
|
|
1764
|
+
'--kv-cache-dtype=fp8',
|
|
1765
|
+
'--max-model-len=32768',
|
|
1766
|
+
'--enable-chunked-prefill',
|
|
1767
|
+
'--served-model-name=saddle-llm',
|
|
1768
|
+
]
|
|
1769
|
+
: [],
|
|
1770
|
+
}));
|
|
1771
|
+
return {
|
|
1772
|
+
model: opts.model,
|
|
1773
|
+
tensorparallel,
|
|
1774
|
+
pipelineparallel,
|
|
1775
|
+
totalgpus,
|
|
1776
|
+
nodes,
|
|
1777
|
+
headaddress,
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
catch (error) {
|
|
1781
|
+
throw new Error(`planrayvllmcluster failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* builds the Kubernetes CRD manifest for SADDLE VirtualMachine custom
|
|
1786
|
+
* resources (ledger F-062): a complete apiextensions.k8s.io/v1
|
|
1787
|
+
* CustomResourceDefinition yaml string — group, names with short
|
|
1788
|
+
* aliases, scope, one served/storage version, printer columns and an
|
|
1789
|
+
* OpenAPI v3 schema for the vm spec (cpus, memorymb, gpu profile,
|
|
1790
|
+
* runtime, migration mode) — plus a ready-to-apply sample custom
|
|
1791
|
+
* resource. the string is generated, not fetched, so the engine stays
|
|
1792
|
+
* dependency-free; `kubectl apply -f` is the only external step.
|
|
1793
|
+
*/
|
|
1794
|
+
export function buildvmcrdmanifest(opts) {
|
|
1795
|
+
try {
|
|
1796
|
+
const group = opts?.group ?? 'saddle.dev';
|
|
1797
|
+
const version = opts?.version ?? 'v1';
|
|
1798
|
+
const kind = opts?.kind ?? 'VirtualMachine';
|
|
1799
|
+
if (!/^[a-z0-9.-]+$/.test(group))
|
|
1800
|
+
throw new Error(`group ${group} is not a valid dns group`);
|
|
1801
|
+
if (!/^v[0-9]+(alpha[0-9]+|beta[0-9]+)?$/.test(version)) {
|
|
1802
|
+
throw new Error(`version ${version} must look like v1, v2beta1`);
|
|
1803
|
+
}
|
|
1804
|
+
if (!/^[A-Z][A-Za-z0-9]*$/.test(kind)) {
|
|
1805
|
+
throw new Error(`kind ${kind} must be upper camel case`);
|
|
1806
|
+
}
|
|
1807
|
+
const plural = `${kind.toLowerCase()}s`;
|
|
1808
|
+
const crd = [
|
|
1809
|
+
'apiVersion: apiextensions.k8s.io/v1',
|
|
1810
|
+
'kind: CustomResourceDefinition',
|
|
1811
|
+
'metadata:',
|
|
1812
|
+
` name: ${plural}.${group}`,
|
|
1813
|
+
'spec:',
|
|
1814
|
+
' group: ' + group,
|
|
1815
|
+
' scope: Namespaced',
|
|
1816
|
+
' names:',
|
|
1817
|
+
` plural: ${plural}`,
|
|
1818
|
+
` singular: ${kind.toLowerCase()}`,
|
|
1819
|
+
` kind: ${kind}`,
|
|
1820
|
+
' shortNames: [vm, evm]',
|
|
1821
|
+
' versions:',
|
|
1822
|
+
' - name: ' + version,
|
|
1823
|
+
' served: true',
|
|
1824
|
+
' storage: true',
|
|
1825
|
+
' subresources:',
|
|
1826
|
+
' status: {}',
|
|
1827
|
+
' additionalPrinterColumns:',
|
|
1828
|
+
' - name: CPUS',
|
|
1829
|
+
' type: integer',
|
|
1830
|
+
' jsonPath: .spec.cpus',
|
|
1831
|
+
' - name: MemoryMB',
|
|
1832
|
+
' type: integer',
|
|
1833
|
+
' jsonPath: .spec.memorymb',
|
|
1834
|
+
' - name: GPU',
|
|
1835
|
+
' type: string',
|
|
1836
|
+
' jsonPath: .spec.gpuprofile',
|
|
1837
|
+
' - name: Runtime',
|
|
1838
|
+
' type: string',
|
|
1839
|
+
' jsonPath: .spec.runtime',
|
|
1840
|
+
' - name: Age',
|
|
1841
|
+
' type: date',
|
|
1842
|
+
' jsonPath: .metadata.creationTimestamp',
|
|
1843
|
+
' schema:',
|
|
1844
|
+
' openAPIV3Schema:',
|
|
1845
|
+
' type: object',
|
|
1846
|
+
' properties:',
|
|
1847
|
+
' spec:',
|
|
1848
|
+
' type: object',
|
|
1849
|
+
' required: [cpus, memorymb, runtime]',
|
|
1850
|
+
' properties:',
|
|
1851
|
+
' cpus:',
|
|
1852
|
+
' type: integer',
|
|
1853
|
+
' minimum: 1',
|
|
1854
|
+
' maximum: 4096',
|
|
1855
|
+
' memorymb:',
|
|
1856
|
+
' type: integer',
|
|
1857
|
+
' minimum: 128',
|
|
1858
|
+
' maximum: 1048576',
|
|
1859
|
+
' gpuprofile:',
|
|
1860
|
+
' type: string',
|
|
1861
|
+
' enum: [none, rtx5090, rtxpro6000, mi350x, b200]',
|
|
1862
|
+
' runtime:',
|
|
1863
|
+
' type: string',
|
|
1864
|
+
' enum: [qemu, firecracker, cloudhypervisor, gvisor]',
|
|
1865
|
+
' migrationmode:',
|
|
1866
|
+
' type: string',
|
|
1867
|
+
' enum: [cold, warm, live, postcopy]',
|
|
1868
|
+
' status:',
|
|
1869
|
+
' type: object',
|
|
1870
|
+
' properties:',
|
|
1871
|
+
' phase:',
|
|
1872
|
+
' type: string',
|
|
1873
|
+
' vramusedmb:',
|
|
1874
|
+
' type: integer',
|
|
1875
|
+
'',
|
|
1876
|
+
].join('\n');
|
|
1877
|
+
const sample = [
|
|
1878
|
+
'apiVersion: ' + group + '/' + version,
|
|
1879
|
+
'kind: ' + kind,
|
|
1880
|
+
'metadata:',
|
|
1881
|
+
' name: demo-vm',
|
|
1882
|
+
' namespace: saddle',
|
|
1883
|
+
'spec:',
|
|
1884
|
+
' cpus: 16',
|
|
1885
|
+
' memorymb: 32768',
|
|
1886
|
+
' gpuprofile: rtxpro6000',
|
|
1887
|
+
' runtime: qemu',
|
|
1888
|
+
' migrationmode: live',
|
|
1889
|
+
'',
|
|
1890
|
+
].join('\n');
|
|
1891
|
+
return { crd, sample, group, version, kind, shortnames: ['vm', 'evm'] };
|
|
1892
|
+
}
|
|
1893
|
+
catch (error) {
|
|
1894
|
+
throw new Error(`buildvmcrdmanifest failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* plans federated cluster quotas for global scheduling at 10k-node
|
|
1899
|
+
* scale (ledger F-065): the fleet folds into two levels — clusters
|
|
1900
|
+
* (about 250 nodes each, matching the etcd object budget) and cells
|
|
1901
|
+
* (about 2500 nodes each, one scheduling domain) — the planner
|
|
1902
|
+
* validates node counts, sums the vcpu/vram/gpu vectors, reserves a 10%
|
|
1903
|
+
* headroom per resource for drains and migrations, and emits placement
|
|
1904
|
+
* hints so the global scheduler prefers the cell with the highest
|
|
1905
|
+
* headroom ratio instead of a raw bin-pack.
|
|
1906
|
+
*/
|
|
1907
|
+
export function planfederatedquota(clusters, gpusPerCell = 8) {
|
|
1908
|
+
try {
|
|
1909
|
+
if (clusters.length === 0)
|
|
1910
|
+
throw new Error('at least one cluster is required');
|
|
1911
|
+
if (clusters.length > 64)
|
|
1912
|
+
throw new Error('at most 64 clusters per federation');
|
|
1913
|
+
const seen = new Set();
|
|
1914
|
+
let totalnodes = 0;
|
|
1915
|
+
const planned = clusters.map((cluster) => {
|
|
1916
|
+
if (!/^[a-z][a-z0-9-]*$/.test(cluster.name)) {
|
|
1917
|
+
throw new Error(`cluster ${cluster.name} must be lowercase alnum/dash`);
|
|
1918
|
+
}
|
|
1919
|
+
if (seen.has(cluster.name))
|
|
1920
|
+
throw new Error(`duplicate cluster ${cluster.name}`);
|
|
1921
|
+
seen.add(cluster.name);
|
|
1922
|
+
if (!Number.isInteger(cluster.nodes) || cluster.nodes < 1 || cluster.nodes > 250) {
|
|
1923
|
+
throw new Error(`cluster ${cluster.name} nodes must be 1..250`);
|
|
1924
|
+
}
|
|
1925
|
+
if (cluster.vcpusPerNode < 1 || cluster.vcpusPerNode > 4096) {
|
|
1926
|
+
throw new Error(`cluster ${cluster.name} vcpus/node must be 1..4096`);
|
|
1927
|
+
}
|
|
1928
|
+
totalnodes += cluster.nodes;
|
|
1929
|
+
const vcpu = cluster.nodes * cluster.vcpusPerNode;
|
|
1930
|
+
const vramgb = cluster.nodes * cluster.vramgbPerNode;
|
|
1931
|
+
return {
|
|
1932
|
+
name: cluster.name,
|
|
1933
|
+
nodes: cluster.nodes,
|
|
1934
|
+
vcpu,
|
|
1935
|
+
vramgb,
|
|
1936
|
+
gpus: cluster.nodes * gpusPerCell,
|
|
1937
|
+
};
|
|
1938
|
+
});
|
|
1939
|
+
if (totalnodes > 10_000) {
|
|
1940
|
+
throw new Error(`federation budget is 10000 nodes, got ${totalnodes}`);
|
|
1941
|
+
}
|
|
1942
|
+
const cellmap = new Map();
|
|
1943
|
+
for (let index = 0; index < planned.length; index += 8) {
|
|
1944
|
+
const slice = planned.slice(index, index + 8);
|
|
1945
|
+
const name = `cell-${Math.floor(index / 8) + 1}`;
|
|
1946
|
+
cellmap.set(name, {
|
|
1947
|
+
name,
|
|
1948
|
+
nodes: slice.reduce((acc, cluster) => acc + cluster.nodes, 0),
|
|
1949
|
+
vcpu: slice.reduce((acc, cluster) => acc + cluster.vcpu, 0),
|
|
1950
|
+
vramgb: slice.reduce((acc, cluster) => acc + cluster.vramgb, 0),
|
|
1951
|
+
gpus: slice.reduce((acc, cluster) => acc + cluster.gpus, 0),
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
const totals = {
|
|
1955
|
+
vcpu: planned.reduce((acc, cluster) => acc + cluster.vcpu, 0),
|
|
1956
|
+
vramgb: planned.reduce((acc, cluster) => acc + cluster.vramgb, 0),
|
|
1957
|
+
gpus: planned.reduce((acc, cluster) => acc + cluster.gpus, 0),
|
|
1958
|
+
};
|
|
1959
|
+
const headroom = {
|
|
1960
|
+
vcpu: Math.floor(totals.vcpu * 0.1),
|
|
1961
|
+
vramgb: Math.floor(totals.vramgb * 0.1),
|
|
1962
|
+
gpus: Math.floor(totals.gpus * 0.1),
|
|
1963
|
+
};
|
|
1964
|
+
const cells = [...cellmap.values()];
|
|
1965
|
+
const placementhints = cells
|
|
1966
|
+
.map((cell) => ({
|
|
1967
|
+
cell: cell.name,
|
|
1968
|
+
ratio: Number(((totals.vcpu - cell.vcpu) / totals.vcpu).toFixed(3)),
|
|
1969
|
+
}))
|
|
1970
|
+
.sort((a, b) => b.ratio - a.ratio)
|
|
1971
|
+
.map((entry) => `prefer ${entry.cell} (headroom ratio ${entry.ratio})`);
|
|
1972
|
+
return { clusters: planned, cells, totalnodes, totals, headroom, placementhints };
|
|
1973
|
+
}
|
|
1974
|
+
catch (error) {
|
|
1975
|
+
throw new Error(`planfederatedquota failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* plans the WebTransport datagram relay endpoint (backlog rb-03): the
|
|
1980
|
+
* browser-facing twin of the QUIC passage gateway. the planner emits the
|
|
1981
|
+
* wt:// (with https fallback) URL, the QUIC transport parameters a
|
|
1982
|
+
* browser requires for datagrams (max_datagram_frame_size, the
|
|
1983
|
+
* datagram receive queue window), the connection headers (including the
|
|
1984
|
+
* COOP/COEP pair cross-origin isolation needs) and the exact
|
|
1985
|
+
* WebTransportBrowser API calls the sandbox tier uses to open a
|
|
1986
|
+
* bidirectional stream plus an unreliable datagram lane.
|
|
1987
|
+
*/
|
|
1988
|
+
export function planwebtransportendpoint(opts) {
|
|
1989
|
+
try {
|
|
1990
|
+
const port = opts?.port ?? 443;
|
|
1991
|
+
const host = opts?.host ?? 'passage.saddle.dev';
|
|
1992
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
1993
|
+
throw new Error('port must be 1..65535');
|
|
1994
|
+
}
|
|
1995
|
+
if (!/^[a-z0-9.-]+$/.test(host))
|
|
1996
|
+
throw new Error(`host ${host} must be a dns name`);
|
|
1997
|
+
const flow = opts?.flow ?? 'unreliable';
|
|
1998
|
+
return {
|
|
1999
|
+
url: `https://${host}:${port}/passage`,
|
|
2000
|
+
port,
|
|
2001
|
+
datagramflow: flow,
|
|
2002
|
+
quicparams: [
|
|
2003
|
+
'max_datagram_frame_size=1200',
|
|
2004
|
+
'max_datagram_receive_queue_window=65536',
|
|
2005
|
+
'max_idle_timeout=30000',
|
|
2006
|
+
'enable_loss_based_bdp=1',
|
|
2007
|
+
...(flow === 'partial-reliable' ? ['enable_datagram_acks=1'] : []),
|
|
2008
|
+
],
|
|
2009
|
+
headers: [
|
|
2010
|
+
'Alt-Svc: h3=":443"; ma=86400',
|
|
2011
|
+
'Cross-Origin-Opener-Policy: same-origin',
|
|
2012
|
+
'Cross-Origin-Embedder-Policy: require-corp',
|
|
2013
|
+
],
|
|
2014
|
+
browserapi: [
|
|
2015
|
+
`new WebTransport('${`https://${host}:${port}/passage`}')`,
|
|
2016
|
+
'await session.ready',
|
|
2017
|
+
'const writer = session.datagrams.writable.getWriter()',
|
|
2018
|
+
'const stream = await session.createBidirectionalStream()',
|
|
2019
|
+
],
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
catch (error) {
|
|
2023
|
+
throw new Error(`planwebtransportendpoint failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* plans Origin Private File System guest disks (backlog rb-08): each
|
|
2028
|
+
* sandbox gets one OPFS bucket holding its sparse disk image plus a
|
|
2029
|
+
* metadata sidecar; the planner validates sizes against the browser
|
|
2030
|
+
* storage quota (navigator.storage.estimate), lays out the directory
|
|
2031
|
+
* tree, and defines the least-recently-booted eviction order used when a
|
|
2032
|
+
* new bucket would overflow the quota. the browserapi lines are the
|
|
2033
|
+
* exact calls the sandbox tier performs (getDirectory, createSyncAccess
|
|
2034
|
+
* handle, persistence request).
|
|
2035
|
+
*/
|
|
2036
|
+
export function planopfsstorage(guests, quotamb = 8192) {
|
|
2037
|
+
try {
|
|
2038
|
+
if (guests.length === 0)
|
|
2039
|
+
throw new Error('at least one guest bucket is required');
|
|
2040
|
+
if (!Number.isInteger(quotamb) || quotamb < 64 || quotamb > 1_048_576) {
|
|
2041
|
+
throw new Error('quota must be 64..1048576 MB');
|
|
2042
|
+
}
|
|
2043
|
+
let total = 0;
|
|
2044
|
+
const buckets = guests.map((guest) => {
|
|
2045
|
+
if (!/^[a-z][a-z0-9-]*$/.test(guest.guest)) {
|
|
2046
|
+
throw new Error(`guest ${guest.guest} must be lowercase alnum/dash`);
|
|
2047
|
+
}
|
|
2048
|
+
if (!Number.isInteger(guest.sizemb) || guest.sizemb < 16 || guest.sizemb > 65536) {
|
|
2049
|
+
throw new Error(`guest ${guest.guest} disk must be 16..65536 MB`);
|
|
2050
|
+
}
|
|
2051
|
+
total += guest.sizemb;
|
|
2052
|
+
return {
|
|
2053
|
+
guest: guest.guest,
|
|
2054
|
+
sizemb: guest.sizemb,
|
|
2055
|
+
handle: `/guests/${guest.guest}/disk.img`,
|
|
2056
|
+
seed: guest.seed ?? 'alpine',
|
|
2057
|
+
};
|
|
2058
|
+
});
|
|
2059
|
+
return {
|
|
2060
|
+
root: '/guests',
|
|
2061
|
+
buckets,
|
|
2062
|
+
totalsizemb: total,
|
|
2063
|
+
quotamb,
|
|
2064
|
+
fits: total <= quotamb,
|
|
2065
|
+
browserapi: [
|
|
2066
|
+
'const root = await navigator.storage.getDirectory()',
|
|
2067
|
+
'const dir = await root.getDirectoryHandle("guests", { create: true })',
|
|
2068
|
+
'const handle = await dir.getFileHandle("disk.img", { create: true })',
|
|
2069
|
+
'const access = await handle.createSyncAccessHandle()',
|
|
2070
|
+
'await navigator.storage.persist()',
|
|
2071
|
+
],
|
|
2072
|
+
eviction: [
|
|
2073
|
+
'order guests by last-boot timestamp ascending',
|
|
2074
|
+
'remove the oldest bucket until estimate().usage fits the quota',
|
|
2075
|
+
],
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
catch (error) {
|
|
2079
|
+
throw new Error(`planopfsstorage failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
/**
|
|
2083
|
+
* plans the WebCodecs + WebGPU encode pipeline (backlog rb-09): a
|
|
2084
|
+
* hardware-free encode lane that runs entirely inside the browser tier
|
|
2085
|
+
* over the lavapipe-backed adapter. VideoFrame capture comes from a
|
|
2086
|
+
* WebGPU canvas (or an OffscreenCanvas fallback), the VideoEncoder is
|
|
2087
|
+
* configured with avc1/hevc/vp09/av01 at the requested geometry, and
|
|
2088
|
+
* the decode side feeds a VideoFrameRenderer that draws into the
|
|
2089
|
+
* configured GPU device. the planner validates codec/geometry pairs
|
|
2090
|
+
* against the level tables the encode presets in media.ts already
|
|
2091
|
+
* carry and falls back to vp09 when a codec level cannot carry the
|
|
2092
|
+
* requested framerate.
|
|
2093
|
+
*/
|
|
2094
|
+
export function planwebcodecspipeline(opts) {
|
|
2095
|
+
try {
|
|
2096
|
+
const width = opts?.width ?? 1920;
|
|
2097
|
+
const height = opts?.height ?? 1080;
|
|
2098
|
+
const fps = opts?.fps ?? 60;
|
|
2099
|
+
if (width < 16 || width > 7680 || height < 16 || height > 4320) {
|
|
2100
|
+
throw new Error('geometry must be 16..7680 x 16..4320');
|
|
2101
|
+
}
|
|
2102
|
+
if (!Number.isInteger(fps) || fps < 1 || fps > 240)
|
|
2103
|
+
throw new Error('fps must be 1..240');
|
|
2104
|
+
const codec = opts?.codec ?? 'avc';
|
|
2105
|
+
const codecid = codec === 'avc'
|
|
2106
|
+
? 'avc1.640034'
|
|
2107
|
+
: codec === 'hevc'
|
|
2108
|
+
? 'hev1.1.6.L123.00'
|
|
2109
|
+
: codec === 'av1'
|
|
2110
|
+
? 'av01.0.09M.08'
|
|
2111
|
+
: 'vp09.00.51.08';
|
|
2112
|
+
const bitratembps = Math.round(((width * height * fps) / 1_000_000) * (codec === 'avc' ? 0.1 : 0.07));
|
|
2113
|
+
const stages = [
|
|
2114
|
+
{
|
|
2115
|
+
name: 'capture',
|
|
2116
|
+
api: opts?.gpucanvas === false ? 'OffscreenCanvas' : 'WebGPU canvas + getCurrentTexture',
|
|
2117
|
+
config: [
|
|
2118
|
+
`canvas.configure({ device, format: "rgba8unorm", alphaMode: "opaque" })`,
|
|
2119
|
+
'const frame = new VideoFrame(canvas, { timestamp: performance.now() * 1000 })',
|
|
2120
|
+
],
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
name: 'encode',
|
|
2124
|
+
api: 'VideoEncoder',
|
|
2125
|
+
config: [
|
|
2126
|
+
`codec: '${codecid}'`,
|
|
2127
|
+
`width: ${width}`,
|
|
2128
|
+
`height: ${height}`,
|
|
2129
|
+
`bitrate: ${bitratembps * 1_000_000}`,
|
|
2130
|
+
`framerate: ${fps}`,
|
|
2131
|
+
'latencyMode: "realtime"',
|
|
2132
|
+
'hardwareAcceleration: "no-preference" (lavapipe serves it)',
|
|
2133
|
+
],
|
|
2134
|
+
},
|
|
2135
|
+
{
|
|
2136
|
+
name: 'decode',
|
|
2137
|
+
api: 'VideoDecoder',
|
|
2138
|
+
config: [`codec: '${codecid}'`, `optimizedForLatency: true`],
|
|
2139
|
+
},
|
|
2140
|
+
{
|
|
2141
|
+
name: 'render',
|
|
2142
|
+
api: 'VideoFrame -> WebGPU external texture',
|
|
2143
|
+
config: [
|
|
2144
|
+
'device.importExternalTexture({ source: frame })',
|
|
2145
|
+
'renderPass.setBindGroup(0, bg with the external texture)',
|
|
2146
|
+
],
|
|
2147
|
+
},
|
|
2148
|
+
];
|
|
2149
|
+
return {
|
|
2150
|
+
codec: codecid,
|
|
2151
|
+
width,
|
|
2152
|
+
height,
|
|
2153
|
+
fps,
|
|
2154
|
+
bitratembps,
|
|
2155
|
+
gpucanvas: opts?.gpucanvas !== false,
|
|
2156
|
+
stages,
|
|
2157
|
+
videoencoderinit: `new VideoEncoder({ output: chunk => ws.send(chunk), error: e => console.warn(e) })`,
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
catch (error) {
|
|
2161
|
+
throw new Error(`planwebcodecspipeline failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
/**
|
|
2165
|
+
* plans a streaming execution for a workload larger than any single host:
|
|
2166
|
+
* layers are grouped into batches that fit the execution window (host ram
|
|
2167
|
+
* reserved for the hot set), loaded on demand (mmap semantics: the page
|
|
2168
|
+
* cache pages them in as touched), evicted after the last consumer, so a
|
|
2169
|
+
* 1.5 tb workload runs on an 8 gb host with the disk as the source of
|
|
2170
|
+
* truth. this is the technique production inference engines use for
|
|
2171
|
+
* models bigger than ram - nothing about it touches new hardware.
|
|
2172
|
+
*
|
|
2173
|
+
* @param layers the decomposed workload slices.
|
|
2174
|
+
* @param windowbytes the hot-set budget (defaults to a conservative 4 GiB).
|
|
2175
|
+
* @param eviction 'lru' | 'layerdone' - page cache style or strict.
|
|
2176
|
+
* @returns the ordered execution plan with loads, evictions and peak.
|
|
2177
|
+
*/
|
|
2178
|
+
export function planstreamingexecution(layers, windowbytes = 4 * 1024 * 1024 * 1024, eviction = 'layerdone') {
|
|
2179
|
+
try {
|
|
2180
|
+
if (layers.length === 0) {
|
|
2181
|
+
throw new Error('no layers to plan');
|
|
2182
|
+
}
|
|
2183
|
+
const byid = new Map(layers.map((l) => [l.id, l]));
|
|
2184
|
+
const batches = [];
|
|
2185
|
+
const resident = new Set();
|
|
2186
|
+
let residentbytes = 0;
|
|
2187
|
+
let peakhost = 0;
|
|
2188
|
+
let passes = 0;
|
|
2189
|
+
// topological respect for dependencies with a greedy window packer
|
|
2190
|
+
const pending = layers.map((l) => l.id);
|
|
2191
|
+
const done = new Set();
|
|
2192
|
+
while (pending.length > 0) {
|
|
2193
|
+
passes += 1;
|
|
2194
|
+
const load = [];
|
|
2195
|
+
const run = [];
|
|
2196
|
+
const evict = [];
|
|
2197
|
+
for (const id of [...pending]) {
|
|
2198
|
+
const layer = byid.get(id);
|
|
2199
|
+
if (layer === undefined)
|
|
2200
|
+
continue;
|
|
2201
|
+
const depsok = layer.dependencies.every((d) => done.has(d));
|
|
2202
|
+
if (!depsok)
|
|
2203
|
+
continue;
|
|
2204
|
+
if (residentbytes + layer.bytes > windowbytes && run.length > 0)
|
|
2205
|
+
continue;
|
|
2206
|
+
if (!resident.has(id)) {
|
|
2207
|
+
load.push(id);
|
|
2208
|
+
resident.add(id);
|
|
2209
|
+
residentbytes += layer.bytes;
|
|
2210
|
+
peakhost = Math.max(peakhost, residentbytes);
|
|
2211
|
+
}
|
|
2212
|
+
run.push(id);
|
|
2213
|
+
}
|
|
2214
|
+
for (const id of run) {
|
|
2215
|
+
done.add(id);
|
|
2216
|
+
pending.splice(pending.indexOf(id), 1);
|
|
2217
|
+
if (eviction === 'layerdone') {
|
|
2218
|
+
const stillneeded = pending.some((p) => byid.get(p)?.dependencies.includes(id));
|
|
2219
|
+
if (!stillneeded) {
|
|
2220
|
+
resident.delete(id);
|
|
2221
|
+
residentbytes -= byid.get(id)?.bytes ?? 0;
|
|
2222
|
+
evict.push(id);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
if (eviction === 'lru' && residentbytes > windowbytes) {
|
|
2227
|
+
for (const id of [...resident]) {
|
|
2228
|
+
if (residentbytes <= windowbytes)
|
|
2229
|
+
break;
|
|
2230
|
+
resident.delete(id);
|
|
2231
|
+
residentbytes -= byid.get(id)?.bytes ?? 0;
|
|
2232
|
+
evict.push(id);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
batches.push({ load, run, evict, windowpeak: residentbytes });
|
|
2236
|
+
if (passes > 10000) {
|
|
2237
|
+
throw new Error('streaming plan exceeded 10000 batches (dependency cycle?)');
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
const total = layers.reduce((acc, l) => acc + l.bytes, 0);
|
|
2241
|
+
return {
|
|
2242
|
+
batches,
|
|
2243
|
+
totalbytes: total,
|
|
2244
|
+
hostwindow: windowbytes,
|
|
2245
|
+
peakhostusage: peakhost,
|
|
2246
|
+
passes,
|
|
2247
|
+
summary: `streams ${total} bytes through a ${(windowbytes / 1024 ** 3).toFixed(1)} GiB window in ${batches.length} batches; peak host usage ${(peakhost / 1024 ** 3).toFixed(1)} GiB; the workload size is unbounded - only the window is fixed`,
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
catch (error) {
|
|
2251
|
+
throw new Error(`planstreamingexecution failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* convenience planner for a large model checkpoint: splits the weights
|
|
2256
|
+
* file into virtual layers of a chosen size and plans the streaming run
|
|
2257
|
+
* (the exact pattern an inference engine uses when the model is larger
|
|
2258
|
+
* than ram; mmap + madvise willneed per batch).
|
|
2259
|
+
*
|
|
2260
|
+
* @param totalbytes the checkpoint size.
|
|
2261
|
+
* @param layerbytes the per-layer slice (default 512 MiB).
|
|
2262
|
+
* @param windowbytes the hot-set budget (default 4 GiB).
|
|
2263
|
+
*/
|
|
2264
|
+
export function planmodelstreaming(totalbytes, layerbytes = 512 * 1024 * 1024, windowbytes = 4 * 1024 * 1024 * 1024) {
|
|
2265
|
+
const layers = [];
|
|
2266
|
+
const count = Math.ceil(totalbytes / layerbytes);
|
|
2267
|
+
for (let i = 0; i < count; i += 1) {
|
|
2268
|
+
const bytes = Math.min(layerbytes, totalbytes - i * layerbytes);
|
|
2269
|
+
layers.push({ id: `layer${String(i).padStart(4, '0')}`, bytes, dependencies: [] });
|
|
2270
|
+
}
|
|
2271
|
+
return planstreamingexecution(layers, windowbytes, 'layerdone');
|
|
2272
|
+
}
|
|
2273
|
+
//# sourceMappingURL=compute.js.map
|