Description
Halide serialization drops scalar-parameter value constraints
serialize_pipeline records the value metadata of a buffer parameter but silently discards it for a scalar parameter. Walking the IR closure from the output Funcs, the serializer sorts parameters into two buckets (src/Serialization.cpp): buffer params reached through Load/Store/output nodes go to parameters_in_pipeline and are emitted by serialize_parameter, which includes default_value / min_value / max_value / estimate; but a scalar param is only ever reached through a Variable node (e.g. a Param<int> used in an RDom bound), and the Variable case routes it to external_parameters, emitted by serialize_external_parameter — which keeps only {is_buffer, type, dimensions, name}. So a scalar Param<int> p; p.set_range(0, 100); p.set_estimate(4); round-trips as nothing more than "an int named p." Those constraints are not generator-specific bookkeeping: they live on Halide::Internal::Parameter, are equally reachable through the freestanding Param<T> / ImageParam API, and are consumed by bounds inference (a min/max range is what lets a runtime-parameterized extent become a compile-time-constant, e.g. bounded stack allocations; an estimate is what the autoscheduler reads). Dropping them makes serialization lossy in a way that can change the lowered pipeline, and asymmetric with the buffer-parameter path for no obvious reason.
The charitable reading is that this is intentional: serialize_pipeline hands the caller a map of the external parameters, and the deserialize side is expected to re-bind the live Parameter objects (with their constraints intact), so the on-disk blob was never meant to be self-describing for externals. That contract is defensible, but it is undocumented at the call site and creates a trap for any consumer that treats the .hlpipe as a complete, self-contained fingerprint of a pipeline (as an algorithm-equality check does): two pipelines that differ only in a scalar parameter's declared range/estimate serialize identically, even though they can compile to different code. Recommended fix, roughly in preference order: (1) serialize scalar min_value/max_value/default_value/estimate on the external-parameter path too, so the format is lossless and symmetric with buffers; or failing that (2) document the "externals are re-bound by the caller" contract prominently and expose the external-parameter constraints alongside the returned params map, so a caller can choose to fold them into its own fingerprint.
Reproducing case
Walking the IR of a serialized/deserailized pipeline should match the original IR's scalar parameter metadata.
How did you get Halide?
None
Halide version
No response
Halide commit (if known)
No response
Target
No response
Operating system
No response
Additional context
No response
Description
Halide serialization drops scalar-parameter value constraints
serialize_pipelinerecords the value metadata of a buffer parameter but silently discards it for a scalar parameter. Walking the IR closure from the outputFuncs, the serializer sorts parameters into two buckets (src/Serialization.cpp): buffer params reached throughLoad/Store/output nodes go toparameters_in_pipelineand are emitted byserialize_parameter, which includesdefault_value/min_value/max_value/estimate; but a scalar param is only ever reached through aVariablenode (e.g. aParam<int>used in anRDombound), and theVariablecase routes it toexternal_parameters, emitted byserialize_external_parameter— which keeps only{is_buffer, type, dimensions, name}. So a scalarParam<int> p; p.set_range(0, 100); p.set_estimate(4);round-trips as nothing more than "an int namedp." Those constraints are not generator-specific bookkeeping: they live onHalide::Internal::Parameter, are equally reachable through the freestandingParam<T>/ImageParamAPI, and are consumed by bounds inference (amin/maxrange is what lets a runtime-parameterized extent become a compile-time-constant, e.g. bounded stack allocations; an estimate is what the autoscheduler reads). Dropping them makes serialization lossy in a way that can change the lowered pipeline, and asymmetric with the buffer-parameter path for no obvious reason.The charitable reading is that this is intentional:
serialize_pipelinehands the caller a map of the external parameters, and the deserialize side is expected to re-bind the liveParameterobjects (with their constraints intact), so the on-disk blob was never meant to be self-describing for externals. That contract is defensible, but it is undocumented at the call site and creates a trap for any consumer that treats the.hlpipeas a complete, self-contained fingerprint of a pipeline (as an algorithm-equality check does): two pipelines that differ only in a scalar parameter's declared range/estimate serialize identically, even though they can compile to different code. Recommended fix, roughly in preference order: (1) serialize scalarmin_value/max_value/default_value/estimateon the external-parameter path too, so the format is lossless and symmetric with buffers; or failing that (2) document the "externals are re-bound by the caller" contract prominently and expose the external-parameter constraints alongside the returned params map, so a caller can choose to fold them into its own fingerprint.Reproducing case
How did you get Halide?
None
Halide version
No response
Halide commit (if known)
No response
Target
No response
Operating system
No response
Additional context
No response