@wenathlan/saddle 1.8.18 → 2.0.3
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 +1346 -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/packager/targetcli.js +4 -22
- 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.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
|
@@ -0,0 +1,1796 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* virtualmemory - virtual memory tiers, controllers and the host-aware
|
|
3
|
+
* extreme memory planner.
|
|
4
|
+
*
|
|
5
|
+
* This module owns everything memory related: the MEMORY_TIERS catalog with
|
|
6
|
+
* real bandwidth figures (DDR5-6400 system memory, HBM3e stacked memory at
|
|
7
|
+
* 8 TB/s, GDDR7 graphics memory at 1792 GB/s), MIG slicing profiles
|
|
8
|
+
* (1g.24gb, 2g.48gb, 4g.96gb), NUMA topology generation, huge page planning
|
|
9
|
+
* (1 GB and 2 MB), the kernel samepage merging controller, virtio ballooning,
|
|
10
|
+
* swap overcommit with vm.overcommit_memory=2 semantics, the tmpfs shader
|
|
11
|
+
* cache (20 GB), Docker memory flags (MemorySwap -1 and ShmSize 2g, the fix
|
|
12
|
+
* for the classic PyTorch DataLoader bus error), tiered memory controllers
|
|
13
|
+
* modeled on real silicon, a fluent region builder, a guarding region Proxy
|
|
14
|
+
* and the virtualmemorymanager facade that ties every technique together.
|
|
15
|
+
*
|
|
16
|
+
* The v2 merge adds two host-aware layers absorbed from the Meta sources:
|
|
17
|
+
* the placement tier taxonomy (DRAM_FAST, DRAM_SLOW, CXL_FM, CXL_PMEM,
|
|
18
|
+
* HBM_BLACKWELL, ZRAM, PMEM_DAX) with CXL 2.0/3.0 Type-3 device profiles,
|
|
19
|
+
* zram compression profiles, transparent huge page profiles and CCD-aware
|
|
20
|
+
* vCPU pinning for the Ryzen 9 9950X3D; and the memorymodularizer class
|
|
21
|
+
* that detects real host tiers (lspci CXL scan, /dev/pmem0, shared
|
|
22
|
+
* Blackwell memory), plans zram with workload-dependent compression
|
|
23
|
+
* factors, synthesizes tiered QEMU arguments, exports cgroup v2 slices and
|
|
24
|
+
* QMP runtime commands, and persists plans as JSON. /proc/meminfo
|
|
25
|
+
* generation (generateVirtualMeminfo, meminfoptions, pagealign) moved here
|
|
26
|
+
* from the processor module so every memory concern lives in one file.
|
|
27
|
+
*
|
|
28
|
+
* Modularity extremes are first class: 1 to 192 vCPUs, 1 to 1024 GB of RAM
|
|
29
|
+
* and 8 to 96 GB of VRAM, all user chosen and validated against hard limits.
|
|
30
|
+
*
|
|
31
|
+
* Bandwidth sources verified on 2026-08-22: JEDEC DDR5-6400 (51.2 GB/s per
|
|
32
|
+
* channel), NVIDIA B200 and AMD Instinct MI350X HBM3e (8 TB/s aggregate),
|
|
33
|
+
* GeForce RTX 5090 and RTX PRO 6000 Blackwell GDDR7 (1792 GB/s over a
|
|
34
|
+
* 512-bit bus). CXL figures follow the CXL 2.0/3.0 Type-3 fabric numbers
|
|
35
|
+
* (178-195 ns latency, 32-64 GB/s per x16 PCIe 5.0 link) documented in the
|
|
36
|
+
* project research; zram compression ratios (lzo-rle 3.1:1, zstd 4.2:1)
|
|
37
|
+
* follow the kernel zram documentation.
|
|
38
|
+
*
|
|
39
|
+
* Contexts (25): memoryerror, memorytierid, MEMORY_TIERS, memorylimits,
|
|
40
|
+
* MIGPROFILES, validatememoryrequest, buildnumatopology, planhugepages,
|
|
41
|
+
* ksmcontroller, ballooningcontroller, overcommitcontroller,
|
|
42
|
+
* planswapdevices, shadercachecontroller, dockerMemoryFlags,
|
|
43
|
+
* memorycontroller, memoryregion, regionledger, memorystats, memoryplan,
|
|
44
|
+
* virtualmemorymanager, placementtier, cxxldevice, zramprofile,
|
|
45
|
+
* memorymodularizer, generateVirtualMeminfo.
|
|
46
|
+
*
|
|
47
|
+
* Patterns: strategy (memorycontroller), builder (regionbuilder), registry
|
|
48
|
+
* (regionledger), proxy (guardregion), observer (memorystats), facade
|
|
49
|
+
* (virtualmemorymanager), planner (memorymodularizer).
|
|
50
|
+
* Rules: lowercase identifiers, english jsdoc, third person, no emoji,
|
|
51
|
+
* try/catch catcher on every fallible path, node:* built-ins only, and no
|
|
52
|
+
* hardcoded localhost address anywhere.
|
|
53
|
+
*/
|
|
54
|
+
import { execSync } from 'node:child_process';
|
|
55
|
+
import { createHash, randomInt, randomUUID } from 'node:crypto';
|
|
56
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
57
|
+
import { cpus, totalmem } from 'node:os';
|
|
58
|
+
/* ------------------------------------------------------------------ */
|
|
59
|
+
/* Section 1: errors */
|
|
60
|
+
/* ------------------------------------------------------------------ */
|
|
61
|
+
/** Error thrown by every memory subsystem with an optional cause chain. */
|
|
62
|
+
export class memoryerror extends Error {
|
|
63
|
+
/** Machine readable subsystem tag. */
|
|
64
|
+
subsystem;
|
|
65
|
+
constructor(message, options) {
|
|
66
|
+
super(message, options);
|
|
67
|
+
this.name = 'memoryerror';
|
|
68
|
+
this.subsystem = options?.subsystem ?? 'virtualmemory';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* MEMORY_TIERS: the catalog of virtual memory technologies with real
|
|
73
|
+
* bandwidth arithmetic. DDR5-6400 moves 51.2 GB/s per channel (2 channels
|
|
74
|
+
* on desktop parts such as Ryzen and Core Ultra, 12 channels on EPYC SP5),
|
|
75
|
+
* HBM3e delivers 8 TB/s aggregate on B200 and MI350X class accelerators and
|
|
76
|
+
* GDDR7 reaches 1792 GB/s across the 512-bit bus of RTX 5090 and RTX PRO
|
|
77
|
+
* 6000 Blackwell boards.
|
|
78
|
+
*/
|
|
79
|
+
export const MEMORY_TIERS = {
|
|
80
|
+
ddr5: {
|
|
81
|
+
id: 'ddr5',
|
|
82
|
+
label: 'DDR5-6400 system memory',
|
|
83
|
+
technology: 'DDR5 SDRAM at 6400 MT/s',
|
|
84
|
+
perchannelgbs: 51.2,
|
|
85
|
+
channels: 12,
|
|
86
|
+
modulegb: 96,
|
|
87
|
+
latencyns: 76,
|
|
88
|
+
usedfor: 'guest RAM, page cache, tmpfs shader cache',
|
|
89
|
+
},
|
|
90
|
+
hbm3e: {
|
|
91
|
+
id: 'hbm3e',
|
|
92
|
+
label: 'HBM3e stacked memory',
|
|
93
|
+
technology: 'high bandwidth memory 3e, 8 stacks',
|
|
94
|
+
perchannelgbs: 1000,
|
|
95
|
+
channels: 8,
|
|
96
|
+
modulegb: 36,
|
|
97
|
+
latencyns: 29,
|
|
98
|
+
usedfor: 'datacenter accelerator memory (B200 192 GB, MI350X 288 GB)',
|
|
99
|
+
},
|
|
100
|
+
gddr7: {
|
|
101
|
+
id: 'gddr7',
|
|
102
|
+
label: 'GDDR7 graphics memory',
|
|
103
|
+
technology: 'GDDR7 at 28 Gbps over a 512-bit bus',
|
|
104
|
+
perchannelgbs: 224,
|
|
105
|
+
channels: 8,
|
|
106
|
+
modulegb: 16,
|
|
107
|
+
latencyns: 42,
|
|
108
|
+
usedfor: 'workstation and consumer GPUs (RTX 5090, RTX PRO 6000)',
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
/** Returns one tier descriptor, throwing for unknown identifiers. */
|
|
112
|
+
export function selecttier(id) {
|
|
113
|
+
const tier = MEMORY_TIERS[id];
|
|
114
|
+
if (tier === undefined) {
|
|
115
|
+
throw new memoryerror(`unknown memory tier "${id}"; valid tiers: ${Object.keys(MEMORY_TIERS).join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
return tier;
|
|
118
|
+
}
|
|
119
|
+
/** Aggregated bandwidth of one tier in GB/s (per channel times channels). */
|
|
120
|
+
export function tierbandwidthgbs(id) {
|
|
121
|
+
const tier = selecttier(id);
|
|
122
|
+
return Number((tier.perchannelgbs * tier.channels).toFixed(1));
|
|
123
|
+
}
|
|
124
|
+
/** Extremes of modularity supported by the virtual memory planner. */
|
|
125
|
+
export const memorylimits = {
|
|
126
|
+
minvcpu: 1,
|
|
127
|
+
maxvcpu: 192,
|
|
128
|
+
minramgb: 1,
|
|
129
|
+
maxramgb: 1024,
|
|
130
|
+
minvramgb: 8,
|
|
131
|
+
maxvramgb: 96,
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* MIG profiles available on the 96 GB virtual device. The identifiers keep
|
|
135
|
+
* the NVIDIA convention of compute-slice names: 1g.24gb exposes 24 GB
|
|
136
|
+
* slices (up to 4 instances), 2g.48gb exposes 48 GB slices (up to 2
|
|
137
|
+
* instances) and 4g.96gb dedicates the full 96 GB to one instance.
|
|
138
|
+
*/
|
|
139
|
+
export const MIGPROFILES = [
|
|
140
|
+
{ id: '1g.24gb', slicegb: 24, maxinstances: 4 },
|
|
141
|
+
{ id: '2g.48gb', slicegb: 48, maxinstances: 2 },
|
|
142
|
+
{ id: '4g.96gb', slicegb: 96, maxinstances: 1 },
|
|
143
|
+
];
|
|
144
|
+
/** Resolves a MIG profile by identifier, throwing for unknown names. */
|
|
145
|
+
export function getmigprofile(id) {
|
|
146
|
+
if (id === 'off') {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const found = MIGPROFILES.find((profile) => profile.id === id);
|
|
150
|
+
if (found === undefined) {
|
|
151
|
+
throw new memoryerror(`unknown MIG profile "${id}"; valid profiles: ${MIGPROFILES.map((profile) => profile.id).join(', ')} or off`);
|
|
152
|
+
}
|
|
153
|
+
return found;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Validates a memory request against the hard limits and the MIG catalog.
|
|
157
|
+
* The function reuses the rule style of the core validator: it accumulates
|
|
158
|
+
* errors for limit violations, warnings for suspicious-but-legal layouts
|
|
159
|
+
* (for example RAM below 16 GB with 96 GB of VRAM, or ballooning suggested
|
|
160
|
+
* when RAM exceeds 512 GB) and builds a plan when the request is valid.
|
|
161
|
+
*/
|
|
162
|
+
export function validatememoryrequest(request) {
|
|
163
|
+
const errors = [];
|
|
164
|
+
const warnings = [];
|
|
165
|
+
try {
|
|
166
|
+
const vcpus = request.vcpus ?? 8;
|
|
167
|
+
const ramgb = request.ramgb ?? 32;
|
|
168
|
+
const vramgb = request.vramgb ?? 24;
|
|
169
|
+
const tier = request.tier ?? 'ddr5';
|
|
170
|
+
if (!Number.isInteger(vcpus) || vcpus < memorylimits.minvcpu || vcpus > memorylimits.maxvcpu) {
|
|
171
|
+
errors.push(`vcpus must be an integer between ${memorylimits.minvcpu} and ${memorylimits.maxvcpu}`);
|
|
172
|
+
}
|
|
173
|
+
if (!Number.isFinite(ramgb) || ramgb < memorylimits.minramgb || ramgb > memorylimits.maxramgb) {
|
|
174
|
+
errors.push(`ramgb must be between ${memorylimits.minramgb} and ${memorylimits.maxramgb}`);
|
|
175
|
+
}
|
|
176
|
+
if (!Number.isFinite(vramgb) ||
|
|
177
|
+
vramgb < memorylimits.minvramgb ||
|
|
178
|
+
vramgb > memorylimits.maxvramgb) {
|
|
179
|
+
errors.push(`vramgb must be between ${memorylimits.minvramgb} and ${memorylimits.maxvramgb}`);
|
|
180
|
+
}
|
|
181
|
+
const mig = request.mig ?? 'off';
|
|
182
|
+
const profile = getmigprofile(mig);
|
|
183
|
+
if (profile !== null && profile.slicegb !== vramgb) {
|
|
184
|
+
errors.push(`MIG profile ${mig} implies ${profile.slicegb} GB slices but vramgb is ${vramgb}`);
|
|
185
|
+
}
|
|
186
|
+
if (ramgb < 16 && vramgb >= 64) {
|
|
187
|
+
warnings.push('vramgb far exceeds ramgb: host page cache may thrash during model loads');
|
|
188
|
+
}
|
|
189
|
+
if (ramgb >= 512) {
|
|
190
|
+
warnings.push('RAM at or above 512 GB benefits from 1 GB huge pages and ballooning');
|
|
191
|
+
}
|
|
192
|
+
if (tier === 'ddr5' && vramgb > 64) {
|
|
193
|
+
warnings.push('ddr5 backing for VRAM above 64 GB halves effective bandwidth; prefer gddr7 or hbm3e tiers');
|
|
194
|
+
}
|
|
195
|
+
if (errors.length > 0) {
|
|
196
|
+
return { valid: false, errors, warnings, plan: null };
|
|
197
|
+
}
|
|
198
|
+
return { valid: true, errors, warnings, plan: planmemory(vcpus, ramgb, vramgb, mig, tier) };
|
|
199
|
+
}
|
|
200
|
+
catch (cause) {
|
|
201
|
+
return {
|
|
202
|
+
valid: false,
|
|
203
|
+
errors: [`validatememoryrequest crashed: ${cause.message}`],
|
|
204
|
+
warnings,
|
|
205
|
+
plan: null,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Builds a NUMA topology for a vCPU count split across a node count. Local
|
|
211
|
+
* distance is 10 and remote nodes sit at 32, mirroring the NPS modes of
|
|
212
|
+
* AMD EPYC parts; cpu ranges are rendered as the compact lists used by
|
|
213
|
+
* lscpu and numactl.
|
|
214
|
+
*/
|
|
215
|
+
export function buildnumatopology(vcpus, nodes) {
|
|
216
|
+
try {
|
|
217
|
+
if (!Number.isInteger(vcpus) || vcpus < 1) {
|
|
218
|
+
throw new memoryerror(`vcpus must be a positive integer, received ${vcpus}`);
|
|
219
|
+
}
|
|
220
|
+
if (!Number.isInteger(nodes) || nodes < 1 || nodes > 16) {
|
|
221
|
+
throw new memoryerror(`nodes must be between 1 and 16, received ${nodes}`);
|
|
222
|
+
}
|
|
223
|
+
const pernode = Math.ceil(vcpus / nodes);
|
|
224
|
+
const cpuranges = [];
|
|
225
|
+
for (let node = 0; node < nodes; node += 1) {
|
|
226
|
+
const start = node * pernode;
|
|
227
|
+
const end = Math.min(start + pernode, vcpus) - 1;
|
|
228
|
+
if (start > end) {
|
|
229
|
+
cpuranges.push('');
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
cpuranges.push(start === end ? String(start) : `${start}-${end}`);
|
|
233
|
+
}
|
|
234
|
+
const distances = [];
|
|
235
|
+
for (let row = 0; row < nodes; row += 1) {
|
|
236
|
+
const line = [];
|
|
237
|
+
for (let column = 0; column < nodes; column += 1) {
|
|
238
|
+
line.push(row === column ? 10 : 32);
|
|
239
|
+
}
|
|
240
|
+
distances.push(line);
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
nodes,
|
|
244
|
+
cpuranges,
|
|
245
|
+
distances,
|
|
246
|
+
description: `${nodes} NUMA node(s), ${pernode} vCPU(s) per node, remote distance 32`,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
catch (cause) {
|
|
250
|
+
if (cause instanceof memoryerror) {
|
|
251
|
+
throw cause;
|
|
252
|
+
}
|
|
253
|
+
throw new memoryerror('buildnumatopology failed', { cause: cause });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/** Curated NUMA presets for common EPYC thread counts. */
|
|
257
|
+
export const NUMATOPOLOGIES = [
|
|
258
|
+
buildnumatopology(192, 4),
|
|
259
|
+
buildnumatopology(192, 8),
|
|
260
|
+
buildnumatopology(128, 4),
|
|
261
|
+
buildnumatopology(96, 2),
|
|
262
|
+
buildnumatopology(64, 2),
|
|
263
|
+
buildnumatopology(32, 1),
|
|
264
|
+
];
|
|
265
|
+
/**
|
|
266
|
+
* Plans huge page reservations for a RAM size. Machines with at least 256 GB
|
|
267
|
+
* reserve half of their RAM as 1 GB pages for large model weights, plus a
|
|
268
|
+
* pool of 2048 2 MB pages for medium allocations; smaller machines keep a
|
|
269
|
+
* 2 MB-only layout.
|
|
270
|
+
*/
|
|
271
|
+
export function planhugepages(ramgb) {
|
|
272
|
+
try {
|
|
273
|
+
if (!Number.isFinite(ramgb) || ramgb <= 0) {
|
|
274
|
+
throw new memoryerror(`ramgb must be a positive number, received ${ramgb}`);
|
|
275
|
+
}
|
|
276
|
+
if (ramgb >= 256) {
|
|
277
|
+
const onegbpages = Math.floor(ramgb / 2);
|
|
278
|
+
return [
|
|
279
|
+
{ size: '1G', count: onegbpages, coversgb: onegbpages },
|
|
280
|
+
{ size: '2M', count: 2048, coversgb: 4 },
|
|
281
|
+
];
|
|
282
|
+
}
|
|
283
|
+
const twombpages = Math.floor((ramgb * 1024) / 4);
|
|
284
|
+
return [
|
|
285
|
+
{ size: '2M', count: twombpages, coversgb: Number(((twombpages * 2) / 1024).toFixed(2)) },
|
|
286
|
+
];
|
|
287
|
+
}
|
|
288
|
+
catch (cause) {
|
|
289
|
+
if (cause instanceof memoryerror) {
|
|
290
|
+
throw cause;
|
|
291
|
+
}
|
|
292
|
+
throw new memoryerror('planhugepages failed', { cause: cause });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Kernel samepage merging controller. KSM scans anonymous pages and merges
|
|
297
|
+
* identical ones, which matters for dense virtual machines running the same
|
|
298
|
+
* runtime image; the controller models the sysfs knobs documented by the
|
|
299
|
+
* kernel (run, pages_to_scan, sleep_millisecs, merge_across_nodes) and
|
|
300
|
+
* estimates the deduplication ratio from the shared/sharing counters.
|
|
301
|
+
*/
|
|
302
|
+
export class ksmcontroller {
|
|
303
|
+
#pagestoscan;
|
|
304
|
+
#sleepmillisecs;
|
|
305
|
+
#mergeacrossnodes;
|
|
306
|
+
#enabled;
|
|
307
|
+
constructor(pagestoscan = 1000, sleepmillisecs = 20) {
|
|
308
|
+
this.#pagestoscan = pagestoscan;
|
|
309
|
+
this.#sleepmillisecs = sleepmillisecs;
|
|
310
|
+
this.#mergeacrossnodes = true;
|
|
311
|
+
this.#enabled = false;
|
|
312
|
+
}
|
|
313
|
+
/** Enables KSM and returns the sysfs writes required. */
|
|
314
|
+
enable() {
|
|
315
|
+
this.#enabled = true;
|
|
316
|
+
return [
|
|
317
|
+
'echo 1 > /sys/kernel/mm/ksm/run',
|
|
318
|
+
`echo ${this.#pagestoscan} > /sys/kernel/mm/ksm/pages_to_scan`,
|
|
319
|
+
`echo ${this.#sleepmillisecs} > /sys/kernel/mm/ksm/sleep_millisecs`,
|
|
320
|
+
`echo ${this.#mergeacrossnodes ? 1 : 0} > /sys/kernel/mm/ksm/merge_across_nodes`,
|
|
321
|
+
];
|
|
322
|
+
}
|
|
323
|
+
/** Disables KSM, leaving the scan counters untouched. */
|
|
324
|
+
disable() {
|
|
325
|
+
this.#enabled = false;
|
|
326
|
+
return ['echo 0 > /sys/kernel/mm/ksm/run'];
|
|
327
|
+
}
|
|
328
|
+
/** Tunes the scanner aggressiveness. */
|
|
329
|
+
tune(pagestoscan, sleepmillisecs) {
|
|
330
|
+
this.#pagestoscan = Math.max(1, pagestoscan);
|
|
331
|
+
this.#sleepmillisecs = Math.max(1, sleepmillisecs);
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
/** Reports whether the controller is currently enabled. */
|
|
335
|
+
get running() {
|
|
336
|
+
return this.#enabled;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Estimates the deduplication ratio from the classic kernel counters:
|
|
340
|
+
* pages_shared counts merged unique pages and pages_sharing counts the
|
|
341
|
+
* extra references pointing at them.
|
|
342
|
+
*/
|
|
343
|
+
estimatesavings(pagesshared, pagessharing) {
|
|
344
|
+
const total = pagesshared + pagessharing;
|
|
345
|
+
const ratio = total === 0 ? 0 : Number((pagessharing / total).toFixed(4));
|
|
346
|
+
const freedmb = Number(((pagessharing * 4) / 1024).toFixed(2));
|
|
347
|
+
return { ratio, freedmb };
|
|
348
|
+
}
|
|
349
|
+
/** Renders the sysfs plan for logs and provisioners. */
|
|
350
|
+
describe() {
|
|
351
|
+
return {
|
|
352
|
+
subsystem: 'ksm',
|
|
353
|
+
enabled: this.#enabled,
|
|
354
|
+
pagestoscan: this.#pagestoscan,
|
|
355
|
+
sleepmillisecs: this.#sleepmillisecs,
|
|
356
|
+
mergeacrossnodes: this.#mergeacrossnodes,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Virtio ballooning controller. The balloon lets the host reclaim guest
|
|
362
|
+
* memory on demand and gives it back under pressure; the controller tracks
|
|
363
|
+
* the current target and honors a deflate-on-OOM policy so guests never
|
|
364
|
+
* deadlock while the balloon is inflated.
|
|
365
|
+
*/
|
|
366
|
+
export class ballooningcontroller {
|
|
367
|
+
#targetmb;
|
|
368
|
+
#maxmb;
|
|
369
|
+
#deflateonom;
|
|
370
|
+
constructor(maxmb) {
|
|
371
|
+
this.#targetmb = 0;
|
|
372
|
+
this.#maxmb = maxmb;
|
|
373
|
+
this.#deflateonom = true;
|
|
374
|
+
}
|
|
375
|
+
/** Inflates the balloon to a target in MB, clamped to the maximum. */
|
|
376
|
+
inflate(targetmb) {
|
|
377
|
+
try {
|
|
378
|
+
if (targetmb < 0) {
|
|
379
|
+
throw new memoryerror(`balloon target must be non-negative, received ${targetmb}`);
|
|
380
|
+
}
|
|
381
|
+
this.#targetmb = Math.min(targetmb, this.#maxmb);
|
|
382
|
+
return this.#targetmb;
|
|
383
|
+
}
|
|
384
|
+
catch (cause) {
|
|
385
|
+
throw new memoryerror('ballooningcontroller.inflate failed', { cause: cause });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/** Deflates the balloon by the requested amount, floored at zero. */
|
|
389
|
+
deflate(amountmb) {
|
|
390
|
+
this.#targetmb = Math.max(0, this.#targetmb - Math.max(0, amountmb));
|
|
391
|
+
return this.#targetmb;
|
|
392
|
+
}
|
|
393
|
+
/** Current balloon target in MB. */
|
|
394
|
+
get targetmb() {
|
|
395
|
+
return this.#targetmb;
|
|
396
|
+
}
|
|
397
|
+
/** Renders the QEMU device plan for the balloon. */
|
|
398
|
+
describe() {
|
|
399
|
+
return {
|
|
400
|
+
subsystem: 'ballooning',
|
|
401
|
+
device: 'virtio-balloon-pci',
|
|
402
|
+
targetmb: this.#targetmb,
|
|
403
|
+
maxmb: this.#maxmb,
|
|
404
|
+
deflateonom: this.#deflateonom,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Swap overcommit controller implementing the vm.overcommit_memory=2
|
|
410
|
+
* semantics documented in Documentation/mm/overcommit-accounting.rst: the
|
|
411
|
+
* total committed address space may never exceed swap plus a configurable
|
|
412
|
+
* percentage of RAM (overcommit_ratio, default 50). The controller computes
|
|
413
|
+
* the commit limit and recommends swap sizing for a target commit budget.
|
|
414
|
+
*/
|
|
415
|
+
export class overcommitcontroller {
|
|
416
|
+
#ratio;
|
|
417
|
+
#mode;
|
|
418
|
+
constructor(ratio = 50) {
|
|
419
|
+
this.#ratio = ratio;
|
|
420
|
+
this.#mode = 2;
|
|
421
|
+
}
|
|
422
|
+
/** Sets the overcommit ratio percentage applied in mode 2. */
|
|
423
|
+
setratio(ratio) {
|
|
424
|
+
if (!Number.isFinite(ratio) || ratio < 0 || ratio > 100) {
|
|
425
|
+
throw new memoryerror(`overcommit ratio must be between 0 and 100, received ${ratio}`);
|
|
426
|
+
}
|
|
427
|
+
this.#ratio = ratio;
|
|
428
|
+
return this;
|
|
429
|
+
}
|
|
430
|
+
/** Computes the commit limit in GB for a RAM and swap configuration. */
|
|
431
|
+
commitlimitgb(ramgb, swapgb) {
|
|
432
|
+
return Number((swapgb + (ramgb * this.#ratio) / 100).toFixed(2));
|
|
433
|
+
}
|
|
434
|
+
/** Recommends the swap size in GB for a desired commit budget. */
|
|
435
|
+
recommendswapgb(ramgb, targetcommitgb) {
|
|
436
|
+
const swapneeded = targetcommitgb - (ramgb * this.#ratio) / 100;
|
|
437
|
+
return Number(Math.max(0, Math.ceil(swapneeded)).toFixed(2));
|
|
438
|
+
}
|
|
439
|
+
/** Renders the sysctl dictionary for this controller. */
|
|
440
|
+
sysctl() {
|
|
441
|
+
return {
|
|
442
|
+
'vm.overcommit_memory': String(this.#mode),
|
|
443
|
+
'vm.overcommit_ratio': String(this.#ratio),
|
|
444
|
+
'vm.swappiness': '10',
|
|
445
|
+
'vm.max_map_count': '1048576',
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
/** Renders the controller plan for logs and provisioners. */
|
|
449
|
+
describe() {
|
|
450
|
+
return { subsystem: 'overcommit', mode: this.#mode, ratio: this.#ratio };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Plans swap devices for a RAM size. ZRAM (compressed in-memory swap,
|
|
455
|
+
* priority 100) covers latency-sensitive reclaim while a swapfile or
|
|
456
|
+
* partition (priority -2) extends the commit budget required by mode 2
|
|
457
|
+
* overcommit for RAM-heavy builds.
|
|
458
|
+
*/
|
|
459
|
+
export function planswapdevices(ramgb) {
|
|
460
|
+
try {
|
|
461
|
+
if (!Number.isFinite(ramgb) || ramgb <= 0) {
|
|
462
|
+
throw new memoryerror(`ramgb must be a positive number, received ${ramgb}`);
|
|
463
|
+
}
|
|
464
|
+
const zramsize = Math.min(Math.max(Math.round(ramgb / 8), 2), 32);
|
|
465
|
+
const devices = [
|
|
466
|
+
{ kind: 'zram', sizegb: zramsize, priority: 100, compressed: true },
|
|
467
|
+
];
|
|
468
|
+
if (ramgb >= 256) {
|
|
469
|
+
devices.push({
|
|
470
|
+
kind: 'swapfile',
|
|
471
|
+
sizegb: Math.min(Math.round(ramgb / 8), 128),
|
|
472
|
+
priority: -2,
|
|
473
|
+
compressed: false,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return devices;
|
|
477
|
+
}
|
|
478
|
+
catch (cause) {
|
|
479
|
+
if (cause instanceof memoryerror) {
|
|
480
|
+
throw cause;
|
|
481
|
+
}
|
|
482
|
+
throw new memoryerror('planswapdevices failed', { cause: cause });
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Tmpfs shader cache controller. Mesa keeps its on-disk shader cache in
|
|
487
|
+
* MESA_SHADER_CACHE_DIR; hosting it on a size-capped tmpfs (20 GB by
|
|
488
|
+
* default) removes disk latency from shader compilation storms typical of
|
|
489
|
+
* LLVMpipe and Lavapipe sessions.
|
|
490
|
+
*/
|
|
491
|
+
export class shadercachecontroller {
|
|
492
|
+
#sizegb;
|
|
493
|
+
#mountpoint;
|
|
494
|
+
constructor(sizegb = 20, mountpoint = '/tmp/shadercache') {
|
|
495
|
+
this.#sizegb = sizegb;
|
|
496
|
+
this.#mountpoint = mountpoint;
|
|
497
|
+
}
|
|
498
|
+
/** Renders the fstab line for the tmpfs mount. */
|
|
499
|
+
fstabline() {
|
|
500
|
+
return `tmpfs ${this.#mountpoint} tmpfs rw,size=${this.#sizegb}g,nodev,nosuid,noatime 0 0`;
|
|
501
|
+
}
|
|
502
|
+
/** Renders the environment block pointing Mesa at the cache. */
|
|
503
|
+
environment() {
|
|
504
|
+
return {
|
|
505
|
+
MESA_SHADER_CACHE_DIR: this.#mountpoint,
|
|
506
|
+
MESA_SHADER_CACHE_MAX_SIZE: `${this.#sizegb * 1024}`,
|
|
507
|
+
MESA_GLSL_CACHE_DISABLE: 'false',
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/** Mount size in GB. */
|
|
511
|
+
get sizegb() {
|
|
512
|
+
return this.#sizegb;
|
|
513
|
+
}
|
|
514
|
+
/** Renders the controller plan for logs and provisioners. */
|
|
515
|
+
describe() {
|
|
516
|
+
return { subsystem: 'shadercache', sizegb: this.#sizegb, mountpoint: this.#mountpoint };
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Generates Docker memory flags for a container. MemorySwap -1 keeps swap
|
|
521
|
+
* unlimited as documented by Docker ("unlimited swap, up to the amount
|
|
522
|
+
* available on the host system"), ShmSize 2g raises /dev/shm from the 64 MB
|
|
523
|
+
* default which is the canonical fix for the PyTorch DataLoader bus error,
|
|
524
|
+
* and the tmpfs entry mounts the 20 GB shader cache. Every value remains a
|
|
525
|
+
* suggestion: callers override before use.
|
|
526
|
+
*/
|
|
527
|
+
export function dockerMemoryFlags(options) {
|
|
528
|
+
try {
|
|
529
|
+
const ramgb = options?.ramgb ?? 32;
|
|
530
|
+
const shmsizegb = options?.shmsizegb ?? 2;
|
|
531
|
+
const unlimitedswap = options?.unlimitedswap ?? true;
|
|
532
|
+
const run = [
|
|
533
|
+
`--memory=${ramgb}g`,
|
|
534
|
+
unlimitedswap ? '--memory-swap=-1' : `--memory-swap=${ramgb * 2}g`,
|
|
535
|
+
`--shm-size=${shmsizegb}g`,
|
|
536
|
+
'--tmpfs /tmp/shadercache:size=20g',
|
|
537
|
+
'--kernel-memory=0',
|
|
538
|
+
];
|
|
539
|
+
const compose = {
|
|
540
|
+
mem_limit: `${ramgb}g`,
|
|
541
|
+
memswap_limit: unlimitedswap ? -1 : `${ramgb * 2}g`,
|
|
542
|
+
shm_size: `${shmsizegb}g`,
|
|
543
|
+
tmpfs: ['/tmp/shadercache:size=20g'],
|
|
544
|
+
oom_score_adj: -500,
|
|
545
|
+
};
|
|
546
|
+
const sysctl = {
|
|
547
|
+
'vm.overcommit_memory': '2',
|
|
548
|
+
'vm.overcommit_ratio': '50',
|
|
549
|
+
};
|
|
550
|
+
const rationale = [
|
|
551
|
+
'memory-swap -1 grants unlimited swap per the Docker resource constraints documentation',
|
|
552
|
+
`shm-size ${shmsizegb}g avoids the PyTorch DataLoader bus error caused by the 64 MB /dev/shm default`,
|
|
553
|
+
'tmpfs shader cache keeps Mesa compilation artifacts in RAM with a 20 GB ceiling',
|
|
554
|
+
'overcommit mode 2 with ratio 50 bounds the commit limit to swap plus half the RAM',
|
|
555
|
+
];
|
|
556
|
+
return { run, compose, sysctl, rationale };
|
|
557
|
+
}
|
|
558
|
+
catch (cause) {
|
|
559
|
+
throw new memoryerror('dockerMemoryFlags failed', { cause: cause });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/** Shared transfer math used by every controller implementation. */
|
|
563
|
+
function simulatetransfer(tier, bytes, direction, bandwidthgbs, latencyns) {
|
|
564
|
+
const effectivengbs = Number((bandwidthgbs * (direction === 'read' ? 1 : 0.92)).toFixed(2));
|
|
565
|
+
const timemicros = Number(((bytes / (effectivengbs * 1e9)) * 1e6 + latencyns / 1000).toFixed(4));
|
|
566
|
+
return { tier, bytes, direction, effectivengbs, timemicros };
|
|
567
|
+
}
|
|
568
|
+
/** DDR5 controller: channels aggregated, bandwidth scales linearly. */
|
|
569
|
+
export class ddr5controller {
|
|
570
|
+
tier = 'ddr5';
|
|
571
|
+
#channels;
|
|
572
|
+
#totalgb;
|
|
573
|
+
constructor(channels = 12, totalgb = 1024) {
|
|
574
|
+
this.#channels = Math.max(1, Math.min(channels, 12));
|
|
575
|
+
this.#totalgb = totalgb;
|
|
576
|
+
}
|
|
577
|
+
/** Aggregate bandwidth in GB/s for the configured channel count. */
|
|
578
|
+
get bandwidthgbs() {
|
|
579
|
+
return Number((MEMORY_TIERS.ddr5.perchannelgbs * this.#channels).toFixed(1));
|
|
580
|
+
}
|
|
581
|
+
/** Total capacity in GB. */
|
|
582
|
+
get totalgb() {
|
|
583
|
+
return this.#totalgb;
|
|
584
|
+
}
|
|
585
|
+
/** Simulates one transfer over the DDR5 interconnect. */
|
|
586
|
+
transfer(bytes, direction) {
|
|
587
|
+
return simulatetransfer(this.tier, bytes, direction, this.bandwidthgbs, MEMORY_TIERS.ddr5.latencyns);
|
|
588
|
+
}
|
|
589
|
+
/** Average column access latency in nanoseconds. */
|
|
590
|
+
latencyns() {
|
|
591
|
+
return MEMORY_TIERS.ddr5.latencyns;
|
|
592
|
+
}
|
|
593
|
+
/** Renders the controller plan for logs and provisioners. */
|
|
594
|
+
describe() {
|
|
595
|
+
return {
|
|
596
|
+
tier: this.tier,
|
|
597
|
+
channels: this.#channels,
|
|
598
|
+
bandwidthgbs: this.bandwidthgbs,
|
|
599
|
+
totalgb: this.#totalgb,
|
|
600
|
+
technology: MEMORY_TIERS.ddr5.technology,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/** HBM3e controller: eight stacks, 8 TB/s aggregate, near-SRAM latency. */
|
|
605
|
+
export class hbm3econtroller {
|
|
606
|
+
tier = 'hbm3e';
|
|
607
|
+
#stacks;
|
|
608
|
+
#totalgb;
|
|
609
|
+
constructor(stacks = 8, totalgb = 192) {
|
|
610
|
+
this.#stacks = Math.max(1, Math.min(stacks, 8));
|
|
611
|
+
this.#totalgb = totalgb;
|
|
612
|
+
}
|
|
613
|
+
/** Aggregate bandwidth in GB/s for the configured stack count. */
|
|
614
|
+
get bandwidthgbs() {
|
|
615
|
+
return Number((MEMORY_TIERS.hbm3e.perchannelgbs * this.#stacks).toFixed(1));
|
|
616
|
+
}
|
|
617
|
+
/** Total capacity in GB. */
|
|
618
|
+
get totalgb() {
|
|
619
|
+
return this.#totalgb;
|
|
620
|
+
}
|
|
621
|
+
/** Simulates one transfer over the HBM3e fabric. */
|
|
622
|
+
transfer(bytes, direction) {
|
|
623
|
+
return simulatetransfer(this.tier, bytes, direction, this.bandwidthgbs, MEMORY_TIERS.hbm3e.latencyns);
|
|
624
|
+
}
|
|
625
|
+
/** Stack access latency in nanoseconds. */
|
|
626
|
+
latencyns() {
|
|
627
|
+
return MEMORY_TIERS.hbm3e.latencyns;
|
|
628
|
+
}
|
|
629
|
+
/** Renders the controller plan for logs and provisioners. */
|
|
630
|
+
describe() {
|
|
631
|
+
return {
|
|
632
|
+
tier: this.tier,
|
|
633
|
+
stacks: this.#stacks,
|
|
634
|
+
bandwidthgbs: this.bandwidthgbs,
|
|
635
|
+
totalgb: this.#totalgb,
|
|
636
|
+
technology: MEMORY_TIERS.hbm3e.technology,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/** GDDR7 controller: 512-bit bus, 1792 GB/s at full width. */
|
|
641
|
+
export class gddr7controller {
|
|
642
|
+
tier = 'gddr7';
|
|
643
|
+
#channels;
|
|
644
|
+
#totalgb;
|
|
645
|
+
constructor(channels = 8, totalgb = 96) {
|
|
646
|
+
this.#channels = Math.max(1, Math.min(channels, 8));
|
|
647
|
+
this.#totalgb = totalgb;
|
|
648
|
+
}
|
|
649
|
+
/** Aggregate bandwidth in GB/s for the configured bus width. */
|
|
650
|
+
get bandwidthgbs() {
|
|
651
|
+
return Number((MEMORY_TIERS.gddr7.perchannelgbs * this.#channels).toFixed(1));
|
|
652
|
+
}
|
|
653
|
+
/** Total capacity in GB. */
|
|
654
|
+
get totalgb() {
|
|
655
|
+
return this.#totalgb;
|
|
656
|
+
}
|
|
657
|
+
/** Simulates one transfer over the GDDR7 bus. */
|
|
658
|
+
transfer(bytes, direction) {
|
|
659
|
+
return simulatetransfer(this.tier, bytes, direction, this.bandwidthgbs, MEMORY_TIERS.gddr7.latencyns);
|
|
660
|
+
}
|
|
661
|
+
/** Memory access latency in nanoseconds. */
|
|
662
|
+
latencyns() {
|
|
663
|
+
return MEMORY_TIERS.gddr7.latencyns;
|
|
664
|
+
}
|
|
665
|
+
/** Renders the controller plan for logs and provisioners. */
|
|
666
|
+
describe() {
|
|
667
|
+
return {
|
|
668
|
+
tier: this.tier,
|
|
669
|
+
channels: this.#channels,
|
|
670
|
+
bandwidthgbs: this.bandwidthgbs,
|
|
671
|
+
totalgb: this.#totalgb,
|
|
672
|
+
technology: MEMORY_TIERS.gddr7.technology,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Controller factory (Factory plus Registry patterns): maps a tier
|
|
678
|
+
* identifier to a controller instance sized by the caller. The const type
|
|
679
|
+
* parameter preserves literal tier identifiers through the call.
|
|
680
|
+
*/
|
|
681
|
+
export function creatememorycontroller(tier, totalgb, channels) {
|
|
682
|
+
switch (tier) {
|
|
683
|
+
case 'ddr5':
|
|
684
|
+
return new ddr5controller(channels ?? 12, totalgb);
|
|
685
|
+
case 'hbm3e':
|
|
686
|
+
return new hbm3econtroller(channels ?? 8, totalgb);
|
|
687
|
+
case 'gddr7':
|
|
688
|
+
return new gddr7controller(channels ?? 8, totalgb);
|
|
689
|
+
default: {
|
|
690
|
+
throw new memoryerror(`no controller registered for tier "${tier}"`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/** A virtual memory region bound to a tier and a purpose. */
|
|
695
|
+
export class memoryregion {
|
|
696
|
+
id;
|
|
697
|
+
tier;
|
|
698
|
+
purpose;
|
|
699
|
+
sizegb;
|
|
700
|
+
#allocated;
|
|
701
|
+
#accesses;
|
|
702
|
+
constructor(tier, purpose, sizegb) {
|
|
703
|
+
this.id = randomUUID();
|
|
704
|
+
this.tier = tier;
|
|
705
|
+
this.purpose = purpose;
|
|
706
|
+
this.sizegb = sizegb;
|
|
707
|
+
this.#allocated = true;
|
|
708
|
+
this.#accesses = 0;
|
|
709
|
+
}
|
|
710
|
+
/** Number of tracked accesses through proxies or direct reads. */
|
|
711
|
+
get accesses() {
|
|
712
|
+
return this.#accesses;
|
|
713
|
+
}
|
|
714
|
+
/** True while the region holds virtual backing. */
|
|
715
|
+
get allocated() {
|
|
716
|
+
return this.#allocated;
|
|
717
|
+
}
|
|
718
|
+
/** Counts one access; used by the guarding Proxy. */
|
|
719
|
+
touch() {
|
|
720
|
+
this.#accesses += 1;
|
|
721
|
+
}
|
|
722
|
+
/** Releases the region; idempotent and safe to call twice. */
|
|
723
|
+
release() {
|
|
724
|
+
this.#allocated = false;
|
|
725
|
+
}
|
|
726
|
+
/** Synchronous disposer so regions compose with `using` declarations. */
|
|
727
|
+
[Symbol.dispose]() {
|
|
728
|
+
this.release();
|
|
729
|
+
}
|
|
730
|
+
/** Renders the region plan for logs and provisioners. */
|
|
731
|
+
describe() {
|
|
732
|
+
return {
|
|
733
|
+
id: this.id,
|
|
734
|
+
tier: this.tier,
|
|
735
|
+
purpose: this.purpose,
|
|
736
|
+
sizegb: this.sizegb,
|
|
737
|
+
allocated: this.#allocated,
|
|
738
|
+
accesses: this.#accesses,
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Fluent builder for memory regions. The builder accumulates tier, purpose
|
|
744
|
+
* and size, validates the result against the tier capacity and produces a
|
|
745
|
+
* memoryregion registered in the regionledger.
|
|
746
|
+
*/
|
|
747
|
+
export class regionbuilder {
|
|
748
|
+
#tier;
|
|
749
|
+
#purpose;
|
|
750
|
+
#sizegb;
|
|
751
|
+
constructor() {
|
|
752
|
+
this.#tier = 'ddr5';
|
|
753
|
+
this.#purpose = 'guest';
|
|
754
|
+
this.#sizegb = 1;
|
|
755
|
+
}
|
|
756
|
+
/** Selects the memory tier backing the region. */
|
|
757
|
+
withtier(tier) {
|
|
758
|
+
this.#tier = tier;
|
|
759
|
+
return this;
|
|
760
|
+
}
|
|
761
|
+
/** Selects the purpose tag of the region. */
|
|
762
|
+
withpurpose(purpose) {
|
|
763
|
+
this.#purpose = purpose;
|
|
764
|
+
return this;
|
|
765
|
+
}
|
|
766
|
+
/** Sets the region size in GB. */
|
|
767
|
+
withsize(sizegb) {
|
|
768
|
+
this.#sizegb = sizegb;
|
|
769
|
+
return this;
|
|
770
|
+
}
|
|
771
|
+
/** Validates and materializes the region, registering it in the ledger. */
|
|
772
|
+
build() {
|
|
773
|
+
try {
|
|
774
|
+
if (!Number.isFinite(this.#sizegb) || this.#sizegb <= 0) {
|
|
775
|
+
throw new memoryerror(`region size must be a positive number, received ${this.#sizegb}`);
|
|
776
|
+
}
|
|
777
|
+
const tier = selecttier(this.#tier);
|
|
778
|
+
const capacity = tier.modulegb * tier.channels;
|
|
779
|
+
if (this.#sizegb > capacity) {
|
|
780
|
+
throw new memoryerror(`region of ${this.#sizegb} GB exceeds the ${capacity} GB capacity of tier ${this.#tier}`);
|
|
781
|
+
}
|
|
782
|
+
const region = new memoryregion(this.#tier, this.#purpose, this.#sizegb);
|
|
783
|
+
regionledger.set(region.id, region);
|
|
784
|
+
return region;
|
|
785
|
+
}
|
|
786
|
+
catch (cause) {
|
|
787
|
+
if (cause instanceof memoryerror) {
|
|
788
|
+
throw cause;
|
|
789
|
+
}
|
|
790
|
+
throw new memoryerror('regionbuilder.build failed', { cause: cause });
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Wraps a region in a guarding Proxy (Proxy pattern). Every property access
|
|
796
|
+
* is counted and recorded in the access ledger; accesses to released
|
|
797
|
+
* regions raise a memoryerror so consumers notice stale handles
|
|
798
|
+
* immediately instead of silently reading freed virtual memory.
|
|
799
|
+
*/
|
|
800
|
+
export function guardregion(region) {
|
|
801
|
+
return new Proxy(region, {
|
|
802
|
+
get(target, property, receiver) {
|
|
803
|
+
target.touch();
|
|
804
|
+
if (!target.allocated && property !== 'describe') {
|
|
805
|
+
throw new memoryerror(`region ${target.id} has been released and can no longer be accessed`);
|
|
806
|
+
}
|
|
807
|
+
return Reflect.get(target, property, receiver);
|
|
808
|
+
},
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
/** Ledger of every live region, keyed by region id (Map first policy). */
|
|
812
|
+
export const regionledger = new Map();
|
|
813
|
+
/** Ephemeral access counters; WeakMap keeps probes from pinning regions. */
|
|
814
|
+
const accessprobes = new WeakMap();
|
|
815
|
+
/** Records an access probe for a region without keeping it alive. */
|
|
816
|
+
export function proberegion(region, accesses) {
|
|
817
|
+
accessprobes.set(region, accesses);
|
|
818
|
+
}
|
|
819
|
+
/** Reads the last access probe recorded for a region, if any. */
|
|
820
|
+
export function readprobe(region) {
|
|
821
|
+
return accessprobes.get(region) ?? null;
|
|
822
|
+
}
|
|
823
|
+
/** Sums the size of every allocated region in the ledger. */
|
|
824
|
+
export function totalallocatedgb() {
|
|
825
|
+
let total = 0;
|
|
826
|
+
for (const region of regionledger.values()) {
|
|
827
|
+
if (region.allocated) {
|
|
828
|
+
total += region.sizegb;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return Number(total.toFixed(2));
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Memory pressure observer. The observer keeps listeners in a Map keyed by
|
|
835
|
+
* name (never bound to a runtime event loop) and evaluates samples against
|
|
836
|
+
* the classic watermarks: low at 85 percent, high at 95 percent and
|
|
837
|
+
* critical at 99 percent of the tracked capacity.
|
|
838
|
+
*/
|
|
839
|
+
export class memorystats {
|
|
840
|
+
#listeners;
|
|
841
|
+
#capacitygb;
|
|
842
|
+
#lastlevel;
|
|
843
|
+
#history;
|
|
844
|
+
constructor(capacitygb) {
|
|
845
|
+
this.#listeners = new Map();
|
|
846
|
+
this.#capacitygb = capacitygb;
|
|
847
|
+
this.#lastlevel = 'nominal';
|
|
848
|
+
this.#history = [];
|
|
849
|
+
}
|
|
850
|
+
/** Subscribes a named listener to pressure transitions. */
|
|
851
|
+
subscribe(name, listener) {
|
|
852
|
+
this.#listeners.set(name, listener);
|
|
853
|
+
return this;
|
|
854
|
+
}
|
|
855
|
+
/** Removes a named listener; returns true when it existed. */
|
|
856
|
+
unsubscribe(name) {
|
|
857
|
+
return this.#listeners.delete(name);
|
|
858
|
+
}
|
|
859
|
+
/** Evaluates one used-memory sample and publishes level transitions. */
|
|
860
|
+
evaluate(usedgb) {
|
|
861
|
+
try {
|
|
862
|
+
const usedfraction = Math.min(1, usedgb / this.#capacitygb);
|
|
863
|
+
this.#history.push(usedfraction);
|
|
864
|
+
if (this.#history.length > 512) {
|
|
865
|
+
this.#history.shift();
|
|
866
|
+
}
|
|
867
|
+
const level = usedfraction >= 0.99
|
|
868
|
+
? 'critical'
|
|
869
|
+
: usedfraction >= 0.95
|
|
870
|
+
? 'high'
|
|
871
|
+
: usedfraction >= 0.85
|
|
872
|
+
? 'low'
|
|
873
|
+
: 'nominal';
|
|
874
|
+
if (level === 'nominal' || level === this.#lastlevel) {
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
this.#lastlevel = level;
|
|
878
|
+
const event = {
|
|
879
|
+
level,
|
|
880
|
+
usedfraction: Number(usedfraction.toFixed(4)),
|
|
881
|
+
at: Date.now(),
|
|
882
|
+
};
|
|
883
|
+
for (const listener of this.#listeners.values()) {
|
|
884
|
+
try {
|
|
885
|
+
listener(event);
|
|
886
|
+
}
|
|
887
|
+
catch {
|
|
888
|
+
/* listener faults never break the observer loop */
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
return event;
|
|
892
|
+
}
|
|
893
|
+
catch (cause) {
|
|
894
|
+
throw new memoryerror('memorystats.evaluate failed', { cause: cause });
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
/** Peak observed usage fraction since construction. */
|
|
898
|
+
get peak() {
|
|
899
|
+
return this.#history.length === 0 ? 0 : Number(Math.max(...this.#history).toFixed(4));
|
|
900
|
+
}
|
|
901
|
+
/** Mean observed usage fraction since construction. */
|
|
902
|
+
get mean() {
|
|
903
|
+
if (this.#history.length === 0) {
|
|
904
|
+
return 0;
|
|
905
|
+
}
|
|
906
|
+
const sum = this.#history.reduce((acc, value) => acc + value, 0);
|
|
907
|
+
return Number((sum / this.#history.length).toFixed(4));
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Computes the full memory plan for a machine: bandwidth per tier, MIG
|
|
912
|
+
* instance count, huge page layout, swap devices, NUMA topology, /dev/shm
|
|
913
|
+
* size and the shader cache ceiling.
|
|
914
|
+
*/
|
|
915
|
+
export function planmemory(vcpus, ramgb, vramgb, mig, tier) {
|
|
916
|
+
try {
|
|
917
|
+
const profile = getmigprofile(mig);
|
|
918
|
+
const miginstances = profile === null
|
|
919
|
+
? 1
|
|
920
|
+
: Math.max(1, Math.floor(memorylimits.maxvramgb / profile.slicegb) *
|
|
921
|
+
(vramgb >= profile.slicegb ? 1 : 0) || 1);
|
|
922
|
+
const numa = buildnumatopology(vcpus, vcpus >= 128 ? 8 : vcpus >= 64 ? 4 : vcpus >= 32 ? 2 : 1);
|
|
923
|
+
return Object.freeze({
|
|
924
|
+
vcpus,
|
|
925
|
+
ramgb,
|
|
926
|
+
vramgb,
|
|
927
|
+
tier,
|
|
928
|
+
mig,
|
|
929
|
+
miginstances: profile === null ? 1 : Math.min(profile.maxinstances, miginstances),
|
|
930
|
+
rambandwidthgbs: tierbandwidthgbs('ddr5'),
|
|
931
|
+
vrambandwidthgbs: tierbandwidthgbs(tier === 'ddr5' ? 'gddr7' : tier),
|
|
932
|
+
hugepages: planhugepages(ramgb),
|
|
933
|
+
swapdevices: planswapdevices(ramgb),
|
|
934
|
+
numa,
|
|
935
|
+
shmsizegb: 2,
|
|
936
|
+
shadercachesizegb: 20,
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
catch (cause) {
|
|
940
|
+
if (cause instanceof memoryerror) {
|
|
941
|
+
throw cause;
|
|
942
|
+
}
|
|
943
|
+
throw new memoryerror('planmemory failed', { cause: cause });
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Renders kernel boot arguments for the memory techniques enabled by a
|
|
948
|
+
* plan: default 1 GB huge pages plus a 2 MB pool, and the MIG-aware NUMA
|
|
949
|
+
* hints consumed by the virtual machine launcher.
|
|
950
|
+
*/
|
|
951
|
+
export function buildKernelArgs(plan) {
|
|
952
|
+
try {
|
|
953
|
+
const parts = ['default_hugepagesz=1G'];
|
|
954
|
+
for (const page of plan.hugepages) {
|
|
955
|
+
parts.push(`hugepagesz=${page.size === '1G' ? '1G' : '2M'}`, `hugepages=${page.count}`);
|
|
956
|
+
}
|
|
957
|
+
parts.push(`numa=fake=${plan.numa.nodes}`);
|
|
958
|
+
return parts.join(' ');
|
|
959
|
+
}
|
|
960
|
+
catch (cause) {
|
|
961
|
+
throw new memoryerror('buildKernelArgs failed', { cause: cause });
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Facade that composes every memory technique for one machine. The manager
|
|
966
|
+
* exposes the plan, the sysctl dictionary, the kernel arguments, the Docker
|
|
967
|
+
* flags, the tier controllers and the pressure observer, and keeps all
|
|
968
|
+
* mutable state in Maps so the instance stays runtime agnostic.
|
|
969
|
+
*/
|
|
970
|
+
export class virtualmemorymanager {
|
|
971
|
+
#plan;
|
|
972
|
+
#controllers;
|
|
973
|
+
#ksm;
|
|
974
|
+
#balloon;
|
|
975
|
+
#overcommit;
|
|
976
|
+
#cache;
|
|
977
|
+
#stats;
|
|
978
|
+
constructor(plan) {
|
|
979
|
+
this.#plan = plan;
|
|
980
|
+
this.#controllers = new Map();
|
|
981
|
+
this.#controllers.set('ddr5', creatememorycontroller('ddr5', plan.ramgb, 12));
|
|
982
|
+
this.#controllers.set(plan.tier === 'ddr5' ? 'gddr7' : plan.tier, creatememorycontroller(plan.tier === 'ddr5' ? 'gddr7' : plan.tier, plan.vramgb, 8));
|
|
983
|
+
this.#ksm = new ksmcontroller(1000, 20);
|
|
984
|
+
this.#balloon = new ballooningcontroller(plan.ramgb * 1024);
|
|
985
|
+
this.#overcommit = new overcommitcontroller(50);
|
|
986
|
+
this.#cache = new shadercachecontroller(plan.shadercachesizegb);
|
|
987
|
+
this.#stats = new memorystats(plan.ramgb);
|
|
988
|
+
}
|
|
989
|
+
/** The frozen plan this manager was constructed from. */
|
|
990
|
+
get plan() {
|
|
991
|
+
return this.#plan;
|
|
992
|
+
}
|
|
993
|
+
/** The pressure observer of this machine. */
|
|
994
|
+
get stats() {
|
|
995
|
+
return this.#stats;
|
|
996
|
+
}
|
|
997
|
+
/** The KSM controller of this machine. */
|
|
998
|
+
get ksm() {
|
|
999
|
+
return this.#ksm;
|
|
1000
|
+
}
|
|
1001
|
+
/** The ballooning controller of this machine. */
|
|
1002
|
+
get balloon() {
|
|
1003
|
+
return this.#balloon;
|
|
1004
|
+
}
|
|
1005
|
+
/** Returns the controller bound to a tier, when configured. */
|
|
1006
|
+
controller(tier) {
|
|
1007
|
+
return this.#controllers.get(tier) ?? null;
|
|
1008
|
+
}
|
|
1009
|
+
/** Simulates a synthetic workload sample and evaluates pressure. */
|
|
1010
|
+
sample() {
|
|
1011
|
+
const usedgb = Number((this.#plan.ramgb * (randomInt(600, 990) / 1000)).toFixed(2));
|
|
1012
|
+
return this.#stats.evaluate(usedgb);
|
|
1013
|
+
}
|
|
1014
|
+
/** Renders the sysctl dictionary of every controller. */
|
|
1015
|
+
sysctl() {
|
|
1016
|
+
return { ...this.#overcommit.sysctl() };
|
|
1017
|
+
}
|
|
1018
|
+
/** Renders the kernel boot arguments for this machine. */
|
|
1019
|
+
kernelargs() {
|
|
1020
|
+
return buildKernelArgs(this.#plan);
|
|
1021
|
+
}
|
|
1022
|
+
/** Renders the Docker memory flags for this machine. */
|
|
1023
|
+
dockerflags() {
|
|
1024
|
+
return dockerMemoryFlags({
|
|
1025
|
+
ramgb: this.#plan.ramgb,
|
|
1026
|
+
shmsizegb: this.#plan.shmsizegb,
|
|
1027
|
+
unlimitedswap: true,
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
/** Renders the fstab line of the shader cache tmpfs. */
|
|
1031
|
+
cachefstab() {
|
|
1032
|
+
return this.#cache.fstabline();
|
|
1033
|
+
}
|
|
1034
|
+
/** Renders a complete JSON-safe description of the machine memory. */
|
|
1035
|
+
describe() {
|
|
1036
|
+
return {
|
|
1037
|
+
plan: this.#plan,
|
|
1038
|
+
controllers: [...this.#controllers.values()].map((controller) => controller.describe()),
|
|
1039
|
+
ksm: this.#ksm.describe(),
|
|
1040
|
+
ballooning: this.#balloon.describe(),
|
|
1041
|
+
overcommit: this.#overcommit.describe(),
|
|
1042
|
+
shadercache: this.#cache.describe(),
|
|
1043
|
+
sysctl: this.sysctl(),
|
|
1044
|
+
kernelargs: this.kernelargs(),
|
|
1045
|
+
stats: { peak: this.#stats.peak, mean: this.#stats.mean },
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Factory that builds a virtualmemorymanager from a raw request. The
|
|
1051
|
+
* request is validated against the hard limits; failures raise memoryerror
|
|
1052
|
+
* with the accumulated validation errors for quick diagnosis.
|
|
1053
|
+
*/
|
|
1054
|
+
export function createVirtualMemory(request) {
|
|
1055
|
+
try {
|
|
1056
|
+
const result = validatememoryrequest(request);
|
|
1057
|
+
if (!result.valid || result.plan === null) {
|
|
1058
|
+
throw new memoryerror(`invalid memory request: ${result.errors.join('; ')}`);
|
|
1059
|
+
}
|
|
1060
|
+
return new virtualmemorymanager(result.plan);
|
|
1061
|
+
}
|
|
1062
|
+
catch (cause) {
|
|
1063
|
+
if (cause instanceof memoryerror) {
|
|
1064
|
+
throw cause;
|
|
1065
|
+
}
|
|
1066
|
+
throw new memoryerror('createVirtualMemory failed', { cause: cause });
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
/* ------------------------------------------------------------------ */
|
|
1070
|
+
/* Section 12: host-aware extreme planner (memorymodularizer) */
|
|
1071
|
+
/* ------------------------------------------------------------------ */
|
|
1072
|
+
/**
|
|
1073
|
+
* Parses a key=value config file into a lowercase-keyed dictionary. Missing
|
|
1074
|
+
* files and parse failures degrade to an empty record; this is the safe
|
|
1075
|
+
* loader contract inherited from the Meta module.
|
|
1076
|
+
*/
|
|
1077
|
+
function loadkv(path) {
|
|
1078
|
+
const out = {};
|
|
1079
|
+
try {
|
|
1080
|
+
if (!existsSync(path)) {
|
|
1081
|
+
return out;
|
|
1082
|
+
}
|
|
1083
|
+
for (const raw of readFileSync(path, 'utf8').split('\n')) {
|
|
1084
|
+
const trimmed = raw.trim();
|
|
1085
|
+
if (trimmed.length === 0 || trimmed.startsWith('#') || trimmed.startsWith('[')) {
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
const eq = trimmed.indexOf('=');
|
|
1089
|
+
if (eq > -1) {
|
|
1090
|
+
const key = trimmed.slice(0, eq).trim().toLowerCase();
|
|
1091
|
+
const value = trimmed
|
|
1092
|
+
.slice(eq + 1)
|
|
1093
|
+
.trim()
|
|
1094
|
+
.replace(/^["']|["']$/g, '');
|
|
1095
|
+
out[key] = value;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
catch {
|
|
1100
|
+
/* unreadable config degrades to defaults */
|
|
1101
|
+
}
|
|
1102
|
+
return out;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Host-aware extreme memory planner, the fusion of the Meta
|
|
1106
|
+
* MemoryModularizer and ExtremeMemoryPlanner. The planner detects the real
|
|
1107
|
+
* host memory tiers (CXL fabric devices via lspci, PMEM DAX devdax nodes,
|
|
1108
|
+
* shared Blackwell HBM), tunes zram with workload-dependent compression
|
|
1109
|
+
* factors, plans host-aware huge pages, pins vCPUs across the 9950X3D CCD
|
|
1110
|
+
* layout, synthesizes the tiered QEMU arguments, exports cgroup v2 slices
|
|
1111
|
+
* and QMP runtime commands, and persists the plan as JSON. All host
|
|
1112
|
+
* interaction is best effort: every probe is wrapped in a catcher and
|
|
1113
|
+
* degrades to the documented defaults.
|
|
1114
|
+
*/
|
|
1115
|
+
export class memorymodularizer {
|
|
1116
|
+
#vmconfig;
|
|
1117
|
+
#qemuconfig;
|
|
1118
|
+
#passageconfig;
|
|
1119
|
+
#hostthreads;
|
|
1120
|
+
constructor(options) {
|
|
1121
|
+
const dir = options?.configdir ?? '.';
|
|
1122
|
+
this.#vmconfig = loadkv(`${dir}/vm.config`);
|
|
1123
|
+
this.#qemuconfig = loadkv(`${dir}/qemu.config`);
|
|
1124
|
+
this.#passageconfig = loadkv(`${dir}/passage.config`);
|
|
1125
|
+
this.#hostthreads = cpus().length;
|
|
1126
|
+
}
|
|
1127
|
+
/** Host logical thread count observed at construction. */
|
|
1128
|
+
get hostthreads() {
|
|
1129
|
+
return this.#hostthreads;
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Detects the real host tiers. DRAM splits 70/30 into fast and slow
|
|
1133
|
+
* bands; an lspci scan for CXL vendor 1e98 or an explicit qemu.config
|
|
1134
|
+
* cxl_enabled flag adds a CXL_FM tier; /dev/pmem0 adds a persistent
|
|
1135
|
+
* PMEM_DAX tier (128 GB, 300 ns, 15 GB/s); a shared_host_mem blackwell
|
|
1136
|
+
* flag exposes half of the GPU VRAM as HBM_BLACKWELL through memfd.
|
|
1137
|
+
*/
|
|
1138
|
+
detecthosttiers() {
|
|
1139
|
+
try {
|
|
1140
|
+
const totalmb = Math.floor(totalmem() / (1024 * 1024));
|
|
1141
|
+
const tiers = [
|
|
1142
|
+
{
|
|
1143
|
+
tier: 'DRAM_FAST',
|
|
1144
|
+
sizemb: Math.floor(totalmb * 0.7),
|
|
1145
|
+
latencyns: 78,
|
|
1146
|
+
bandwidthgbps: 89.6,
|
|
1147
|
+
qemubackend: 'memory-backend-ram',
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
tier: 'DRAM_SLOW',
|
|
1151
|
+
sizemb: Math.floor(totalmb * 0.3),
|
|
1152
|
+
latencyns: 95,
|
|
1153
|
+
bandwidthgbps: 67,
|
|
1154
|
+
qemubackend: 'memory-backend-ram',
|
|
1155
|
+
},
|
|
1156
|
+
];
|
|
1157
|
+
try {
|
|
1158
|
+
const lspci = execSync('lspci -d 1e98: 2>/dev/null || true', { encoding: 'utf8' });
|
|
1159
|
+
if (lspci.includes('CXL') || this.#qemuconfig.cxl_enabled === 'true') {
|
|
1160
|
+
tiers.push({
|
|
1161
|
+
tier: 'CXL_FM',
|
|
1162
|
+
sizemb: Number.parseInt(this.#qemuconfig.cxl_size_mb ?? '65536', 10),
|
|
1163
|
+
latencyns: 170,
|
|
1164
|
+
bandwidthgbps: 32,
|
|
1165
|
+
qemubackend: 'memory-backend-file',
|
|
1166
|
+
extraopts: { mem_path: '/dev/cxl/mem0', share: true },
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
catch {
|
|
1171
|
+
/* lspci unavailable: CXL tier stays hidden */
|
|
1172
|
+
}
|
|
1173
|
+
try {
|
|
1174
|
+
if (existsSync('/dev/pmem0')) {
|
|
1175
|
+
tiers.push({
|
|
1176
|
+
tier: 'PMEM_DAX',
|
|
1177
|
+
sizemb: 131072,
|
|
1178
|
+
latencyns: 300,
|
|
1179
|
+
bandwidthgbps: 15,
|
|
1180
|
+
persistent: true,
|
|
1181
|
+
qemubackend: 'memory-backend-file',
|
|
1182
|
+
extraopts: { mem_path: '/dev/pmem0', pmem: true },
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
catch {
|
|
1187
|
+
/* devdax probe failed: PMEM tier stays hidden */
|
|
1188
|
+
}
|
|
1189
|
+
if (this.#vmconfig.shared_host_mem === 'true') {
|
|
1190
|
+
const vramgb = Number(this.#vmconfig.blackwell_vram_gb ?? 32);
|
|
1191
|
+
tiers.push({
|
|
1192
|
+
tier: 'HBM_BLACKWELL',
|
|
1193
|
+
sizemb: Math.floor(vramgb * 1024 * 0.5),
|
|
1194
|
+
latencyns: 120,
|
|
1195
|
+
bandwidthgbps: 224,
|
|
1196
|
+
qemubackend: 'memory-backend-memfd',
|
|
1197
|
+
extraopts: { share: true },
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
return tiers;
|
|
1201
|
+
}
|
|
1202
|
+
catch (cause) {
|
|
1203
|
+
throw new memoryerror('detecthosttiers failed', { cause: cause });
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Plans zram sizing and command sequence for a requested capacity. The
|
|
1208
|
+
* compression factor adapts to the workload type from passage.config
|
|
1209
|
+
* (media_transcode 1.8, build 3.2, default 2.5) and to the algorithm
|
|
1210
|
+
* (zstd x1.15, lzo-rle x0.92); the disksize takes 30 percent of the
|
|
1211
|
+
* request and the returned commands cover modprobe, comp_algorithm,
|
|
1212
|
+
* disksize, mkswap and swapon at priority 100.
|
|
1213
|
+
*/
|
|
1214
|
+
configurezram(requestedmb, algorithm = 'zstd', workload) {
|
|
1215
|
+
try {
|
|
1216
|
+
if (!Number.isFinite(requestedmb) || requestedmb <= 0) {
|
|
1217
|
+
throw new memoryerror(`requestedmb must be a positive number, received ${requestedmb}`);
|
|
1218
|
+
}
|
|
1219
|
+
const kind = workload ?? this.#passageconfig.workload_type ?? 'general';
|
|
1220
|
+
let factor = 2.5;
|
|
1221
|
+
if (kind === 'media_transcode') {
|
|
1222
|
+
factor = 1.8;
|
|
1223
|
+
}
|
|
1224
|
+
if (kind === 'build') {
|
|
1225
|
+
factor = 3.2;
|
|
1226
|
+
}
|
|
1227
|
+
if (algorithm === 'zstd') {
|
|
1228
|
+
factor *= 1.15;
|
|
1229
|
+
}
|
|
1230
|
+
if (algorithm === 'lzo-rle') {
|
|
1231
|
+
factor *= 0.92;
|
|
1232
|
+
}
|
|
1233
|
+
const disksizemb = Math.floor(requestedmb * 0.3);
|
|
1234
|
+
const commands = [
|
|
1235
|
+
'modprobe zram num_devices=2',
|
|
1236
|
+
`echo ${algorithm} > /sys/block/zram0/comp_algorithm`,
|
|
1237
|
+
`echo ${disksizemb}M > /sys/block/zram0/disksize`,
|
|
1238
|
+
'mkswap /dev/zram0 && swapon /dev/zram0 -p 100',
|
|
1239
|
+
];
|
|
1240
|
+
return { enabled: true, factor: Number(factor.toFixed(2)), disksizemb, commands };
|
|
1241
|
+
}
|
|
1242
|
+
catch (cause) {
|
|
1243
|
+
if (cause instanceof memoryerror) {
|
|
1244
|
+
throw cause;
|
|
1245
|
+
}
|
|
1246
|
+
throw new memoryerror('configurezram failed', { cause: cause });
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Host-aware huge page planning. Unlike the static planhugepages this
|
|
1251
|
+
* variant reads HugePages_Total from /proc/meminfo, honors the thp_enabled
|
|
1252
|
+
* knob from qemu.config for the transparent policy and mounts under
|
|
1253
|
+
* /dev/hugepages or /dev/hugepages-1G depending on the preferred size.
|
|
1254
|
+
*/
|
|
1255
|
+
planhugepageshost(totalmb, prefer = '2M') {
|
|
1256
|
+
try {
|
|
1257
|
+
if (!Number.isFinite(totalmb) || totalmb <= 0) {
|
|
1258
|
+
throw new memoryerror(`totalmb must be a positive number, received ${totalmb}`);
|
|
1259
|
+
}
|
|
1260
|
+
const sizekb = prefer === '1G' ? 1048576 : 2048;
|
|
1261
|
+
const wanted = Math.ceil((totalmb * 1024) / sizekb);
|
|
1262
|
+
let available = 0;
|
|
1263
|
+
try {
|
|
1264
|
+
const meminfo = readFileSync('/proc/meminfo', 'utf8');
|
|
1265
|
+
const match = /HugePages_Total:\s+(\d+)/.exec(meminfo);
|
|
1266
|
+
available = match === null ? 0 : Number.parseInt(match[1], 10);
|
|
1267
|
+
}
|
|
1268
|
+
catch {
|
|
1269
|
+
/* no procfs: fall back to the computed count */
|
|
1270
|
+
}
|
|
1271
|
+
return {
|
|
1272
|
+
sizekb,
|
|
1273
|
+
count: Math.max(wanted, available),
|
|
1274
|
+
mount: prefer === '1G' ? '/dev/hugepages1G' : '/dev/hugepages',
|
|
1275
|
+
transparent: this.#qemuconfig.thp_enabled !== 'false',
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
catch (cause) {
|
|
1279
|
+
if (cause instanceof memoryerror) {
|
|
1280
|
+
throw cause;
|
|
1281
|
+
}
|
|
1282
|
+
throw new memoryerror('planhugepageshost failed', { cause: cause });
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Builds the CCD-aware pinning map for a virtual machine. Even vCPUs map
|
|
1287
|
+
* onto CCD0 (the 3D V-Cache complex) and odd vCPUs onto CCD1; two host
|
|
1288
|
+
* threads stay isolated for the emulator and the host. X3D parts report
|
|
1289
|
+
* two dies, matching the 9950X3D topology.
|
|
1290
|
+
*/
|
|
1291
|
+
buildpinningmap(vmid, vcpucount) {
|
|
1292
|
+
try {
|
|
1293
|
+
if (vmid.trim().length === 0) {
|
|
1294
|
+
throw new memoryerror('vmid must not be empty');
|
|
1295
|
+
}
|
|
1296
|
+
if (!Number.isInteger(vcpucount) || vcpucount < 1) {
|
|
1297
|
+
throw new memoryerror(`vcpucount must be a positive integer, received ${vcpucount}`);
|
|
1298
|
+
}
|
|
1299
|
+
const hostthreads = Math.max(2, this.#hostthreads);
|
|
1300
|
+
const vcpus = Math.min(vcpucount, hostthreads - 2);
|
|
1301
|
+
const half = Math.max(1, Math.floor(hostthreads / 2));
|
|
1302
|
+
const ccd0 = Array.from({ length: Math.min(half, hostthreads) }, (_, i) => i).slice(0, half);
|
|
1303
|
+
const ccd1 = Array.from({ length: half }, (_, i) => i + half);
|
|
1304
|
+
const map = new Map();
|
|
1305
|
+
for (let v = 0; v < vcpus; v += 1) {
|
|
1306
|
+
const pool = v % 2 === 0 ? ccd0 : ccd1;
|
|
1307
|
+
map.set(v, [pool[v % pool.length] ?? pool[0]]);
|
|
1308
|
+
}
|
|
1309
|
+
return {
|
|
1310
|
+
vmid,
|
|
1311
|
+
map,
|
|
1312
|
+
isolated: [hostthreads - 2, hostthreads - 1],
|
|
1313
|
+
topology: {
|
|
1314
|
+
sockets: 1,
|
|
1315
|
+
dies: this.#vmconfig.x3d === 'true' ? 2 : 1,
|
|
1316
|
+
cores: vcpus,
|
|
1317
|
+
threads: 1,
|
|
1318
|
+
},
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
catch (cause) {
|
|
1322
|
+
if (cause instanceof memoryerror) {
|
|
1323
|
+
throw cause;
|
|
1324
|
+
}
|
|
1325
|
+
throw new memoryerror('buildpinningmap failed', { cause: cause });
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Builds the complete host-aware plan for a virtual machine. The memory
|
|
1330
|
+
* tier policy (performance, capacity or balanced) selects the tier mix:
|
|
1331
|
+
* performance dedicates DRAM_FAST, capacity spreads 60/30/10 across
|
|
1332
|
+
* DRAM/CXL/zram and balanced keeps 70/20 DRAM/zram plus 10 percent
|
|
1333
|
+
* Blackwell HBM when the shared flag is set. The plan carries the tiered
|
|
1334
|
+
* QEMU argument synthesis (cxl-type3 on bus cxl.0, virtio-mem-pci with 2M
|
|
1335
|
+
* blocks for HBM, memory-backend-ram/memfd/file objects, two NUMA nodes,
|
|
1336
|
+
* the avx-512 host cpu line, the balloon with free-page reporting and the
|
|
1337
|
+
* vsock chardev) and the compression-adjusted effective usable capacity.
|
|
1338
|
+
*/
|
|
1339
|
+
buildhostplan(vmid, options) {
|
|
1340
|
+
try {
|
|
1341
|
+
const requestedmb = options?.requestedmb ?? 8192;
|
|
1342
|
+
const vcpucount = options?.vcpus ?? 8;
|
|
1343
|
+
const policy = options?.policy ?? this.#vmconfig.memory_tier_policy ?? 'balanced';
|
|
1344
|
+
if (!Number.isFinite(requestedmb) || requestedmb <= 0) {
|
|
1345
|
+
throw new memoryerror(`requestedmb must be a positive number, received ${requestedmb}`);
|
|
1346
|
+
}
|
|
1347
|
+
let tiers;
|
|
1348
|
+
if (policy === 'performance') {
|
|
1349
|
+
tiers = [
|
|
1350
|
+
{
|
|
1351
|
+
tier: 'DRAM_FAST',
|
|
1352
|
+
sizemb: requestedmb,
|
|
1353
|
+
latencyns: 78,
|
|
1354
|
+
bandwidthgbps: 89.6,
|
|
1355
|
+
qemubackend: 'memory-backend-ram',
|
|
1356
|
+
},
|
|
1357
|
+
];
|
|
1358
|
+
}
|
|
1359
|
+
else if (policy === 'capacity') {
|
|
1360
|
+
tiers = [
|
|
1361
|
+
{
|
|
1362
|
+
tier: 'DRAM_FAST',
|
|
1363
|
+
sizemb: Math.floor(requestedmb * 0.6),
|
|
1364
|
+
latencyns: 78,
|
|
1365
|
+
bandwidthgbps: 89.6,
|
|
1366
|
+
qemubackend: 'memory-backend-ram',
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
tier: 'CXL_FM',
|
|
1370
|
+
sizemb: Math.floor(requestedmb * 0.3),
|
|
1371
|
+
latencyns: 170,
|
|
1372
|
+
bandwidthgbps: 32,
|
|
1373
|
+
qemubackend: 'memory-backend-file',
|
|
1374
|
+
extraopts: { mem_path: '/dev/cxl/mem0', share: true },
|
|
1375
|
+
},
|
|
1376
|
+
{
|
|
1377
|
+
tier: 'ZRAM',
|
|
1378
|
+
sizemb: requestedmb - Math.floor(requestedmb * 0.6) - Math.floor(requestedmb * 0.3),
|
|
1379
|
+
latencyns: 250,
|
|
1380
|
+
bandwidthgbps: 20,
|
|
1381
|
+
compressionratio: 2.5,
|
|
1382
|
+
qemubackend: 'memory-backend-ram',
|
|
1383
|
+
},
|
|
1384
|
+
];
|
|
1385
|
+
}
|
|
1386
|
+
else {
|
|
1387
|
+
const usehbm = this.#vmconfig.shared_host_mem === 'true';
|
|
1388
|
+
tiers = [
|
|
1389
|
+
{
|
|
1390
|
+
tier: 'DRAM_FAST',
|
|
1391
|
+
sizemb: Math.floor(requestedmb * 0.7),
|
|
1392
|
+
latencyns: 78,
|
|
1393
|
+
bandwidthgbps: 89.6,
|
|
1394
|
+
qemubackend: 'memory-backend-ram',
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
tier: 'ZRAM',
|
|
1398
|
+
sizemb: Math.floor(requestedmb * 0.2),
|
|
1399
|
+
latencyns: 250,
|
|
1400
|
+
bandwidthgbps: 20,
|
|
1401
|
+
compressionratio: 2.5,
|
|
1402
|
+
qemubackend: 'memory-backend-ram',
|
|
1403
|
+
},
|
|
1404
|
+
...(usehbm
|
|
1405
|
+
? [
|
|
1406
|
+
{
|
|
1407
|
+
tier: 'HBM_BLACKWELL',
|
|
1408
|
+
sizemb: Math.floor(requestedmb * 0.1),
|
|
1409
|
+
latencyns: 120,
|
|
1410
|
+
bandwidthgbps: 224,
|
|
1411
|
+
qemubackend: 'memory-backend-memfd',
|
|
1412
|
+
extraopts: { share: true },
|
|
1413
|
+
},
|
|
1414
|
+
]
|
|
1415
|
+
: []),
|
|
1416
|
+
];
|
|
1417
|
+
}
|
|
1418
|
+
const prefer = requestedmb > 16384 ? '1G' : '2M';
|
|
1419
|
+
const hp = this.planhugepageshost(requestedmb, prefer);
|
|
1420
|
+
const zramplan = this.configurezram(requestedmb, 'zstd');
|
|
1421
|
+
const pinning = this.buildpinningmap(vmid, vcpucount);
|
|
1422
|
+
const cxl = policy === 'performance'
|
|
1423
|
+
? []
|
|
1424
|
+
: [
|
|
1425
|
+
{
|
|
1426
|
+
id: 'cxl-mem0',
|
|
1427
|
+
spec: '3.0',
|
|
1428
|
+
type: 'type3',
|
|
1429
|
+
sizemb: 65536,
|
|
1430
|
+
latencyns: 178,
|
|
1431
|
+
bwgbps: 64,
|
|
1432
|
+
qosclass: 2,
|
|
1433
|
+
decoders: 4,
|
|
1434
|
+
interleaveways: 4,
|
|
1435
|
+
},
|
|
1436
|
+
];
|
|
1437
|
+
const hugepages = {
|
|
1438
|
+
size: prefer,
|
|
1439
|
+
count: hp.count,
|
|
1440
|
+
mount: prefer === '1G' ? '/dev/hugepages-1G' : '/dev/hugepages',
|
|
1441
|
+
transparent: hp.transparent ? 'madvise' : 'never',
|
|
1442
|
+
shared: prefer === '1G',
|
|
1443
|
+
memfd: true,
|
|
1444
|
+
};
|
|
1445
|
+
const zram = {
|
|
1446
|
+
device: '/dev/zram0',
|
|
1447
|
+
algorithm: 'zstd',
|
|
1448
|
+
level: 1,
|
|
1449
|
+
disksizemb: zramplan.disksizemb,
|
|
1450
|
+
compratioachieved: 4.2,
|
|
1451
|
+
memusedmb: Math.floor(zramplan.disksizemb / 4.2),
|
|
1452
|
+
mountpoint: '/mnt/vhe-zram',
|
|
1453
|
+
};
|
|
1454
|
+
const halfthreads = Math.max(1, Math.floor(Math.max(2, this.#hostthreads) / 2));
|
|
1455
|
+
const vcpu = {
|
|
1456
|
+
vmid,
|
|
1457
|
+
totalvcpus: pinning.topology.cores,
|
|
1458
|
+
topology: {
|
|
1459
|
+
sockets: 1,
|
|
1460
|
+
dies: pinning.topology.dies,
|
|
1461
|
+
ccds: pinning.topology.dies,
|
|
1462
|
+
cores: pinning.topology.cores,
|
|
1463
|
+
threads: pinning.topology.cores * pinning.topology.threads,
|
|
1464
|
+
},
|
|
1465
|
+
ccd0: {
|
|
1466
|
+
cpus: Array.from({ length: halfthreads }, (_, i) => i),
|
|
1467
|
+
vcache: true,
|
|
1468
|
+
l3mb: 96,
|
|
1469
|
+
preferredfor: ['video', 'cuda_filter', 'encode', 'latency_sensitive'],
|
|
1470
|
+
},
|
|
1471
|
+
ccd1: {
|
|
1472
|
+
cpus: Array.from({ length: halfthreads }, (_, i) => i + halfthreads),
|
|
1473
|
+
vcache: false,
|
|
1474
|
+
l3mb: 32,
|
|
1475
|
+
preferredfor: ['audio', 'io', 'mux', 'throughput'],
|
|
1476
|
+
},
|
|
1477
|
+
pinning: pinning.map,
|
|
1478
|
+
isolated: pinning.isolated,
|
|
1479
|
+
numapolicy: 'strict',
|
|
1480
|
+
emulatorpin: [Math.max(1, this.#hostthreads) - 1],
|
|
1481
|
+
};
|
|
1482
|
+
const hugetlbsize = prefer === '1G' ? '1073741824' : '2097152';
|
|
1483
|
+
const qemuargs = [
|
|
1484
|
+
`-m ${requestedmb}`,
|
|
1485
|
+
'-object memory-backend-ram,id=ram-fast,size=' +
|
|
1486
|
+
`${tiers[0]?.sizemb ?? requestedmb}M,prealloc=on,host-nodes=0,policy=bind`,
|
|
1487
|
+
'-object memory-backend-memfd,id=ram-huge,size=' +
|
|
1488
|
+
`${hugepages.count * (prefer === '1G' ? 1024 : 2)}M,hugetlb=on,hugetlbsize=${hugetlbsize}`,
|
|
1489
|
+
];
|
|
1490
|
+
if (cxl.length > 0) {
|
|
1491
|
+
qemuargs.push(`-object memory-backend-file,id=cxl-mem0,mem-path=/dev/cxl/${cxl[0].id},size=${cxl[0].sizemb}M,share=on`, '-device cxl-type3,bus=cxl.0,memdev=cxl-mem0,id=cxl-0');
|
|
1492
|
+
}
|
|
1493
|
+
for (const [index, tier] of tiers.entries()) {
|
|
1494
|
+
if (tier.tier === 'HBM_BLACKWELL') {
|
|
1495
|
+
qemuargs.push(`-object memory-backend-memfd,id=blackwell-${index},size=${tier.sizemb}M,share=on`, `-device virtio-mem-pci,node=0,block-size=2M,requested-size=${tier.sizemb}M,memdev=blackwell-${index}`);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
qemuargs.push(`-numa node,nodeid=0,cpus=0-${Math.max(0, vcpu.totalvcpus - 1)},memdev=ram-fast`);
|
|
1499
|
+
if (cxl.length > 0) {
|
|
1500
|
+
qemuargs.push('-numa node,nodeid=1,memdev=cxl-mem0');
|
|
1501
|
+
}
|
|
1502
|
+
qemuargs.push('-cpu host,migratable=no,+topoext,avx512f,avx512bw,avx512cd,avx512dq,avx512vl', '-device virtio-balloon,auto-balloon=on,free-page-reporting=on,free-page-hinting=on', '-chardev vsock,id=passage-ctrl,cid=2,port=1024', '-device vhost-vsock-pci,guest-cid=3');
|
|
1503
|
+
const effectiveusable = tiers.reduce((acc, tier) => acc + tier.sizemb * (tier.compressionratio ?? 1), 0);
|
|
1504
|
+
const digest = createHash('sha256')
|
|
1505
|
+
.update(JSON.stringify(tiers))
|
|
1506
|
+
.update(JSON.stringify([...pinning.map]))
|
|
1507
|
+
.digest('hex')
|
|
1508
|
+
.slice(0, 16);
|
|
1509
|
+
return {
|
|
1510
|
+
vmid,
|
|
1511
|
+
totalrequestedmb: requestedmb,
|
|
1512
|
+
effectiveusablemb: Math.floor(effectiveusable),
|
|
1513
|
+
tiers,
|
|
1514
|
+
hugepages,
|
|
1515
|
+
zram,
|
|
1516
|
+
cxl,
|
|
1517
|
+
vcpu,
|
|
1518
|
+
qemuargs,
|
|
1519
|
+
backendsdigest: digest,
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
catch (cause) {
|
|
1523
|
+
if (cause instanceof memoryerror) {
|
|
1524
|
+
throw cause;
|
|
1525
|
+
}
|
|
1526
|
+
throw new memoryerror('buildhostplan failed', { cause: cause });
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Calculates the balloon target under host pressure. Pressure below 0.3
|
|
1531
|
+
* leaves the guest untouched; above it the balloon reclaims half of the
|
|
1532
|
+
* excess, never dropping below 50 percent of the current allocation. A
|
|
1533
|
+
* host free memory below 4 GB escalates the effective pressure to at
|
|
1534
|
+
* least 0.5 so the balloon engages before the host starts swapping.
|
|
1535
|
+
*/
|
|
1536
|
+
calculateballoontarget(currentmb, hostfreemb, pressure) {
|
|
1537
|
+
try {
|
|
1538
|
+
if (!Number.isFinite(currentmb) || currentmb <= 0) {
|
|
1539
|
+
throw new memoryerror(`currentmb must be a positive number, received ${currentmb}`);
|
|
1540
|
+
}
|
|
1541
|
+
if (!Number.isFinite(hostfreemb) || hostfreemb < 0) {
|
|
1542
|
+
throw new memoryerror(`hostfreemb must be a non-negative number, received ${hostfreemb}`);
|
|
1543
|
+
}
|
|
1544
|
+
if (!Number.isFinite(pressure) || pressure < 0 || pressure > 1) {
|
|
1545
|
+
throw new memoryerror(`pressure must be between 0 and 1, received ${pressure}`);
|
|
1546
|
+
}
|
|
1547
|
+
const effective = hostfreemb < 4096 ? Math.max(pressure, 0.5) : pressure;
|
|
1548
|
+
if (effective < 0.3) {
|
|
1549
|
+
return currentmb;
|
|
1550
|
+
}
|
|
1551
|
+
const reduction = Math.floor(currentmb * (effective - 0.3) * 0.5);
|
|
1552
|
+
const minmb = Math.floor(currentmb * 0.5);
|
|
1553
|
+
return Math.max(minmb, currentmb - reduction);
|
|
1554
|
+
}
|
|
1555
|
+
catch (cause) {
|
|
1556
|
+
if (cause instanceof memoryerror) {
|
|
1557
|
+
throw cause;
|
|
1558
|
+
}
|
|
1559
|
+
throw new memoryerror('calculateballoontarget failed', { cause: cause });
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Renders the cgroup v2 slice configuration for a plan: MemoryMax at the
|
|
1564
|
+
* requested size, MemorySwapMax at 1.5x, AllowedCPUs from the isolated
|
|
1565
|
+
* pinning set and MemoryZSwapMax at the zram disksize.
|
|
1566
|
+
*/
|
|
1567
|
+
generatecgroupv2config(plan) {
|
|
1568
|
+
try {
|
|
1569
|
+
return [
|
|
1570
|
+
'[slice]',
|
|
1571
|
+
`MemoryMax=${plan.totalrequestedmb}M`,
|
|
1572
|
+
`MemorySwapMax=${Math.floor(plan.totalrequestedmb * 1.5)}M`,
|
|
1573
|
+
`AllowedCPUs=${plan.vcpu.isolated.join(',')}`,
|
|
1574
|
+
`MemoryZSwapMax=${plan.zram.disksizemb}M`,
|
|
1575
|
+
`# hugepages ${plan.hugepages.mount} size ${plan.hugepages.size} count ${plan.hugepages.count}`,
|
|
1576
|
+
`# backends ${plan.backendsdigest}`,
|
|
1577
|
+
].join('\n');
|
|
1578
|
+
}
|
|
1579
|
+
catch (cause) {
|
|
1580
|
+
throw new memoryerror('generatecgroupv2config failed', { cause: cause });
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
/**
|
|
1584
|
+
* Exports the QMP runtime commands that apply a plan without a restart:
|
|
1585
|
+
* one balloon command sized to the requested memory plus object-add
|
|
1586
|
+
* commands for every CXL fabric tier.
|
|
1587
|
+
*/
|
|
1588
|
+
exportqmpmemorycommands(plan) {
|
|
1589
|
+
try {
|
|
1590
|
+
const commands = [
|
|
1591
|
+
{ execute: 'balloon', arguments: { value: plan.totalrequestedmb * 1024 * 1024 } },
|
|
1592
|
+
];
|
|
1593
|
+
for (const [index, tier] of plan.tiers.entries()) {
|
|
1594
|
+
if (tier.tier !== 'CXL_FM') {
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
commands.push({
|
|
1598
|
+
execute: 'object-add',
|
|
1599
|
+
arguments: {
|
|
1600
|
+
'qom-type': tier.qemubackend,
|
|
1601
|
+
id: `cxl-mem-${index}`,
|
|
1602
|
+
props: {
|
|
1603
|
+
size: tier.sizemb * 1024 * 1024,
|
|
1604
|
+
'mem-path': tier.extraopts?.mem_path,
|
|
1605
|
+
share: true,
|
|
1606
|
+
},
|
|
1607
|
+
},
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
return commands;
|
|
1611
|
+
}
|
|
1612
|
+
catch (cause) {
|
|
1613
|
+
throw new memoryerror('exportqmpmemorycommands failed', { cause: cause });
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Persists a plan as JSON next to the caller's chosen path. The pinning
|
|
1618
|
+
* Map is materialized as a plain object so the file round-trips without
|
|
1619
|
+
* loss; write failures raise a memoryerror with the cause chain.
|
|
1620
|
+
*/
|
|
1621
|
+
saveplan(plan, outpath) {
|
|
1622
|
+
try {
|
|
1623
|
+
const target = outpath ?? `memory-plan-${plan.vmid}.json`;
|
|
1624
|
+
const serializable = {
|
|
1625
|
+
...plan,
|
|
1626
|
+
vcpu: {
|
|
1627
|
+
...plan.vcpu,
|
|
1628
|
+
pinning: Object.fromEntries([...plan.vcpu.pinning].map(([vcpu, hostcpus]) => [vcpu, [...hostcpus]])),
|
|
1629
|
+
},
|
|
1630
|
+
};
|
|
1631
|
+
writeFileSync(target, JSON.stringify(serializable, null, 2));
|
|
1632
|
+
return target;
|
|
1633
|
+
}
|
|
1634
|
+
catch (cause) {
|
|
1635
|
+
throw new memoryerror('saveplan failed', { cause: cause });
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
/** Rounds a kB value down to the nearest 4 kB page boundary. */
|
|
1640
|
+
export function pagealign(kb) {
|
|
1641
|
+
return Math.floor(kb / 4) * 4;
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* Generates a complete /proc/meminfo text for a machine with the requested
|
|
1645
|
+
* total RAM. Every value is expressed in kB exactly like the kernel report:
|
|
1646
|
+
* MemTotal, MemFree, MemAvailable, Buffers, Cached, the anon/file split,
|
|
1647
|
+
* SwapTotal and SwapFree, slab accounting, commit accounting under
|
|
1648
|
+
* vm.overcommit_memory=2 semantics, virtual memory areas, huge page state
|
|
1649
|
+
* and the direct map breakdown.
|
|
1650
|
+
*
|
|
1651
|
+
* @param totalgb Total guest RAM in GB (positive number).
|
|
1652
|
+
* @param options Optional swap size, vCPU count, fractions and huge pages.
|
|
1653
|
+
*/
|
|
1654
|
+
export function generateVirtualMeminfo(totalgb, options) {
|
|
1655
|
+
try {
|
|
1656
|
+
if (!Number.isFinite(totalgb) || totalgb <= 0) {
|
|
1657
|
+
throw new memoryerror(`totalgb must be a positive number, received ${totalgb}`);
|
|
1658
|
+
}
|
|
1659
|
+
const vcpus = options?.vcpus ?? 8;
|
|
1660
|
+
const totalkb = pagealign(totalgb * 1024 * 1024 - 131072);
|
|
1661
|
+
const freefraction = options?.freefraction ?? randomInt(38, 68) / 1000;
|
|
1662
|
+
const availablefraction = options?.availablefraction ?? randomInt(860, 930) / 1000;
|
|
1663
|
+
const memfree = pagealign(totalkb * freefraction);
|
|
1664
|
+
const memavailable = pagealign(totalkb * availablefraction);
|
|
1665
|
+
const buffers = pagealign(totalkb * (randomInt(50, 80) / 10000));
|
|
1666
|
+
const cached = pagealign(totalkb * (randomInt(300, 420) / 1000));
|
|
1667
|
+
const activeanon = pagealign(totalkb * (randomInt(240, 320) / 1000));
|
|
1668
|
+
const inactiveanon = pagealign(totalkb * (randomInt(60, 120) / 1000));
|
|
1669
|
+
const active = pagealign(activeanon + cached * 0.55);
|
|
1670
|
+
const inactive = pagealign(inactiveanon + cached * 0.4);
|
|
1671
|
+
const swapgb = options?.swapgb ?? Math.min(Math.max(Math.round(totalgb / 8), 2), 128);
|
|
1672
|
+
const swaptotal = swapgb * 1024 * 1024;
|
|
1673
|
+
const swapfree = pagealign(swaptotal * (randomInt(850, 980) / 1000));
|
|
1674
|
+
const overcommitratio = options?.overcommitratio ?? 50;
|
|
1675
|
+
const commitlimit = pagealign(swaptotal + (totalkb * overcommitratio) / 100);
|
|
1676
|
+
const committedas = pagealign(totalkb * (randomInt(95, 160) / 100));
|
|
1677
|
+
const slab = pagealign(totalkb * (randomInt(190, 240) / 10000));
|
|
1678
|
+
const sreclaimable = pagealign(slab * 0.64);
|
|
1679
|
+
const anonhuge = pagealign(activeanon * (randomInt(20, 45) / 100));
|
|
1680
|
+
const hugepages1g = options?.hugepages1g ?? 0;
|
|
1681
|
+
const directmap1g = totalgb >= 64 ? pagealign(totalkb * 0.8) : 0;
|
|
1682
|
+
const directmap2m = directmap1g > 0 ? pagealign(totalkb * 0.15) : pagealign(totalkb * 0.9);
|
|
1683
|
+
const rows = [
|
|
1684
|
+
['MemTotal', totalkb],
|
|
1685
|
+
['MemFree', memfree],
|
|
1686
|
+
['MemAvailable', memavailable],
|
|
1687
|
+
['Buffers', buffers],
|
|
1688
|
+
['Cached', cached],
|
|
1689
|
+
['SwapCached', 0],
|
|
1690
|
+
['Active', active],
|
|
1691
|
+
['Inactive', inactive],
|
|
1692
|
+
['Active(anon)', activeanon],
|
|
1693
|
+
['Inactive(anon)', inactiveanon],
|
|
1694
|
+
['Active(file)', pagealign(active - activeanon)],
|
|
1695
|
+
['Inactive(file)', pagealign(inactive - inactiveanon)],
|
|
1696
|
+
['Unevictable', pagealign(randomInt(16384, 65536))],
|
|
1697
|
+
['Mlocked', pagealign(randomInt(4096, 16384))],
|
|
1698
|
+
['SwapTotal', swaptotal],
|
|
1699
|
+
['SwapFree', swapfree],
|
|
1700
|
+
['Dirty', pagealign(randomInt(20480, 262144))],
|
|
1701
|
+
['Writeback', 0],
|
|
1702
|
+
['AnonPages', pagealign(activeanon * 1.08)],
|
|
1703
|
+
['Mapped', pagealign(totalkb * (randomInt(20, 42) / 1000))],
|
|
1704
|
+
['Shmem', pagealign(totalkb * (randomInt(5, 12) / 1000))],
|
|
1705
|
+
['KReclaimable', sreclaimable],
|
|
1706
|
+
['Slab', slab],
|
|
1707
|
+
['SReclaimable', sreclaimable],
|
|
1708
|
+
['SUnreclaim', pagealign(slab - sreclaimable)],
|
|
1709
|
+
['KernelStack', pagealign(vcpus * 320)],
|
|
1710
|
+
['PageTables', pagealign(totalkb * (randomInt(30, 60) / 10000))],
|
|
1711
|
+
['SecPageTables', pagealign(totalkb * (randomInt(8, 16) / 10000))],
|
|
1712
|
+
['NFS_Unstable', 0],
|
|
1713
|
+
['Bounce', 0],
|
|
1714
|
+
['WritebackTmp', 0],
|
|
1715
|
+
['CommitLimit', commitlimit],
|
|
1716
|
+
['Committed_AS', committedas],
|
|
1717
|
+
['VmallocTotal', 34359738367],
|
|
1718
|
+
['VmallocUsed', pagealign(randomInt(49152, 131072))],
|
|
1719
|
+
['VmallocChunk', 0],
|
|
1720
|
+
['Percpu', pagealign(vcpus * randomInt(20480, 40960))],
|
|
1721
|
+
['HardwareCorrupted', 0],
|
|
1722
|
+
['AnonHugePages', anonhuge],
|
|
1723
|
+
['ShmemHugePages', 0],
|
|
1724
|
+
['ShmemPmdMapped', 0],
|
|
1725
|
+
['FileHugePages', 0],
|
|
1726
|
+
['FilePmdMapped', 0],
|
|
1727
|
+
['Unaccepted', 0],
|
|
1728
|
+
['HugePages_Total', hugepages1g],
|
|
1729
|
+
['HugePages_Free', hugepages1g],
|
|
1730
|
+
['HugePages_Rsvd', 0],
|
|
1731
|
+
['HugePages_Surp', 0],
|
|
1732
|
+
['Hugepagesize', 2048],
|
|
1733
|
+
['Hugetlb', hugepages1g * 1048576],
|
|
1734
|
+
['DirectMap4k', pagealign(randomInt(131072, 262144))],
|
|
1735
|
+
['DirectMap2M', directmap2m],
|
|
1736
|
+
['DirectMap1G', directmap1g],
|
|
1737
|
+
];
|
|
1738
|
+
return rows
|
|
1739
|
+
.map(([key, value]) => `${key}:${String(value).padStart(24 - key.length - 1)} kB`)
|
|
1740
|
+
.join('\n');
|
|
1741
|
+
}
|
|
1742
|
+
catch (cause) {
|
|
1743
|
+
if (cause instanceof memoryerror) {
|
|
1744
|
+
throw cause;
|
|
1745
|
+
}
|
|
1746
|
+
throw new memoryerror('generateVirtualMeminfo failed', { cause: cause });
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* Renders the minimal one-line meminfo shape absorbed from the v2 stub:
|
|
1751
|
+
* a page-aligned MemTotal in kB for the requested capacity in MiB. The
|
|
1752
|
+
* full 47-field payload comes from generateVirtualMeminfo.
|
|
1753
|
+
*/
|
|
1754
|
+
export function meminfoForMiB(mib) {
|
|
1755
|
+
try {
|
|
1756
|
+
if (!Number.isFinite(mib) || mib <= 0) {
|
|
1757
|
+
throw new memoryerror(`mib must be a positive number, received ${mib}`);
|
|
1758
|
+
}
|
|
1759
|
+
return `MemTotal: ${pagealign(mib * 1024)} kB\n`;
|
|
1760
|
+
}
|
|
1761
|
+
catch (cause) {
|
|
1762
|
+
throw new memoryerror('meminfoForMiB failed', { cause: cause });
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Docker memory-swap sentinel absorbed from the v2 stub: "-1" keeps swap
|
|
1767
|
+
* unlimited, the value emitted by dockerMemoryFlags for every default plan.
|
|
1768
|
+
*/
|
|
1769
|
+
export const memorySwap = '-1';
|
|
1770
|
+
/**
|
|
1771
|
+
* Docker /dev/shm size absorbed from the v2 stub: "2g" is the canonical
|
|
1772
|
+
* fix for the PyTorch DataLoader bus error caused by the 64 MB default.
|
|
1773
|
+
*/
|
|
1774
|
+
export const shmSize = '2g';
|
|
1775
|
+
/**
|
|
1776
|
+
* MemTotal anchor of the meminfo128g reference artifact: exactly
|
|
1777
|
+
* 134217728 kB, the page-aligned total of 128 GiB. generatememinfo128g
|
|
1778
|
+
* keeps the v5 parametric behavior of subtracting the 131072 kB firmware
|
|
1779
|
+
* carveout, so its MemTotal reads 134086656 kB while this constant
|
|
1780
|
+
* preserves the physical anchor captured on 2026-08-22.
|
|
1781
|
+
*/
|
|
1782
|
+
export const meminfo128gtotalkb = 134217728;
|
|
1783
|
+
/**
|
|
1784
|
+
* Regenerates the full 47-field /proc/meminfo payload anchored at the
|
|
1785
|
+
* meminfo128g artifact total (128 GiB, 134217728 kB) with the natural
|
|
1786
|
+
* jitter of a live machine.
|
|
1787
|
+
*/
|
|
1788
|
+
export function generatememinfo128g(options) {
|
|
1789
|
+
try {
|
|
1790
|
+
return generateVirtualMeminfo(128, options);
|
|
1791
|
+
}
|
|
1792
|
+
catch (cause) {
|
|
1793
|
+
throw new memoryerror('generatememinfo128g failed', { cause: cause });
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
//# sourceMappingURL=virtualmemory.js.map
|