Skip to content

Commit da80a9b

Browse files
andifeclaude
andcommitted
Fix ruff lint errors in onnx2c backend
- Extract _io_lines() and _parse_graph_io() and _build_binary() helpers to reduce McCabe complexity of _generate_harness (9→3) and prepare (11→4) - Use math.prod() instead of manual shape product loops - Fix Q003: use single outer quotes in f-strings containing double quotes - Add missing docstrings (D102, D105, D107) - Add strict=False to zip() calls (B905) - Suppress BLE001 with noqa on intentional broad except clauses Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 57caedf commit da80a9b

1 file changed

Lines changed: 102 additions & 99 deletions

File tree

backends/onnx2c/backend.py

Lines changed: 102 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
"""ONNX backend wrapper for onnx2c (ONNX to C code generator)."""
44

5+
import math
56
import os
67
import re
78
import shutil
@@ -74,6 +75,24 @@ def _parse_tensor_info(value_info):
7475
return _c_name(value_info.name), c_type, np_dtype, shape
7576

7677

78+
def _io_lines(tensors_info, arg_offset, mode):
79+
"""Return C lines that open and read/write tensor binary files via argv."""
80+
rw_func = "fread" if mode == "rb" else "fwrite"
81+
direction = "input" if mode == "rb" else "output"
82+
lines = []
83+
for i, (c_nm, c_type, _, shape) in enumerate(tensors_info):
84+
n = math.prod(shape)
85+
arg_idx = arg_offset + i + 1
86+
lines.append(f' f = fopen(argv[{arg_idx}], "{mode}");')
87+
lines.append(
88+
f' if (!f) {{ fprintf(stderr, "Cannot open {direction} {i}\\n"); '
89+
f"return 1; }}"
90+
)
91+
lines.append(f" {rw_func}({c_nm}, sizeof({c_type}), {n}, f);")
92+
lines.append(" fclose(f);")
93+
return lines
94+
95+
7796
def _generate_harness(inputs_info, outputs_info):
7897
"""Return C harness source that feeds inputs, calls entry(), writes outputs."""
7998
lines = [
@@ -84,74 +103,109 @@ def _generate_harness(inputs_info, outputs_info):
84103
"",
85104
]
86105

87-
# extern declarations for input/output tensors (defined in generated model.c)
88106
for c_nm, c_type, _, shape in inputs_info:
89-
n = 1
90-
for d in shape:
91-
n *= d
92-
lines.append(f"extern {c_type} {c_nm}[{n}];")
107+
lines.append(f"extern {c_type} {c_nm}[{math.prod(shape)}];")
93108

94109
for c_nm, c_type, _, shape in outputs_info:
95-
n = 1
96-
for d in shape:
97-
n *= d
98-
lines.append(f"extern {c_type} {c_nm}[{n}];")
110+
lines.append(f"extern {c_type} {c_nm}[{math.prod(shape)}];")
99111

100112
lines += ["", "void entry(void);", "", "int main(int argc, char** argv) {"]
101113
lines.append(" FILE* f;")
102114
lines.append("")
115+
lines += _io_lines(inputs_info, 0, "rb")
116+
lines += ["", " entry();", ""]
117+
lines += _io_lines(outputs_info, len(inputs_info), "wb")
118+
lines += ["", " return 0;", "}"]
119+
return "\n".join(lines)
103120

104-
# Read inputs from files passed as argv[1..n_inputs]
105-
for i, (c_nm, c_type, _, shape) in enumerate(inputs_info):
106-
n = 1
107-
for d in shape:
108-
n *= d
109-
lines.append(f" f = fopen(argv[{i + 1}], \"rb\");")
110-
lines.append(
111-
f" if (!f) {{ fprintf(stderr, \"Cannot open input {i}\\n\"); return 1; }}"
112-
)
113-
lines.append(f" fread({c_nm}, sizeof({c_type}), {n}, f);")
114-
lines.append(" fclose(f);")
115121

