hailo.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. // Experimental
  2. const hailo = {};
  3. hailo.ModelFactory = class {
  4. async match(context) {
  5. const container = await hailo.Container.open(context);
  6. if (container) {
  7. return context.set(container.type, container);
  8. }
  9. return null;
  10. }
  11. filter(context, match) {
  12. if (context.type === 'hailo.metadata' && (match.type === 'hailo.configuration' || match.type === 'npz' || match.type === 'onnx.proto')) {
  13. return false;
  14. }
  15. if (context.type === 'hailo.configuration' && match.type === 'npz') {
  16. return false;
  17. }
  18. return true;
  19. }
  20. async open(context) {
  21. const metadata = await context.metadata('hailo-metadata.json');
  22. const target = context.value;
  23. await target.read();
  24. return new hailo.Model(metadata, target);
  25. }
  26. };
  27. hailo.Model = class {
  28. constructor(metadata, container) {
  29. const configuration = container.configuration;
  30. this.modules = [new hailo.Graph(metadata, configuration, container.weights)];
  31. this.name = configuration && configuration.name || "";
  32. this.format = container.format + (container.metadata && container.metadata.sdk_version ? ` v${container.metadata.sdk_version}` : '');
  33. this.metadata = [];
  34. if (container.metadata && container.metadata.state) {
  35. this.metadata.push(new hailo.Argument('state', container.metadata.state));
  36. }
  37. }
  38. };
  39. hailo.Graph = class {
  40. constructor(metadata, configuration, weights) {
  41. this.inputs = [];
  42. this.outputs = [];
  43. this.nodes = [];
  44. const values = new Map();
  45. values.map = (name, type, tensor) => {
  46. if (name.length === 0 && tensor) {
  47. return new hailo.Value(name, type || null, tensor);
  48. }
  49. if (!values.has(name)) {
  50. values.set(name, new hailo.Value(name, type || null, tensor || null));
  51. } else if (tensor) {
  52. throw new hailo.Error(`Duplicate value '${name}'.`);
  53. } else if (type && !type.equals(values.get(name).type)) {
  54. throw new hailo.Error(`Duplicate value '${name}'.`);
  55. }
  56. return values.get(name);
  57. };
  58. const layers = Object.entries(configuration.layers || {}).map(([name, value]) => {
  59. value.name = name;
  60. return value;
  61. });
  62. const inputs = new Set();
  63. for (const layer of layers) {
  64. switch (layer.type) {
  65. case 'input_layer': {
  66. for (let i = 0; i < layer.output.length; i++) {
  67. const shape = Array.isArray(layer.output_shapes) && layer.output_shapes.length > 0 ? layer.output_shapes[0] : null;
  68. const type = shape ? new hailo.TensorType('?', new hailo.TensorShape(shape)) : null;
  69. const output = layer.output[i];
  70. if (!inputs.has(output)) {
  71. const name = `${layer.name}\n${output}`;
  72. const argument = new hailo.Argument('input', [values.map(name, type)]);
  73. this.inputs.push(argument);
  74. inputs.add(output);
  75. }
  76. }
  77. break;
  78. }
  79. case 'output_layer': {
  80. for (let i = 0; i < layer.input.length; i++) {
  81. const shape = Array.isArray(layer.input_shapes) && layer.input_shapes.length > 0 ? layer.input_shapes[i] : null;
  82. const type = shape ? new hailo.TensorType('?', new hailo.TensorShape(shape)) : null;
  83. const input = layer.input[i];
  84. const name = `${input}\n${layer.name}`;
  85. const argument = new hailo.Argument('output', [values.map(name, type)]);
  86. this.outputs.push(argument);
  87. }
  88. break;
  89. }
  90. default: {
  91. const node = new hailo.Node(metadata, layer, values, weights.get(layer.name));
  92. this.nodes.push(node);
  93. break;
  94. }
  95. }
  96. }
  97. }
  98. };
  99. hailo.Argument = class {
  100. constructor(name, value, type = null, visible = true) {
  101. this.name = name;
  102. this.value = value;
  103. this.type = type;
  104. this.visible = visible;
  105. }
  106. };
  107. hailo.Value = class {
  108. constructor(name, type, initializer) {
  109. if (typeof name !== 'string') {
  110. throw new hailo.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
  111. }
  112. this.name = name;
  113. this.type = initializer ? initializer.type : type;
  114. this.initializer = initializer;
  115. }
  116. };
  117. hailo.Node = class {
  118. constructor(metadata, layer, values, weights) {
  119. weights = weights || new Map();
  120. this.name = layer.name || '';
  121. this.type = metadata.type(layer.type);
  122. if (layer.type === 'activation') {
  123. const name = layer.params.activation || layer.name || '';
  124. this.type = { ...this.type, name };
  125. }
  126. this.inputs = layer.input.map((name, index) => {
  127. const shape = layer.input_shapes ? layer.input_shapes[index] : null;
  128. const type = shape ? new hailo.TensorType('?', new hailo.TensorShape(shape)) : null;
  129. name = `${name}\n${layer.name}`;
  130. return new hailo.Argument("input", [values.map(name, type, null)]);
  131. });
  132. const layer_params = layer.params ? Object.entries(layer.params) : [];
  133. const params_list = layer_params.reduce((acc, [name, value]) => {
  134. const schema = metadata.attribute(layer.type, name) || {};
  135. if (schema.visible) {
  136. const label = schema.label ? schema.label : name;
  137. if (!weights.has(label)) {
  138. const array = weights.get(label);
  139. const tensor = new hailo.Tensor(array, value);
  140. acc.push(new hailo.Argument(label, [values.map('', tensor.type, tensor)]));
  141. }
  142. }
  143. return acc;
  144. }, []);
  145. const params_from_npz = Array.from(weights).filter(([, value]) => value).map(([name, value]) => {
  146. const tensor = new hailo.Tensor(value);
  147. return new hailo.Argument(name, [values.map('', tensor.type, tensor)]);
  148. });
  149. this.inputs = this.inputs.concat(params_list).concat(params_from_npz);
  150. this.outputs = (layer.output || []).map((name, index) => {
  151. const shape = layer.output_shapes ? layer.output_shapes[index] : null;
  152. const type = shape ? new hailo.TensorType('?', new hailo.TensorShape(shape)) : null;
  153. name = `${layer.name}\n${name}`;
  154. return new hailo.Argument("output", [values.map(name, type, null)]);
  155. });
  156. this.attributes = [];
  157. const attrs = Object.assign(layer.params || {}, { original_names: layer.original_names || [] });
  158. for (const [name, value] of Object.entries(attrs)) {
  159. const schema = metadata.attribute(layer.type, name);
  160. const type = schema && schema.type ? schema.type : '';
  161. const visible = name === 'original_names' || (schema && schema.visible === false) ? false : true;
  162. const attribute = new hailo.Argument(name, value, type, visible);
  163. this.attributes.push(attribute);
  164. }
  165. this.chain = [];
  166. if (layer && layer.params && layer.params.activation && layer.params.activation !== 'linear' && layer.type !== 'activation') {
  167. const activation = {
  168. type: layer.params.activation,
  169. name: layer.params.activation,
  170. input: [],
  171. output: []
  172. };
  173. const node = new hailo.Node(metadata, activation, values.map);
  174. this.chain.push(node);
  175. }
  176. }
  177. };
  178. hailo.Tensor = class {
  179. constructor(array, shape) {
  180. const dataType = array && array.dtype ? array.dtype.__name__ : '?';
  181. shape = array && array.shape ? array.shape : shape;
  182. this.type = new hailo.TensorType(dataType, new hailo.TensorShape(shape));
  183. if (array) {
  184. this.stride = array.strides.map((stride) => stride / array.itemsize);
  185. this.layout = this.type.dataType === 'string' || this.type.dataType === 'object' ? '|' : array.dtype.byteorder;
  186. this.values = this.type.dataType === 'string' || this.type.dataType === 'object' || this.type.dataType === 'void' ? array.tolist() : array.tobytes();
  187. }
  188. }
  189. };
  190. hailo.TensorType = class {
  191. constructor(dataType, shape) {
  192. this.dataType = dataType;
  193. this.shape = shape;
  194. }
  195. equals(obj) {
  196. return obj && this.dataType === obj.dataType && this.shape && this.shape.equals(obj.shape);
  197. }
  198. toString() {
  199. return (this.dataType || '?') + this.shape.toString();
  200. }
  201. };
  202. hailo.TensorShape = class {
  203. constructor(dimensions) {
  204. this.dimensions = dimensions;
  205. }
  206. equals(obj) {
  207. if (obj && Array.isArray(obj.dimensions) && Array.isArray(this.dimensions)) {
  208. if (this.dimensions.length === obj.dimensions.length) {
  209. return obj.dimensions.every((value, index) => this.dimensions[index] === value);
  210. }
  211. const a = this.dimensions.filter((value, index) => index === 0 || index === this.dimensions.length - 1 || value !== 1);
  212. const b = obj.dimensions.filter((value, index) => index === 0 || index === obj.dimensions.length - 1 || value !== 1);
  213. if (a.length === b.length) {
  214. return a.every((value, index) => b[index] === value);
  215. }
  216. }
  217. return false;
  218. }
  219. toString() {
  220. if (this.dimensions && this.dimensions.length > 0) {
  221. return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
  222. }
  223. return '';
  224. }
  225. };
  226. hailo.Container = class {
  227. static async open(context) {
  228. const identifier = context.identifier;
  229. const basename = identifier.split('.');
  230. basename.pop();
  231. if (identifier.toLowerCase().endsWith('.hn')) {
  232. if (basename.length > 1 && (basename[basename.length - 1] === 'native' || basename[basename.length - 1] === 'fp')) {
  233. basename.pop();
  234. }
  235. const configuration = await context.peek('json');
  236. if (configuration && configuration.name && configuration.net_params && configuration.layers) {
  237. return new hailo.Container(context, 'hailo.configuration', basename.join('.'), configuration, null);
  238. }
  239. } else if (identifier.toLowerCase().endsWith('.metadata.json')) {
  240. basename.pop();
  241. const metadata = await context.peek('json');
  242. if (metadata && metadata.state && metadata.hn) {
  243. return new hailo.Container(context, 'hailo.metadata', basename.join('.'), null, metadata);
  244. }
  245. }
  246. return null;
  247. }
  248. constructor(context, type, basename, configuration, metadata) {
  249. this.type = type;
  250. this.context = context;
  251. this.basename = basename;
  252. this.configuration = configuration;
  253. this.metadata = metadata;
  254. }
  255. async _request(name, type) {
  256. try {
  257. const content = await this.context.fetch(name);
  258. if (content) {
  259. return await content.read(type);
  260. }
  261. } catch {
  262. // continue regardless of error
  263. }
  264. return null;
  265. }
  266. async read() {
  267. this.format = 'Hailo NN';
  268. this.weights = new Map();
  269. if (!this.metadata) {
  270. this.metadata = await this._request(`${this.basename}.metadata.json`, 'json');
  271. }
  272. if (this.metadata) {
  273. this.format = 'Hailo Archive';
  274. this.configuration = await this._request(this.metadata.hn, 'json');
  275. if (!this.configuration) {
  276. throw new hailo.Error("Archive does not contain '.nn' configuration.");
  277. }
  278. let extension = '';
  279. switch (this.metadata.state) {
  280. case 'fp_optimized_model': extension = '.fpo.npz'; break;
  281. case 'quantized_model': extension = '.q.npz'; break;
  282. case 'compiled_model': extension = '.q.npz'; break;
  283. default: extension = '.npz'; break;
  284. }
  285. const entries = await this._request(this.basename + extension, 'npz');
  286. if (entries && entries.size > 0) {
  287. const inputs = new Set([
  288. 'kernel', 'bias',
  289. 'input_activation_bits', 'output_activation_bits',
  290. 'weight_bits', 'bias_decomposition'
  291. ]);
  292. for (const [name, value] of entries) {
  293. const key = name.split('.').slice(0, -1).join('.');
  294. const match = key.match(/.*?(?=:[0-9])/);
  295. if (match) {
  296. const path = match[0].split('/');
  297. if (inputs.has(path[2])) {
  298. const layer = `${path[0]}/${path[1]}`;
  299. if (!this.weights.has(layer)) {
  300. this.weights.set(layer, new Map());
  301. }
  302. const weights = this.weights.get(layer);
  303. weights.set(path[2], value);
  304. }
  305. }
  306. }
  307. }
  308. }
  309. delete this.context;
  310. delete this.basename;
  311. }
  312. };
  313. hailo.Error = class extends Error {
  314. constructor(message) {
  315. super(message);
  316. this.name = 'Error loading Hailo model.';
  317. }
  318. };
  319. export const ModelFactory = hailo.ModelFactory;