executorch.js 47 KB

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