sujitvasanth's picture
Fixed-shape Piper MNN encoder/decoder for word-by-word streaming on CPU or GPU
67faadf verified
Raw History Blame Contribute Delete
5.92 kB
"""Remove data-dependent shapes from the duration predictor's spline flows.
Each spline flow (dp/flows.3/5/7) uses NonZero(mask) to pick the elements inside
the spline range (+-5), runs the spline on just those, then scatters results back.
That makes tensor sizes depend on the data.
Dense rewrite, same output:
- every NonZero is replaced by a constant listing all positions, so gathers and
scatters always see all N elements
- the spline input is clamped to the spline range so out-of-range elements can't
index past the bins (their results are discarded below)
- the final "inside" scatter becomes Where(inside_mask, spline_result, previous),
so outside elements keep the identity value written by the "outside" scatter
"""
import sys
from pathlib import Path
import numpy as np
import onnx
from onnx import TensorProto, helper, numpy_helper
ROOT = Path(__file__).resolve().parent
SRC = sys.argv[1] if len(sys.argv) > 1 else "data.encoder.fixed64x160.onnx"
DST = sys.argv[2] if len(sys.argv) > 2 else SRC.replace(".onnx", ".static.onnx")
def main() -> None:
m = onnx.load(ROOT / SRC)
g = m.graph
m = onnx.shape_inference.infer_shapes(m)
g = m.graph
shapes = {}
for vi in list(g.value_info) + list(g.input) + list(g.output):
shapes[vi.name] = [d.dim_value if d.HasField("dim_value") else None for d in vi.type.tensor_type.shape.dim]
producer = {o: n for n in g.node for o in n.output}
consumers = {}
for n in g.node:
for x in n.input:
consumers.setdefault(x, []).append(n)
def mask_root(name):
# Follow Expand back to the And / Not node that made the mask.
n = producer[name]
while n.op_type == "Expand":
n = producer[n.input[0]]
return n
new_nodes, extra = [], []
nz_kind = {} # NonZero output -> "in" / "out"
for n in g.node:
if n.op_type != "NonZero":
continue
root = mask_root(n.input[0])
shp = shapes.get(n.input[0])
if not (shp and all(shp)):
# Expanded masks: batch and channel are 1 here, same as the source mask.
shp = shapes.get(root.output[0])
# dp flows act on one channel of a [1, 2, N] tensor: masks are [1, 1, N].
shp = [d if d else 1 for d in (shp or [None, None, None])]
assert len(shp) == 3 and shp[-1] > 1, f"unexpected mask shape for {n.name}: {shp}"
nz_kind[n.output[0]] = "in" if root.op_type == "And" else "out"
idx = np.stack(np.meshgrid(*[np.arange(s) for s in shp], indexing="ij")).reshape(len(shp), -1).astype(np.int64)
n.op_type = "Constant"
del n.input[:]
del n.attribute[:]
n.attribute.append(helper.make_attribute("value", numpy_helper.from_array(idx, n.output[0] + "_all")))
def idx_kind(idx_name):
# GatherND/ScatterND indices = Transpose(NonZero) (possibly via Cast/Reshape).
n = producer.get(idx_name)
while n is not None and n.output[0] not in nz_kind:
n = producer.get(n.input[0]) if n.input else None
return nz_kind.get(n.output[0]) if n is not None else None
clamped = where_fixed = 0
nodes = list(g.node)
out_nodes = []
for n in nodes:
if n.op_type == "GatherND" and idx_kind(n.input[1]) == "in" and len(shapes.get(n.input[0], [])) == 3:
# Spline input x (shape [1,1,N]): clamp into the spline range.
lo = n.name + "/lo"; hi = n.name + "/hi"; cl = n.input[0] + "_clamped_" + str(clamped)
out_nodes.append(helper.make_node("Constant", [], [lo], value=numpy_helper.from_array(np.array(-5.0, np.float32))))
out_nodes.append(helper.make_node("Constant", [], [hi], value=numpy_helper.from_array(np.array(5.0, np.float32))))
out_nodes.append(helper.make_node("Clip", [n.input[0], lo, hi], [cl], name=n.name + "/Clamp"))
n.input[0] = cl
clamped += 1
out_nodes.append(n)
elif n.op_type == "ScatterND" and idx_kind(n.input[1]) == "in":
data, idx, upd = n.input
shp = shapes.get(data)
# The mask fed to the (now constant) NonZero: find it via the original Expand node.
nz = producer[idx]
while nz.output[0] not in nz_kind:
nz = producer[nz.input[0]]
# Rebuild the boolean mask: inside = And(...) expanded to data's shape.
and_node = None
for cand in g.node:
if cand.op_type == "And" and cand.name.rsplit("/", 1)[0] == n.name.rsplit("/", 1)[0]:
and_node = cand
assert and_node is not None, n.name
if not (shp and all(shp)):
shp = shapes[and_node.output[0]]
shp = [d if d else 1 for d in shp]
shp_name = n.name + "/shape"
out_nodes.append(helper.make_node("Constant", [], [shp_name], value=numpy_helper.from_array(np.array(shp, np.int64))))
mask = n.name + "/inside_mask"
out_nodes.append(helper.make_node("Expand", [and_node.output[0], shp_name], [mask], name=n.name + "/MaskExpand"))
upd_r = n.name + "/upd_dense"
out_nodes.append(helper.make_node("Reshape", [upd, shp_name], [upd_r], name=n.name + "/UpdReshape"))
out_nodes.append(helper.make_node("Where", [mask, upd_r, data], list(n.output), name=n.name + "/Where"))
where_fixed += 1
else:
out_nodes.append(n)
g.ClearField("node")
g.node.extend(out_nodes)
g.ClearField("value_info")
m = onnx.shape_inference.infer_shapes(m)
onnx.checker.check_model(m)
onnx.save(m, ROOT / DST)
left = sum(1 for n in m.graph.node if n.op_type == "NonZero")
print(f"NonZero replaced: {len(nz_kind)}; clamps: {clamped}; inside scatters -> Where: {where_fixed}; NonZero left: {left}")
print("saved", DST)
if __name__ == "__main__":
main()