12 if (
n.domain ==
"com.microsoft" &&
n.op ==
"DynamicQuantizeLSTM") {
13 if (
n.inputs.size() != 12 ||
n.outputs.empty() ||
n.outputs.size() > 3)
return false;
14 for (
const auto& [
key,
a] :
n.attrs) {
15 if (
key ==
"direction") {
16 if (
a.type != 3 || (
a.text !=
"forward" &&
a.text !=
"reverse" &&
a.text !=
"bidirectional"))
18 }
else if (
key ==
"hidden_size" ||
key ==
"input_forget") {
19 if (
a.type != 2)
return false;
20 }
else if (
key ==
"clip") {
21 if (
a.type != 1)
return false;
27 if (!
n.domain.empty())
return false;
28 if (
n.op ==
"If" ||
n.op ==
"Loop") {
29 const std::set<std::string> keys =
30 n.op ==
"If" ? std::set<std::string>{
"then_branch",
"else_branch"} : std::set<std::string>{
"body"};
31 if (
n.attrs.size() != keys.size() ||
n.outputs.empty() ||
32 (
n.op ==
"If" ?
n.inputs.size() != 1 :
n.inputs.size() < 2))
34 for (
const auto&
key : keys) {
35 auto it =
n.attrs.find(
key);
36 if (it ==
n.attrs.end() || it->second.type != 5 || !it->second.graph)
return false;
41 static const std::map<std::string, std::set<std::string>> allowed{
42 {
"DynamicQuantizeLinear", {}},
43 {
"QuantizeLinear", {
"axis"}},
44 {
"DequantizeLinear", {
"axis"}},
45 {
"MatMulInteger", {}},
46 {
"ConvInteger", {
"auto_pad",
"dilations",
"group",
"kernel_shape",
"pads",
"strides"}},
47 {
"Constant", {
"value"}},
50 {
"CumSum", {
"exclusive",
"reverse"}},
51 {
"SequenceEmpty", {
"dtype"}},
53 {
"SequenceInsert", {}},
54 {
"SplitToSequence", {
"axis",
"keepdims"}},
55 {
"ConcatFromSequence", {
"axis",
"new_axis"}},
56 {
"RandomUniformLike", {
"dtype",
"seed",
"low",
"high"}},
57 {
"RandomNormalLike", {
"dtype",
"seed",
"mean",
"scale"}},
58 {
"InstanceNormalization", {
"epsilon"}},
59 {
"ConvTranspose", {
"auto_pad",
"dilations",
"group",
"kernel_shape",
"output_padding",
"pads",
"strides"}},
60 {
"Resize", {
"coordinate_transformation_mode",
"mode",
"nearest_mode",
"cubic_coeff_a"}},
62 {
"TopK", {
"axis",
"largest",
"sorted"}},
63 {
"ScatterND", {
"reduction"}},
64 {
"ScatterElements", {
"axis",
"reduction"}},
65 {
"ReduceProd", {
"axes",
"keepdims"}},
66 {
"ReduceMax", {
"axes",
"keepdims"}},
70 {
"ConstantOfShape", {
"value"}},
78 {
"LeakyRelu", {
"alpha"}},
85 {
"Shape", {
"start",
"end"}},
86 {
"Reshape", {
"allowzero"}},
87 {
"Transpose", {
"perm"}},
106 {
"Softmax", {
"axis"}},
107 {
"ReduceMean", {
"axes",
"keepdims"}},
108 {
"ReduceSum", {
"keepdims",
"noop_with_empty_axes"}}};
109 auto op = allowed.find(
n.op);
110 if (op == allowed.end())
return false;
120 if (
n.op ==
"SequenceInsert") {
124 if (
n.op ==
"SplitToSequence")
maximum = 2;
126 if (
n.op ==
"ConvTranspose") {
130 if (
n.op ==
"Resize") {
134 if (
n.op ==
"Range" ||
n.op ==
"ScatterElements" ||
n.op ==
"ScatterND")
minimum =
maximum = 3;
135 if (
n.op ==
"TopK") {
139 if (
n.op ==
"Slice") {
143 if (
n.op ==
"Concat")
maximum = 100000;
145 if (
n.op ==
"Expand" ||
n.op ==
"Equal" ||
n.op ==
"Less" ||
n.op ==
"Greater" ||
n.op ==
"And")
148 if (
n.op ==
"DynamicQuantizeLinear") outputs = 3;
149 if (
n.op ==
"QuantizeLinear" ||
n.op ==
"DequantizeLinear") {
153 if (
n.op ==
"MatMulInteger" ||
n.op ==
"ConvInteger") {
157 if (
n.op ==
"Gather" ||
n.op ==
"Reshape" ||
n.op ==
"Unsqueeze" ||
n.op ==
"MatMul" ||
n.op ==
"Add" ||
158 n.op ==
"Sub" ||
n.op ==
"Mul" ||
n.op ==
"Div" ||
n.op ==
"Pow")
160 if (
n.op ==
"Squeeze" ||
n.op ==
"ReduceSum")
maximum = 2;
161 if (
n.inputs.size() <
minimum ||
n.inputs.size() >
maximum ||
n.outputs.size() != outputs)
return false;
162 for (
const auto& [
key,
a] :
n.attrs) {
163 if (!op->second.contains(
key))
return false;
164 const int expected = (
key ==
"alpha" ||
key ==
"epsilon" ||
key ==
"cubic_coeff_a" ||
key ==
"seed" ||
165 key ==
"low" ||
key ==
"high" ||
key ==
"mean" ||
key ==
"scale")
168 : (
key ==
"auto_pad" ||
key ==
"reduction" ||
key ==
"coordinate_transformation_mode" ||
169 key ==
"mode" ||
key ==
"nearest_mode")
171 : (
key ==
"perm" ||
key ==
"axes" ||
key ==
"dilations" ||
key ==
"kernel_shape" ||
172 key ==
"pads" ||
key ==
"strides" ||
key ==
"output_padding")
175 if (
a.type != expected)
return false;
176 if (
key ==
"reduction" &&
a.text !=
"none")
return false;
177 if (
key ==
"auto_pad" &&
a.text !=
"NOTSET")
return false;
178 if (
key ==
"allowzero" &&
a.integer != 0)
return false;
180 if (
n.op ==
"Pad" &&
n.attrs.contains(
"mode") &&
n.attrs.at(
"mode").text !=
"constant" &&
181 n.attrs.at(
"mode").text !=
"edge" &&
n.attrs.at(
"mode").text !=
"reflect")
183 if (
n.op ==
"Resize") {
184 const auto mode =
n.attrs.contains(
"mode") ?
n.attrs.at(
"mode").text :
"nearest";
185 const auto coordinate =
n.attrs.contains(
"coordinate_transformation_mode")
186 ?
n.attrs.at(
"coordinate_transformation_mode").text
188 const auto nearest =
n.attrs.contains(
"nearest_mode") ?
n.attrs.at(
"nearest_mode").text :
"round_prefer_floor";
189 if (mode ==
"nearest") {
190 if (coordinate !=
"asymmetric" || nearest !=
"floor")
return false;
191 }
else if (mode !=
"linear" || (coordinate !=
"half_pixel" && coordinate !=
"asymmetric"))
197std::vector<int> dims(
const RuntimeTensor&
x) {
198 std::vector<int> out;
201 out.push_back(
static_cast<int>(
d));
205std::vector<RuntimeTensor> single(RuntimeTensor
x) {
return {std::move(
x)}; }
209 if (
n.op ==
"Constant") {
210 auto a =
n.attrs.find(
"value");
211 if (
a ==
n.attrs.end() || !
a->second.tensor)
throw Failure(
"Constant requires tensor value");
212 return single(*
a->second.tensor);
214 if (
n.op.find(
"Quantize") != std::string::npos ||
n.op ==
"DequantizeLinear" ||
n.op ==
"MatMulInteger" ||
215 n.op ==
"ConvInteger")
218 if (
n.op ==
"Identity")
return single(
x);
219 if (
n.op ==
"Shape") {
220 const int64_t rank =
x.shape.size();
221 auto norm = [rank](int64_t i) {
return std::clamp(i < 0 ? i + rank : i, int64_t(0), rank); };
223 std::vector<int64_t>
shape;
227 if (
n.op ==
"Cast") {
233 for (
size_t i = 0; i <
count(
x.shape); ++i) {
237 std::memcpy(out.bytes.data() + i * 4, &
v, 4);
241 const double f = read<float>(
x, i);
242 if (!std::isfinite(
f) || f < -9223372036854775808.0 || f >= 9223372036854775808.0)
243 throw Failure(
"Cast overflow");
248 std::memcpy(out.bytes.data() + i *
size, &
v,
size);
251 return single(std::move(out));
253 if (
n.op ==
"Gather") {
255 const int a =
axis(
attr(
n,
"axis", 0),
x.shape.size());
257 throw Failure(
"Gather indices must be int32/int64");
258 size_t inner = 1, outer = 1;
259 for (
size_t i =
a + 1; i <
x.shape.size(); ++i) inner *=
x.shape[i];
260 for (
int i = 0; i <
a; ++i) outer *=
x.shape[i];
261 std::vector<int64_t>
shape(
x.shape.begin(),
x.shape.begin() +
a);
263 shape.insert(
shape.end(),
x.shape.begin() +
a + 1,
x.shape.end());
266 for (
size_t o = 0; o < outer; ++o)
267 for (
size_t i = 0; i < ni; ++i) {
269 if (j < 0) j +=
x.shape[
a];
270 if (j < 0 || j >=
x.shape[
a])
throw Failure(
"Gather index out of range");
272 std::memcpy(out.bytes.data() + (o * ni + i) * stride,
273 x.bytes.data() + (o *
x.shape[
a] + j) * stride, stride);
275 return single(std::move(out));
277 if (
n.op ==
"Reshape" ||
n.op ==
"Unsqueeze" ||
n.op ==
"Squeeze") {
279 if (
n.op ==
"Reshape") {
283 for (
size_t i = 0; i <
shape.size(); ++i) {
285 if (i >=
x.shape.size())
throw Failure(
"Reshape zero axis out of range");
288 if (
shape[i] == -1) {
289 if (infer != -1)
throw Failure(
"Multiple inferred dimensions");
290 infer =
static_cast<int>(i);
293 (
shape[i] && known > 128u * 1024u * 1024u /
static_cast<size_t>(
shape[i])))
294 throw Failure(
"Reshape overflow");
295 known *=
static_cast<size_t>(
shape[i]);
299 if (!known ||
count(
x.shape) % known)
throw Failure(
"Invalid inferred shape");
303 std::vector<int64_t> axes;
304 if (in.size() > 1 && in[1])
306 else if (
n.op ==
"Unsqueeze")
307 throw Failure(
"Unsqueeze needs axes");
309 for (
size_t i = 0; i <
shape.size(); ++i)
310 if (
shape[i] == 1) axes.push_back(i);
311 const size_t rank =
shape.size() + (
n.op ==
"Unsqueeze" ? axes.size() : 0);
315 if (
n.op ==
"Unsqueeze") {
319 if (
shape[*it] != 1)
throw Failure(
"Squeeze dimension not one");
326 return single(std::move(out));
328 if (
n.op ==
"Transpose") {
331 for (
size_t i =
x.shape.size(); i > 0; --i)
order.push_back(i - 1);
332 if (
order.size() !=
x.shape.size())
throw Failure(
"Invalid transpose rank");
333 std::set<int64_t> used;
334 std::vector<int64_t>
shape;
336 if (a < 0 || a >=
static_cast<int64_t
>(
x.shape.size()) || !used.insert(
a).second)
337 throw Failure(
"Invalid transpose permutation");
342 std::vector<size_t> strides(
x.shape.size(), 1);
343 for (
size_t i =
x.shape.size(); i > 1; --i) strides[i - 2] = strides[i - 1] *
x.shape[i - 1];
345 size_t rest = i,
source = 0;
346 for (
size_t j =
shape.size(); j > 0; --j) {
348 rest /=
shape[j - 1];
352 return single(std::move(out));
354 if (
auto indexResult =
executeIndex(
n, in))
return std::move(*indexResult);
355 if (
auto shapeResult =
executeShape(
n, in))
return single(std::move(*shapeResult));
360 if (
n.op ==
"Softmax") {
362 std::vector<float> out(
a.size());
367 static const std::map<std::string, OpType> unary{
371 auto op = unary.find(
n.op);
373 std::vector<float> out(
a.size());
std::vector< float > positions
const UnitySourceAsset & source
GPU execution boundary for native ONNX; retains no model and retains compiled resources for the lifet...
void softmax(const float *in, const int *dims, int rank, int axis, bool logMode, float *out)
Softmax.
void unaryOp(OpType type, const float *in, int count, float *out, float s0, float s1)
Unary op.
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
std::optional< RuntimeTensor > executeMisc(const Node &, const std::vector< const RuntimeTensor * > &, OnnxCompute *)
Execute misc.
std::vector< int64_t > ints(const RuntimeTensor &v)
Ints.
int64_t attr(const Node &n, const char *key, int64_t fallback)
Attr.
std::optional< RuntimeTensor > executeNumeric(const Node &, const std::vector< const RuntimeTensor * > &, OnnxCompute *)
Execute numeric.
std::vector< int64_t > attrs(const Node &n, const char *key, std::vector< int64_t > fallback)
Attrs.
std::vector< RuntimeTensor > executeQuant(const Node &node, const std::vector< const RuntimeTensor * > &inputs, OnnxCompute *compute=nullptr)
Execute quant.
std::optional< RuntimeTensor > executeNeural(const Node &, const std::vector< const RuntimeTensor * > &, OnnxCompute *)
Execute neural.
RuntimeTensor make(OnnxElement e, std::vector< int64_t > shape, const std::vector< T > &values)
Make.
int axis(int64_t a, size_t rank)
Axis.
std::vector< float > floats(const RuntimeTensor &v)
Floats.
RuntimeTensor dispatchFloat(OnnxCompute &, const std::vector< const RuntimeTensor * > &, const std::vector< int64_t > &, const std::string &, size_t work=0)
Dispatches float.
bool isSupported(const Node &n)
True when supported.
size_t elementSize(OnnxElement e)
Element size.
std::vector< RuntimeTensor > executeQuantLstm(const Node &node, const std::vector< const RuntimeTensor * > &inputs, OnnxCompute *compute=nullptr)
Execute quant lstm.
std::optional< RuntimeTensor > executeShape(const Node &, const std::vector< const RuntimeTensor * > &)
Execute shape.
std::vector< RuntimeTensor > execute(const Node &n, const std::vector< const RuntimeTensor * > &in, OnnxCompute *compute)
Execute.
std::optional< std::vector< RuntimeTensor > > executeIndex(const Node &n, const std::vector< const RuntimeTensor * > &in)
Execute index.
OnnxElement
ONNX wire element types; distinct from block-quantized Tensor storage.
RuntimeTensor public API.
std::vector< int64_t > shape