executorch.js 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269
  1. // Experimental
  2. const executorch = {};
  3. const coreml = {};
  4. const vulkan = {};
  5. const xnnpack = {};
  6. const qnn = {};
  7. const ethosu = {};
  8. import * as base from './base.js';
  9. import * as python from './python.js';
  10. import * as pytorch from './pytorch.js';
  11. executorch.ModelFactory = class {
  12. async match(context) {
  13. const reader = await executorch.Reader.open(context);
  14. if (reader) {
  15. return context.set('executorch', reader);
  16. }
  17. return null;
  18. }
  19. async open(context) {
  20. executorch.schema = await context.require('./executorch-schema');
  21. const target = context.value;
  22. await target.read();
  23. return new executorch.Model(target);
  24. }
  25. };
  26. executorch.Model = class {
  27. constructor(target) {
  28. this.format = `ExecuTorch v${target.program.version}`;
  29. this.modules = [];
  30. for (const plan of target.program.execution_plan) {
  31. for (const chain of plan.chains) {
  32. const graph = new executorch.Graph(target, plan, chain);
  33. this.modules.push(graph);
  34. }
  35. }
  36. }
  37. };
  38. executorch.Graph = class {
  39. constructor(target, plan, chain) {
  40. this.name = plan.name || '';
  41. this.inputs = [];
  42. this.outputs = [];
  43. this.nodes = [];
  44. const values = new Map();
  45. values.tensors = (index, items) => {
  46. const list = [];
  47. for (let i = 0; i < items.length; i++) {
  48. const item = items[i];
  49. const type = item ? new executorch.TensorType(item) : null;
  50. let initializer = null;
  51. if (item && item.data_buffer_idx > 0) {
  52. initializer = new executorch.Tensor(item, target);
  53. }
  54. const identifier = items.length > 1 ? `${index}.${i}` : index.toString();
  55. const value = new executorch.Value(identifier, type, initializer);
  56. list.push(value);
  57. }
  58. return list;
  59. };
  60. values.map = (index, output) => {
  61. if (!values.has(index)) {
  62. const executorch_flatbuffer = executorch.schema.executorch_flatbuffer;
  63. const val = plan.values[index].val;
  64. const tensor = val instanceof executorch_flatbuffer.Tensor || val instanceof executorch_flatbuffer.TensorList || val instanceof executorch_flatbuffer.OptionalTensorList;
  65. if (output && !tensor) {
  66. const value = [new executorch.Value(index.toString(), null, null)];
  67. values.set(index, { type: null, value });
  68. } else if (val instanceof executorch_flatbuffer.Null) {
  69. values.set(index, { type: 'attribute', value: null });
  70. } else if (val instanceof executorch_flatbuffer.Int) {
  71. values.set(index, { type: 'int64', value: val.int_val });
  72. } else if (val instanceof executorch_flatbuffer.Bool) {
  73. values.set(index, { type: 'int64', value: val.bool_val });
  74. } else if (val instanceof executorch_flatbuffer.Double) {
  75. values.set(index, { type: 'float64', value: val.double_val });
  76. } else if (val instanceof executorch_flatbuffer.Tensor) {
  77. const items = [val];
  78. values.set(index, { type: null, value: values.tensors(index, items) });
  79. } else if (val instanceof executorch_flatbuffer.String) {
  80. values.set(index, { type: 'string', value: val.string_val });
  81. } else if (val instanceof executorch_flatbuffer.IntList) {
  82. const list = val.items.map((index) => plan.values[index].val.int_val);
  83. values.set(index, { type: 'int64[]', value: list });
  84. } else if (val instanceof executorch_flatbuffer.DoubleList) {
  85. values.set(index, { type: 'float64[]', value: Array.from(val.items) });
  86. } else if (val instanceof executorch_flatbuffer.BoolList) {
  87. throw new executorch.Error('executorch_flatbuffer.BoolList not implemented.');
  88. } else if (val instanceof executorch_flatbuffer.TensorList) {
  89. const items = Array.from(val.items).map((arg) => arg === -1 ? null : plan.values[arg].val);
  90. values.set(index, { type: null, value: values.tensors(index, items) });
  91. } else if (val instanceof executorch_flatbuffer.OptionalTensorList) {
  92. const items = Array.from(val.items).map((arg) => arg === -1 ? null : plan.values[arg].val);
  93. values.set(index, { type: null, value: values.tensors(index, items) });
  94. } else {
  95. throw new Error(`Value type '${val.constructor.name}' not implemented.`);
  96. }
  97. }
  98. return values.get(index);
  99. };
  100. for (let i = 0; i < plan.inputs.length; i++) {
  101. const input = plan.inputs[i];
  102. const value = values.map(input);
  103. const name = plan.inputs.length === 1 ? 'input' : `input.${i}`;
  104. const argument = new executorch.Argument(name, value.value, value.type);
  105. this.inputs.push(argument);
  106. }
  107. for (let i = 0; i < plan.outputs.length; i++) {
  108. const output = plan.outputs[i];
  109. const value = values.map(output);
  110. const name = plan.outputs.length === 1 ? 'output' : `output.${i}`;
  111. const argument = new executorch.Argument(name, value.value, value.type);
  112. this.outputs.push(argument);
  113. }
  114. for (const instruction of chain.instructions) {
  115. const node = new executorch.Node(target, plan, chain, instruction, values);
  116. this.nodes.push(node);
  117. }
  118. }
  119. };
  120. executorch.Argument = class {
  121. constructor(name, value, type = null, visible = true) {
  122. this.name = name;
  123. this.value = value;
  124. this.type = type;
  125. this.visible = visible;
  126. }
  127. };
  128. executorch.Value = class Value {
  129. constructor(name, type, initializer = null) {
  130. if (typeof name !== 'string') {
  131. throw new executorch.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
  132. }
  133. this.name = name;
  134. this.type = initializer && initializer.type ? initializer.type : type || null;
  135. this.initializer = initializer;
  136. }
  137. };
  138. executorch.Node = class {
  139. constructor(target, plan, chain, instruction, values) {
  140. this.name = '';
  141. this.inputs = [];
  142. this.outputs = [];
  143. this.attributes = [];
  144. const instr_args = instruction.instr_args;
  145. const executorch_flatbuffer = executorch.schema.executorch_flatbuffer;
  146. if (instr_args instanceof executorch_flatbuffer.KernelCall) {
  147. const op = plan.operators[instr_args.op_index];
  148. const name = op.name.split('::').pop();
  149. const identifier = op.overload ? `${op.name}.${op.overload}` : op.name;
  150. const torch = target.execution.__import__('torch');
  151. const schemas = torch._C._jit_get_schemas_for_operator(op.name);
  152. const schema = schemas.find((schema) => schema.name === op.name && schema.overload_name === op.overload);
  153. if (!schema) {
  154. throw new executorch.Error(`Operator schema for '${identifier}' not found.`);
  155. }
  156. const category = schema && schema.category ? schema.category : '';
  157. const alias = (arg) => arg && arg.alias_info && arg.alias_info.before_set.length === 1 ? arg.alias_info.before_set[0] : null;
  158. const outputs = new Set(schema && Array.isArray(schema.returns) ? schema.returns.map((arg) => alias(arg)).filter((alias) => alias !== null) : []);
  159. const inputs = new Map();
  160. this.type = { name, identifier, category };
  161. let i = 0;
  162. const args = instr_args.args;
  163. for (; i < schema.arguments.length; i++) {
  164. const index = args[i];
  165. const arg = schema && i < schema.arguments.length ? schema.arguments[i] : null;
  166. const output = arg ? alias(schema.arguments[i]) : null;
  167. if (output && outputs.has(output)) {
  168. inputs.set(output, index);
  169. continue;
  170. }
  171. const name = arg ? arg.name : i.toString();
  172. const value = values.map(index);
  173. const argument = new executorch.Argument(name, value.value, value.type);
  174. this.inputs.push(argument);
  175. }
  176. for (let j = 0; j < schema.returns.length; j++) {
  177. const ret = schema.returns[j];
  178. const output = alias(ret);
  179. let index = args[i++];
  180. index = output && inputs.has(output) ? inputs.get(output) : index;
  181. const name = ret.name;
  182. const value = values.map(index, true);
  183. const argument = new executorch.Argument(name || '', value.value, value.type);
  184. this.outputs.push(argument);
  185. }
  186. } else if (instr_args instanceof executorch_flatbuffer.DelegateCall) {
  187. const delegate = plan.delegates[instr_args.delegate_index];
  188. const args = instr_args.args;
  189. if (!delegate.backend || !delegate.backend.type) {
  190. throw new executorch.Error(`ExecuTorch delegate '${delegate.id}' not implemented.`);
  191. }
  192. this.type = delegate.backend.type;
  193. const inputs = args.slice(0, this.type.inputs.length);
  194. for (let i = 0; i < inputs.length; i++) {
  195. const input = inputs[i];
  196. const value = values.map(input);
  197. const name = inputs.length === 1 ? 'input' : `input.${i}`;
  198. const argument = new executorch.Argument(name, value.value, value.type);
  199. this.inputs.push(argument);
  200. }
  201. const outputs = args.slice(this.type.inputs.length, this.type.inputs.length + this.type.outputs.length);
  202. for (let i = 0; i < outputs.length; i++) {
  203. const output = outputs[i];
  204. const value = values.map(output);
  205. const name = inputs.length === 1 ? 'output' : `output.${i}`;
  206. const argument = new executorch.Argument(name, value.value, value.type);
  207. this.outputs.push(argument);
  208. }
  209. for (const spec of delegate.compile_specs) {
  210. const value = spec.value instanceof Uint8Array ? new TextDecoder('utf-8').decode(spec.value) : spec.value;
  211. const attribute = new executorch.Argument(spec.key, value, 'attribute');
  212. this.attributes.push(attribute);
  213. }
  214. } else {
  215. throw new Error(`Instruction type '${instr_args.constructor.name}' not implemented.`);
  216. }
  217. }
  218. };
  219. executorch.TensorType = class {
  220. constructor(tensor) {
  221. const ScalarType = executorch.schema.executorch_flatbuffer.ScalarType;
  222. switch (tensor.scalar_type) {
  223. case ScalarType.BYTE: this.dataType = 'uint8'; break;
  224. case ScalarType.CHAR: this.dataType = 'int8'; break;
  225. case ScalarType.SHORT: this.dataType = 'int16'; break;
  226. case ScalarType.INT: this.dataType = 'int32'; break;
  227. case ScalarType.LONG: this.dataType = 'int64'; break;
  228. case ScalarType.HALF: this.dataType = 'float16'; break;
  229. case ScalarType.FLOAT: this.dataType = 'float32'; break;
  230. case ScalarType.DOUBLE: this.dataType = 'float64'; break;
  231. case 8: this.dataType = 'complex32'; break;
  232. case 9: this.dataType = 'complex64'; break;
  233. case 10: this.dataType = 'complex128'; break;
  234. case ScalarType.BOOL: this.dataType = 'boolean'; break;
  235. case ScalarType.QINT8: this.dataType = 'qint8'; break;
  236. case ScalarType.QUINT8: this.dataType = 'quint8'; break;
  237. case ScalarType.QINT32: this.dataType = 'qint32'; break;
  238. case 15: this.dataType = 'bfloat16'; break;
  239. case ScalarType.QUINT4X2: this.dataType = 'quint4x2'; break;
  240. case ScalarType.QUINT2X4: this.dataType = 'quint2x4'; break;
  241. case 18: this.dataType = 'bits1x8'; break;
  242. case 19: this.dataType = 'bits2x4'; break;
  243. case 20: this.dataType = 'bits4x2'; break;
  244. case 21: this.dataType = 'bits8'; break;
  245. case ScalarType.BITS16: this.dataType = 'bits16'; break;
  246. case ScalarType.FLOAT8E5M2: this.dataType = 'float8e5m2'; break;
  247. case ScalarType.FLOAT8E4M3FN: this.dataType = 'float8e4m3fn'; break;
  248. case ScalarType.FLOAT8E5M2FNUZ: this.dataType = 'float8e5m2fnuz'; break;
  249. case ScalarType.FLOAT8E4M3FNUZ: this.dataType = 'float8e4m3fnuz'; break;
  250. case ScalarType.UINT16: this.dataType = 'uint16'; break;
  251. case ScalarType.UINT32: this.dataType = 'uint32'; break;
  252. case ScalarType.UINT64: this.dataType = 'uint64'; break;
  253. default: throw new executorch.Error(`Unknown tensor data type '${tensor.scalar_type}'.`);
  254. }
  255. this.shape = new executorch.TensorShape(Array.from(tensor.sizes));
  256. }
  257. toString() {
  258. return this.dataType + this.shape.toString();
  259. }
  260. };
  261. executorch.TensorShape = class {
  262. constructor(dimensions = []) {
  263. this.dimensions = dimensions;
  264. }
  265. toString() {
  266. if (this.dimensions && this.dimensions.length > 0) {
  267. return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
  268. }
  269. return '';
  270. }
  271. };
  272. executorch.Tensor = class {
  273. constructor(tensor, target) {
  274. this.type = new executorch.TensorType(tensor);
  275. const data_buffer_idx = tensor.data_buffer_idx;
  276. const program = target.program;
  277. if (tensor.extra_tensor_info) {
  278. throw new executorch.Error('Extra tensor info not implemented.');
  279. } else if (Array.isArray(program.constant_buffer) && program.constant_buffer.length > 0) {
  280. if (data_buffer_idx >= program.constant_buffer.length) {
  281. throw new executorch.Error(`Constant buffer index out of range.`);
  282. }
  283. const buffer = program.constant_buffer[data_buffer_idx];
  284. this.values = buffer.storage;
  285. this.encoding = '<';
  286. } else if (tensor.allocation_info === null) {
  287. const constant_segment = program.constant_segment;
  288. const data_segment = program.segments[constant_segment.segment_index];
  289. const offset = constant_segment.offsets[data_buffer_idx];
  290. let next = data_segment.size;
  291. if (data_buffer_idx + 1 < constant_segment.offsets.length) {
  292. next = constant_segment.offsets[data_buffer_idx + 1];
  293. }
  294. const size = next - offset;
  295. const position = data_segment.offset + offset;
  296. this.values = target.blob(position.toNumber(), size.toNumber());
  297. this.encoding = '<';
  298. } else {
  299. throw new executorch.Error('Tensor allocation info not implemented.');
  300. }
  301. }
  302. };
  303. executorch.Reader = class {
  304. static async open(context) {
  305. const reader = await context.peek('flatbuffers.binary');
  306. if (reader && reader.identifier === 'ET12') {
  307. return new executorch.Reader(context, reader);
  308. }
  309. return null;
  310. }
  311. constructor(context, reader) {
  312. this.context = context;
  313. this.reader = reader;
  314. }
  315. async read() {
  316. const context = this.context;
  317. this.metadata = await pytorch.Metadata.open(context);
  318. this.execution = new python.Execution();
  319. this.metadata.register(this.execution);
  320. const executorch_flatbuffer = executorch.schema.executorch_flatbuffer;
  321. this.program = executorch_flatbuffer.Program.create(this.reader);
  322. this.named_data = new Map();
  323. if (this.program.named_data) {
  324. this.named_data = new Map(this.program.named_data.map((entry) => [entry.key, entry.segment_index]));
  325. }
  326. this.reader = await context.read('binary');
  327. if (this.reader.length >= 32) {
  328. this.reader.seek(8);
  329. const magic = String.fromCharCode(...this.reader.read(4));
  330. if (magic === 'eh00') {
  331. this.extended_file_header = {
  332. length: this.reader.uint32(),
  333. program_size: this.reader.uint64(),
  334. segment_base_offset: this.reader.uint64(),
  335. };
  336. }
  337. this.reader.seek(0);
  338. }
  339. for (const plan of this.program.execution_plan) {
  340. for (const chain of plan.chains) {
  341. for (const instruction of chain.instructions) {
  342. const instr_args = instruction.instr_args;
  343. if (instr_args instanceof executorch_flatbuffer.DelegateCall) {
  344. const delegate = plan.delegates[instr_args.delegate_index];
  345. if (delegate.backend) {
  346. continue;
  347. }
  348. let data = null;
  349. switch (delegate.processed.location) {
  350. case executorch_flatbuffer.DataLocation.INLINE: {
  351. data = this.program.backend_delegate_data[delegate.processed.index].data;
  352. break;
  353. }
  354. case executorch_flatbuffer.DataLocation.SEGMENT: {
  355. const segment = this.program.segments[delegate.processed.index];
  356. const offset = segment.offset;
  357. const size = segment.size;
  358. data = this.blob(offset.toNumber(), size.toNumber());
  359. break;
  360. }
  361. default: {
  362. throw new executorch.Error(`Delegate data location '${delegate.processed.location}' not implemented.`);
  363. }
  364. }
  365. switch (delegate.id) {
  366. case 'XnnpackBackend':
  367. delegate.backend = xnnpack.Reader.open(data, this);
  368. break;
  369. case 'CoreMLBackend':
  370. delegate.backend = coreml.Reader.open(data, this);
  371. break;
  372. case 'VulkanBackend':
  373. delegate.backend = vulkan.Reader.open(data, this);
  374. break;
  375. case 'QnnBackend':
  376. delegate.backend = qnn.Reader.open(data, this);
  377. break;
  378. case 'EthosUBackend':
  379. delegate.backend = ethosu.Reader.open(data, this);
  380. break;
  381. default:
  382. throw new executorch.Error(`ExecuTorch delegate '${delegate.id}' not implemented.`);
  383. }
  384. /* eslint-disable no-await-in-loop */
  385. await delegate.backend.read();
  386. /* eslint-enable no-await-in-loop */
  387. }
  388. }
  389. }
  390. }
  391. }
  392. blob(offset, size) {
  393. if (this.extended_file_header) {
  394. const segment_base_offset = this.extended_file_header.segment_base_offset;
  395. this.reader.seek(segment_base_offset.toNumber() + offset);
  396. const data = this.reader.read(size);
  397. this.reader.seek(0);
  398. return data;
  399. }
  400. return null;
  401. }
  402. segment(key) {
  403. if (this.named_data.has(key)) {
  404. const segment_index = this.named_data.get(key);
  405. if (segment_index >= 0 && segment_index < this.program.segments.length) {
  406. const segment = this.program.segments[segment_index];
  407. const offset = segment.offset;
  408. const size = segment.size;
  409. return this.blob(offset.toNumber(), size.toNumber());
  410. }
  411. }
  412. return null;
  413. }
  414. };
  415. executorch.Error = class extends Error {
  416. constructor(message) {
  417. super(message);
  418. this.name = 'Error loading ExecuTorch model.';
  419. }
  420. };
  421. xnnpack.Reader = class {
  422. static open(data, target) {
  423. if (data.length >= 30) {
  424. const reader = base.BinaryReader.open(data);
  425. reader.skip(4);
  426. const magic = String.fromCharCode(...reader.read(4));
  427. if (magic === 'XH00') {
  428. return new xnnpack.Reader(reader, target);
  429. }
  430. }
  431. return null;
  432. }
  433. constructor(reader, target) {
  434. this.reader = reader;
  435. this.target = target;
  436. reader.skip(2);
  437. this.flatbuffer = {
  438. offset: reader.uint32(),
  439. size: reader.uint32(),
  440. };
  441. this.constants = {
  442. offset: reader.uint32(),
  443. size: reader.uint32(),
  444. };
  445. }
  446. async read() {
  447. this.reader.seek(this.flatbuffer.offset);
  448. const flatbuffers = await import('./flatbuffers.js');
  449. const data = this.reader.read(this.flatbuffer.size);
  450. const reader = flatbuffers.BinaryReader.open(data);
  451. if (!executorch.schema.fb_xnnpack.XNNGraph.identifier(reader)) {
  452. throw new xnnpack.Error('Invalid XNNPACK data.');
  453. }
  454. this.graph = executorch.schema.fb_xnnpack.XNNGraph.create(reader);
  455. this.reader.seek(0);
  456. const metadata = new xnnpack.Metadata();
  457. this.type = new xnnpack.Graph(metadata, this.graph, this);
  458. }
  459. constant(idx) {
  460. const constant_data = this.graph.constant_data[idx];
  461. const named_key = constant_data.named_key;
  462. if (named_key) {
  463. return this.target.segment(named_key);
  464. }
  465. const offset = constant_data.offset;
  466. const size = constant_data.size;
  467. this.reader.seek(this.constants.offset + offset.toNumber());
  468. const data = this.reader.read(size.toNumber());
  469. this.reader.seek(0);
  470. return data;
  471. }
  472. };
  473. xnnpack.Graph = class {
  474. constructor(metadata, graph, reader) {
  475. this.name = 'XnnpackBackend';
  476. this.type = 'graph';
  477. this.inputs = [];
  478. this.outputs = [];
  479. this.nodes = [];
  480. const values = new Map();
  481. values.map = (id) => {
  482. if (!values.has(id)) {
  483. const fb_xnnpack = executorch.schema.fb_xnnpack;
  484. const name = id.toString();
  485. const xvalue = graph.xvalues[id].xvalue_union;
  486. if (xvalue instanceof fb_xnnpack.XNNTensorValue) {
  487. const type = new xnnpack.TensorType(xvalue);
  488. const initializer = xvalue.constant_buffer_idx === 0 ? null : new xnnpack.Tensor(xvalue, reader);
  489. const value = new xnnpack.Value(name, type, initializer);
  490. values.set(id, value);
  491. } else if (xvalue instanceof fb_xnnpack.XNNQuantizedTensorValue) {
  492. const value = new xnnpack.Value(name, null, null);
  493. values.set(id, value);
  494. } else {
  495. throw new xnnpack.Error(`Value type '${xvalue.constructor.name}' not implemented.`);
  496. }
  497. }
  498. return values.get(id);
  499. };
  500. for (let i = 0; i < graph.input_ids.length; i++) {
  501. const id = graph.input_ids[i];
  502. const value = values.map(id);
  503. const name = graph.input_ids.length === 1 ? 'input' : `input.${i}`;
  504. const argument = new xnnpack.Argument(name, [value]);
  505. this.inputs.push(argument);
  506. }
  507. for (let i = 0; i < graph.output_ids.length; i++) {
  508. const id = graph.output_ids[i];
  509. const value = values.map(id);
  510. const name = graph.output_ids.length === 1 ? 'output' : `output.${i}`;
  511. const argument = new xnnpack.Argument(name, [value]);
  512. this.outputs.push(argument);
  513. }
  514. for (const xnode of graph.xnodes) {
  515. const node = new xnnpack.Node(metadata, xnode, values);
  516. this.nodes.push(node);
  517. }
  518. }
  519. };
  520. xnnpack.Node = class {
  521. constructor(metadata, xnode, values) {
  522. const node = xnode.xnode_union;
  523. this.type = metadata.type(node.constructor.name) || { name: node.constructor.name };
  524. this.name = '';
  525. this.inputs = [];
  526. this.outputs = [];
  527. for (const [name, obj] of Object.entries(node)) {
  528. let value = ArrayBuffer.isView(obj) ? Array.from(obj) : obj;
  529. let type = 'attribute';
  530. if (name.endsWith('_id')) {
  531. value = obj === -1 ? [] : [values.map(obj)];
  532. type = null;
  533. }
  534. const argument = new xnnpack.Argument(name, value, type);
  535. if (name === 'output_id') {
  536. this.outputs.push(argument);
  537. } else {
  538. this.inputs.push(argument);
  539. }
  540. }
  541. }
  542. };
  543. xnnpack.Argument = class {
  544. constructor(name, value, type = null, visible = true) {
  545. this.name = name;
  546. this.value = value;
  547. this.type = type;
  548. this.visible = visible;
  549. }
  550. };
  551. xnnpack.Value = class Value {
  552. constructor(name, type, initializer = null) {
  553. if (typeof name !== 'string') {
  554. throw new executorch.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
  555. }
  556. this.name = name;
  557. this.type = initializer && initializer.type ? initializer.type : type || null;
  558. this.initializer = initializer;
  559. }
  560. };
  561. xnnpack.Metadata = class {
  562. constructor() {
  563. this._types = new Map();
  564. this.register('_XNNCat', 'Tensor');
  565. this.register('_XNNNodeConv', 'Layer');
  566. this.register('XNNArgMaxPooling2d', 'Pool');
  567. this.register('XNNAvgPooling2d', 'Pool');
  568. this.register('XNNCeiling', 'Activation');
  569. this.register('XNNConcatenate2', 'Tensor');
  570. this.register('XNNConcatenate3', 'Tensor');
  571. this.register('XNNConcatenate4', 'Tensor');
  572. this.register('XNNConcatenate5', 'Tensor');
  573. this.register('XNNConv2d', 'Layer');
  574. this.register('XNNConvTranspose2d', 'Layer');
  575. this.register('XNNDepthwiseConv2d', 'Layer');
  576. this.register('XNNELU', 'Activation');
  577. this.register('XNNFullyConnected', 'Layer');
  578. this.register('XNNGelu', 'Activation');
  579. this.register('XNNGlobalAvgPooling2d', 'Pool');
  580. this.register('XNNGlobalAvgPooling2d', 'Pool');
  581. this.register('XNNHardswish', 'Activation');
  582. this.register('XNNLeakyReLU', 'Activation');
  583. this.register('XNNMaxPooling2d', 'Pool');
  584. this.register('XNNPReLU', 'Activation');
  585. this.register('XNNSigmoid', 'Activation');
  586. this.register('XNNSoftmax', 'Activation');
  587. this.register('XNNTanh', 'Activation');
  588. this.register('XNNStaticTranspose', 'Transform');
  589. }
  590. register(name, category) {
  591. this._types.set(name, { name, category });
  592. }
  593. type(name) {
  594. return this._types.get(name);
  595. }
  596. };
  597. xnnpack.TensorType = class {
  598. constructor(tensor) {
  599. xnnpack.TensorType._types = executorch.TensorType._types || [
  600. 'invalid', 'float32', 'float16',
  601. 'qint8', 'quint8', 'qint32',
  602. 'qcint8', 'qcint32', 'qcint4',
  603. 'qdint8', 'qbint4', 'qpint8',
  604. 'int32', 'pfp32', 'bfloat16'
  605. ];
  606. if (tensor.datatype >= xnnpack.TensorType._types.length) {
  607. throw new xnnpack.Error(`Unknown tensor data type '${tensor.datatype}'.`);
  608. }
  609. this.dataType = xnnpack.TensorType._types[tensor.datatype];
  610. this.shape = new xnnpack.TensorShape(Array.from(tensor.dims));
  611. }
  612. toString() {
  613. return this.dataType + this.shape.toString();
  614. }
  615. };
  616. xnnpack.TensorShape = class {
  617. constructor(dimensions = []) {
  618. this.dimensions = dimensions;
  619. }
  620. toString() {
  621. if (this.dimensions && this.dimensions.length > 0) {
  622. return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
  623. }
  624. return '';
  625. }
  626. };
  627. xnnpack.Tensor = class {
  628. constructor(tensor, reader) {
  629. this.type = new xnnpack.TensorType(tensor);
  630. this.values = reader.constant(tensor.constant_buffer_idx);
  631. this.encoding = '<';
  632. }
  633. };
  634. xnnpack.Error = class extends Error {
  635. constructor(message) {
  636. super(message);
  637. this.name = 'Error loading XNNPACK model.';
  638. }
  639. };
  640. vulkan.Reader = class {
  641. static open(data, target) {
  642. if (data.length >= 30) {
  643. const reader = base.BinaryReader.open(data);
  644. reader.skip(4);
  645. const magic = String.fromCharCode(...reader.read(4));
  646. if (magic === 'VH00') {
  647. return new vulkan.Reader(reader, target);
  648. }
  649. }
  650. return null;
  651. }
  652. constructor(reader, target) {
  653. this.reader = reader;
  654. this.target = target;
  655. reader.skip(2);
  656. this.flatbuffer = {
  657. offset: reader.uint32(),
  658. size: reader.uint32(),
  659. };
  660. this.constants = {
  661. offset: reader.uint32(),
  662. size: reader.uint32(),
  663. };
  664. }
  665. async read() {
  666. this.reader.seek(this.flatbuffer.offset);
  667. const metadata = new vulkan.Metadata(this.target.execution);
  668. metadata.register('conv_with_clamp(Tensor input, Tensor weight, Tensor? bias, SymInt[] stride, SymInt[] padding, SymInt[] dilation, bool transposed, SymInt[] output_padding, SymInt groups, Scalar? output_min, Scalar? output_max) -> Tensor)');
  669. const flatbuffers = await import('./flatbuffers.js');
  670. const data = this.reader.read(this.flatbuffer.size);
  671. const reader = flatbuffers.BinaryReader.open(data);
  672. if (!executorch.schema.vkgraph.VkGraph.identifier(reader)) {
  673. throw new xnnpack.Error('Invalid Vuklan data.');
  674. }
  675. this.graph = executorch.schema.vkgraph.VkGraph.create(reader);
  676. this.reader.seek(0);
  677. this.type = new vulkan.Graph(metadata, this.graph, this);
  678. }
  679. constant(id) {
  680. const constant = this.graph.constants[id];
  681. const offset = constant.offset;
  682. const length = constant.length;
  683. this.reader.seek(this.constants.offset + offset.toNumber());
  684. const data = this.reader.read(length.toNumber());
  685. this.reader.seek(0);
  686. return data;
  687. }
  688. };
  689. vulkan.Graph = class {
  690. constructor(metadata, graph, reader) {
  691. this.name = 'VulkanBackend';
  692. this.inputs = [];
  693. this.outputs = [];
  694. this.nodes = [];
  695. const values = new Map();
  696. values.map = (id) => {
  697. if (!values.has(id)) {
  698. const vkgraph = executorch.schema.vkgraph;
  699. const arg = graph.values[id].value;
  700. if (arg instanceof vkgraph.VkTensor) {
  701. const type = new vulkan.TensorType(arg);
  702. const initializer = arg.constant_id === -1 ? null : new vulkan.Tensor(arg, reader);
  703. const value = new vulkan.Value(id.toString(), type, initializer);
  704. values.set(id, { type: null, value: [value] });
  705. } else if (arg instanceof vkgraph.Int) {
  706. values.set(id, { type: 'int64', value: arg.int_val });
  707. } else if (arg instanceof vkgraph.IntList) {
  708. values.set(id, { type: 'int64[]', value: Array.from(arg.items) });
  709. } else if (arg instanceof vkgraph.Double) {
  710. values.set(id, { type: 'float64', value: arg.double_val });
  711. } else if (arg instanceof vkgraph.Bool) {
  712. values.set(id, { type: 'boolean', value: arg.bool_val });
  713. } else if (arg instanceof vkgraph.Null) {
  714. values.set(id, { type: 'attribute', value: null });
  715. } else {
  716. throw new Error(`Value type '${arg.constructor.name}' not implemented.`);
  717. }
  718. }
  719. return values.get(id);
  720. };
  721. for (let i = 0; i < graph.input_ids.length; i++) {
  722. const id = graph.input_ids[i];
  723. const value = values.map(id);
  724. const name = graph.input_ids.length === 1 ? 'input' : `input.${i}`;
  725. const argument = new vulkan.Argument(name, value.value, value.type);
  726. this.inputs.push(argument);
  727. }
  728. for (let i = 0; i < graph.output_ids.length; i++) {
  729. const id = graph.output_ids[i];
  730. const value = values.map(id);
  731. const name = graph.output_ids.length === 1 ? 'output' : `output.${i}`;
  732. const argument = new vulkan.Argument(name, value.value, value.type);
  733. this.outputs.push(argument);
  734. }
  735. for (const op of graph.chain) {
  736. const node = new vulkan.Node(metadata, op, values);
  737. this.nodes.push(node);
  738. }
  739. }
  740. };
  741. vulkan.Node = class {
  742. constructor(metadata, op, values) {
  743. const schema = metadata.type(op.name);
  744. if (!schema) {
  745. throw new vulkan.Error(`Operator schema for '${op.name}' not found.`);
  746. }
  747. this.type = {
  748. name: op.name.split(/\.([^.]*)$/)[0],
  749. identifier: op.name,
  750. category: schema.category || ''
  751. };
  752. this.name = op.node_id.toString();
  753. this.inputs = [];
  754. this.outputs = [];
  755. this.attributes = [];
  756. for (let i = 0; i < op.args.length; i++) {
  757. const arg = op.args[i];
  758. const input = schema && i < schema.arguments.length;
  759. const def = input ? schema.arguments[i] : schema.returns[i - schema.arguments.length];
  760. const value = values.map(arg);
  761. const argument = new vulkan.Argument(def.name || '', value.value, value.type);
  762. if (input) {
  763. this.inputs.push(argument);
  764. } else {
  765. this.outputs.push(argument);
  766. }
  767. }
  768. }
  769. };
  770. vulkan.Argument = class {
  771. constructor(name, value, type = null, visible = true) {
  772. this.name = name;
  773. this.value = value;
  774. this.type = type;
  775. this.visible = visible;
  776. }
  777. };
  778. vulkan.Value = class Value {
  779. constructor(name, type, initializer = null) {
  780. if (typeof name !== 'string') {
  781. throw new executorch.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
  782. }
  783. this.name = name;
  784. this.type = initializer && initializer.type ? initializer.type : type || null;
  785. this.initializer = initializer;
  786. }
  787. };
  788. vulkan.TensorType = class {
  789. constructor(tensor) {
  790. const types = ['bool', 'uint8', 'int8', 'int32', 'float16', 'float32', 'float64', 'int64'];
  791. if (tensor.datatype >= types.length) {
  792. throw new vulkan.Error(`Unknown tensor data type '${tensor.datatype}'.`);
  793. }
  794. this.dataType = types[tensor.datatype];
  795. this.shape = new vulkan.TensorShape(Array.from(tensor.dims));
  796. }
  797. toString() {
  798. return this.dataType + this.shape.toString();
  799. }
  800. };
  801. vulkan.TensorShape = class {
  802. constructor(dimensions = []) {
  803. this.dimensions = dimensions;
  804. }
  805. toString() {
  806. if (this.dimensions && this.dimensions.length > 0) {
  807. return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
  808. }
  809. return '';
  810. }
  811. };
  812. vulkan.Tensor = class {
  813. constructor(tensor, reader) {
  814. this.type = new vulkan.TensorType(tensor);
  815. this.values = reader.constant(tensor.constant_id);
  816. this.encoding = '<';
  817. }
  818. };
  819. vulkan.Metadata = class {
  820. constructor(execution) {
  821. this.execution = execution;
  822. }
  823. register(signature) {
  824. const torch = this.execution.__import__('torch');
  825. const registry = torch._C.getRegistry();
  826. const schema = torch.FunctionSchema.parse(signature);
  827. const op = new torch._C.Operator(schema);
  828. registry.registerOperator(op);
  829. }
  830. type(identifier) {
  831. identifier = identifier.split(/\.([^.]*)$/);
  832. const name = identifier[0].replace('.', '::');
  833. const overload = identifier[1] === 'default' ? '' : identifier[1];
  834. const torch = this.execution.__import__('torch');
  835. const schemas = torch._C._jit_get_schemas_for_operator(name);
  836. const schema = schemas.find((schema) => schema.name === name && schema.overload_name === overload);
  837. return schema;
  838. }
  839. };
  840. vulkan.Error = class extends Error {
  841. constructor(message) {
  842. super(message);
  843. this.name = 'Error loading Vulkan model.';
  844. }
  845. };
  846. coreml.Reader = class {
  847. static open(data, target) {
  848. const reader = base.BinaryReader.open(data);
  849. return new coreml.Reader(reader, target);
  850. }
  851. constructor(reader, target) {
  852. this.reader = reader;
  853. this.target = target;
  854. }
  855. async factory() {
  856. const coreml = await import('./coreml.js');
  857. return new coreml.ModelFactory();
  858. }
  859. async read() {
  860. const entries = this.entries(this.reader);
  861. const factory = await this.factory();
  862. const protobuf = await import('./protobuf.js');
  863. for (const [key, value] of entries) {
  864. const path = key.split('/');
  865. const identifier = path.pop();
  866. const folder = path.length === 0 ? '' : `${path.join('/')}/`;
  867. const locals = new Map(Array.from(entries).filter(([key]) => key.startsWith(folder)).map(([key, value]) => [key.substring(folder.length), value]));
  868. const context = new coreml.Context(this, identifier, value, locals, protobuf);
  869. /* eslint-disable no-await-in-loop */
  870. const type = await factory.match(context);
  871. /* eslint-enable no-await-in-loop */
  872. if (type === 'coreml.manifest') {
  873. /* eslint-disable no-await-in-loop */
  874. const model = await factory.open(context);
  875. /* eslint-enable no-await-in-loop */
  876. [this.type] = model.modules;
  877. this.type.name = 'CoreMLBackend';
  878. return;
  879. }
  880. }
  881. }
  882. stream(offset, size) {
  883. this.reader.seek(offset);
  884. const stream = this.reader.stream(size);
  885. this.reader.seek(0);
  886. return stream;
  887. }
  888. entries(reader) {
  889. const files = new Map();
  890. reader.seek(reader.length - 1);
  891. const str = [];
  892. let depth = 0;
  893. do {
  894. const c = String.fromCharCode(reader.byte());
  895. reader.skip(-2);
  896. if (c === '{') {
  897. depth++;
  898. } else if (c === '}') {
  899. depth--;
  900. }
  901. str.push(c);
  902. } while (depth > 0);
  903. const metadata = JSON.parse(str.join(''));
  904. const nodes = metadata.nodes;
  905. const roots = Array.from(nodes);
  906. for (const root of roots) {
  907. if (root !== null) {
  908. for (const index of Object.values(root.children)) {
  909. roots[index] = null;
  910. }
  911. }
  912. }
  913. const process = (path, node) => {
  914. path = path ? `${path}/${node.name}` : node.name;
  915. if (node.kind === 0) {
  916. files.set(path, node.dataRegion);
  917. } else if (node.kind === 1) {
  918. for (const index of Object.values(node.children)) {
  919. process(path, nodes[index]);
  920. }
  921. } else {
  922. throw new Error(`Node kind '${node.kind}' not implemented.`);
  923. }
  924. };
  925. for (const root of roots.filter((node) => node !== null)) {
  926. process('', root);
  927. }
  928. return files;
  929. }
  930. };
  931. coreml.Context = class {
  932. constructor(reader, identifier, location, entries, protobuf) {
  933. this._reader = reader;
  934. this._location = location;
  935. this._identifier = identifier;
  936. this._entries = entries;
  937. this._protobuf = protobuf;
  938. }
  939. get identifier() {
  940. return this._identifier;
  941. }
  942. get stream() {
  943. if (!this._stream) {
  944. this._stream = this._reader.stream(this._location.offset, this._location.size);
  945. }
  946. return this._stream;
  947. }
  948. async tags(type) {
  949. if (type === 'pb' && this.identifier.endsWith('.mlmodel')) {
  950. return new Map([[1,0],[2,2]]);
  951. }
  952. return new Map();
  953. }
  954. async peek(type) {
  955. if (type === 'json') {
  956. const data = this.stream.peek();
  957. const decoder = new TextDecoder('utf-8');
  958. const text = decoder.decode(data);
  959. return JSON.parse(text);
  960. }
  961. return null;
  962. }
  963. async read(type) {
  964. if (type === 'protobuf.binary') {
  965. return this._protobuf.BinaryReader.open(this.stream);
  966. }
  967. return null;
  968. }
  969. async fetch(file) {
  970. if (this._entries.has(file)) {
  971. const location = this._entries.get(file);
  972. const identifier = file.split('/').pop();
  973. return new coreml.Context(this._reader, identifier, location, this._entries, this._protobuf);
  974. }
  975. return null;
  976. }
  977. async require(id) {
  978. return this._reader.target.context.require(id);
  979. }
  980. async metadata(name) {
  981. return this._reader.target.context.metadata(name);
  982. }
  983. set(type, value) {
  984. this.type = type;
  985. this.value = value;
  986. return type;
  987. }
  988. };
  989. qnn.Reader = class {
  990. static open(data, target) {
  991. if (data.length >= 20) {
  992. const reader = base.BinaryReader.open(data);
  993. const magic = reader.uint32();
  994. if (magic === 0x5678ABCD) {
  995. return new qnn.Reader(reader, target);
  996. }
  997. }
  998. return null;
  999. }
  1000. constructor(reader, target) {
  1001. this.reader = reader;
  1002. this.target = target;
  1003. this.signature = reader.uint64();
  1004. this.size = reader.uint64();
  1005. }
  1006. async read() {
  1007. // https://github.com/pytorch/executorch/blob/main/backends/qualcomm/runtime/backends/QnnCustomProtocol.h
  1008. throw new executorch.Error('QNN backend not implemented.');
  1009. }
  1010. };
  1011. ethosu.Reader = class {
  1012. static open(data /* , target */) {
  1013. if (data.length >= 32) {
  1014. const reader = base.BinaryReader.open(data);
  1015. const magicBuffer = reader.read(16);
  1016. const magic = String.fromCharCode(...magicBuffer).replace(/\0/g, '');
  1017. if (magic === 'vela_bin_stream') {
  1018. return new ethosu.Reader(reader, data.length);
  1019. }
  1020. }
  1021. return null;
  1022. }
  1023. constructor(reader, size) {
  1024. this.reader = reader;
  1025. this.size = size;
  1026. }
  1027. async read() {
  1028. this.reader.seek(0);
  1029. const blocks = new Map();
  1030. while (this.reader.position < this.size) {
  1031. const nameBuffer = this.reader.read(16);
  1032. const name = String.fromCharCode(...nameBuffer).replace(/\0/g, '');
  1033. const size = this.reader.uint32();
  1034. this.reader.skip(12);
  1035. const data = this.reader.read(size);
  1036. blocks.set(name, data);
  1037. const padding = (16 - (size % 16)) % 16;
  1038. this.reader.skip(padding);
  1039. if (name === 'vela_end_stream') {
  1040. break;
  1041. }
  1042. }
  1043. const args = (data) => {
  1044. if (data && data.length >= 4) {
  1045. const reader = base.BinaryReader.open(data);
  1046. const count = reader.int32();
  1047. const arg = [];
  1048. for (let i = 0; i < count; i++) {
  1049. const shape = [];
  1050. for (let j = 0; j < 6; j++) {
  1051. shape.push(reader.int32());
  1052. }
  1053. const elem_size = reader.int32();
  1054. const offset = reader.int32();
  1055. const region = reader.int32();
  1056. arg.push({ shape, elem_size, offset, region });
  1057. }
  1058. return arg;
  1059. }
  1060. return [];
  1061. };
  1062. const inputs = args(blocks.get('inputs'));
  1063. const outputs = args(blocks.get('outputs'));
  1064. this.type = new ethosu.Graph(inputs, outputs);
  1065. }
  1066. };
  1067. ethosu.Graph = class {
  1068. constructor(inputs, outputs) {
  1069. this.name = 'EthosUBackend';
  1070. this.inputs = [];
  1071. this.outputs = [];
  1072. this.nodes = [];
  1073. for (let i = 0; i < inputs.length; i++) {
  1074. const input = inputs[i];
  1075. const type = new ethosu.TensorType(input);
  1076. const value = new ethosu.Value(i.toString(), type, null);
  1077. const name = inputs.length === 1 ? 'input' : `input.${i}`;
  1078. const argument = new ethosu.Argument(name, [value]);
  1079. this.inputs.push(argument);
  1080. }
  1081. for (let i = 0; i < outputs.length; i++) {
  1082. const output = outputs[i];
  1083. const type = new ethosu.TensorType(output);
  1084. const value = new ethosu.Value((inputs.length + i).toString(), type, null);
  1085. const name = outputs.length === 1 ? 'output' : `output.${i}`;
  1086. const argument = new ethosu.Argument(name, [value]);
  1087. this.outputs.push(argument);
  1088. }
  1089. }
  1090. };
  1091. ethosu.Argument = class {
  1092. constructor(name, value, type = null, visible = true) {
  1093. this.name = name;
  1094. this.value = value;
  1095. this.type = type;
  1096. this.visible = visible;
  1097. }
  1098. };
  1099. ethosu.Value = class Value {
  1100. constructor(name, type, initializer = null) {
  1101. if (typeof name !== 'string') {
  1102. throw new executorch.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
  1103. }
  1104. this.name = name;
  1105. this.type = initializer && initializer.type ? initializer.type : type || null;
  1106. this.initializer = initializer;
  1107. }
  1108. };
  1109. ethosu.TensorType = class {
  1110. constructor(io) {
  1111. switch (io.elem_size) {
  1112. case 1: this.dataType = 'int8'; break;
  1113. case 2: this.dataType = 'int16'; break;
  1114. case 4: this.dataType = 'int32'; break;
  1115. default: this.dataType = `?${io.elem_size}`; break;
  1116. }
  1117. const shape = io.shape.filter((dim, index) => dim !== 1 || index === io.shape.length - 1 || io.shape.slice(index).some((d) => d !== 1));
  1118. this.shape = new ethosu.TensorShape(shape.length > 0 ? shape : [1]);
  1119. }
  1120. toString() {
  1121. return this.dataType + this.shape.toString();
  1122. }
  1123. };
  1124. ethosu.TensorShape = class {
  1125. constructor(dimensions = []) {
  1126. this.dimensions = dimensions;
  1127. }
  1128. toString() {
  1129. if (this.dimensions && this.dimensions.length > 0) {
  1130. return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
  1131. }
  1132. return '';
  1133. }
  1134. };
  1135. ethosu.Error = class extends Error {
  1136. constructor(message) {
  1137. super(message);
  1138. this.name = 'Error loading Ethos-U model.';
  1139. }
  1140. };
  1141. export const ModelFactory = executorch.ModelFactory;