Download export.py from phanerozoic/coilstellaration-direct: direct link, hf CLI and curl.
- Browser
- Download file 3.38 kB
-
https://huggingface.co/phanerozoic/coilstellaration-direct/resolve/main/export.py
- Command line
-
hf download hf://phanerozoic/coilstellaration-direct/export.py
-
curl -L -o export.py https://huggingface.co/phanerozoic/coilstellaration-direct/resolve/main/export.py
3.38 kB
| """A trained direct predictor as an ONNX file under the benchmark's contract: python export.py NAME OUT.onnx | |
| Reads ml/runs/NAME.pt and writes a graph from boundary_r_cos, boundary_z_sin (5, 9), requirement_metrics (4), | |
| n_field_periods and n_coils_per_half_period to coil_x_n, coil_y_n, coil_z_n (K, 15) and currents (K) in amperes, | |
| with the metadata key supported_tracks set to fixed_shape. The scalings of the inputs and outputs are constants of | |
| the graph. Prints the largest difference between the graph's outputs in onnxruntime and the network's in PyTorch. | |
| """ | |
| from __future__ import annotations | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| import onnx | |
| import onnxruntime as ort | |
| import torch | |
| from torch import nn | |
| from train import Net | |
| HERE = Path(__file__).resolve().parent | |
| class Wrapped(nn.Module): | |
| def __init__(self, ck): | |
| super().__init__() | |
| self.k = int(ck["k"]) | |
| self.net = Net(int(ck["varies"].sum()), self.k * 45 + self.k, int(ck["width"]), int(ck["depth"])) | |
| self.net.load_state_dict(ck["net"]) | |
| self.register_buffer("pick", torch.nonzero(ck["varies"]).reshape(-1)) | |
| for name in ("x_mean", "x_std", "y_mean", "y_std"): | |
| self.register_buffer(name, ck[name].float()) | |
| def forward(self, boundary_r_cos, boundary_z_sin, requirement_metrics, n_field_periods, n_coils_per_half_period): | |
| x = torch.cat([boundary_r_cos.reshape(45), boundary_z_sin.reshape(45), requirement_metrics.log()]) | |
| x = (x.index_select(0, self.pick) - self.x_mean) / self.x_std | |
| y = self.net(x.reshape(1, -1)).reshape(-1) * self.y_std + self.y_mean | |
| # the two shape inputs are part of the contract; the graph is for one shape, so they add nothing | |
| y = y + 0.0 * (n_field_periods + n_coils_per_half_period).to(y.dtype) | |
| coef = y[:self.k * 45].reshape(self.k, 3, 15) | |
| return coef[:, 0], coef[:, 1], coef[:, 2], y[self.k * 45:] * 1e6 | |
| def main(name, out): | |
| ck = torch.load(HERE / "runs" / f"{name}.pt", map_location="cpu") | |
| model = Wrapped(ck).eval() | |
| feed = (torch.randn(5, 9) * 0.05, torch.randn(5, 9) * 0.05, torch.tensor([0.4, 0.8, 3.0, 0.05]), torch.tensor(3), torch.tensor(5)) | |
| feed[0][0, 4] = 1.0 | |
| names_in = ["boundary_r_cos", "boundary_z_sin", "requirement_metrics", "n_field_periods", "n_coils_per_half_period"] | |
| names_out = ["coil_x_n", "coil_y_n", "coil_z_n", "currents"] | |
| torch.onnx.export(model, feed, out, input_names=names_in, output_names=names_out, opset_version=17, dynamo=False) | |
| proto = onnx.load(out) | |
| proto.metadata_props.append(onnx.StringStringEntryProto(key="supported_tracks", value="fixed_shape")) | |
| onnx.save(proto, out) | |
| onnx.checker.check_model(out) | |
| sess = ort.InferenceSession(out, providers=["CPUExecutionProvider"]) | |
| got = sess.run(None, {n: (v.numpy().astype(np.int64) if v.dtype == torch.int64 else v.numpy()) for n, v in zip(names_in, feed)}) | |
| with torch.no_grad(): | |
| want = model(*feed) | |
| worst = max(float(np.abs(g - w.numpy()).max() / max(1.0, float(w.abs().max()))) for g, w in zip(got, want)) | |
| print(f"wrote {out}: {Path(out).stat().st_size / 2 ** 20:.1f} MiB, dev feasible in training {ck.get('dev_feasible')}, " | |
| f"largest relative difference from PyTorch {worst:.2e}, inputs {[i.name for i in sess.get_inputs()]}") | |
| if __name__ == "__main__": | |
| main(*sys.argv[1:3]) | |