116-
lines += ["", " entry();", ""]
122+
def _parse_graph_io(graph):
123+
"""Parse graph inputs/outputs; return (inputs_info, outputs_info) or raise."""
124+
initializer_names = {init.name for init in graph.initializer}
125+
inputs_info = []
126+
for vi in graph.input:
127+
if vi.name in initializer_names:
128+
continue
129+
info = _parse_tensor_info(vi)
130+
if info is None:
131+
raise BackendIsNotSupposedToImplementIt(
132+
f"Input '{vi.name}' has unsupported type or dynamic shape"
133+
)
134+
inputs_info.append(info)
135+
136+
outputs_info = []
137+
for vi in graph.output:
138+
info = _parse_tensor_info(vi)
139+
if info is None:
140+
raise BackendIsNotSupposedToImplementIt(
141+
f"Output '{vi.name}' has unsupported type or dynamic shape"
142+
)
143+
outputs_info.append(info)
117144

118-
# Write outputs to files passed as argv[n_inputs+1..n_inputs+n_outputs]
119-
n_in = len(inputs_info)
120-
for i, (c_nm, c_type, _, shape) in enumerate(outputs_info):
121-
n = 1
122-
for d in shape:
123-
n *= d
124-
lines.append(f" f = fopen(argv[{n_in + i + 1}], \"wb\");")
125-
lines.append(
126-
f" if (!f) {{ fprintf(stderr, \"Cannot open output {i}\\n\"); return 1; }}"
127-
)
128-
lines.append(f" fwrite({c_nm}, sizeof({c_type}), {n}, f);")
129-
lines.append(" fclose(f);")
145+
return inputs_info, outputs_info
130146

131-
lines += ["", " return 0;", "}"]
132-
return "\n".join(lines)
147+
148+
def _build_binary(workdir, model, inputs_info, outputs_info):
149+
"""Compile the ONNX model to a native binary; return the binary path."""
150+
model_path = os.path.join(workdir, "model.onnx")
151+
with open(model_path, "wb") as f:
152+
f.write(model.SerializeToString())
153+
154+
result = subprocess.run(
155+
["onnx2c", model_path], capture_output=True, timeout=120
156+
)
157+
if result.returncode != 0:
158+
raise BackendIsNotSupposedToImplementIt(
159+
f"onnx2c failed: {result.stderr.decode()}"
160+
)
161+
model_c_path = os.path.join(workdir, "model.c")
162+
with open(model_c_path, "wb") as f:
163+
f.write(result.stdout)
164+
165+
harness_path = os.path.join(workdir, "harness.c")
166+
with open(harness_path, "w") as f:
167+
f.write(_generate_harness(inputs_info, outputs_info))
168+
169+
binary_path = os.path.join(workdir, "model_exec")
170+
result = subprocess.run(
171+
["gcc", "-O2", "-o", binary_path, harness_path, model_c_path, "-lm"],
172+
capture_output=True,
173+
timeout=120,
174+
)
175+
if result.returncode != 0:
176+
raise BackendIsNotSupposedToImplementIt(
177+
f"Compilation failed: {result.stderr.decode()}"
178+
)
179+
return binary_path
133180

134181

135182
class Onnx2cBackendRep(BackendRep):
136183
"""Holds a compiled onnx2c binary and runs it for each inference call."""
137184

138185
def __init__(self, binary_path, workdir, inputs_info, outputs_info):
186+
"""Store compiled binary path and run-time type/shape information."""
139187
self._binary = binary_path
140188
self._workdir = workdir
141189
# Store only dtype and shape needed at run time
142190
self._input_dtypes = [np_dtype for _, _, np_dtype, _ in inputs_info]
143-
self._output_info = [(np_dtype, shape) for _, _, np_dtype, shape in outputs_info]
191+
self._output_info = [
192+
(np_dtype, shape) for _, _, np_dtype, shape in outputs_info
193+
]
144194

145195
def __del__(self):
196+
"""Remove the temporary working directory on garbage collection."""
146197
if self._workdir and os.path.isdir(self._workdir):
147198
shutil.rmtree(self._workdir, ignore_errors=True)
148199

149200
def run(self, inputs, **kwargs):
201+
"""Write inputs as binary files, invoke compiled binary, return outputs."""
150202
run_dir = tempfile.mkdtemp()
151203
try:
152204
# Write each input as raw binary with the expected dtype
153205
input_files = []
154-
for i, (inp, dtype) in enumerate(zip(inputs, self._input_dtypes)):
206+
for i, (inp, dtype) in enumerate(
207+
zip(inputs, self._input_dtypes, strict=False)
208+
):
155209
path = os.path.join(run_dir, f"input_{i}.bin")
156210
np.asarray(inp, dtype=dtype).flatten().tofile(path)
157211
input_files.append(path)
@@ -172,7 +226,9 @@ def run(self, inputs, **kwargs):
172226
)
173227

