edelfrade commited on
Commit
1528ba5
·
verified ·
1 Parent(s): 20f6e53

Replace Sum with Add chains (no WebGPU Sum kernel: std::bad_alloc in wasm)

Browse files
Files changed (4) hide show
  1. README.md +5 -1
  2. onnx/model.onnx +2 -2
  3. onnx/model_fp16.onnx +2 -2
  4. rebuild.py +27 -2
README.md CHANGED
@@ -22,7 +22,11 @@ The ONNX export in [onnx-community/BiRefNet_lite-ONNX](https://huggingface.co/on
22
  Too many storage buffers in shader. Current: 17, Max is 16
23
  ```
24
 
25
- This repo replaces those 4 Concat and 59 Split nodes with trees of smaller ones (at most 6 inputs per Concat, 5 outputs per Split, so every node uses ≤ 7 storage buffers). Nothing else changes: the outputs are **bit-for-bit identical** to the original on ONNX Runtime CPU, for both files.
 
 
 
 
26
 
27
  | File | Precision | Size | Needs |
28
  |---|---|---|---|
 
22
  Too many storage buffers in shader. Current: 17, Max is 16
23
  ```
24
 
25
+ This repo replaces those 4 Concat and 59 Split nodes with trees of smaller ones (at most 6 inputs per Concat, 5 outputs per Split, so every node uses ≤ 7 storage buffers).
26
+
27
+ It also replaces the 20 `Sum` nodes with chains of `Add` (same order). ONNX Runtime's WebGPU backend has no Sum kernel, so Sum ran on the CPU, inside WebAssembly's 4 GB heap; in the deformable convolutions it adds four 392 MiB tensors (784 MiB each after the casts to fp32 the CPU needs), which fails with `std::bad_alloc`.
28
+
29
+ Nothing else changes: the outputs are **bit-for-bit identical** to the original on ONNX Runtime CPU, for both files.
30
 
31
  | File | Precision | Size | Needs |
32
  |---|---|---|---|
onnx/model.onnx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:a9e7aebec91bad0cf3949015e8ddc379602db6a2b7a06918e81340f4e0ce3716
3
- size 224182130
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fda4d8be0d03f3e4ee2f61aa4a4f2b72e0175b237839e0afc37db0e4d9420a11
3
+ size 224191490
onnx/model_fp16.onnx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:e41401436e7e9a074f0f06556f5296702d48c8a0a227fb8a6f9ce4c7d08166f3
3
- size 114715263
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fb9bdae2f16fefd542929fe6efa52a0f8333bab5d92f35fdfcfb57ad0bc95e0d
3
+ size 114724609
rebuild.py CHANGED
@@ -13,6 +13,11 @@ values (concatenation and splitting are associative along one axis):
13
  - Split: at most SPLIT_FAN_OUT outputs per node (+ data + sizes inputs)
14
  Every node then uses at most MAX_BUFFERS storage buffers.
15
 
 
 
 
 
 
16
  Usage: python rebuild.py <in.onnx> <out.onnx>
17
  """
18
 
@@ -128,11 +133,31 @@ class Rewriter:
128
  split(node.input[0], list(zip(sizes, outputs)), node.name)
129
  return made
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  def run(self) -> dict[str, int]:
132
- stats = {"Concat": 0, "Split": 0}
133
  new_nodes: list[onnx.NodeProto] = []
134
  for node in self.graph.node:
135
- if buffers(node) > MAX_BUFFERS and node.op_type == "Concat":
 
 
 
 
 
 
 
136
  new_nodes.extend(self.concat_tree(node))
137
  stats["Concat"] += 1
138
  elif buffers(node) > MAX_BUFFERS and node.op_type == "Split":
 
13
  - Split: at most SPLIT_FAN_OUT outputs per node (+ data + sizes inputs)
14
  Every node then uses at most MAX_BUFFERS storage buffers.
15
 
16
+ It also replaces Sum with a chain of Add nodes: ONNX Runtime's WebGPU backend
17
+ has no Sum kernel, so Sum runs on the CPU in WebAssembly memory, and the
18
+ deformable convolutions' Sums of four 392 MiB tensors exhaust its 4 GB heap
19
+ ("std::bad_alloc").
20
+
21
  Usage: python rebuild.py <in.onnx> <out.onnx>
22
  """
23
 
 
133
  split(node.input[0], list(zip(sizes, outputs)), node.name)
134
  return made
135
 
136
+ def add_chain(self, node: onnx.NodeProto) -> list[onnx.NodeProto]:
137
+ # ((a + b) + c) + d: the same order Sum adds in, but Add has a WebGPU kernel.
138
+ inputs = [i for i in node.input if i]
139
+ if len(inputs) == 1:
140
+ return [helper.make_node("Identity", inputs, list(node.output), name=node.name)]
141
+ made: list[onnx.NodeProto] = []
142
+ acc = inputs[0]
143
+ for index, other in enumerate(inputs[1:], start=2):
144
+ out = node.output[0] if index == len(inputs) else self.fresh(node.name)
145
+ made.append(helper.make_node("Add", [acc, other], [out], name=self.fresh(node.name)))
146
+ acc = out
147
+ return made
148
+
149
  def run(self) -> dict[str, int]:
150
+ stats = {"Concat": 0, "Split": 0, "Sum": 0}
151
  new_nodes: list[onnx.NodeProto] = []
152
  for node in self.graph.node:
153
+ if node.op_type == "Sum":
154
+ # ONNX Runtime's WebGPU backend has no Sum kernel, so Sum runs on the
155
+ # CPU, inside WebAssembly's 4 GB heap. In the deformable convolutions it
156
+ # adds four 392 MiB tensors (784 MiB each after the fp16 → fp32 casts
157
+ # the CPU needs): "std::bad_alloc".
158
+ new_nodes.extend(self.add_chain(node))
159
+ stats["Sum"] += 1
160
+ elif buffers(node) > MAX_BUFFERS and node.op_type == "Concat":
161
  new_nodes.extend(self.concat_tree(node))
162
  stats["Concat"] += 1
163
  elif buffers(node) > MAX_BUFFERS and node.op_type == "Split":