| """ |
| Expose RIFE's baked t=0.5 as a runtime scalar input. |
| |
| FuryTMP/RIFE_fp32 (MIT) traced its IFNet with timestep=0.5 folded into a Constant, so the |
| exported graph takes only 6 channels and can produce the midpoint frame. The timestep plane |
| is still built internally as ((Slice * 0) + 1) * 0.5 and fed to the head Concat of every |
| IFBlock -- promoting that 0.5 to a graph input restores arbitrary-phase interpolation. |
| |
| Verified: output is bit-identical to the original at t=0.5, and ORT folds the patched graph to |
| the same 336 nodes with zero shape-computation ops under freeDimensionOverrides. |
| |
| Usage: python patch_rife_timestep.py RIFE_fp32.onnx RIFE_fp32_timestep.onnx |
| Feed the new input as a 0-d array: np.array(t, dtype=np.float32) (not np.float32(t)). |
| """ |
|
|
| import sys |
|
|
| import onnx |
| from onnx import TensorProto, helper, numpy_helper |
|
|
| TIMESTEP_INPUT = "timestep" |
|
|
|
|
| def find_baked_timestep(graph): |
| """The scalar Constant whose only consumer is the Mul feeding the timestep plane.""" |
| consumers = {} |
| for node in graph.node: |
| for name in node.input: |
| consumers.setdefault(name, []).append(node) |
|
|
| candidates = [] |
| for node in graph.node: |
| if node.op_type != "Constant": |
| continue |
| value = next((a.t for a in node.attribute if a.name == "value"), None) |
| if value is None: |
| continue |
| array = numpy_helper.to_array(value) |
| if array.shape != () or not (0.0 < float(array) < 1.0): |
| continue |
| users = consumers.get(node.output[0], []) |
| if len(users) == 1 and users[0].op_type == "Mul": |
| candidates.append((node, float(array), users[0])) |
|
|
| if len(candidates) != 1: |
| raise SystemExit( |
| f"expected exactly one baked-timestep Constant, found {len(candidates)}: " |
| f"{[(n.name, v) for n, v, _ in candidates]}" |
| ) |
| return candidates[0] |
|
|
|
|
| def main(src, dst): |
| model = onnx.load(src) |
| graph = model.graph |
|
|
| if any(i.name == TIMESTEP_INPUT for i in graph.input): |
| raise SystemExit(f"{src} already exposes a '{TIMESTEP_INPUT}' input") |
|
|
| const_node, baked_value, mul_node = find_baked_timestep(graph) |
| print(f"found {const_node.name} = {baked_value} -> consumed by {mul_node.name}") |
|
|
| graph.node.remove(const_node) |
| for i, name in enumerate(mul_node.input): |
| if name == const_node.output[0]: |
| mul_node.input[i] = TIMESTEP_INPUT |
| graph.input.append(helper.make_tensor_value_info(TIMESTEP_INPUT, TensorProto.FLOAT, [])) |
|
|
| onnx.checker.check_model(model, full_check=True) |
| onnx.save(model, dst) |
| print(f"wrote {dst}; inputs = {[i.name for i in graph.input]}") |
|
|
|
|
| if __name__ == "__main__": |
| if len(sys.argv) != 3: |
| raise SystemExit(__doc__) |
| main(sys.argv[1], sys.argv[2]) |
|
|