174228
outputs = []
175-
for path, (dtype, shape) in zip(output_files, self._output_info):
229+
for path, (dtype, shape) in zip(
230+
output_files, self._output_info, strict=False
231+
):
176232
arr = np.fromfile(path, dtype=dtype).reshape(shape)
177233
outputs.append(arr)
178234
return outputs
@@ -185,91 +241,38 @@ class Onnx2cBackend(Backend):
185241

186242
@classmethod
187243
def is_compatible(cls, model, device="CPU", **kwargs):
244+
"""Return whether this backend can attempt to handle the model."""
188245
return True
189246

190247
@classmethod
191248
def prepare(cls, model, device="CPU", **kwargs):
192-
# Run ONNX shape inference so output shapes are concrete where possible
249+
"""Compile the ONNX model to native code and return a runnable rep."""
193250
try:
194251
model = shape_inference.infer_shapes(model)
195-
except Exception:
252+
except Exception: # noqa: BLE001
196253
pass
197254

198-
initializer_names = {init.name for init in model.graph.initializer}
199-
200-
# Parse inputs (exclude initializers / weights)
201-
inputs_info = []
202-
for vi in model.graph.input:
203-
if vi.name in initializer_names:
204-
continue
205-
info = _parse_tensor_info(vi)
206-
if info is None:
207-
raise BackendIsNotSupposedToImplementIt(
208-
f"Input '{vi.name}' has unsupported type or dynamic shape"
209-
)
210-
inputs_info.append(info)
211-
212-
# Parse outputs
213-
outputs_info = []
214-
for vi in model.graph.output:
215-
info = _parse_tensor_info(vi)
216-
if info is None:
217-
raise BackendIsNotSupposedToImplementIt(
218-
f"Output '{vi.name}' has unsupported type or dynamic shape"
219-
)
220-
outputs_info.append(info)
255+
inputs_info, outputs_info = _parse_graph_io(model.graph)
221256

222257
workdir = tempfile.mkdtemp()
223258
try:
224-
# Serialize model
225-
model_path = os.path.join(workdir, "model.onnx")
226-
with open(model_path, "wb") as f:
227-
f.write(model.SerializeToString())
228-
229-
# Generate C code with onnx2c
230-
result = subprocess.run(
231-
["onnx2c", model_path], capture_output=True, timeout=120
232-
)
233-
if result.returncode != 0:
234-
raise BackendIsNotSupposedToImplementIt(
235-
f"onnx2c failed: {result.stderr.decode()}"
236-
)
237-
model_c_path = os.path.join(workdir, "model.c")
238-
with open(model_c_path, "wb") as f:
239-
f.write(result.stdout)
240-
241-
# Write harness
242-
harness_path = os.path.join(workdir, "harness.c")
243-
with open(harness_path, "w") as f:
244-
f.write(_generate_harness(inputs_info, outputs_info))
245-
246-
# Compile
247-
binary_path = os.path.join(workdir, "model_exec")
248-
result = subprocess.run(
249-
["gcc", "-O2", "-o", binary_path, harness_path, model_c_path, "-lm"],
250-
capture_output=True,
251-
timeout=120,
252-
)
253-
if result.returncode != 0:
254-
raise BackendIsNotSupposedToImplementIt(
255-
f"Compilation failed: {result.stderr.decode()}"
256-
)
257-
259+
binary_path = _build_binary(workdir, model, inputs_info, outputs_info)
258260
return Onnx2cBackendRep(binary_path, workdir, inputs_info, outputs_info)
259-
260261
except BackendIsNotSupposedToImplementIt:
261262
shutil.rmtree(workdir, ignore_errors=True)
262263
raise
263-
except Exception as e:
264+
except Exception as e: # noqa: BLE001
264265
shutil.rmtree(workdir, ignore_errors=True)
265266
raise BackendIsNotSupposedToImplementIt(str(e)) from e
266267

267268
@classmethod
268269
def run_model(cls, model, inputs, device="CPU", **kwargs):
270+
"""Prepare then run a model in one call."""
269271
return cls.prepare(model, device, **kwargs).run(inputs)
270272

271273
@classmethod
272274
def supports_device(cls, device):
275+
"""Return whether the backend supports the given device."""
273276
return device == "CPU"
274277

275278

0 commit comments

Comments
 (0)