Skill: Model Schema Validation Guide
Use this guide to define and enforce structural constraints (shapes, data types, runtime constraints) on loaded ExecuTorch .pte models using validateSpec.
🔍 Why Validate Model Specs?
Every ExecuTorch model exposes a schema (ModelSpec<ConcreteDim>), derived either from standard ExecuTorch MethodMeta at load time or from an exported get_model_schema companion method returning a JSON model spec.
To prevent runtime crashes, type mismatches, and memory allocation errors in C++, TypeScript task pipelines validate the loaded model's exported schema against allowed spec variants using validateSpec before allocating static tensors or executing inference.
🛠️ Validation API Reference
import {
validateSpec,
method,
f32,
i64,
i32,
DynamicDim as Dyn,
constraint,
} from '../../../core/schema';
const { variant, dims } = validateSpec(model.schema, {
batched: method(
'forward',
[i64(1, Dyn('L')), i64(1, Dyn('L'))],
[f32(1, 'D')],
[
constraint.equality(
{ paramSide: 'input', tensorIdx: 0, dimIdx: 1 },
{ paramSide: 'input', tensorIdx: 1, dimIdx: 1 }
),
]
),
unbatched: method(
'forward',
[i64(Dyn('L')), i64(Dyn('L'))],
[f32('D')],
[
constraint.equality(
{ paramSide: 'input', tensorIdx: 0, dimIdx: 0 },
{ paramSide: 'input', tensorIdx: 1, dimIdx: 0 }
),
]
),
});
const [D] = dims.constant('D');
const L = dims.range('L');
Key Schema Utilities from src/core/schema.ts:
method(name, inputs, outputs, runtimeConstraints?): Constructs a method specification.f32(...)/i64(...)/i32(...)/ui8(...): Shorthand helpers for tensor parameter specs.StaticDim('symbol')/ String Literals: Strings passed to shape helpers (e.g.'H','W') automatically map toStaticDim, acting as static dimension wildcards. They bind strictly toconstantpositive integer dimensions in the exported spec.DynamicDim('symbol')(orDyn('symbol')): Creates a dynamic dimension symbol. Must be used when a dimension genuinely varies at runtime and binds to arangeorenumdomain in the exported spec.- Constraint Helpers (
constraint):DimRefObject Literal ({ paramSide: 'input' | 'output', tensorIdx, dimIdx }): Explicit reference to a tensor's dimension.constraint.equality(...dims): Creates an equality constraint requiring the referenced dimensions to take the exact same value at runtime.constraint.linear(lhs, rhs, a, b?): Creates a linear constraintlhs = a * rhs + b.
validateSpec(exportedSchema, allowedVariants): Compares the model's exported schema against named variants and returns{ variant, dim, dims }.- Symbol Accessors (
dims&dim):dims.constant('N', 'H'): Extracts constant values for symbols as numbers.dims.range('S'): Extracts range domains{ min, max, step }.dims.enum('E'): Extracts enum choicesreadonly number[].dims.dynamic('L'): Extracts dynamicrangeorenumas rawConcreteDim.dims.any('D'): Extracts raw dimension value (number,Range,readonly number[], orConcreteDim).dim('N'): Extracts value for a single symbol.
📏 Symbolic Dimensions & Dynamic Shapes
Tensors can have static dimensions (integers), static symbol wildcards (strings), or dynamic symbols (DynamicDim).
The validateSpec utility matches allowed variants in order:
Numbers (Static Match): Must match the exact static integer in the exported spec.
Strings /
StaticDim(Static Wildcard Match): Binds string symbol names (e.g.'H') to static constant integers in the exported spec. Repeated occurrences of the same static symbol must bind to the same constant value.DynamicDim(Dynamic Domain Match): Binds dynamic symbol names torangeorenumdomains in the exported spec.Variant Selection: Specify multiple variant keys in
validateSpec(e.g.batchedvsunbatched). The validator tests variants sequentially and returns the first matching key and bound symbols.
🔀 Dimension Domains vs. Runtime Constraints
Understanding the distinction between a dimension's domain and its runtime value is central to schema design and validation:
1. Dimension Domains & Domain Matching
- Dimension Domain: The set of values a dimension may take:
constant: Exactlyvalue(a singleton integer).range: Any value betweenminandmaxstepping bystep.enum: Any value listed inchoices.
- Runtime Value: The concrete integer size of a tensor's dimension in a specific execution call.
- Domain Matching (
validateSpec):validateSpecperforms static, load-time domain matching.- Dynamic symbols (
DynamicDim('S')) bind to exported dimension domains. Reusing a symbol ('S') across tensor inputs or outputs requires every occurrence to bind to the exact same domain. - ⚠️ Key Rule: Binding to the same domain does NOT mean runtime values coincide! Two dimensions bound to the same domain (e.g., both having range
1..512) may take different runtime values in a single execution (e.g. length 10 and length 25).
2. Runtime Constraints (constraint.equality & constraint.linear)
- Runtime Constraints: Declarations about the runtime values of tensor dimensions during execution:
- Equality Constraint (
constraint.equality(...)): Requires all referenced dimensions to take the exact same runtime value in any execution call.method( 'forward', [f32('B', Dyn('S1')), f32('B', Dyn('S2'))], [f32('B', Dyn('S1'))], [ constraint.equality( { paramSide: 'input', tensorIdx: 0, dimIdx: 0 }, { paramSide: 'input', tensorIdx: 1, dimIdx: 0 } ), ] ); - Linear Constraint (
constraint.linear(...)): Requires two dimensions to satisfydimLhs = a * dimRhs + bat runtime.
- Equality Constraint (
- Validation & Enforcement:
validateSpecverifies that the exported model spec declares the exact same runtime constraints (1-to-1 declaration match).- Native C++ validates input runtime constraints before invoking
model.execute().
⚙️ Companion JSON Spec (get_model_schema)
For models with runtime dynamic dimensions or runtime constraints (equality/linear relations between dimensions), the .pte model exports a companion method named get_model_schema returning a JSON string representation of ModelSpec<ConcreteDim>.
When get_model_schema is present, the C++ loader parses it to populate model.schema with exact RangeDim / EnumDim domains and runtime constraints. Without it, model.schema is populated strictly from static ExecuTorch MethodMeta.
📋 Common Validation Recipes
1. Classification (Batched vs Unbatched)
Accepts an image tensor and outputs class probabilities:
const { variant, dims } = validateSpec(model.schema, {
batched: method('forward', [f32(1, 3, 'H', 'W')], [f32(1, 'N')]),
unbatched: method('forward', [f32(3, 'H', 'W')], [f32('N')]),
});
const [N, H, W] = dims.constant('N', 'H', 'W');
const inpShape = { batched: [1, 3, H, W], unbatched: [3, H, W] }[variant];
const outShape = { batched: [1, N], unbatched: [N] }[variant];
2. Image-to-Image / Style Transfer
Accepts an image tensor and returns a modified image tensor with identical dimensions:
const { dims } = validateSpec(model.schema, {
default: method('forward', [f32(1, 3, 'H', 'W')], [f32(1, 3, 'H', 'W')]),
});
const [H, W] = dims.constant('H', 'W');
3. Object Detection (Dynamic Box Count)
Accepts an image tensor, and outputs boxes, scores, and class labels for N detections:
const { dims } = validateSpec(model.schema, {
default: method('forward', [f32(1, 3, 'H', 'W')], [f32('N', 4), f32('N'), f32('N')]),
});
const [N, H, W] = dims.constant('N', 'H', 'W');
4. Single-Model Pipeline (Fully Static Export)
Assert exact shape constants from the task file:
const { dims } = validateSpec(model.schema, {
default: method(
'denoise',
[
f32(1, LATENT_CHANNELS, LATENT_SIZE, LATENT_SIZE),
i64(1),
f32(1, CLIP_MAX_TOKENS, CLIP_HIDDEN_SIZE),
],
[f32(1, LATENT_CHANNELS, LATENT_SIZE, LATENT_SIZE)]
),
});
🚫 Avoid / Anti-Patterns
- Do NOT write manual imperative shape/type checks: Always use declarative
validateSpecvariants which report unified, readable mismatch errors. - Do NOT use hardcoded integers for dynamic dimensions: Use string symbols (like
'H','W','N') for dynamic dimensions. Conversely, fixed export dimensions should be integers. - Do NOT skip validation at startup: Validate
model.schemabefore allocating static tensors. - Do NOT skip validation for single-model pipelines: Single-model pipelines should still validate against task shape constants.
⚠️ Failure Codes
validateSpec and the native spec validation raise SCHEMA_MISMATCH (the spec-builder
helpers ConstantDim / RangeDim / EnumDim raise INVALID_ARGUMENT for their own
arguments). A runtime constraint violated by the tensors actually passed to execute
raises INVALID_ARGUMENT, not SCHEMA_MISMATCH: the model is fine, the call is not. See the Error Handling Skill.
📋 Verification Checklist
When specifying model schema validations, verify that:
- Schema validation is performed immediately after model loading and before tensor initialization using
validateSpec(model.schema, { ... }). - Dynamic dimensions are defined as string symbols, while fixed dimensions are plain integers.
- Symbol values are extracted using
dims.constant(...),dims.range(...), ordims.enum(...). - Multiple shape variants (e.g.
batchedvsunbatched) are provided when supported. - Input and output constraints map accurately to model specifications.
- New validation failures throw
SCHEMA_MISMATCH(orINVALID_ARGUMENTwhen the caller's own arguments are at fault), never a bareError.