@kaminos/webgpu-inference-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # @kaminos/webgpu-inference-kit
2
+
3
+ Native WebGPU inference route substrate for Kaminos.
4
+
5
+ Install:
6
+
7
+ ```sh
8
+ npm install @kaminos/webgpu-inference-kit
9
+ ```
10
+
11
+ Import:
12
+
13
+ ```js
14
+ import {
15
+ createWebGpuRouteSchemaContract,
16
+ createWebGpuLocalRouteReceipt,
17
+ classifyWebGpuRouteReceiptEvidence,
18
+ } from "@kaminos/webgpu-inference-kit";
19
+ ```
20
+
21
+ This package starts with contracts, not kernels. Its first job is to make
22
+ browser-local model routes prove what they actually ran before Kaminos treats
23
+ their outputs as asset evidence.
24
+
25
+ Current surface:
26
+
27
+ - `createWebGpuLocalRouteReceipt(input)`: creates a
28
+ `kaminos.webgpu-route-receipt.v0` receipt for a `webgpu-local` route.
29
+ - `createWebGpuRouteSchemaContract(input)`: exposes the kit-owned route
30
+ definition/request/result/receipt/runtime-profile/evidence-classification
31
+ schema strings as a compact contract object so route repos can run conformance
32
+ checks instead of manually mirroring hidden constants.
33
+ - `createWebGpuRouteReceiptFromArtifacts(input)` plus
34
+ `createRouteReceiptArtifacts`, `createRouteReceiptInputArtifact`,
35
+ `finishAndValidateRouteProfile`, and validation helpers: shared route receipt
36
+ substrate used by MoGE, SHARP, Kimodo, and SF3D factories to preserve artifact
37
+ identity, backend identity, and staged profile requirements without duplicating
38
+ false-closure-prone boilerplate.
39
+ - `validateRouteReceipt(receipt)`: validates requested/effective route identity,
40
+ backend/model/kernel identity, input/output artifact ids, timings, and
41
+ fallback status.
42
+ - `assertAuthoritativeRouteReceipt(receipt)`: rejects fallback, cached,
43
+ partial, missing, or non-real outputs before they can masquerade as
44
+ authoritative Kaminos evidence.
45
+ - `defineTensorManifest(input)` and `validateTensorManifest(manifest)`: normalize
46
+ and validate model tensor metadata, including dtype sizes and byte lengths.
47
+ - `createWebGpuDeviceRequest(adapter, options)`: derives requested WebGPU
48
+ features and max adapter limits for model inference without silently capping
49
+ below the adapter's own reported capacity.
50
+ - `requestBrowserWebGpuDevice(gpu, options)`: requests a browser WebGPU adapter
51
+ and device, then returns the effective device request and backend identity
52
+ that route receipts should preserve.
53
+ - `createWebGpuBackendIdentity(input)` and
54
+ `validateWebGpuBackendIdentity(identity)`: preserve effective browser,
55
+ adapter, feature, limit, and timestamp-query identity for route receipts.
56
+ - `createStagedSubmitProfile(input)`, `addStagedSubmitStage(profile, stage)`,
57
+ `finishStagedSubmitProfile(profile)`, and
58
+ `validateStagedSubmitProfile(profile)`: record staged-submit timing evidence
59
+ and reject timestamp-query timing unless it is validated against staged waits.
60
+ - `createKernelProfileMetadata(input)` and
61
+ `createRouteKernelProfileMetadata(input)`: normalize shared kit version,
62
+ kernel profile, commit, required stage, and timing-source metadata for route
63
+ definitions and receipts while keeping route-specific semantics local.
64
+ - `createWebGpuRuntimeProfileInput(input)`,
65
+ `createWebGpuRuntimeProfile(input)`, and
66
+ `validateWebGpuRuntimeProfile(profile)`: combine effective WebGPU backend
67
+ identity, kernel metadata, staged profile evidence, and evidence mode into a
68
+ single producer-side runtime profile object.
69
+ - `createWebGpuRouteSchedulerProfile(input)` and
70
+ `validateWebGpuRouteSchedulerProfile(profile)`: preserve requested versus
71
+ effective WebGPU route scheduling, including throughput/cooperative mode,
72
+ route-specific phase chunk sizes, submitted-work waits, yield cadence, and
73
+ unsupported scheduler fields so a route cannot claim cooperative behavior
74
+ without effective telemetry.
75
+ - `createWebGpuRouteBackpressureProfile(input)` and
76
+ `validateWebGpuRouteBackpressureProfile(profile)`: record route pressure
77
+ classification, warm/cache and memory-sharing posture, and frame-tail
78
+ evidence for visible-wait/furnace classification without turning internal
79
+ scheduler knobs into operator-facing controls.
80
+ - `classifyWebGpuRouteReceiptEvidence(receipt)` and
81
+ `classifyWebGpuRouteWorkerResultEvidence(result)`: commoner-side receipt
82
+ classification helpers that distinguish authoritative live WebGPU evidence
83
+ from fallback, partial, cache/demo, stale, route-mismatch, and invalid
84
+ receipts, while surfacing scheduler verification and frame-tail fields when a
85
+ route provides them.
86
+ - `createMogeDepthNormalRouteReceipt(input)`: first concrete `webgpu-local`
87
+ route receipt factory for `moge.depth-normal.webgpu-local.v0`.
88
+ - `defineWebGpuRoute(input)`, `createWebGpuRouteRegistry(routes)`,
89
+ `createRouteInvocationRequest(route, input)`, `createRouteWorkerResult(route,
90
+ input)`, and their validators: define worker-executable routes, create
91
+ invocation envelopes, and validate route results before Wake/Pipeline consume
92
+ them as Kaminos evidence.
93
+ - `createMogeDepthNormalRouteDefinition(input)`: first concrete route
94
+ definition for MoGE source-image to depth/normal/pointmap truth-layer output.
95
+ - `createSharpImageToSplatRouteReceipt(input)`: concrete receipt factory for
96
+ `sharp.image-to-splat.webgpu-local.v0`, preserving source image, browser
97
+ WebGPU backend identity, PLY splat candidate, depth map, SHARP metadata, and
98
+ optional splat autocrop evidence.
99
+ - `createSharpImageToSplatRouteDefinition(input)`: route definition aligned to
100
+ the native SHARP-WebGPU browser adapter surface used by Kaminos Pipeline:
101
+ source image in, splat candidate/depth/metadata out, with optional
102
+ `kaminos.splat-autocrop-evidence.v0` side evidence.
103
+ - `createKimodoTextToMotionRouteReceipt(input)` and
104
+ `createKimodoTextToMotionRouteDefinition(input)`: browser WebGPU
105
+ text-to-motion route contract for Kimodo SOMA-RP-v1.1, preserving prompt
106
+ identity, SOMA77 joint output, motion sidecar output, optional filmstrip, and
107
+ staged text-embedding/DDIM/FK/output-capture timing.
108
+ - `createSf3dImageToMeshRouteReceipt(input)` and
109
+ `createSf3dImageToMeshRouteDefinition(input)`: browser WebGPU image-to-mesh
110
+ route contract for Stable Fast 3D, preserving source image, GLB mesh, albedo
111
+ texture, normal map, optional OBJ, and DINOv2/two-stream/triplane/marching-tet
112
+ stage identity.
113
+
114
+ Near-term extraction order:
115
+
116
+ 1. Route receipt and tensor manifest contracts. Done in the scaffold slice.
117
+ 2. WebGPU device/feature/profiling identity helpers. First pure contract helpers
118
+ are in place; browser adapters should wire into these next.
119
+ 3. MoGE depth/normal route receipt. First factory is in place and the MoGE
120
+ runtime emits this receipt from live inference.
121
+ 4. Browser route boundary. Route registry, invocation request, worker result,
122
+ browser device request, and MoGE route definition contracts are in place.
123
+ 5. SHARP image-to-splat route contract. First factory and route definition are
124
+ in place for the browser-native SHARP-WebGPU path; runtime emission remains
125
+ owned by SHARP/Pipeline adapter surfaces.
126
+ 6. Kimodo and SF3D route contracts. First factories and route definitions are
127
+ in place for browser-native text-to-motion and image-to-mesh routes; runtime
128
+ emission remains owned by those route repos and Kaminos motion/pipeline
129
+ consumers.
130
+ 7. MoGE schema mirror drift reduction. The kit exposes a schema contract object;
131
+ MoGE has a dev conformance test against that contract while the runtime still
132
+ avoids a brittle temporary worktree dependency.
133
+ 8. Shared route receipt helper. Artifact normalization, backend identity
134
+ validation, staged profile validation, and receipt construction now live in
135
+ one helper consumed by all four concrete route factories.
136
+ 9. Shared kernel/profile metadata helper. Kit version, kernel profile, commit,
137
+ required stage, and timing-source normalization now live in one helper
138
+ consumed by all four concrete route factories.
139
+ 10. Runtime profile and commoner receipt classification helpers. Producers can
140
+ normalize effective WebGPU runtime evidence, and downstream commoners can
141
+ classify receipts before treating outputs as live route evidence.
142
+ 11. Scheduler/backpressure contracts. Routes can now report throughput versus
143
+ cooperative scheduling, requested/effective phase chunking, unsupported
144
+ fields, visible-wait/furnace pressure, and frame-tail evidence without
145
+ implying browser GPU preemption that WebGPU does not provide.
146
+ 12. Pipeline, bind-group, uniform, and buffer caches from MoGE/SHARP.
147
+ 13. Shared kernels only when at least two real routes need them or a measured
148
+ kernel slice proves the extraction useful.
149
+
150
+ Non-goals:
151
+
152
+ - Generic ONNX import parity.
153
+ - General LLM runtime competition.
154
+ - Kaminos graph, scene, library, or promotion ownership.
155
+ - Any route that hides fallback, stale output, fixture data, partial output, or
156
+ effective backend identity.
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@kaminos/webgpu-inference-kit",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Native WebGPU inference route substrate for Kaminos.",
7
+ "license": "UNLICENSED",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/lyonsno/kaminos.git",
11
+ "directory": "webgpu-inference-kit"
12
+ },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "files": [
17
+ "README.md",
18
+ "src"
19
+ ],
20
+ "exports": {
21
+ ".": "./src/index.js"
22
+ },
23
+ "scripts": {
24
+ "test": "node tests/receipt-contracts.mjs && node tests/tensor-manifest-contracts.mjs && node tests/gpu-environment-contracts.mjs && node tests/browser-device-context-contracts.mjs && node tests/staged-profile-contracts.mjs && node tests/kernel-profile-contracts.mjs && node tests/runtime-profile-contracts.mjs && node tests/scheduler-backpressure-contracts.mjs && node tests/route-schema-contracts.mjs && node tests/route-receipt-helper-contracts.mjs && node tests/route-receipt-consumer-contracts.mjs && node tests/moge-route-contracts.mjs && node tests/sharp-route-contracts.mjs && node tests/kimodo-route-contracts.mjs && node tests/sf3d-route-contracts.mjs && node tests/route-boundary-contracts.mjs"
25
+ }
26
+ }
@@ -0,0 +1,139 @@
1
+ const LIMIT_KEYS = [
2
+ 'maxBufferSize',
3
+ 'maxStorageBufferBindingSize',
4
+ 'maxComputeWorkgroupStorageSize',
5
+ 'maxComputeInvocationsPerWorkgroup',
6
+ 'maxComputeWorkgroupSizeX',
7
+ 'maxComputeWorkgroupSizeY',
8
+ ];
9
+
10
+ function featureList(features) {
11
+ if (!features) return [];
12
+ return Array.from(features).map(String).sort();
13
+ }
14
+
15
+ function hasFeature(features, name) {
16
+ return featureList(features).includes(name);
17
+ }
18
+
19
+ function isNonEmptyString(value) {
20
+ return typeof value === 'string' && value.trim().length > 0;
21
+ }
22
+
23
+ function copyLimits(limits = {}) {
24
+ const out = {};
25
+ for (const key of LIMIT_KEYS) {
26
+ if (Number.isFinite(limits[key])) out[key] = limits[key];
27
+ }
28
+ return out;
29
+ }
30
+
31
+ export function createWebGpuDeviceRequest(adapter, options = {}) {
32
+ if (!adapter || typeof adapter !== 'object') {
33
+ throw new Error('adapter must be an object');
34
+ }
35
+
36
+ const timestampPreference = options.timestampQuery || 'prefer';
37
+ const requiredFeatures = [];
38
+ let timestampQuery = 'disabled';
39
+
40
+ if (timestampPreference !== 'disable') {
41
+ if (hasFeature(adapter.features, 'timestamp-query')) {
42
+ requiredFeatures.push('timestamp-query');
43
+ timestampQuery = 'requested';
44
+ } else if (timestampPreference === 'require') {
45
+ throw new Error('timestamp-query required but not supported by adapter');
46
+ } else {
47
+ timestampQuery = 'unavailable';
48
+ }
49
+ }
50
+
51
+ return {
52
+ requiredFeatures,
53
+ requiredLimits: copyLimits(adapter.limits),
54
+ timestampQuery,
55
+ };
56
+ }
57
+
58
+ export function requestBrowserWebGpuDevice(gpu, options = {}) {
59
+ if (!gpu || typeof gpu.requestAdapter !== 'function') {
60
+ throw new Error('gpu.requestAdapter must be available');
61
+ }
62
+
63
+ return (async () => {
64
+ const adapter = await gpu.requestAdapter(options.adapterOptions || {});
65
+ if (!adapter) throw new Error('WebGPU adapter unavailable');
66
+
67
+ const deviceRequest = createWebGpuDeviceRequest(adapter, options);
68
+ const descriptor = {
69
+ requiredFeatures: deviceRequest.requiredFeatures,
70
+ requiredLimits: deviceRequest.requiredLimits,
71
+ };
72
+ if (isNonEmptyString(options.label)) descriptor.label = options.label;
73
+
74
+ const device = await adapter.requestDevice(descriptor);
75
+ const backendIdentity = createWebGpuBackendIdentity({
76
+ adapterName: options.adapterName || adapter.info?.description || adapter.info?.device || adapter.info?.vendor || 'unknown-webgpu-adapter',
77
+ browser: options.browser || globalThis.navigator?.userAgent || null,
78
+ requestedFeatures: deviceRequest.requiredFeatures,
79
+ effectiveFeatures: device?.features || deviceRequest.requiredFeatures,
80
+ limits: device?.limits || adapter.limits,
81
+ timestampQuery: deviceRequest.timestampQuery,
82
+ });
83
+
84
+ return {
85
+ adapter,
86
+ device,
87
+ deviceRequest,
88
+ backendIdentity,
89
+ };
90
+ })();
91
+ }
92
+
93
+ export function createWebGpuBackendIdentity(input) {
94
+ return {
95
+ kind: 'webgpu-local',
96
+ runtime: 'browser',
97
+ adapterName: input.adapterName || null,
98
+ browser: input.browser || null,
99
+ requestedFeatures: featureList(input.requestedFeatures),
100
+ features: featureList(input.effectiveFeatures || input.features),
101
+ limits: copyLimits(input.limits),
102
+ timestampQuery: input.timestampQuery || 'unavailable',
103
+ };
104
+ }
105
+
106
+ export function validateWebGpuBackendIdentity(identity) {
107
+ const errors = [];
108
+
109
+ if (!identity || typeof identity !== 'object') {
110
+ return { ok: false, errors: ['identity must be an object'] };
111
+ }
112
+ if (identity.kind !== 'webgpu-local') errors.push('kind must be webgpu-local');
113
+ if (identity.runtime !== 'browser') errors.push('runtime must be browser');
114
+ if (!isNonEmptyString(identity.adapterName)) errors.push('adapterName must be a non-empty string');
115
+ if (!Array.isArray(identity.features) || identity.features.length === 0) {
116
+ errors.push('features must be a non-empty array');
117
+ }
118
+ if (!identity.limits || typeof identity.limits !== 'object' || Object.keys(identity.limits).length === 0) {
119
+ errors.push('limits must be a non-empty object');
120
+ }
121
+
122
+ const timestampStates = new Set(['requested', 'available', 'unavailable', 'disabled']);
123
+ if (!timestampStates.has(identity.timestampQuery)) {
124
+ errors.push('timestampQuery has unsupported state');
125
+ }
126
+
127
+ if (identity.timestampQuery === 'requested') {
128
+ const requested = featureList(identity.requestedFeatures);
129
+ const effective = featureList(identity.features);
130
+ if (!requested.includes('timestamp-query')) {
131
+ errors.push('timestamp-query requested state must include timestamp-query in requestedFeatures');
132
+ }
133
+ if (!effective.includes('timestamp-query')) {
134
+ errors.push('timestamp-query requested state must include timestamp-query in features');
135
+ }
136
+ }
137
+
138
+ return { ok: errors.length === 0, errors };
139
+ }
package/src/index.js ADDED
@@ -0,0 +1,106 @@
1
+ export {
2
+ assertAuthoritativeRouteReceipt,
3
+ createWebGpuLocalRouteReceipt,
4
+ validateRouteReceipt,
5
+ WEBGPU_ROUTE_RECEIPT_SCHEMA,
6
+ } from './route-receipt.js';
7
+
8
+ export {
9
+ defineTensorManifest,
10
+ validateTensorManifest,
11
+ } from './tensor-manifest.js';
12
+
13
+ export {
14
+ createWebGpuBackendIdentity,
15
+ createWebGpuDeviceRequest,
16
+ requestBrowserWebGpuDevice,
17
+ validateWebGpuBackendIdentity,
18
+ } from './gpu-environment.js';
19
+
20
+ export {
21
+ addStagedSubmitStage,
22
+ createStagedSubmitProfile,
23
+ finishStagedSubmitProfile,
24
+ validateStagedSubmitProfile,
25
+ } from './staged-profile.js';
26
+
27
+ export {
28
+ createKernelProfileMetadata,
29
+ createRouteKernelProfileMetadata,
30
+ createRouteTimingMetadata,
31
+ validateKernelProfileMetadata,
32
+ validateRouteTimingMetadata,
33
+ } from './kernel-profile.js';
34
+
35
+ export {
36
+ createWebGpuRuntimeProfile,
37
+ createWebGpuRuntimeProfileInput,
38
+ validateWebGpuRuntimeProfile,
39
+ WEBGPU_RUNTIME_PROFILE_SCHEMA,
40
+ } from './runtime-profile.js';
41
+
42
+ export {
43
+ createWebGpuRouteBackpressureProfile,
44
+ createWebGpuRouteSchedulerProfile,
45
+ validateWebGpuRouteBackpressureProfile,
46
+ validateWebGpuRouteSchedulerProfile,
47
+ WEBGPU_ROUTE_BACKPRESSURE_SCHEMA,
48
+ WEBGPU_ROUTE_SCHEDULER_SCHEMA,
49
+ } from './scheduler-backpressure.js';
50
+
51
+ export {
52
+ classifyWebGpuRouteReceiptEvidence,
53
+ classifyWebGpuRouteWorkerResultEvidence,
54
+ WEBGPU_ROUTE_EVIDENCE_CLASSIFICATION_SCHEMA,
55
+ } from './route-receipt-consumer.js';
56
+
57
+ export {
58
+ MOGE_DEPTH_NORMAL_ROUTE_ID,
59
+ createMogeDepthNormalRouteDefinition,
60
+ createMogeDepthNormalRouteReceipt,
61
+ } from './moge-route.js';
62
+
63
+ export {
64
+ SHARP_IMAGE_TO_SPLAT_ROUTE_ID,
65
+ createSharpImageToSplatRouteDefinition,
66
+ createSharpImageToSplatRouteReceipt,
67
+ } from './sharp-route.js';
68
+
69
+ export {
70
+ KIMODO_TEXT_TO_MOTION_ROUTE_ID,
71
+ createKimodoTextToMotionRouteDefinition,
72
+ createKimodoTextToMotionRouteReceipt,
73
+ } from './kimodo-route.js';
74
+
75
+ export {
76
+ SF3D_IMAGE_TO_MESH_ROUTE_ID,
77
+ createSf3dImageToMeshRouteDefinition,
78
+ createSf3dImageToMeshRouteReceipt,
79
+ } from './sf3d-route.js';
80
+
81
+ export {
82
+ createWebGpuRouteSchemaContract,
83
+ } from './route-schema-contract.js';
84
+
85
+ export {
86
+ createRouteReceiptArtifacts,
87
+ createRouteReceiptInputArtifact,
88
+ createWebGpuRouteReceiptFromArtifacts,
89
+ finishAndValidateRouteProfile,
90
+ validateRouteReceiptArtifact,
91
+ validateRouteReceiptBackendIdentity,
92
+ } from './route-receipt-helper.js';
93
+
94
+ export {
95
+ assertAuthoritativeRouteWorkerResult,
96
+ createRouteInvocationRequest,
97
+ createRouteWorkerResult,
98
+ createWebGpuRouteRegistry,
99
+ defineWebGpuRoute,
100
+ validateRouteDefinition,
101
+ validateRouteInvocationRequest,
102
+ validateRouteWorkerResult,
103
+ WEBGPU_ROUTE_DEFINITION_SCHEMA,
104
+ WEBGPU_ROUTE_REQUEST_SCHEMA,
105
+ WEBGPU_ROUTE_RESULT_SCHEMA,
106
+ } from './route-boundary.js';
@@ -0,0 +1,101 @@
1
+ const DEFAULT_KIT_VERSION = '0.0.0';
2
+ const DEFAULT_TIMING_SOURCE = 'queue-submit-wait';
3
+
4
+ function isNonEmptyString(value) {
5
+ return typeof value === 'string' && value.trim().length > 0;
6
+ }
7
+
8
+ function normalizeSource(input) {
9
+ return input && typeof input === 'object' ? input : {};
10
+ }
11
+
12
+ function stringOrDefault(value, fallback) {
13
+ return isNonEmptyString(value) ? value : fallback;
14
+ }
15
+
16
+ export function validateKernelProfileMetadata(kernel) {
17
+ const errors = [];
18
+
19
+ if (!kernel || typeof kernel !== 'object') {
20
+ return { ok: false, errors: ['kernel must be an object'] };
21
+ }
22
+
23
+ if (!isNonEmptyString(kernel.kitVersion)) errors.push('kernel.kitVersion must be a non-empty string');
24
+ if (!isNonEmptyString(kernel.profile)) errors.push('kernel.profile must be a non-empty string');
25
+ if (kernel.commit != null && typeof kernel.commit !== 'string') {
26
+ errors.push('kernel.commit must be a string or null');
27
+ }
28
+
29
+ return { ok: errors.length === 0, errors };
30
+ }
31
+
32
+ export function createKernelProfileMetadata(input = {}, options = {}) {
33
+ const source = normalizeSource(input);
34
+ const kernel = {
35
+ kitVersion: stringOrDefault(source.kitVersion, options.defaultKitVersion || DEFAULT_KIT_VERSION),
36
+ profile: stringOrDefault(source.profile, options.defaultProfile),
37
+ commit: source.commit || null,
38
+ };
39
+
40
+ if (options.requireProfile === true || options.validate === true) {
41
+ const result = validateKernelProfileMetadata(kernel);
42
+ if (!result.ok) throw new Error(result.errors.join('; '));
43
+ }
44
+
45
+ return kernel;
46
+ }
47
+
48
+ export function validateRouteTimingMetadata(timing) {
49
+ const errors = [];
50
+
51
+ if (!timing || typeof timing !== 'object') {
52
+ return { ok: false, errors: ['timing metadata must be an object'] };
53
+ }
54
+
55
+ if (!Array.isArray(timing.requiredStages) || timing.requiredStages.length === 0) {
56
+ errors.push('requiredStages must be a non-empty array');
57
+ } else {
58
+ timing.requiredStages.forEach((stage, index) => {
59
+ if (!isNonEmptyString(stage)) errors.push(`requiredStages[${index}] must be a non-empty string`);
60
+ });
61
+ }
62
+ if (!isNonEmptyString(timing.timingSource)) errors.push('timingSource must be a non-empty string');
63
+
64
+ return { ok: errors.length === 0, errors };
65
+ }
66
+
67
+ export function createRouteTimingMetadata(input = {}, options = {}) {
68
+ const source = normalizeSource(input);
69
+ const requiredStages = Array.isArray(source.requiredStages)
70
+ ? source.requiredStages
71
+ : (Array.isArray(options.requiredStages) ? options.requiredStages : []);
72
+
73
+ const timing = {
74
+ requiredStages: [...requiredStages],
75
+ timingSource: stringOrDefault(source.timingSource, options.timingSource || DEFAULT_TIMING_SOURCE),
76
+ };
77
+
78
+ if (options.validate === true) {
79
+ const result = validateRouteTimingMetadata(timing);
80
+ if (!result.ok) throw new Error(result.errors.join('; '));
81
+ }
82
+
83
+ return timing;
84
+ }
85
+
86
+ export function createRouteKernelProfileMetadata(input = {}, options = {}) {
87
+ const source = normalizeSource(input);
88
+ return {
89
+ kernel: createKernelProfileMetadata(source.kernel, {
90
+ defaultKitVersion: options.defaultKitVersion,
91
+ defaultProfile: options.defaultProfile,
92
+ requireProfile: options.requireProfile,
93
+ validate: options.validateKernel,
94
+ }),
95
+ ...createRouteTimingMetadata(source, {
96
+ requiredStages: options.requiredStages,
97
+ timingSource: options.timingSource,
98
+ validate: options.validateTiming,
99
+ }),
100
+ };
101
+ }
@@ -0,0 +1,84 @@
1
+ import { defineWebGpuRoute } from './route-boundary.js';
2
+ import {
3
+ createKernelProfileMetadata,
4
+ createRouteKernelProfileMetadata,
5
+ } from './kernel-profile.js';
6
+ import {
7
+ createRouteReceiptArtifacts,
8
+ createRouteReceiptInputArtifact,
9
+ createWebGpuRouteReceiptFromArtifacts,
10
+ } from './route-receipt-helper.js';
11
+
12
+ export const KIMODO_TEXT_TO_MOTION_ROUTE_ID = 'kimodo.text-to-motion.webgpu-local.v0';
13
+ const KIMODO_MODEL_ID = 'NVIDIA/Kimodo-SOMA-RP-v1.1';
14
+ const DEFAULT_KERNEL_PROFILE = 'twostage-denoiser-ddim50-fk';
15
+ const REQUIRED_STAGES = ['text-embedding', 'ddim-sampling', 'fk-decode', 'output-capture'];
16
+ const OUTPUT_ROLES = [
17
+ { key: 'soma77Joints', role: 'soma77-joints', required: true },
18
+ { key: 'motionClip', role: 'motion-clip', required: true },
19
+ { key: 'filmstrip', role: 'filmstrip', required: false },
20
+ ];
21
+
22
+ export function createKimodoTextToMotionRouteReceipt(input) {
23
+ if (!input || typeof input !== 'object') throw new Error('input must be an object');
24
+ if (!input.input?.artifactId || !input.input?.sha256) {
25
+ throw new Error('text prompt artifactId and sha256 are required');
26
+ }
27
+ if (!input.outputs?.soma77Joints) throw new Error('soma77Joints output is required');
28
+ if (!input.outputs?.motionClip) throw new Error('motionClip output is required');
29
+
30
+ return createWebGpuRouteReceiptFromArtifacts({
31
+ requestedRouteId: KIMODO_TEXT_TO_MOTION_ROUTE_ID,
32
+ effectiveRouteId: input.effectiveRouteId || KIMODO_TEXT_TO_MOTION_ROUTE_ID,
33
+ status: input.status || (input.fallbackReason ? 'fallback' : 'real'),
34
+ fallbackReason: input.fallbackReason || null,
35
+ backend: input.backend,
36
+ model: {
37
+ id: KIMODO_MODEL_ID,
38
+ revision: input.model?.revision,
39
+ weightsHash: input.model?.weightsHash,
40
+ dtype: input.model?.dtype || 'fp16',
41
+ },
42
+ kernel: createKernelProfileMetadata(input.kernel, { requireProfile: true }),
43
+ inputs: [
44
+ createRouteReceiptInputArtifact('text-prompt', input.input),
45
+ ],
46
+ outputs: createRouteReceiptArtifacts({ artifacts: input.outputs, roles: OUTPUT_ROLES }),
47
+ profile: input.profile,
48
+ });
49
+ }
50
+
51
+ export function createKimodoTextToMotionRouteDefinition(input = {}) {
52
+ const routeMetadata = createRouteKernelProfileMetadata(input, {
53
+ defaultProfile: DEFAULT_KERNEL_PROFILE,
54
+ requiredStages: REQUIRED_STAGES,
55
+ timingSource: 'adapter-phase-wall-clock',
56
+ });
57
+
58
+ return defineWebGpuRoute({
59
+ routeId: KIMODO_TEXT_TO_MOTION_ROUTE_ID,
60
+ backendKind: 'webgpu-local',
61
+ model: {
62
+ id: KIMODO_MODEL_ID,
63
+ revision: input.model?.revision || 'SOMA-RP-v1.1',
64
+ dtype: input.model?.dtype || 'fp16',
65
+ },
66
+ kernel: routeMetadata.kernel,
67
+ inputs: [
68
+ { role: 'text-prompt', required: true, artifactRequired: true, hashRequired: true },
69
+ ],
70
+ outputs: [
71
+ { role: 'soma77-joints', required: true, artifactRequired: true, hashRequired: true, shape: [90, 77, 3] },
72
+ { role: 'motion-clip', required: true, artifactRequired: true, hashRequired: true, shape: [1] },
73
+ { role: 'filmstrip', required: false, artifactRequired: true, hashRequired: true },
74
+ ],
75
+ requiredFeatures: input.requiredFeatures || [],
76
+ requiredStages: routeMetadata.requiredStages,
77
+ timingSource: routeMetadata.timingSource,
78
+ worker: input.worker || {
79
+ exportName: 'runKimodoTextToMotionRoute',
80
+ textEmbedding: 'external-llama3-8b',
81
+ motionFormat: 'kimodo-soma77-explicit-joints',
82
+ },
83
+ });
84
+ }